Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
Make compositing updates incremental
https://bugs.webkit.org/show_bug.cgi?id=90342

Reviewed by Antti Koivisto.

Source/WebCore:

Previously, updating compositing layers required two full RenderLayer tree traversals,
and all the work was done for every RenderLayer on each composting update. This could be expensive
on pages with lots of RenderLayers.

These changes make compositing updates more incremental. Compositing updates still require
two tree traversals. The first determines which RenderLayers need to be composited (of those which
weren't already made composited at style-change time), because of reasons that can only be determined
post-layout, and indirect reasons including overlap. The second traversal updates the configuration, geometry
and GraphicsLayer tree for the composited layers. Dependencies on both descendant and ancestor state make
it hard to fold these two traversals together.

In order to minimize the work done during these traversals, dirty bits are stored on RenderLayers,
and propagated to ancestor layers in paint order. There are two sets of bits: those related to the first
"compositing requirements" traversal, and those related to the second "update backing and hierarchy" traversal.
When a RenderLayer gets a dirty bit set, bits are propagated to ancestors to indicate that children need
to be visited.

Sadly entire subtrees can't be skipped during the "compositing requirements" traversal becaue we still have
to accumulate overlap rects, but RenderLayerCompositor::traverseUnchangedSubtree() is used to minimize
work in that case. Subtrees can be skipped in the "update backing and hierarchy" traveral. Entire traversals can
be skipped if no change has triggered the need for that traversal.

These changes fix a correctness issue where transform changes now trigger overlap re-evaluation, which causes
more layer geometry updates than before. This regressed the MotionMark "Focus" test, when geometry updates
triggered layer resizes as the filter blur radius changed, which then triggered repaints. This is fixed by
excluding composited filters from the composited bounds (but still taking them into account for overlap).

Care is taken to avoid triggering traversals in non-composited documents (tested by no-updates-in-non-composited-iframe.html).

Code to set the dirty bits is added in various places that change properties that compositing depends on.

These changes also subsume the patch in 176196; we now never consult properties that rely on layout from the
style change code path, and the only call stack for geometry updates is from the "update backing and hierarchy"
traversal, which is always a pre-order traversal.

Tests: compositing/geometry/stacking-context-change-layer-reparent.html
       compositing/layer-creation/change-to-overlap.html
       compositing/updates/no-updates-in-non-composited-iframe.html

* html/canvas/WebGLRenderingContextBase.cpp:
(WebCore::WebGLRenderingContextBase::markContextChanged): Need to differentiate between a canvas becoming composited
for the first time, and its pixels changing with a new 'CanvasPixelsChanged' value.
* page/FrameView.cpp:
(WebCore::FrameView::setViewportConstrainedObjectsNeedLayout):
* page/Page.cpp:
(WebCore::Page::setPageScaleFactor):
* platform/graphics/ca/GraphicsLayerCA.cpp:
(WebCore::GraphicsLayerCA::updateBackdropFilters): If we just made a layer for backdrops, we need to update sublayers.
* rendering/RenderBox.cpp:
(WebCore::RenderBox::styleWillChange):
* rendering/RenderLayer.cpp:
(WebCore::RenderLayer::RenderLayer):
(WebCore::RenderLayer::~RenderLayer):
(WebCore::RenderLayer::addChild):
(WebCore::RenderLayer::removeChild):
(WebCore::RenderLayer::shouldBeStackingContext const):
(WebCore::RenderLayer::stackingContext const):
(WebCore::RenderLayer::dirtyZOrderLists):
(WebCore::RenderLayer::dirtyNormalFlowList):
(WebCore::RenderLayer::updateNormalFlowList):
(WebCore::RenderLayer::rebuildZOrderLists):
(WebCore::RenderLayer::setAncestorsHaveCompositingDirtyFlag):
(WebCore::RenderLayer::contentChanged):
(WebCore::RenderLayer::updateLayerPositions):
(WebCore::RenderLayer::updateTransform):
(WebCore::RenderLayer::updateLayerPosition):
(WebCore::RenderLayer::enclosingCompositingLayer const):
(WebCore::RenderLayer::enclosingCompositingLayerForRepaint const):
(WebCore::RenderLayer::clippingRootForPainting const):
(WebCore::RenderLayer::scrollTo):
(WebCore::RenderLayer::updateCompositingLayersAfterScroll):
(WebCore::RenderLayer::updateScrollInfoAfterLayout):
(WebCore::RenderLayer::paintLayerContents):
(WebCore::RenderLayer::hitTest):
(WebCore::RenderLayer::hitTestLayer):
(WebCore::RenderLayer::calculateClipRects const):
(WebCore::outputPaintOrderTreeLegend):
(WebCore::outputPaintOrderTreeRecursive):
(WebCore::compositingContainer): Deleted.
* rendering/RenderLayer.h:
(WebCore::RenderLayer::clearZOrderLists):
(WebCore::RenderLayer::paintOrderParent const):
* rendering/RenderLayerBacking.cpp:
(WebCore::RenderLayerBacking::updateCompositedBounds):
(WebCore::RenderLayerBacking::updateAfterWidgetResize):
(WebCore::RenderLayerBacking::updateAfterLayout):
(WebCore::RenderLayerBacking::updateConfigurationAfterStyleChange):
(WebCore::RenderLayerBacking::updateConfiguration):
(WebCore::RenderLayerBacking::updateGeometry):
(WebCore::RenderLayerBacking::setRequiresBackgroundLayer):
(WebCore::RenderLayerBacking::updateMaskingLayer):
(WebCore::RenderLayerBacking::paintsContent const):
(WebCore::RenderLayerBacking::contentChanged):
(WebCore::RenderLayerBacking::setContentsNeedDisplay):
(WebCore::RenderLayerBacking::setContentsNeedDisplayInRect):
(WebCore::RenderLayerBacking::startAnimation):
(WebCore::RenderLayerBacking::animationFinished):
(WebCore::RenderLayerBacking::startTransition):
(WebCore::RenderLayerBacking::transitionFinished):
(WebCore::RenderLayerBacking::setCompositedBounds):
* rendering/RenderLayerBacking.h:
* rendering/RenderLayerCompositor.cpp:
(WebCore::RenderLayerCompositor::CompositingState::CompositingState):
(WebCore::RenderLayerCompositor::enableCompositingMode):
(WebCore::RenderLayerCompositor::cacheAcceleratedCompositingFlags):
(WebCore::RenderLayerCompositor::cacheAcceleratedCompositingFlagsAfterLayout):
(WebCore::RenderLayerCompositor::willRecalcStyle):
(WebCore::RenderLayerCompositor::didRecalcStyleWithNoPendingLayout):
(WebCore::RenderLayerCompositor::updateCompositingLayers):
(WebCore::RenderLayerCompositor::computeCompositingRequirements):
(WebCore::RenderLayerCompositor::traverseUnchangedSubtree):
(WebCore::RenderLayerCompositor::updateBackingAndHierarchy):
(WebCore::RenderLayerCompositor::appendDocumentOverlayLayers):
(WebCore::RenderLayerCompositor::layerBecameNonComposited):
(WebCore::RenderLayerCompositor::logLayerInfo):
(WebCore::clippingChanged):
(WebCore::styleAffectsLayerGeometry):
(WebCore::RenderLayerCompositor::layerStyleChanged):
(WebCore::RenderLayerCompositor::needsCompositingUpdateForStyleChangeOnNonCompositedLayer const):
(WebCore::RenderLayerCompositor::updateBacking):
(WebCore::RenderLayerCompositor::updateLayerCompositingState):
(WebCore::RenderLayerCompositor::layerWasAdded):
(WebCore::RenderLayerCompositor::layerWillBeRemoved):
(WebCore::RenderLayerCompositor::enclosingNonStackingClippingLayer const):
(WebCore::RenderLayerCompositor::computeExtent const):
(WebCore::RenderLayerCompositor::addToOverlapMap):
(WebCore::RenderLayerCompositor::addToOverlapMapRecursive):
(WebCore::RenderLayerCompositor::rootLayerConfigurationChanged):
(WebCore::RenderLayerCompositor::parentFrameContentLayers):
(WebCore::RenderLayerCompositor::updateRootLayerPosition):
(WebCore::RenderLayerCompositor::needsToBeComposited const):
(WebCore::RenderLayerCompositor::requiresCompositingLayer const):
(WebCore::RenderLayerCompositor::requiresOwnBackingStore const):
(WebCore::RenderLayerCompositor::reasonsForCompositing const):
(WebCore::RenderLayerCompositor::clippedByAncestor const):
(WebCore::RenderLayerCompositor::requiresCompositingForAnimation const):
(WebCore::RenderLayerCompositor::requiresCompositingForTransform const):
(WebCore::RenderLayerCompositor::requiresCompositingForVideo const):
(WebCore::RenderLayerCompositor::requiresCompositingForFilters const):
(WebCore::RenderLayerCompositor::requiresCompositingForWillChange const):
(WebCore::RenderLayerCompositor::requiresCompositingForPlugin const):
(WebCore::RenderLayerCompositor::requiresCompositingForFrame const):
(WebCore::RenderLayerCompositor::requiresCompositingForScrollableFrame const):
(WebCore::RenderLayerCompositor::requiresCompositingForPosition const):
(WebCore::RenderLayerCompositor::requiresCompositingForOverflowScrolling const):
(WebCore::RenderLayerCompositor::styleChangeMayAffectIndirectCompositingReasons):
(WebCore::RenderLayerCompositor::fixedLayerIntersectsViewport const):
(WebCore::RenderLayerCompositor::useCoordinatedScrollingForLayer const):
(WebCore::RenderLayerCompositor::rootOrBodyStyleChanged):
(WebCore::RenderLayerCompositor::rootBackgroundColorOrTransparencyChanged):
(WebCore::operator<<):
(WebCore::RenderLayerCompositor::setCompositingLayersNeedRebuild): Deleted.
(WebCore::checkIfDescendantClippingContextNeedsUpdate): Deleted.
(WebCore::isScrollableOverflow): Deleted.
(WebCore::styleHasTouchScrolling): Deleted.
(WebCore::styleChangeRequiresLayerRebuild): Deleted.
(WebCore::RenderLayerCompositor::rebuildCompositingLayerTree): Deleted.
(WebCore::RenderLayerCompositor::rootFixedBackgroundsChanged): Deleted.
(WebCore::RenderLayerCompositor::updateLayerTreeGeometry): Deleted.
(WebCore::RenderLayerCompositor::updateCompositingDescendantGeometry): Deleted.
* rendering/RenderLayerCompositor.h:
* rendering/RenderTreeAsText.cpp:
(WebCore::writeLayers):

Source/WebKitLegacy/mac:

Fix spelling error.

* WebView/WebView.mm:
(-[WebView _setMediaLayer:forPluginView:]):

LayoutTests:

Add some new tests for issues discovered during development.

Filter tests get new results because composited layer bounds are no longer affected
by pixel-moving filters.

* compositing/filters/sw-layer-overlaps-hw-shadow-expected.txt:
* compositing/filters/sw-nested-shadow-overlaps-hw-nested-shadow-expected.txt:
* compositing/filters/sw-shadow-overlaps-hw-layer-expected.txt:
* compositing/filters/sw-shadow-overlaps-hw-shadow-expected.txt:
* compositing/geometry/stacking-context-change-layer-reparent-expected.html: Added.
* compositing/geometry/stacking-context-change-layer-reparent.html: Added.
* compositing/layer-creation/change-to-overlap-expected.txt: Added.
* compositing/layer-creation/change-to-overlap.html: Added.
* compositing/updates/no-updates-in-non-composited-iframe-expected.txt: Added.
* compositing/updates/no-updates-in-non-composited-iframe.html: Added.
* compositing/updates/resources/non-composited.html: Added.
* compositing/video/video-clip-change-src.html: This test was timing-sensitive; the behavior differed bases on whether we
happened to do a compositing flush between the first and second video load.
* platform/mac-wk1/TestExpectations: Mark compositing/layer-creation/fixed-overlap-extent.html as flakey; it depends on the
timing of various AppKit-related things that aren't consistent.

Canonical link: https://commits.webkit.org/206304@main
git-svn-id: https://svn.webkit.org/repository/webkit/trunk@238090 268f45cc-cd09-0410-ab3c-d52691b4dbfc
  • Loading branch information
smfr committed Nov 12, 2018
1 parent 1f0efed commit 01f8445
Show file tree
Hide file tree
Showing 29 changed files with 2,052 additions and 1,137 deletions.
28 changes: 28 additions & 0 deletions LayoutTests/ChangeLog
@@ -1,3 +1,31 @@
2018-11-12 Simon Fraser <simon.fraser@apple.com>

Make compositing updates incremental
https://bugs.webkit.org/show_bug.cgi?id=90342

Reviewed by Antti Koivisto.

Add some new tests for issues discovered during development.

Filter tests get new results because composited layer bounds are no longer affected
by pixel-moving filters.

* compositing/filters/sw-layer-overlaps-hw-shadow-expected.txt:
* compositing/filters/sw-nested-shadow-overlaps-hw-nested-shadow-expected.txt:
* compositing/filters/sw-shadow-overlaps-hw-layer-expected.txt:
* compositing/filters/sw-shadow-overlaps-hw-shadow-expected.txt:
* compositing/geometry/stacking-context-change-layer-reparent-expected.html: Added.
* compositing/geometry/stacking-context-change-layer-reparent.html: Added.
* compositing/layer-creation/change-to-overlap-expected.txt: Added.
* compositing/layer-creation/change-to-overlap.html: Added.
* compositing/updates/no-updates-in-non-composited-iframe-expected.txt: Added.
* compositing/updates/no-updates-in-non-composited-iframe.html: Added.
* compositing/updates/resources/non-composited.html: Added.
* compositing/video/video-clip-change-src.html: This test was timing-sensitive; the behavior differed bases on whether we
happened to do a compositing flush between the first and second video load.
* platform/mac-wk1/TestExpectations: Mark compositing/layer-creation/fixed-overlap-extent.html as flakey; it depends on the
timing of various AppKit-related things that aren't consistent.

2018-11-12 Jer Noble <jer.noble@apple.com>

[MSE] Frame re-ordering can cause iframes to never be enqueued
Expand Down
Expand Up @@ -7,10 +7,9 @@
(contentsOpaque 1)
(children 2
(GraphicsLayer
(offsetFromRenderer width=-25 height=-25)
(position 80.00 80.00)
(anchor 0.60 0.60)
(bounds 125.00 125.00)
(position 105.00 105.00)
(bounds 100.00 100.00)
(contentsOpaque 1)
(drawsContent 1)
)
(GraphicsLayer
Expand Down
Expand Up @@ -7,14 +7,14 @@
(contentsOpaque 1)
(children 2
(GraphicsLayer
(offsetFromRenderer width=-125 height=-125)
(position 205.00 205.00)
(anchor 0.78 0.78)
(bounds 225.00 225.00)
(offsetFromRenderer width=-100 height=-100)
(position 230.00 230.00)
(anchor 0.75 0.75)
(bounds 200.00 200.00)
(drawsContent 1)
)
(GraphicsLayer
(bounds 225.00 225.00)
(bounds 200.00 200.00)
(drawsContent 1)
)
)
Expand Down
Expand Up @@ -12,7 +12,8 @@
(contentsOpaque 1)
)
(GraphicsLayer
(bounds 125.00 125.00)
(bounds 100.00 100.00)
(contentsOpaque 1)
(drawsContent 1)
)
)
Expand Down
Expand Up @@ -7,14 +7,14 @@
(contentsOpaque 1)
(children 2
(GraphicsLayer
(offsetFromRenderer width=-25 height=-25)
(position 105.00 105.00)
(anchor 0.60 0.60)
(bounds 125.00 125.00)
(position 130.00 130.00)
(bounds 100.00 100.00)
(contentsOpaque 1)
(drawsContent 1)
)
(GraphicsLayer
(bounds 125.00 125.00)
(bounds 100.00 100.00)
(contentsOpaque 1)
(drawsContent 1)
)
)
Expand Down
@@ -0,0 +1,36 @@
<!DOCTYPE html>
<style>
#container {
height: 250px;
width: 250px;
border: 1px solid black;
transform: translateZ(0);
}

