Create a PaintWorker thread pool and dispatch tiles to it (bug 1425056, r=bas) draft
authorRyan Hunt <rhunt@eqrion.net>
Fri, 08 Dec 2017 01:18:05 -0600
changeset 713694 e7b08dc19e36ae00a59f8f27f97e4dab73ff5b71
parent 713693 3646c7c12727f777e7dd1994e8ab2f17ebeb329e
child 744416 8cabe1852646c222470608f376d58503638fdba9
push id93735
push userbmo:rhunt@eqrion.net
push dateWed, 20 Dec 2017 22:39:00 +0000
reviewersbas
bugs1425056
milestone59.0a1
Create a PaintWorker thread pool and dispatch tiles to it (bug 1425056, r=bas) This commit adds a paint worker thread pool to PaintThread, and dispatches tiled paints to it. The thread pool is only created if tiling is enabled, and its size is set by 'layers.omtp.paint-workers' and defaults to 1. If -1 is specified, it will be sized to 'max((cpu_cores * 3) / 4, 1)'. The one tricky part of dispatching tiled paints to a thread pool is the AsyncEndLayerTransaction message that must execute once all paints are finished. Previously, this runnable would be queued after all the paints had been queued, ensuring it would be run after they had all completed. With a thread pool, there is no guarantee. Instead this commit, uses a flag on CompositorBridgeChild to signify whether all of the paints have been queued ('mOutstandingAsyncEndLayerTransaction'), and after every tiled paint it is examined to see if that paint was the last paint, and if it is to run AsyncEndLayerTransaction. In addition, if the async paints complete before we even mark the end of the layer transaction, we queue it like normal. The profiler markers are also complicated by using a thread pool. I don't know of a great way to keep them working as they are per thread, so for now I've removed them. I may have been the only one using them anyway. MozReview-Commit-ID: 5LIJ9GWSfCn
gfx/layers/PaintThread.cpp
gfx/layers/PaintThread.h
gfx/layers/client/ClientLayerManager.cpp
gfx/layers/client/ClientPaintedLayer.cpp
gfx/layers/ipc/CompositorBridgeChild.cpp
gfx/layers/ipc/CompositorBridgeChild.h
gfx/thebes/gfxPlatform.cpp
gfx/thebes/gfxPlatform.h
gfx/thebes/gfxPrefs.h
modules/libpref/init/all.js
xpcom/base/moz.build
--- a/gfx/layers/PaintThread.cpp
+++ b/gfx/layers/PaintThread.cpp
@@ -1,25 +1,32 @@
 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
 /* 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 "PaintThread.h"
 
+#include <algorithm>
+
 #include "base/task.h"
+#include "gfxPlatform.h"
 #include "gfxPrefs.h"
 #include "GeckoProfiler.h"
 #include "mozilla/layers/CompositorBridgeChild.h"
 #include "mozilla/layers/ShadowLayers.h"
 #include "mozilla/layers/SyncObject.h"
 #include "mozilla/gfx/2D.h"
 #include "mozilla/Preferences.h"
+#include "mozilla/SharedThreadPool.h"
 #include "mozilla/SyncRunnable.h"
+#include "nsIPropertyBag2.h"
+#include "nsServiceManagerUtils.h"
+#include "nsSystemInfo.h"
 
 // Uncomment the following line to dispatch sync runnables when
 // painting so that rasterization happens synchronously from
 // the perspective of the main thread
 // #define OMTP_FORCE_SYNC
 
 namespace mozilla {
 namespace layers {
@@ -93,60 +100,57 @@ CapturedTiledPaintState::Clear::ClearBuf
 
   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
-{
-  AutoCapturedPaintSetup(CapturedPaintState* aState, CompositorBridgeChild* aBridge)
-  : mState(aState)
-  , mTarget(aState->mTargetDual)
-  , mRestorePermitsSubpixelAA(mTarget->GetPermitSubpixelAA())
-  , mOldTransform(mTarget->GetTransform())
-  , mBridge(aBridge)
-  {
-    mTarget->SetTransform(aState->mCapture->GetTransform());
-    mTarget->SetPermitSubpixelAA(aState->mCapture->GetPermitSubpixelAA());
-  }
-
-  ~AutoCapturedPaintSetup()
-  {
-    mTarget->SetTransform(mOldTransform);
-    mTarget->SetPermitSubpixelAA(mRestorePermitsSubpixelAA);
-    mBridge->NotifyFinishedAsyncPaint(mState);
-  }
-
-  RefPtr<CapturedPaintState> mState;
-  RefPtr<DrawTarget> mTarget;
-  bool mRestorePermitsSubpixelAA;
-  Matrix mOldTransform;
-  RefPtr<CompositorBridgeChild> mBridge;
-};
-
 PaintThread::PaintThread()
-  : mInAsyncPaintGroup(false)
 {
 }
 
 void
 PaintThread::Release()
 {
 }
 
 void
 PaintThread::AddRef()
 {
 }
 
+/* static */ int32_t
+PaintThread::CalculatePaintWorkerCount()
+{
+  int32_t cpuCores = 1;
+  nsCOMPtr<nsIPropertyBag2> systemInfo = do_GetService(NS_SYSTEMINFO_CONTRACTID);
+  if (systemInfo) {
+    nsresult rv = systemInfo->GetPropertyAsInt32(NS_LITERAL_STRING("cpucores"), &cpuCores);
+    if (NS_FAILED(rv)) {
+      cpuCores = 1;
+    }
+  }
+
+  int32_t workerCount = gfxPrefs::LayersOMTPPaintWorkers();
+
+  // If not manually specified, default to (cpuCores * 3) / 4
+  if (workerCount < 1) {
+    workerCount = std::max((cpuCores * 3) / 4, 1);
+  }
+
+  // Sanity check
+  if (workerCount > 32) {
+    workerCount = 32;
+  }
+
+  return workerCount;
+}
+
 /* static */ void
 PaintThread::Start()
 {
   PaintThread::sSingleton = new PaintThread();
 
   if (!PaintThread::sSingleton->Init()) {
     gfxCriticalNote << "Unable to start paint thread";
     PaintThread::sSingleton = nullptr;
@@ -160,16 +164,21 @@ PaintThread::Init()
 
   RefPtr<nsIThread> thread;
   nsresult rv = NS_NewNamedThread("PaintThread", getter_AddRefs(thread));
   if (NS_FAILED(rv)) {
     return false;
   }
   sThread = thread;
 
+  if (gfxPlatform::UsesTiling()) {
+    int32_t paintWorkerCount = PaintThread::CalculatePaintWorkerCount();
+    mPaintWorkers = SharedThreadPool::Get(NS_LITERAL_CSTRING("PaintWorker"), paintWorkerCount);
+  }
+
   nsCOMPtr<nsIRunnable> paintInitTask =
     NewRunnableMethod("PaintThread::InitOnPaintThread",
                       this, &PaintThread::InitOnPaintThread);
   SyncRunnable::DispatchToThread(sThread, paintInitTask);
   return true;
 }
 
 void
@@ -215,22 +224,20 @@ PaintThread::Get()
 }
 
 /* static */ bool
 PaintThread::IsOnPaintThread()
 {
   return sThreadId == PlatformThread::CurrentId();
 }
 
