diff --git a/dom/canvas/CanvasRenderingContext2D.cpp b/dom/canvas/CanvasRenderingContext2D.cpp index 8e34238cd66f..daa4c2d6e33a 100644 --- a/dom/canvas/CanvasRenderingContext2D.cpp +++ b/dom/canvas/CanvasRenderingContext2D.cpp @@ -905,7 +905,7 @@ public: if (!context || !context->mTarget) return; - context->ReturnTarget(); + context->OnStableState(); } static void DidTransactionCallback(void* aData) @@ -1504,6 +1504,10 @@ CanvasRenderingContext2D::ScheduleStableStateCallback() void CanvasRenderingContext2D::OnStableState() { + if (!mHasPendingStableStateCallback) { + return; + } + ReturnTarget(); mHasPendingStableStateCallback = false; @@ -1529,6 +1533,8 @@ CanvasRenderingContext2D::EnsureTarget(const gfx::Rect* aCoveredRect, return mRenderingMode; } + ScheduleStableStateCallback(); + if (mBufferProvider && mode == mRenderingMode) { gfx::Rect rect(0, 0, mWidth, mHeight); if (aCoveredRect && CurrentState().transform.TransformBounds(*aCoveredRect).Contains(rect)) { @@ -1537,8 +1543,6 @@ CanvasRenderingContext2D::EnsureTarget(const gfx::Rect* aCoveredRect, mTarget = mBufferProvider->BorrowDrawTarget(IntRect(0, 0, mWidth, mHeight)); } - ScheduleStableStateCallback(); - if (mTarget) { // Restore clip and transform. for (uint32_t i = 0; i < mStyleStack.Length(); i++) { @@ -1731,7 +1735,7 @@ CanvasRenderingContext2D::ClearTarget() void CanvasRenderingContext2D::ReturnTarget() { - if (mTarget && mBufferProvider) { + if (mTarget && mBufferProvider && mTarget != sErrorTarget) { CurrentState().transform = mTarget->GetTransform(); for (uint32_t i = 0; i < mStyleStack.Length(); i++) { for (uint32_t c = 0; c < mStyleStack[i].clipsPushed.Length(); c++) { @@ -5693,7 +5697,7 @@ CanvasRenderingContext2D::GetBufferProvider(LayerManager* aManager) return mBufferProvider; } - if (aManager) { + if (aManager && !mIsSkiaGL) { mBufferProvider = aManager->CreatePersistentBufferProvider(gfx::IntSize(mWidth, mHeight), GetSurfaceFormat()); } diff --git a/gfx/layers/PersistentBufferProvider.cpp b/gfx/layers/PersistentBufferProvider.cpp index c506d44561ec..b4f76e0dd7e6 100644 --- a/gfx/layers/PersistentBufferProvider.cpp +++ b/gfx/layers/PersistentBufferProvider.cpp @@ -103,8 +103,6 @@ PersistentBufferProviderShared::Create(gfx::IntSize aSize, return nullptr; } - texture->EnableReadLock(); - RefPtr provider = new PersistentBufferProviderShared(aSize, aFormat, aFwd, texture); return provider.forget(); @@ -118,8 +116,11 @@ PersistentBufferProviderShared::PersistentBufferProviderShared(gfx::IntSize aSiz : mSize(aSize) , mFormat(aFormat) , mFwd(aFwd) -, mBack(aTexture.forget()) +, mFront(Nothing()) { + if (mTextures.append(aTexture)) { + mBack = Some(0); + } MOZ_COUNT_CTOR(PersistentBufferProviderShared); } @@ -134,6 +135,15 @@ PersistentBufferProviderShared::~PersistentBufferProviderShared() Destroy(); } +TextureClient* +PersistentBufferProviderShared::GetTexture(Maybe aIndex) +{ + if (aIndex.isNothing() || !CheckIndex(aIndex.value())) { + return nullptr; + } + return mTextures[aIndex.value()]; +} + already_AddRefed PersistentBufferProviderShared::BorrowDrawTarget(const gfx::IntRect& aPersistedRect) { @@ -149,51 +159,76 @@ PersistentBufferProviderShared::BorrowDrawTarget(const gfx::IntRect& aPersistedR mFwd->GetActiveResourceTracker().AddObject(this); } - if (!mDrawTarget) { - bool changedBackBuffer = false; - if (!mBack || mBack->IsReadLocked()) { - if (mBuffer && !mBuffer->IsReadLocked()) { - mBack.swap(mBuffer); - } else { - mBack = TextureClient::CreateForDrawing( - mFwd, mFormat, mSize, - BackendSelector::Canvas, - TextureFlags::DEFAULT, - TextureAllocationFlags::ALLOC_DEFAULT - ); - if (mBack) { - mBack->EnableReadLock(); - } + if (mDrawTarget) { + RefPtr dt(mDrawTarget); + return dt.forget(); + } + + mFront = Nothing(); + + auto previousBackBuffer = mBack; + + TextureClient* tex = GetTexture(mBack); + + // First try to reuse the current back buffer. If we can do that it means + // we can skip copying its content to the new back buffer. + if (tex && tex->IsReadLocked()) { + // The back buffer is currently used by the compositor, we can't draw + // into it. + tex = nullptr; + } + + if (!tex) { + // Try to grab an already allocated texture if any is available. + for (uint32_t i = 0; i < mTextures.length(); ++i) { + if (!mTextures[i]->IsReadLocked()) { + mBack = Some(i); + tex = mTextures[i]; + break; } - changedBackBuffer = true; - } else { - // Fast path, our front buffer is already writable because the texture upload - // has completed on the compositor side. - if (mBack->HasIntermediateBuffer()) { - // No need to keep an extra buffer around - mBuffer = nullptr; - } - } - - if (!mBack || !mBack->Lock(OpenMode::OPEN_READ_WRITE)) { - return nullptr; - } - - if (changedBackBuffer && !aPersistedRect.IsEmpty() - && mFront && mFront->Lock(OpenMode::OPEN_READ)) { - - DebugOnly success = mFront->CopyToTextureClient(mBack, &aPersistedRect, nullptr); - MOZ_ASSERT(success); - - mFront->Unlock(); - } - - mDrawTarget = mBack->BorrowDrawTarget(); - if (!mDrawTarget) { - return nullptr; } } + if (!tex) { + // We have to allocate a new texture. + if (mTextures.length() >= 4) { + // We should never need to buffer that many textures, something's wrong. + MOZ_ASSERT(false); + return nullptr; + } + + RefPtr newTexture = TextureClient::CreateForDrawing( + mFwd, mFormat, mSize, + BackendSelector::Canvas, + TextureFlags::DEFAULT, + TextureAllocationFlags::ALLOC_DEFAULT + ); + + MOZ_ASSERT(newTexture); + if (newTexture) { + if (mTextures.append(newTexture)) { + tex = newTexture; + mBack = Some(mTextures.length() - 1); + } + } + } + + if (!tex || !tex->Lock(OpenMode::OPEN_READ_WRITE)) { + return nullptr; + } + + if (mBack != previousBackBuffer && !aPersistedRect.IsEmpty()) { + TextureClient* previous = GetTexture(previousBackBuffer); + if (previous && previous->Lock(OpenMode::OPEN_READ)) { + DebugOnly success = previous->CopyToTextureClient(tex, &aPersistedRect, nullptr); + MOZ_ASSERT(success); + + previous->Unlock(); + } + } + + mDrawTarget = tex->BorrowDrawTarget(); + RefPtr dt(mDrawTarget); return dt.forget(); } @@ -203,45 +238,60 @@ PersistentBufferProviderShared::ReturnDrawTarget(already_AddRefed dt(aDT); MOZ_ASSERT(mDrawTarget == dt); + // Can't change the current front buffer while its snapshot is borrowed! MOZ_ASSERT(!mSnapshot); mDrawTarget = nullptr; dt = nullptr; - mBack->Unlock(); + TextureClient* back = GetTexture(mBack); + MOZ_ASSERT(back); - if (!mBuffer && mFront && !mFront->IsLocked()) { - mBuffer.swap(mFront); + if (back) { + back->Unlock(); + mFront = mBack; } - mFront = mBack; + return !!back; +} - return true; +TextureClient* +PersistentBufferProviderShared::GetTextureClient() +{ + // Can't access the front buffer while drawing. + MOZ_ASSERT(!mDrawTarget); + TextureClient* texture = GetTexture(mFront); + if (texture) { + texture->EnableReadLock(); + } else { + gfxCriticalNote << "PersistentBufferProviderShared: front buffer unavailable"; + } + return texture; } already_AddRefed PersistentBufferProviderShared::BorrowSnapshot() { - // TODO[nical] currently we can't snapshot while drawing, looks like it does - // the job but I am not sure whether we want to be able to do that. MOZ_ASSERT(!mDrawTarget); - if (!mFront || mFront->IsLocked()) { + auto front = GetTexture(mFront); + if (!front || front->IsLocked()) { MOZ_ASSERT(false); return nullptr; } - if (!mFront->Lock(OpenMode::OPEN_READ)) { + if (!front->Lock(OpenMode::OPEN_READ)) { return nullptr; } - mDrawTarget = mFront->BorrowDrawTarget(); + RefPtr dt = front->BorrowDrawTarget(); - if (!mDrawTarget) { - mFront->Unlock(); + if (!dt) { + front->Unlock(); + return nullptr; } - mSnapshot = mDrawTarget->Snapshot(); + mSnapshot = dt->Snapshot(); RefPtr snapshot = mSnapshot; return snapshot.forget(); @@ -256,20 +306,35 @@ PersistentBufferProviderShared::ReturnSnapshot(already_AddRefedUnlock(); + auto front = GetTexture(mFront); + if (front) { + front->Unlock(); + } } void PersistentBufferProviderShared::NotifyInactive() { - if (mBuffer && mBuffer->IsLocked()) { - // mBuffer should never be locked - MOZ_ASSERT(false); - mBuffer->Unlock(); + RefPtr front = GetTexture(mFront); + RefPtr back = GetTexture(mBack); + + // Clear all textures (except the front and back ones that we just kept). + mTextures.clear(); + + if (back) { + if (mTextures.append(back)) { + mBack = Some(0); + } + if (front == back) { + mFront = mBack; + } + } + + if (front && front != back) { + if (mTextures.append(front)) { + mFront = Some(mTextures.length() - 1); + } } - mBuffer = nullptr; } void @@ -278,21 +343,15 @@ PersistentBufferProviderShared::Destroy() mSnapshot = nullptr; mDrawTarget = nullptr; - if (mFront && mFront->IsLocked()) { - mFront->Unlock(); + for (uint32_t i = 0; i < mTextures.length(); ++i) { + TextureClient* texture = mTextures[i]; + if (texture && texture->IsLocked()) { + MOZ_ASSERT(false); + texture->Unlock(); + } } - if (mBack && mBack->IsLocked()) { - mBack->Unlock(); - } - - if (mBuffer && mBuffer->IsLocked()) { - mBuffer->Unlock(); - } - - mFront = nullptr; - mBack = nullptr; - mBuffer = nullptr; + mTextures.clear(); } } // namespace layers diff --git a/gfx/layers/PersistentBufferProvider.h b/gfx/layers/PersistentBufferProvider.h index 716539a906ec..e6271629235b 100644 --- a/gfx/layers/PersistentBufferProvider.h +++ b/gfx/layers/PersistentBufferProvider.h @@ -11,6 +11,7 @@ #include "mozilla/layers/LayersTypes.h" #include "mozilla/layers/CompositableForwarder.h" #include "mozilla/gfx/Types.h" +#include "mozilla/Vector.h" namespace mozilla { @@ -117,9 +118,7 @@ public: virtual void ReturnSnapshot(already_AddRefed aSnapshot) override; - TextureClient* GetTextureClient() override { - return mFront; - } + virtual TextureClient* GetTextureClient() override; virtual void NotifyInactive() override; @@ -132,17 +131,20 @@ protected: ~PersistentBufferProviderShared(); + TextureClient* GetTexture(Maybe aIndex); + bool CheckIndex(uint32_t aIndex) { return aIndex < mTextures.length(); } + void Destroy(); gfx::IntSize mSize; gfx::SurfaceFormat mFormat; RefPtr mFwd; - // The texture presented to the compositor. - RefPtr mFront; - // The texture that the canvas uses. - RefPtr mBack; - // An extra texture we keep around temporarily to avoid allocating. - RefPtr mBuffer; + Vector, 4> mTextures; + // Offset of the texture in mTextures that the canvas uses. + Maybe mBack; + // Offset of the texture in mTextures that is presented to the compositor. + Maybe mFront; + RefPtr mDrawTarget; RefPtr mSnapshot; }; diff --git a/gfx/layers/client/CanvasClient.cpp b/gfx/layers/client/CanvasClient.cpp index fa408271c0e0..0c4f03317935 100644 --- a/gfx/layers/client/CanvasClient.cpp +++ b/gfx/layers/client/CanvasClient.cpp @@ -77,11 +77,13 @@ CanvasClient2D::UpdateFromTexture(TextureClient* aTexture) } } - mBackBuffer = aTexture; + mBackBuffer = nullptr; + mFrontBuffer = nullptr; + mBufferProviderTexture = aTexture; AutoTArray textures; CompositableForwarder::TimedTextureClient* t = textures.AppendElement(); - t->mTextureClient = mBackBuffer; + t->mTextureClient = aTexture; t->mPictureRect = nsIntRect(nsIntPoint(0, 0), aTexture->GetSize()); t->mFrameID = mFrameID; t->mInputFrameID = VRManagerChild::Get()->GetInputFrameID(); @@ -93,6 +95,8 @@ CanvasClient2D::UpdateFromTexture(TextureClient* aTexture) void CanvasClient2D::Update(gfx::IntSize aSize, ClientCanvasLayer* aLayer) { + mBufferProviderTexture = nullptr; + AutoRemoveTexture autoRemove(this); if (mBackBuffer && (mBackBuffer->IsImmutable() || mBackBuffer->GetSize() != aSize)) { diff --git a/gfx/layers/client/CanvasClient.h b/gfx/layers/client/CanvasClient.h index 3899535169e0..cd88d02ab752 100644 --- a/gfx/layers/client/CanvasClient.h +++ b/gfx/layers/client/CanvasClient.h @@ -128,6 +128,12 @@ private: RefPtr mBackBuffer; RefPtr mFrontBuffer; + // We store this texture separately to make sure it is not written into + // in Update() if for some silly reason we end up alternating between + // UpdateFromTexture and Update. + // This code is begging for a cleanup. The situation described above should + // not be made possible. + RefPtr mBufferProviderTexture; }; // Used for GL canvases where we don't need to do any readback, i.e., with a