Bug 1289816 - Simplify CopyableCanvasLayer::UpdateTarget and remove unnecessary copies. r=jnicol
authorNicolas Silva <nsilva@mozilla.com>
Fri, 29 Jul 2016 20:19:28 +0200
changeset 349457 e5db12322fd393fe7970e726cd1f4b64845f6d23
parent 349456 e46e53dfb22bc320ffbf5b16269256329bb949e0
child 349458 6b695df8579dc41bb951111b56e75a92a9f4c8e9
push id1230
push userjlund@mozilla.com
push dateMon, 31 Oct 2016 18:13:35 +0000
treeherdermozilla-release@5e06e3766db2 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjnicol
bugs1289816
milestone50.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 1289816 - Simplify CopyableCanvasLayer::UpdateTarget and remove unnecessary copies. r=jnicol
dom/canvas/CanvasRenderingContext2D.cpp
gfx/layers/CopyableCanvasLayer.cpp
gfx/layers/CopyableCanvasLayer.h
gfx/layers/basic/BasicCanvasLayer.cpp
gfx/layers/basic/BasicCanvasLayer.h
gfx/layers/client/CanvasClient.cpp
gfx/layers/client/ClientCanvasLayer.cpp
gfx/layers/client/ClientCanvasLayer.h
--- a/dom/canvas/CanvasRenderingContext2D.cpp
+++ b/dom/canvas/CanvasRenderingContext2D.cpp
@@ -1332,53 +1332,64 @@ bool CanvasRenderingContext2D::SwitchRen
       !gfxPlatform::GetPlatform()->UseAcceleratedCanvas()) {
       return false;
   }
 #endif
 
   RefPtr<SourceSurface> snapshot;
   Matrix transform;
   RefPtr<PersistentBufferProvider> oldBufferProvider = mBufferProvider;
+  RefPtr<DrawTarget> oldTarget = mTarget;
+
   AutoReturnSnapshot autoReturn(nullptr);
 
   if (mTarget) {
     snapshot = mTarget->Snapshot();
     transform = mTarget->GetTransform();
   } else {
     MOZ_ASSERT(mBufferProvider);
     // When mBufferProvider is true but we have no mTarget, our current state's
     // transform is always valid. See ReturnTarget().
     transform = CurrentState().transform;
     snapshot = mBufferProvider->BorrowSnapshot();
     autoReturn.mBufferProvider = mBufferProvider;
     autoReturn.mSnapshot = &snapshot;
   }
+
   mTarget = nullptr;
   mBufferProvider = nullptr;
   mResetLayer = true;
 
   // Recreate target using the new rendering mode
   RenderingMode attemptedMode = EnsureTarget(nullptr, aRenderingMode);
-  if (!IsTargetValid())
+  if (!IsTargetValid()) {
+    if (oldBufferProvider && oldTarget) {
+      oldBufferProvider->ReturnDrawTarget(oldTarget.forget());
+    }
     return false;
+  }
 
   // We succeeded, so update mRenderingMode to reflect reality
   mRenderingMode = attemptedMode;
 
   // Restore the content from the old DrawTarget
   gfx::Rect r(0, 0, mWidth, mHeight);
   mTarget->DrawSurface(snapshot, r, r);
 
   // Restore the clips and transform
   for (uint32_t i = 0; i < CurrentState().clipsPushed.Length(); i++) {
     mTarget->PushClip(CurrentState().clipsPushed[i]);
   }
 
   mTarget->SetTransform(transform);
 
