Log Message
[Experiment] Implement code to detect high frequency painting 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.
Modified Paths
- trunk/LayoutTests/ChangeLog
- trunk/Source/WebCore/ChangeLog
- trunk/Source/WebCore/rendering/PaintInfo.h
- trunk/Source/WebCore/rendering/RenderLayer.cpp
- trunk/Source/WebCore/rendering/RenderLayer.h
- trunk/Source/WebCore/testing/Internals.cpp
- trunk/Source/WebCore/testing/Internals.h
- trunk/Source/WebCore/testing/Internals.idl
Added Paths
Diff
Modified: trunk/LayoutTests/ChangeLog (224581 => 224582)
--- trunk/LayoutTests/ChangeLog 2017-11-08 16:00:18 UTC (rev 224581)
+++ trunk/LayoutTests/ChangeLog 2017-11-08 16:11:55 UTC (rev 224582)
@@ -1,3 +1,13 @@
+2017-11-08 David Hyatt <[email protected]>
+
+ [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 <[email protected]>
Update XMLHttpRequest tests.
Added: trunk/LayoutTests/fast/block/block-move-frequent-paint-expected.txt (0 => 224582)
--- trunk/LayoutTests/fast/block/block-move-frequent-paint-expected.txt (rev 0)
+++ trunk/LayoutTests/fast/block/block-move-frequent-paint-expected.txt 2017-11-08 16:11:55 UTC (rev 224582)
@@ -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
+
Added: trunk/LayoutTests/fast/block/block-move-frequent-paint.html (0 => 224582)
--- trunk/LayoutTests/fast/block/block-move-frequent-paint.html (rev 0)
+++ trunk/LayoutTests/fast/block/block-move-frequent-paint.html 2017-11-08 16:11:55 UTC (rev 224582)
@@ -0,0 +1,49 @@
+<html>
+<head>
+<script src=""
+<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=""
+
Modified: trunk/Source/WebCore/ChangeLog (224581 => 224582)
--- trunk/Source/WebCore/ChangeLog 2017-11-08 16:00:18 UTC (rev 224581)
+++ trunk/Source/WebCore/ChangeLog 2017-11-08 16:11:55 UTC (rev 224582)
@@ -1,3 +1,50 @@
+2017-11-08 David Hyatt <[email protected]>
+
+ [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 <[email protected]>
[LayoutState cleanup] Make public data members private.
Modified: trunk/Source/WebCore/rendering/PaintInfo.h (224581 => 224582)
--- trunk/Source/WebCore/rendering/PaintInfo.h 2017-11-08 16:00:18 UTC (rev 224581)
+++ trunk/Source/WebCore/rendering/PaintInfo.h 2017-11-08 16:11:55 UTC (rev 224582)
@@ -38,6 +38,7 @@
class OverlapTestRequestClient;
class RenderInline;
+class RenderLayer;
class RenderLayerModelObject;
class RenderObject;
@@ -51,7 +52,7 @@
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 @@
, overlapTestRequests(overlapTestRequests)
, paintContainer(newPaintContainer)
, requireSecurityOriginAccessForWidgets(newRequireSecurityOriginAccessForWidgets)
+ , m_enclosingSelfPaintingLayer(enclosingSelfPaintingLayer)
, m_context(&newContext)
{
}
@@ -100,6 +102,8 @@
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 @@
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;
Modified: trunk/Source/WebCore/rendering/RenderLayer.cpp (224581 => 224582)
--- trunk/Source/WebCore/rendering/RenderLayer.cpp 2017-11-08 16:00:18 UTC (rev 224581)
+++ trunk/Source/WebCore/rendering/RenderLayer.cpp 2017-11-08 16:11:55 UTC (rev 224582)
@@ -123,6 +123,7 @@
#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>
@@ -133,10 +134,65 @@
#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 @@
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 @@
// 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 @@
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 @@
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 @@
// 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 @@
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 @@
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()) {
Modified: trunk/Source/WebCore/rendering/RenderLayer.h (224581 => 224582)
--- trunk/Source/WebCore/rendering/RenderLayer.h 2017-11-08 16:00:18 UTC (rev 224581)
+++ trunk/Source/WebCore/rendering/RenderLayer.h 2017-11-08 16:11:55 UTC (rev 224582)
@@ -66,6 +66,7 @@
class HitTestRequest;
class HitTestResult;
class HitTestingTransformState;
+class PaintFrequencyInfo;
class RenderFragmentedFlow;
class RenderGeometryMap;
class RenderLayerBacking;
@@ -712,6 +713,10 @@
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 @@
IntRect m_blockSelectionGapsBounds;
std::unique_ptr<RenderLayerBacking> m_backing;
+
+ std::unique_ptr<PaintFrequencyInfo> m_paintFrequencyInfo;
};
inline void RenderLayer::clearZOrderLists()
Modified: trunk/Source/WebCore/testing/Internals.cpp (224581 => 224582)
--- trunk/Source/WebCore/testing/Internals.cpp 2017-11-08 16:00:18 UTC (rev 224581)
+++ trunk/Source/WebCore/testing/Internals.cpp 2017-11-08 16:11:55 UTC (rev 224582)
@@ -985,7 +985,18 @@
{
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
{
bool allowVisitedStyle = true;
Modified: trunk/Source/WebCore/testing/Internals.h (224581 => 224582)
--- trunk/Source/WebCore/testing/Internals.h 2017-11-08 16:00:18 UTC (rev 224581)
+++ trunk/Source/WebCore/testing/Internals.h 2017-11-08 16:11:55 UTC (rev 224582)
@@ -103,6 +103,9 @@
ExceptionOr<String> elementRenderTreeAsText(Element&);
bool hasPausedImageAnimations(Element&);
+ bool isPaintingFrequently(Element&);
+ void incrementFrequentPaintCounter(Element&);
+
String address(Node&);
bool nodeNeedsStyleRecalc(Node&);
String styleChangeType(Node&);
Modified: trunk/Source/WebCore/testing/Internals.idl (224581 => 224582)
--- trunk/Source/WebCore/testing/Internals.idl 2017-11-08 16:00:18 UTC (rev 224581)
+++ trunk/Source/WebCore/testing/Internals.idl 2017-11-08 16:11:55 UTC (rev 224582)
@@ -93,6 +93,10 @@
// 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);
_______________________________________________ webkit-changes mailing list [email protected] https://lists.webkit.org/mailman/listinfo/webkit-changes
