Commit 491d1097 authored by Xianzhu Wang's avatar Xianzhu Wang Committed by Commit Bot

[BGPT] Generate effect node for rotated clip and force render surface

Cc requires that a rectangular clip is 2d-axis-aligned with the render
surface to correctly apply the clip. When we find that a rectangular
clip is not 2d-axis-aligned with the render surface, we should create
an effect node and let it create a render surface.

Bug: 890919
Cq-Include-Trybots: luci.chromium.try:linux-blink-gen-property-trees;luci.chromium.try:linux_layout_tests_slimming_paint_v2;master.tryserver.blink:linux_trusty_blink_rel
Change-Id: I6f7b462d5506949d1571c60231e066d27b9a238d
Reviewed-on: https://chromium-review.googlesource.com/1244104Reviewed-by: default avatarPhilip Rogers <pdr@chromium.org>
Commit-Queue: Xianzhu Wang <wangxianzhu@chromium.org>
Cr-Commit-Position: refs/heads/master@{#596304}
parent c99cbb2f
......@@ -154,6 +154,7 @@ Bug(none) css3/blending/background-blend-mode-overlapping-accelerated-elements.h
# Benign subpixel differences.
Bug(none) transforms/3d/point-mapping/3d-point-mapping-deep.html [ Failure ]
Bug(none) transforms/3d/point-mapping/3d-point-mapping-preserve-3d.html [ Failure ]
Bug(none) compositing/direct-image-compositing.html [ Failure ]
Bug(none) compositing/masks/direct-image-mask.html [ Failure ]
Bug(none) compositing/geometry/layer-due-to-layer-children.html [ Failure ]
Bug(none) compositing/perpendicular-layer-sorting.html [ Failure ]
......
{
"layers": [
{
"name": "LayoutView #document",
"bounds": [800, 600],
"drawsContent": false,
"backgroundColor": "#FFFFFF"
},
{
"name": "Scrolling Layer",
"bounds": [800, 600],
"drawsContent": false
},
{
"name": "Scrolling Contents Layer",
"bounds": [800, 600],
"contentsOpaque": true,
"backgroundColor": "#FFFFFF"
},
{
"name": "LayoutBlockFlow DIV",
"bounds": [240, 240],
"transform": 2
},
{
"name": "Child Containment Layer",
"position": [20, 20],
"bounds": [200, 200],
"drawsContent": false,
"transform": 2
},
{
"name": "LayoutBlockFlow DIV",
"position": [20, 20],
"bounds": [400, 400],
"contentsOpaque": true,
"backgroundColor": "#0000FF",
"transform": 2
}
],
"transforms": [
{
"id": 1,
"transform": [
[1, 0, 0, 0],
[0, 1, 0, 0],
[0, 0, 1, 0],
[108, 100, 0, 1]
]
},
{
"id": 2,
"parent": 1,
"transform": [
[0.707106781186548, 0.707106781186548, 0, 0],
[-0.707106781186548, 0.707106781186548, 0, 0],
[0, 0, 1, 0],
[0, 0, 0, 1]
],
"origin": [120, 120]
}
]
}
<!DOCTYPE html>
<div style="margin: 100px; transform: rotate(45deg); overflow: hidden;
width: 200px; height: 200px; border: 20px solid green">
<div style="will-change: transform; width: 400px; height: 400px; background: blue"></div>
</div>
<script>
if (window.testRunner)
testRunner.setCustomTextOutput(internals.layerTreeAsText(document));
</script>
{
"layers": [
{
"name": "LayoutView #document",
"bounds": [800, 600],
"drawsContent": false,
"backgroundColor": "#FFFFFF"
},
{
"name": "Scrolling Layer",
"bounds": [800, 600],
"drawsContent": false
},
{
"name": "Scrolling Contents Layer",
"bounds": [800, 600],
"contentsOpaque": true,
"backgroundColor": "#FFFFFF"
},
{
"name": "LayoutBlockFlow DIV",
"bounds": [240, 240],
"drawsContent": false,
"transform": 2
},
{
"name": "LayoutBlockFlow DIV",
"bounds": [240, 240],
"opacity": 0.899999976158142,
"transform": 2
},
{
"name": "Ancestor Clipping Layer",
"position": [20, 20],
"bounds": [200, 200],
"drawsContent": false,
"transform": 2
},
{
"name": "LayoutBlockFlow DIV",
"position": [20, 20],
"bounds": [400, 400],
"contentsOpaque": true,
"backgroundColor": "#0000FF",
"transform": 2
},
{
"name": "LayoutBlockFlow (positioned) DIV",
"position": [20, 0],
"bounds": [400, 100],
"contentsOpaque": true,
"backgroundColor": "#00FFFF",
"transform": 2
},
{
"name": "Ancestor Clipping Layer",
"position": [20, 20],
"bounds": [200, 200],
"drawsContent": false,
"transform": 2
},
{
"name": "LayoutBlockFlow (relative positioned) DIV",
"position": [20, 20],
"bounds": [100, 400],
"contentsOpaque": true,
"backgroundColor": "#FF00FF",
"transform": 2
}
],
"transforms": [
{
"id": 1,
"transform": [
[1, 0, 0, 0],
[0, 1, 0, 0],
[0, 0, 1, 0],
[108, 100, 0, 1]
]
},
{
"id": 2,
"parent": 1,
"transform": [
[0.707106781186548, 0.707106781186548, 0, 0],
[-0.707106781186548, 0.707106781186548, 0, 0],
[0, 0, 1, 0],
[0, 0, 0, 1]
],
"origin": [120, 120]
}
]
}
<!DOCTYPE html>
<div style="margin: 100px; width: 240px; height: 240px; transform: rotate(45deg)">
<div style="opacity: 0.9">
<div style="overflow: hidden; width: 200px; height: 200px; border: 20px solid green">
<div style="will-change: transform; width: 400px; height: 400px; background: blue"></div>
<div style="position: fixed; top: 0; width: 400px; height: 100px; background: cyan"></div>
<div style="will-change: transform; z-index: 1; position: relative; top: -400px; width: 100px; height: 400px; background: magenta"></div>
</div>
</div>
</div>
<script>
if (window.testRunner)
testRunner.setCustomTextOutput(internals.layerTreeAsText(document));
</script>
{
"layers": [
{
"name": "LayoutView #document",
"bounds": [800, 600],
"drawsContent": false,
"backgroundColor": "#FFFFFF"
},
{
"name": "Scrolling Layer",
"bounds": [800, 600],
"drawsContent": false
},
{
"name": "Scrolling Contents Layer",
"bounds": [800, 600],
"contentsOpaque": true,
"backgroundColor": "#FFFFFF"
},
{
"name": "LayoutBlockFlow DIV",
"bounds": [240, 240],
"transform": 2
},
{
"name": "Ancestor Clipping Layer",
"position": [20, 20],
"bounds": [200, 200],
"drawsContent": false,
"transform": 2
},
{
"name": "LayoutBlockFlow DIV",
"position": [20, 20],
"bounds": [400, 400],
"contentsOpaque": true,
"backgroundColor": "#0000FF",
"transform": 2
}
],
"transforms": [
{
"id": 1,
"transform": [
[1, 0, 0, 0],
[0, 1, 0, 0],
[0, 0, 1, 0],
[108, 100, 0, 1]
]
},
{
"id": 2,
"parent": 1,
"transform": [
[0.707106781186548, 0.707106781186548, 0, 0],
[-0.707106781186548, 0.707106781186548, 0, 0],
[0, 0, 1, 0],
[0, 0, 0, 1]
],
"origin": [120, 120]
}
]
}
{
"layers": [
{
"name": "LayoutView #document",
"bounds": [800, 600],
"drawsContent": false,
"backgroundColor": "#FFFFFF"
},
{
"name": "Scrolling Layer",
"bounds": [800, 600],
"drawsContent": false
},
{
"name": "Scrolling Contents Layer",
"bounds": [800, 600],
"contentsOpaque": true,
"backgroundColor": "#FFFFFF"
},
{
"name": "LayoutBlockFlow DIV",
"bounds": [300, 100],
"transform": 2
},
{
"name": "Ancestor Clipping Layer",
"bounds": [100, 100],
"drawsContent": false,
"transform": 2
},
{
"name": "LayoutBlockFlow (relative positioned) DIV",
"bounds": [200, 100],
"contentsOpaque": true,
"backgroundColor": "#008000",
"transform": 2
},
{
"name": "LayoutBlockFlow (positioned) DIV",
"bounds": [200, 22],
"contentsOpaque": true,
"backgroundColor": "#FFFF00",
"transform": 2
},
{
"name": "Ancestor Clipping Layer",
"bounds": [100, 100],
"drawsContent": false,
"transform": 2
},
{
"name": "LayoutBlockFlow (relative positioned) DIV",
"bounds": [50, 200],
"contentsOpaque": true,
"backgroundColor": "#FF0000",
"transform": 2
}
],
"transforms": [
{
"id": 1,
"transform": [
[1, 0, 0, 0],
[0, 1, 0, 0],
[0, 0, 1, 0],
[108, 100, 0, 1]
]
},
{
"id": 2,
"parent": 1,
"transform": [
[0.707106781186548, 0.707106781186548, 0, 0],
[-0.707106781186548, 0.707106781186548, 0, 0],
[0, 0, 1, 0],
[0, 0, 0, 1]
],
"origin": [150, 50]
}
]
}
<!DOCTYPE html>
<div style="margin: 100px; width: 300px; transform: rotate(45deg)">
<div style="position: absolute; width: 200px; height: 22px; background: yellow; z-index: 1; will-change: transform"></div>
<div style="width: 100px; height: 100px; overflow: hidden">
<div style="position: relative; width: 200px; height: 100px; background: green; will-change: transform"></div>
<div style="position: relative; top: -100px; z-index: 2; width: 50px; height: 200px; background: red; will-change: transform"></div>
</div>
</div>
<script>
if (window.testRunner)
testRunner.setCustomTextOutput(internals.layerTreeAsText(document));
</script>
<!DOCTYPE html>
<div style="margin: 100px; width: 240px; height: 240px; transform: rotate(45deg)">
<div style="overflow: hidden; width: 200px; height: 200px; border: 20px solid green">
<div style="will-change: transform; width: 400px; height: 400px; background: blue"></div>
</div>
</div>
<script>
if (window.testRunner)
testRunner.setCustomTextOutput(internals.layerTreeAsText(document));
</script>
{
"layers": [
{
"name": "LayoutView #document",
"bounds": [800, 600],
"drawsContent": false,
"backgroundColor": "#FFFFFF"
},
{
"name": "Scrolling Layer",
"bounds": [800, 600],
"drawsContent": false
},
{
"name": "Scrolling Contents Layer",
"bounds": [800, 600],
"contentsOpaque": true,
"backgroundColor": "#FFFFFF"
},
{
"name": "LayoutBlockFlow DIV",
"bounds": [240, 240],
"transform": 2
},
{
"name": "LayoutBlockFlow DIV",
"position": [20, 20],
"bounds": [400, 400],
"contentsOpaque": true,
"backgroundColor": "#0000FF",
"transform": 2
}
],
"transforms": [
{
"id": 1,
"transform": [
[1, 0, 0, 0],
[0, 1, 0, 0],
[0, 0, 1, 0],
[108, 100, 0, 1]
]
},
{
"id": 2,
"parent": 1,
"transform": [
[0.707106781186548, 0.707106781186548, 0, 0],
[-0.707106781186548, 0.707106781186548, 0, 0],
[0, 0, 1, 0],
[0, 0, 0, 1]
],
"origin": [120, 120]
}
]
}
{
"layers": [
{
"name": "LayoutView #document",
"bounds": [800, 600],
"drawsContent": false,
"backgroundColor": "#FFFFFF"
},
{
"name": "Scrolling Layer",
"bounds": [800, 600],
"drawsContent": false
},
{
"name": "Scrolling Contents Layer",
"bounds": [800, 600],
"contentsOpaque": true,
"backgroundColor": "#FFFFFF"
},
{
"name": "LayoutBlockFlow DIV",
"bounds": [240, 240],
"drawsContent": false,
"transform": 2
},
{
"name": "LayoutBlockFlow DIV",
"bounds": [240, 240],
"opacity": 0.899999976158142,
"transform": 2
},
{
"name": "LayoutBlockFlow DIV",
"position": [20, 20],
"bounds": [400, 400],
"contentsOpaque": true,
"backgroundColor": "#0000FF",
"transform": 2
},
{
"name": "LayoutBlockFlow (positioned) DIV",
"position": [20, 0],
"bounds": [400, 100],
"contentsOpaque": true,
"backgroundColor": "#00FFFF",
"transform": 2
},
{
"name": "LayoutBlockFlow (relative positioned) DIV",
"position": [20, 20],
"bounds": [100, 400],
"contentsOpaque": true,
"backgroundColor": "#FF00FF",
"transform": 2
}
],
"transforms": [
{
"id": 1,
"transform": [
[1, 0, 0, 0],
[0, 1, 0, 0],
[0, 0, 1, 0],
[108, 100, 0, 1]
]
},
{
"id": 2,
"parent": 1,
"transform": [
[0.707106781186548, 0.707106781186548, 0, 0],
[-0.707106781186548, 0.707106781186548, 0, 0],
[0, 0, 1, 0],
[0, 0, 0, 1]
],
"origin": [120, 120]
}
]
}
{
"layers": [
{
"name": "LayoutView #document",
"bounds": [800, 600],
"drawsContent": false,
"backgroundColor": "#FFFFFF"
},
{
"name": "Scrolling Layer",
"bounds": [800, 600],
"drawsContent": false
},
{
"name": "Scrolling Contents Layer",
"bounds": [800, 600],
"contentsOpaque": true,
"backgroundColor": "#FFFFFF"
},
{
"name": "LayoutBlockFlow DIV",
"bounds": [240, 240],
"transform": 2
},
{
"name": "LayoutBlockFlow DIV",
"position": [20, 20],
"bounds": [400, 400],
"contentsOpaque": true,
"backgroundColor": "#0000FF",
"transform": 2
}
],
"transforms": [
{
"id": 1,
"transform": [
[1, 0, 0, 0],
[0, 1, 0, 0],
[0, 0, 1, 0],
[108, 100, 0, 1]
]
},
{
"id": 2,
"parent": 1,
"transform": [
[0.707106781186548, 0.707106781186548, 0, 0],
[-0.707106781186548, 0.707106781186548, 0, 0],
[0, 0, 1, 0],
[0, 0, 0, 1]
],
"origin": [120, 120]
}
]
}
{
"layers": [
{
"name": "LayoutView #document",
"bounds": [800, 600],
"drawsContent": false,
"backgroundColor": "#FFFFFF"
},
{
"name": "Scrolling Layer",
"bounds": [800, 600],
"drawsContent": false
},
{
"name": "Scrolling Contents Layer",
"bounds": [800, 600],
"contentsOpaque": true,
"backgroundColor": "#FFFFFF"
},
{
"name": "LayoutBlockFlow DIV",
"bounds": [300, 100],
"transform": 2
},
{
"name": "LayoutBlockFlow (relative positioned) DIV",
"bounds": [200, 100],
"contentsOpaque": true,
"backgroundColor": "#008000",
"transform": 2
},
{
"name": "LayoutBlockFlow (positioned) DIV",
"bounds": [200, 22],
"contentsOpaque": true,
"backgroundColor": "#FFFF00",
"transform": 2
},
{
"name": "LayoutBlockFlow (relative positioned) DIV",
"bounds": [50, 200],
"contentsOpaque": true,
"backgroundColor": "#FF0000",
"transform": 2
}
],
"transforms": [
{
"id": 1,
"transform": [
[1, 0, 0, 0],
[0, 1, 0, 0],
[0, 0, 1, 0],
[108, 100, 0, 1]
]
},
{
"id": 2,
"parent": 1,
"transform": [
[0.707106781186548, 0.707106781186548, 0, 0],
[-0.707106781186548, 0.707106781186548, 0, 0],
[0, 0, 1, 0],
[0, 0, 0, 1]
],
"origin": [150, 50]
}
]
}
{
"layers": [
{
"name": "LayoutView #document",
"bounds": [800, 600],
"contentsOpaque": true,
"backgroundColor": "#FFFFFF"
},
{
"name": "LayoutBlockFlow DIV",
"position": [20, 20],
"bounds": [400, 400],
"contentsOpaque": true,
"backgroundColor": "#0000FF",
"transform": 2
}
],
"transforms": [
{
"id": 1,
"transform": [
[1, 0, 0, 0],
[0, 1, 0, 0],
[0, 0, 1, 0],
[108, 100, 0, 1]
]
},
{
"id": 2,
"parent": 1,
"transform": [
[0.707106781186548, 0.707106781186548, 0, 0],
[-0.707106781186548, 0.707106781186548, 0, 0],
[0, 0, 1, 0],
[0, 0, 0, 1]
],
"origin": [120, 120]
}
]
}
{
"layers": [
{
"name": "LayoutView #document",
"bounds": [800, 600],
"contentsOpaque": true,
"backgroundColor": "#FFFFFF"
},
{
"name": "LayoutBlockFlow DIV",
"bounds": [240, 240],
"transform": 2
},
{
"name": "LayoutBlockFlow DIV",
"position": [20, 20],
"bounds": [400, 400],
"contentsOpaque": true,
"backgroundColor": "#0000FF",
"transform": 2
},
{
"name": "LayoutBlockFlow (positioned) DIV",
"position": [20, 0],
"bounds": [400, 100],
"contentsOpaque": true,
"backgroundColor": "#00FFFF",
"transform": 2
},
{
"name": "LayoutBlockFlow (relative positioned) DIV",
"position": [20, 20],
"bounds": [100, 400],
"contentsOpaque": true,
"backgroundColor": "#FF00FF",
"transform": 2
}
],
"transforms": [
{
"id": 1,
"transform": [
[1, 0, 0, 0],
[0, 1, 0, 0],
[0, 0, 1, 0],
[108, 100, 0, 1]
]
},
{
"id": 2,
"parent": 1,
"transform": [
[0.707106781186548, 0.707106781186548, 0, 0],
[-0.707106781186548, 0.707106781186548, 0, 0],
[0, 0, 1, 0],
[0, 0, 0, 1]
],
"origin": [120, 120]
}
]
}
{
"layers": [
{
"name": "LayoutView #document",
"bounds": [800, 600],
"contentsOpaque": true,
"backgroundColor": "#FFFFFF"
},
{
"name": "LayoutBlockFlow DIV",
"position": [20, 20],
"bounds": [400, 400],
"contentsOpaque": true,
"backgroundColor": "#0000FF",
"transform": 2
}
],
"transforms": [
{
"id": 1,
"transform": [
[1, 0, 0, 0],
[0, 1, 0, 0],
[0, 0, 1, 0],
[108, 100, 0, 1]
]
},
{
"id": 2,
"parent": 1,
"transform": [
[0.707106781186548, 0.707106781186548, 0, 0],
[-0.707106781186548, 0.707106781186548, 0, 0],
[0, 0, 1, 0],
[0, 0, 0, 1]
],
"origin": [120, 120]
}
]
}
{
"layers": [
{
"name": "LayoutView #document",
"bounds": [800, 600],
"contentsOpaque": true,
"backgroundColor": "#FFFFFF"
},
{
"name": "LayoutBlockFlow (relative positioned) DIV",
"bounds": [200, 100],
"contentsOpaque": true,
"backgroundColor": "#008000",
"transform": 2
},
{
"name": "LayoutBlockFlow (positioned) DIV",
"bounds": [200, 22],
"contentsOpaque": true,
"backgroundColor": "#FFFF00",
"transform": 2
},
{
"name": "LayoutBlockFlow (relative positioned) DIV",
"bounds": [50, 200],
"contentsOpaque": true,
"backgroundColor": "#FF0000",
"transform": 2
}
],
"transforms": [
{
"id": 1,
"transform": [
[1, 0, 0, 0],
[0, 1, 0, 0],
[0, 0, 1, 0],
[108, 100, 0, 1]
]
},
{
"id": 2,
"parent": 1,
"transform": [
[0.707106781186548, 0.707106781186548, 0, 0],
[-0.707106781186548, 0.707106781186548, 0, 0],
[0, 0, 1, 0],
[0, 0, 0, 1]
],
"origin": [150, 50]
}
]
}
......@@ -136,10 +136,9 @@ void PropertyTreeManager::SetupRootEffectNode() {
effect_node.has_render_surface = true;
root_layer_->SetEffectTreeIndex(effect_node.id);
current_effect_id_ = effect_node.id;
current_effect_type_ = CcEffectType::kEffect;
current_effect_ = &EffectPaintPropertyNode::Root();
current_clip_ = current_effect_->OutputClip();
SetCurrentEffectState(effect_node, CcEffectType::kEffect,
&EffectPaintPropertyNode::Root(),
&ClipPaintPropertyNode::Root());
}
void PropertyTreeManager::SetupRootScrollNode() {
......@@ -155,6 +154,26 @@ void PropertyTreeManager::SetupRootScrollNode() {
root_layer_->SetScrollTreeIndex(scroll_node.id);
}
void PropertyTreeManager::SetCurrentEffectState(
const cc::EffectNode& cc_effect_node,
CcEffectType effect_type,
const EffectPaintPropertyNode* effect,
const ClipPaintPropertyNode* clip) {
current_.effect_id = cc_effect_node.id;
current_.effect_type = effect_type;
current_.effect = effect;
current_.clip = clip;
if (cc_effect_node.has_render_surface)
current_.render_surface_transform = effect->LocalTransformSpace();
}
// TODO(crbug.com/504464): Remove this when move render surface decision logic
// into cc compositor thread.
void PropertyTreeManager::SetCurrentEffectHasRenderSurface() {
GetEffectTree().Node(current_.effect_id)->has_render_surface = true;
current_.render_surface_transform = current_.effect->LocalTransformSpace();
}
int PropertyTreeManager::EnsureCompositorTransformNode(
const TransformPaintPropertyNode* transform_node) {
DCHECK(transform_node);
......@@ -255,14 +274,15 @@ int PropertyTreeManager::EnsureCompositorTransformNode(
// If the parent transform node flattens transform (as |transform_node|
// flattens inherited transform) while it participates in the 3d sorting
// context of an ancestor, cc needs a render surface for correct flattening.
if (transform_node->FlattensInheritedTransform() &&
// TODO(crbug.com/504464): Move the logic into cc compositor thread.
auto* current_cc_effect = GetEffectTree().Node(current_.effect_id);
if (current_cc_effect && !current_cc_effect->has_render_surface &&
current_cc_effect->transform_id == parent_id &&
transform_node->FlattensInheritedTransform() &&
transform_node->Parent() &&
transform_node->Parent()->RenderingContextId() &&
!transform_node->Parent()->FlattensInheritedTransform()) {
auto* current_cc_effect = GetEffectTree().Node(current_effect_id_);
if (current_cc_effect && current_cc_effect->transform_id == parent_id)
current_cc_effect->has_render_surface = true;
}
!transform_node->Parent()->FlattensInheritedTransform())
SetCurrentEffectHasRenderSurface();
auto result = transform_node_map_.Set(transform_node, id);
DCHECK(result.is_new_entry);
......@@ -379,12 +399,12 @@ int PropertyTreeManager::EnsureCompositorScrollNode(
}
void PropertyTreeManager::EmitClipMaskLayer() {
int clip_id = EnsureCompositorClipNode(current_clip_);
int clip_id = EnsureCompositorClipNode(current_.clip);
CompositorElementId mask_isolation_id, mask_effect_id;
cc::Layer* mask_layer = client_.CreateOrReuseSynthesizedClipLayer(
current_clip_, mask_isolation_id, mask_effect_id);
current_.clip, mask_isolation_id, mask_effect_id);
cc::EffectNode& mask_isolation = *GetEffectTree().Node(current_effect_id_);
cc::EffectNode& mask_isolation = *GetEffectTree().Node(current_.effect_id);
// Assignment of mask_isolation.stable_id was delayed until now.
// See PropertyTreeManager::SynthesizeCcEffectsForClipsIfNeeded().
DCHECK_EQ(static_cast<uint64_t>(cc::EffectNode::INVALID_STABLE_ID),
......@@ -392,13 +412,13 @@ void PropertyTreeManager::EmitClipMaskLayer() {
mask_isolation.stable_id = mask_isolation_id.GetInternalValue();
cc::EffectNode& mask_effect = *GetEffectTree().Node(
GetEffectTree().Insert(cc::EffectNode(), current_effect_id_));
GetEffectTree().Insert(cc::EffectNode(), current_.effect_id));
mask_effect.stable_id = mask_effect_id.GetInternalValue();
mask_effect.clip_id = clip_id;
mask_effect.has_render_surface = true;
mask_effect.blend_mode = SkBlendMode::kDstIn;
const auto* clip_space = current_clip_->LocalTransformSpace();
const auto* clip_space = current_.clip->LocalTransformSpace();
layer_list_builder_->Add(mask_layer);
mask_layer->set_property_tree_sequence_number(
root_layer_->property_tree_sequence_number());
......@@ -415,7 +435,7 @@ void PropertyTreeManager::EmitClipMaskLayer() {
void PropertyTreeManager::CloseCcEffect() {
DCHECK(effect_stack_.size());
const EffectStackEntry& previous_state = effect_stack_.back();
const auto& previous_state = effect_stack_.back();
// An effect with exotic blending that is masked by a synthesized clip must
// have its blending to the outermost synthesized clip. It is because
......@@ -425,18 +445,15 @@ void PropertyTreeManager::CloseCcEffect() {
// thus the clip can't be shared with sibling layers, and must be closed now.
bool clear_synthetic_effects =
!IsCurrentCcEffectSynthetic() &&
current_effect_->BlendMode() != SkBlendMode::kSrcOver;
current_.effect->BlendMode() != SkBlendMode::kSrcOver;
// We are about to close an effect that was synthesized for isolating
// a clip mask. Now emit the actual clip mask that will be composited on
// top of masked contents with SkBlendMode::kDstIn.
if (IsCurrentCcEffectSynthetic())
if (IsCurrentCcEffectSyntheticForNonTrivialClip())
EmitClipMaskLayer();
current_effect_id_ = previous_state.effect_id;
current_effect_type_ = previous_state.effect_type;
current_effect_ = previous_state.effect;
current_clip_ = previous_state.clip;
current_ = previous_state;
effect_stack_.pop_back();
if (clear_synthetic_effects) {
......@@ -445,6 +462,12 @@ void PropertyTreeManager::CloseCcEffect() {
}
}
static bool TransformsAre2dAxisAligned(const TransformPaintPropertyNode* a,
const TransformPaintPropertyNode* b) {
return a == b || GeometryMapper::SourceToDestinationProjection(a, b)
.Preserves2dAxisAlignment();
}
int PropertyTreeManager::SwitchToEffectNodeWithSynthesizedClip(
const EffectPaintPropertyNode& next_effect,
const ClipPaintPropertyNode& next_clip) {
......@@ -460,7 +483,7 @@ int PropertyTreeManager::SwitchToEffectNodeWithSynthesizedClip(
// For example with the following clip and effect tree and pending layers:
// E0 <-- E1
// C0 <-- C1(rounded)
// [P0(E1,C0), P1(E1,C1), P2(E0, C1)]
// [P0(E1,C0), P1(E1,C1), P2(E0,C1)]
// In effect stack diagram:
// P0(C0) P1(C1)
// [ E1 ] P2(C1)
......@@ -470,11 +493,11 @@ int PropertyTreeManager::SwitchToEffectNodeWithSynthesizedClip(
// E0 <+- E1 <-- E_C1_1 <-- E_C1_1M
// +- E_C1_2 <-- E_C1_2M
// C0 <-- C1
// [L0(E1,C0), L1(E_C1_1, C1), L_C1_1(E_C1_1M, C1), L2(E0, C1),
// L_C1_2(E_C1_2M, C1)]
// [L0(E1,C0), L1(E_C1_1, C1), L1M(E_C1_1M, C1), L2(E_C1_2, C1),
// L2M(E_C1_2M, C1)]
// In effect stack diagram:
// L_C1_1
// L1(C1) [ E_C1_1M ] L_C2_2
// L1M(C1)
// L1(C1) [ E_C1_1M ] L2M(C1)
// L0(C0) [ E_C1_1 ] L2(C1) [ E_C1_2M ]
// [ E1 ][ E_C1_2 ]
// [ E0 ]
......@@ -488,23 +511,22 @@ int PropertyTreeManager::SwitchToEffectNodeWithSynthesizedClip(
// emits P1.
// Prior to emitting P2, this method is invoked with (E0, C1). Both previously
// entered effects must be closed, because synthetic effect for C1 is enclosed
// by E1, thus must be closed before E1 can be closed. A mask layer L_C1_1
// is generated along with an internal effect node for blending. After closing
// by E1, thus must be closed before E1 can be closed. A mask layer L1M is
// generated along with an internal effect node for blending. After closing
// both effects, C1 has to be entered again, thus generates another synthetic
// compositor effect. The caller emits P2.
// At last, the caller invokes Finalize() to close the unclosed synthetic
// effect. Another mask layer L_C1_2 is generated, along with its internal
// effect. Another mask layer L2M is generated, along with its internal
// effect node for blending.
const auto& ancestor =
*LowestCommonAncestor(*current_effect_, next_effect).Unalias();
while (current_effect_ != &ancestor)
*LowestCommonAncestor(*current_.effect, next_effect).Unalias();
while (current_.effect != &ancestor)
CloseCcEffect();
bool newly_built = BuildEffectNodesRecursively(&next_effect);
SynthesizeCcEffectsForClipsIfNeeded(&next_clip, SkBlendMode::kSrcOver,
newly_built);
return current_effect_id_;
return current_.effect_id;
}
static bool IsNodeOnAncestorChain(const ClipPaintPropertyNode& find,
......@@ -523,34 +545,48 @@ static bool IsNodeOnAncestorChain(const ClipPaintPropertyNode& find,
return false;
}
base::Optional<PropertyTreeManager::CcEffectType>
PropertyTreeManager::NeedsSyntheticEffect(
const ClipPaintPropertyNode& clip) const {
if (clip.ClipRect().IsRounded() || clip.ClipPath())
return CcEffectType::kSyntheticForNonTrivialClip;
// Cc requires that a rectangluar clip is 2d-axis-aligned with the render
// surface to correctly apply the clip.
if (!TransformsAre2dAxisAligned(clip.LocalTransformSpace(),
current_.render_surface_transform))
return CcEffectType::kSyntheticFor2dAxisAlignment;
return base::nullopt;
}
SkBlendMode PropertyTreeManager::SynthesizeCcEffectsForClipsIfNeeded(
const ClipPaintPropertyNode* target_clip,
SkBlendMode delegated_blend,
bool effect_is_newly_built) {
target_clip = target_clip->Unalias();
if (delegated_blend != SkBlendMode::kSrcOver) {
// Exit all synthetic effect node for rounded clip if the next child has
// exotic blending mode because it has to access the backdrop of enclosing
// effect.
// Exit all synthetic effect node if the next child has exotic blending mode
// because it has to access the backdrop of enclosing effect.
while (IsCurrentCcEffectSynthetic())
CloseCcEffect();
// An effect node can't omit render surface if it has child with exotic
// blending mode. See comments below for more detail.
// TODO(crbug.com/504464): Remove premature optimization here.
GetEffectTree().Node(current_effect_id_)->has_render_surface = true;
SetCurrentEffectHasRenderSurface();
} else {
// Exit synthetic effects until there are no more synthesized clips below
// our lowest common ancestor.
const auto& lca =
*LowestCommonAncestor(*current_clip_, *target_clip).Unalias();
while (current_clip_ != &lca) {
*LowestCommonAncestor(*current_.clip, *target_clip).Unalias();
while (current_.clip != &lca) {
DCHECK(IsCurrentCcEffectSynthetic());
const auto* pre_exit_clip = current_clip_;
const auto* pre_exit_clip = current_.clip;
CloseCcEffect();
// We may run past the lowest common ancestor because it may not have
// been synthesized.
if (IsNodeOnAncestorChain(lca, *pre_exit_clip, *current_clip_))
if (IsNodeOnAncestorChain(lca, *pre_exit_clip, *current_.clip))
break;
}
......@@ -561,48 +597,64 @@ SkBlendMode PropertyTreeManager::SynthesizeCcEffectsForClipsIfNeeded(
// See comments in PropertyTreeManager::BuildEffectNodesRecursively().
// TODO(crbug.com/504464): Remove premature optimization here.
if (!effect_is_newly_built && !IsCurrentCcEffectSynthetic() &&
current_effect_->Opacity() != 1.f)
GetEffectTree().Node(current_effect_id_)->has_render_surface = true;
current_.effect->Opacity() != 1.f)
SetCurrentEffectHasRenderSurface();
}
DCHECK(current_clip_->IsAncestorOf(*target_clip));
DCHECK(current_.clip->IsAncestorOf(*target_clip));
Vector<const ClipPaintPropertyNode*> pending_clips;
for (; target_clip != current_clip_;
struct PendingClip {
const ClipPaintPropertyNode* clip;
CcEffectType type;
};
Vector<PendingClip> pending_clips;
for (; target_clip != current_.clip;
target_clip = target_clip->Parent()->Unalias()) {
DCHECK(target_clip);
bool should_synthesize =
target_clip->ClipRect().IsRounded() || target_clip->ClipPath();
if (should_synthesize)
pending_clips.push_back(target_clip);
if (auto type = NeedsSyntheticEffect(*target_clip))
pending_clips.emplace_back(PendingClip{target_clip, *type});
}
for (size_t i = pending_clips.size(); i--;) {
const ClipPaintPropertyNode* next_clip = pending_clips[i];
// For each of clip synthesized, an isolation effect node needs to be
// created to enclose only the layers that should be masked by the clip.
cc::EffectNode& mask_isolation = *GetEffectTree().Node(
GetEffectTree().Insert(cc::EffectNode(), current_effect_id_));
// mask_isolation.stable_id will be assigned later when the effect is
// closed. For now the default value of INVALID_STABLE_ID is used.
// See PropertyTreeManager::EmitClipMaskLayer().
mask_isolation.clip_id = EnsureCompositorClipNode(next_clip);
mask_isolation.has_render_surface = true;
const auto& pending_clip = pending_clips[i];
// For a non-trivial clip, the synthetic effect is an isolation to enclose
// only the layers that should be masked by the synthesized clip.
// For a non-2d-axis-preserving clip, the synthetic effect creates a render
// surface which is axis-aligned with the clip.
cc::EffectNode& synthetic_effect = *GetEffectTree().Node(
GetEffectTree().Insert(cc::EffectNode(), current_.effect_id));
if (pending_clip.type == CcEffectType::kSyntheticForNonTrivialClip) {
synthetic_effect.clip_id = EnsureCompositorClipNode(pending_clip.clip);
// For non-trivial clip, isolation_effect.stable_id will be assigned later
// when the effect is closed. For now the default value INVALID_STABLE_ID
// is used. See PropertyTreeManager::EmitClipMaskLayer().
} else {
DCHECK_EQ(pending_clip.type, CcEffectType::kSyntheticFor2dAxisAlignment);
synthetic_effect.stable_id =
CompositorElementIdFromUniqueObjectId(NewUniqueObjectId())
.GetInternalValue();
// The clip of the synthetic effect is the parent of the clip, so that
// the clip itself will be applied in the render surface.
synthetic_effect.clip_id =
EnsureCompositorClipNode(pending_clip.clip->Parent());
}
synthetic_effect.transform_id =
EnsureCompositorTransformNode(pending_clip.clip->LocalTransformSpace());
synthetic_effect.has_render_surface = true;
// Clip and kDstIn do not commute. This shall never be reached because
// kDstIn is only used internally to implement CSS clip-path and mask,
// and there is never a difference between the output clip of the effect
// and the mask content.
DCHECK(delegated_blend != SkBlendMode::kDstIn);
mask_isolation.blend_mode = delegated_blend;
synthetic_effect.blend_mode = delegated_blend;
delegated_blend = SkBlendMode::kSrcOver;
effect_stack_.emplace_back(
EffectStackEntry{current_effect_id_, current_effect_type_,
current_effect_, current_clip_});
current_effect_id_ = mask_isolation.id;
current_effect_type_ = CcEffectType::kSynthesizedClip;
current_clip_ = next_clip;
effect_stack_.emplace_back(current_);
SetCurrentEffectState(synthetic_effect, pending_clip.type, current_.effect,
pending_clip.clip);
current_.render_surface_transform =
pending_clip.clip->LocalTransformSpace();
}
return delegated_blend;
......@@ -611,12 +663,12 @@ SkBlendMode PropertyTreeManager::SynthesizeCcEffectsForClipsIfNeeded(
bool PropertyTreeManager::BuildEffectNodesRecursively(
const EffectPaintPropertyNode* next_effect) {
next_effect = next_effect ? next_effect->Unalias() : nullptr;
if (next_effect == current_effect_)
if (next_effect == current_.effect)
return false;
DCHECK(next_effect);
bool newly_built = BuildEffectNodesRecursively(next_effect->Parent());
DCHECK_EQ(next_effect->Parent()->Unalias(), current_effect_);
DCHECK_EQ(next_effect->Parent()->Unalias(), current_.effect);
#if DCHECK_IS_ON()
DCHECK(!effect_nodes_converted_.Contains(next_effect))
......@@ -627,10 +679,11 @@ bool PropertyTreeManager::BuildEffectNodesRecursively(
SkBlendMode used_blend_mode;
int output_clip_id;
if (next_effect->OutputClip()) {
const auto* output_clip = next_effect->OutputClip();
if (output_clip) {
used_blend_mode = SynthesizeCcEffectsForClipsIfNeeded(
next_effect->OutputClip(), next_effect->BlendMode(), newly_built);
output_clip_id = EnsureCompositorClipNode(next_effect->OutputClip());
output_clip, next_effect->BlendMode(), newly_built);
output_clip_id = EnsureCompositorClipNode(output_clip);
} else {
while (IsCurrentCcEffectSynthetic())
CloseCcEffect();
......@@ -638,15 +691,17 @@ bool PropertyTreeManager::BuildEffectNodesRecursively(
// blending mode, nor being opacity-only node with more than one child.
// TODO(crbug.com/504464): Remove premature optimization here.
if (next_effect->BlendMode() != SkBlendMode::kSrcOver ||
(!newly_built && current_effect_->Opacity() != 1.f))
GetEffectTree().Node(current_effect_id_)->has_render_surface = true;
(!newly_built && current_.effect->Opacity() != 1.f))
SetCurrentEffectHasRenderSurface();
used_blend_mode = next_effect->BlendMode();
output_clip_id = GetEffectTree().Node(current_effect_id_)->clip_id;
output_clip = current_.clip;
output_clip_id = GetEffectTree().Node(current_.effect_id)->clip_id;
DCHECK_EQ(output_clip_id, EnsureCompositorClipNode(output_clip));
}
cc::EffectNode& effect_node = *GetEffectTree().Node(
GetEffectTree().Insert(cc::EffectNode(), current_effect_id_));
GetEffectTree().Insert(cc::EffectNode(), current_.effect_id));
effect_node.stable_id =
next_effect->GetCompositorElementId().GetInternalValue();
effect_node.clip_id = output_clip_id;
......@@ -663,6 +718,7 @@ bool PropertyTreeManager::BuildEffectNodesRecursively(
if (!next_effect->Filter().IsEmpty() ||
used_blend_mode != SkBlendMode::kSrcOver)
effect_node.has_render_surface = true;
effect_node.opacity = next_effect->Opacity();
if (next_effect->GetColorFilter() != kColorFilterNone) {
// Currently color filter is only used by SVG masks.
......@@ -691,14 +747,9 @@ bool PropertyTreeManager::BuildEffectNodesRecursively(
property_trees_.element_id_to_effect_node_index[compositor_element_id] =
effect_node.id;
}
effect_stack_.emplace_back(EffectStackEntry{current_effect_id_,
current_effect_type_,
current_effect_, current_clip_});
current_effect_id_ = effect_node.id;
current_effect_type_ = CcEffectType::kEffect;
current_effect_ = next_effect;
if (next_effect->OutputClip())
current_clip_ = next_effect->OutputClip()->Unalias();
effect_stack_.emplace_back(current_);
SetCurrentEffectState(effect_node, CcEffectType::kEffect, next_effect,
output_clip);
return true;
}
......
......@@ -19,6 +19,7 @@ class Layer;
class PropertyTrees;
class ScrollTree;
class TransformTree;
struct EffectNode;
struct TransformNode;
}
......@@ -126,10 +127,39 @@ class PropertyTreeManager {
bool effect_is_newly_built);
void EmitClipMaskLayer();
void CloseCcEffect();
bool IsCurrentCcEffectSynthetic() const {
return current_effect_type_ != CcEffectType::kEffect;
return current_.effect_type != CcEffectType::kEffect;
}
bool IsCurrentCcEffectSyntheticForNonTrivialClip() const {
return current_.effect_type == CcEffectType::kSyntheticForNonTrivialClip;
}
// The type of operation the current cc effect node applies.
enum class CcEffectType {
// The cc effect corresponds to a Blink effect node.
kEffect,
// The cc effect is synthetic for a blink clip node that has to be
// rasterized because the clip is non-trivial.
kSyntheticForNonTrivialClip,
// The cc effect is synthetic to create a render surface that is
// 2d-axis-aligned with a blink clip node that is non-2d-axis-aligned
// in the the original render surface. Cc requires a rectangular clip to be
// 2d-axis-aligned with the render surface to correctly apply the clip.
// TODO(crbug.com/504464): This will be changed when we move render surface
// decision logic into the cc compositor thread.
kSyntheticFor2dAxisAlignment,
};
base::Optional<CcEffectType> NeedsSyntheticEffect(
const ClipPaintPropertyNode&) const;
void SetCurrentEffectState(const cc::EffectNode&,
CcEffectType,
const EffectPaintPropertyNode*,
const ClipPaintPropertyNode*);
void SetCurrentEffectHasRenderSurface();
cc::TransformTree& GetTransformTree();
cc::ClipTree& GetClipTree();
cc::EffectTree& GetEffectTree();
......@@ -158,32 +188,36 @@ class PropertyTreeManager {
HashMap<const ClipPaintPropertyNode*, int> clip_node_map_;
HashMap<const ScrollPaintPropertyNode*, int> scroll_node_map_;
// The cc effect node that has the corresponding drawing state to the
// effect and clip state from the last SwitchToEffectNodeWithSynthesizedClip.
int current_effect_id_;
// The type of operation the current cc effect node applies. kEffect means
// it corresponds to a Blink effect node. kSynthesizedClip means it implements
// a Blink clip node that has to be rasterized.
enum class CcEffectType { kEffect, kSynthesizedClip } current_effect_type_;
// The effect state of the current cc effect node.
const EffectPaintPropertyNode* current_effect_;
// The clip state of the current cc effect node. This value may be shallower
// than the one passed into SwitchToEffectNodeWithSynthesizedClip because not
// every clip needs to be synthesized as cc effect.
// Is set to output clip of the effect if the type is kEffect, or set to the
// synthesized clip node if the type is kSynthesizedClip.
const ClipPaintPropertyNode* current_clip_;
// This keep track of cc effect stack. Whenever a new cc effect is nested,
// a new entry is pushed, and the entry will be popped when the effect closed.
// Note: This is a "restore stack", i.e. the top element does not represent
// the current state, but the state prior to most recent push.
struct EffectStackEntry {
struct EffectState {
// The cc effect node that has the corresponding drawing state to the
// effect and clip state from the last
// SwitchToEffectNodeWithSynthesizedClip.
int effect_id;
CcEffectType effect_type;
// The effect state of the cc effect node.
const EffectPaintPropertyNode* effect;
// The clip state of the cc effect node. This value may be shallower than
// the one passed into SwitchToEffectNodeWithSynthesizedClip because not
// every clip needs to be synthesized as cc effect.
// Is set to output clip of the effect if the type is kEffect, or set to the
// synthesized clip node if the type is kSyntheticForNonTrivialClip.
const ClipPaintPropertyNode* clip;
// The transform space of the containing render surface.
// TODO(crbug.com/504464): Remove this when move render surface decision
// logic into cc compositor thread.
const TransformPaintPropertyNode* render_surface_transform;
};
Vector<EffectStackEntry> effect_stack_;
// The current effect state. Virtually it's the top of the effect stack if
// it and effect_stack_ are treated as a whole stack.
EffectState current_;
// This keep track of cc effect stack. Whenever a new cc effect is nested,
// a new entry is pushed, and the entry will be popped when the effect closed.
// Note: This is a "restore stack", i.e. the top element does not represent
// the current state (which is in current_), but the state prior to most
// recent push.
Vector<EffectState> effect_stack_;
#if DCHECK_IS_ON()
HashSet<const EffectPaintPropertyNode*> effect_nodes_converted_;
......
......@@ -1827,6 +1827,56 @@ bool TransformationMatrix::IsIntegerTranslation() const {
return true;
}
// This is the same as gfx::Transform::Preserves2dAxisAlignment().
bool TransformationMatrix::Preserves2dAxisAlignment() const {
// Check whether an axis aligned 2-dimensional rect would remain axis-aligned
// after being transformed by this matrix (and implicitly projected by
// dropping any non-zero z-values).
//
// The 4th column can be ignored because translations don't affect axis
// alignment. The 3rd column can be ignored because we are assuming 2d
// inputs, where z-values will be zero. The 3rd row can also be ignored
// because we are assuming 2d outputs, and any resulting z-value is dropped
// anyway. For the inner 2x2 portion, the only effects that keep a rect axis
// aligned are (1) swapping axes and (2) scaling axes. This can be checked by
// verifying only 1 element of every column and row is non-zero. Degenerate
// cases that project the x or y dimension to zero are considered to preserve
// axis alignment.
//
// If the matrix does have perspective component that is affected by x or y
// values: The current implementation conservatively assumes that axis
// alignment is not preserved.
bool has_x_or_y_perspective = M14() != 0 || M24() != 0;
if (has_x_or_y_perspective)
return false;
constexpr double kEpsilon = std::numeric_limits<double>::epsilon();
int num_non_zero_in_row_1 = 0;
int num_non_zero_in_row_2 = 0;
int num_non_zero_in_col_1 = 0;
int num_non_zero_in_col_2 = 0;
if (std::abs(M11()) > kEpsilon) {
num_non_zero_in_col_1++;
num_non_zero_in_row_1++;
}
if (std::abs(M12()) > kEpsilon) {
num_non_zero_in_col_1++;
num_non_zero_in_row_2++;
}
if (std::abs(M21()) > kEpsilon) {
num_non_zero_in_col_2++;
num_non_zero_in_row_1++;
}
if (std::abs(M22()) > kEpsilon) {
num_non_zero_in_col_2++;
num_non_zero_in_row_2++;
}
return num_non_zero_in_row_1 <= 1 && num_non_zero_in_row_2 <= 1 &&
num_non_zero_in_col_1 <= 1 && num_non_zero_in_col_2 <= 1;
}
FloatSize TransformationMatrix::To2DTranslation() const {
DCHECK(IsIdentityOr2DTranslation());
return FloatSize(matrix_[3][0], matrix_[3][1]);
......
......@@ -474,6 +474,10 @@ class PLATFORM_EXPORT TransformationMatrix {
bool IsIntegerTranslation() const;
// Returns true if axis-aligned 2d rects will remain axis-aligned after being
// transformed by this matrix.
bool Preserves2dAxisAlignment() const;
// If this transformation is identity or 2D translation, returns the
// translation.
FloatSize To2DTranslation() const;
......
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