Bug 1592026 - Move AsyncCATransaction suspension into NativeLayerRootCA. r=jrmuizel
authorMarkus Stange <mstange@themasta.com>
Sun, 29 Dec 2019 12:18:32 +0000
changeset 508470 f478a2e999e45ad5b2559908505bbb0fa8cb4542
parent 508469 5433f2ea1f4b871815f57125fec545a689120d91
child 508471 b5cf7c3c4771f92523245b10382903ec7d5430a5
push id104012
push usermstange@themasta.com
push dateSun, 29 Dec 2019 12:53:08 +0000
treeherderautoland@781f53bf9c78 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjrmuizel
bugs1592026
milestone73.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 1592026 - Move AsyncCATransaction suspension into NativeLayerRootCA. r=jrmuizel Differential Revision: https://phabricator.services.mozilla.com/D57061
gfx/layers/NativeLayer.h
gfx/layers/NativeLayerCA.h
gfx/layers/NativeLayerCA.mm
widget/cocoa/nsChildView.h
widget/cocoa/nsChildView.mm
--- a/gfx/layers/NativeLayer.h
+++ b/gfx/layers/NativeLayer.h
@@ -36,16 +36,20 @@ class NativeLayerRoot {
 
   virtual already_AddRefed<NativeLayer> CreateLayer(
       const gfx::IntSize& aSize, bool aIsOpaque,
       SurfacePoolHandle* aSurfacePoolHandle) = 0;
   virtual void AppendLayer(NativeLayer* aLayer) = 0;
   virtual void RemoveLayer(NativeLayer* aLayer) = 0;
   virtual void SetLayers(const nsTArray<RefPtr<NativeLayer>>& aLayers) = 0;
 
+  // Publish the layer changes to the screen. Returns whether the commit was
+  // successful.
+  virtual bool CommitToScreen() = 0;
+
  protected:
   virtual ~NativeLayerRoot() {}
 };
 
 // Represents a native layer. Native layers, such as CoreAnimation layers on
 // macOS, are used to put pixels on the screen and to refresh and manipulate
 // the visual contents of a window efficiently. For example, drawing to a layer
 // once and then displaying the layer for multiple frames while moving it to
