Implement buffer copying and clearing on the paint thread for multi tiled layers (
bug 1422392, r=nical)
This implements recording of buffer preparing commands for MultiTiledContentClient
and replaying it on the paint thread.
The order of locking for tiling is important and for buffer copying the front
buffer needs to be attempted to be locked before the back buffer. This means
we can't do this on the paint thread like we do for ContentClient, so instead
I've changed the OpenMode::ASYNC to be a flag that can be applied to
OpenMode::OPEN_READ as well as WRITE, as it looks like we can apply the same
logic for READ as WRITE.
MozReview-Commit-ID: ED6eeYx8dUV
--- a/gfx/layers/CompositorTypes.h
+++ b/gfx/layers/CompositorTypes.h
@@ -247,20 +247,21 @@ struct TextureInfo
*/
enum class OpenMode : uint8_t {
OPEN_NONE = 0,
OPEN_READ = 0x1,
OPEN_WRITE = 0x2,
// This is only used in conjunction with OMTP to indicate that the DrawTarget
// that is being borrowed will be painted asynchronously, and so will outlive
// the write lock.
- OPEN_ASYNC_WRITE = 0x04,
+ OPEN_ASYNC = 0x04,
OPEN_READ_WRITE = OPEN_READ|OPEN_WRITE,
- OPEN_READ_ASYNC_WRITE = OPEN_READ|OPEN_WRITE|OPEN_ASYNC_WRITE,
+ OPEN_READ_WRITE_ASYNC = OPEN_READ|OPEN_WRITE|OPEN_ASYNC,
+ OPEN_READ_ASYNC = OPEN_READ|OPEN_ASYNC,
OPEN_READ_ONLY = OPEN_READ,
OPEN_WRITE_ONLY = OPEN_WRITE,
};
MOZ_MAKE_ENUM_CLASS_BITWISE_OPERATORS(OpenMode)
// The kinds of mask texture a shader can support
// We rely on the items in this enum being sequential
enum class MaskType : uint8_t {
--- a/gfx/layers/PaintThread.cpp
+++ b/gfx/layers/PaintThread.cpp
@@ -89,16 +89,59 @@ CapturedBufferState::GetTextureClients(n
aTextureClients.AppendElement(destination);
}
if (TextureClient* destinationOnWhite = mBufferInitialize->mDestination->GetClientOnWhite()) {
aTextureClients.AppendElement(destinationOnWhite);
}
}
}
+bool
+CapturedTiledPaintState::Copy::CopyBuffer()
+{
+ RefPtr<gfx::SourceSurface> source = mSource->Snapshot();
+
+ // This operation requires the destination draw target to be untranslated,
+ // but the destination will have a transform from being part of a tiled draw
+ // target. However in this case, CopySurface ignores transforms so we don't
+ // need to do anything.
+ mDestination->CopySurface(source,
+ mBounds,
+ mBounds.TopLeft());
+ return true;
+}
+
+void
+CapturedTiledPaintState::Clear::ClearBuffer()
+{
+ // See the comment in CopyBuffer for why we need to temporarily reset
+ // the transform of the draw target.
+ Matrix oldTransform = mTarget->GetTransform();
+ mTarget->SetTransform(Matrix());
+
+ if (mTargetOnWhite) {
+ mTargetOnWhite->SetTransform(Matrix());
+ for (auto iter = mDirtyRegion.RectIter(); !iter.Done(); iter.Next()) {
+ const gfx::Rect drawRect(iter.Get().x, iter.Get().y,
+ iter.Get().width, iter.Get().height);
+ mTarget->FillRect(drawRect, ColorPattern(Color(0.0, 0.0, 0.0, 1.0)));
+ mTargetOnWhite->FillRect(drawRect, ColorPattern(Color(1.0, 1.0, 1.0, 1.0)));
+ }
+ mTargetOnWhite->SetTransform(oldTransform);
+ } else {
+ for (auto iter = mDirtyRegion.RectIter(); !iter.Done(); iter.Next()) {
+ const gfx::Rect drawRect(iter.Get().x, iter.Get().y,
+ iter.Get().width, iter.Get().height);
+ mTarget->ClearRect(drawRect);
+ }
+ }
+
+ mTarget->SetTransform(oldTransform);
+}
+
StaticAutoPtr<PaintThread> PaintThread::sSingleton;
StaticRefPtr<nsIThread> PaintThread::sThread;
PlatformThreadId PaintThread::sThreadId;
// RAII make sure we clean up and restore our draw targets
// when we paint async.
struct MOZ_STACK_CLASS AutoCapturedPaintSetup
{
@@ -373,16 +416,24 @@ PaintThread::AsyncPaintTiledContents(Com
MOZ_ASSERT(IsOnPaintThread());
MOZ_ASSERT(aState);
if (!mInAsyncPaintGroup) {
mInAsyncPaintGroup = true;
PROFILER_TRACING("Paint", "Rasterize", TRACING_INTERVAL_START);
}
+ for (auto& copy : aState->mCopies) {
+ copy.CopyBuffer();
+ }
+
+ for (auto& clear : aState->mClears) {
+ clear.ClearBuffer();
+ }
+
DrawTarget* target = aState->mTargetTiled;
DrawTargetCapture* capture = aState->mCapture;
// Draw all the things into the actual dest target.
target->DrawCapturedDT(capture, Matrix());
if (!mDrawTargetsToFlush.Contains(target)) {
mDrawTargetsToFlush.AppendElement(target);
--- a/gfx/layers/PaintThread.h
+++ b/gfx/layers/PaintThread.h
@@ -112,24 +112,59 @@ protected:
typedef bool (*PrepDrawTargetForPaintingCallback)(CapturedPaintState* aPaintState);
// Holds the key operations needed to update a tiled content client on the
// paint thread.
class CapturedTiledPaintState {
NS_INLINE_DECL_THREADSAFE_REFCOUNTING(CapturedPaintState)
public:
+ struct Copy {
+ Copy(RefPtr<gfx::DrawTarget> aSource,
+ RefPtr<gfx::DrawTarget> aDestination,
+ gfx::IntRect aBounds)
+ : mSource(aSource)
+ , mDestination(aDestination)
+ , mBounds(aBounds)
+ {}
+
+ bool CopyBuffer();
+
+ RefPtr<gfx::DrawTarget> mSource;
+ RefPtr<gfx::DrawTarget> mDestination;
+ gfx::IntRect mBounds;
+ };
+
+ struct Clear {
+ Clear(RefPtr<gfx::DrawTarget> aTarget,
+ RefPtr<gfx::DrawTarget> aTargetOnWhite,
+ nsIntRegion aDirtyRegion)
+ : mTarget(aTarget)
+ , mTargetOnWhite(aTargetOnWhite)
+ , mDirtyRegion(aDirtyRegion)
+ {}
+
+ void ClearBuffer();
+
+ RefPtr<gfx::DrawTarget> mTarget;
+ RefPtr<gfx::DrawTarget> mTargetOnWhite;
+ nsIntRegion mDirtyRegion;
+ };
+
CapturedTiledPaintState(gfx::DrawTarget* aTargetTiled,
gfx::DrawTargetCapture* aCapture)
: mTargetTiled(aTargetTiled)
, mCapture(aCapture)
{}
RefPtr<gfx::DrawTarget> mTargetTiled;
RefPtr<gfx::DrawTargetCapture> mCapture;
+ std::vector<Copy> mCopies;
+ std::vector<Clear> mClears;
+
std::vector<RefPtr<TextureClient>> mClients;
protected:
virtual ~CapturedTiledPaintState() {}
};
class CompositorBridgeChild;
--- a/gfx/layers/client/ContentClient.cpp
+++ b/gfx/layers/client/ContentClient.cpp
@@ -152,17 +152,17 @@ ContentClient::BeginPaint(PaintedLayer*
// Also disable buffer rotation when using webrender.
bool canHaveRotation = gfxPlatform::BufferRotationEnabled() &&
!(aFlags & (PAINT_WILL_RESAMPLE | PAINT_NO_ROTATION)) &&
!(aLayer->Manager()->AsWebRenderLayerManager());
bool canDrawRotated = aFlags & PAINT_CAN_DRAW_ROTATED;
bool asyncPaint = (aFlags & PAINT_ASYNC);
IntRect drawBounds = result.mRegionToDraw.GetBounds();
- OpenMode lockMode = asyncPaint ? OpenMode::OPEN_READ_ASYNC_WRITE
+ OpenMode lockMode = asyncPaint ? OpenMode::OPEN_READ_WRITE_ASYNC
: OpenMode::OPEN_READ_WRITE;
if (asyncPaint) {
result.mBufferState = new CapturedBufferState();
}
if (mBuffer) {
if (mBuffer->Lock(lockMode)) {
--- a/gfx/layers/client/SingleTiledContentClient.cpp
+++ b/gfx/layers/client/SingleTiledContentClient.cpp
@@ -146,17 +146,18 @@ ClientSingleTiledLayerBuffer::PaintThebe
nsIntRegion extraPainted;
RefPtr<TextureClient> backBufferOnWhite;
RefPtr<TextureClient> backBuffer =
mTile.GetBackBuffer(mCompositableClient,
tileDirtyRegion,
content, mode,
extraPainted,
TilePaintFlags::None,
- &backBufferOnWhite);
+ &backBufferOnWhite,
+ nullptr);
// Mark the area we need to paint in the back buffer as invalid in the
// front buffer as they will become out of sync.
mTile.mInvalidFront.OrWith(tileDirtyRegion);
// Add backbuffer's invalid region to the dirty region to be painted.
// This will be empty if we were able to copy from the front in to the back.
paintRegion.OrWith(mTile.mInvalidBack.MovedBy(mTilingOrigin));
--- a/gfx/layers/client/TextureClient.cpp
+++ b/gfx/layers/client/TextureClient.cpp
@@ -546,17 +546,17 @@ TextureClient::Unlock()
{
MOZ_ASSERT(IsValid());
MOZ_ASSERT(mIsLocked);
if (!IsValid() || !mIsLocked) {
return;
}
if (mBorrowedDrawTarget) {
- if (!(mOpenMode & OpenMode::OPEN_ASYNC_WRITE)) {
+ if (!(mOpenMode & OpenMode::OPEN_ASYNC)) {
if (mOpenMode & OpenMode::OPEN_WRITE) {
mBorrowedDrawTarget->Flush();
if (mReadbackSink && !mData->ReadBack(mReadbackSink)) {
// Fallback implementation for reading back, because mData does not
// have a backend-specific implementation and returned false.
RefPtr<SourceSurface> snapshot = mBorrowedDrawTarget->Snapshot();
RefPtr<DataSourceSurface> dataSurf = snapshot->GetDataSurface();
mReadbackSink->ProcessReadback(dataSurf);
--- a/gfx/layers/client/TiledContentClient.cpp
+++ b/gfx/layers/client/TiledContentClient.cpp
@@ -473,41 +473,61 @@ TileClient::Flip()
mInvalidFront = mInvalidBack;
mInvalidBack = invalidFront;
}
static bool
CopyFrontToBack(TextureClient* aFront,
TextureClient* aBack,
const gfx::IntRect& aRectToCopy,
- TilePaintFlags aFlags)
+ TilePaintFlags aFlags,
+ std::vector<CapturedTiledPaintState::Copy>* aCopies)
{
bool asyncPaint = !!(aFlags & TilePaintFlags::Async);
- OpenMode lockMode = asyncPaint ? OpenMode::OPEN_READ_ASYNC_WRITE
- : OpenMode::OPEN_READ_WRITE;
+ OpenMode asyncFlags = asyncPaint ? OpenMode::OPEN_ASYNC : OpenMode::OPEN_NONE;
- TextureClientAutoLock frontLock(aFront, OpenMode::OPEN_READ);
+ TextureClientAutoLock frontLock(aFront, OpenMode::OPEN_READ | asyncFlags);
if (!frontLock.Succeeded()) {
return false;
}
- if (!aBack->Lock(lockMode)) {
+ if (!aBack->Lock(OpenMode::OPEN_READ_WRITE | asyncFlags)) {
gfxCriticalError() << "[Tiling:Client] Failed to lock the tile's back buffer";
return false;
}
- gfx::IntPoint rectToCopyTopLeft = aRectToCopy.TopLeft();
- aFront->CopyToTextureClient(aBack, &aRectToCopy, &rectToCopyTopLeft);
+ RefPtr<gfx::DrawTarget> backBuffer = aBack->BorrowDrawTarget();
+ if (!backBuffer) {
+ gfxWarning() << "[Tiling:Client] Failed to aquire the back buffer's draw target";
+ return false;
+ }
+
+ RefPtr<gfx::DrawTarget> frontBuffer = aFront->BorrowDrawTarget();
+ if (!frontBuffer) {
+ gfxWarning() << "[Tiling:Client] Failed to aquire the front buffer's draw target";
+ return false;
+ }
+
+ auto copy = CapturedTiledPaintState::Copy{
+ frontBuffer, backBuffer, aRectToCopy
+ };
+
+ if (asyncPaint) {
+ aCopies->push_back(copy);
+ } else {
+ copy.CopyBuffer();
+ }
return true;
}
void
TileClient::ValidateBackBufferFromFront(const nsIntRegion& aDirtyRegion,
nsIntRegion& aAddPaintedRegion,
- TilePaintFlags aFlags)
+ TilePaintFlags aFlags,
+ std::vector<CapturedTiledPaintState::Copy>* aCopies)
{
if (mBackBuffer && mFrontBuffer) {
gfx::IntSize tileSize = mFrontBuffer->GetSize();
const IntRect tileRect = IntRect(0, 0, tileSize.width, tileSize.height);
if (aDirtyRegion.Contains(tileRect)) {
// The dirty region means that we no longer need the front buffer, so
// discard it.
@@ -525,20 +545,20 @@ TileClient::ValidateBackBufferFromFront(
return;
}
// Copy the bounding rect of regionToCopy. As tiles are quite small, it
// is unlikely that we'd save much by copying each individual rect of the
// region, but we can reevaluate this if it becomes an issue.
const IntRect rectToCopy = regionToCopy.GetBounds();
gfx::IntRect gfxRectToCopy(rectToCopy.x, rectToCopy.y, rectToCopy.Width(), rectToCopy.Height());
- if (CopyFrontToBack(mFrontBuffer, mBackBuffer, gfxRectToCopy, aFlags)) {
+ if (CopyFrontToBack(mFrontBuffer, mBackBuffer, gfxRectToCopy, aFlags, aCopies)) {
if (mBackBufferOnWhite) {
MOZ_ASSERT(mFrontBufferOnWhite);
- if (CopyFrontToBack(mFrontBufferOnWhite, mBackBufferOnWhite, gfxRectToCopy, aFlags)) {
+ if (CopyFrontToBack(mFrontBufferOnWhite, mBackBufferOnWhite, gfxRectToCopy, aFlags, aCopies)) {
mInvalidBack.SetEmpty();
}
}
}
}
}
}
@@ -629,17 +649,18 @@ CreateBackBufferTexture(TextureClient* a
TextureClient*
TileClient::GetBackBuffer(CompositableClient& aCompositable,
const nsIntRegion& aDirtyRegion,
gfxContentType aContent,
SurfaceMode aMode,
nsIntRegion& aAddPaintedRegion,
TilePaintFlags aFlags,
- RefPtr<TextureClient>* aBackBufferOnWhite)
+ RefPtr<TextureClient>* aBackBufferOnWhite,
+ std::vector<CapturedTiledPaintState::Copy>* aCopies)
{
if (!mAllocator) {
gfxCriticalError() << "[TileClient] Missing TextureClientAllocator.";
return nullptr;
}
if (aMode != SurfaceMode::SURFACE_COMPONENT_ALPHA) {
// It can happen that a component-alpha layer stops being on component alpha
// on the next frame, just drop the buffers on white if that happens.
@@ -684,20 +705,20 @@ TileClient::GetBackBuffer(CompositableCl
if (!mBackBufferOnWhite) {
DiscardBackBuffer();
DiscardFrontBuffer();
return nullptr;
}
mInvalidBack = IntRect(IntPoint(), mBackBufferOnWhite->GetSize());
}
- ValidateBackBufferFromFront(aDirtyRegion, aAddPaintedRegion, aFlags);
+ ValidateBackBufferFromFront(aDirtyRegion, aAddPaintedRegion, aFlags, aCopies);
}
- OpenMode lockMode = aFlags & TilePaintFlags::Async ? OpenMode::OPEN_READ_ASYNC_WRITE
+ OpenMode lockMode = aFlags & TilePaintFlags::Async ? OpenMode::OPEN_READ_WRITE_ASYNC
: OpenMode::OPEN_READ_WRITE;
if (!mBackBuffer->IsLocked()) {
if (!mBackBuffer->Lock(lockMode)) {
gfxCriticalError() << "[Tiling:Client] Failed to lock a tile (B)";
DiscardBackBuffer();
DiscardFrontBuffer();
return nullptr;
@@ -960,16 +981,18 @@ void ClientMultiTiledLayerBuffer::Update
}
}
oldRetainedTiles.Clear();
nsIntRegion paintRegion = aPaintRegion;
nsIntRegion dirtyRegion = aDirtyRegion;
if (!paintRegion.IsEmpty()) {
+ MOZ_ASSERT(mPaintClears.size() == 0);
+ MOZ_ASSERT(mPaintCopies.size() == 0);
for (size_t i = 0; i < newTileCount; ++i) {
const TileIntPoint tilePosition = newTiles.TilePosition(i);
IntPoint tileOffset = GetTileOffset(tilePosition);
nsIntRegion tileDrawRegion = IntRect(tileOffset, scaledTileSize);
tileDrawRegion.AndWith(paintRegion);
if (tileDrawRegion.IsEmpty()) {
@@ -1018,20 +1041,25 @@ void ClientMultiTiledLayerBuffer::Update
DrawRegionClip::DRAW, nsIntRegion(), mCallbackData);
ctx = nullptr;
// Replay on the paint thread
RefPtr<CapturedTiledPaintState> capturedState =
new CapturedTiledPaintState(drawTarget,
captureDT);
capturedState->mClients = std::move(mPaintTilesTextureClients);
+ capturedState->mCopies = std::move(mPaintCopies);
+ capturedState->mClears = std::move(mPaintClears);
PaintThread::Get()->PaintTiledContents(capturedState);
mManager->SetQueuedAsyncPaints();
} else {
+ MOZ_ASSERT(mPaintCopies.size() == 0);
+ MOZ_ASSERT(mPaintClears.size() == 0);
+
// Draw into the tiled draw target
RefPtr<gfxContext> ctx = gfxContext::CreateOrNull(drawTarget);
MOZ_ASSERT(ctx); // already checked the draw target above
ctx->SetMatrix(
ctx->CurrentMatrix().PreScale(mResolution, mResolution).PreTranslate(-mTilingOrigin));
mCallback(&mPaintedLayer, ctx, paintRegion, dirtyRegion,
DrawRegionClip::DRAW, nsIntRegion(), mCallbackData);
@@ -1117,17 +1145,18 @@ ClientMultiTiledLayerBuffer::ValidateTil
nsIntRegion extraPainted;
RefPtr<TextureClient> backBufferOnWhite;
RefPtr<TextureClient> backBuffer =
aTile.GetBackBuffer(mCompositableClient,
offsetScaledDirtyRegion,
content, mode,
extraPainted,
aFlags,
- &backBufferOnWhite);
+ &backBufferOnWhite,
+ &mPaintCopies);
// Mark the area we need to paint in the back buffer as invalid in the
// front buffer as they will become out of sync.
aTile.mInvalidFront.OrWith(offsetScaledDirtyRegion);
// Add backbuffer's invalid region to the dirty region to be painted.
// This will be empty if we were able to copy from the front in to the back.
nsIntRegion invalidBack = aTile.mInvalidBack;
@@ -1168,26 +1197,26 @@ ClientMultiTiledLayerBuffer::ValidateTil
if (aFlags & TilePaintFlags::Async) {
mPaintTilesTextureClients.push_back(backBuffer);
if (backBufferOnWhite) {
mPaintTilesTextureClients.push_back(backBufferOnWhite);
}
}
- for (auto iter = offsetScaledDirtyRegion.RectIter(); !iter.Done(); iter.Next()) {
- const gfx::Rect drawRect(iter.Get().x, iter.Get().y,
- iter.Get().width, iter.Get().height);
+ auto clear = CapturedTiledPaintState::Clear{
+ dt,
+ dtOnWhite,
+ offsetScaledDirtyRegion
+ };
- if (mode == SurfaceMode::SURFACE_COMPONENT_ALPHA) {
- dt->FillRect(drawRect, ColorPattern(Color(0.0, 0.0, 0.0, 1.0)));
- dtOnWhite->FillRect(drawRect, ColorPattern(Color(1.0, 1.0, 1.0, 1.0)));
- } else if (content == gfxContentType::COLOR_ALPHA) {
- dt->ClearRect(drawRect);
- }
+ if (aFlags & TilePaintFlags::Async) {
+ mPaintClears.push_back(clear);
+ } else {
+ clear.ClearBuffer();
}
// The new buffer is now validated, remove the dirty region from it.
aTile.mInvalidBack.SubOut(offsetScaledDirtyRegion);
aTile.Flip();
return true;
--- a/gfx/layers/client/TiledContentClient.h
+++ b/gfx/layers/client/TiledContentClient.h
@@ -19,16 +19,17 @@
#include "mozilla/RefPtr.h" // for RefPtr
#include "mozilla/ipc/Shmem.h" // for Shmem
#include "mozilla/ipc/SharedMemory.h" // for SharedMemory
#include "mozilla/layers/AsyncCompositionManager.h" // for ViewTransform
#include "mozilla/layers/CompositableClient.h" // for CompositableClient
#include "mozilla/layers/CompositorTypes.h" // for TextureInfo, etc
#include "mozilla/layers/LayersMessages.h" // for TileDescriptor
#include "mozilla/layers/LayersTypes.h" // for TextureDumpMode
+#include "mozilla/layers/PaintThread.h" // for CapturedTiledPaintState
#include "mozilla/layers/TextureClient.h"
#include "mozilla/layers/TextureClientPool.h"
#include "ClientLayerManager.h"
#include "mozilla/mozalloc.h" // for operator delete
#include "nsISupportsImpl.h" // for MOZ_COUNT_DTOR
#include "nsPoint.h" // for nsIntPoint
#include "nsRect.h" // for mozilla::gfx::IntRect
#include "nsRegion.h" // for nsIntRegion
@@ -125,17 +126,18 @@ struct TileClient
*
* If nullptr is returned, aTextureClientOnWhite is undefined.
*/
TextureClient* GetBackBuffer(CompositableClient&,
const nsIntRegion& aDirtyRegion,
gfxContentType aContent, SurfaceMode aMode,
nsIntRegion& aAddPaintedRegion,
TilePaintFlags aFlags,
- RefPtr<TextureClient>* aTextureClientOnWhite);
+ RefPtr<TextureClient>* aTextureClientOnWhite,
+ std::vector<CapturedTiledPaintState::Copy>* aCopies);
void DiscardFrontBuffer();
void DiscardBackBuffer();
/* We wrap the back buffer in a class that disallows assignment
* so that we can track when ever it changes so that we can update
* the expiry tracker for expiring the back buffers */
@@ -163,17 +165,18 @@ struct TileClient
nsIntRegion mInvalidFront;
nsIntRegion mInvalidBack;
nsExpirationState mExpirationState;
private:
// Copies dirty pixels from the front buffer into the back buffer,
// and records the copied region in aAddPaintedRegion.
void ValidateBackBufferFromFront(const nsIntRegion &aDirtyRegion,
nsIntRegion& aAddPaintedRegion,
- TilePaintFlags aFlags);
+ TilePaintFlags aFlags,
+ std::vector<CapturedTiledPaintState::Copy>* aCopies);
};
/**
* This struct stores all the data necessary to perform a paint so that it
* doesn't need to be recalculated on every repeated transaction.
*/
struct BasicTiledLayerPaintData {
/*
@@ -441,16 +444,18 @@ private:
nsIntRegion mNewValidRegion;
SharedFrameMetricsHelper* mSharedFrameMetricsHelper;
// Parameters that are collected during Update for a paint before they
// are either executed or replayed on the paint thread.
std::vector<gfx::Tile> mPaintTiles;
std::vector<RefPtr<TextureClient>> mPaintTilesTextureClients;
+ std::vector<CapturedTiledPaintState::Copy> mPaintCopies;
+ std::vector<CapturedTiledPaintState::Clear> mPaintClears;
/**
* While we're adding tiles, this is used to keep track of the position of
* the top-left of the top-left-most tile. When we come to wrap the tiles in
* TiledDrawTarget we subtract the value of this member from each tile's
* offset so that all the tiles have a positive offset, then add a
* translation to the TiledDrawTarget to compensate. This is important so
* that the mRect of the TiledDrawTarget is always at a positive x/y