Bug 1464032 Part 15: Spread the playback of canvas recordings across multiple threads in the GPU process. r=mattwoodrow
authorBob Owen <bobowencode@gmail.com>
Sun, 02 Dec 2018 14:22:28 +0000
changeset 477766 2195b79ea888b1e49406406f528870ca9cab1525
parent 477765 2fd1ba3d96cd90f32b2858569580cc1c6da976e6
child 477767 d5732da1844be4de4558a506046fb716d9da4ec7
push id113373
push userbobowencode@gmail.com
push dateFri, 07 Jun 2019 11:10:59 +0000
treeherdermozilla-inbound@2195b79ea888 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmattwoodrow
bugs1464032
milestone69.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
Bug 1464032 Part 15: Spread the playback of canvas recordings across multiple threads in the GPU process. r=mattwoodrow
gfx/2d/2D.h
gfx/2d/Factory.cpp
gfx/layers/CanvasTranslator.cpp
gfx/layers/RecordedCanvasEventImpl.h
gfx/layers/ipc/CanvasParent.cpp
gfx/layers/ipc/CanvasParent.h
--- a/gfx/2d/2D.h
+++ b/gfx/2d/2D.h
@@ -56,16 +56,17 @@ struct FT_FaceRec_;
 typedef FT_FaceRec_* FT_Face;
 
 typedef int FT_Error;
 
 struct ID3D11Texture2D;
 struct ID3D11Device;
 struct ID2D1Device;
 struct ID2D1DeviceContext;
+struct ID2D1Multithread;
 struct IDWriteFactory;
 struct IDWriteRenderingParams;
 struct IDWriteFontFace;
 struct IDWriteFontCollection;
 
 class SkCanvas;
 struct gfxFontStyle;
 
@@ -1866,12 +1867,23 @@ class GFX2D_API Factory {
 
   friend class DrawTargetD2D1;
 #endif
 
  private:
   static DrawEventRecorder* mRecorder;
 };
 
+class MOZ_RAII AutoSerializeWithMoz2D final {
+ public:
+  explicit AutoSerializeWithMoz2D(BackendType aBackendType);
+  ~AutoSerializeWithMoz2D();
+
+ private:
+#if defined(WIN32)
+  RefPtr<ID2D1Multithread> mMT;
+#endif
+};
+
 }  // namespace gfx
 }  // namespace mozilla
 
 #endif  // _MOZILLA_GFX_2D_H
