[iOS] Throttle painting using a UI-process-side CADisplayLink
authortimothy_horton@apple.com <timothy_horton@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Mon, 14 Jul 2014 18:11:46 +0000 (18:11 +0000)
committertimothy_horton@apple.com <timothy_horton@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Mon, 14 Jul 2014 18:11:46 +0000 (18:11 +0000)
https://bugs.webkit.org/show_bug.cgi?id=134879
<rdar://problem/17641699>

Reviewed by Simon Fraser.

Just waiting for CA to commit is insufficient to actually throttle to 60fps,
because nothing will block the main runloop from spinning.

Instead, listen to a CADisplayLink, and send didUpdate to the WebProcess
the first time it fires after we commit. This is not a guarantee that
our content is on the screen, but we don't have any way to make that guarantee yet.

This will throttle painting, rAF, etc. to the display refresh rate.

* UIProcess/mac/RemoteLayerTreeDrawingAreaProxy.h:
* UIProcess/mac/RemoteLayerTreeDrawingAreaProxy.mm:
(-[OneShotDisplayLinkHandler initWithDrawingAreaProxy:]):
(-[OneShotDisplayLinkHandler dealloc]):
(-[OneShotDisplayLinkHandler displayLinkFired:]):
(-[OneShotDisplayLinkHandler invalidate]):
(-[OneShotDisplayLinkHandler schedule]):
(WebKit::RemoteLayerTreeDrawingAreaProxy::RemoteLayerTreeDrawingAreaProxy):
(WebKit::RemoteLayerTreeDrawingAreaProxy::~RemoteLayerTreeDrawingAreaProxy):
(WebKit::RemoteLayerTreeDrawingAreaProxy::commitLayerTree):
(WebKit::RemoteLayerTreeDrawingAreaProxy::didRefreshDisplay):
(WebKit::RemoteLayerTreeDrawingAreaProxy::coreAnimationDidCommitLayers): Deleted.

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

Source/WebKit2/ChangeLog
Source/WebKit2/UIProcess/mac/RemoteLayerTreeDrawingAreaProxy.h
Source/WebKit2/UIProcess/mac/RemoteLayerTreeDrawingAreaProxy.mm

index b290a027522f59068bf48892784eb67415cb1c04..1f1fc8737616bab40a63cbbf2a5edcc21a549900 100644 (file)
@@ -1,3 +1,33 @@
+2014-07-14  Tim Horton  <timothy_horton@apple.com>
+
+        [iOS] Throttle painting using a UI-process-side CADisplayLink
+        https://bugs.webkit.org/show_bug.cgi?id=134879
+        <rdar://problem/17641699>
+
+        Reviewed by Simon Fraser.
+
+        Just waiting for CA to commit is insufficient to actually throttle to 60fps,
+        because nothing will block the main runloop from spinning.
+
+        Instead, listen to a CADisplayLink, and send didUpdate to the WebProcess
+        the first time it fires after we commit. This is not a guarantee that
+        our content is on the screen, but we don't have any way to make that guarantee yet.
+
+        This will throttle painting, rAF, etc. to the display refresh rate.
+
+        * UIProcess/mac/RemoteLayerTreeDrawingAreaProxy.h:
+        * UIProcess/mac/RemoteLayerTreeDrawingAreaProxy.mm:
+        (-[OneShotDisplayLinkHandler initWithDrawingAreaProxy:]):
+        (-[OneShotDisplayLinkHandler dealloc]):
+        (-[OneShotDisplayLinkHandler displayLinkFired:]):
+        (-[OneShotDisplayLinkHandler invalidate]):
+        (-[OneShotDisplayLinkHandler schedule]):
+        (WebKit::RemoteLayerTreeDrawingAreaProxy::RemoteLayerTreeDrawingAreaProxy):
+        (WebKit::RemoteLayerTreeDrawingAreaProxy::~RemoteLayerTreeDrawingAreaProxy):
+        (WebKit::RemoteLayerTreeDrawingAreaProxy::commitLayerTree):
+        (WebKit::RemoteLayerTreeDrawingAreaProxy::didRefreshDisplay):
+        (WebKit::RemoteLayerTreeDrawingAreaProxy::coreAnimationDidCommitLayers): Deleted.
+
 2014-07-14  Dan Bernstein  <mitz@apple.com>
 
         <rdar://problem/17657391> [iOS] Networking process writes persistent credentials to the keychain
index 948753836b9aa4ff2b8419ac80668de6be14e2b8..3a0aaae32164cae9c7dda3441281a1b6d6f5b38c 100644 (file)
@@ -31,7 +31,8 @@
 #include <WebCore/FloatPoint.h>
 #include <WebCore/IntPoint.h>
 #include <WebCore/IntSize.h>
-#include <WebCore/RunLoopObserver.h>
+
+OBJC_CLASS OneShotDisplayLinkHandler;
 
 namespace WebKit {
 
@@ -47,11 +48,11 @@ public:
 
     void acceleratedAnimationDidStart(uint64_t layerID, const String& key, double startTime);
 
-    void coreAnimationDidCommitLayers();
-
     uint64_t nextLayerTreeTransactionID() const { return m_pendingLayerTreeTransactionID + 1; }
     uint64_t lastCommittedLayerTreeTransactionID() const { return m_transactionIDForPendingCACommit; }
 
+    void didRefreshDisplay(double timestamp);
+
 private:
     virtual void sizeDidChange() override;
     virtual void deviceScaleFactorDidChange() override;
@@ -96,13 +97,13 @@ private:
     RetainPtr<CALayer> m_tileMapHostLayer;
     RetainPtr<CALayer> m_exposedRectIndicatorLayer;
 
-    std::unique_ptr<WebCore::RunLoopObserver> m_layerCommitObserver;
-
     uint64_t m_pendingLayerTreeTransactionID;
     uint64_t m_lastVisibleTransactionID;
     uint64_t m_transactionIDForPendingCACommit;
 
     CallbackMap m_callbacks;
+
+    RetainPtr<OneShotDisplayLinkHandler> m_displayLinkHandler;
 };
 
 DRAWING_AREA_PROXY_TYPE_CASTS(RemoteLayerTreeDrawingAreaProxy, type() == DrawingAreaTypeRemoteLayerTree);
