/* -*- 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 PersistentBufferProviderBasic::BorrowDrawTarget( const gfx::IntRect& aPersistedRect) { MOZ_ASSERT(!mSnapshot); RefPtr dt(mDrawTarget); return dt.forget(); } bool PersistentBufferProviderBasic::ReturnDrawTarget( already_AddRefed aDT) { RefPtr 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 PersistentBufferProviderBasic::BorrowSnapshot(gfx::DrawTarget* aTarget) { mSnapshot = mDrawTarget->Snapshot(); RefPtr snapshot = mSnapshot; return snapshot.forget(); } void PersistentBufferProviderBasic::ReturnSnapshot( already_AddRefed aSnapshot) { RefPtr snapshot = aSnapshot; MOZ_ASSERT(!snapshot || snapshot == mSnapshot); mSnapshot = nullptr; } void PersistentBufferProviderBasic::Destroy() { mSnapshot = nullptr; mDrawTarget = nullptr; } // static already_AddRefed PersistentBufferProviderBasic::Create(gfx::IntSize aSize, gfx::SurfaceFormat aFormat, gfx::BackendType aBackend) { RefPtr 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 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(mDrawTarget.get()); } Maybe PersistentBufferProviderAccelerated::GetFrontBuffer() { return GetDrawTargetWebgl()->GetFrontBuffer(); } already_AddRefed PersistentBufferProviderAccelerated::BorrowDrawTarget( const gfx::IntRect& aPersistedRect) { GetDrawTargetWebgl()->BeginFrame(aPersistedRect); return PersistentBufferProviderBasic::BorrowDrawTarget(aPersistedRect); } bool PersistentBufferProviderAccelerated::ReturnDrawTarget( already_AddRefed aDT) { bool result = PersistentBufferProviderBasic::ReturnDrawTarget(std::move(aDT)); GetDrawTargetWebgl()->EndFrame(); return result; } already_AddRefed 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 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::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 texture = CreateTexture(aKnowsCompositor, aFormat, aSize, aWillReadFrequently); if (!texture) { return nullptr; } RefPtr provider = new PersistentBufferProviderShared(aSize, aFormat, aKnowsCompositor, texture, aWillReadFrequently); return provider.forget(); } PersistentBufferProviderShared::PersistentBufferProviderShared( gfx::IntSize aSize, gfx::SurfaceFormat aFormat, KnowsCompositor* aKnowsCompositor, RefPtr& aTexture, bool aWillReadFrequently) : mSize(aSize), mFormat(aFormat), mKnowsCompositor(aKnowsCompositor), mFront(Nothing()), mWillReadFrequently(aWillReadFrequently) { MOZ_ASSERT(aKnowsCompositor); if (mTextures.append(aTexture)) { mBack = Some(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 prevTexture = GetTexture(mFront); // Get rid of everything else Destroy(); if (prevTexture && !prevTexture->IsValid()) { aOutLostFrontTexture = true; } else if (prevTexture && prevTexture->IsValid()) { RefPtr 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(mTextures.length() - 1); mBack = mFront; } } mKnowsCompositor = aKnowsCompositor; return true; } TextureClient* PersistentBufferProviderShared::GetTexture( const Maybe& aIndex) { if (aIndex.isNothing() || !CheckIndex(aIndex.value())) { return nullptr; } return mTextures[aIndex.value()]; } already_AddRefed 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 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 newTexture = CreateTexture(mKnowsCompositor, mFormat, mSize, mWillReadFrequently); MOZ_ASSERT(newTexture); if (newTexture) { if (mTextures.append(newTexture)) { tex = newTexture; mBack = Some(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 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 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 dt(mDrawTarget); return dt.forget(); } bool PersistentBufferProviderShared::ReturnDrawTarget( already_AddRefed aDT) { RefPtr 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 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 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 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 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 aSnapshot) { RefPtr 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 front = GetTexture(mFront); RefPtr 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(0); } if (front == back) { mFront = mBack; } } if (front && front != back) { if (mTextures.append(front)) { mFront = Some(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