Commit c0b74347 authored by Philip Rogers's avatar Philip Rogers Committed by Commit Bot

[BlinkGenPropertyTrees] Inform cc of damage caused by property trees

The compositor has a concept of "damage" that is similar to the raster
invalidation calculated in blink and represents the part of the screen
that needs to be redrawn. Damage in the compositor is calculated using
|Layer::subtree_property_changed()|. This patch uses SPV2's property
node "changed" bits to mark cc::Layers as changed if any ancestor
property tree node changes.

Bug: 901051
Change-Id: I8899ce2ef8dd2b37855f38c71dea443d28520788
Reviewed-on: https://chromium-review.googlesource.com/c/1324093
Commit-Queue: Philip Rogers <pdr@chromium.org>
Reviewed-by: default avatarXianzhu Wang <wangxianzhu@chromium.org>
Cr-Commit-Position: refs/heads/master@{#606472}
parent 6c8ed782
...@@ -12999,6 +12999,198 @@ TEST_P(SlimmingPaintWebFrameSimTest, NoopChangeDoesNotCauseFullTreeSync) { ...@@ -12999,6 +12999,198 @@ TEST_P(SlimmingPaintWebFrameSimTest, NoopChangeDoesNotCauseFullTreeSync) {
EXPECT_FALSE(layer_tree_host->needs_full_tree_sync()); EXPECT_FALSE(layer_tree_host->needs_full_tree_sync());
} }
// When a property tree change occurs that affects layer position, all layers
// associated with the changed property tree node, and all layers associated
// with a descendant of the changed property tree node need to have
// |subtree_property_changed| set for damage tracking. In non-layer-list mode,
// this occurs in BuildPropertyTreesInternal (see:
// SetLayerPropertyChangedForChild).
TEST_P(SlimmingPaintWebFrameSimTest, LayerSubtreeTransformPropertyChanged) {
// TODO(crbug.com/765003): SPV2 may make different layerization decisions and
// we cannot guarantee that both divs will be composited in this test. When
// SPV2 gets closer to launch, this test should be updated to pass.
if (RuntimeEnabledFeatures::SlimmingPaintV2Enabled())
return;
InitializeWithHTML(R"HTML(
<!DOCTYPE html>
<style>
html { overflow: hidden; }
#outer {
width: 100px;
height: 100px;
will-change: transform;
transform: translate(10px, 10px);
}
#inner {
width: 100px;
height: 100px;
will-change: transform;
background: lightblue;
}
</style>
<div id='outer'>
<div id='inner'></div>
</div>
)HTML");
Compositor().BeginFrame();
auto* outer_element = GetElementById("outer");
auto* outer_element_layer = ContentLayerAt(ContentLayerCount() - 2);
DCHECK_EQ(outer_element_layer->element_id(),
CompositorElementIdFromUniqueObjectId(
outer_element->GetLayoutObject()->UniqueId(),
CompositorElementIdNamespace::kPrimary));
auto* inner_element = GetElementById("inner");
auto* inner_element_layer = ContentLayerAt(ContentLayerCount() - 1);
DCHECK_EQ(inner_element_layer->element_id(),
CompositorElementIdFromUniqueObjectId(
inner_element->GetLayoutObject()->UniqueId(),
CompositorElementIdNamespace::kPrimary));
// Initially, no layer should have |subtree_property_changed| set.
EXPECT_FALSE(outer_element_layer->subtree_property_changed());
EXPECT_FALSE(inner_element_layer->subtree_property_changed());
// Modifying the transform style should set |subtree_property_changed| on
// both layers.
outer_element->setAttribute(html_names::kStyleAttr,
"transform: translate(20px, 20px)");
WebView().UpdateAllLifecyclePhases();
EXPECT_TRUE(outer_element_layer->subtree_property_changed());
EXPECT_TRUE(inner_element_layer->subtree_property_changed());
// After a frame the |subtree_property_changed| value should be reset.
Compositor().BeginFrame();
EXPECT_FALSE(outer_element_layer->subtree_property_changed());
EXPECT_FALSE(inner_element_layer->subtree_property_changed());
}
// This test is similar to |LayerSubtreeTransformPropertyChanged| but for
// effect property node changes.
TEST_P(SlimmingPaintWebFrameSimTest, LayerSubtreeEffectPropertyChanged) {
// TODO(crbug.com/765003): SPV2 may make different layerization decisions and
// we cannot guarantee that both divs will be composited in this test. When
// SPV2 gets closer to launch, this test should be updated to pass.
if (RuntimeEnabledFeatures::SlimmingPaintV2Enabled())
return;
InitializeWithHTML(R"HTML(
<!DOCTYPE html>
<style>
html { overflow: hidden; }
#outer {
width: 100px;
height: 100px;
will-change: transform;
filter: blur(10px);
}
#inner {
width: 100px;
height: 100px;
will-change: transform;
background: lightblue;
}
</style>
<div id='outer'>
<div id='inner'></div>
</div>
)HTML");
Compositor().BeginFrame();
auto* outer_element = GetElementById("outer");
auto* outer_element_layer = ContentLayerAt(ContentLayerCount() - 2);
DCHECK_EQ(outer_element_layer->element_id(),
CompositorElementIdFromUniqueObjectId(
outer_element->GetLayoutObject()->UniqueId(),
CompositorElementIdNamespace::kEffectFilter));
auto* inner_element = GetElementById("inner");
auto* inner_element_layer = ContentLayerAt(ContentLayerCount() - 1);
DCHECK_EQ(inner_element_layer->element_id(),
CompositorElementIdFromUniqueObjectId(
inner_element->GetLayoutObject()->UniqueId(),
CompositorElementIdNamespace::kPrimary));
// Initially, no layer should have |subtree_property_changed| set.
EXPECT_FALSE(outer_element_layer->subtree_property_changed());
EXPECT_FALSE(inner_element_layer->subtree_property_changed());
// Modifying the filter style should set |subtree_property_changed| on
// both layers.
outer_element->setAttribute(html_names::kStyleAttr, "filter: blur(20px)");
WebView().UpdateAllLifecyclePhases();
EXPECT_TRUE(outer_element_layer->subtree_property_changed());
EXPECT_TRUE(inner_element_layer->subtree_property_changed());
// After a frame the |subtree_property_changed| value should be reset.
Compositor().BeginFrame();
EXPECT_FALSE(outer_element_layer->subtree_property_changed());
EXPECT_FALSE(inner_element_layer->subtree_property_changed());
}
// This test is similar to |LayerSubtreeTransformPropertyChanged| but for
// clip property node changes.
TEST_P(SlimmingPaintWebFrameSimTest, LayerSubtreeClipPropertyChanged) {
// TODO(crbug.com/765003): SPV2 may make different layerization decisions and
// we cannot guarantee that both divs will be composited in this test. When
// SPV2 gets closer to launch, this test should be updated to pass.
if (RuntimeEnabledFeatures::SlimmingPaintV2Enabled())
return;
InitializeWithHTML(R"HTML(
<!DOCTYPE html>
<style>
html { overflow: hidden; }
#outer {
width: 100px;
height: 100px;
will-change: transform;
position: absolute;
clip: rect(10px, 80px, 70px, 40px);
}
#inner {
width: 100px;
height: 100px;
will-change: transform;
background: lightblue;
}
</style>
<div id='outer'>
<div id='inner'></div>
</div>
)HTML");
Compositor().BeginFrame();
auto* outer_element = GetElementById("outer");
auto* outer_element_layer = ContentLayerAt(ContentLayerCount() - 2);
auto* inner_element = GetElementById("inner");
auto* inner_element_layer = ContentLayerAt(ContentLayerCount() - 1);
DCHECK_EQ(inner_element_layer->element_id(),
CompositorElementIdFromUniqueObjectId(
inner_element->GetLayoutObject()->UniqueId(),
CompositorElementIdNamespace::kPrimary));
// Initially, no layer should have |subtree_property_changed| set.
EXPECT_FALSE(outer_element_layer->subtree_property_changed());
EXPECT_FALSE(inner_element_layer->subtree_property_changed());
// Modifying the clip style should set |subtree_property_changed| on
// both layers.
outer_element->setAttribute(html_names::kStyleAttr,
"clip: rect(1px, 8px, 7px, 4px);");
WebView().UpdateAllLifecyclePhases();
EXPECT_TRUE(outer_element_layer->subtree_property_changed());
EXPECT_TRUE(inner_element_layer->subtree_property_changed());
// After a frame the |subtree_property_changed| value should be reset.
Compositor().BeginFrame();
EXPECT_FALSE(outer_element_layer->subtree_property_changed());
EXPECT_FALSE(inner_element_layer->subtree_property_changed());
}
static void TestFramePrinting(WebLocalFrameImpl* frame) { static void TestFramePrinting(WebLocalFrameImpl* frame) {
WebPrintParams print_params; WebPrintParams print_params;
WebSize page_size(500, 500); WebSize page_size(500, 500);
......
...@@ -2588,9 +2588,15 @@ void LocalFrameView::RunPaintLifecyclePhase() { ...@@ -2588,9 +2588,15 @@ void LocalFrameView::RunPaintLifecyclePhase() {
// Notify the controller that the artifact has been pushed and some // Notify the controller that the artifact has been pushed and some
// lifecycle state can be freed (such as raster invalidations). // lifecycle state can be freed (such as raster invalidations).
paint_controller_->FinishCycle(); paint_controller_->FinishCycle();
// PaintController for BlinkGenPropertyTrees is transient. // PaintController for BlinkGenPropertyTrees is transient.
if (RuntimeEnabledFeatures::BlinkGenPropertyTreesEnabled() && if (RuntimeEnabledFeatures::BlinkGenPropertyTreesEnabled() &&
!RuntimeEnabledFeatures::SlimmingPaintV2Enabled()) { !RuntimeEnabledFeatures::SlimmingPaintV2Enabled()) {
// Property tree changed state is typically cleared through
// |PaintController::FinishCycle| but that will be a no-op because
// the paint controller is transient, so force the changed state to be
// updated here.
paint_controller_->ClearPropertyTreeChangedState();
paint_controller_ = nullptr; paint_controller_ = nullptr;
} }
} }
......
...@@ -264,6 +264,16 @@ PaintArtifactCompositor::CompositedLayerForPendingLayer( ...@@ -264,6 +264,16 @@ PaintArtifactCompositor::CompositedLayerForPendingLayer(
return cc_layer; return cc_layer;
} }
bool PaintArtifactCompositor::PropertyTreeStateChanged(
const PropertyTreeState& state) const {
const PropertyTreeState root = PropertyTreeState::Root();
bool changed = false;
changed = changed || state.Transform()->Changed(*root.Transform());
changed = changed || state.Clip()->Changed(root, state.Transform());
changed = changed || state.Effect()->Changed(root, state.Transform());
return changed;
}
PaintArtifactCompositor::PendingLayer::PendingLayer( PaintArtifactCompositor::PendingLayer::PendingLayer(
const PaintChunk& first_paint_chunk, const PaintChunk& first_paint_chunk,
size_t chunk_index, size_t chunk_index,
...@@ -793,12 +803,19 @@ void PaintArtifactCompositor::Update( ...@@ -793,12 +803,19 @@ void PaintArtifactCompositor::Update(
layer->SetScrollTreeIndex(scroll_id); layer->SetScrollTreeIndex(scroll_id);
layer->SetClipTreeIndex(clip_id); layer->SetClipTreeIndex(clip_id);
layer->SetEffectTreeIndex(effect_id); layer->SetEffectTreeIndex(effect_id);
bool backface_hidden = bool backface_hidden = property_state.Transform()->IsBackfaceHidden();
pending_layer.property_tree_state.Transform()->IsBackfaceHidden();
layer->SetDoubleSided(!backface_hidden); layer->SetDoubleSided(!backface_hidden);
// TODO(wangxianzhu): cc::PropertyTreeBuilder has a more sophisticated // TODO(wangxianzhu): cc::PropertyTreeBuilder has a more sophisticated
// condition for this. Do we need to do the same here? // condition for this. Do we need to do the same here?
layer->SetShouldCheckBackfaceVisibility(backface_hidden); layer->SetShouldCheckBackfaceVisibility(backface_hidden);
// If the property tree state has changed between the layer and the root, we
// need to inform the compositor so damage can be calculated.
// Calling |PropertyTreeStateChanged| for every pending layer is
// O(|property nodes|^2) and could be optimized by caching the lookup of
// nodes known to be changed/unchanged.
if (PropertyTreeStateChanged(property_state))
layer->SetSubtreePropertyChanged();
} }
property_tree_manager.Finalize(); property_tree_manager.Finalize();
content_layer_clients_.swap(new_content_layer_clients); content_layer_clients_.swap(new_content_layer_clients);
......
...@@ -185,6 +185,8 @@ class PLATFORM_EXPORT PaintArtifactCompositor final ...@@ -185,6 +185,8 @@ class PLATFORM_EXPORT PaintArtifactCompositor final
new_content_layer_clients, new_content_layer_clients,
Vector<scoped_refptr<cc::Layer>>& new_scroll_hit_test_layers); Vector<scoped_refptr<cc::Layer>>& new_scroll_hit_test_layers);
bool PropertyTreeStateChanged(const PropertyTreeState&) const;
const TransformPaintPropertyNode& ScrollTranslationForPendingLayer( const TransformPaintPropertyNode& ScrollTranslationForPendingLayer(
const PaintArtifact&, const PaintArtifact&,
const PendingLayer&); const PendingLayer&);
......
...@@ -562,6 +562,16 @@ void PaintController::FinishCycle() { ...@@ -562,6 +562,16 @@ void PaintController::FinishCycle() {
#endif #endif
} }
void PaintController::ClearPropertyTreeChangedState() {
DCHECK(RuntimeEnabledFeatures::BlinkGenPropertyTreesEnabled());
DCHECK(usage_ == kTransient);
// Calling |ClearChangedToRoot| for every chunk is O(|property nodes|^2) and
// could be optimized by caching which nodes that have already been cleared.
for (const auto& chunk : current_paint_artifact_->PaintChunks())
chunk.properties.ClearChangedToRoot();
}
size_t PaintController::ApproximateUnsharedMemoryUsage() const { size_t PaintController::ApproximateUnsharedMemoryUsage() const {
size_t memory_usage = sizeof(*this); size_t memory_usage = sizeof(*this);
......
...@@ -173,6 +173,13 @@ class PLATFORM_EXPORT PaintController { ...@@ -173,6 +173,13 @@ class PLATFORM_EXPORT PaintController {
// there FinishCycle() at the same time to ensure consistent caching status. // there FinishCycle() at the same time to ensure consistent caching status.
void FinishCycle(); void FinishCycle();
// |FinishCycle| clears the property tree changed state but only does this for
// non-transient controllers. The root paint controller is transient with
// BlinkGenPropertyTrees and this function provides a hook for clearing
// the property tree changed state after paint.
// TODO(pdr): Remove this when BlinkGenPropertyTrees ships.
void ClearPropertyTreeChangedState();
// Returns the approximate memory usage, excluding memory likely to be // Returns the approximate memory usage, excluding memory likely to be
// shared with the embedder after copying to WebPaintController. // shared with the embedder after copying to WebPaintController.
// Should only be called after a full document life cycle update. // Should only be called after a full document life cycle update.
......
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