Bug 1592026 - Move AsyncCATransaction suspension into NativeLayerRootCA. r=jrmuizel

Differential Revision: https://phabricator.services.mozilla.com/D57061

--HG--
extra : moz-landing-system : lando
This commit is contained in:
Markus Stange 2019-12-29 12:18:32 +00:00
parent a090f2ff17
commit c96a136f94
5 changed files with 83 additions and 55 deletions

View File

@ -41,6 +41,10 @@ class NativeLayerRoot {
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() {}
};

View File

@ -41,13 +41,36 @@ class SurfacePoolHandleCA;
// 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,
@ -59,9 +82,6 @@ class NativeLayerRootCA : public NativeLayerRoot {
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;
@ -71,6 +91,18 @@ class NativeLayerRootCA : public NativeLayerRoot {
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

View File

@ -5,8 +5,9 @@
#include "mozilla/layers/NativeLayerCA.h"
#import <QuartzCore/QuartzCore.h>
#import <AppKit/NSAnimationContext.h>
#import <AppKit/NSColor.h>
#import <QuartzCore/QuartzCore.h>
#include <utility>
#include <algorithm>
@ -123,10 +124,32 @@ float NativeLayerRootCA::BackingScale() {
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
@ -144,6 +167,11 @@ void NativeLayerRootCA::ApplyChanges() {
mRootCALayer.sublayers = sublayers;
mMutated = false;
}
[NSAnimationContext endGrouping];
mCommitPending = false;
return true;
}
NativeLayerCA::NativeLayerCA(const IntSize& aSize, bool aIsOpaque,

View File

@ -593,22 +593,6 @@ class nsChildView final : public nsBaseWidget {
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

View File

@ -236,7 +236,6 @@ nsChildView::nsChildView()
mDrawing(false),
mIsDispatchPaint(false),
mPluginFocused{false},
mCompositingState("nsChildView::mCompositingState"),
mCurrentPanGestureBelongsToSwipe{false} {}
nsChildView::~nsChildView() {
@ -849,13 +848,12 @@ void nsChildView::SuspendAsyncCATransactions() {
// 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);
@ -866,14 +864,12 @@ void nsChildView::MaybeScheduleUnsuspendAsyncCATransactions() {
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];
}
}
@ -1378,11 +1374,7 @@ void nsChildView::HandleMainThreadCATransaction() {
// 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();
}
@ -1717,20 +1709,8 @@ bool nsChildView::PreRender(WidgetRenderingContext* aContext) {
}
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();
}