+  if (oldBufferProvider && oldTarget) {
+    oldBufferProvider->ReturnDrawTarget(oldTarget.forget());
+  }
+
   return true;
 }
 
 void CanvasRenderingContext2D::Demote()
 {
   if (SwitchRenderingMode(RenderingMode::SoftwareBackendMode)) {
     RemoveDemotableContext(this);
   }
--- a/gfx/layers/CopyableCanvasLayer.cpp
+++ b/gfx/layers/CopyableCanvasLayer.cpp
@@ -45,18 +45,16 @@ CopyableCanvasLayer::CopyableCanvasLayer
 CopyableCanvasLayer::~CopyableCanvasLayer()
 {
   MOZ_COUNT_DTOR(CopyableCanvasLayer);
 }
 
 void
 CopyableCanvasLayer::Initialize(const Data& aData)
 {
-  NS_ASSERTION(mSurface == nullptr, "BasicCanvasLayer::Initialize called twice!");
-
   if (aData.mGLContext) {
     mGLContext = aData.mGLContext;
     mIsAlphaPremultiplied = aData.mIsGLAlphaPremult;
     mOriginPos = gl::OriginPos::BottomLeft;
     mIsMirror = aData.mIsMirror;
 
     MOZ_ASSERT(mGLContext->IsOffscreen(), "canvas gl context isn't offscreen");
 
@@ -67,150 +65,38 @@ CopyableCanvasLayer::Initialize(const Da
       mBufferProvider = aData.mBufferProvider;
     }
   } else if (aData.mBufferProvider) {
     mBufferProvider = aData.mBufferProvider;
   } else if (aData.mRenderer) {
     mAsyncRenderer = aData.mRenderer;
     mOriginPos = gl::OriginPos::BottomLeft;
   } else {
-    MOZ_CRASH("GFX: CanvasLayer created without mSurface, mDrawTarget or mGLContext?");
+    MOZ_CRASH("GFX: CanvasLayer created without BufferProvider, DrawTarget or GLContext?");
   }
 
   mBounds.SetRect(0, 0, aData.mSize.width, aData.mSize.height);
 }
 
 bool
 CopyableCanvasLayer::IsDataValid(const Data& aData)
 {
   return mGLContext == aData.mGLContext;
 }
 
-void
-CopyableCanvasLayer::UpdateTarget(DrawTarget* aDestTarget)
-{
-  AutoReturnSnapshot autoReturn;
-
-  if (mAsyncRenderer) {
-    mSurface = mAsyncRenderer->GetSurface();
-  } else if (!mGLFrontbuffer && mBufferProvider) {
-    mSurface = mBufferProvider->BorrowSnapshot();
-    if (aDestTarget) {
-      // If !aDestTarget we'll end up painting using mSurface later,
-      // so we can't return it to the provider (note that this will trigger a
-      // copy of the snapshot behind the scenes when the provider is unlocked).
-      autoReturn.mSnapshot = &mSurface;
-    }
-    // Either way we need to call ReturnSnapshot because ther may be an
-    // underlying TextureClient that has to be unlocked.
-    autoReturn.mBufferProvider = mBufferProvider;
-  }
-
-  if (!mGLContext && aDestTarget) {
-    NS_ASSERTION(mSurface, "Must have surface to draw!");
-    if (mSurface) {
-      aDestTarget->CopySurface(mSurface,
-                               IntRect(0, 0, mBounds.width, mBounds.height),
-                               IntPoint(0, 0));
-      mSurface = nullptr;
-    }
-
-    return;
-  }
-
-  if ((!mGLFrontbuffer && mBufferProvider) || mAsyncRenderer) {
-    return;
-  }
-
-  MOZ_ASSERT(mGLContext);
-
-  SharedSurface* frontbuffer = nullptr;
-  if (mGLFrontbuffer) {
-    frontbuffer = mGLFrontbuffer.get();
-  } else {
-    GLScreenBuffer* screen = mGLContext->Screen();
-    const auto& front = screen->Front();
-    if (front) {
-      frontbuffer = front->Surf();
-    }
-  }
-
-  if (!frontbuffer) {
-    NS_WARNING("Null frame received.");
-    return;
-  }
-
-  IntSize readSize(frontbuffer->mSize);
-  SurfaceFormat format = (GetContentFlags() & CONTENT_OPAQUE)
-                          ? SurfaceFormat::B8G8R8X8
-                          : SurfaceFormat::B8G8R8A8;
-  bool needsPremult = frontbuffer->mHasAlpha && !mIsAlphaPremultiplied;
-
-  // Try to read back directly into aDestTarget's output buffer
-  if (aDestTarget) {
-    uint8_t* destData;
-    IntSize destSize;
-    int32_t destStride;
-    SurfaceFormat destFormat;
-    if (aDestTarget->LockBits(&destData, &destSize, &destStride, &destFormat)) {
-      if (destSize == readSize && destFormat == format) {
-        RefPtr<DataSourceSurface> data =
-          Factory::CreateWrappingDataSourceSurface(destData, destStride, destSize, destFormat);
-        mGLContext->Readback(frontbuffer, data);
-        if (needsPremult) {
-          gfxUtils::PremultiplyDataSurface(data, data);
-        }
-        aDestTarget->ReleaseBits(destData);
-        return;
-      }
-      aDestTarget->ReleaseBits(destData);
-    }
-  }
-
-  RefPtr<DataSourceSurface> resultSurf = GetTempSurface(readSize, format);
-  // There will already be a warning from inside of GetTempSurface, but
-  // it doesn't hurt to complain:
-  if (NS_WARN_IF(!resultSurf)) {
-    return;
-  }
-
-  // Readback handles Flush/MarkDirty.
-  mGLContext->Readback(frontbuffer, resultSurf);
-  if (needsPremult) {
-    gfxUtils::PremultiplyDataSurface(resultSurf, resultSurf);
-  }
-  MOZ_ASSERT(resultSurf);
-
-  if (aDestTarget) {
-    aDestTarget->CopySurface(resultSurf,
-                             IntRect(0, 0, readSize.width, readSize.height),
-                             IntPoint(0, 0));
-  } else {
-    // If !aDestSurface then we will end up painting using mSurface, so
-    // stick our surface into mSurface, so that the Paint() path is the same.
-    mSurface = resultSurf;
-  }
-}
-
 DataSourceSurface*
 CopyableCanvasLayer::GetTempSurface(const IntSize& aSize,
                                     const SurfaceFormat aFormat)
 {
   if (!mCachedTempSurface ||
       aSize != mCachedTempSurface->GetSize() ||
       aFormat != mCachedTempSurface->GetFormat())
   {
     // Create a surface aligned to 8 bytes since that's the highest alignment WebGL can handle.
     uint32_t stride = GetAlignedStride<8>(aSize.width * BytesPerPixel(aFormat));
     mCachedTempSurface = Factory::CreateDataSourceSurfaceWithStride(aSize, aFormat, stride);
   }
 
   return mCachedTempSurface;
 }
 
-void
-CopyableCanvasLayer::DiscardTempSurface()
-{
-  mCachedTempSurface = nullptr;
-}
-
 } // namespace layers
 } // namespace mozilla
