Replay buffer commands on paint thread when OMTP is enabled (
bug 1399692 part 7, r=bas)
This commit does the work of actually dispatching the recorded buffer operations
to the paint thread, and removing some main thread asserts from TextureClient.
MozReview-Commit-ID: CN3RoQPz9fP
--- a/gfx/layers/PaintThread.cpp
+++ b/gfx/layers/PaintThread.cpp
@@ -40,16 +40,44 @@ CapturedBufferState::Unrotate::UnrotateB
bool
CapturedBufferState::PrepareBuffer()
{
return (!mBufferCopy || mBufferCopy->CopyBuffer()) &&
(!mBufferUnrotate || mBufferUnrotate->UnrotateBuffer());
}
+void
+CapturedBufferState::GetTextureClients(nsTArray<RefPtr<TextureClient>>& aTextureClients)
+{
+ if (mBufferCopy) {
+ if (TextureClient* source = mBufferCopy->mSource->GetClient()) {
+ aTextureClients.AppendElement(source);
+ }
+ if (TextureClient* sourceOnWhite = mBufferCopy->mSource->GetClientOnWhite()) {
+ aTextureClients.AppendElement(sourceOnWhite);
+ }
+ if (TextureClient* destination = mBufferCopy->mDestination->GetClient()) {
+ aTextureClients.AppendElement(destination);
+ }
+ if (TextureClient* destinationOnWhite = mBufferCopy->mDestination->GetClientOnWhite()) {
+ aTextureClients.AppendElement(destinationOnWhite);
+ }
+ }
+
+ if (mBufferUnrotate) {
+ if (TextureClient* client = mBufferUnrotate->mBuffer->GetClient()) {
+ aTextureClients.AppendElement(client);
+ }
+ if (TextureClient* clientOnWhite = mBufferUnrotate->mBuffer->GetClientOnWhite()) {
+ aTextureClients.AppendElement(clientOnWhite);
+ }
+ }
+}
+
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
{
@@ -178,16 +206,66 @@ void
PaintThread::BeginLayerTransaction()
{
MOZ_ASSERT(NS_IsMainThread());
MOZ_ASSERT(!mInAsyncPaintGroup);
}
void
+PaintThread::PrepareBuffer(CapturedBufferState* aState)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(aState);
+
+ // If painting asynchronously, we need to acquire the compositor bridge which
+ // owns the underlying MessageChannel. Otherwise we leave it null and use
+ // synchronous dispatch.
+ RefPtr<CompositorBridgeChild> cbc;
+ if (!gfxPrefs::LayersOMTPForceSync()) {
+ cbc = CompositorBridgeChild::Get();
+ cbc->NotifyBeginAsyncPrepareBuffer(aState);
+ }
+ RefPtr<CapturedBufferState> state(aState);
+
+ RefPtr<PaintThread> self = this;
+ RefPtr<Runnable> task = NS_NewRunnableFunction("PaintThread::PrepareBuffer",
+ [self, cbc, state]() -> void
+ {
+ self->AsyncPrepareBuffer(cbc,
+ state);
+ });
+
+ if (cbc) {
+ sThread->Dispatch(task.forget());
+ } else {
+ SyncRunnable::DispatchToThread(sThread, task);
+ }
+}
+
+void
+PaintThread::AsyncPrepareBuffer(CompositorBridgeChild* aBridge,
+ CapturedBufferState* aState)
+{
+ MOZ_ASSERT(IsOnPaintThread());
+ MOZ_ASSERT(aState);
+
+ if (!mInAsyncPaintGroup) {
+ mInAsyncPaintGroup = true;
+ PROFILER_TRACING("Paint", "Rasterize", TRACING_INTERVAL_START);
+ }
+
+ if (!aState->PrepareBuffer()) {
+ gfxCriticalNote << "Failed to prepare buffers on the paint thread.";
+ }
+
+ aBridge->NotifyFinishedAsyncPrepareBuffer(aState);
+}
+
+void
PaintThread::PaintContents(CapturedPaintState* aState,
PrepDrawTargetForPaintingCallback aCallback)
{
MOZ_ASSERT(NS_IsMainThread());
MOZ_ASSERT(aState);
// If painting asynchronously, we need to acquire the compositor bridge which
// owns the underlying MessageChannel. Otherwise we leave it null and use
--- a/gfx/layers/PaintThread.h
+++ b/gfx/layers/PaintThread.h
@@ -95,16 +95,17 @@ public:
/**
* Prepares the rotated buffers for painting by copying a previous frame
* into the buffer and/or unrotating the pixels and returns whether the
* operations were successful. If this fails a new buffer should be created
* for the frame.
*/
bool PrepareBuffer();
+ void GetTextureClients(nsTArray<RefPtr<TextureClient>>& aTextureClients);
Maybe<Copy> mBufferCopy;
Maybe<Unrotate> mBufferUnrotate;
protected:
~CapturedBufferState() {}
};
@@ -125,16 +126,18 @@ public:
static bool IsOnPaintThread();
// Must be called on the main thread. Signifies that a new layer transaction
// is beginning. This must be called immediately after FlushAsyncPaints, and
// before any new painting occurs, as there can't be any async paints queued
// or running while this is executing.
void BeginLayerTransaction();
+ void PrepareBuffer(CapturedBufferState* aState);
+
void PaintContents(CapturedPaintState* aState,
PrepDrawTargetForPaintingCallback aCallback);
// Must be called on the main thread. Signifies that the current
// batch of CapturedPaintStates* for PaintContents have been recorded
// and the main thread is finished recording this layer.
void EndLayer();
@@ -155,16 +158,18 @@ public:
private:
PaintThread();
bool Init();
void ShutdownOnPaintThread();
void InitOnPaintThread();
+ void AsyncPrepareBuffer(CompositorBridgeChild* aBridge,
+ CapturedBufferState* aState);
void AsyncPaintContents(CompositorBridgeChild* aBridge,
CapturedPaintState* aState,
PrepDrawTargetForPaintingCallback aCallback);
void AsyncEndLayer();
void AsyncEndLayerTransaction(CompositorBridgeChild* aBridge,
SyncObjectClient* aSyncObject);
static StaticAutoPtr<PaintThread> sSingleton;
--- a/gfx/layers/RotatedBuffer.cpp
+++ b/gfx/layers/RotatedBuffer.cpp
@@ -492,27 +492,27 @@ RemoteRotatedBuffer::Lock(OpenMode aMode
if (!locked) {
Unlock();
return false;
}
mTarget = mClient->BorrowDrawTarget();
if (!mTarget || !mTarget->IsValid()) {
gfxCriticalNote << "Invalid draw target " << hexa(mTarget)
- << "in RemoteRotatedBuffer::Lock";
+ << " in RemoteRotatedBuffer::Lock";
Unlock();
return false;
}
if (mClientOnWhite) {
mTargetOnWhite = mClientOnWhite->BorrowDrawTarget();
if (!mTargetOnWhite || !mTargetOnWhite->IsValid()) {
gfxCriticalNote << "Invalid draw target(s) " << hexa(mTarget)
<< " and " << hexa(mTargetOnWhite)
- << "in RemoteRotatedBuffer::Lock";
+ << " in RemoteRotatedBuffer::Lock";
Unlock();
return false;
}
}
return true;
}
--- a/gfx/layers/RotatedBuffer.h
+++ b/gfx/layers/RotatedBuffer.h
@@ -238,16 +238,23 @@ public:
virtual gfx::SurfaceFormat GetFormat() const = 0;
virtual already_AddRefed<gfx::SourceSurface> GetSourceSurface(ContextSource aSource) const = 0;
virtual gfx::DrawTarget* GetDTBuffer() const = 0;
virtual gfx::DrawTarget* GetDTBufferOnWhite() const = 0;
+ virtual TextureClient* GetClient() const {
+ return nullptr;
+ }
+ virtual TextureClient* GetClientOnWhite() const {
+ return nullptr;
+ }
+
/**
* Creates a shallow copy of the rotated buffer with the same underlying
* texture clients and draw targets. Rotated buffers are not thread safe,
* so a copy needs to be sent for off main thread painting.
*/
virtual RefPtr<RotatedBuffer> ShallowCopy() const = 0;
protected:
@@ -333,19 +340,16 @@ public:
virtual RefPtr<RotatedBuffer> ShallowCopy() const override {
return new RemoteRotatedBuffer {
mClient, mClientOnWhite,
mTarget, mTargetOnWhite,
mBufferRect, mBufferRotation
};
}
- TextureClient* GetClient() const { return mClient; }
- TextureClient* GetClientOnWhite() const { return mClientOnWhite; }
-
void SyncWithObject(SyncObjectClient* aSyncObject);
void Clear();
private:
RemoteRotatedBuffer(TextureClient* aClient, TextureClient* aClientOnWhite,
gfx::DrawTarget* aTarget, gfx::DrawTarget* aTargetOnWhite,
const gfx::IntRect& aBufferRect,
const gfx::IntPoint& aBufferRotation)
--- a/gfx/layers/client/ClientPaintedLayer.cpp
+++ b/gfx/layers/client/ClientPaintedLayer.cpp
@@ -205,21 +205,27 @@ ClientPaintedLayer::PaintThebes(nsTArray
* the main thread. Sync OMTP is only meant to be used as a debugging tool.
*/
bool
ClientPaintedLayer::PaintOffMainThread()
{
uint32_t flags = GetPaintFlags();
PaintState state = mContentClient->BeginPaint(this, flags | ContentClient::PAINT_ASYNC);
+ bool didUpdate = false;
+
+ if (state.mBufferState) {
+ PaintThread::Get()->PrepareBuffer(state.mBufferState);
+ didUpdate = true;
+ }
+
if (!UpdatePaintRegion(state)) {
return false;
}
- bool didUpdate = false;
RotatedBuffer::DrawIterator iter;
// Debug Protip: Change to BorrowDrawTargetForPainting if using sync OMTP.
while (RefPtr<CapturedPaintState> captureState =
mContentClient->BorrowDrawTargetForRecording(state, &iter))
{
DrawTarget* target = captureState->mTargetDual;
if (!target || !target->IsValid()) {
--- a/gfx/layers/client/ContentClient.cpp
+++ b/gfx/layers/client/ContentClient.cpp
@@ -176,22 +176,41 @@ ContentClient::BeginPaint(PaintedLayer*
if ((!canHaveRotation && newParameters.IsRotated()) ||
(!canDrawRotated && newParameters.RectWrapsBuffer(drawBounds))) {
bufferState->mBufferUnrotate = Some(CapturedBufferState::Unrotate {
newParameters,
mBuffer->ShallowCopy(),
});
}
- if (bufferState->PrepareBuffer()) {
- if (bufferState->mBufferUnrotate) {
- newParameters.SetUnrotated();
+ // If we're async painting then return the buffer state to
+ // be dispatched to the paint thread, otherwise do it now
+ if (asyncPaint) {
+ // We cannot do a buffer unrotate if the buffer is already rotated
+ // and we're async painting as that may fail
+ if (!bufferState->mBufferUnrotate ||
+ mBuffer->BufferRotation() == IntPoint(0,0)) {
+ result.mBufferState = bufferState;
+
+ // We can then assume that preparing the buffer will always
+ // succeed and update our parameters unconditionally
+ if (bufferState->mBufferUnrotate) {
+ newParameters.SetUnrotated();
+ }
+ mBuffer->SetParameters(newParameters);
+ canReuseBuffer = true;
}
- mBuffer->SetParameters(newParameters);
- canReuseBuffer = true;
+ } else {
+ if (bufferState->PrepareBuffer()) {
+ if (bufferState->mBufferUnrotate) {
+ newParameters.SetUnrotated();
+ }
+ mBuffer->SetParameters(newParameters);
+ canReuseBuffer = true;
+ }
}
}
if (!canReuseBuffer) {
if (mBuffer->IsLocked()) {
mBuffer->Unlock();
}
dest.mBufferRect = ComputeBufferRect(dest.mNeededRegion.GetBounds());
@@ -237,19 +256,26 @@ ContentClient::BeginPaint(PaintedLayer*
RefPtr<CapturedBufferState> bufferState = new CapturedBufferState();
bufferState->mBufferCopy = Some(CapturedBufferState::Copy {
frontBuffer->ShallowCopy(),
newBuffer->ShallowCopy(),
newBuffer->BufferRect(),
});
- if (!bufferState->PrepareBuffer()) {
- gfxCriticalNote << "Failed to copy front buffer to back buffer.";
- return result;
+ // If we're async painting then return the buffer state to
+ // be dispatched to the paint thread, otherwise do it now
+ if (asyncPaint) {
+ MOZ_ASSERT(!result.mBufferState);
+ result.mBufferState = bufferState;
+ } else {
+ if (!bufferState->PrepareBuffer()) {
+ gfxCriticalNote << "Failed to copy front buffer to back buffer.";
+ return result;
+ }
}
if (dest.mMustRemoveFrontBuffer) {
Clear();
}
}
mBuffer = newBuffer;
--- a/gfx/layers/client/ContentClient.h
+++ b/gfx/layers/client/ContentClient.h
@@ -113,16 +113,17 @@ public:
, mContentType(gfxContentType::SENTINEL)
{}
nsIntRegion mRegionToDraw;
nsIntRegion mRegionToInvalidate;
SurfaceMode mMode;
DrawRegionClip mClip;
gfxContentType mContentType;
+ RefPtr<CapturedBufferState> mBufferState;
};
enum {
PAINT_WILL_RESAMPLE = 0x01,
PAINT_NO_ROTATION = 0x02,
PAINT_CAN_DRAW_ROTATED = 0x04,
PAINT_ASYNC = 0x08,
};
--- a/gfx/layers/client/TextureClient.cpp
+++ b/gfx/layers/client/TextureClient.cpp
@@ -687,20 +687,16 @@ TextureClient::BorrowDrawTarget()
// the DrawTarget, just to get a snapshot, which is legit in term of OpenMode
// but we should have a way to get a SourceSurface directly instead.
//MOZ_ASSERT(mOpenMode & OpenMode::OPEN_WRITE);
if (!IsValid() || !mIsLocked) {
return nullptr;
}
- if (!NS_IsMainThread()) {
- return nullptr;
- }
-
if (!mBorrowedDrawTarget) {
mBorrowedDrawTarget = mData->BorrowDrawTarget();
#ifdef DEBUG
mExpectedDtRefs = mBorrowedDrawTarget ? mBorrowedDrawTarget->refCount() : 0;
#endif
}
return mBorrowedDrawTarget;
--- a/gfx/layers/d3d11/TextureD3D11.cpp
+++ b/gfx/layers/d3d11/TextureD3D11.cpp
@@ -759,17 +759,17 @@ CreateTextureHostD3D11(const SurfaceDesc
}
return result.forget();
}
already_AddRefed<DrawTarget>
D3D11TextureData::BorrowDrawTarget()
{
- MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(NS_IsMainThread() || PaintThread::IsOnPaintThread());
if (!mDrawTarget && mTexture) {
// This may return a null DrawTarget
mDrawTarget = Factory::CreateDrawTargetForD3D11Texture(mTexture, mFormat);
if (!mDrawTarget) {
gfxCriticalNote << "Could not borrow DrawTarget (D3D11) " << (int)mFormat;
}
}
--- a/gfx/layers/ipc/CompositorBridgeChild.cpp
+++ b/gfx/layers/ipc/CompositorBridgeChild.cpp
@@ -1189,16 +1189,44 @@ CompositorBridgeChild::FlushAsyncPaints(
double ratio = double(mSlowFlushCount) / double(mTotalFlushCount);
Telemetry::ScalarSet(Telemetry::ScalarID::GFX_OMTP_PAINT_WAIT_RATIO,
uint32_t(ratio * 100 * 100));
}
}
void
+CompositorBridgeChild::NotifyBeginAsyncPrepareBuffer(CapturedBufferState* aState)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+
+ MonitorAutoLock lock(mPaintLock);
+
+ // We must not be waiting for paints (or buffer copying) to complete yet. This
+ // would imply we started a new paint without waiting for a previous one, which
+ // could lead to incorrect rendering or IPDL deadlocks.
+ MOZ_ASSERT(!mIsDelayingForAsyncPaints);
+
+ mOutstandingAsyncPaints++;
+
+ // Mark texture clients that they are being used for async painting, and
+ // make sure we hold them alive on the main thread.
+ aState->GetTextureClients(mTextureClientsForAsyncPaint);
+}
+
+void
+CompositorBridgeChild::NotifyFinishedAsyncPrepareBuffer(CapturedBufferState* aState)
+{
+ MOZ_ASSERT(PaintThread::IsOnPaintThread());
+
+ MonitorAutoLock lock(mPaintLock);
+ mOutstandingAsyncPaints--;
+}
+
+void
CompositorBridgeChild::NotifyBeginAsyncPaint(CapturedPaintState* aState)
{
MOZ_ASSERT(NS_IsMainThread());
MonitorAutoLock lock(mPaintLock);
// We must not be waiting for paints to complete yet. This would imply we
// started a new paint without waiting for a previous one, which could lead to
--- a/gfx/layers/ipc/CompositorBridgeChild.h
+++ b/gfx/layers/ipc/CompositorBridgeChild.h
@@ -40,16 +40,17 @@ using mozilla::dom::TabChild;
class IAPZCTreeManager;
class APZCTreeManagerChild;
class ClientLayerManager;
class CompositorBridgeParent;
class CompositorManagerChild;
class CompositorOptions;
class TextureClient;
class TextureClientPool;
+class CapturedBufferState;
class CapturedPaintState;
struct FrameMetrics;
class CompositorBridgeChild final : public PCompositorBridgeChild,
public TextureForwarder
{
typedef InfallibleTArray<AsyncParentMessageData> AsyncParentMessageArray;
@@ -223,16 +224,24 @@ public:
wr::PipelineId GetNextPipelineId();
// Must only be called from the main thread. Ensures that any paints from
// previous frames have been flushed. The main thread blocks until the
// operation completes.
void FlushAsyncPaints();
// Must only be called from the main thread. Notifies the CompositorBridge
+ // that the paint thread is going to begin preparing a buffer asynchronously.
+ void NotifyBeginAsyncPrepareBuffer(CapturedBufferState* aState);
+
+ // Must only be called from the paint thread. Notifies the CompositorBridge
+ // that the paint thread has finished an asynchronous buffer prepare.
+ void NotifyFinishedAsyncPrepareBuffer(CapturedBufferState* aState);
+
+ // Must only be called from the main thread. Notifies the CompositorBridge
// that the paint thread is going to begin painting asynchronously.
void NotifyBeginAsyncPaint(CapturedPaintState* aState);
// Must only be called from the paint thread. Notifies the CompositorBridge
// that the paint thread has finished an asynchronous paint request.
void NotifyFinishedAsyncPaint(CapturedPaintState* aState);
// Must only be called from the main thread. Notifies the CompositorBridge