index 140672b5b923ad7696c62c8280fe12101bf91e68..721309073208631cf4cc64529193f8267e78a77b 100644 (file)
 using namespace IPC;
 using namespace WebCore;
 
-static const CFIndex didCommitLayersRunLoopOrder = (CFIndex)RunLoopObserver::WellKnownRunLoopOrders::CoreAnimationCommit + 1;
+// FIXME: Mac will need something similar; we should figure out how to share this with DisplayRefreshMonitor without
+// breaking WebKit1 behavior or WebKit2-WebKit1 coexistence.
+#if PLATFORM(IOS)
+@interface OneShotDisplayLinkHandler : NSObject {
+    WebKit::RemoteLayerTreeDrawingAreaProxy* _drawingAreaProxy;
+    CADisplayLink *_displayLink;
+}
+
+- (id)initWithDrawingAreaProxy:(WebKit::RemoteLayerTreeDrawingAreaProxy*)drawingAreaProxy;
+- (void)displayLinkFired:(CADisplayLink *)sender;
+- (void)invalidate;
+- (void)schedule;
+
+@end
+
+@implementation OneShotDisplayLinkHandler
+
+- (id)initWithDrawingAreaProxy:(WebKit::RemoteLayerTreeDrawingAreaProxy*)drawingAreaProxy
+{
+    if (self = [super init]) {
+        _drawingAreaProxy = drawingAreaProxy;
+        // Note that CADisplayLink retains its target (self), so a call to -invalidate is needed on teardown.
+        _displayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(displayLinkFired:)];
+        [_displayLink addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSRunLoopCommonModes];
+        _displayLink.paused = YES;
+    }
+    return self;
+}
+
+- (void)dealloc
+{
+    ASSERT(!_displayLink);
+    [super dealloc];
+}
+
+- (void)displayLinkFired:(CADisplayLink *)sender
+{
+    ASSERT(isMainThread());
+    _drawingAreaProxy->didRefreshDisplay(sender.timestamp);
+    _displayLink.paused = YES;
+}
+
+- (void)invalidate
+{
+    [_displayLink invalidate];
+    _displayLink = nullptr;
+}
+
+- (void)schedule
+{
+    _displayLink.paused = NO;
+}
+
+@end
+#endif
 
 namespace WebKit {
 
@@ -51,6 +105,9 @@ RemoteLayerTreeDrawingAreaProxy::RemoteLayerTreeDrawingAreaProxy(WebPageProxy* w
     , m_pendingLayerTreeTransactionID(0)
     , m_lastVisibleTransactionID(0)
     , m_transactionIDForPendingCACommit(0)
+#if PLATFORM(IOS)
+    , m_displayLinkHandler(adoptNS([[OneShotDisplayLinkHandler alloc] initWithDrawingAreaProxy:this]))
+#endif
 {
 #if USE(IOSURFACE)
     // We don't want to pool surfaces in the UI process.
@@ -62,16 +119,16 @@ RemoteLayerTreeDrawingAreaProxy::RemoteLayerTreeDrawingAreaProxy(WebPageProxy* w
 
     if (m_webPageProxy->preferences().tiledScrollingIndicatorVisible())
         initializeDebugIndicator();
-
-    m_layerCommitObserver = RunLoopObserver::create(didCommitLayersRunLoopOrder, [this]() {
-        this->coreAnimationDidCommitLayers();
-    });
 }
 
 RemoteLayerTreeDrawingAreaProxy::~RemoteLayerTreeDrawingAreaProxy()
 {
     m_callbacks.invalidate(CallbackBase::Error::OwnerWasInvalidated);
     m_webPageProxy->process().removeMessageReceiver(Messages::RemoteLayerTreeDrawingAreaProxy::messageReceiverName(), m_webPageProxy->pageID());
+
+#if PLATFORM(IOS)
+    [m_displayLinkHandler invalidate];
+#endif
 }
 
 void RemoteLayerTreeDrawingAreaProxy::sizeDidChange()
@@ -167,7 +224,11 @@ void RemoteLayerTreeDrawingAreaProxy::commitLayerTree(const RemoteLayerTreeTrans
         asLayer(m_debugIndicatorLayerTreeHost->rootLayer()).name = @"Indicator host root";
     }
 
-    m_layerCommitObserver->schedule();
+#if PLATFORM(IOS)
+    [m_displayLinkHandler schedule];
+#else
+    didRefreshDisplay(monotonicallyIncreasingTime());
+#endif
 }
 
 void RemoteLayerTreeDrawingAreaProxy::acceleratedAnimationDidStart(uint64_t layerID, const String& key, double startTime)
@@ -302,10 +363,8 @@ void RemoteLayerTreeDrawingAreaProxy::initializeDebugIndicator()
     }
 }
 
-void RemoteLayerTreeDrawingAreaProxy::coreAnimationDidCommitLayers()
+void RemoteLayerTreeDrawingAreaProxy::didRefreshDisplay(double)
 {
-    m_layerCommitObserver->invalidate();
-
     if (!m_webPageProxy->isValid())
         return;