Align Mac WK2 layer flush throttling with iOS
authorantti@apple.com <antti@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Wed, 14 Nov 2018 16:53:25 +0000 (16:53 +0000)
committerantti@apple.com <antti@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Wed, 14 Nov 2018 16:53:25 +0000 (16:53 +0000)
https://bugs.webkit.org/show_bug.cgi?id=191632

Reviewed by Zalan Bujtas.

Currently Mac WK2 uses WebCore side throttling implementation in RenderLayerCompositor. This code has
throttling timer per-frame while the actual decision making and layer flushes itself are per page. These
timers generate way more flushes than expected in presence of multiple frames. There are also bugs in how
flushing state is updated when frames are created dynamically.

On iOS WK2 throttling is implemented on WebKit side and controlled by a per-page timer. Recent fixes also
make this implementation visually fast. We should align the Mac implementation and eventually unify them.

This patch implements throttling in TiledCoreAnimationDrawingArea mirroring the iOS RemoteLayerTreeDrawingArea
implementation. There are some adjustments for platform differences (local vs remote layers) and we continue
using runloop observer for the actual flushes. Timings are as in the existing Mac code.

The patch appears to be a significant performance progression.

* WebProcess/WebPage/mac/TiledCoreAnimationDrawingArea.h:
* WebProcess/WebPage/mac/TiledCoreAnimationDrawingArea.mm:
(WebKit::TiledCoreAnimationDrawingArea::TiledCoreAnimationDrawingArea):
(WebKit::TiledCoreAnimationDrawingArea::setLayerTreeStateIsFrozen):

Schedule an immediate flush when layers are unfrozen.

(WebKit::TiledCoreAnimationDrawingArea::scheduleCompositingLayerFlush):
(WebKit::TiledCoreAnimationDrawingArea::scheduleCompositingLayerFlushImmediately):

Track pending flushes with m_hasPendingFlush bit.
Delay flush if the flush throttling timer is active. Start it if we throttling but it is not active yet.

(WebKit::TiledCoreAnimationDrawingArea::flushLayers):

Clear m_hasPendingFlush if the flush succeeded.
Restart the throttling timer if we are still throttling.
Manage runloop observer invalidation here instead of the caller (and stop returning value).

(WebKit::TiledCoreAnimationDrawingArea::layerFlushRunLoopCallback):
(WebKit::TiledCoreAnimationDrawingArea::adjustLayerFlushThrottling):

Returning 'true' here disables WebCore side throttling code.

Start or stop the throttling timer on state changes.

(WebKit::TiledCoreAnimationDrawingArea::layerFlushThrottlingIsActive const):

This is used to control style and layout timers on WebCore side. Return false on frozen
state since unfreezing depends on style and layout.

(WebKit::TiledCoreAnimationDrawingArea::startLayerFlushThrottlingTimer):
(WebKit::TiledCoreAnimationDrawingArea::layerFlushThrottlingTimerFired):

git-svn-id: https://svn.webkit.org/repository/webkit/trunk@238178 268f45cc-cd09-0410-ab3c-d52691b4dbfc

Source/WebKit/ChangeLog
Source/WebKit/WebProcess/WebPage/mac/TiledCoreAnimationDrawingArea.h
Source/WebKit/WebProcess/WebPage/mac/TiledCoreAnimationDrawingArea.mm

index f2e779c..6f9bed9 100644 (file)
@@ -1,3 +1,58 @@
+2018-11-14  Antti Koivisto  <antti@apple.com>
+
+        Align Mac WK2 layer flush throttling with iOS
+        https://bugs.webkit.org/show_bug.cgi?id=191632
+
+        Reviewed by Zalan Bujtas.
+
+        Currently Mac WK2 uses WebCore side throttling implementation in RenderLayerCompositor. This code has
+        throttling timer per-frame while the actual decision making and layer flushes itself are per page. These
+        timers generate way more flushes than expected in presence of multiple frames. There are also bugs in how
+        flushing state is updated when frames are created dynamically.
+
+        On iOS WK2 throttling is implemented on WebKit side and controlled by a per-page timer. Recent fixes also
+        make this implementation visually fast. We should align the Mac implementation and eventually unify them.
+
+        This patch implements throttling in TiledCoreAnimationDrawingArea mirroring the iOS RemoteLayerTreeDrawingArea
+        implementation. There are some adjustments for platform differences (local vs remote layers) and we continue
+        using runloop observer for the actual flushes. Timings are as in the existing Mac code.
+
+        The patch appears to be a significant performance progression.
+
+        * WebProcess/WebPage/mac/TiledCoreAnimationDrawingArea.h:
+        * WebProcess/WebPage/mac/TiledCoreAnimationDrawingArea.mm:
+        (WebKit::TiledCoreAnimationDrawingArea::TiledCoreAnimationDrawingArea):
+        (WebKit::TiledCoreAnimationDrawingArea::setLayerTreeStateIsFrozen):
+
+        Schedule an immediate flush when layers are unfrozen.
+
+        (WebKit::TiledCoreAnimationDrawingArea::scheduleCompositingLayerFlush):
+        (WebKit::TiledCoreAnimationDrawingArea::scheduleCompositingLayerFlushImmediately):
+
+        Track pending flushes with m_hasPendingFlush bit.
+        Delay flush if the flush throttling timer is active. Start it if we throttling but it is not active yet.
+
+        (WebKit::TiledCoreAnimationDrawingArea::flushLayers):
+
+        Clear m_hasPendingFlush if the flush succeeded.
+        Restart the throttling timer if we are still throttling.
+        Manage runloop observer invalidation here instead of the caller (and stop returning value).
+
+        (WebKit::TiledCoreAnimationDrawingArea::layerFlushRunLoopCallback):
+        (WebKit::TiledCoreAnimationDrawingArea::adjustLayerFlushThrottling):
+
+        Returning 'true' here disables WebCore side throttling code.
+
+        Start or stop the throttling timer on state changes.
+
+        (WebKit::TiledCoreAnimationDrawingArea::layerFlushThrottlingIsActive const):
+
+        This is used to control style and layout timers on WebCore side. Return false on frozen
+        state since unfreezing depends on style and layout.
+
+        (WebKit::TiledCoreAnimationDrawingArea::startLayerFlushThrottlingTimer):
+        (WebKit::TiledCoreAnimationDrawingArea::layerFlushThrottlingTimerFired):
+
 2018-11-14  Chris Dumez  <cdumez@apple.com>
 
         WebKit.WKHTTPCookieStoreWithoutProcessPool API test is failing with process prewarming is enabled
index a50cf16..d1eb193 100644 (file)
@@ -88,7 +88,7 @@ private:
 
     bool dispatchDidReachLayoutMilestone(OptionSet<WebCore::LayoutMilestone>) override;
 
-    bool flushLayers();
+    void flushLayers();
 
     // Message handlers.
     void updateGeometry(const WebCore::IntSize& viewSize, bool flushSynchronously, const WTF::MachSendRight& fencePort) override;
@@ -130,6 +130,12 @@ private:
     void invalidateLayerFlushRunLoopObserver();
     void scheduleLayerFlushRunLoopObserver();
 
+    bool adjustLayerFlushThrottling(WebCore::LayerFlushThrottleState::Flags) override;
+    bool layerFlushThrottlingIsActive() const override;
+
+    void startLayerFlushThrottlingTimer();
+    void layerFlushThrottlingTimerFired();
+
     bool m_layerTreeStateIsFrozen;
 
     std::unique_ptr<LayerHostingContext> m_layerHostingContext;
@@ -168,6 +174,12 @@ private:
     Vector<CallbackID> m_pendingCallbackIDs;
 
     std::unique_ptr<WebCore::RunLoopObserver> m_layerFlushRunLoopObserver;
