[Experiment] Implement code to detect high frequency painting
authorhyatt@apple.com <hyatt@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Wed, 8 Nov 2017 16:11:55 +0000 (16:11 +0000)
committerhyatt@apple.com <hyatt@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Wed, 8 Nov 2017 16:11:55 +0000 (16:11 +0000)
https://bugs.webkit.org/show_bug.cgi?id=179118
Source/WebCore:

<rdar://problem/35347068>

Reviewed by Simon Fraser.

This patch implements a model for detecting that objects whose
graphics resources we want to cache (like glyphs or paths) are painting
frequently enough that it's worth taking the memory hit to cache them.

Paint frequency detection is done at the self-painting RenderLayer level, since
a lower level of granularity (e.g., per-RenderObject) would be too memory-intensive.

* rendering/PaintInfo.h:
(WebCore::PaintInfo::PaintInfo):
(WebCore::PaintInfo::enclosingSelfPaintingLayer):
Add the enclosing self-painting layer that is currently painting to the PaintInfo
so that objects that have cachaeble graphics resources can notify the layer when
they actually paint them.

* rendering/RenderLayer.cpp:
(WebCore::PaintFrequencyInfo::PaintFrequencyInfo):
(WebCore::PaintFrequencyInfo::paintingFrequently const):
(WebCore::PaintFrequencyInfo::updatePaintFrequency):
(WebCore::PaintFrequencyInfo::paintingCacheableResource):
RenderLayers have a pointer to PaintFrequencyInfo that is allocated once you've
painted cachaeble resources.

(WebCore::RenderLayer::paintLayerContents):
(WebCore::RenderLayer::paintBackgroundForFragments):
(WebCore::RenderLayer::paintForegroundForFragmentsWithPhase):
(WebCore::RenderLayer::paintOutlineForFragments):
(WebCore::RenderLayer::paintMaskForFragments):
(WebCore::RenderLayer::paintChildClippingMaskForFragments):
(WebCore::RenderLayer::calculateClipRects const):
* rendering/RenderLayer.h:
Patch to pass the layer with the PaintInfo.

* testing/Internals.cpp:
(WebCore::Internals::isPaintingFrequently):
(WebCore::Internals::incrementFrequentPaintCounter):
* testing/Internals.h:
* testing/Internals.idl:
Test methods for making layout tests to ensure we go into the mode properly.

LayoutTests:

Reviewed by Simon Fraser.

* fast/block/block-move-frequent-paint-expected.txt: Added.
* fast/block/block-move-frequent-paint.html: Added.

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

LayoutTests/ChangeLog
LayoutTests/fast/block/block-move-frequent-paint-expected.txt [new file with mode: 0644]
LayoutTests/fast/block/block-move-frequent-paint.html [new file with mode: 0644]
Source/WebCore/ChangeLog
Source/WebCore/rendering/PaintInfo.h
Source/WebCore/rendering/RenderLayer.cpp
Source/WebCore/rendering/RenderLayer.h
Source/WebCore/testing/Internals.cpp
Source/WebCore/testing/Internals.h
Source/WebCore/testing/Internals.idl

index 989df7a..2e4ef65 100644 (file)
@@ -1,3 +1,13 @@
+2017-11-08  David Hyatt  <hyatt@apple.com>
+
+        [Experiment] Implement code to detect high frequency painting
+        https://bugs.webkit.org/show_bug.cgi?id=179118
+
+        Reviewed by Simon Fraser.
+
+        * fast/block/block-move-frequent-paint-expected.txt: Added.
+        * fast/block/block-move-frequent-paint.html: Added.
+
 2017-11-08  Ms2ger  <Ms2ger@igalia.com>
 
         Update XMLHttpRequest tests.
