Replay buffer commands on paint thread when OMTP is enabled (bug 1399692 part 7, r=bas) draft
authorRyan Hunt <rhunt@eqrion.net>
Thu, 26 Oct 2017 00:47:17 -0400
changeset 695948 10a55540d515c4654071d35388a9b681425e9c6e
parent 695947 c0b28ba4fbcd0d28e5cc94b5b36dfc59fd249c4b
child 695949 3e2d7d0481465fd2ed588f7b36fe49a7ec2c7a9f
push id88593
push userbmo:rhunt@eqrion.net
push dateFri, 10 Nov 2017 01:41:10 +0000
reviewersbas
bugs1399692
milestone58.0a1
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
gfx/layers/PaintThread.cpp
gfx/layers/PaintThread.h
gfx/layers/RotatedBuffer.cpp
gfx/layers/RotatedBuffer.h
gfx/layers/client/ClientPaintedLayer.cpp
gfx/layers/client/ContentClient.cpp
gfx/layers/client/ContentClient.h
gfx/layers/client/TextureClient.cpp
gfx/layers/d3d11/TextureD3D11.cpp
gfx/layers/ipc/CompositorBridgeChild.cpp
gfx/layers/ipc/CompositorBridgeChild.h
--- 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