Bug 1042291 - Implement a better heuristic for when to use HW accelerated <canvas> r=snorp
--- a/dom/canvas/CanvasRenderingContext2D.cpp
+++ b/dom/canvas/CanvasRenderingContext2D.cpp
@@ -719,16 +719,19 @@ public:
}
static void DidTransactionCallback(void* aData)
{
CanvasRenderingContext2DUserData* self =
static_cast<CanvasRenderingContext2DUserData*>(aData);
if (self->mContext) {
self->mContext->MarkContextClean();
+ if (self->mContext->mDrawObserver) {
+ self->mContext->mDrawObserver->FrameEnd();
+ }
}
}
bool IsForContext(CanvasRenderingContext2D *aContext)
{
return mContext == aContext;
}
void Forget()
{
@@ -821,28 +824,30 @@ DrawTarget* CanvasRenderingContext2D::sE
CanvasRenderingContext2D::CanvasRenderingContext2D()
: mRenderingMode(RenderingMode::OpenGLBackendMode)
// these are the default values from the Canvas spec
, mWidth(0), mHeight(0)
, mZero(false), mOpaque(false)
, mResetLayer(true)
, mIPC(false)
, mStream(nullptr)
+ , mDrawObserver(nullptr)
, mIsEntireFrameInvalid(false)
, mPredictManyRedrawCalls(false), mPathTransformWillUpdate(false)
, mInvalidateCount(0)
{
sNumLivingContexts++;
SetIsDOMBinding();
// The default is to use OpenGL mode
if (!gfxPlatform::GetPlatform()->UseAcceleratedSkiaCanvas()) {
mRenderingMode = RenderingMode::SoftwareBackendMode;
}
+ mDrawObserver = new CanvasDrawObserver(this);
}
CanvasRenderingContext2D::~CanvasRenderingContext2D()
{
RemovePostRefreshObserver();
Reset();
// Drop references from all CanvasRenderingContext2DUserData to this context
for (uint32_t i = 0; i < mUserDatas.Length(); ++i) {
@@ -3811,16 +3816,20 @@ bool CanvasRenderingContext2D::IsPointIn
void
CanvasRenderingContext2D::DrawImage(const HTMLImageOrCanvasOrVideoElement& image,
double sx, double sy, double sw,
double sh, double dx, double dy,
double dw, double dh,
uint8_t optional_argc,
ErrorResult& error)
{
+ if (mDrawObserver) {
+ mDrawObserver->DidDrawCall(CanvasDrawObserver::DrawCallType::DrawImage);
+ }
+
MOZ_ASSERT(optional_argc == 0 || optional_argc == 2 || optional_argc == 6);
RefPtr<SourceSurface> srcSurf;
gfxIntSize imgSize;
Element* element;
EnsureTarget();
@@ -4327,16 +4336,20 @@ CanvasRenderingContext2D::AsyncDrawXULEl
// device pixel getting/setting
//
already_AddRefed<ImageData>
CanvasRenderingContext2D::GetImageData(JSContext* aCx, double aSx,
double aSy, double aSw,
double aSh, ErrorResult& error)
{
+ if (mDrawObserver) {
+ mDrawObserver->DidDrawCall(CanvasDrawObserver::DrawCallType::GetImageData);
+ }
+
EnsureTarget();
if (!IsTargetValid()) {
error.Throw(NS_ERROR_FAILURE);
return nullptr;
}
if (!mCanvasElement && !mDocShell) {
NS_ERROR("No canvas element and no docshell in GetImageData!!!");
@@ -4407,16 +4420,20 @@ CanvasRenderingContext2D::GetImageData(J
nsresult
CanvasRenderingContext2D::GetImageDataArray(JSContext* aCx,
int32_t aX,
int32_t aY,
uint32_t aWidth,
uint32_t aHeight,
JSObject** aRetval)
{
+ if (mDrawObserver) {
+ mDrawObserver->DidDrawCall(CanvasDrawObserver::DrawCallType::GetImageData);
+ }
+
MOZ_ASSERT(aWidth && aHeight);
CheckedInt<uint32_t> len = CheckedInt<uint32_t>(aWidth) * aHeight * 4;
if (!len.isValid()) {
return NS_ERROR_DOM_INDEX_SIZE_ERR;
}
CheckedInt<int32_t> rightMost = CheckedInt<int32_t>(aX) + aWidth;
@@ -4580,16 +4597,20 @@ CanvasRenderingContext2D::PutImageData(I
// void putImageData (in ImageData d, in double x, in double y, in double dirtyX, in double dirtyY, in double dirtyWidth, in double dirtyHeight);
nsresult
CanvasRenderingContext2D::PutImageData_explicit(int32_t x, int32_t y, uint32_t w, uint32_t h,
dom::Uint8ClampedArray* aArray,
bool hasDirtyRect, int32_t dirtyX, int32_t dirtyY,
int32_t dirtyWidth, int32_t dirtyHeight)
{
+ if (mDrawObserver) {
+ mDrawObserver->DidDrawCall(CanvasDrawObserver::DrawCallType::PutImageData);
+ }
+
if (w == 0 || h == 0) {
return NS_ERROR_DOM_INVALID_STATE_ERR;
}
IntRect dirtyRect;
IntRect imageDataRect(0, 0, w, h);
if (hasDirtyRect) {
--- a/dom/canvas/CanvasRenderingContext2D.h
+++ b/dom/canvas/CanvasRenderingContext2D.h
@@ -16,16 +16,17 @@
#include "CanvasUtils.h"
#include "gfxTextRun.h"
#include "mozilla/ErrorResult.h"
#include "mozilla/dom/CanvasGradient.h"
#include "mozilla/dom/CanvasRenderingContext2DBinding.h"
#include "mozilla/dom/CanvasPattern.h"
#include "mozilla/gfx/Rect.h"
#include "mozilla/gfx/2D.h"
+#include "mozilla/TimeStamp.h"
#include "gfx2DGlue.h"
#include "imgIEncoder.h"
#include "nsLayoutUtils.h"
#include "mozilla/EnumeratedArray.h"
#include "FilterSupport.h"
#include "nsSVGEffects.h"
class nsGlobalWindow;
@@ -110,16 +111,17 @@ private:
mutable RefPtr<gfx::Path> mPath;
mutable RefPtr<gfx::PathBuilder> mPathBuilder;
void EnsurePathBuilder() const;
};
struct CanvasBidiProcessor;
class CanvasRenderingContext2DUserData;
+class CanvasDrawObserver;
/**
** CanvasRenderingContext2D
**/
class CanvasRenderingContext2D MOZ_FINAL :
public nsICanvasRenderingContextInternal,
public nsWrapperCache
{
@@ -762,16 +764,21 @@ protected:
// This is created lazily so it is necessary to call EnsureTarget before
// accessing it. In the event of an error it will be equal to
// sErrorTarget.
mozilla::RefPtr<mozilla::gfx::DrawTarget> mTarget;
RefPtr<gl::SurfaceStream> mStream;
+ // This observes our draw calls at the beginning of the canvas
+ // lifetime and switches to software or GPU mode depending on
+ // what it thinks is best
+ CanvasDrawObserver *mDrawObserver;
+
/**
* Flag to avoid duplicate calls to InvalidateFrame. Set to true whenever
* Redraw is called, reset to false when Render is called.
*/
bool mIsEntireFrameInvalid;
/**
* When this is set, the first call to Redraw(gfxRect) should set
* mIsEntireFrameInvalid since we expect it will be followed by
@@ -1076,16 +1083,83 @@ protected:
FINISH:
if (perDevPixel)
*perDevPixel = devPixel;
if (perCSSPixel)
*perCSSPixel = cssPixel;
}
friend struct CanvasBidiProcessor;
+ friend class CanvasDrawObserver;
+};
+
+class CanvasDrawObserver
+{
+public:
+ CanvasDrawObserver(CanvasRenderingContext2D* aCanvasContext)
+ : mCanvasContext(aCanvasContext)
+ , mDisabled(false)
+ , mSoftwarePreferredCalls(0)
+ , mGPUPreferredCalls(0)
+ , mFramesRendered(0)
+ , mCreationTime(TimeStamp::NowLoRes())
+ {}
+
+ // Only enumerate draw calls that could affect the heuristic
+ enum DrawCallType {
+ PutImageData,
+ GetImageData,
+ DrawImage
+ };
+
+ void DidDrawCall(DrawCallType aType) {
+ if (mDisabled) {
+ return;
+ }
+
+ switch (aType) {
+ case PutImageData:
+ case GetImageData:
+ mSoftwarePreferredCalls++;
+ break;
+ case DrawImage:
+ mGPUPreferredCalls++;
+ break;
+ }
+ }
+
+ void FrameEnd() {
+ if (mDisabled) {
+ return;
+ }
+
+ mFramesRendered++;
+
+ TimeDuration timeElapsed = TimeStamp::NowLoRes() - mCreationTime;
+
+ // We log the first 30 frames of any canvas object then make a
+ // call to determine whether it should be GPU or CPU backed
+ if (mFramesRendered >= 30 || timeElapsed.ToSeconds() >= 5.0) {
+ if (mGPUPreferredCalls >= mSoftwarePreferredCalls) {
+ mCanvasContext->SwitchRenderingMode(CanvasRenderingContext2D::RenderingMode::OpenGLBackendMode);
+ } else {
+ mCanvasContext->SwitchRenderingMode(CanvasRenderingContext2D::RenderingMode::SoftwareBackendMode);
+ }
+
+ mDisabled = true;
+ }
+ }
+
+private:
+ CanvasRenderingContext2D* mCanvasContext;
+ bool mDisabled;
+ unsigned int mSoftwarePreferredCalls;
+ unsigned int mGPUPreferredCalls;
+ unsigned int mFramesRendered;
+ TimeStamp mCreationTime;
};
MOZ_FINISH_NESTED_ENUM_CLASS(CanvasRenderingContext2D::CanvasMultiGetterType)
MOZ_FINISH_NESTED_ENUM_CLASS(CanvasRenderingContext2D::Style)
MOZ_FINISH_NESTED_ENUM_CLASS(CanvasRenderingContext2D::TextAlign)
MOZ_FINISH_NESTED_ENUM_CLASS(CanvasRenderingContext2D::TextBaseline)
MOZ_FINISH_NESTED_ENUM_CLASS(CanvasRenderingContext2D::TextDrawOperation)