--- a/gfx/layers/CopyableCanvasLayer.h
+++ b/gfx/layers/CopyableCanvasLayer.h
@@ -42,33 +42,26 @@ protected:
 public:
   virtual void Initialize(const Data& aData) override;
 
   virtual bool IsDataValid(const Data& aData) override;
 
   bool IsGLLayer() { return !!mGLContext; }
 
 protected:
-  void UpdateTarget(gfx::DrawTarget* aDestTarget = nullptr);
-
-  RefPtr<gfx::SourceSurface> mSurface;
   RefPtr<gl::GLContext> mGLContext;
-  GLuint mCanvasFrontbufferTexID;
   RefPtr<PersistentBufferProvider> mBufferProvider;
-
   UniquePtr<gl::SharedSurface> mGLFrontbuffer;
 
   bool mIsAlphaPremultiplied;
   gl::OriginPos mOriginPos;
   bool mIsMirror;
 
   RefPtr<gfx::DataSourceSurface> mCachedTempSurface;
 
   gfx::DataSourceSurface* GetTempSurface(const gfx::IntSize& aSize,
                                          const gfx::SurfaceFormat aFormat);
-
-  void DiscardTempSurface();
 };
 
 } // namespace layers
 } // namespace mozilla
 
 #endif
--- a/gfx/layers/basic/BasicCanvasLayer.cpp
+++ b/gfx/layers/basic/BasicCanvasLayer.cpp
@@ -6,64 +6,132 @@
 #include "BasicCanvasLayer.h"
 #include "AsyncCanvasRenderer.h"
 #include "basic/BasicLayers.h"          // for BasicLayerManager
 #include "basic/BasicLayersImpl.h"      // for GetEffectiveOperator
 #include "mozilla/mozalloc.h"           // for operator new
 #include "nsCOMPtr.h"                   // for already_AddRefed
 #include "nsISupportsImpl.h"            // for Layer::AddRef, etc
 #include "gfx2DGlue.h"