#parent {
position: absolute;
left: 320px;
width: 250px;
height: 250px;
background-color: green;
z-index: auto;
}

#child {
position: absolute;
left: 25px;
top: 25px;
width: 200px;
height: 200px;
z-index: -2;
background-color: red;
transform: translateZ(0);
}
</style>
<body>
<div id="container">
<div id="parent">
<div id="child"></div>
</div>
</div>
</body>
@@ -0,0 +1,52 @@
<!DOCTYPE html>
<style>
#container {
height: 250px;
width: 250px;
border: 1px solid black;
transform: translateZ(0);
}

#parent {
position: absolute;
left: 320px;
width: 250px;
height: 250px;
background-color: green;
z-index: 0;
}

#parent.changed {
z-index: auto;
}

#child {
position: absolute;
left: 25px;
top: 25px;
width: 200px;
height: 200px;
z-index: -2;
background-color: red;
transform: translateZ(0);
}
</style>
<script>
if (window.testRunner)
testRunner.waitUntilDone();

window.addEventListener('load', function() {
setTimeout(function() {
document.getElementById('parent').classList.add('changed');
if (window.testRunner)
testRunner.notifyDone();
}, 0);
}, false);
</script>
<body>
<div id="container">
<div id="parent">
<div id="child"></div>
</div>
</div>
</body>
Expand Up @@ -7,15 +7,13 @@
(contentsOpaque 1)
(children 2
(GraphicsLayer
(offsetFromRenderer width=-25 height=-25)
(position 80.00 80.00)
(anchor 0.60 0.60)
(bounds 125.00 125.00)
(drawsContent 1)
(position 8.00 63.00)
(bounds 320.00 170.00)
(contentsOpaque 1)
)
(GraphicsLayer
(bounds 100.00 100.00)
(contentsOpaque 1)
(position 5.00 5.00)
(bounds 150.00 150.00)
)
)
)
Expand Down
55 changes: 55 additions & 0 deletions LayoutTests/compositing/layer-creation/change-to-overlap.html
@@ -0,0 +1,55 @@
<!DOCTYPE html>
<html>
<head>
<style type="text/css" media="screen">
#box {
height: 170px;
width: 320px;
float: left;
margin-top: 55px;
background-color: silver;
will-change: transform;
}