--- a/gfx/2d/Factory.cpp
+++ b/gfx/2d/Factory.cpp
@@ -733,16 +733,40 @@ void Factory::ReleaseFTFace(FT_Face aFac
 
 FT_Error Factory::LoadFTGlyph(FT_Face aFace, uint32_t aGlyphIndex,
                               int32_t aFlags) {
   StaticMutexAutoLock lock(mFTLock);
   return FT_Load_Glyph(aFace, aGlyphIndex, aFlags);
 }
 #endif
 
+AutoSerializeWithMoz2D::AutoSerializeWithMoz2D(BackendType aBackendType) {
+#ifdef WIN32
+  // We use a multi-threaded ID2D1Factory1, so that makes the calls through the
+  // Direct2D API thread-safe. However, if the Moz2D objects are using Direct3D
+  // resources we need to make sure that calls through the Direct3D or DXGI API
+  // use the Direct2D synchronization. It's possible that this should be pushed
+  // down into the TextureD3D11 objects, so that we always use this.
+  if (aBackendType == BackendType::DIRECT2D1_1 ||
+      aBackendType == BackendType::DIRECT2D) {
+    D2DFactory()->QueryInterface(
+        static_cast<ID2D1Multithread**>(getter_AddRefs(mMT)));
+    mMT->Enter();
+  }
+#endif
+}
+
+AutoSerializeWithMoz2D::~AutoSerializeWithMoz2D() {
+#ifdef WIN32
+  if (mMT) {
+    mMT->Leave();
+  }
+#endif
+};
+
 #ifdef WIN32
 already_AddRefed<DrawTarget> Factory::CreateDrawTargetForD3D11Texture(
     ID3D11Texture2D* aTexture, SurfaceFormat aFormat) {
   MOZ_ASSERT(aTexture);
 
   RefPtr<DrawTargetD2D1> newTarget;
 
   newTarget = new DrawTargetD2D1();
--- a/gfx/layers/CanvasTranslator.cpp
+++ b/gfx/layers/CanvasTranslator.cpp
@@ -1,16 +1,17 @@
 /* -*- 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 https://mozilla.org/MPL/2.0/. */
 
 #include "CanvasTranslator.h"
 
+#include "mozilla/gfx/2D.h"
 #include "mozilla/gfx/Logging.h"
 #include "RecordedCanvasEventImpl.h"
 
 #if defined(XP_WIN)
 #  include "mozilla/gfx/DeviceManagerDx.h"
 #  include "mozilla/layers/TextureD3D11.h"
 #endif
 
@@ -134,16 +135,18 @@ bool CanvasTranslator::HandleExtensionEv
       return false;
   }
 }
 
 void CanvasTranslator::BeginTransaction() { mIsInTransaction = true; }
 
 void CanvasTranslator::Flush() {
 #if defined(XP_WIN)
+  gfx::AutoSerializeWithMoz2D serializeWithMoz2D(
+      GetReferenceDrawTarget()->GetBackendType());
   RefPtr<ID3D11Device> device = gfx::DeviceManagerDx::Get()->GetCanvasDevice();
   RefPtr<ID3D11DeviceContext> deviceContext;
   device->GetImmediateContext(getter_AddRefs(deviceContext));
   deviceContext->Flush();
 #endif
 }
 
 void CanvasTranslator::EndTransaction() {
@@ -161,28 +164,32 @@ void CanvasTranslator::AddSurfaceDescrip
   MonitorAutoLock lock(mSurfaceDescriptorsMonitor);
   mSurfaceDescriptors[aRefPtr] = std::move(descriptor);
   mSurfaceDescriptorsMonitor.Notify();
 }
 
 already_AddRefed<gfx::DrawTarget> CanvasTranslator::CreateDrawTarget(
     gfx::ReferencePtr aRefPtr, const gfx::IntSize& aSize,
     gfx::SurfaceFormat aFormat) {
+  gfx::AutoSerializeWithMoz2D serializeWithMoz2D(
+      GetReferenceDrawTarget()->GetBackendType());
   TextureData* textureData = CreateTextureData(mTextureType, aSize, aFormat);
   textureData->Lock(OpenMode::OPEN_READ_WRITE);
   mTextureDatas[aRefPtr] = UniquePtr<TextureData>(textureData);
   AddSurfaceDescriptor(aRefPtr, textureData);
   RefPtr<gfx::DrawTarget> dt = textureData->BorrowDrawTarget();
   AddDrawTarget(aRefPtr, dt);
 
   return dt.forget();
 }
 
 void CanvasTranslator::RemoveDrawTarget(gfx::ReferencePtr aDrawTarget) {
   InlineTranslator::RemoveDrawTarget(aDrawTarget);
+  gfx::AutoSerializeWithMoz2D serializeWithMoz2D(
+      GetReferenceDrawTarget()->GetBackendType());
   mTextureDatas.erase(aDrawTarget);
 }
 
 TextureData* CanvasTranslator::LookupTextureData(
     gfx::ReferencePtr aDrawTarget) {
   TextureMap::const_iterator result = mTextureDatas.find(aDrawTarget);
   if (result == mTextureDatas.end()) {
     return nullptr;
--- a/gfx/layers/RecordedCanvasEventImpl.h
+++ b/gfx/layers/RecordedCanvasEventImpl.h
@@ -148,16 +148,18 @@ class RecordedTextureLock final
 
 inline bool RecordedTextureLock::PlayCanvasEvent(
     CanvasTranslator* aTranslator) const {
   TextureData* textureData = aTranslator->LookupTextureData(mDT);
   if (!textureData) {
     return false;
   }
 
+  gfx::AutoSerializeWithMoz2D serializeWithMoz2D(
+      aTranslator->GetReferenceDrawTarget()->GetBackendType());
   textureData->Lock(mMode);
   return true;
 }
 
 template <class S>
 void RecordedTextureLock::Record(S& aStream) const {
   WriteElement(aStream, mDT);
   WriteElement(aStream, mMode);
@@ -192,16 +194,18 @@ class RecordedTextureUnlock final
 
 inline bool RecordedTextureUnlock::PlayCanvasEvent(
     CanvasTranslator* aTranslator) const {
   TextureData* textureData = aTranslator->LookupTextureData(mDT);
   if (!textureData) {
     return false;
   }
 
+  gfx::AutoSerializeWithMoz2D serializeWithMoz2D(
+      aTranslator->GetReferenceDrawTarget()->GetBackendType());
   textureData->Unlock();
   return true;
 }
 
 template <class S>
 void RecordedTextureUnlock::Record(S& aStream) const {
   WriteElement(aStream, mDT);
 }
--- a/gfx/layers/ipc/CanvasParent.cpp
+++ b/gfx/layers/ipc/CanvasParent.cpp
@@ -4,68 +4,100 @@
  * 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 "CanvasParent.h"
 
 #include "base/thread.h"
 #include "mozilla/layers/SourceSurfaceSharedData.h"
 #include "mozilla/layers/TextureClient.h"
+#include "mozilla/SharedThreadPool.h"
+#include "prsystem.h"
 
 #if defined(XP_WIN)
 #  include "mozilla/gfx/DeviceManagerDx.h"
 #  include "mozilla/layers/DeviceAttachmentsD3D11.h"
 #endif
 
 bool NS_IsInCanvasThread() {
   return mozilla::layers::CanvasParent::IsInCanvasThread();
 }
 
 namespace mozilla {
 namespace layers {
 
 static base::Thread* sCanvasThread = nullptr;
+static StaticRefPtr<nsIThreadPool> sCanvasWorkers;
+static bool sShuttingDown = false;
 
 static MessageLoop* CanvasPlaybackLoop() {
-  if (!sCanvasThread) {
+  if (!sCanvasThread && !sShuttingDown) {
     MOZ_ASSERT(NS_IsInCompositorThread());
     base::Thread* canvasThread = new base::Thread("Canvas");
     if (canvasThread->Start()) {
       sCanvasThread = canvasThread;
     }
   }
 
-  return sCanvasThread ? sCanvasThread->message_loop() : MessageLoop::current();
+  return sCanvasThread ? sCanvasThread->message_loop() : nullptr;
 }
 
 /* static */
 already_AddRefed<CanvasParent> CanvasParent::Create(
     ipc::Endpoint<PCanvasParent>&& aEndpoint) {
   MOZ_ASSERT(NS_IsInCompositorThread());
 
+  if (sShuttingDown) {
+    return nullptr;
+  }
+
   RefPtr<CanvasParent> canvasParent = new CanvasParent();
   if (CanvasPlaybackLoop()->IsAcceptingTasks()) {
     RefPtr<Runnable> runnable = NewRunnableMethod<Endpoint<PCanvasParent>&&>(
         "CanvasParent::Bind", canvasParent, &CanvasParent::Bind,
         std::move(aEndpoint));
     CanvasPlaybackLoop()->PostTask(runnable.forget());
   }
   return do_AddRef(canvasParent);
 }
 
 /* static */ bool CanvasParent::IsInCanvasThread() {
-  return sCanvasThread &&
-         sCanvasThread->thread_id() == PlatformThread::CurrentId();
+  return (sCanvasWorkers && sCanvasWorkers->IsOnCurrentThread()) ||
+         (sCanvasThread &&
+          sCanvasThread->thread_id() == PlatformThread::CurrentId());
+}
+
+static already_AddRefed<nsIThreadPool> GetCanvasWorkers() {
+  if (!sCanvasWorkers && !sShuttingDown) {
+    // Given that the canvas workers are receiving instructions from content
+    // processes, it probably doesn't make sense to have more than half the
+    // number of processors doing canvas drawing. We set the lower limit to 2,
+    // so that even on single processor systems, if there is more than one
+    // window with canvas drawing, the OS can manage the load between them.
+    uint32_t threadLimit = std::max(2, PR_GetNumberOfProcessors() / 2);
+    sCanvasWorkers =
+        SharedThreadPool::Get(NS_LITERAL_CSTRING("CanvasWorkers"), threadLimit);
+  }
+
+  return do_AddRef(sCanvasWorkers);
 }
 
 /* static */ void CanvasParent::Shutdown() {
+  sShuttingDown = true;
+
   if (sCanvasThread) {
+    sCanvasThread->Stop();
     delete sCanvasThread;
     sCanvasThread = nullptr;
   }
+
+  if (sCanvasWorkers) {
+    sCanvasWorkers->Shutdown();
+    sCanvasWorkers = nullptr;
+  }
 }
 
 CanvasParent::CanvasParent() {}
 
 CanvasParent::~CanvasParent() {}
 
 void CanvasParent::Bind(Endpoint<PCanvasParent>&& aEndpoint) {
   if (!aEndpoint.Bind(this)) {
@@ -87,33 +119,35 @@ mozilla::ipc::IPCResult CanvasParent::Re
 
 ipc::IPCResult CanvasParent::RecvResumeTranslation() {
   MOZ_ASSERT(mTranslator);
 
   if (!mTranslator->IsValid()) {
     return IPC_FAIL(this, "Canvas Translation failed.");
   }
 
-  PostStartTranslationTask();
+  PostStartTranslationTask(nsIThread::DISPATCH_NORMAL);
 
   return IPC_OK();
 }
 
-void CanvasParent::PostStartTranslationTask() {
-  if (MessageLoop::current()->IsAcceptingTasks()) {
-    RefPtr<Runnable> runnable =
-        NewRunnableMethod("CanvasParent::StartTranslation", this,
-                          &CanvasParent::StartTranslation);
-    MessageLoop::current()->PostTask(runnable.forget());
+void CanvasParent::PostStartTranslationTask(uint32_t aDispatchFlags) {
+  if (sShuttingDown) {
+    return;
   }
+
+  RefPtr<nsIThreadPool> canvasWorkers = GetCanvasWorkers();
+  RefPtr<Runnable> runnable = NewRunnableMethod(
+      "CanvasParent::StartTranslation", this, &CanvasParent::StartTranslation);
+  canvasWorkers->Dispatch(runnable.forget(), aDispatchFlags);
 }
 
 void CanvasParent::StartTranslation() {
   if (!mTranslator->TranslateRecording()) {
-    PostStartTranslationTask();
+    PostStartTranslationTask(nsIThread::DISPATCH_AT_END);
   }
 }
 
 UniquePtr<SurfaceDescriptor>
 CanvasParent::LookupSurfaceDescriptorForClientDrawTarget(
     const uintptr_t aDrawTarget) {
   return mTranslator->WaitForSurfaceDescriptor(
       reinterpret_cast<void*>(aDrawTarget));
--- a/gfx/layers/ipc/CanvasParent.h
+++ b/gfx/layers/ipc/CanvasParent.h
@@ -78,17 +78,17 @@ class CanvasParent final : public PCanva
  private:
   CanvasParent();
   ~CanvasParent() final;
 
   DISALLOW_COPY_AND_ASSIGN(CanvasParent);
 
   void Bind(Endpoint<PCanvasParent>&& aEndpoint);
 
-  void PostStartTranslationTask();
+  void PostStartTranslationTask(uint32_t aDispatchFlags);
 
   void StartTranslation();
 
   RefPtr<CanvasParent> mSelfRef;
   UniquePtr<CanvasTranslator> mTranslator;
 };
 
 }  // namespace layers