+
+    bool m_isThrottlingLayerFlushes { false };
+    bool m_isLayerFlushThrottlingTemporarilyDisabledForInteraction { false };
+    bool m_hasPendingFlush { false };
+
+    WebCore::Timer m_layerFlushThrottlingTimer;
 };
 
 } // namespace WebKit
index dcacb8e..a791e93 100644 (file)
@@ -80,6 +80,7 @@ TiledCoreAnimationDrawingArea::TiledCoreAnimationDrawingArea(WebPage& webPage, c
     , m_transientZoomScale(1)
     , m_sendDidUpdateActivityStateTimer(RunLoop::main(), this, &TiledCoreAnimationDrawingArea::didUpdateActivityStateTimerFired)
     , m_viewOverlayRootLayer(nullptr)
+    , m_layerFlushThrottlingTimer(*this, &TiledCoreAnimationDrawingArea::layerFlushThrottlingTimerFired)
 {
     m_webPage.corePage()->settings().setForceCompositingMode(true);
 
@@ -174,10 +175,14 @@ void TiledCoreAnimationDrawingArea::setLayerTreeStateIsFrozen(bool layerTreeStat
 
     m_layerTreeStateIsFrozen = layerTreeStateIsFrozen;
 
-    if (m_layerTreeStateIsFrozen)
+    if (m_layerTreeStateIsFrozen) {
         invalidateLayerFlushRunLoopObserver();
-    else
-        scheduleLayerFlushRunLoopObserver();
+        m_layerFlushThrottlingTimer.stop();
+    } else {
+        // Immediate flush as any delay in unfreezing can result in flashes.
+        if (m_hasPendingFlush)
+            scheduleLayerFlushRunLoopObserver();
+    }
 }
 
 bool TiledCoreAnimationDrawingArea::layerTreeStateIsFrozen() const
@@ -191,15 +196,36 @@ void TiledCoreAnimationDrawingArea::scheduleInitialDeferredPaint()
 
 void TiledCoreAnimationDrawingArea::scheduleCompositingLayerFlush()
 {
-    if (m_layerTreeStateIsFrozen)
+    m_hasPendingFlush = true;
+
+    if (m_layerTreeStateIsFrozen) {
+        m_isLayerFlushThrottlingTemporarilyDisabledForInteraction = false;
         return;
+    }
+
+    if (m_isLayerFlushThrottlingTemporarilyDisabledForInteraction) {
+        m_isLayerFlushThrottlingTemporarilyDisabledForInteraction = false;
+        scheduleLayerFlushRunLoopObserver();
+        m_layerFlushThrottlingTimer.stop();
+        return;
+    }
+
+    if (m_layerFlushThrottlingTimer.isActive()) {
+        ASSERT(m_isThrottlingLayerFlushes);
+        return;
+    }
+
+    if (m_isThrottlingLayerFlushes) {
+        startLayerFlushThrottlingTimer();
+        return;
+    }
 
     scheduleLayerFlushRunLoopObserver();
 }
 
 void TiledCoreAnimationDrawingArea::scheduleCompositingLayerFlushImmediately()
 {
-    scheduleCompositingLayerFlush();
+    scheduleLayerFlushRunLoopObserver();
 }
 
 void TiledCoreAnimationDrawingArea::updatePreferences(const WebPreferencesStore&)
@@ -424,10 +450,10 @@ void TiledCoreAnimationDrawingArea::addTransactionCallbackID(CallbackID callback
     scheduleCompositingLayerFlush();
 }
 
-bool TiledCoreAnimationDrawingArea::flushLayers()
+void TiledCoreAnimationDrawingArea::flushLayers()
 {
     if (layerTreeStateIsFrozen())
-        return false;
+        return;
 
     @autoreleasepool {
         scaleViewToFitDocumentIfNeeded();
@@ -461,7 +487,8 @@ bool TiledCoreAnimationDrawingArea::flushLayers()
                 drawingArea->sendPendingNewlyReachedLayoutMilestones();
         } forPhase:kCATransactionPhasePostCommit];
 
-        bool returnValue = m_webPage.mainFrameView()->flushCompositingStateIncludingSubframes();
+        m_hasPendingFlush = !m_webPage.mainFrameView()->flushCompositingStateIncludingSubframes();
+
 #if ENABLE(ASYNC_SCROLLING)
         if (ScrollingCoordinator* scrollingCoordinator = m_webPage.corePage()->scrollingCoordinator())
             scrollingCoordinator->commitTreeStateIfNeeded();
@@ -477,7 +504,13 @@ bool TiledCoreAnimationDrawingArea::flushLayers()
             m_pendingCallbackIDs.clear();
         }
 
-        return returnValue;
+        if (!m_hasPendingFlush)
+            invalidateLayerFlushRunLoopObserver();
+
+        if (m_isThrottlingLayerFlushes)
+            startLayerFlushThrottlingTimer();
+        else
+            m_layerFlushThrottlingTimer.stop();
     }
 }
 