+#include "GLScreenBuffer.h"
+#include "GLContext.h"
+#include "gfxUtils.h"
+#include "mozilla/layers/PersistentBufferProvider.h"
+#include "client/TextureClientSharedSurface.h"
 
 class gfxContext;
 
 using namespace mozilla::gfx;
 using namespace mozilla::gl;
 
 namespace mozilla {
 namespace layers {
 
+already_AddRefed<SourceSurface>
+BasicCanvasLayer::UpdateSurface()
+{
+  if (mAsyncRenderer) {
+    MOZ_ASSERT(!mBufferProvider);
+    MOZ_ASSERT(!mGLContext);
+    return mAsyncRenderer->GetSurface();
+  }
+
+  if (!mGLContext) {
+    return nullptr;
+  }
+
+  SharedSurface* frontbuffer = nullptr;
+  if (mGLFrontbuffer) {
+    frontbuffer = mGLFrontbuffer.get();
+  } else {
+    GLScreenBuffer* screen = mGLContext->Screen();
+    const auto& front = screen->Front();
+    if (front) {
+      frontbuffer = front->Surf();
+    }
+  }
+
+  if (!frontbuffer) {
+    NS_WARNING("Null frame received.");
+    return nullptr;
+  }
+
+  IntSize readSize(frontbuffer->mSize);
+  SurfaceFormat format = (GetContentFlags() & CONTENT_OPAQUE)
+                          ? SurfaceFormat::B8G8R8X8
+                          : SurfaceFormat::B8G8R8A8;
+  bool needsPremult = frontbuffer->mHasAlpha && !mIsAlphaPremultiplied;
+
+  RefPtr<DataSourceSurface> resultSurf = GetTempSurface(readSize, format);
+  // There will already be a warning from inside of GetTempSurface, but
+  // it doesn't hurt to complain:
+  if (NS_WARN_IF(!resultSurf)) {
+    return nullptr;
+  }
+
+  // Readback handles Flush/MarkDirty.
+  mGLContext->Readback(frontbuffer, resultSurf);
+  if (needsPremult) {
+    gfxUtils::PremultiplyDataSurface(resultSurf, resultSurf);
+  }
+  MOZ_ASSERT(resultSurf);
+
+  return resultSurf.forget();
+}
+
 void
 BasicCanvasLayer::Paint(DrawTarget* aDT,
                         const Point& aDeviceOffset,
                         Layer* aMaskLayer)
 {
   if (IsHidden())
     return;
 
+  RefPtr<SourceSurface> surface;
   if (IsDirty()) {
     Painted();
 
     FirePreTransactionCallback();
-    UpdateTarget();
+    surface = UpdateSurface();
     FireDidTransactionCallback();
   }
 
-  if (!mSurface) {
+  bool bufferPoviderSnapshot = false;
+  if (!surface && mBufferProvider) {
+    surface = mBufferProvider->BorrowSnapshot();
+    bufferPoviderSnapshot = !!surface;
+  }
+
+  if (!surface) {
     return;
   }
 
   const bool needsYFlip = (mOriginPos == gl::OriginPos::BottomLeft);
 
   Matrix oldTM;
   if (needsYFlip) {
     oldTM = aDT->GetTransform();
     aDT->SetTransform(Matrix(oldTM).
                         PreTranslate(0.0f, mBounds.height).
                         PreScale(1.0f, -1.0f));
   }
 
   FillRectWithMask(aDT, aDeviceOffset,
                    Rect(0, 0, mBounds.width, mBounds.height),
-                   mSurface, mSamplingFilter,
+                   surface, mSamplingFilter,
                    DrawOptions(GetEffectiveOpacity(), GetEffectiveOperator(this)),
                    aMaskLayer);
 
   if (needsYFlip) {
     aDT->SetTransform(oldTM);
   }
+
+  if (bufferPoviderSnapshot) {
+    mBufferProvider->ReturnSnapshot(surface.forget());
+  }
 }
 
 already_AddRefed<CanvasLayer>
 BasicLayerManager::CreateCanvasLayer()
 {
   NS_ASSERTION(InConstruction(), "Only allowed in construction phase");
   RefPtr<CanvasLayer> layer = new BasicCanvasLayer(this);
   return layer.forget();
--- a/gfx/layers/basic/BasicCanvasLayer.h
+++ b/gfx/layers/basic/BasicCanvasLayer.h
@@ -31,16 +31,19 @@ public:
     CanvasLayer::SetVisibleRegion(aRegion);
   }
 
   virtual void Paint(gfx::DrawTarget* aDT,
                      const gfx::Point& aDeviceOffset,
                      Layer* aMaskLayer) override;
 
 protected:
+
+  already_AddRefed<gfx::SourceSurface> UpdateSurface();
+
   BasicLayerManager* BasicManager()
   {
     return static_cast<BasicLayerManager*>(mManager);
   }
 };
 
 } // namespace layers
 } // namespace mozilla
--- a/gfx/layers/client/CanvasClient.cpp
+++ b/gfx/layers/client/CanvasClient.cpp
@@ -132,17 +132,20 @@ CanvasClient2D::Update(gfx::IntSize aSiz
     TextureClientAutoLock autoLock(mBackBuffer, OpenMode::OPEN_WRITE_ONLY);
     if (!autoLock.Succeeded()) {
       mBackBuffer = nullptr;
       return;
     }
 
     RefPtr<DrawTarget> target = mBackBuffer->BorrowDrawTarget();
     if (target) {
-      aLayer->UpdateTarget(target);
+      if (!aLayer->UpdateTarget(target)) {
+        NS_WARNING("Failed to copy the canvas into a TextureClient.");
+        return;
+      }
       updated = true;
     }
   }
 
   if (bufferCreated && !AddTextureClient(mBackBuffer)) {
     mBackBuffer = nullptr;
     return;
   }