-void
-PaintThread::BeginLayerTransaction()
+bool
+PaintThread::IsOnPaintWorkerThread()
 {
-  MOZ_ASSERT(NS_IsMainThread());
-
-  MOZ_ASSERT(!mInAsyncPaintGroup);
+  return mPaintWorkers && mPaintWorkers->IsOnCurrentThread();
 }
 
 void
 PaintThread::PrepareBuffer(CapturedBufferState* aState)
 {
   MOZ_ASSERT(NS_IsMainThread());
   MOZ_ASSERT(aState);
 
@@ -259,26 +266,25 @@ PaintThread::PrepareBuffer(CapturedBuffe
 
 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->NotifyFinishedAsyncPaint(aState);
+  if (aBridge->NotifyFinishedAsyncWorkerPaint(aState)) {
+    // We need to dispatch this task to ourselves so it runs after
+    // AsyncEndLayer
+    DispatchEndLayerTransaction(aBridge);
+  }
 }
 
 void
 PaintThread::PaintContents(CapturedPaintState* aState,
                            PrepDrawTargetForPaintingCallback aCallback)
 {
   MOZ_ASSERT(NS_IsMainThread());
   MOZ_ASSERT(aState);
@@ -307,41 +313,48 @@ PaintThread::PaintContents(CapturedPaint
 void
 PaintThread::AsyncPaintContents(CompositorBridgeChild* aBridge,
                                 CapturedPaintState* aState,
                                 PrepDrawTargetForPaintingCallback aCallback)
 {
   MOZ_ASSERT(IsOnPaintThread());
   MOZ_ASSERT(aState);
 
-  if (!mInAsyncPaintGroup) {
-    mInAsyncPaintGroup = true;
-    PROFILER_TRACING("Paint", "Rasterize", TRACING_INTERVAL_START);
-  }
-
   DrawTarget* target = aState->mTargetDual;
   DrawTargetCapture* capture = aState->mCapture;
 
-  AutoCapturedPaintSetup setup(aState, aBridge);
+  Matrix oldTransform = target->GetTransform();
+  bool oldPermitsSubpixelAA = target->GetPermitSubpixelAA();
+
+  target->SetTransform(capture->GetTransform());
+  target->SetPermitSubpixelAA(capture->GetPermitSubpixelAA());
+
+  if (aCallback(aState)) {
+    // Draw all the things into the actual dest target.
+    target->DrawCapturedDT(capture, Matrix());
 
-  if (!aCallback(aState)) {
-    return;
+    if (!mDrawTargetsToFlush.Contains(target)) {
+      mDrawTargetsToFlush.AppendElement(target);
+    }
+
+    if (gfxPrefs::LayersOMTPReleaseCaptureOnMainThread()) {
+      // This should ensure the capture drawtarget, which may hold on to UnscaledFont objects,
+      // gets destroyed on the main thread (See bug 1404742). This assumes (unflushed) target
+      // DrawTargets do not themselves hold on to UnscaledFonts.
+      NS_ReleaseOnMainThreadSystemGroup("CapturePaintState::DrawTargetCapture", aState->mCapture.forget());
+    }
   }
 
-  // Draw all the things into the actual dest target.
-  target->DrawCapturedDT(capture, Matrix());
-  if (!mDrawTargetsToFlush.Contains(target)) {
-    mDrawTargetsToFlush.AppendElement(target);
-  }
+  target->SetTransform(oldTransform);
+  target->SetPermitSubpixelAA(oldPermitsSubpixelAA);
 
-  if (gfxPrefs::LayersOMTPReleaseCaptureOnMainThread()) {
-    // This should ensure the capture drawtarget, which may hold on to UnscaledFont objects,
-    // gets destroyed on the main thread (See bug 1404742). This assumes (unflushed) target
-    // DrawTargets do not themselves hold on to UnscaledFonts.
-    NS_ReleaseOnMainThreadSystemGroup("CapturePaintState::DrawTargetCapture", aState->mCapture.forget());
+  if (aBridge->NotifyFinishedAsyncWorkerPaint(aState)) {
+    // We need to dispatch this task to ourselves so it runs after
+    // AsyncEndLayer
+    DispatchEndLayerTransaction(aBridge);
   }
 }
 
 void
 PaintThread::PaintTiledContents(CapturedTiledPaintState* aState)
 {
   MOZ_ASSERT(NS_IsMainThread());
   MOZ_ASSERT(aState);
@@ -350,39 +363,33 @@ PaintThread::PaintTiledContents(Captured
   RefPtr<CapturedTiledPaintState> state(aState);
 
   cbc->NotifyBeginAsyncPaint(state);
 
   RefPtr<PaintThread> self = this;
   RefPtr<Runnable> task = NS_NewRunnableFunction("PaintThread::PaintTiledContents",
     [self, cbc, state]() -> void
   {
-    self->AsyncPaintTiledContents(cbc,
-                                  state);
+    self->AsyncPaintTiledContents(cbc, state);
   });
 
 #ifndef OMTP_FORCE_SYNC
-  sThread->Dispatch(task.forget());
+  mPaintWorkers->Dispatch(task.forget());
 #else
-  SyncRunnable::DispatchToThread(sThread, task);
+  SyncRunnable::DispatchToThread(mPaintWorkers, task);
 #endif
 }
 
 void
 PaintThread::AsyncPaintTiledContents(CompositorBridgeChild* aBridge,
                                      CapturedTiledPaintState* aState)
 {
-  MOZ_ASSERT(IsOnPaintThread());
+  MOZ_ASSERT(IsOnPaintWorkerThread());
   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();
   }
 
@@ -395,17 +402,43 @@ PaintThread::AsyncPaintTiledContents(Com
 
   if (gfxPrefs::LayersOMTPReleaseCaptureOnMainThread()) {
     // This should ensure the capture drawtarget, which may hold on to UnscaledFont objects,
     // gets destroyed on the main thread (See bug 1404742). This assumes (unflushed) target
     // DrawTargets do not themselves hold on to UnscaledFonts.
     NS_ReleaseOnMainThreadSystemGroup("CapturePaintState::DrawTargetCapture", aState->mCapture.forget());
   }
 
-  aBridge->NotifyFinishedAsyncPaint(aState);
+  {
+    RefPtr<CompositorBridgeChild> cbc(aBridge);
+    RefPtr<CapturedTiledPaintState> state(aState);
+
+    RefPtr<PaintThread> self = this;
+    RefPtr<Runnable> task = NS_NewRunnableFunction("PaintThread::AsyncPaintTiledContentsFinished",
+      [self, cbc, state]() -> void
+    {
+      self->AsyncPaintTiledContentsFinished(cbc, state);
+    });
+
+  #ifndef OMTP_FORCE_SYNC
+    sThread->Dispatch(task.forget());
+  #else
+    SyncRunnable::DispatchToThread(sThread, task);
+  #endif
+  }
+}
+
+void
+PaintThread::AsyncPaintTiledContentsFinished(CompositorBridgeChild* aBridge,
+                                             CapturedTiledPaintState* aState)
+{
+  MOZ_ASSERT(IsOnPaintThread());
+  if (aBridge->NotifyFinishedAsyncWorkerPaint(aState)) {
+    aBridge->NotifyFinishedAsyncEndLayerTransaction();
+  }
 }
 
 void
 PaintThread::EndLayer()
 {
   MOZ_ASSERT(NS_IsMainThread());
 
   RefPtr<PaintThread> self = this;
@@ -431,61 +464,68 @@ PaintThread::Dispatch(RefPtr<Runnable>& 
   SyncRunnable::DispatchToThread(sThread, aRunnable);
 #endif
 }
 
 void
 PaintThread::AsyncEndLayer()
 {
   MOZ_ASSERT(IsOnPaintThread());
+
   // Textureclient forces a flush once we "end paint", so
   // users of this texture expect all the drawing to be complete.
   // Force a flush now.
   for (size_t i = 0; i < mDrawTargetsToFlush.Length(); i++) {
     mDrawTargetsToFlush[i]->Flush();
   }
 
   mDrawTargetsToFlush.Clear();
 }
 
 void
 PaintThread::EndLayerTransaction(SyncObjectClient* aSyncObject)
 {
   MOZ_ASSERT(NS_IsMainThread());
 
   RefPtr<CompositorBridgeChild> cbc(CompositorBridgeChild::Get());
-  RefPtr<SyncObjectClient> syncObject(aSyncObject);
-
-  cbc->NotifyBeginAsyncEndLayerTransaction();
 
-  RefPtr<PaintThread> self = this;
-  RefPtr<Runnable> task = NS_NewRunnableFunction("PaintThread::AsyncEndLayerTransaction",
-    [self, cbc, syncObject]() -> void
-  {
-    self->AsyncEndLayerTransaction(cbc, syncObject);
-  });
+  if (cbc->NotifyBeginAsyncEndLayerTransaction(aSyncObject)) {
+    RefPtr<PaintThread> self = this;
+    RefPtr<Runnable> task = NS_NewRunnableFunction("PaintThread::AsyncEndLayerTransaction",
+      [self, cbc]() -> void
+    {
+      self->AsyncEndLayerTransaction(cbc);
+    });
 
-#ifndef OMTP_FORCE_SYNC
-  sThread->Dispatch(task.forget());
-#else
-  SyncRunnable::DispatchToThread(sThread, task);
-#endif
+  #ifndef OMTP_FORCE_SYNC
+    sThread->Dispatch(task.forget());
+  #else
+    SyncRunnable::DispatchToThread(sThread, task);
+  #endif
+  }
 }
 
 void
-PaintThread::AsyncEndLayerTransaction(CompositorBridgeChild* aBridge,
-                                      SyncObjectClient* aSyncObject)
+PaintThread::AsyncEndLayerTransaction(CompositorBridgeChild* aBridge)
 {
   MOZ_ASSERT(IsOnPaintThread());
-  MOZ_ASSERT(mInAsyncPaintGroup);
-
-  if (aSyncObject) {
-    aSyncObject->Synchronize();
-  }
-
-  mInAsyncPaintGroup = false;
-  PROFILER_TRACING("Paint", "Rasterize", TRACING_INTERVAL_END);
 
   aBridge->NotifyFinishedAsyncEndLayerTransaction();
 }
 
+void
+PaintThread::DispatchEndLayerTransaction(CompositorBridgeChild* aBridge)
+{
+  MOZ_ASSERT(IsOnPaintThread());
+
+  RefPtr<CompositorBridgeChild> cbc = aBridge;
+  RefPtr<PaintThread> self = this;
+  RefPtr<Runnable> task = NS_NewRunnableFunction("PaintThread::AsyncEndLayerTransaction",
+    [self, cbc]() -> void
+  {
+    self->AsyncEndLayerTransaction(cbc);
+  });
+
+  sThread->Dispatch(task.forget());
+}
+
 } // namespace layers
 } // namespace mozilla
--- a/gfx/layers/PaintThread.h
+++ b/gfx/layers/PaintThread.h
@@ -10,16 +10,18 @@
 #include "base/platform_thread.h"
 #include "mozilla/RefPtr.h"
 #include "mozilla/StaticPtr.h"
 #include "mozilla/UniquePtr.h"
 #include "mozilla/layers/TextureClient.h"
 #include "RotatedBuffer.h"
 #include "nsThreadUtils.h"
 
+class nsIThreadPool;
+
 namespace mozilla {
 namespace gfx {
 class DrawTarget;
 class DrawTargetCapture;
 };
 
 namespace layers {
 
@@ -256,22 +258,17 @@ class PaintThread final
 
 public:
   static void Start();
   static void Shutdown();
   static PaintThread* Get();
 
   // Helper for asserts.
   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();
+  bool IsOnPaintWorkerThread();
 
   void PrepareBuffer(CapturedBufferState* aState);
 
   void PaintContents(CapturedPaintState* aState,
                      PrepDrawTargetForPaintingCallback aCallback);
 
   void PaintTiledContents(CapturedTiledPaintState* aState);
 
@@ -296,36 +293,41 @@ public:
   // We're only temporarily using sync runnables so
   // Override release/addref but don't do anything.
   void Release();
   void AddRef();
 
 private:
   PaintThread();
 
+  static int32_t CalculatePaintWorkerCount();
+
   bool Init();
   void ShutdownOnPaintThread();
   void InitOnPaintThread();
 
   void AsyncPrepareBuffer(CompositorBridgeChild* aBridge,
                           CapturedBufferState* aState);
   void AsyncPaintContents(CompositorBridgeChild* aBridge,
                           CapturedPaintState* aState,
                           PrepDrawTargetForPaintingCallback aCallback);
   void AsyncPaintTiledContents(CompositorBridgeChild* aBridge,
                                CapturedTiledPaintState* aState);
+  void AsyncPaintTiledContentsFinished(CompositorBridgeChild* aBridge,
+                                       CapturedTiledPaintState* aState);
   void AsyncEndLayer();
-  void AsyncEndLayerTransaction(CompositorBridgeChild* aBridge,
-                                SyncObjectClient* aSyncObject);
+  void AsyncEndLayerTransaction(CompositorBridgeChild* aBridge);
+
+  void DispatchEndLayerTransaction(CompositorBridgeChild* aBridge);
 
   static StaticAutoPtr<PaintThread> sSingleton;
   static StaticRefPtr<nsIThread> sThread;
   static PlatformThreadId sThreadId;
 
-  bool mInAsyncPaintGroup;
+  RefPtr<nsIThreadPool> mPaintWorkers;
 
   // This shouldn't be very many elements, so a list should be fine.
   // Should only be accessed on the paint thread.
   nsTArray<RefPtr<gfx::DrawTarget>> mDrawTargetsToFlush;
 };
 
 } // namespace layers
 } // namespace mozilla
--- a/gfx/layers/client/ClientLayerManager.cpp
+++ b/gfx/layers/client/ClientLayerManager.cpp
@@ -221,19 +221,16 @@ ClientLayerManager::CreateReadbackLayer(
   return layer.forget();
 }
 
 bool
 ClientLayerManager::BeginTransactionWithTarget(gfxContext* aTarget)
 {
   // Wait for any previous async paints to complete before starting to paint again.
   GetCompositorBridgeChild()->FlushAsyncPaints();
-  if (PaintThread::Get()) {
-    PaintThread::Get()->BeginLayerTransaction();
-  }
 
   MOZ_ASSERT(mForwarder, "ClientLayerManager::BeginTransaction without forwarder");
   if (!mForwarder->IPCOpen()) {
     gfxCriticalNote << "ClientLayerManager::BeginTransaction with IPC channel down. GPU process may have died.";
     return false;
   }
 
   mInTransaction = true;
--- a/gfx/layers/client/ClientPaintedLayer.cpp
+++ b/gfx/layers/client/ClientPaintedLayer.cpp
@@ -319,23 +319,17 @@ ClientLayerManager::CreatePaintedLayer()
 {
   return CreatePaintedLayerWithHint(NONE);
 }
 
 already_AddRefed<PaintedLayer>
 ClientLayerManager::CreatePaintedLayerWithHint(PaintedLayerCreationHint aHint)
 {
   NS_ASSERTION(InConstruction(), "Only allowed in construction phase");
-  // The non-tiling ContentClient requires CrossProcessSemaphore which
-  // isn't implemented for OSX.
-#ifdef XP_MACOSX
-  if (true) {
-#else
-  if (gfxPrefs::LayersTilesEnabled()) {
-#endif
+  if (gfxPlatform::UsesTiling()) {
     RefPtr<ClientTiledPaintedLayer> layer = new ClientTiledPaintedLayer(this, aHint);
     CREATE_SHADOW(Painted);
     return layer.forget();
   } else {
     RefPtr<ClientPaintedLayer> layer = new ClientPaintedLayer(this, aHint);
     CREATE_SHADOW(Painted);
     return layer.forget();
   }
--- a/gfx/layers/ipc/CompositorBridgeChild.cpp
+++ b/gfx/layers/ipc/CompositorBridgeChild.cpp
@@ -1187,30 +1187,38 @@ CompositorBridgeChild::FlushAsyncPaints(
     mTotalFlushCount++;
 
     double ratio = double(mSlowFlushCount) / double(mTotalFlushCount);
     Telemetry::ScalarSet(Telemetry::ScalarID::GFX_OMTP_PAINT_WAIT_RATIO,
                          uint32_t(ratio * 100 * 100));
   }
 }
 
-void
-CompositorBridgeChild::NotifyBeginAsyncEndLayerTransaction()
+bool
+CompositorBridgeChild::NotifyBeginAsyncEndLayerTransaction(SyncObjectClient* aSyncObject)
 {
   MOZ_ASSERT(NS_IsMainThread());
   MonitorAutoLock lock(mPaintLock);
 
   MOZ_ASSERT(!mOutstandingAsyncEndTransaction);
   mOutstandingAsyncEndTransaction = true;
+  mOutstandingAsyncSyncObject = aSyncObject;
+  return mOutstandingAsyncPaints == 0;
 }
 
 void
 CompositorBridgeChild::NotifyFinishedAsyncEndLayerTransaction()
 {
   MOZ_ASSERT(PaintThread::IsOnPaintThread());
+
+  if (mOutstandingAsyncSyncObject) {
+    mOutstandingAsyncSyncObject->Synchronize();
+    mOutstandingAsyncSyncObject = nullptr;
+  }
+
   MonitorAutoLock lock(mPaintLock);
 
   // Since this should happen after ALL paints are done and
   // at the end of a transaction, this should always be true.
   MOZ_RELEASE_ASSERT(mOutstandingAsyncPaints == 0);
   MOZ_ASSERT(mOutstandingAsyncEndTransaction);
 
   mOutstandingAsyncEndTransaction = false;
--- a/gfx/layers/ipc/CompositorBridgeChild.h
+++ b/gfx/layers/ipc/CompositorBridgeChild.h
@@ -245,38 +245,44 @@ public:
       aClient->AddPaintThreadRef();
       mTextureClientsForAsyncPaint.AppendElement(aClient);
     });
   }
 
   // Must only be called from the paint thread. Notifies the CompositorBridge
   // that the paint thread has finished an asynchronous paint request.
   template<typename CapturedState>
-  void NotifyFinishedAsyncPaint(CapturedState& aState)
+  bool NotifyFinishedAsyncWorkerPaint(CapturedState& aState)
   {
     MOZ_ASSERT(PaintThread::IsOnPaintThread());
 
     MonitorAutoLock lock(mPaintLock);
     mOutstandingAsyncPaints--;
 
     aState->ForEachTextureClient([] (auto aClient) {
       aClient->DropPaintThreadRef();
     });
     aState->DropTextureClients();
+
+    // If the main thread has completed queuing work and this was the
+    // last paint, then it is time to end the layer transaction and sync
+    return mOutstandingAsyncEndTransaction && mOutstandingAsyncPaints == 0;
   }
 
   // Must only be called from the main thread. Notifies the CompositorBridge
-  // that the paint thread is going to perform texture synchronization at the
-  // end of async painting, and should postpone messages if needed until
-  // finished.
-  void NotifyBeginAsyncEndLayerTransaction();
+  // that all work has been submitted to the paint thread or paint worker
+  // threads, and returns whether all paints are completed. If this returns
+  // true, then an AsyncEndLayerTransaction must be queued, otherwise once
+  // NotifyFinishedAsyncWorkerPaint returns true, an AsyncEndLayerTransaction
+  // must be executed.
+  bool NotifyBeginAsyncEndLayerTransaction(SyncObjectClient* aSyncObject);
 
   // Must only be called from the paint thread. Notifies the CompositorBridge
-  // that the paint thread has finished all async paints and texture syncs from
-  // a given transaction and may resume sending messages.
+  // that the paint thread has finished all async paints and and may do the
+  // requested texture sync and resume sending messages.
   void NotifyFinishedAsyncEndLayerTransaction();
 
   // Must only be called from the main thread. Notifies the CompoistorBridge
   // that a transaction is about to be sent, and if the paint thread is
   // currently painting, to begin delaying IPC messages.
   void PostponeMessagesIfAsyncPainting();
 
 private:
@@ -401,16 +407,17 @@ private:
 
   // Contains the number of outstanding asynchronous paints tied to a
   // PLayerTransaction on this bridge. This is R/W on both the main and paint
   // threads, and must be accessed within the paint lock.
   size_t mOutstandingAsyncPaints;
 
   // Whether we are waiting for an async paint end transaction
   bool mOutstandingAsyncEndTransaction;
+  RefPtr<SyncObjectClient> mOutstandingAsyncSyncObject;
 
   // True if this CompositorBridge is currently delaying its messages until the
   // paint thread completes. This is R/W on both the main and paint threads, and
   // must be accessed within the paint lock.
   bool mIsDelayingForAsyncPaints;
 
   uintptr_t mSlowFlushCount;
   uintptr_t mTotalFlushCount;
--- a/gfx/thebes/gfxPlatform.cpp
+++ b/gfx/thebes/gfxPlatform.cpp
@@ -2585,17 +2585,17 @@ gfxPlatform::InitOMTPConfig()
   if (mContentBackend == BackendType::CAIRO) {
     omtp.ForceDisable(FeatureStatus::Broken, "OMTP is not supported when using cairo",
       NS_LITERAL_CSTRING("FEATURE_FAILURE_COMP_PREF"));
   }
 
   if (InSafeMode()) {
     omtp.ForceDisable(FeatureStatus::Blocked, "OMTP blocked by safe-mode",
                       NS_LITERAL_CSTRING("FEATURE_FAILURE_COMP_SAFEMODE"));
-  } else if (gfxPrefs::LayersTilesEnabled() && gfxPrefs::TileEdgePaddingEnabled()) {
+  } else if (gfxPlatform::UsesTiling() && gfxPrefs::TileEdgePaddingEnabled()) {
     omtp.ForceDisable(FeatureStatus::Blocked, "OMTP does not yet support tiling with edge padding",
                       NS_LITERAL_CSTRING("FEATURE_FAILURE_OMTP_TILING"));
   }
 
   if (omtp.IsEnabled()) {
     gfxVars::SetUseOMTP(true);
     reporter.SetSuccessful();
   }
@@ -2657,16 +2657,28 @@ gfxPlatform::UsesOffMainThreadCompositin
 
 #endif
     firstTime = false;
   }
 
   return result;
 }
 
+/* static */ bool
+gfxPlatform::UsesTiling()
+{
+  // The non-tiling ContentClient requires CrossProcessSemaphore which
+  // isn't implemented for OSX.
+#ifdef XP_MACOSX
+  return true;
+#else
+  return gfxPrefs::LayersTilesEnabled();
+#endif
+}
+
 /***
  * The preference "layout.frame_rate" has 3 meanings depending on the value:
  *
  * -1 = Auto (default), use hardware vsync or software vsync @ 60 hz if hw vsync fails.
  *  0 = ASAP mode - used during talos testing.
  *  X = Software vsync at a rate of X times per second.
  */
 already_AddRefed<mozilla::gfx::VsyncSource>
--- a/gfx/thebes/gfxPlatform.h
+++ b/gfx/thebes/gfxPlatform.h
@@ -594,16 +594,18 @@ public:
     mozilla::layers::DiagnosticTypes GetLayerDiagnosticTypes();
 
     mozilla::gl::SkiaGLGlue* GetSkiaGLGlue();
     void PurgeSkiaGPUCache();
     static void PurgeSkiaFontCache();
 
     static bool UsesOffMainThreadCompositing();
 
+    static bool UsesTiling();
+
     bool HasEnoughTotalSystemMemoryForSkiaGL();
 
     /**
      * Get the hardware vsync source for each platform.
      * Should only exist and be valid on the parent process
      */
     virtual mozilla::gfx::VsyncSource* GetHardwareVsync() {
       MOZ_ASSERT(mVsyncSource != nullptr);
--- a/gfx/thebes/gfxPrefs.h
+++ b/gfx/thebes/gfxPrefs.h
@@ -605,16 +605,17 @@ private:
   DECL_GFX_PREF(Once, "layers.mlgpu.enable-clear-view",        AdvancedLayersEnableClearView, bool, true);
   DECL_GFX_PREF(Once, "layers.mlgpu.enable-cpu-occlusion",     AdvancedLayersEnableCPUOcclusion, bool, true);
   DECL_GFX_PREF(Once, "layers.mlgpu.enable-depth-buffer",      AdvancedLayersEnableDepthBuffer, bool, false);
   DECL_GFX_PREF(Live, "layers.mlgpu.enable-invalidation",      AdvancedLayersUseInvalidation, bool, true);
   DECL_GFX_PREF(Once, "layers.mlgpu.enable-on-windows7",       AdvancedLayersEnableOnWindows7, bool, false);
   DECL_GFX_PREF(Once, "layers.mlgpu.enable-container-resizing", AdvancedLayersEnableContainerResizing, bool, true);
   DECL_GFX_PREF(Once, "layers.offmainthreadcomposition.force-disabled", LayersOffMainThreadCompositionForceDisabled, bool, false);
   DECL_GFX_PREF(Live, "layers.offmainthreadcomposition.frame-rate", LayersCompositionFrameRate, int32_t,-1);
+  DECL_GFX_PREF(Live, "layers.omtp.paint-workers",             LayersOMTPPaintWorkers, int32_t, 1);
   DECL_GFX_PREF(Live, "layers.omtp.release-capture-on-main-thread", LayersOMTPReleaseCaptureOnMainThread, bool, false);
   DECL_GFX_PREF(Live, "layers.orientation.sync.timeout",       OrientationSyncMillis, uint32_t, (uint32_t)0);
   DECL_GFX_PREF(Once, "layers.prefer-opengl",                  LayersPreferOpenGL, bool, false);
   DECL_GFX_PREF(Live, "layers.progressive-paint",              ProgressivePaint, bool, false);
   DECL_GFX_PREF(Live, "layers.shared-buffer-provider.enabled", PersistentBufferProviderSharedEnabled, bool, false);
   DECL_GFX_PREF(Live, "layers.single-tile.enabled",            LayersSingleTileEnabled, bool, true);
   DECL_GFX_PREF(Live, "layers.force-synchronous-resize",       LayersForceSynchronousResize, bool, true);
 
--- a/modules/libpref/init/all.js
+++ b/modules/libpref/init/all.js
@@ -5878,8 +5878,9 @@ pref("toolkit.crashreporter.include_cont
 pref("dom.noopener.newprocess.enabled", true);
 
 #if defined(XP_WIN) || defined(XP_MACOSX)
 pref("layers.omtp.enabled", true);
 #else
 pref("layers.omtp.enabled", false);
 #endif
 pref("layers.omtp.release-capture-on-main-thread", false);
+pref("layers.omtp.paint-workers", 1);
--- a/xpcom/base/moz.build
+++ b/xpcom/base/moz.build
@@ -72,16 +72,17 @@ EXPORTS += [
     'nsISizeOf.h',
     'nsISupportsBase.h',
     'nsISupportsImpl.h',
     'nsISupportsUtils.h',
     'nsIWeakReferenceUtils.h',
     'nsMemory.h',
     'nsObjCExceptions.h',
     'nsQueryObject.h',
+    'nsSystemInfo.h',
     'nsTraceRefcnt.h',
     'nsTWeakRef.h',
     'nsVersionComparator.h',
     'nsWeakPtr.h',
     'nsWeakReference.h',
 ]
 
 if CONFIG['OS_ARCH'] == 'WINNT':