diff --git a/LayoutTests/fast/block/block-move-frequent-paint-expected.txt b/LayoutTests/fast/block/block-move-frequent-paint-expected.txt
new file mode 100644 (file)
index 0000000..c6fd4ef
--- /dev/null
@@ -0,0 +1,11 @@
+This tests that we kick into high frequency painting mode properly when animating something quickly.
+
+On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".
+
+
+PASS window.internals.isPaintingFrequently(square) is false
+PASS window.internals.isPaintingFrequently(square) is true
+PASS successfullyParsed is true
+
+TEST COMPLETE
+\13
diff --git a/LayoutTests/fast/block/block-move-frequent-paint.html b/LayoutTests/fast/block/block-move-frequent-paint.html
new file mode 100644 (file)
index 0000000..d5ec200
--- /dev/null
@@ -0,0 +1,49 @@
+<html>
+<head>
+<script src="../../resources/js-test-pre.js"></script>
+<script>
+var counter = 0
+var square
+var debugFudge = 200;
+
+function runTest(timestamp)
+{
+    square = document.getElementById('square');
+    
+    if (counter == 0 && window.internals)
+        shouldBe("window.internals.isPaintingFrequently(square)", "false");
+    
+    var oldLeft = square.offsetLeft;
+    var oldTop = square.offsetTop;
+    square.style.left = (oldLeft + 1) + 'px';
+    square.style.top = (oldTop + 1) + 'px';
+    
+    counter++;
+    if (window.testRunner) {
+        // For debug builds that can't animate fast we just do a large number of increments.
+        for (var i = 0; i < debugFudge; ++i)
+            window.internals.incrementFrequentPaintCounter(square);
+    }
+
+    if (counter < 60)
+        requestAnimationFrame(runTest);
+    else if (window.internals) {
+        shouldBe("window.internals.isPaintingFrequently(square)", "true");
+        finishJSTest();
+    }
+}
+
+</script>
+<body>
+<div style="position:absolute; left:0;top:0">
+
+<div style="position:absolute; left:0;top:0;width:2px; height:2px; background-color:green" id="square"></div>
+</div>
+
+<script>
+description("This tests that we kick into high frequency painting mode properly when animating something quickly.");
+window.jsTestIsAsync = true;
+window.requestAnimationFrame(runTest);
+</script>
+<script src="../../resources/js-test-post.js"></script>
+\13
index af5d460..b495d8a 100644 (file)
@@ -1,3 +1,50 @@
+2017-11-08  David Hyatt  <hyatt@apple.com>
+
+        [Experiment] Implement code to detect high frequency painting
+        https://bugs.webkit.org/show_bug.cgi?id=179118
+        <rdar://problem/35347068>
+
+        Reviewed by Simon Fraser.
+
+        This patch implements a model for detecting that objects whose
+        graphics resources we want to cache (like glyphs or paths) are painting
+        frequently enough that it's worth taking the memory hit to cache them.
+
+        Paint frequency detection is done at the self-painting RenderLayer level, since
+        a lower level of granularity (e.g., per-RenderObject) would be too memory-intensive.
+
+        * rendering/PaintInfo.h:
+        (WebCore::PaintInfo::PaintInfo):
+        (WebCore::PaintInfo::enclosingSelfPaintingLayer):
+        Add the enclosing self-painting layer that is currently painting to the PaintInfo
+        so that objects that have cachaeble graphics resources can notify the layer when
+        they actually paint them.
+
+        * rendering/RenderLayer.cpp:
+        (WebCore::PaintFrequencyInfo::PaintFrequencyInfo):
+        (WebCore::PaintFrequencyInfo::paintingFrequently const):
+        (WebCore::PaintFrequencyInfo::updatePaintFrequency):
+        (WebCore::PaintFrequencyInfo::paintingCacheableResource):
+        RenderLayers have a pointer to PaintFrequencyInfo that is allocated once you've
+        painted cachaeble resources.
+
+        (WebCore::RenderLayer::paintLayerContents):
+        (WebCore::RenderLayer::paintBackgroundForFragments):
+        (WebCore::RenderLayer::paintForegroundForFragmentsWithPhase):
+        (WebCore::RenderLayer::paintOutlineForFragments):
+        (WebCore::RenderLayer::paintMaskForFragments):
+        (WebCore::RenderLayer::paintChildClippingMaskForFragments):
+        (WebCore::RenderLayer::calculateClipRects const):
+        * rendering/RenderLayer.h:
+        Patch to pass the layer with the PaintInfo.
+
+        * testing/Internals.cpp:
+        (WebCore::Internals::isPaintingFrequently):
+        (WebCore::Internals::incrementFrequentPaintCounter):
+        * testing/Internals.h:
+        * testing/Internals.idl:
+        Test methods for making layout tests to ensure we go into the mode properly.
+
 2017-11-08  Zalan Bujtas  <zalan@apple.com>
 
         [LayoutState cleanup] Make public data members private.