--- a/gfx/layers/NativeLayerCA.h
+++ b/gfx/layers/NativeLayerCA.h
@@ -36,46 +36,78 @@ namespace layers {
 
 class SurfacePoolHandleCA;
 
 // NativeLayerRootCA is the CoreAnimation implementation of the NativeLayerRoot
 // interface. A NativeLayerRootCA is created by the widget around an existing
 // CALayer with a call to CreateForCALayer.
 // All methods can be called from any thread, there is internal locking.
 // All effects from mutating methods are buffered locally and don't modify the
-// underlying CoreAnimation layers until ApplyChanges() is called. This ensures
-// that the modifications can be limited to run within a CoreAnimation
-// transaction, and on a thread of the caller's choosing.
+// underlying CoreAnimation layers until CommitToScreen() is called. This
+// ensures that the modifications happen on the right thread.
+//
+// More specifically: During normal operation, screen updates are driven from a
+// compositing thread. On this thread, the layers are created / destroyed, their
+// contents are painted, and the result is committed to the screen. However,
+// there are some scenarios that need to involve the main thread, most notably
+// window resizing: During a window resize, we still need the drawing part to
+// happen on the compositing thread, but the modifications to the underlying
+// CALayers need to happen on the main thread, once compositing is done.
 class NativeLayerRootCA : public NativeLayerRoot {
  public:
   static already_AddRefed<NativeLayerRootCA> CreateForCALayer(CALayer* aLayer);
 
+  // Can be called on any thread at any point. Returns whether comitting was
+  // successful. Will return false if called off the main thread while
+  // off-main-thread commits are suspended.
+  bool CommitToScreen() override;
+
+  // Enters a mode during which CommitToScreen(), when called on a non-main
+  // thread, will not apply any updates to the CALayer tree.
+  void SuspendOffMainThreadCommits();
+
+  // Exits the mode entered by SuspendOffMainThreadCommits().
+  // Returns true if the last CommitToScreen() was canceled due to suspension,
+  // indicating that another call to CommitToScreen() is needed.
+  bool UnsuspendOffMainThreadCommits();
+
+  bool AreOffMainThreadCommitsSuspended();
+
   // Overridden methods
   already_AddRefed<NativeLayer> CreateLayer(
       const gfx::IntSize& aSize, bool aIsOpaque,
       SurfacePoolHandle* aSurfacePoolHandle) override;
   void AppendLayer(NativeLayer* aLayer) override;
   void RemoveLayer(NativeLayer* aLayer) override;
   void SetLayers(const nsTArray<RefPtr<NativeLayer>>& aLayers) override;
 
   void SetBackingScale(float aBackingScale);
   float BackingScale();
 
-  // Must be called within a current CATransaction on the transaction's thread.
-  void ApplyChanges();
-
  protected:
   explicit NativeLayerRootCA(CALayer* aLayer);
   ~NativeLayerRootCA() override;
 
   Mutex mMutex;                                // protects all other fields
   nsTArray<RefPtr<NativeLayerCA>> mSublayers;  // in z-order
   CALayer* mRootCALayer = nullptr;             // strong
   float mBackingScale = 1.0f;
   bool mMutated = false;
+
+  // While mOffMainThreadCommitsSuspended is true, no commits
+  // should happen on a non-main thread, because they might race with
+  // main-thread driven updates such as window shape changes, and cause
+  // glitches.
+  bool mOffMainThreadCommitsSuspended = false;
+
+  // Set to true if CommitToScreen() was aborted because of commit suspension.
+  // Set to false when CommitToScreen() completes successfully. When true,
+  // indicates that CommitToScreen() needs to be called at the next available
+  // opportunity.
+  bool mCommitPending = false;
 };
 
 // NativeLayerCA wraps a CALayer and lets you draw to it. It ensures that only
 // fully-drawn frames make their way to the screen, by maintaining a swap chain
 // of IOSurfaces.
 // All calls to mutating methods are buffered, and don't take effect on the
 // underlying CoreAnimation layers until ApplyChanges() is called.
 // The two most important methods are NextSurface and NotifySurfaceReady:
--- a/gfx/layers/NativeLayerCA.mm
+++ b/gfx/layers/NativeLayerCA.mm
@@ -1,17 +1,18 @@
 /* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nullptr; c-basic-offset: 2 -*-
  * 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 "mozilla/layers/NativeLayerCA.h"
 
+#import <AppKit/NSAnimationContext.h>
+#import <AppKit/NSColor.h>
 #import <QuartzCore/QuartzCore.h>
-#import <AppKit/NSColor.h>
 
 #include <utility>
 #include <algorithm>
 
 #include "GLBlitHelper.h"
 #include "GLContextCGL.h"
 #include "MozFramebuffer.h"
 #include "mozilla/layers/SurfacePoolCA.h"
@@ -118,20 +119,42 @@ void NativeLayerRootCA::SetBackingScale(
   }
 }
 
 float NativeLayerRootCA::BackingScale() {
   MutexAutoLock lock(mMutex);
   return mBackingScale;
 }
 
-// Must be called within a current CATransaction on the transaction's thread.
-void NativeLayerRootCA::ApplyChanges() {
+void NativeLayerRootCA::SuspendOffMainThreadCommits() {
+  MutexAutoLock lock(mMutex);
+  mOffMainThreadCommitsSuspended = true;
+}
+
+bool NativeLayerRootCA::UnsuspendOffMainThreadCommits() {
+  MutexAutoLock lock(mMutex);
+  mOffMainThreadCommitsSuspended = false;
+  return mCommitPending;
+}
+
+bool NativeLayerRootCA::AreOffMainThreadCommitsSuspended() {
+  MutexAutoLock lock(mMutex);
+  return mOffMainThreadCommitsSuspended;
+}
+
+bool NativeLayerRootCA::CommitToScreen() {
   MutexAutoLock lock(mMutex);
 
+  if (!NS_IsMainThread() && mOffMainThreadCommitsSuspended) {
+    mCommitPending = true;
+    return false;
+  }
+
+  // Force a CoreAnimation layer tree update from this thread.
+  [NSAnimationContext beginGrouping];
   [CATransaction setDisableActions:YES];
 
   // Call ApplyChanges on our sublayers first, and then update the root layer's
   // list of sublayers. The order is important because we need layer->UnderlyingCALayer()
   // to be non-null, and the underlying CALayer gets lazily initialized in ApplyChanges().
   for (auto layer : mSublayers) {
     layer->ApplyChanges();
   }
@@ -139,16 +162,21 @@ void NativeLayerRootCA::ApplyChanges() {
   if (mMutated) {
     NSMutableArray<CALayer*>* sublayers = [NSMutableArray arrayWithCapacity:mSublayers.Length()];
     for (auto layer : mSublayers) {
       [sublayers addObject:layer->UnderlyingCALayer()];
     }
     mRootCALayer.sublayers = sublayers;
     mMutated = false;
   }
+  [NSAnimationContext endGrouping];
+
+  mCommitPending = false;
+
+  return true;
 }
 
 NativeLayerCA::NativeLayerCA(const IntSize& aSize, bool aIsOpaque,
                              SurfacePoolHandleCA* aSurfacePoolHandle)
     : mMutex("NativeLayerCA"),
       mSurfacePoolHandle(aSurfacePoolHandle),
       mSize(aSize),
       mIsOpaque(aIsOpaque) {
--- a/widget/cocoa/nsChildView.h
+++ b/widget/cocoa/nsChildView.h
@@ -588,32 +588,16 @@ class nsChildView final : public nsBaseW
 
   // In BasicLayers mode, this is the invalid region of mContentLayer.
   LayoutDeviceIntRegion mContentLayerInvalidRegion;
 
   mozilla::UniquePtr<mozilla::VibrancyManager> mVibrancyManager;
   RefPtr<mozilla::SwipeTracker> mSwipeTracker;
   mozilla::UniquePtr<mozilla::SwipeEventQueue> mSwipeEventQueue;
 
-  // Coordinates the triggering of CoreAnimation transactions between the main
-  // thread and the compositor thread in order to avoid glitches during window
-  // resizing and window focus changes.
-  struct WidgetCompositingState {
-    // While mAsyncCATransactionsSuspended is true, no CoreAnimation transaction
-    // should be triggered on a non-main thread, because they might race with
-    // main-thread driven updates such as window shape changes, and cause glitches.
-    bool mAsyncCATransactionsSuspended = false;
-
-    // Set to true if mNativeLayerRoot->ApplyChanges() needs to be called at the
-    // next available opportunity. Set to false whenever ApplyChanges does get
-    // called.
-    bool mNativeLayerChangesPending = false;
-  };
-  mozilla::DataMutex<WidgetCompositingState> mCompositingState;
-
   RefPtr<mozilla::CancelableRunnable> mUnsuspendAsyncCATransactionsRunnable;
 
   // This flag is only used when APZ is off. It indicates that the current pan
   // gesture was processed as a swipe. Sometimes the swipe animation can finish
   // before momentum events of the pan gesture have stopped firing, so this
   // flag tells us that we shouldn't allow the remaining events to cause
   // scrolling. It is reset to false once a new gesture starts (as indicated by
   // a PANGESTURE_(MAY)START event).
--- a/widget/cocoa/nsChildView.mm
+++ b/widget/cocoa/nsChildView.mm
@@ -231,17 +231,16 @@ nsChildView::nsChildView()
       mParentView(nil),
       mParentWidget(nullptr),
       mViewTearDownLock("ChildViewTearDown"),
       mBackingScaleFactor(0.0),
       mVisible(false),
       mDrawing(false),
       mIsDispatchPaint(false),
       mPluginFocused{false},
-      mCompositingState("nsChildView::mCompositingState"),
       mCurrentPanGestureBelongsToSwipe{false} {}
 
 nsChildView::~nsChildView() {
   if (mSwipeTracker) {
     mSwipeTracker->Destroy();
     mSwipeTracker = nullptr;
   }
 
@@ -844,41 +843,38 @@ void nsChildView::SuspendAsyncCATransact
     mUnsuspendAsyncCATransactionsRunnable = nullptr;
   }
 
   // Make sure that there actually will be a CATransaction on the main thread
   // during which we get a chance to schedule unsuspension. Otherwise we might
   // accidentally stay suspended indefinitely.
   [mView markLayerForDisplay];
 
-  auto compositingState = mCompositingState.Lock();
-  compositingState->mAsyncCATransactionsSuspended = true;
+  mNativeLayerRoot->SuspendOffMainThreadCommits();
 }
 
 void nsChildView::MaybeScheduleUnsuspendAsyncCATransactions() {
-  auto compositingState = mCompositingState.Lock();
-  if (compositingState->mAsyncCATransactionsSuspended && !mUnsuspendAsyncCATransactionsRunnable) {
+  if (mNativeLayerRoot->AreOffMainThreadCommitsSuspended() &&
+      !mUnsuspendAsyncCATransactionsRunnable) {
     mUnsuspendAsyncCATransactionsRunnable =
         NewCancelableRunnableMethod("nsChildView::MaybeScheduleUnsuspendAsyncCATransactions", this,
                                     &nsChildView::UnsuspendAsyncCATransactions);
     NS_DispatchToMainThread(mUnsuspendAsyncCATransactionsRunnable);
   }
 }
 
 void nsChildView::UnsuspendAsyncCATransactions() {
   mUnsuspendAsyncCATransactionsRunnable = nullptr;
 
-  auto compositingState = mCompositingState.Lock();
-  compositingState->mAsyncCATransactionsSuspended = false;
-  if (compositingState->mNativeLayerChangesPending) {
-    // We need to call mNativeLayerRoot->ApplyChanges() at the next available
-    // opportunity, and it needs to happen during a CoreAnimation transaction.
+  if (mNativeLayerRoot->UnsuspendOffMainThreadCommits()) {
+    // We need to call mNativeLayerRoot->CommitToScreen() at the next available
+    // opportunity.
     // The easiest way to handle this request is to mark the layer as needing
     // display, because this will schedule a main thread CATransaction, during
-    // which HandleMainThreadCATransaction will call ApplyChanges().
+    // which HandleMainThreadCATransaction will call CommitToScreen().
     [mView markLayerForDisplay];
   }
 }
 
 nsresult nsChildView::SynthesizeNativeKeyEvent(int32_t aNativeKeyboardLayout,
                                                int32_t aNativeKeyCode, uint32_t aModifierFlags,
                                                const nsAString& aCharacters,
                                                const nsAString& aUnmodifiedCharacters,
@@ -1373,21 +1369,17 @@ void nsChildView::HandleMainThreadCATran
     // contents, and the main thread (this thread) will wait inside PaintWindow
     // during that time.
     PaintWindow(LayoutDeviceIntRegion(GetBounds()));
   }
 
   // Apply the changes inside mNativeLayerRoot to the underlying CALayers. Now is a
   // good time to call this because we know we're currently inside a main thread
   // CATransaction.
-  {
-    auto compositingState = mCompositingState.Lock();
-    mNativeLayerRoot->ApplyChanges();
-    compositingState->mNativeLayerChangesPending = false;
-  }
+  mNativeLayerRoot->CommitToScreen();
 
   MaybeScheduleUnsuspendAsyncCATransactions();
 }
 
 #pragma mark -
 
 void nsChildView::ReportMoveEvent() { NotifyWindowMoved(mBounds.x, mBounds.y); }
 
@@ -1712,30 +1704,18 @@ bool nsChildView::PreRender(WidgetRender
   if (aContext->mGL && gfxPlatform::CanMigrateMacGPUs()) {
     GLContextCGL::Cast(aContext->mGL)->MigrateToActiveGPU();
   }
 
   return true;
 }
 
 void nsChildView::PostRender(WidgetRenderingContext* aContext) {
-  {  // scope for lock
-    auto compositingState = mCompositingState.Lock();
-    if (compositingState->mAsyncCATransactionsSuspended) {
-      // We should not trigger a CATransactions on this thread. Instead, let the
-      // main thread take care of calling ApplyChanges at an appropriate time.
-      compositingState->mNativeLayerChangesPending = true;
-    } else {
-      // Force a CoreAnimation layer tree update from this thread.
-      [NSAnimationContext beginGrouping];
-      mNativeLayerRoot->ApplyChanges();
-      compositingState->mNativeLayerChangesPending = false;
-      [NSAnimationContext endGrouping];
-    }
-  }
+  mNativeLayerRoot->CommitToScreen();
+
   mViewTearDownLock.Unlock();
 }
 
 RefPtr<layers::NativeLayerRoot> nsChildView::GetNativeLayerRoot() { return mNativeLayerRoot; }
 
 static int32_t FindTitlebarBottom(const nsTArray<nsIWidget::ThemeGeometry>& aThemeGeometries,
                                   int32_t aWindowWidth) {
   int32_t titlebarBottom = 0;