Skip to content
Permalink
Browse files
Don't allocate backing stores for non-animated compositing layers wit…
…h zero opacity

https://bugs.webkit.org/show_bug.cgi?id=241935

Reviewed by Simon Fraser.

We currently allocate (and paint) a backing store for opacity:0 layers, so that we can
initiate animations faster. Unfortunately this uses a lot of memory, so we're going to
try skipping this, and allocating/painting on demand when an animation is started.

This adds a new test with various opacity:0 layers, with and without animations. It also
tests the case where we animate 0 to 0, which could in theory skip a backing store, but
doesn't yet (and is unlikely to be a problem in the wild).

* LayoutTests/compositing/backing/zero-opacity-expected.txt: Added.
* LayoutTests/compositing/backing/zero-opacity.html: Added.
* LayoutTests/compositing/backing/zero-opacity-invalidation-expected.txt: Added.
* LayoutTests/compositing/backing/zero-opacity-invalidation.html: Added.
* LayoutTests/compositing/geometry/bounds-ignores-hidden-dynamic-negzindex-expected.txt:
* LayoutTests/compositing/geometry/fixed-position-flipped-writing-mode-expected.txt:
* LayoutTests/compositing/visibility/layer-visible-content-expected.txt:
* Source/WebCore/rendering/RenderLayerBacking.cpp:
(WebCore::RenderLayerBacking::containsPaintedContent const):
* Source/WebCore/style/Styleable.cpp:
(WebCore::Styleable::mayHaveNonZeroOpacity const):
* Source/WebCore/style/Styleable.h:

Canonical link: https://commits.webkit.org/251965@main
  • Loading branch information