index 5a6748e..40b611d 100644 (file)
@@ -38,6 +38,7 @@ namespace WebCore {
 
 class OverlapTestRequestClient;
 class RenderInline;
+class RenderLayer;
 class RenderLayerModelObject;
 class RenderObject;
 
@@ -51,7 +52,7 @@ struct PaintInfo {
     PaintInfo(GraphicsContext& newContext, const LayoutRect& newRect, PaintPhase newPhase, PaintBehavior newPaintBehavior,
         RenderObject* newSubtreePaintRoot = nullptr, ListHashSet<RenderInline*>* newOutlineObjects = nullptr,
         OverlapTestRequestMap* overlapTestRequests = nullptr, const RenderLayerModelObject* newPaintContainer = nullptr,
-        bool newRequireSecurityOriginAccessForWidgets = false)
+        const RenderLayer* enclosingSelfPaintingLayer = nullptr, bool newRequireSecurityOriginAccessForWidgets = false)
             : rect(newRect)
             , phase(newPhase)
             , paintBehavior(newPaintBehavior)
@@ -60,6 +61,7 @@ struct PaintInfo {
             , overlapTestRequests(overlapTestRequests)
             , paintContainer(newPaintContainer)
             , requireSecurityOriginAccessForWidgets(newRequireSecurityOriginAccessForWidgets)
+            , m_enclosingSelfPaintingLayer(enclosingSelfPaintingLayer)
             , m_context(&newContext)
     {
     }
@@ -100,6 +102,8 @@ struct PaintInfo {
     bool skipRootBackground() const { return paintBehavior & PaintBehaviorSkipRootBackground; }
     bool paintRootBackgroundOnly() const { return paintBehavior & PaintBehaviorRootBackgroundOnly; }
 
+    const RenderLayer* enclosingSelfPaintingLayer() const { return m_enclosingSelfPaintingLayer; }
+
     void applyTransform(const AffineTransform& localToAncestorTransform)
     {
         if (localToAncestorTransform.isIdentity())
@@ -123,6 +127,7 @@ struct PaintInfo {
     OverlapTestRequestMap* overlapTestRequests;
     const RenderLayerModelObject* paintContainer; // the layer object that originates the current painting
     bool requireSecurityOriginAccessForWidgets { false };
+    const RenderLayer* m_enclosingSelfPaintingLayer { nullptr };
 
 private:
     GraphicsContext* m_context;
index 697cf05..72f923f 100644 (file)
 #include "TranslateTransformOperation.h"
 #include "WheelEventTestTrigger.h"
 #include <stdio.h>
+#include <wtf/MonotonicTime.h>
 #include <wtf/StdLibExtras.h>
 #include <wtf/text/CString.h>
 #include <wtf/text/TextStream.h>
 
 #define MIN_INTERSECT_FOR_REVEAL 32
 
+const Seconds paintFrequencyTimePerFrameThreshold = 32_ms;
+const Seconds paintFrequencySecondsIdleThreshold = 5_s;
+
 namespace WebCore {
 
 using namespace HTMLNames;
 
+// This class is used to detect when we are painting frequently so that - even in a painting model
+// without display lists - we can build and cache portions of display lists and reuse them only when
+// animating. Once we transition fully to display lists, we can probably just pull from the previous
+// paint's display list if it is still around and get rid of this code.
+class PaintFrequencyInfo {
+    WTF_MAKE_FAST_ALLOCATED;
+    
+public:
+    PaintFrequencyInfo(MonotonicTime now)
+        : m_firstPaintTime(now)
+        , m_lastPaintTime(now)
+    { }
+
+    enum PaintFrequency { Idle, Low, High };
+    PaintFrequency updatePaintFrequency();
+    
+    void paintingCacheableResource(MonotonicTime);
+    void setPaintedCacheableResource(bool painted) { m_paintedCacheableResource = painted; }
+
+    bool paintingFrequently() const { return m_paintingFrequently; }
+
+private:
+    MonotonicTime m_firstPaintTime;
+    MonotonicTime m_lastPaintTime;
+    unsigned m_totalPaints { 1 };
+    bool m_paintedCacheableResource { false };
+    bool m_paintingFrequently { false };
+};
+
+PaintFrequencyInfo::PaintFrequency PaintFrequencyInfo::updatePaintFrequency()
+{
+    MonotonicTime now = MonotonicTime::now(); // FIXME: Should have a single time for the paint of the whole frame.
+    if ((now - m_lastPaintTime) > paintFrequencySecondsIdleThreshold)
+        return Idle;
+    if (m_totalPaints && ((now - m_firstPaintTime) / m_totalPaints) <= paintFrequencyTimePerFrameThreshold) {
+        m_paintingFrequently = true;
+        return High;
+    }
+    m_paintingFrequently = false;
+    return Low;
+}
+
+void PaintFrequencyInfo::paintingCacheableResource(MonotonicTime now)
+{
+    if (m_paintedCacheableResource)
+        return;
+    
+    m_paintedCacheableResource = true;
+    m_lastPaintTime = now;
+    m_totalPaints++;
+}
+
 class ClipRects : public RefCounted<ClipRects> {
     WTF_MAKE_FAST_ALLOCATED;
 public:
@@ -4291,6 +4347,9 @@ void RenderLayer::paintLayerContents(GraphicsContext& context, const LayerPainti
 
     bool selectionAndBackgroundsOnly = paintingInfo.paintBehavior & PaintBehaviorSelectionAndBackgroundsOnly;
     bool selectionOnly = paintingInfo.paintBehavior & PaintBehaviorSelectionOnly;
+    
+    if (shouldPaintContent && m_paintFrequencyInfo && m_paintFrequencyInfo->updatePaintFrequency() == PaintFrequencyInfo::PaintFrequency::Idle)
+        clearPaintFrequencyInfo();
 
     LayerFragments layerFragments;
     RenderObject* subtreePaintRootForRenderer = nullptr;
@@ -4702,7 +4761,7 @@ void RenderLayer::paintBackgroundForFragments(const LayerFragments& layerFragmen
         
         // Paint the background.
         // FIXME: Eventually we will collect the region from the fragment itself instead of just from the paint info.
-        PaintInfo paintInfo(context, fragment.backgroundRect.rect(), PaintPhaseBlockBackground, paintBehavior, subtreePaintRootForRenderer, nullptr, nullptr, &localPaintingInfo.rootLayer->renderer());
+        PaintInfo paintInfo(context, fragment.backgroundRect.rect(), PaintPhaseBlockBackground, paintBehavior, subtreePaintRootForRenderer, nullptr, nullptr, &localPaintingInfo.rootLayer->renderer(), this);
         renderer().paint(paintInfo, toLayoutPoint(fragment.layerBounds.location() - renderBoxLocation() + localPaintingInfo.subpixelOffset));
 
         if (localPaintingInfo.clipToDirtyRect)
@@ -4777,7 +4836,7 @@ void RenderLayer::paintForegroundForFragmentsWithPhase(PaintPhase phase, const L
         if (shouldClip)
             clipToRect(context, localPaintingInfo, fragment.foregroundRect);
     
-        PaintInfo paintInfo(context, fragment.foregroundRect.rect(), phase, paintBehavior, subtreePaintRootForRenderer, nullptr, nullptr, &localPaintingInfo.rootLayer->renderer(), localPaintingInfo.requireSecurityOriginAccessForWidgets);
+        PaintInfo paintInfo(context, fragment.foregroundRect.rect(), phase, paintBehavior, subtreePaintRootForRenderer, nullptr, nullptr, &localPaintingInfo.rootLayer->renderer(), this, localPaintingInfo.requireSecurityOriginAccessForWidgets);
         if (phase == PaintPhaseForeground)
             paintInfo.overlapTestRequests = localPaintingInfo.overlapTestRequests;
         renderer().paint(paintInfo, toLayoutPoint(fragment.layerBounds.location() - renderBoxLocation() + localPaintingInfo.subpixelOffset));
@@ -4795,7 +4854,7 @@ void RenderLayer::paintOutlineForFragments(const LayerFragments& layerFragments,
             continue;
     
         // Paint our own outline
-        PaintInfo paintInfo(context, fragment.backgroundRect.rect(), PaintPhaseSelfOutline, paintBehavior, subtreePaintRootForRenderer, nullptr, nullptr, &localPaintingInfo.rootLayer->renderer());
+        PaintInfo paintInfo(context, fragment.backgroundRect.rect(), PaintPhaseSelfOutline, paintBehavior, subtreePaintRootForRenderer, nullptr, nullptr, &localPaintingInfo.rootLayer->renderer(), this);
         clipToRect(context, localPaintingInfo, fragment.backgroundRect, DoNotIncludeSelfForBorderRadius);
         renderer().paint(paintInfo, toLayoutPoint(fragment.layerBounds.location() - renderBoxLocation() + localPaintingInfo.subpixelOffset));
         restoreClip(context, localPaintingInfo, fragment.backgroundRect);
@@ -4814,7 +4873,7 @@ void RenderLayer::paintMaskForFragments(const LayerFragments& layerFragments, Gr
         
         // Paint the mask.
         // FIXME: Eventually we will collect the region from the fragment itself instead of just from the paint info.
-        PaintInfo paintInfo(context, fragment.backgroundRect.rect(), PaintPhaseMask, paintBehavior, subtreePaintRootForRenderer, nullptr, nullptr, &localPaintingInfo.rootLayer->renderer());
+        PaintInfo paintInfo(context, fragment.backgroundRect.rect(), PaintPhaseMask, paintBehavior, subtreePaintRootForRenderer, nullptr, nullptr, &localPaintingInfo.rootLayer->renderer(), this);
         renderer().paint(paintInfo, toLayoutPoint(fragment.layerBounds.location() - renderBoxLocation() + localPaintingInfo.subpixelOffset));
         
         if (localPaintingInfo.clipToDirtyRect)
@@ -4832,7 +4891,7 @@ void RenderLayer::paintChildClippingMaskForFragments(const LayerFragments& layer
             clipToRect(context, localPaintingInfo, fragment.foregroundRect, IncludeSelfForBorderRadius); // Child clipping mask painting will handle clipping to self.
 
         // Paint the clipped mask.
-        PaintInfo paintInfo(context, fragment.backgroundRect.rect(), PaintPhaseClippingMask, paintBehavior, subtreePaintRootForRenderer, nullptr, nullptr, &localPaintingInfo.rootLayer->renderer());
+        PaintInfo paintInfo(context, fragment.backgroundRect.rect(), PaintPhaseClippingMask, paintBehavior, subtreePaintRootForRenderer, nullptr, nullptr, &localPaintingInfo.rootLayer->renderer(), this);
         renderer().paint(paintInfo, toLayoutPoint(fragment.layerBounds.location() - renderBoxLocation() + localPaintingInfo.subpixelOffset));
 
         if (localPaintingInfo.clipToDirtyRect)
@@ -6806,6 +6865,28 @@ RenderStyle RenderLayer::createReflectionStyle()
     return newStyle;
 }
 
+bool RenderLayer::paintingFrequently() const
+{
+    return m_paintFrequencyInfo && m_paintFrequencyInfo->paintingFrequently();
+}
+
+void RenderLayer::simulateFrequentPaint()
+{
+    auto now = MonotonicTime::now();
+    if (!m_paintFrequencyInfo)
+        m_paintFrequencyInfo = std::make_unique<PaintFrequencyInfo>(now);
+    else {
+        m_paintFrequencyInfo->paintingCacheableResource(now);
+        m_paintFrequencyInfo->setPaintedCacheableResource(false);
+        m_paintFrequencyInfo->updatePaintFrequency();
+    }
+}
+
+void RenderLayer::clearPaintFrequencyInfo()
+{
+    m_paintFrequencyInfo = nullptr;
+}
+
 void RenderLayer::updateOrRemoveFilterClients()
 {
     if (!hasFilter()) {
index f34b198..996cfdc 100644 (file)
@@ -66,6 +66,7 @@ class FilterOperations;
 class HitTestRequest;
 class HitTestResult;
 class HitTestingTransformState;
+class PaintFrequencyInfo;
 class RenderFragmentedFlow;
 class RenderGeometryMap;
 class RenderLayerBacking;
@@ -712,6 +713,10 @@ public:
 
     bool shouldPlaceBlockDirectionScrollbarOnLeft() const final { return renderer().shouldPlaceBlockDirectionScrollbarOnLeft(); }
 
+    WEBCORE_EXPORT void simulateFrequentPaint();
+    WEBCORE_EXPORT bool paintingFrequently() const;
+    void clearPaintFrequencyInfo();
+
 private:
     enum CollectLayersBehavior { StopAtStackingContexts, StopAtStackingContainers };
 
@@ -1164,6 +1169,8 @@ private:
     IntRect m_blockSelectionGapsBounds;
 
     std::unique_ptr<RenderLayerBacking> m_backing;
+    
+    std::unique_ptr<PaintFrequencyInfo> m_paintFrequencyInfo;
 };
 
 inline void RenderLayer::clearZOrderLists()
index 7267235..0235d6b 100644 (file)
@@ -985,6 +985,17 @@ bool Internals::hasPausedImageAnimations(Element& element)
 {
     return element.renderer() && element.renderer()->hasPausedImageAnimations();
 }
+    
+bool Internals::isPaintingFrequently(Element& element)
+{
+    return element.renderer() && element.renderer()->enclosingLayer() && element.renderer()->enclosingLayer()->paintingFrequently();
+}
+
+void Internals::incrementFrequentPaintCounter(Element& element)
+{
+    if (element.renderer() && element.renderer()->enclosingLayer())
+        element.renderer()->enclosingLayer()->simulateFrequentPaint();
+}
 
 Ref<CSSComputedStyleDeclaration> Internals::computedStyleIncludingVisitedInfo(Element& element) const
 {
index 9230801..9b43ffa 100644 (file)
@@ -103,6 +103,9 @@ public:
     ExceptionOr<String> elementRenderTreeAsText(Element&);
     bool hasPausedImageAnimations(Element&);
 
+    bool isPaintingFrequently(Element&);
+    void incrementFrequentPaintCounter(Element&);
+
     String address(Node&);
     bool nodeNeedsStyleRecalc(Node&);
     String styleChangeType(Node&);
index a5f4b0a..fd89804 100644 (file)
@@ -93,6 +93,10 @@ enum EventThrottlingBehavior {
     // Animated image pausing testing.
     boolean hasPausedImageAnimations(Element element);
 
+    // Must be called on an element whose enclosingLayer() is self-painting.
+    boolean isPaintingFrequently(Element element);
+    void incrementFrequentPaintCounter(Element element);
+
     [MayThrowException] DOMString elementRenderTreeAsText(Element element);
     boolean isPreloaded(DOMString url);
     boolean isLoadingFromMemoryCache(DOMString url);