// 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; } int 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(); return id; } 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(scroll_node->ContainerRect().Size()); compositor_node.bounds = static_cast(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( scroll_node->OverscrollBehaviorX()), static_cast( 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(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::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 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(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