mattwoodrow committed Jun 29, 2022
1 parent bf9a884 commit 6d4c5f059b8767e4b3a7696a94a90660bb411048
Showing 14 changed files with 205 additions and 8 deletions.
@@ -0,0 +1,43 @@
(GraphicsLayer
(anchor 0.00 0.00)
(bounds 800.00 600.00)
(children 1
(GraphicsLayer
(bounds 800.00 600.00)
(contentsOpaque 1)
(children 4
(GraphicsLayer
(position 8.00 13.00)
(bounds 100.00 100.00)
(opacity 0.00)
(contentsOpaque 1)
)
(GraphicsLayer
(position 8.00 113.00)
(bounds 100.00 100.00)
(opacity 0.00)
(contentsOpaque 1)
(drawsContent 1)
)
(GraphicsLayer
(position 8.00 213.00)
(bounds 100.00 100.00)
(opacity 0.00)
(contentsOpaque 1)
(drawsContent 1)
)
(GraphicsLayer
(position 8.00 313.00)
(bounds 100.00 100.00)
(opacity 0.00)
(contentsOpaque 1)
(drawsContent 1)
)
)
)
)
)
First
Second
Third
Fourth
@@ -0,0 +1,20 @@
(GraphicsLayer
(anchor 0.00 0.00)
(bounds 800.00 600.00)
(children 1
(GraphicsLayer
(bounds 800.00 600.00)
(contentsOpaque 1)
(children 1
(GraphicsLayer
(position 8.00 13.00)
(bounds 100.00 100.00)
(opacity 0.50)
(contentsOpaque 1)
(drawsContent 1)
)
)
)
)
)
First
@@ -0,0 +1,40 @@
<!DOCTYPE html>
<html>
<head>
<style>
div {
width: 100px;
height: 100px;
background-color: green;
transform: translateZ(0px);
}
#opacity {
opacity: 0;
}
.second {
</style>
<script>
if (window.testRunner) {
testRunner.dumpAsText();
testRunner.waitUntilDone();
}

window.onload = function() {
if (!window.testRunner)
return;

requestAnimationFrame(function() {
document.getElementById('opacity').style.opacity = 0.5;

let out = document.getElementById('out');
out.textContent = internals.layerTreeAsText(document);
testRunner.notifyDone();
});
};
</script>
</head>
<body>
<pre id="out"></pre>
<div id="opacity">First</div>
</body>
</html>
@@ -0,0 +1,67 @@
<!DOCTYPE html>
<html>
<head>
<style>

@keyframes opacity0to1 {
0% {
opacity: 0;
}
100% {
opacity: 1;
}
}

@keyframes opacity0to0 {
0% {
opacity: 0;
}
100% {
opacity: 1;
}
}

div {
width: 100px;
height: 100px;
background-color: green;
transform: translateZ(0px);
}
.first {
opacity: 0;
}
.second {
opacity: 0;
will-change: opacity;
}
.third {
animation: opacity0to1 1000s linear infinite;
}
.fourth {
animation: opacity0to0 1000s linear infinite;
}
</style>
<script>
if (window.testRunner) {
testRunner.dumpAsText();
testRunner.waitUntilDone();
}

window.onload = function() {
if (!window.testRunner)
return;

let out = document.getElementById('out');
out.textContent = internals.layerTreeAsText(document);
testRunner.notifyDone();
};
</script>
</head>
<body>
<pre id="out"></pre>
<div class="first">First</div>
<div class="second">Second</div>
<div class="third">Third</div>
<div class="fourth">Fourth</div>
</body>
</html>
@@ -46,7 +46,6 @@
(position 8.00 8.00)
(bounds 784.00 15.00)
(opacity 0.00)
(drawsContent 1)
)
(GraphicsLayer
(bounds 100.00 100.00)
@@ -17,8 +17,6 @@
(position 0.00 13.00)
(bounds 5000.00 15.00)
(opacity 0.00)
(usingTiledLayer 1)
(drawsContent 1)
)
)
)
@@ -22,7 +22,6 @@
(GraphicsLayer
(bounds 800.00 16.00)
(opacity 0.00)
(drawsContent 1)
)
)
)
@@ -23,7 +23,6 @@
(GraphicsLayer
(bounds 800.00 16.00)
(opacity 0.00)
(drawsContent 1)
)
)
)
@@ -46,7 +46,6 @@
(position 8.00 8.00)
(bounds 784.00 14.00)
(opacity 0.00)
(drawsContent 1)
)
(GraphicsLayer
(bounds 100.00 100.00)
@@ -17,8 +17,6 @@
(position 0.00 13.00)
(bounds 5000.00 14.00)
(opacity 0.00)
(usingTiledLayer 1)
(drawsContent 1)
)
)
)
@@ -2873,6 +2873,11 @@ bool RenderLayerBacking::containsPaintedContent(PaintedContentsInfo& contentsInf
if (contentsInfo.isDirectlyCompositedImage())
return false;

if (auto styleable = Styleable::fromRenderer(renderer())) {
if (!styleable->mayHaveNonZeroOpacity())
return false;
}

// FIXME: we could optimize cases where the image, video or canvas is known to fill the border box entirely,
// and set background color on the layer in that case, instead of allocating backing store and painting.
#if ENABLE(VIDEO)
@@ -1756,6 +1756,10 @@ void RenderLayerCompositor::layerStyleChanged(StyleDifference diff, RenderLayer&
// For RenderWidgets this is necessary to get iframe layers hooked up in response to scheduleInvalidateStyleAndLayerComposition().
layer.setNeedsCompositingConfigurationUpdate();
}
// If we're changing to/from 0 opacity, then we need to reconfigure the layer since we try to
// skip backing store allocation for opacity:0.
if (oldStyle && oldStyle->opacity() != newStyle.opacity() && (!oldStyle->opacity() || !newStyle.opacity()))
layer.setNeedsCompositingConfigurationUpdate();
}
if (oldStyle && recompositeChangeRequiresGeometryUpdate(*oldStyle, newStyle)) {
// FIXME: transform changes really need to trigger layout. See RenderElement::adjustStyleDifference().
@@ -150,6 +150,30 @@ bool Styleable::computeAnimationExtent(LayoutRect& bounds) const
return true;
}

bool Styleable::mayHaveNonZeroOpacity() const
{
auto* renderer = this->renderer();
if (!renderer)
return false;

if (renderer->style().opacity() != 0.0f)
return true;

if (renderer->style().willChange() && renderer->style().willChange()->containsProperty(CSSPropertyOpacity))
return true;

auto* effectStack = keyframeEffectStack();
if (!effectStack || !effectStack->hasEffects())
return false;

for (const auto& effect : effectStack->sortedEffects()) {
if (effect->animatesProperty(CSSPropertyOpacity))
return true;
}

return false;
}

bool Styleable::isRunningAcceleratedTransformAnimation() const
{
auto* effectStack = keyframeEffectStack();
@@ -77,6 +77,8 @@ struct Styleable {
// we were unable to cheaply compute its effect on the extent.
bool computeAnimationExtent(LayoutRect&) const;

bool mayHaveNonZeroOpacity() const;

bool isRunningAcceleratedTransformAnimation() const;

bool runningAnimationsAreAllAccelerated() const;

0 comments on commit 6d4c5f0

Please sign in to comment.