#overlay {
position: absolute;
width: 150px;
height: 50px;
top: 5px;
left: 5px;
background-color: rgba(0, 128, 0, 0.9);
}

pre {
clear: both;
}
</style>
<script type="text/javascript" charset="utf-8">
if (window.testRunner) {
testRunner.dumpAsText();
testRunner.waitUntilDone();
}

function doTest()
{
// Need to wait for compositing layers to be updated.
window.setTimeout(function() {
document.getElementById('overlay').style.height = '150px';
if (window.testRunner) {
testRunner.displayAndTrackRepaints(); // Painting has to happen to detect overlap.
document.getElementById('layers').innerHTML = window.internals.layerTreeAsText(document);
testRunner.notifyDone();
}
}, 0);
}

window.addEventListener('load', doTest, false);
</script>
</head>
<body>
<div id="box"></div>
<div id="overlay"></div>

<pre id="layers">Layer tree appears here in DRT.</pre>
</body>
</html>
@@ -0,0 +1,29 @@
Test that compositing updates do not happen in an iframe with no compoting layers.

On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".


Initial
PASS updateCount is 0
Layout
PASS updateCount is 0
Change stacking order
PASS updateCount is 0
Negative z-order child
PASS updateCount is 0
Change visibility
PASS updateCount is 0
Change content
PASS updateCount is 0
Change clip path
PASS updateCount is 0
Change transform
PASS updateCount is 0
Image change
PASS updateCount is 0
Remove layer
PASS updateCount is 0
PASS successfullyParsed is true

TEST COMPLETE

0 comments on commit 01f8445

Please sign in to comment.