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 208571 12ec3e08ee67e43d5bb902db23c3c1f21c39c703
parent 208570 86be5b7145ca21861ca8706131a96f94c22d741e
child 208572 a2f9887819628328433cfb887ed9543d309feb43
push id1
push userroot
push dateMon, 20 Oct 2014 17:29:22 +0000
reviewerssnorp
bugs1042291
milestone35.0a1
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)