Replay buffer commands on paint thread when OMTP is enabled (bug 1399692 part 7, r=bas)
☠☠ backed out by ee367696744a ☠ ☠
authorRyan Hunt <rhunt@eqrion.net>
Thu, 26 Oct 2017 00:47:17 -0400
changeset 444281 0fc2414f146d8f5d08c97e5b7eedb25c5632ab2d
parent 444280 f235b12eda6efe0bdec8e6590d813738f53ffe82
child 444282 76bf99decf0906f0a6a4ad99539fd40f97c4539f
push id1618
push userCallek@gmail.com
push dateThu, 11 Jan 2018 17:45:48 +0000
treeherdermozilla-release@882ca853e05a [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbas
bugs1399692
milestone58.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
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,61 @@ 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 (!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