diff options
Diffstat (limited to '')
-rw-r--r-- | gfx/layers/PersistentBufferProvider.cpp | 674 |
1 files changed, 674 insertions, 0 deletions
diff --git a/gfx/layers/PersistentBufferProvider.cpp b/gfx/layers/PersistentBufferProvider.cpp new file mode 100644 index 0000000000..4ce356bea9 --- /dev/null +++ b/gfx/layers/PersistentBufferProvider.cpp @@ -0,0 +1,674 @@ +/* -*- 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/TextureClient.h" +#include "mozilla/layers/TextureForwarder.h" +#include "mozilla/gfx/gfxVars.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(); +} + +PersistentBufferProviderAccelerated::PersistentBufferProviderAccelerated( + DrawTarget* aDt) + : PersistentBufferProviderBasic(aDt) { + MOZ_COUNT_CTOR(PersistentBufferProviderAccelerated); + MOZ_ASSERT(aDt->GetBackendType() == BackendType::WEBGL); +} + +PersistentBufferProviderAccelerated::~PersistentBufferProviderAccelerated() { + MOZ_COUNT_DTOR(PersistentBufferProviderAccelerated); +} + +inline gfx::DrawTargetWebgl* +PersistentBufferProviderAccelerated::GetDrawTargetWebgl() const { + return static_cast<gfx::DrawTargetWebgl*>(mDrawTarget.get()); +} + +Maybe<layers::SurfaceDescriptor> +PersistentBufferProviderAccelerated::GetFrontBuffer() { + return GetDrawTargetWebgl()->GetFrontBuffer(); +} + +already_AddRefed<gfx::DrawTarget> +PersistentBufferProviderAccelerated::BorrowDrawTarget( + const gfx::IntRect& aPersistedRect) { + GetDrawTargetWebgl()->BeginFrame(aPersistedRect); + return PersistentBufferProviderBasic::BorrowDrawTarget(aPersistedRect); +} + +bool PersistentBufferProviderAccelerated::ReturnDrawTarget( + already_AddRefed<gfx::DrawTarget> aDT) { + bool result = PersistentBufferProviderBasic::ReturnDrawTarget(std::move(aDT)); + GetDrawTargetWebgl()->EndFrame(); + return result; +} + +already_AddRefed<gfx::SourceSurface> +PersistentBufferProviderAccelerated::BorrowSnapshot(gfx::DrawTarget* aTarget) { + mSnapshot = GetDrawTargetWebgl()->GetOptimizedSnapshot(aTarget); + return do_AddRef(mSnapshot); +} + +bool PersistentBufferProviderAccelerated::RequiresRefresh() const { + return GetDrawTargetWebgl()->RequiresRefresh(); +} + +void PersistentBufferProviderAccelerated::OnMemoryPressure() { + GetDrawTargetWebgl()->OnMemoryPressure(); +} + +static already_AddRefed<TextureClient> CreateTexture( + KnowsCompositor* aKnowsCompositor, gfx::SurfaceFormat aFormat, + gfx::IntSize aSize, bool aWillReadFrequently) { + TextureAllocationFlags flags = ALLOC_DEFAULT; + if (aWillReadFrequently) { + flags = TextureAllocationFlags(flags | ALLOC_DO_NOT_ACCELERATE); + } + return TextureClient::CreateForDrawing( + aKnowsCompositor, aFormat, aSize, BackendSelector::Canvas, + TextureFlags::DEFAULT | TextureFlags::NON_BLOCKING_READ_LOCK, flags); +} + +// static +already_AddRefed<PersistentBufferProviderShared> +PersistentBufferProviderShared::Create(gfx::IntSize aSize, + gfx::SurfaceFormat aFormat, + KnowsCompositor* aKnowsCompositor, + bool aWillReadFrequently) { + 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, unless we are remoting the canvas drawing to the GPU process. + if (gfxPlatform::GetPlatform()->GetPreferredCanvasBackend() == + BackendType::DIRECT2D1_1 && + !TextureData::IsRemote(aKnowsCompositor, BackendSelector::Canvas)) { + return nullptr; + } +#endif + + RefPtr<TextureClient> texture = + CreateTexture(aKnowsCompositor, aFormat, aSize, aWillReadFrequently); + if (!texture) { + return nullptr; + } + + RefPtr<PersistentBufferProviderShared> provider = + new PersistentBufferProviderShared(aSize, aFormat, aKnowsCompositor, + texture, aWillReadFrequently); + return provider.forget(); +} + +PersistentBufferProviderShared::PersistentBufferProviderShared( + gfx::IntSize aSize, gfx::SurfaceFormat aFormat, + KnowsCompositor* aKnowsCompositor, RefPtr<TextureClient>& aTexture, + bool aWillReadFrequently) + + : mSize(aSize), + mFormat(aFormat), + mKnowsCompositor(aKnowsCompositor), + mFront(Nothing()), + mWillReadFrequently(aWillReadFrequently) { + 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()) { + mKnowsCompositor->GetActiveResourceTracker()->RemoveObject(this); + } + + 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()) { + mKnowsCompositor->GetActiveResourceTracker()->RemoveObject(this); + } + + 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; + } + + MOZ_ASSERT(!mSnapshot); + + if (IsActivityTracked()) { + mKnowsCompositor->GetActiveResourceTracker()->MarkUsed(this); + } else { + mKnowsCompositor->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(); + // ...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(); +} + +bool PersistentBufferProviderShared::IsAccelerated() const { +#ifdef XP_WIN + // Detect if we're using D2D canvas. + if (mWillReadFrequently || mTextures.empty() || !mTextures[0]) { + return false; + } + auto* data = mTextures[0]->GetInternalData(); + if (data && data->AsD3D11TextureData()) { + return true; + } +#endif + return false; +} + +} // namespace layers +} // namespace mozilla |