Bug 1042291 - Implement a better heuristic for when to use HW accelerated <canvas> r=snorp
☠☠ backed out by 041d2343dce5 ☠ ☠
authorGeorge Wright <george@mozilla.com>
Wed, 10 Sep 2014 16:15:43 -0400
changeset 231786 12ec3e08ee67e43d5bb902db23c3c1f21c39c703
parent 231785 86be5b7145ca21861ca8706131a96f94c22d741e
child 231787 a2f9887819628328433cfb887ed9543d309feb43
push id4187
push userbhearsum@mozilla.com
push dateFri, 28 Nov 2014 15:29:12 +0000
treeherdermozilla-beta@f23cc6a30c11 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerssnorp
bugs1042291
milestone35.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1042291 - Implement a better heuristic for when to use HW accelerated <canvas> r=snorp
dom/canvas/CanvasRenderingContext2D.cpp
dom/canvas/CanvasRenderingContext2D.h
--- 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)