--- a/gfx/layers/client/ClientCanvasLayer.cpp
+++ b/gfx/layers/client/ClientCanvasLayer.cpp
@@ -132,16 +132,109 @@ ClientCanvasLayer::RenderLayer()
   }
 
   FireDidTransactionCallback();
 
   ClientManager()->Hold(this);
   mCanvasClient->Updated();
 }
 
+bool
+ClientCanvasLayer::UpdateTarget(DrawTarget* aDestTarget)
+{
+  MOZ_ASSERT(aDestTarget);
+  if (!aDestTarget) {
+    return false;
+  }
+
+  RefPtr<SourceSurface> surface;
+
+  if (!mGLContext) {
+    AutoReturnSnapshot autoReturn;
+
+    if (mAsyncRenderer) {
+      surface = mAsyncRenderer->GetSurface();
+    } else if (mBufferProvider) {
+      surface = mBufferProvider->BorrowSnapshot();
+      autoReturn.mSnapshot = &surface;
+      autoReturn.mBufferProvider = mBufferProvider;
+    }
+
+    MOZ_ASSERT(surface);
+    if (!surface) {
+      return false;
+    }
+
+    aDestTarget->CopySurface(surface,
+                             IntRect(0, 0, mBounds.width, mBounds.height),
+                             IntPoint(0, 0));
+    return true;
+  }
+
+  SharedSurface* frontbuffer = nullptr;
+  if (mGLFrontbuffer) {
+    frontbuffer = mGLFrontbuffer.get();
+  } else {
+    GLScreenBuffer* screen = mGLContext->Screen();
+    const auto& front = screen->Front();
+    if (front) {
+      frontbuffer = front->Surf();
+    }
+  }
+
+  if (!frontbuffer) {
+    NS_WARNING("Null frame received.");
+    return false;
+  }
+
+  IntSize readSize(frontbuffer->mSize);
+  SurfaceFormat format = (GetContentFlags() & CONTENT_OPAQUE)
+                          ? SurfaceFormat::B8G8R8X8
+                          : SurfaceFormat::B8G8R8A8;
+  bool needsPremult = frontbuffer->mHasAlpha && !mIsAlphaPremultiplied;
+
+  // Try to read back directly into aDestTarget's output buffer
+  uint8_t* destData;
+  IntSize destSize;
+  int32_t destStride;
+  SurfaceFormat destFormat;
+  if (aDestTarget->LockBits(&destData, &destSize, &destStride, &destFormat)) {
+    if (destSize == readSize && destFormat == format) {
+      RefPtr<DataSourceSurface> data =
+        Factory::CreateWrappingDataSourceSurface(destData, destStride, destSize, destFormat);
+      mGLContext->Readback(frontbuffer, data);
+      if (needsPremult) {
+        gfxUtils::PremultiplyDataSurface(data, data);
+      }
+      aDestTarget->ReleaseBits(destData);
+      return true;
+    }
+    aDestTarget->ReleaseBits(destData);
+  }
+
+  RefPtr<DataSourceSurface> resultSurf = GetTempSurface(readSize, format);
+  // There will already be a warning from inside of GetTempSurface, but
+  // it doesn't hurt to complain:
+  if (NS_WARN_IF(!resultSurf)) {
+    return false;
+  }
+
+  // Readback handles Flush/MarkDirty.
+  mGLContext->Readback(frontbuffer, resultSurf);
+  if (needsPremult) {
+    gfxUtils::PremultiplyDataSurface(resultSurf, resultSurf);
+  }
+
+  aDestTarget->CopySurface(resultSurf,
+                           IntRect(0, 0, readSize.width, readSize.height),
+                           IntPoint(0, 0));
+
+  return true;
+}
+
 CanvasClient::CanvasClientType
 ClientCanvasLayer::GetCanvasClientType()
 {
   if (mAsyncRenderer) {
     return CanvasClient::CanvasClientAsync;
   }
 
   if (mGLContext) {
--- a/gfx/layers/client/ClientCanvasLayer.h
+++ b/gfx/layers/client/ClientCanvasLayer.h
@@ -85,16 +85,19 @@ public:
   virtual CompositableClient* GetCompositableClient() override
   {
     return mCanvasClient;
   }
 
   const TextureFlags& Flags() const { return mFlags; }
 
 protected:
+
+  bool UpdateTarget(gfx::DrawTarget* aDestTarget = nullptr);
+
   ClientLayerManager* ClientManager()
   {
     return static_cast<ClientLayerManager*>(mManager);
   }
 
   CanvasClientType GetCanvasClientType();
 
   RefPtr<CanvasClient> mCanvasClient;