| // Copyright 2015 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "third_party/blink/renderer/platform/graphics/compositing/property_tree_manager.h" |
| |
| #include "cc/layers/layer.h" |
| #include "cc/trees/clip_node.h" |
| #include "cc/trees/effect_node.h" |
| #include "cc/trees/layer_tree_host.h" |
| #include "cc/trees/property_tree.h" |
| #include "cc/trees/scroll_node.h" |
| #include "cc/trees/transform_node.h" |
| #include "third_party/blink/renderer/platform/graphics/compositing/paint_artifact_compositor.h" |
| #include "third_party/blink/renderer/platform/graphics/paint/clip_paint_property_node.h" |
| #include "third_party/blink/renderer/platform/graphics/paint/effect_paint_property_node.h" |
| #include "third_party/blink/renderer/platform/graphics/paint/geometry_mapper.h" |
| #include "third_party/blink/renderer/platform/graphics/paint/scroll_paint_property_node.h" |
| #include "third_party/blink/renderer/platform/graphics/paint/transform_paint_property_node.h" |
| #include "third_party/skia/include/effects/SkColorFilterImageFilter.h" |
| #include "third_party/skia/include/effects/SkLumaColorFilter.h" |
| |
| namespace blink { |
| |
| namespace { |
| |
| static constexpr int kInvalidNodeId = -1; |
| // cc's property trees use 0 for the root node (always non-null). |
| static constexpr int kRealRootNodeId = 0; |
| // cc allocates special nodes for root effects such as the device scale. |
| static constexpr int kSecondaryRootNodeId = 1; |
| |
| } // namespace |
| |
| PropertyTreeManager::PropertyTreeManager(PropertyTreeManagerClient& client, |
| cc::PropertyTrees& property_trees, |
| cc::Layer* root_layer, |
| LayerListBuilder* layer_list_builder) |
| : client_(client), |
| property_trees_(property_trees), |
| root_layer_(root_layer), |
| layer_list_builder_(layer_list_builder) { |
| SetupRootTransformNode(); |
| SetupRootClipNode(); |
| SetupRootEffectNode(); |
| SetupRootScrollNode(); |
| } |
| |
| void PropertyTreeManager::Finalize() { |
| while (effect_stack_.size()) |
| CloseCcEffect(); |
| } |
| |
| cc::TransformTree& PropertyTreeManager::GetTransformTree() { |
| return property_trees_.transform_tree; |
| } |
| |
| cc::ClipTree& PropertyTreeManager::GetClipTree() { |
| return property_trees_.clip_tree; |
| } |
| |
| cc::EffectTree& PropertyTreeManager::GetEffectTree() { |
| return property_trees_.effect_tree; |
| } |
| |
| cc::ScrollTree& PropertyTreeManager::GetScrollTree() { |
| return property_trees_.scroll_tree; |
| } |
| |
| void PropertyTreeManager::SetupRootTransformNode() { |
| // cc is hardcoded to use transform node index 1 for device scale and |
| // transform. |
| cc::TransformTree& transform_tree = property_trees_.transform_tree; |
| transform_tree.clear(); |
| property_trees_.element_id_to_transform_node_index.clear(); |
| cc::TransformNode& transform_node = *transform_tree.Node( |
| transform_tree.Insert(cc::TransformNode(), kRealRootNodeId)); |
| DCHECK_EQ(transform_node.id, kSecondaryRootNodeId); |
| transform_node.source_node_id = transform_node.parent_id; |
| |
| // TODO(jaydasika): We shouldn't set ToScreen and FromScreen of root |
| // transform node here. They should be set while updating transform tree in |
| // cc. |
| float device_scale_factor = |
| root_layer_->layer_tree_host()->device_scale_factor(); |
| gfx::Transform to_screen; |
| to_screen.Scale(device_scale_factor, device_scale_factor); |
| transform_tree.SetToScreen(kRealRootNodeId, to_screen); |
| gfx::Transform from_screen; |
| bool invertible = to_screen.GetInverse(&from_screen); |
| DCHECK(invertible); |
| transform_tree.SetFromScreen(kRealRootNodeId, from_screen); |
| transform_tree.set_needs_update(true); |
| |
| transform_node_map_.Set(&TransformPaintPropertyNode::Root(), |
| transform_node.id); |
| root_layer_->SetTransformTreeIndex(transform_node.id); |
| } |
| |
| void PropertyTreeManager::SetupRootClipNode() { |
| // cc is hardcoded to use clip node index 1 for viewport clip. |
| cc::ClipTree& clip_tree = property_trees_.clip_tree; |
| clip_tree.clear(); |
| cc::ClipNode& clip_node = |
| *clip_tree.Node(clip_tree.Insert(cc::ClipNode(), kRealRootNodeId)); |
| DCHECK_EQ(clip_node.id, kSecondaryRootNodeId); |
| |
| clip_node.clip_type = cc::ClipNode::ClipType::APPLIES_LOCAL_CLIP; |
| // TODO(bokan): This needs to come from the Visual Viewport which will |
| // correctly account for the URL bar. In fact, the visual viewport property |
| // tree builder should probably be the one to create the property tree state |
| // and have this created in the same way as other layers. |
| clip_node.clip = gfx::RectF( |
| gfx::SizeF(root_layer_->layer_tree_host()->device_viewport_size())); |
| clip_node.transform_id = kRealRootNodeId; |
| |
| clip_node_map_.Set(&ClipPaintPropertyNode::Root(), clip_node.id); |
| root_layer_->SetClipTreeIndex(clip_node.id); |
| } |
| |
| void PropertyTreeManager::SetupRootEffectNode() { |
| // cc is hardcoded to use effect node index 1 for root render surface. |
| cc::EffectTree& effect_tree = property_trees_.effect_tree; |
| effect_tree.clear(); |
| property_trees_.element_id_to_effect_node_index.clear(); |
| cc::EffectNode& effect_node = |
| *effect_tree.Node(effect_tree.Insert(cc::EffectNode(), kInvalidNodeId)); |
| DCHECK_EQ(effect_node.id, kSecondaryRootNodeId); |
| |
| static UniqueObjectId unique_id = NewUniqueObjectId(); |
| |
| effect_node.stable_id = |
| CompositorElementIdFromUniqueObjectId(unique_id).GetInternalValue(); |
| effect_node.transform_id = kRealRootNodeId; |
| effect_node.clip_id = kSecondaryRootNodeId; |
| effect_node.has_render_surface = true; |
| root_layer_->SetEffectTreeIndex(effect_node.id); |
| |
| SetCurrentEffectState(effect_node, CcEffectType::kEffect, |
| &EffectPaintPropertyNode::Root(), |
| &ClipPaintPropertyNode::Root()); |
| } |
| |
| void PropertyTreeManager::SetupRootScrollNode() { |
| cc::ScrollTree& scroll_tree = property_trees_.scroll_tree; |
| scroll_tree.clear(); |
| property_trees_.element_id_to_scroll_node_index.clear(); |
| cc::ScrollNode& scroll_node = |
| *scroll_tree.Node(scroll_tree.Insert(cc::ScrollNode(), kRealRootNodeId)); |
| DCHECK_EQ(scroll_node.id, kSecondaryRootNodeId); |
| scroll_node.transform_id = kSecondaryRootNodeId; |
| |
| scroll_node_map_.Set(&ScrollPaintPropertyNode::Root(), scroll_node.id); |
| 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; |
| DCHECK(!effect->IsParentAlias() || !effect->Parent()); |
| current_.effect = effect; |
| DCHECK(!clip->IsParentAlias() || !clip->Parent()); |
| 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) { |
| transform_node = transform_node->Unalias(); |
| auto it = transform_node_map_.find(transform_node); |
| if (it != transform_node_map_.end()) |
| return it->value; |
| |
| int parent_id = EnsureCompositorTransformNode(transform_node->Parent()); |
| int id = GetTransformTree().Insert(cc::TransformNode(), parent_id); |
| |
| cc::TransformNode& compositor_node = *GetTransformTree().Node(id); |
| compositor_node.source_node_id = parent_id; |
| |
| FloatPoint3D origin = transform_node->Origin(); |
| compositor_node.pre_local.matrix().setTranslate(-origin.X(), -origin.Y(), |
| -origin.Z()); |
| // The sticky offset on the blink transform node is pre-computed and stored |
| // to the local matrix. Cc applies sticky offset dynamically on top of the |
| // local matrix. We should not set the local matrix on cc node if it is a |
| // sticky node because the sticky offset would be applied twice otherwise. |
| if (!transform_node->GetStickyConstraint()) { |
| compositor_node.local.matrix() = |
| TransformationMatrix::ToSkMatrix44(transform_node->Matrix()); |
| } |
| compositor_node.post_local.matrix().setTranslate(origin.X(), origin.Y(), |
| origin.Z()); |
| compositor_node.needs_local_transform_update = true; |
| compositor_node.flattens_inherited_transform = |
| transform_node->FlattensInheritedTransform(); |
| compositor_node.sorting_context_id = transform_node->RenderingContextId(); |
| |
| if (transform_node->IsAffectedByOuterViewportBoundsDelta()) { |
| compositor_node.moved_by_outer_viewport_bounds_delta_y = true; |
| GetTransformTree().AddNodeAffectedByOuterViewportBoundsDelta(id); |
| } |
| |
| if (const auto* sticky_constraint = transform_node->GetStickyConstraint()) { |
| DCHECK(sticky_constraint->is_sticky); |
| cc::StickyPositionNodeData* sticky_data = |
| GetTransformTree().StickyPositionData(id); |
| sticky_data->constraints = *sticky_constraint; |
| // TODO(pdr): This could be a performance issue because it crawls up the |
| // transform tree for each pending layer. If this is on profiles, we should |
| // cache a lookup of transform node to scroll translation transform node. |
| const auto& scroll_ancestor = |
| transform_node->NearestScrollTranslationNode(); |
| sticky_data->scroll_ancestor = EnsureCompositorScrollNode(&scroll_ancestor); |
| if (scroll_ancestor.ScrollNode()->ScrollsOuterViewport()) |
| GetTransformTree().AddNodeAffectedByOuterViewportBoundsDelta(id); |
| if (CompositorElementId shifting_sticky_box_element_id = |
| sticky_data->constraints.nearest_element_shifting_sticky_box) { |
| sticky_data->nearest_node_shifting_sticky_box = |
| GetTransformTree() |
| .FindNodeFromElementId(shifting_sticky_box_element_id) |
| ->id; |
| } |
| if (CompositorElementId shifting_containing_block_element_id = |
| sticky_data->constraints |
| .nearest_element_shifting_containing_block) { |
| sticky_data->nearest_node_shifting_containing_block = |
| GetTransformTree() |
| .FindNodeFromElementId(shifting_containing_block_element_id) |
| ->id; |
| } |
| } |
| |
| CompositorElementId compositor_element_id = |
| transform_node->GetCompositorElementId(); |
| if (compositor_element_id) { |
| property_trees_.element_id_to_transform_node_index[compositor_element_id] = |
| id; |
| compositor_node.element_id = compositor_element_id; |
| } |
| |
| // If this transform is a scroll offset translation, create the associated |
| // compositor scroll property node and adjust the compositor transform node's |
| // scroll offset. |
| if (auto* scroll_node = transform_node->ScrollNode()) { |
| // Blink creates a 2d transform node just for scroll offset whereas cc's |
| // transform node has a special scroll offset field. To handle this we |
| // adjust cc's transform node to remove the 2d scroll translation and |
| // instead set the scroll_offset field. |
| auto scroll_offset_size = transform_node->Matrix().To2DTranslation(); |
| auto scroll_offset = gfx::ScrollOffset(-scroll_offset_size.Width(), |
| -scroll_offset_size.Height()); |
| DCHECK(compositor_node.local.IsIdentityOr2DTranslation()); |
| compositor_node.scroll_offset = scroll_offset; |
| compositor_node.local.MakeIdentity(); |
| compositor_node.scrolls = true; |
| compositor_node.should_be_snapped = true; |
| |
| CreateCompositorScrollNode(scroll_node, compositor_node); |
| } |
| |
| // 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. |
| // 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()) |
| SetCurrentEffectHasRenderSurface(); |
| |
| auto result = transform_node_map_.Set(transform_node, id); |
| DCHECK(result.is_new_entry); |
| GetTransformTree().set_needs_update(true); |
| |
| return id; |
| } |
| |
| void PropertyTreeManager::EnsureCompositorPageScaleTransformNode( |
| const TransformPaintPropertyNode* node) { |
| int id = EnsureCompositorTransformNode(node); |
| DCHECK(GetTransformTree().Node(id)); |
| cc::TransformNode& compositor_node = *GetTransformTree().Node(id); |
| |
| // The page scale node is special because its transform matrix is assumed to |
| // be in the post_local matrix by the compositor. There should be no |
| // translation from the origin so we clear the other matrices. |
| DCHECK(node->Origin() == FloatPoint3D()); |
| compositor_node.post_local.matrix() = compositor_node.local.matrix(); |
| compositor_node.pre_local.matrix().setIdentity(); |
| compositor_node.local.matrix().setIdentity(); |
| } |
| |
| int PropertyTreeManager::EnsureCompositorClipNode( |
| const ClipPaintPropertyNode* clip_node) { |
| clip_node = clip_node->Unalias(); |
| auto it = clip_node_map_.find(clip_node); |
| if (it != clip_node_map_.end()) |
| return it->value; |
| |
| int parent_id = EnsureCompositorClipNode(clip_node->Parent()); |
| int id = GetClipTree().Insert(cc::ClipNode(), parent_id); |
| |
| cc::ClipNode& compositor_node = *GetClipTree().Node(id); |
| |
| compositor_node.clip = clip_node->ClipRect().Rect(); |
| compositor_node.transform_id = |
| EnsureCompositorTransformNode(clip_node->LocalTransformSpace()); |
| compositor_node.clip_type = cc::ClipNode::ClipType::APPLIES_LOCAL_CLIP; |
| |
| auto result = clip_node_map_.Set(clip_node, id); |
| DCHECK(result.is_new_entry); |
| GetClipTree().set_needs_update(true); |
| return id; |
| } |
| |
| void PropertyTreeManager::CreateCompositorScrollNode( |
| const ScrollPaintPropertyNode* scroll_node, |
| const cc::TransformNode& scroll_offset_translation) { |
| DCHECK(!scroll_node_map_.Contains(scroll_node)); |
| |
| auto parent_it = scroll_node_map_.find(scroll_node->Parent()); |
| // Compositor transform nodes up to scroll_offset_translation must exist. |
| // Scrolling uses the transform tree for scroll offsets so this means all |
| // ancestor scroll nodes must also exist. |
| DCHECK(parent_it != scroll_node_map_.end()); |
| int parent_id = parent_it->value; |
| int id = GetScrollTree().Insert(cc::ScrollNode(), parent_id); |
| |
| cc::ScrollNode& compositor_node = *GetScrollTree().Node(id); |
| compositor_node.scrollable = true; |
| |
| compositor_node.container_bounds = |
| static_cast<gfx::Size>(scroll_node->ContainerRect().Size()); |
| compositor_node.bounds = static_cast<gfx::Size>(scroll_node->ContentsSize()); |
| compositor_node.user_scrollable_horizontal = |
| scroll_node->UserScrollableHorizontal(); |
| compositor_node.user_scrollable_vertical = |
| scroll_node->UserScrollableVertical(); |
| compositor_node.scrolls_inner_viewport = scroll_node->ScrollsInnerViewport(); |
| compositor_node.scrolls_outer_viewport = scroll_node->ScrollsOuterViewport(); |
| compositor_node.max_scroll_offset_affected_by_page_scale = |
| scroll_node->MaxScrollOffsetAffectedByPageScale(); |
| compositor_node.main_thread_scrolling_reasons = |
| scroll_node->GetMainThreadScrollingReasons(); |
| compositor_node.overscroll_behavior = cc::OverscrollBehavior( |
| static_cast<cc::OverscrollBehavior::OverscrollBehaviorType>( |
| scroll_node->OverscrollBehaviorX()), |
| static_cast<cc::OverscrollBehavior::OverscrollBehaviorType>( |
| scroll_node->OverscrollBehaviorY())); |
| compositor_node.snap_container_data = scroll_node->GetSnapContainerData(); |
| |
| auto compositor_element_id = scroll_node->GetCompositorElementId(); |
| if (compositor_element_id) { |
| compositor_node.element_id = compositor_element_id; |
| property_trees_.element_id_to_scroll_node_index[compositor_element_id] = id; |
| } |
| |
| compositor_node.transform_id = scroll_offset_translation.id; |
| |
| // TODO(pdr): Set the scroll node's non_fast_scrolling_region value. |
| |
| auto result = scroll_node_map_.Set(scroll_node, id); |
| DCHECK(result.is_new_entry); |
| |
| GetScrollTree().SetScrollOffset(compositor_element_id, |
| scroll_offset_translation.scroll_offset); |
| GetScrollTree().set_needs_update(true); |
| } |
| |
| int PropertyTreeManager::EnsureCompositorScrollNode( |
| const TransformPaintPropertyNode* scroll_offset_translation) { |
| const auto* scroll_node = scroll_offset_translation->ScrollNode(); |
| DCHECK(scroll_node); |
| EnsureCompositorTransformNode(scroll_offset_translation); |
| auto it = scroll_node_map_.find(scroll_node); |
| DCHECK(it != scroll_node_map_.end()); |
| return it->value; |
| } |
| |
| void PropertyTreeManager::EmitClipMaskLayer() { |
| 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); |
| |
| 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), |
| mask_isolation.stable_id); |
| mask_isolation.stable_id = mask_isolation_id.GetInternalValue(); |
| |
| cc::EffectNode& mask_effect = *GetEffectTree().Node( |
| 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(); |
| layer_list_builder_->Add(mask_layer); |
| mask_layer->set_property_tree_sequence_number( |
| root_layer_->property_tree_sequence_number()); |
| mask_layer->SetTransformTreeIndex(EnsureCompositorTransformNode(clip_space)); |
| // TODO(pdr): This could be a performance issue because it crawls up the |
| // transform tree for each pending layer. If this is on profiles, we should |
| // cache a lookup of transform node to scroll translation transform node. |
| int scroll_id = |
| EnsureCompositorScrollNode(&clip_space->NearestScrollTranslationNode()); |
| mask_layer->SetScrollTreeIndex(scroll_id); |
| mask_layer->SetClipTreeIndex(clip_id); |
| mask_layer->SetEffectTreeIndex(mask_effect.id); |
| } |
| |
| void PropertyTreeManager::CloseCcEffect() { |
| DCHECK(effect_stack_.size()); |
| 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 |
| // blending needs access to the backdrop of the enclosing effect. With |
| // the isolation for a synthesized clip, a blank backdrop will be seen. |
| // Therefore the blending is delegated to the outermost synthesized clip, |
| // 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; |
| |
| // 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 (IsCurrentCcEffectSyntheticForNonTrivialClip()) |
| EmitClipMaskLayer(); |
| |
| current_ = previous_state; |
| effect_stack_.pop_back(); |
| |
| if (clear_synthetic_effects) { |
| while (IsCurrentCcEffectSynthetic()) |
| 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) { |
| // This function is expected to be invoked right before emitting each layer. |
| // It keeps track of the nesting of clip and effects, output a composited |
| // effect node whenever an effect is entered, or a non-trivial clip is |
| // entered. In the latter case, the generated composited effect node is |
| // called a "synthetic effect", and the corresponding clip a "synthesized |
| // clip". Upon exiting a synthesized clip, a mask layer will be appended, |
| // which will be kDstIn blended on top of contents enclosed by the synthetic |
| // effect, i.e. applying the clip as a mask. |
| // |
| // 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)] |
| // In effect stack diagram: |
| // P0(C0) P1(C1) |
| // [ E1 ] P2(C1) |
| // [ E0 ] |
| // |
| // The following cc property trees and layers will be generated: |
| // E0 <+- E1 <-- E_C1_1 <-- E_C1_1M |
| // +- E_C1_2 <-- E_C1_2M |
| // C0 <-- 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: |
| // L1M(C1) |
| // L1(C1) [ E_C1_1M ] L2M(C1) |
| // L0(C0) [ E_C1_1 ] L2(C1) [ E_C1_2M ] |
| // [ E1 ][ E_C1_2 ] |
| // [ E0 ] |
| // |
| // As the caller iterates the layer list, the sequence of events happen in |
| // the following order: |
| // Prior to emitting P0, this method is invoked with (E1, C0). A compositor |
| // effect node for E1 is generated as we are entering it. The caller emits P0. |
| // Prior to emitting P1, this method is invoked with (E1, C1). A synthetic |
| // compositor effect for C1 is generated as we are entering it. The caller |
| // 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 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 L2M is generated, along with its internal |
| // effect node for blending. |
| const auto& 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; |
| } |
| |
| static bool IsNodeOnAncestorChain(const ClipPaintPropertyNode& find, |
| const ClipPaintPropertyNode& current, |
| const ClipPaintPropertyNode& ancestor) { |
| // Precondition: |ancestor| must be an (inclusive) ancestor of |current| |
| // otherwise the behavior is undefined. |
| // Returns true if node |find| is one of the node on the ancestor chain |
| // [current, ancestor). Returns false otherwise. |
| DCHECK(ancestor.IsAncestorOf(current)); |
| |
| for (const auto* node = ¤t; node != &ancestor; node = node->Parent()) { |
| if (node == &find) |
| return true; |
| } |
| 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) { |
| auto* unaliased_target_clip = target_clip->Unalias(); |
| if (delegated_blend != SkBlendMode::kSrcOver) { |
| // 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. |
| SetCurrentEffectHasRenderSurface(); |
| } else { |
| // Exit synthetic effects until there are no more synthesized clips below |
| // our lowest common ancestor. |
| const auto& lca = |
| *LowestCommonAncestor(*current_.clip, *unaliased_target_clip).Unalias(); |
| while (current_.clip != &lca) { |
| DCHECK(IsCurrentCcEffectSynthetic()); |
| 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)) |
| break; |
| } |
| |
| // If the effect is an existing node, i.e. already has at least one paint |
| // chunk or child effect, and by reaching here it implies we are going to |
| // attach either another paint chunk or child effect to it. We can no longer |
| // omit render surface for it even for opacity-only node. |
| // See comments in PropertyTreeManager::BuildEffectNodesRecursively(). |
| // TODO(crbug.com/504464): Remove premature optimization here. |
| if (!effect_is_newly_built && !IsCurrentCcEffectSynthetic() && |
| current_.effect->Opacity() != 1.f) |
| SetCurrentEffectHasRenderSurface(); |
| } |
| |
| DCHECK(current_.clip->IsAncestorOf(*unaliased_target_clip)); |
| |
| struct PendingClip { |
| const ClipPaintPropertyNode* clip; |
| CcEffectType type; |
| }; |
| Vector<PendingClip> pending_clips; |
| for (; unaliased_target_clip != current_.clip; |
| unaliased_target_clip = unaliased_target_clip->Parent()->Unalias()) { |
| DCHECK(unaliased_target_clip); |
| if (auto type = NeedsSyntheticEffect(*unaliased_target_clip)) |
| pending_clips.emplace_back(PendingClip{unaliased_target_clip, *type}); |
| } |
| |
| for (size_t i = pending_clips.size(); i--;) { |
| 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); |
| synthetic_effect.blend_mode = delegated_blend; |
| delegated_blend = SkBlendMode::kSrcOver; |
| |
| 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; |
| } |
| |
| bool PropertyTreeManager::BuildEffectNodesRecursively( |
| const EffectPaintPropertyNode* next_effect) { |
| next_effect = SafeUnalias(next_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); |
| |
| #if DCHECK_IS_ON() |
| DCHECK(!effect_nodes_converted_.Contains(next_effect)) |
| << "Malformed paint artifact. Paint chunks under the same effect should " |
| "be contiguous."; |
| effect_nodes_converted_.insert(next_effect); |
| #endif |
| |
| SkBlendMode used_blend_mode; |
| int output_clip_id; |
| const auto* output_clip = SafeUnalias(next_effect->OutputClip()); |
| if (output_clip) { |
| used_blend_mode = SynthesizeCcEffectsForClipsIfNeeded( |
| output_clip, next_effect->BlendMode(), newly_built); |
| output_clip_id = EnsureCompositorClipNode(output_clip); |
| } else { |
| while (IsCurrentCcEffectSynthetic()) |
| CloseCcEffect(); |
| // An effect node can't omit render surface if it has child with exotic |
| // 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)) |
| SetCurrentEffectHasRenderSurface(); |
| |
| used_blend_mode = next_effect->BlendMode(); |
| 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)); |
| effect_node.stable_id = |
| next_effect->GetCompositorElementId().GetInternalValue(); |
| effect_node.clip_id = output_clip_id; |
| // Every effect is supposed to have render surface enabled for grouping, |
| // but we can get away without one if the effect is opacity-only and has only |
| // one compositing child with kSrcOver blend mode. This is both for |
| // optimization and not introducing sub-pixel differences in layout tests. |
| // See PropertyTreeManager::switchToEffectNode() and above where we |
| // retrospectively enable render surface when more than one compositing child |
| // or a child with exotic blend mode is detected. |
| // TODO(crbug.com/504464): There is ongoing work in cc to delay render surface |
| // decision until later phase of the pipeline. Remove premature optimization |
| // here once the work is ready. |
| if (!next_effect->Filter().IsEmpty() || |
| !next_effect->BackdropFilter().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. |
| // We are cutting corner here by support only specific configuration. |
| DCHECK(next_effect->GetColorFilter() == kColorFilterLuminanceToAlpha); |
| DCHECK(used_blend_mode == SkBlendMode::kDstIn); |
| DCHECK(next_effect->Filter().IsEmpty()); |
| effect_node.filters.Append(cc::FilterOperation::CreateReferenceFilter( |
| sk_make_sp<ColorFilterPaintFilter>(SkLumaColorFilter::Make(), |
| nullptr))); |
| } else { |
| effect_node.filters = next_effect->Filter().AsCcFilterOperations(); |
| effect_node.backdrop_filters = |
| next_effect->BackdropFilter().AsCcFilterOperations(); |
| effect_node.filters_origin = next_effect->FiltersOrigin(); |
| effect_node.transform_id = |
| EnsureCompositorTransformNode(next_effect->LocalTransformSpace()); |
| } |
| effect_node.blend_mode = used_blend_mode; |
| effect_node.double_sided = |
| !next_effect->LocalTransformSpace()->IsBackfaceHidden(); |
| CompositorElementId compositor_element_id = |
| next_effect->GetCompositorElementId(); |
| if (compositor_element_id) { |
| DCHECK(property_trees_.element_id_to_effect_node_index.find( |
| compositor_element_id) == |
| property_trees_.element_id_to_effect_node_index.end()); |
| property_trees_.element_id_to_effect_node_index[compositor_element_id] = |
| effect_node.id; |
| } |
| effect_stack_.emplace_back(current_); |
| SetCurrentEffectState(effect_node, CcEffectType::kEffect, next_effect, |
| output_clip); |
| |
| return true; |
| } |
| |
| } // namespace blink |