Bug 1294351 - Avoid redcording/replaying canvas clips whenever possible. r=Bas, a=rkothari
authorNicolas Silva <nsilva@mozilla.com>
Wed, 07 Sep 2016 11:19:28 +0200
changeset 348054 3cad1bde8677
parent 348053 d7b3e0350c05
child 348055 54f5d11ad7a5
push id6389
push userraliiev@mozilla.com
push dateMon, 19 Sep 2016 13:38:22 +0000
treeherdermozilla-beta@01d67bfe6c81 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersBas, rkothari
bugs1294351
milestone50.0a2
Bug 1294351 - Avoid redcording/replaying canvas clips whenever possible. r=Bas, a=rkothari
dom/canvas/CanvasRenderingContext2D.cpp
gfx/layers/PersistentBufferProvider.h
--- a/dom/canvas/CanvasRenderingContext2D.cpp
+++ b/dom/canvas/CanvasRenderingContext2D.cpp
@@ -1564,23 +1564,38 @@ CanvasRenderingContext2D::EnsureTarget(c
     gfx::Rect rect(0, 0, mWidth, mHeight);
     if (aCoveredRect && CurrentState().transform.TransformBounds(*aCoveredRect).Contains(rect)) {
       mTarget = mBufferProvider->BorrowDrawTarget(IntRect());
     } else {
       mTarget = mBufferProvider->BorrowDrawTarget(IntRect(0, 0, mWidth, mHeight));
     }
 
     if (mTarget) {
-      // Restore clip and transform.
-      for (const auto& style : mStyleStack) {
-        for (const auto& clipOrTransform : style.clipsAndTransforms) {
-          if (clipOrTransform.IsClip()) {
-            mTarget->PushClip(clipOrTransform.clip);
-          } else {
-            mTarget->SetTransform(clipOrTransform.transform);
+      if (!mBufferProvider->PreservesDrawingState()) {
+        // Restore clips and transform.
+        mTarget->SetTransform(Matrix());
+
+        if (mTarget->GetBackendType() == gfx::BackendType::CAIRO) {
+          // Cairo doesn't play well with huge clips. When given a very big clip it
+          // will try to allocate big mask surface without taking the target
+          // size into account which can cause OOM. See bug 1034593.
+          // This limits the clip extents to the size of the canvas.
+          // A fix in Cairo would probably be preferable, but requires somewhat
+          // invasive changes.
+          mTarget->PushClipRect(rect);
+        }
+
+        // Restore clip and transform.
+        for (const auto& style : mStyleStack) {
+          for (const auto& clipOrTransform : style.clipsAndTransforms) {
+            if (clipOrTransform.IsClip()) {
+              mTarget->PushClip(clipOrTransform.clip);
+            } else {
+              mTarget->SetTransform(clipOrTransform.transform);
+            }
           }
         }
       }
       return mRenderingMode;
     } else {
       mBufferProvider = nullptr;
     }
   }
@@ -1760,22 +1775,29 @@ CanvasRenderingContext2D::ClearTarget()
   }
 }
 
 void
 CanvasRenderingContext2D::ReturnTarget()
 {
   if (mTarget && mBufferProvider && mTarget != sErrorTarget) {
     CurrentState().transform = mTarget->GetTransform();
-    for (const auto& style : mStyleStack) {
-      for (const auto& clipOrTransform : style.clipsAndTransforms) {
-        if (clipOrTransform.IsClip()) {
-          mTarget->PopClip();
+    if (!mBufferProvider->PreservesDrawingState()) {
+      for (const auto& style : mStyleStack) {
+        for (const auto& clipOrTransform : style.clipsAndTransforms) {
+          if (clipOrTransform.IsClip()) {
+            mTarget->PopClip();
+          }
         }
       }
+      if (mTarget->GetBackendType() == gfx::BackendType::CAIRO) {
+        // With the cairo backend we pushed an extra clip rect which we have to
+        // balance out here. See the comment in EnsureDrawTarget.
+        mTarget->PopClip();
+      }
     }
     mBufferProvider->ReturnDrawTarget(mTarget.forget());
   }
 }
 
 NS_IMETHODIMP
 CanvasRenderingContext2D::InitializeWithDrawTarget(nsIDocShell* aShell,
                                                    gfx::DrawTarget* aTarget)
--- a/gfx/layers/PersistentBufferProvider.h
+++ b/gfx/layers/PersistentBufferProvider.h
@@ -60,16 +60,24 @@ public:
 
   virtual void ReturnSnapshot(already_AddRefed<gfx::SourceSurface> aSnapshot) = 0;
 
   virtual TextureClient* GetTextureClient() { return nullptr; }
 
   virtual void OnShutdown() {}
 
   virtual bool SetForwarder(CompositableForwarder* aFwd) { return true; }
+
+  /**
+   * Return true if this provider preserves the drawing state (clips, transforms,
+   * etc.) across frames. In practice this means users of the provider can skip
+   * popping all of the clips at the end of the frames and pushing them back at
+   * the beginning of the following frames, which can be costly (cf. bug 1294351).
+   */
+  virtual bool PreservesDrawingState() const = 0;
 };
 
 
 class PersistentBufferProviderBasic : public PersistentBufferProvider
 {
 public:
   MOZ_DECLARE_REFCOUNTED_VIRTUAL_TYPENAME(PersistentBufferProviderBasic, override)
 
@@ -83,16 +91,17 @@ public:
   virtual already_AddRefed<gfx::DrawTarget> BorrowDrawTarget(const gfx::IntRect& aPersistedRect) override;
 
   virtual bool ReturnDrawTarget(already_AddRefed<gfx::DrawTarget> aDT) override;
 
   virtual already_AddRefed<gfx::SourceSurface> BorrowSnapshot() override;
 
   virtual void ReturnSnapshot(already_AddRefed<gfx::SourceSurface> aSnapshot) override;
 
+  virtual bool PreservesDrawingState() const override { return true; }
 private:
   ~PersistentBufferProviderBasic();
 
   RefPtr<gfx::DrawTarget> mDrawTarget;
   RefPtr<gfx::SourceSurface> mSnapshot;
 };
 
 
@@ -123,16 +132,17 @@ public:
   virtual TextureClient* GetTextureClient() override;
 
   virtual void NotifyInactive() override;
 
   virtual void OnShutdown() override { Destroy(); }
 
   virtual bool SetForwarder(CompositableForwarder* aFwd) override;
 
+  virtual bool PreservesDrawingState() const override { return false; }
 protected:
   PersistentBufferProviderShared(gfx::IntSize aSize, gfx::SurfaceFormat aFormat,
                                  CompositableForwarder* aFwd,
                                  RefPtr<TextureClient>& aTexture);
 
   ~PersistentBufferProviderShared();
 
   TextureClient* GetTexture(Maybe<uint32_t> aIndex);