Commit caaa5c77 authored by wkorman's avatar wkorman Committed by Commit bot

Skip paint chunks with effectively invisible opacity.

For SPv2 we always paint effectively invisible content,
but during layerization we scrutinize effect node and
skip chunks that would be effectively invisible. This
simplifies code in SPv2 while still avoiding the
resource and processing overhead of creating, managing
and rastering a layer for that chunk.

BUG=713403
CQ_INCLUDE_TRYBOTS=master.tryserver.chromium.linux:linux_layout_tests_slimming_paint_v2

Review-Url: https://codereview.chromium.org/2833883003
Cr-Commit-Position: refs/heads/master@{#468120}
parent 2e2708b5
...@@ -135,9 +135,14 @@ PaintResult PaintLayerPainter::Paint( ...@@ -135,9 +135,14 @@ PaintResult PaintLayerPainter::Paint(
->ShouldThrottleRendering()) ->ShouldThrottleRendering())
return kFullyPainted; return kFullyPainted;
// If this layer is totally invisible then there is nothing to paint. // If this layer is totally invisible then there is nothing to paint. In SPv2
if (PaintedOutputInvisible(painting_info)) // we simplify this optimization by painting even when effectively invisible
// but skipping the painted content during layerization in
// PaintArtifactCompositor.
if (!RuntimeEnabledFeatures::slimmingPaintV2Enabled() &&
PaintedOutputInvisible(painting_info)) {
return kFullyPainted; return kFullyPainted;
}
if (paint_layer_.PaintsWithTransparency(painting_info.GetGlobalPaintFlags())) if (paint_layer_.PaintsWithTransparency(painting_info.GetGlobalPaintFlags()))
paint_flags |= kPaintLayerHaveTransparency; paint_flags |= kPaintLayerHaveTransparency;
......
...@@ -32,23 +32,19 @@ class PaintLayerPainterTest ...@@ -32,23 +32,19 @@ class PaintLayerPainterTest
: ScopedRootLayerScrollingForTest(GetParam().root_layer_scrolling), : ScopedRootLayerScrollingForTest(GetParam().root_layer_scrolling),
PaintControllerPaintTestBase(GetParam().slimming_paint_v2) {} PaintControllerPaintTestBase(GetParam().slimming_paint_v2) {}
void ExpectPaintedOutputVisibility(const char* element_name, void ExpectPaintedOutputInvisible(const char* element_name,
bool expected_spv1) { bool expected_value) {
ExpectPaintedOutputVisibility(element_name, expected_spv1, expected_spv1); // The optimization to skip painting for effectively-invisible content is
} // limited to SPv1.
if (RuntimeEnabledFeatures::slimmingPaintV2Enabled())
return;
void ExpectPaintedOutputVisibility(const char* element_name,
bool expected_spv1,
bool expected_spv2) {
PaintLayer* target_layer = PaintLayer* target_layer =
ToLayoutBox(GetLayoutObjectByElementId(element_name))->Layer(); ToLayoutBox(GetLayoutObjectByElementId(element_name))->Layer();
PaintLayerPaintingInfo painting_info(nullptr, LayoutRect(), PaintLayerPaintingInfo painting_info(nullptr, LayoutRect(),
kGlobalPaintNormalPhase, LayoutSize()); kGlobalPaintNormalPhase, LayoutSize());
bool invisible = bool invisible =
PaintLayerPainter(*target_layer).PaintedOutputInvisible(painting_info); PaintLayerPainter(*target_layer).PaintedOutputInvisible(painting_info);
bool expected_value = RuntimeEnabledFeatures::slimmingPaintV2Enabled()
? expected_spv2
: expected_spv1;
EXPECT_EQ(expected_value, invisible) EXPECT_EQ(expected_value, invisible)
<< "Failed painted output visibility [spv2_enabled=" << "Failed painted output visibility [spv2_enabled="
<< RuntimeEnabledFeatures::slimmingPaintV2Enabled() << RuntimeEnabledFeatures::slimmingPaintV2Enabled()
...@@ -795,21 +791,21 @@ TEST_P(PaintLayerPainterTest, ...@@ -795,21 +791,21 @@ TEST_P(PaintLayerPainterTest,
TEST_P(PaintLayerPainterTest, DontPaintWithTinyOpacity) { TEST_P(PaintLayerPainterTest, DontPaintWithTinyOpacity) {
SetBodyInnerHTML( SetBodyInnerHTML(
"<div id='target' style='background: blue; opacity: 0.0001'></div>"); "<div id='target' style='background: blue; opacity: 0.0001'></div>");
ExpectPaintedOutputVisibility("target", true, false); ExpectPaintedOutputInvisible("target", true);
} }
TEST_P(PaintLayerPainterTest, DoPaintWithTinyOpacityAndWillChangeOpacity) { TEST_P(PaintLayerPainterTest, DoPaintWithTinyOpacityAndWillChangeOpacity) {
SetBodyInnerHTML( SetBodyInnerHTML(
"<div id='target' style='background: blue; opacity: 0.0001; " "<div id='target' style='background: blue; opacity: 0.0001; "
"will-change: opacity'></div>"); "will-change: opacity'></div>");
ExpectPaintedOutputVisibility("target", false); ExpectPaintedOutputInvisible("target", false);
} }
TEST_P(PaintLayerPainterTest, DoPaintWithTinyOpacityAndBackdropFilter) { TEST_P(PaintLayerPainterTest, DoPaintWithTinyOpacityAndBackdropFilter) {
SetBodyInnerHTML( SetBodyInnerHTML(
"<div id='target' style='background: blue; opacity: 0.0001;" "<div id='target' style='background: blue; opacity: 0.0001;"
" backdrop-filter: blur(2px);'></div>"); " backdrop-filter: blur(2px);'></div>");
ExpectPaintedOutputVisibility("target", false); ExpectPaintedOutputInvisible("target", false);
} }
TEST_P(PaintLayerPainterTest, TEST_P(PaintLayerPainterTest,
...@@ -817,20 +813,20 @@ TEST_P(PaintLayerPainterTest, ...@@ -817,20 +813,20 @@ TEST_P(PaintLayerPainterTest,
SetBodyInnerHTML( SetBodyInnerHTML(
"<div id='target' style='background: blue; opacity: 0.0001;" "<div id='target' style='background: blue; opacity: 0.0001;"
" backdrop-filter: blur(2px); will-change: opacity'></div>"); " backdrop-filter: blur(2px); will-change: opacity'></div>");
ExpectPaintedOutputVisibility("target", false); ExpectPaintedOutputInvisible("target", false);
} }
TEST_P(PaintLayerPainterTest, DoPaintWithCompositedTinyOpacity) { TEST_P(PaintLayerPainterTest, DoPaintWithCompositedTinyOpacity) {
SetBodyInnerHTML( SetBodyInnerHTML(
"<div id='target' style='background: blue; opacity: 0.0001;" "<div id='target' style='background: blue; opacity: 0.0001;"
" will-change: transform'></div>"); " will-change: transform'></div>");
ExpectPaintedOutputVisibility("target", false); ExpectPaintedOutputInvisible("target", false);
} }
TEST_P(PaintLayerPainterTest, DoPaintWithNonTinyOpacity) { TEST_P(PaintLayerPainterTest, DoPaintWithNonTinyOpacity) {
SetBodyInnerHTML( SetBodyInnerHTML(
"<div id='target' style='background: blue; opacity: 0.1'></div>"); "<div id='target' style='background: blue; opacity: 0.1'></div>");
ExpectPaintedOutputVisibility("target", false); ExpectPaintedOutputInvisible("target", false);
} }
TEST_P(PaintLayerPainterTest, DoPaintWithEffectAnimationZeroOpacity) { TEST_P(PaintLayerPainterTest, DoPaintWithEffectAnimationZeroOpacity) {
...@@ -848,10 +844,10 @@ TEST_P(PaintLayerPainterTest, DoPaintWithEffectAnimationZeroOpacity) { ...@@ -848,10 +844,10 @@ TEST_P(PaintLayerPainterTest, DoPaintWithEffectAnimationZeroOpacity) {
"} " "} "
"</style> " "</style> "
"<div id='target'></div>"); "<div id='target'></div>");
ExpectPaintedOutputVisibility("target", false); ExpectPaintedOutputInvisible("target", false);
} }
TEST_P(PaintLayerPainterTest, DontPaintWithTransformAnimationZeroOpacity) { TEST_P(PaintLayerPainterTest, DoPaintWithTransformAnimationZeroOpacity) {
SetBodyInnerHTML( SetBodyInnerHTML(
"<style> " "<style> "
"div#target { " "div#target { "
...@@ -865,7 +861,7 @@ TEST_P(PaintLayerPainterTest, DontPaintWithTransformAnimationZeroOpacity) { ...@@ -865,7 +861,7 @@ TEST_P(PaintLayerPainterTest, DontPaintWithTransformAnimationZeroOpacity) {
"} " "} "
"</style> " "</style> "
"<div id='target'>x</div></div>"); "<div id='target'>x</div></div>");
ExpectPaintedOutputVisibility("target", false, true); ExpectPaintedOutputInvisible("target", false);
} }
TEST_P(PaintLayerPainterTest, TEST_P(PaintLayerPainterTest,
...@@ -884,7 +880,7 @@ TEST_P(PaintLayerPainterTest, ...@@ -884,7 +880,7 @@ TEST_P(PaintLayerPainterTest,
"} " "} "
"</style> " "</style> "
"<div id='target'>x</div></div>"); "<div id='target'>x</div></div>");
ExpectPaintedOutputVisibility("target", false); ExpectPaintedOutputInvisible("target", false);
} }
TEST_P(PaintLayerPainterTest, DoPaintWithWillChangeOpacity) { TEST_P(PaintLayerPainterTest, DoPaintWithWillChangeOpacity) {
...@@ -897,7 +893,7 @@ TEST_P(PaintLayerPainterTest, DoPaintWithWillChangeOpacity) { ...@@ -897,7 +893,7 @@ TEST_P(PaintLayerPainterTest, DoPaintWithWillChangeOpacity) {
"}" "}"
"</style> " "</style> "
"<div id='target'></div>"); "<div id='target'></div>");
ExpectPaintedOutputVisibility("target", false); ExpectPaintedOutputInvisible("target", false);
} }
TEST_P(PaintLayerPainterTest, DoPaintWithZeroOpacityAndWillChangeOpacity) { TEST_P(PaintLayerPainterTest, DoPaintWithZeroOpacityAndWillChangeOpacity) {
...@@ -911,7 +907,7 @@ TEST_P(PaintLayerPainterTest, DoPaintWithZeroOpacityAndWillChangeOpacity) { ...@@ -911,7 +907,7 @@ TEST_P(PaintLayerPainterTest, DoPaintWithZeroOpacityAndWillChangeOpacity) {
"}" "}"
"</style> " "</style> "
"<div id='target'></div>"); "<div id='target'></div>");
ExpectPaintedOutputVisibility("target", false); ExpectPaintedOutputInvisible("target", false);
} }
TEST_P(PaintLayerPainterTest, TEST_P(PaintLayerPainterTest,
...@@ -926,7 +922,7 @@ TEST_P(PaintLayerPainterTest, ...@@ -926,7 +922,7 @@ TEST_P(PaintLayerPainterTest,
"}" "}"
"</style> " "</style> "
"<div id='target'></div>"); "<div id='target'></div>");
ExpectPaintedOutputVisibility("target", false); ExpectPaintedOutputInvisible("target", false);
} }
} // namespace blink } // namespace blink
...@@ -474,11 +474,49 @@ bool PaintArtifactCompositor::CanDecompositeEffect( ...@@ -474,11 +474,49 @@ bool PaintArtifactCompositor::CanDecompositeEffect(
return true; return true;
} }
static bool EffectGroupContainsChunk(
const EffectPaintPropertyNode& group_effect,
const PaintChunk& chunk) {
const EffectPaintPropertyNode* effect =
chunk.properties.property_tree_state.Effect();
return effect == &group_effect ||
StrictChildOfAlongPath(&group_effect, effect);
}
static bool SkipGroupIfEffectivelyInvisible(
const PaintArtifact& paint_artifact,
const EffectPaintPropertyNode& current_group,
Vector<PaintChunk>::const_iterator& chunk_it) {
// The lower bound of visibility is considered to be 0.0004f < 1/2048. With
// 10-bit color channels (only available on the newest Macs as of 2016;
// otherwise it's 8-bit), we see that an alpha of 1/2048 or less leads to a
// color output of less than 0.5 in all channels, hence not visible.
static const float kMinimumVisibleOpacity = 0.0004f;
if (current_group.Opacity() >= kMinimumVisibleOpacity ||
current_group.HasDirectCompositingReasons()) {
return false;
}
// Fast-forward to just past the end of the chunk sequence within this
// effect group.
DCHECK(EffectGroupContainsChunk(current_group, *chunk_it));
while (++chunk_it != paint_artifact.PaintChunks().end()) {
if (!EffectGroupContainsChunk(current_group, *chunk_it))
break;
}
return true;
}
void PaintArtifactCompositor::LayerizeGroup( void PaintArtifactCompositor::LayerizeGroup(
const PaintArtifact& paint_artifact, const PaintArtifact& paint_artifact,
Vector<PendingLayer>& pending_layers, Vector<PendingLayer>& pending_layers,
const EffectPaintPropertyNode& current_group, const EffectPaintPropertyNode& current_group,
Vector<PaintChunk>::const_iterator& chunk_it) { Vector<PaintChunk>::const_iterator& chunk_it) {
// Skip paint chunks that are effectively invisible due to opacity and don't
// have a direct compositing reason.
if (SkipGroupIfEffectivelyInvisible(paint_artifact, current_group, chunk_it))
return;
size_t first_layer_in_current_group = pending_layers.size(); size_t first_layer_in_current_group = pending_layers.size();
// The worst case time complexity of the algorithm is O(pqd), where // The worst case time complexity of the algorithm is O(pqd), where
// p = the number of paint chunks. // p = the number of paint chunks.
......
...@@ -143,6 +143,30 @@ class PaintArtifactCompositorTestWithPropertyTrees ...@@ -143,6 +143,30 @@ class PaintArtifactCompositorTestWithPropertyTrees
.get(); .get();
} }
void AddSimpleRectChunk(TestPaintArtifact& artifact) {
artifact
.Chunk(TransformPaintPropertyNode::Root(),
ClipPaintPropertyNode::Root(), EffectPaintPropertyNode::Root())
.RectDrawing(FloatRect(100, 100, 200, 100), Color::kBlack);
}
void CreateSimpleArtifactWithOpacity(TestPaintArtifact& artifact,
float opacity,
bool include_preceding_chunk,
bool include_subsequent_chunk) {
if (include_preceding_chunk)
AddSimpleRectChunk(artifact);
RefPtr<EffectPaintPropertyNode> effect =
CreateOpacityOnlyEffect(EffectPaintPropertyNode::Root(), opacity);
artifact
.Chunk(TransformPaintPropertyNode::Root(),
ClipPaintPropertyNode::Root(), effect)
.RectDrawing(FloatRect(0, 0, 100, 100), Color::kBlack);
if (include_subsequent_chunk)
AddSimpleRectChunk(artifact);
Update(artifact.Build());
}
private: private:
std::unique_ptr<PaintArtifactCompositor> paint_artifact_compositor_; std::unique_ptr<PaintArtifactCompositor> paint_artifact_compositor_;
scoped_refptr<base::TestSimpleTaskRunner> task_runner_; scoped_refptr<base::TestSimpleTaskRunner> task_runner_;
...@@ -2075,4 +2099,171 @@ TEST_F(PaintArtifactCompositorTestWithPropertyTrees, ...@@ -2075,4 +2099,171 @@ TEST_F(PaintArtifactCompositorTestWithPropertyTrees,
composited_element_ids.Contains(effect->GetCompositorElementId())); composited_element_ids.Contains(effect->GetCompositorElementId()));
} }
TEST_F(PaintArtifactCompositorTestWithPropertyTrees, SkipChunkWithOpacityZero) {
{
TestPaintArtifact artifact;
CreateSimpleArtifactWithOpacity(artifact, 0, false, false);
ASSERT_EQ(0u, ContentLayerCount());
}
{
TestPaintArtifact artifact;
CreateSimpleArtifactWithOpacity(artifact, 0, true, false);
ASSERT_EQ(1u, ContentLayerCount());
}
{
TestPaintArtifact artifact;
CreateSimpleArtifactWithOpacity(artifact, 0, true, true);
ASSERT_EQ(1u, ContentLayerCount());
}
{
TestPaintArtifact artifact;
CreateSimpleArtifactWithOpacity(artifact, 0, false, true);
ASSERT_EQ(1u, ContentLayerCount());
}
}
TEST_F(PaintArtifactCompositorTestWithPropertyTrees, SkipChunkWithTinyOpacity) {
{
TestPaintArtifact artifact;
CreateSimpleArtifactWithOpacity(artifact, 0.0003f, false, false);
ASSERT_EQ(0u, ContentLayerCount());
}
{
TestPaintArtifact artifact;
CreateSimpleArtifactWithOpacity(artifact, 0.0003f, true, false);
ASSERT_EQ(1u, ContentLayerCount());
}
{
TestPaintArtifact artifact;
CreateSimpleArtifactWithOpacity(artifact, 0.0003f, true, true);
ASSERT_EQ(1u, ContentLayerCount());
}
{
TestPaintArtifact artifact;
CreateSimpleArtifactWithOpacity(artifact, 0.0003f, false, true);
ASSERT_EQ(1u, ContentLayerCount());
}
}
TEST_F(PaintArtifactCompositorTestWithPropertyTrees,
DontSkipChunkWithMinimumOpacity) {
{
TestPaintArtifact artifact;
CreateSimpleArtifactWithOpacity(artifact, 0.0004f, false, false);
ASSERT_EQ(1u, ContentLayerCount());
}
{
TestPaintArtifact artifact;
CreateSimpleArtifactWithOpacity(artifact, 0.0004f, true, false);
ASSERT_EQ(1u, ContentLayerCount());
}
{
TestPaintArtifact artifact;
CreateSimpleArtifactWithOpacity(artifact, 0.0004f, true, true);
ASSERT_EQ(1u, ContentLayerCount());
}
{
TestPaintArtifact artifact;
CreateSimpleArtifactWithOpacity(artifact, 0.0004f, false, true);
ASSERT_EQ(1u, ContentLayerCount());
}
}
TEST_F(PaintArtifactCompositorTestWithPropertyTrees,
DontSkipChunkWithAboveMinimumOpacity) {
{
TestPaintArtifact artifact;
CreateSimpleArtifactWithOpacity(artifact, 0.3f, false, false);
ASSERT_EQ(1u, ContentLayerCount());
}
{
TestPaintArtifact artifact;
CreateSimpleArtifactWithOpacity(artifact, 0.3f, true, false);
ASSERT_EQ(1u, ContentLayerCount());
}
{
TestPaintArtifact artifact;
CreateSimpleArtifactWithOpacity(artifact, 0.3f, true, true);
ASSERT_EQ(1u, ContentLayerCount());
}
{
TestPaintArtifact artifact;
CreateSimpleArtifactWithOpacity(artifact, 0.3f, false, true);
ASSERT_EQ(1u, ContentLayerCount());
}
}
PassRefPtr<EffectPaintPropertyNode> CreateEffectWithOpacityAndReason(
float opacity,
CompositingReasons reason,
RefPtr<EffectPaintPropertyNode> parent = nullptr) {
return EffectPaintPropertyNode::Create(
parent ? parent : EffectPaintPropertyNode::Root(),
TransformPaintPropertyNode::Root(), ClipPaintPropertyNode::Root(),
kColorFilterNone, CompositorFilterOperations(), opacity,
SkBlendMode::kSrcOver, reason);
}
TEST_F(PaintArtifactCompositorTestWithPropertyTrees,
DontSkipChunkWithTinyOpacityAndDirectCompositingReason) {
RefPtr<EffectPaintPropertyNode> effect =
CreateEffectWithOpacityAndReason(0.0001f, kCompositingReasonCanvas);
TestPaintArtifact artifact;
artifact
.Chunk(TransformPaintPropertyNode::Root(), ClipPaintPropertyNode::Root(),
effect)
.RectDrawing(FloatRect(0, 0, 100, 100), Color::kBlack);
Update(artifact.Build());
ASSERT_EQ(1u, ContentLayerCount());
}
TEST_F(PaintArtifactCompositorTestWithPropertyTrees,
SkipChunkWithTinyOpacityAndVisibleChildEffectNode) {
RefPtr<EffectPaintPropertyNode> tinyEffect =
CreateEffectWithOpacityAndReason(0.0001f, kCompositingReasonNone);
RefPtr<EffectPaintPropertyNode> visibleEffect =
CreateEffectWithOpacityAndReason(0.5f, kCompositingReasonNone,
tinyEffect);
TestPaintArtifact artifact;
artifact
.Chunk(TransformPaintPropertyNode::Root(), ClipPaintPropertyNode::Root(),
visibleEffect)
.RectDrawing(FloatRect(0, 0, 100, 100), Color::kBlack);
Update(artifact.Build());
ASSERT_EQ(0u, ContentLayerCount());
}
TEST_F(
PaintArtifactCompositorTestWithPropertyTrees,
DontSkipChunkWithTinyOpacityAndVisibleChildEffectNodeWithCompositingParent) {
RefPtr<EffectPaintPropertyNode> tinyEffect =
CreateEffectWithOpacityAndReason(0.0001f, kCompositingReasonCanvas);
RefPtr<EffectPaintPropertyNode> visibleEffect =
CreateEffectWithOpacityAndReason(0.5f, kCompositingReasonNone,
tinyEffect);
TestPaintArtifact artifact;
artifact
.Chunk(TransformPaintPropertyNode::Root(), ClipPaintPropertyNode::Root(),
visibleEffect)
.RectDrawing(FloatRect(0, 0, 100, 100), Color::kBlack);
Update(artifact.Build());
ASSERT_EQ(1u, ContentLayerCount());
}
TEST_F(PaintArtifactCompositorTestWithPropertyTrees,
SkipChunkWithTinyOpacityAndVisibleChildEffectNodeWithCompositingChild) {
RefPtr<EffectPaintPropertyNode> tinyEffect =
CreateEffectWithOpacityAndReason(0.0001f, kCompositingReasonNone);
RefPtr<EffectPaintPropertyNode> visibleEffect =
CreateEffectWithOpacityAndReason(0.5f, kCompositingReasonCanvas,
tinyEffect);
TestPaintArtifact artifact;
artifact
.Chunk(TransformPaintPropertyNode::Root(), ClipPaintPropertyNode::Root(),
visibleEffect)
.RectDrawing(FloatRect(0, 0, 100, 100), Color::kBlack);
Update(artifact.Build());
ASSERT_EQ(0u, ContentLayerCount());
}
} // namespace blink } // namespace blink
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment