/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- * 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 "Layers.h" #include "mozilla/layers/ShadowLayers.h" #include "mozilla/gfx/Logging.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); } 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() { 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; } //static already_AddRefed PersistentBufferProviderBasic::Create(gfx::IntSize aSize, gfx::SurfaceFormat aFormat, gfx::BackendType aBackend) { RefPtr dt = gfxPlatform::GetPlatform()->CreateDrawTargetForBackend(aBackend, aSize, aFormat); if (!dt) { return nullptr; } RefPtr provider = new PersistentBufferProviderBasic(dt); return provider.forget(); } //static already_AddRefed PersistentBufferProviderShared::Create(gfx::IntSize aSize, gfx::SurfaceFormat aFormat, ShadowLayerForwarder* aFwd) { if (!aFwd || !aFwd->GetTextureForwarder()->IPCOpen()) { return nullptr; } RefPtr texture = TextureClient::CreateForDrawing( aFwd, aFormat, aSize, BackendSelector::Canvas, TextureFlags::DEFAULT, TextureAllocationFlags::ALLOC_DEFAULT ); if (!texture) { return nullptr; } RefPtr provider = new PersistentBufferProviderShared(aSize, aFormat, aFwd, texture); return provider.forget(); } PersistentBufferProviderShared::PersistentBufferProviderShared(gfx::IntSize aSize, gfx::SurfaceFormat aFormat, ShadowLayerForwarder* aFwd, RefPtr& aTexture) : mSize(aSize) , mFormat(aFormat) , mFwd(aFwd) , mFront(Nothing()) { if (mTextures.append(aTexture)) { mBack = Some(0); } MOZ_COUNT_CTOR(PersistentBufferProviderShared); } PersistentBufferProviderShared::~PersistentBufferProviderShared() { MOZ_COUNT_DTOR(PersistentBufferProviderShared); if (IsActivityTracked()) { mFwd->GetActiveResourceTracker().RemoveObject(this); } Destroy(); } bool PersistentBufferProviderShared::SetForwarder(ShadowLayerForwarder* aFwd) { MOZ_ASSERT(aFwd); if (!aFwd) { return false; } if (mFwd == aFwd) { // The forwarder should not change most of the time. return true; } if (IsActivityTracked()) { mFwd->GetActiveResourceTracker().RemoveObject(this); } if (mFwd->GetTextureForwarder() != aFwd->GetTextureForwarder() || mFwd->GetCompositorBackendType() != aFwd->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) { RefPtr newTexture = TextureClient::CreateForDrawing( aFwd, mFormat, mSize, BackendSelector::Canvas, TextureFlags::DEFAULT, TextureAllocationFlags::ALLOC_DEFAULT ); 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; } } mFwd = aFwd; 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 (!mFwd->GetTextureForwarder()->IPCOpen()) { return nullptr; } MOZ_ASSERT(!mSnapshot); if (IsActivityTracked()) { mFwd->GetActiveResourceTracker().MarkUsed(this); } else { mFwd->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() >= 4) { // 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... mFwd->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) { gfxCriticalError() << "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 = 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(); } 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); mDrawTarget = nullptr; dt = nullptr; TextureClient* back = GetTexture(mBack); MOZ_ASSERT(back); 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) { texture->EnableReadLock(); } else { gfxCriticalNote << "PersistentBufferProviderShared: front buffer unavailable"; } return texture; } already_AddRefed PersistentBufferProviderShared::BorrowSnapshot() { MOZ_ASSERT(!mDrawTarget); auto front = GetTexture(mFront); if (!front || front->IsLocked()) { MOZ_ASSERT(false); return nullptr; } if (!front->Lock(OpenMode::OPEN_READ)) { return nullptr; } RefPtr dt = front->BorrowDrawTarget(); if (!dt) { front->Unlock(); return nullptr; } mSnapshot = dt->Snapshot(); RefPtr snapshot = mSnapshot; return snapshot.forget(); } void PersistentBufferProviderShared::ReturnSnapshot(already_AddRefed aSnapshot) { RefPtr snapshot = aSnapshot; MOZ_ASSERT(!snapshot || snapshot == mSnapshot); mSnapshot = nullptr; snapshot = nullptr; 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(); 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; for (auto& mTexture : mTextures) { TextureClient* texture = mTexture; if (texture && texture->IsLocked()) { MOZ_ASSERT(false); texture->Unlock(); } } mTextures.clear(); } } // namespace layers } // namespace mozilla