@@ -902,8 +935,7 @@ bool TiledCoreAnimationDrawingArea::dispatchDidReachLayoutMilestone(OptionSet<We
 
 void TiledCoreAnimationDrawingArea::layerFlushRunLoopCallback()
 {
-    if (flushLayers())
-        invalidateLayerFlushRunLoopObserver();
+    flushLayers();
 }
 
 void TiledCoreAnimationDrawingArea::invalidateLayerFlushRunLoopObserver()
@@ -916,6 +948,51 @@ void TiledCoreAnimationDrawingArea::scheduleLayerFlushRunLoopObserver()
     m_layerFlushRunLoopObserver->schedule(CFRunLoopGetCurrent());
 }
 
+bool TiledCoreAnimationDrawingArea::adjustLayerFlushThrottling(WebCore::LayerFlushThrottleState::Flags flags)
+{
+    bool wasThrottlingLayerFlushes = m_isThrottlingLayerFlushes;
+    m_isThrottlingLayerFlushes = flags & WebCore::LayerFlushThrottleState::Enabled;
+    m_isLayerFlushThrottlingTemporarilyDisabledForInteraction = flags & WebCore::LayerFlushThrottleState::UserIsInteracting;
+
+    if (wasThrottlingLayerFlushes == m_isThrottlingLayerFlushes)
+        return true;
+
+    m_layerFlushThrottlingTimer.stop();
+
+    if (m_layerTreeStateIsFrozen)
+        return true;
+
+    if (m_isThrottlingLayerFlushes) {
+        invalidateLayerFlushRunLoopObserver();
+        startLayerFlushThrottlingTimer();
+    } else if (m_hasPendingFlush)
+        scheduleLayerFlushRunLoopObserver();
+
+    return true;
+}
+
+bool TiledCoreAnimationDrawingArea::layerFlushThrottlingIsActive() const
+{
+    return m_isThrottlingLayerFlushes && !m_layerTreeStateIsFrozen;
+}
+
+void TiledCoreAnimationDrawingArea::startLayerFlushThrottlingTimer()
+{
+    ASSERT(m_isThrottlingLayerFlushes);
+
+    const auto throttledFlushDelay = 500_ms;
+    m_layerFlushThrottlingTimer.startOneShot(throttledFlushDelay);
+}
+
+void TiledCoreAnimationDrawingArea::layerFlushThrottlingTimerFired()
+{
+    if (m_layerTreeStateIsFrozen)
+        return;
+    if (!m_hasPendingFlush)
+        return;
+    scheduleLayerFlushRunLoopObserver();
+}
+
 } // namespace WebKit
 
 #endif // !PLATFORM(IOS_FAMILY)