summaryrefslogtreecommitdiffstats
path: root/gfx/layers/PersistentBufferProvider.cpp
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 00:47:55 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 00:47:55 +0000
commit26a029d407be480d791972afb5975cf62c9360a6 (patch)
treef435a8308119effd964b339f76abb83a57c29483 /gfx/layers/PersistentBufferProvider.cpp
parentInitial commit. (diff)
downloadfirefox-26a029d407be480d791972afb5975cf62c9360a6.tar.xz
firefox-26a029d407be480d791972afb5975cf62c9360a6.zip
Adding upstream version 124.0.1.upstream/124.0.1
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'gfx/layers/PersistentBufferProvider.cpp')
-rw-r--r--gfx/layers/PersistentBufferProvider.cpp775
1 files changed, 775 insertions, 0 deletions
diff --git a/gfx/layers/PersistentBufferProvider.cpp b/gfx/layers/PersistentBufferProvider.cpp
new file mode 100644
index 0000000000..0e02501a4a
--- /dev/null
+++ b/gfx/layers/PersistentBufferProvider.cpp
@@ -0,0 +1,775 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "PersistentBufferProvider.h"
+
+#include "mozilla/layers/KnowsCompositor.h"
+#include "mozilla/layers/RemoteTextureMap.h"
+#include "mozilla/layers/TextureClient.h"
+#include "mozilla/layers/TextureForwarder.h"
+#include "mozilla/gfx/gfxVars.h"
+#include "mozilla/gfx/CanvasManagerChild.h"
+#include "mozilla/gfx/DrawTargetWebgl.h"
+#include "mozilla/gfx/Logging.h"
+#include "mozilla/Maybe.h"
+#include "mozilla/StaticPrefs_layers.h"
+#include "pratom.h"
+#include "gfxPlatform.h"
+
+namespace mozilla {
+
+using namespace gfx;
+
+namespace layers {
+
+PersistentBufferProviderBasic::PersistentBufferProviderBasic(DrawTarget* aDt)
+ : mDrawTarget(aDt) {
+ MOZ_COUNT_CTOR(PersistentBufferProviderBasic);
+}
+
+PersistentBufferProviderBasic::~PersistentBufferProviderBasic() {
+ MOZ_COUNT_DTOR(PersistentBufferProviderBasic);
+ Destroy();
+}
+
+already_AddRefed<gfx::DrawTarget>
+PersistentBufferProviderBasic::BorrowDrawTarget(
+ const gfx::IntRect& aPersistedRect) {
+ MOZ_ASSERT(!mSnapshot);
+ RefPtr<gfx::DrawTarget> dt(mDrawTarget);
+ return dt.forget();
+}
+
+bool PersistentBufferProviderBasic::ReturnDrawTarget(
+ already_AddRefed<gfx::DrawTarget> aDT) {
+ RefPtr<gfx::DrawTarget> dt(aDT);
+ MOZ_ASSERT(mDrawTarget == dt);
+ if (dt) {
+ // Since SkiaGL default to storing drawing command until flush
+ // we have to flush it before present.
+ dt->Flush();
+ }
+ return true;
+}
+
+already_AddRefed<gfx::SourceSurface>
+PersistentBufferProviderBasic::BorrowSnapshot(gfx::DrawTarget* aTarget) {
+ mSnapshot = mDrawTarget->Snapshot();
+ RefPtr<SourceSurface> snapshot = mSnapshot;
+ return snapshot.forget();
+}
+
+void PersistentBufferProviderBasic::ReturnSnapshot(
+ already_AddRefed<gfx::SourceSurface> aSnapshot) {
+ RefPtr<SourceSurface> snapshot = aSnapshot;
+ MOZ_ASSERT(!snapshot || snapshot == mSnapshot);
+ mSnapshot = nullptr;
+}
+
+void PersistentBufferProviderBasic::Destroy() {
+ mSnapshot = nullptr;
+ mDrawTarget = nullptr;
+}
+
+// static
+already_AddRefed<PersistentBufferProviderBasic>
+PersistentBufferProviderBasic::Create(gfx::IntSize aSize,
+ gfx::SurfaceFormat aFormat,
+ gfx::BackendType aBackend) {
+ RefPtr<DrawTarget> dt =
+ gfxPlatform::GetPlatform()->CreateDrawTargetForBackend(aBackend, aSize,
+ aFormat);
+
+ if (dt) {
+ // This is simply to ensure the DrawTarget gets initialized, and will detect
+ // a device reset, even if we're on the main thread.
+ dt->ClearRect(Rect(0, 0, 0, 0));
+ }
+
+ if (!dt || !dt->IsValid()) {
+ return nullptr;
+ }
+
+ RefPtr<PersistentBufferProviderBasic> provider =
+ new PersistentBufferProviderBasic(dt);
+
+ return provider.forget();
+}
+
+static already_AddRefed<TextureClient> CreateTexture(
+ KnowsCompositor* aKnowsCompositor, gfx::SurfaceFormat aFormat,
+ gfx::IntSize aSize, bool aWillReadFrequently = false,
+ Maybe<RemoteTextureOwnerId> aRemoteTextureOwnerId = {}) {
+ TextureAllocationFlags flags = ALLOC_DEFAULT;
+ if (aWillReadFrequently) {
+ flags = TextureAllocationFlags(flags | ALLOC_DO_NOT_ACCELERATE);
+ }
+ if (aRemoteTextureOwnerId) {
+ flags = TextureAllocationFlags(flags | ALLOC_FORCE_REMOTE);
+ }
+ RefPtr<TextureClient> tc = TextureClient::CreateForDrawing(
+ aKnowsCompositor, aFormat, aSize, BackendSelector::Canvas,
+ TextureFlags::DEFAULT | TextureFlags::NON_BLOCKING_READ_LOCK, flags);
+ if (tc && aRemoteTextureOwnerId) {
+ if (TextureData* td = tc->GetInternalData()) {
+ td->SetRemoteTextureOwnerId(*aRemoteTextureOwnerId);
+ }
+ }
+ return tc.forget();
+}
+
+// static
+already_AddRefed<PersistentBufferProviderAccelerated>
+PersistentBufferProviderAccelerated::Create(gfx::IntSize aSize,
+ gfx::SurfaceFormat aFormat,
+ KnowsCompositor* aKnowsCompositor) {
+ if (!aKnowsCompositor || !aKnowsCompositor->GetTextureForwarder() ||
+ !aKnowsCompositor->GetTextureForwarder()->IPCOpen()) {
+ return nullptr;
+ }
+
+ if (!DrawTargetWebgl::CanCreate(aSize, aFormat)) {
+#ifdef XP_WIN
+ // Direct2D acceleration does not require DrawTargetWebgl, but still
+ // requires PersistentBufferProviderAccelerated.
+ if (!TextureData::IsRemote(aKnowsCompositor, BackendSelector::Canvas,
+ aFormat, aSize)) {
+ return nullptr;
+ }
+#else
+ return nullptr;
+#endif
+ }
+
+ auto remoteTextureOwnerId = RemoteTextureOwnerId::GetNext();
+
+ RefPtr<TextureClient> texture = CreateTexture(
+ aKnowsCompositor, aFormat, aSize, false, Some(remoteTextureOwnerId));
+ if (!texture) {
+ return nullptr;
+ }
+
+ RefPtr<PersistentBufferProviderAccelerated> provider =
+ new PersistentBufferProviderAccelerated(texture);
+ return provider.forget();
+}
+
+PersistentBufferProviderAccelerated::PersistentBufferProviderAccelerated(
+ const RefPtr<TextureClient>& aTexture)
+ : mTexture(aTexture) {
+ MOZ_COUNT_CTOR(PersistentBufferProviderAccelerated);
+}
+
+PersistentBufferProviderAccelerated::~PersistentBufferProviderAccelerated() {
+ MOZ_COUNT_DTOR(PersistentBufferProviderAccelerated);
+ Destroy();
+}
+
+void PersistentBufferProviderAccelerated::Destroy() {
+ mSnapshot = nullptr;
+ mDrawTarget = nullptr;
+
+ if (mTexture) {
+ if (mTexture->IsLocked()) {
+ MOZ_ASSERT(false);
+ mTexture->Unlock();
+ }
+ mTexture = nullptr;
+ }
+}
+
+already_AddRefed<gfx::DrawTarget>
+PersistentBufferProviderAccelerated::BorrowDrawTarget(
+ const gfx::IntRect& aPersistedRect) {
+ if (!mDrawTarget) {
+ if (aPersistedRect.IsEmpty()) {
+ mTexture->GetInternalData()->InvalidateContents();
+ }
+ if (!mTexture->Lock(OpenMode::OPEN_READ_WRITE)) {
+ return nullptr;
+ }
+ mDrawTarget = mTexture->BorrowDrawTarget();
+ if (!mDrawTarget || !mDrawTarget->IsValid()) {
+ mDrawTarget = nullptr;
+ mTexture->Unlock();
+ return nullptr;
+ }
+ }
+ return do_AddRef(mDrawTarget);
+}
+
+bool PersistentBufferProviderAccelerated::ReturnDrawTarget(
+ already_AddRefed<gfx::DrawTarget> aDT) {
+ {
+ RefPtr<gfx::DrawTarget> dt(aDT);
+ MOZ_ASSERT(mDrawTarget == dt);
+ if (!mDrawTarget) {
+ return false;
+ }
+ mDrawTarget = nullptr;
+ }
+ mTexture->Unlock();
+ return true;
+}
+
+already_AddRefed<gfx::SourceSurface>
+PersistentBufferProviderAccelerated::BorrowSnapshot(gfx::DrawTarget* aTarget) {
+ if (mDrawTarget) {
+ MOZ_ASSERT(mTexture->IsLocked());
+ } else {
+ if (mTexture->IsLocked()) {
+ MOZ_ASSERT(false);
+ return nullptr;
+ }
+ if (!mTexture->Lock(OpenMode::OPEN_READ)) {
+ return nullptr;
+ }
+ }
+ mSnapshot = mTexture->BorrowSnapshot();
+ return do_AddRef(mSnapshot);
+}
+
+void PersistentBufferProviderAccelerated::ReturnSnapshot(
+ already_AddRefed<gfx::SourceSurface> aSnapshot) {
+ RefPtr<SourceSurface> snapshot = aSnapshot;
+ MOZ_ASSERT(!snapshot || snapshot == mSnapshot);
+ snapshot = nullptr;
+ mTexture->ReturnSnapshot(mSnapshot.forget());
+ if (!mDrawTarget) {
+ mTexture->Unlock();
+ }
+}
+
+Maybe<SurfaceDescriptor> PersistentBufferProviderAccelerated::GetFrontBuffer() {
+ SurfaceDescriptor desc;
+ if (mTexture->GetInternalData()->Serialize(desc)) {
+ return Some(desc);
+ }
+ return Nothing();
+}
+
+bool PersistentBufferProviderAccelerated::RequiresRefresh() const {
+ return mTexture->GetInternalData()->RequiresRefresh();
+}
+
+already_AddRefed<FwdTransactionTracker>
+PersistentBufferProviderAccelerated::UseCompositableForwarder(
+ CompositableForwarder* aForwarder) {
+ return mTexture->GetInternalData()->UseCompositableForwarder(aForwarder);
+}
+
+// static
+already_AddRefed<PersistentBufferProviderShared>
+PersistentBufferProviderShared::Create(gfx::IntSize aSize,
+ gfx::SurfaceFormat aFormat,
+ KnowsCompositor* aKnowsCompositor,
+ bool aWillReadFrequently,
+ const Maybe<uint64_t>& aWindowID) {
+ if (!aKnowsCompositor || !aKnowsCompositor->GetTextureForwarder() ||
+ !aKnowsCompositor->GetTextureForwarder()->IPCOpen()) {
+ return nullptr;
+ }
+
+ if (!StaticPrefs::layers_shared_buffer_provider_enabled()) {
+ return nullptr;
+ }
+
+#ifdef XP_WIN
+ // Bug 1285271 - Disable shared buffer provider on Windows with D2D due to
+ // instability.
+ aWillReadFrequently = true;
+#endif
+
+ RefPtr<TextureClient> texture =
+ CreateTexture(aKnowsCompositor, aFormat, aSize, aWillReadFrequently);
+ if (!texture) {
+ return nullptr;
+ }
+
+ RefPtr<PersistentBufferProviderShared> provider =
+ new PersistentBufferProviderShared(aSize, aFormat, aKnowsCompositor,
+ texture, aWillReadFrequently,
+ aWindowID);
+ return provider.forget();
+}
+
+PersistentBufferProviderShared::PersistentBufferProviderShared(
+ gfx::IntSize aSize, gfx::SurfaceFormat aFormat,
+ KnowsCompositor* aKnowsCompositor, RefPtr<TextureClient>& aTexture,
+ bool aWillReadFrequently, const Maybe<uint64_t>& aWindowID)
+ : mSize(aSize),
+ mFormat(aFormat),
+ mKnowsCompositor(aKnowsCompositor),
+ mFront(Nothing()),
+ mWillReadFrequently(aWillReadFrequently),
+ mWindowID(aWindowID) {
+ MOZ_ASSERT(aKnowsCompositor);
+ if (mTextures.append(aTexture)) {
+ mBack = Some<uint32_t>(0);
+ }
+
+ // XXX KnowsCompositor could be used for mMaxAllowedTextures
+ if (gfxVars::UseWebRenderTripleBufferingWin()) {
+ ++mMaxAllowedTextures;
+ }
+
+ MOZ_COUNT_CTOR(PersistentBufferProviderShared);
+}
+
+PersistentBufferProviderShared::~PersistentBufferProviderShared() {
+ MOZ_COUNT_DTOR(PersistentBufferProviderShared);
+
+ if (IsActivityTracked()) {
+ if (auto* cm = CanvasManagerChild::Get()) {
+ cm->GetActiveResourceTracker()->RemoveObject(this);
+ } else {
+ MOZ_ASSERT_UNREACHABLE("Tracked but no CanvasManagerChild!");
+ }
+ }
+
+ Destroy();
+}
+
+bool PersistentBufferProviderShared::SetKnowsCompositor(
+ KnowsCompositor* aKnowsCompositor, bool& aOutLostFrontTexture) {
+ MOZ_ASSERT(aKnowsCompositor);
+ MOZ_ASSERT(!aOutLostFrontTexture);
+ if (!aKnowsCompositor) {
+ return false;
+ }
+
+ if (mKnowsCompositor == aKnowsCompositor) {
+ // The forwarder should not change most of the time.
+ return true;
+ }
+
+ if (IsActivityTracked()) {
+ if (auto* cm = CanvasManagerChild::Get()) {
+ cm->GetActiveResourceTracker()->RemoveObject(this);
+ } else {
+ MOZ_ASSERT_UNREACHABLE("Tracked but no CanvasManagerChild!");
+ }
+ }
+
+ if (mKnowsCompositor->GetTextureForwarder() !=
+ aKnowsCompositor->GetTextureForwarder() ||
+ mKnowsCompositor->GetCompositorBackendType() !=
+ aKnowsCompositor->GetCompositorBackendType()) {
+ // We are going to be used with an different and/or incompatible forwarder.
+ // This should be extremely rare. We have to copy the front buffer into a
+ // texture that is compatible with the new forwarder.
+
+ // Grab the current front buffer.
+ RefPtr<TextureClient> prevTexture = GetTexture(mFront);
+
+ // Get rid of everything else
+ Destroy();
+
+ if (prevTexture && !prevTexture->IsValid()) {
+ aOutLostFrontTexture = true;
+ } else if (prevTexture && prevTexture->IsValid()) {
+ RefPtr<TextureClient> newTexture =
+ CreateTexture(aKnowsCompositor, mFormat, mSize, mWillReadFrequently);
+
+ MOZ_ASSERT(newTexture);
+ if (!newTexture) {
+ return false;
+ }
+
+ // If we early-return in one of the following branches, we will
+ // leave the buffer provider in an empty state, since we called
+ // Destroy. Not ideal but at least we won't try to use it with a
+ // an incompatible ipc channel.
+
+ if (!newTexture->Lock(OpenMode::OPEN_WRITE)) {
+ return false;
+ }
+
+ if (!prevTexture->Lock(OpenMode::OPEN_READ)) {
+ newTexture->Unlock();
+ return false;
+ }
+
+ bool success =
+ prevTexture->CopyToTextureClient(newTexture, nullptr, nullptr);
+
+ prevTexture->Unlock();
+ newTexture->Unlock();
+
+ if (!success) {
+ return false;
+ }
+
+ if (!mTextures.append(newTexture)) {
+ return false;
+ }
+ mFront = Some<uint32_t>(mTextures.length() - 1);
+ mBack = mFront;
+ }
+ }
+
+ mKnowsCompositor = aKnowsCompositor;
+
+ return true;
+}
+
+TextureClient* PersistentBufferProviderShared::GetTexture(
+ const Maybe<uint32_t>& aIndex) {
+ if (aIndex.isNothing() || !CheckIndex(aIndex.value())) {
+ return nullptr;
+ }
+ return mTextures[aIndex.value()];
+}
+
+already_AddRefed<gfx::DrawTarget>
+PersistentBufferProviderShared::BorrowDrawTarget(
+ const gfx::IntRect& aPersistedRect) {
+ if (!mKnowsCompositor->GetTextureForwarder() ||
+ !mKnowsCompositor->GetTextureForwarder()->IPCOpen()) {
+ return nullptr;
+ }
+
+ auto* cm = CanvasManagerChild::Get();
+ if (NS_WARN_IF(!cm)) {
+ return nullptr;
+ }
+
+ MOZ_ASSERT(!mSnapshot);
+
+ if (IsActivityTracked()) {
+ cm->GetActiveResourceTracker()->MarkUsed(this);
+ } else {
+ cm->GetActiveResourceTracker()->AddObject(this);
+ }
+
+ if (mDrawTarget) {
+ RefPtr<gfx::DrawTarget> dt(mDrawTarget);
+ return dt.forget();
+ }
+
+ 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;
+ }
+ }
+ }
+
+ if (!tex) {
+ // We have to allocate a new texture.
+ if (mTextures.length() >= mMaxAllowedTextures) {
+ // We should never need to buffer that many textures, something's wrong.
+ // In theory we throttle the main thread when the compositor can't keep
+ // up, so we shoud never get in a situation where we sent 4 textures to
+ // the compositor and the latter has not released any of them. In
+ // practice, though, the throttling mechanism appears to have some issues,
+ // especially when switching between layer managers (during tab-switch).
+ // To make sure we don't get too far ahead of the compositor, we send a
+ // sync ping to the compositor thread...
+ mKnowsCompositor->SyncWithCompositor(mWindowID);
+ // ...and try again.
+ for (uint32_t i = 0; i < mTextures.length(); ++i) {
+ if (!mTextures[i]->IsReadLocked()) {
+ gfxCriticalNote << "Managed to allocate after flush.";
+ mBack = Some(i);
+ tex = mTextures[i];
+ break;
+ }
+ }
+
+ if (!tex) {
+ gfxCriticalNote << "Unexpected BufferProvider over-production.";
+ // It would be pretty bad to keep piling textures up at this point so we
+ // call NotifyInactive to remove some of our textures.
+ NotifyInactive();
+ // Give up now. The caller can fall-back to a non-shared buffer
+ // provider.
+ return nullptr;
+ }
+ }
+
+ RefPtr<TextureClient> newTexture =
+ CreateTexture(mKnowsCompositor, mFormat, mSize, mWillReadFrequently);
+
+ MOZ_ASSERT(newTexture);
+ if (newTexture) {
+ if (mTextures.append(newTexture)) {
+ tex = newTexture;
+ mBack = Some<uint32_t>(mTextures.length() - 1);
+ }
+ }
+ }
+
+ if (!tex) {
+ return nullptr;
+ }
+
+ if (mPermanentBackBuffer) {
+ // If we have a permanent back buffer lock the selected one and switch to
+ // the permanent one before borrowing the DrawTarget. We will copy back into
+ // the selected one when ReturnDrawTarget is called, before we make it the
+ // new front buffer.
+ if (!tex->Lock(OpenMode::OPEN_WRITE)) {
+ return nullptr;
+ }
+ tex = mPermanentBackBuffer;
+ } else {
+ // Copy from the previous back buffer if required.
+ Maybe<TextureClientAutoLock> autoReadLock;
+ TextureClient* previous = nullptr;
+ if (mBack != previousBackBuffer && !aPersistedRect.IsEmpty()) {
+ if (tex->HasSynchronization()) {
+ // We are about to read lock a texture that is in use by the compositor
+ // and has synchronization. To prevent possible future contention we
+ // switch to using a permanent back buffer.
+ mPermanentBackBuffer = CreateTexture(mKnowsCompositor, mFormat, mSize,
+ mWillReadFrequently);
+ if (!mPermanentBackBuffer) {
+ return nullptr;
+ }
+ if (!tex->Lock(OpenMode::OPEN_WRITE)) {
+ return nullptr;
+ }
+ tex = mPermanentBackBuffer;
+ }
+
+ previous = GetTexture(previousBackBuffer);
+ if (previous) {
+ autoReadLock.emplace(previous, OpenMode::OPEN_READ);
+ }
+ }
+
+ if (!tex->Lock(OpenMode::OPEN_READ_WRITE)) {
+ return nullptr;
+ }
+
+ if (autoReadLock.isSome() && autoReadLock->Succeeded() && previous) {
+ DebugOnly<bool> success =
+ previous->CopyToTextureClient(tex, &aPersistedRect, nullptr);
+ MOZ_ASSERT(success);
+ }
+ }
+
+ mDrawTarget = tex->BorrowDrawTarget();
+ if (mDrawTarget) {
+ // This is simply to ensure the DrawTarget gets initialized, and will detect
+ // a device reset, even if we're on the main thread.
+ mDrawTarget->ClearRect(Rect(0, 0, 0, 0));
+
+ if (!mDrawTarget->IsValid()) {
+ mDrawTarget = nullptr;
+ }
+ }
+
+ RefPtr<gfx::DrawTarget> dt(mDrawTarget);
+ return dt.forget();
+}
+
+bool PersistentBufferProviderShared::ReturnDrawTarget(
+ already_AddRefed<gfx::DrawTarget> aDT) {
+ RefPtr<gfx::DrawTarget> dt(aDT);
+ MOZ_ASSERT(mDrawTarget == dt);
+ // Can't change the current front buffer while its snapshot is borrowed!
+ MOZ_ASSERT(!mSnapshot);
+
+ TextureClient* back = GetTexture(mBack);
+ MOZ_ASSERT(back);
+
+ mDrawTarget = nullptr;
+ dt = nullptr;
+
+ // If we have a permanent back buffer we have actually been drawing to that,
+ // so now we must copy to the shared one.
+ if (mPermanentBackBuffer && back) {
+ DebugOnly<bool> success =
+ mPermanentBackBuffer->CopyToTextureClient(back, nullptr, nullptr);
+ MOZ_ASSERT(success);
+
+ // Let our permanent back buffer know that we have finished drawing.
+ mPermanentBackBuffer->EndDraw();
+ }
+
+ if (back) {
+ back->Unlock();
+ mFront = mBack;
+ }
+
+ return !!back;
+}
+
+TextureClient* PersistentBufferProviderShared::GetTextureClient() {
+ // Can't access the front buffer while drawing.
+ MOZ_ASSERT(!mDrawTarget);
+ TextureClient* texture = GetTexture(mFront);
+ if (!texture) {
+ gfxCriticalNote
+ << "PersistentBufferProviderShared: front buffer unavailable";
+ return nullptr;
+ }
+
+ // Sometimes, for example on tab switch, we re-forward our texture. So if we
+ // are and it is still read locked, then borrow and return our DrawTarget to
+ // force it to be copied to a texture that we will safely read lock.
+ if (texture->IsReadLocked()) {
+ RefPtr<DrawTarget> dt =
+ BorrowDrawTarget(IntRect(0, 0, mSize.width, mSize.height));
+
+ // If we failed to borrow a DrawTarget then all our textures must be read
+ // locked or we failed to create one, so we'll just return the current front
+ // buffer even though that might lead to contention.
+ if (dt) {
+ ReturnDrawTarget(dt.forget());
+ texture = GetTexture(mFront);
+ if (!texture) {
+ gfxCriticalNote
+ << "PersistentBufferProviderShared: front buffer unavailable";
+ return nullptr;
+ }
+ }
+ } else {
+ // If it isn't read locked then make sure it is set as updated, so that we
+ // will always read lock even if we've forwarded the texture before.
+ texture->SetUpdated();
+ }
+
+ return texture;
+}
+
+already_AddRefed<gfx::SourceSurface>
+PersistentBufferProviderShared::BorrowSnapshot(gfx::DrawTarget* aTarget) {
+ // If we have a permanent back buffer we can always use that to snapshot.
+ if (mPermanentBackBuffer) {
+ mSnapshot = mPermanentBackBuffer->BorrowSnapshot();
+ return do_AddRef(mSnapshot);
+ }
+
+ if (mDrawTarget) {
+ auto back = GetTexture(mBack);
+ MOZ_ASSERT(back && back->IsLocked());
+ mSnapshot = back->BorrowSnapshot();
+ return do_AddRef(mSnapshot);
+ }
+
+ auto front = GetTexture(mFront);
+ if (!front || front->IsLocked()) {
+ MOZ_ASSERT(false);
+ return nullptr;
+ }
+
+ if (front->IsReadLocked() && front->HasSynchronization()) {
+ // We are about to read lock a texture that is in use by the compositor and
+ // has synchronization. To prevent possible future contention we switch to
+ // using a permanent back buffer.
+ mPermanentBackBuffer =
+ CreateTexture(mKnowsCompositor, mFormat, mSize, mWillReadFrequently);
+ if (!mPermanentBackBuffer ||
+ !mPermanentBackBuffer->Lock(OpenMode::OPEN_READ_WRITE)) {
+ return nullptr;
+ }
+
+ if (!front->Lock(OpenMode::OPEN_READ)) {
+ return nullptr;
+ }
+
+ DebugOnly<bool> success =
+ front->CopyToTextureClient(mPermanentBackBuffer, nullptr, nullptr);
+ MOZ_ASSERT(success);
+ front->Unlock();
+ mSnapshot = mPermanentBackBuffer->BorrowSnapshot();
+ return do_AddRef(mSnapshot);
+ }
+
+ if (!front->Lock(OpenMode::OPEN_READ)) {
+ return nullptr;
+ }
+
+ mSnapshot = front->BorrowSnapshot();
+
+ return do_AddRef(mSnapshot);
+}
+
+void PersistentBufferProviderShared::ReturnSnapshot(
+ already_AddRefed<gfx::SourceSurface> aSnapshot) {
+ RefPtr<SourceSurface> snapshot = aSnapshot;
+ MOZ_ASSERT(!snapshot || snapshot == mSnapshot);
+
+ mSnapshot = nullptr;
+ snapshot = nullptr;
+
+ if (mDrawTarget || mPermanentBackBuffer) {
+ return;
+ }
+
+ auto front = GetTexture(mFront);
+ if (front) {
+ front->Unlock();
+ }
+}
+
+void PersistentBufferProviderShared::NotifyInactive() {
+ ClearCachedResources();
+}
+
+void PersistentBufferProviderShared::ClearCachedResources() {
+ RefPtr<TextureClient> front = GetTexture(mFront);
+ RefPtr<TextureClient> back = GetTexture(mBack);
+
+ // Clear all textures (except the front and back ones that we just kept).
+ mTextures.clear();
+ mPermanentBackBuffer = nullptr;
+
+ if (back) {
+ if (mTextures.append(back)) {
+ mBack = Some<uint32_t>(0);
+ }
+ if (front == back) {
+ mFront = mBack;
+ }
+ }
+
+ if (front && front != back) {
+ if (mTextures.append(front)) {
+ mFront = Some<uint32_t>(mTextures.length() - 1);
+ }
+ }
+}
+
+void PersistentBufferProviderShared::Destroy() {
+ mSnapshot = nullptr;
+ mDrawTarget = nullptr;
+
+ if (mPermanentBackBuffer) {
+ mPermanentBackBuffer->Unlock();
+ mPermanentBackBuffer = nullptr;
+ }
+
+ for (auto& texture : mTextures) {
+ if (texture && texture->IsLocked()) {
+ MOZ_ASSERT(false);
+ texture->Unlock();
+ }
+ }
+
+ mTextures.clear();
+}
+
+} // namespace layers
+} // namespace mozilla