| // 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 "build/build_config.h" |
| #include "cc/input/overscroll_behavior.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/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, |
| int new_sequence_number) |
| : client_(client), |
| property_trees_(property_trees), |
| root_layer_(root_layer), |
| layer_list_builder_(layer_list_builder), |
| new_sequence_number_(new_sequence_number) { |
| SetupRootTransformNode(); |
| SetupRootClipNode(); |
| SetupRootEffectNode(); |
| SetupRootScrollNode(); |
| } |
| |
| PropertyTreeManager::~PropertyTreeManager() { |
| DCHECK(!effect_stack_.size()) << "PropertyTreeManager::Finalize() must be " |
| "called at the end of tree conversion."; |
| } |
| |
| void PropertyTreeManager::Finalize() { |
| while (effect_stack_.size()) |
| CloseCcEffect(); |
| DCHECK(effect_stack_.IsEmpty()); |
| } |
| |
| static void UpdateCcTransformLocalMatrix( |
| cc::TransformNode& compositor_node, |
| const TransformPaintPropertyNode& transform_node) { |
| if (transform_node.GetStickyConstraint()) { |
| // 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. |
| DCHECK(compositor_node.local.IsIdentity()); |
| DCHECK_EQ(gfx::Point3F(), compositor_node.origin); |
| } else if (transform_node.IsIdentityOr2DTranslation()) { |
| auto translation = transform_node.Translation2D(); |
| if (transform_node.ScrollNode()) { |
| // Blink creates a 2d transform node just for scroll offset whereas cc's |
| // transform node has a special scroll offset field. |
| compositor_node.scroll_offset = |
| gfx::ScrollOffset(-translation.Width(), -translation.Height()); |
| DCHECK(compositor_node.local.IsIdentity()); |
| DCHECK_EQ(gfx::Point3F(), compositor_node.origin); |
| } else { |
| compositor_node.local.matrix().setTranslate(translation.Width(), |
| translation.Height(), 0); |
| DCHECK_EQ(FloatPoint3D(), transform_node.Origin()); |
| compositor_node.origin = gfx::Point3F(); |
| } |
| } else { |
| DCHECK(!transform_node.ScrollNode()); |
| FloatPoint3D origin = transform_node.Origin(); |
| compositor_node.local.matrix() = |
| TransformationMatrix::ToSkMatrix44(transform_node.Matrix()); |
| compositor_node.origin = origin; |
| } |
| compositor_node.needs_local_transform_update = true; |
| } |
| |
| static void SetTransformTreePageScaleFactor( |
| cc::TransformTree* transform_tree, |
| cc::TransformNode* page_scale_node) { |
| DCHECK(page_scale_node->local.IsScale2d()); |
| auto page_scale = page_scale_node->local.Scale2d(); |
| DCHECK_EQ(page_scale.x(), page_scale.y()); |
| transform_tree->set_page_scale_factor(page_scale.x()); |
| } |
| |
| bool PropertyTreeManager::DirectlyUpdateCompositedOpacityValue( |
| cc::LayerTreeHost& host, |
| const EffectPaintPropertyNode& effect) { |
| auto* property_trees = host.property_trees(); |
| auto* cc_effect = property_trees->effect_tree.Node( |
| effect.CcNodeId(property_trees->sequence_number)); |
| if (!cc_effect) |
| return false; |
| |
| // We directly update opacity only when it's not animating in compositor. If |
| // the compositor has not cleared is_currently_animating_opacity, we should |
| // clear it now to let the compositor respect the new value. |
| cc_effect->is_currently_animating_opacity = false; |
| |
| cc_effect->opacity = effect.Opacity(); |
| cc_effect->effect_changed = true; |
| property_trees->effect_tree.set_needs_update(true); |
| host.SetNeedsCommit(); |
| return true; |
| } |
| |
| bool PropertyTreeManager::DirectlyUpdateScrollOffsetTransform( |
| cc::LayerTreeHost& host, |
| const TransformPaintPropertyNode& transform) { |
| auto* scroll_node = transform.ScrollNode(); |
| // Only handle scroll adjustments. |
| if (!scroll_node) |
| return false; |
| |
| auto* property_trees = host.property_trees(); |
| auto* cc_scroll_node = property_trees->scroll_tree.Node( |
| scroll_node->CcNodeId(property_trees->sequence_number)); |
| if (!cc_scroll_node) |
| return false; |
| |
| auto* cc_transform = property_trees->transform_tree.Node( |
| transform.CcNodeId(property_trees->sequence_number)); |
| if (!cc_transform) |
| return false; |
| |
| DCHECK(!cc_transform->is_currently_animating); |
| |
| auto translation = transform.Translation2D(); |
| auto scroll_offset = |
| gfx::ScrollOffset(-translation.Width(), -translation.Height()); |
| |
| DirectlySetScrollOffset(host, scroll_node->GetCompositorElementId(), |
| scroll_offset); |
| if (cc_transform->scroll_offset != scroll_offset) { |
| UpdateCcTransformLocalMatrix(*cc_transform, transform); |
| cc_transform->transform_changed = true; |
| property_trees->transform_tree.set_needs_update(true); |
| host.SetNeedsCommit(); |
| } |
| return true; |
| } |
| |
| bool PropertyTreeManager::DirectlyUpdateTransform( |
| cc::LayerTreeHost& host, |
| const TransformPaintPropertyNode& transform) { |
| // If we have a ScrollNode, we should be using |
| // DirectlyUpdateScrollOffsetTransform(). |
| DCHECK(!transform.ScrollNode()); |
| |
| auto* property_trees = host.property_trees(); |
| auto* cc_transform = property_trees->transform_tree.Node( |
| transform.CcNodeId(property_trees->sequence_number)); |
| if (!cc_transform) |
| return false; |
| |
| UpdateCcTransformLocalMatrix(*cc_transform, transform); |
| |
| // We directly update transform only when the transform is not animating in |
| // compositor. If the compositor has not cleared the is_currently_animating |
| // flag, we should clear it to let the compositor respect the new value. |
| cc_transform->is_currently_animating = false; |
| |
| cc_transform->transform_changed = true; |
| property_trees->transform_tree.set_needs_update(true); |
| host.SetNeedsCommit(); |
| return true; |
| } |
| |
| bool PropertyTreeManager::DirectlyUpdatePageScaleTransform( |
| cc::LayerTreeHost& host, |
| const TransformPaintPropertyNode& transform) { |
| DCHECK(!transform.ScrollNode()); |
| |
| auto* property_trees = host.property_trees(); |
| auto* cc_transform = property_trees->transform_tree.Node( |
| transform.CcNodeId(property_trees->sequence_number)); |
| if (!cc_transform) |
| return false; |
| |
| UpdateCcTransformLocalMatrix(*cc_transform, transform); |
| SetTransformTreePageScaleFactor(&property_trees->transform_tree, |
| cc_transform); |
| cc_transform->transform_changed = true; |
| property_trees->transform_tree.set_needs_update(true); |
| host.SetNeedsCommit(); |
| return true; |
| } |
| |
| void PropertyTreeManager::DirectlySetScrollOffset( |
| cc::LayerTreeHost& host, |
| CompositorElementId element_id, |
| const gfx::ScrollOffset& scroll_offset) { |
| auto* property_trees = host.property_trees(); |
| if (property_trees->scroll_tree.SetScrollOffset(element_id, scroll_offset)) { |
| // Scroll offset animations are clobbered via |Layer::PushPropertiesTo|. |
| if (auto* layer = host.LayerByElementId(element_id)) |
| layer->SetNeedsPushProperties(); |
| host.SetNeedsCommit(); |
| } |
| } |
| |
| 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::EnsureCompositorScrollNodes( |
| const Vector<const TransformPaintPropertyNode*>& scroll_translation_nodes) { |
| DCHECK(RuntimeEnabledFeatures::ScrollUnificationEnabled()); |
| |
| for (auto* node : scroll_translation_nodes) |
| EnsureCompositorScrollNode(*node); |
| } |
| |
| void PropertyTreeManager::SetCcScrollNodeIsComposited(int cc_node_id) { |
| DCHECK(RuntimeEnabledFeatures::ScrollUnificationEnabled()); |
| GetScrollTree().Node(cc_node_id)->is_composited = true; |
| } |
| |
| 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); |
| |
| // 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(); |
| transform_tree.set_device_scale_factor(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); |
| |
| TransformPaintPropertyNode::Root().SetCcNodeId(new_sequence_number_, |
| 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(root_layer_.layer_tree_host()->device_viewport_rect()); |
| clip_node.transform_id = kRealRootNodeId; |
| |
| ClipPaintPropertyNode::Root().SetCcNodeId(new_sequence_number_, 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).GetStableId(); |
| effect_node.transform_id = kRealRootNodeId; |
| effect_node.clip_id = kSecondaryRootNodeId; |
| effect_node.render_surface_reason = cc::RenderSurfaceReason::kRoot; |
| root_layer_.SetEffectTreeIndex(effect_node.id); |
| |
| EffectPaintPropertyNode::Root().SetCcNodeId(new_sequence_number_, |
| effect_node.id); |
| SetCurrentEffectState( |
| effect_node, CcEffectType::kEffect, EffectPaintPropertyNode::Root(), |
| ClipPaintPropertyNode::Root(), TransformPaintPropertyNode::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; |
| |
| ScrollPaintPropertyNode::Root().SetCcNodeId(new_sequence_number_, |
| scroll_node.id); |
| root_layer_.SetScrollTreeIndex(scroll_node.id); |
| } |
| |
| static bool TransformsToAncestorHaveNonAxisAlignedActiveAnimation( |
| const TransformPaintPropertyNode& descendant, |
| const TransformPaintPropertyNode& ancestor) { |
| if (&descendant == &ancestor) |
| return false; |
| for (const auto* n = &descendant; n != &ancestor; n = n->UnaliasedParent()) { |
| if (n->HasActiveTransformAnimation() && |
| !n->TransformAnimationIsAxisAligned()) { |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| bool TransformsMayBe2dAxisMisaligned(const TransformPaintPropertyNode& a, |
| const TransformPaintPropertyNode& b) { |
| if (&a == &b) |
| return false; |
| const auto& translation_2d_or_matrix = |
| GeometryMapper::SourceToDestinationProjection(a, b); |
| if (!translation_2d_or_matrix.IsIdentityOr2DTranslation() && |
| !translation_2d_or_matrix.Matrix().Preserves2dAxisAlignment()) |
| return true; |
| const auto& lca = a.LowestCommonAncestor(b).Unalias(); |
| if (TransformsToAncestorHaveNonAxisAlignedActiveAnimation(a, lca) || |
| TransformsToAncestorHaveNonAxisAlignedActiveAnimation(b, lca)) |
| return true; |
| return false; |
| } |
| |
| void PropertyTreeManager::SetCurrentEffectState( |
| const cc::EffectNode& cc_effect_node, |
| CcEffectType effect_type, |
| const EffectPaintPropertyNode& effect, |
| const ClipPaintPropertyNode& clip, |
| const TransformPaintPropertyNode& transform) { |
| const auto* previous_transform = |
| effect.IsRoot() ? nullptr : current_.transform; |
| current_.effect_id = cc_effect_node.id; |
| current_.effect_type = effect_type; |
| current_.effect = &effect; |
| current_.clip = &clip; |
| current_.transform = &transform; |
| |
| if (cc_effect_node.HasRenderSurface()) { |
| current_.may_be_2d_axis_misaligned_to_render_surface = |
| EffectState::kAligned; |
| current_.contained_by_non_render_surface_synthetic_rounded_clip = false; |
| } else { |
| if (current_.may_be_2d_axis_misaligned_to_render_surface == |
| EffectState::kAligned && |
| previous_transform != current_.transform) { |
| current_.may_be_2d_axis_misaligned_to_render_surface = |
| EffectState::kUnknown; |
| } |
| current_.contained_by_non_render_surface_synthetic_rounded_clip |= |
| (effect_type & CcEffectType::kSyntheticForNonTrivialClip); |
| } |
| } |
| |
| // TODO(crbug.com/504464): Remove this when move render surface decision logic |
| // into cc compositor thread. |
| void PropertyTreeManager::SetCurrentEffectRenderSurfaceReason( |
| cc::RenderSurfaceReason reason) { |
| auto* effect = GetEffectTree().Node(current_.effect_id); |
| effect->render_surface_reason = reason; |
| } |
| |
| int PropertyTreeManager::EnsureCompositorTransformNode( |
| const TransformPaintPropertyNode& transform_node) { |
| int id = transform_node.CcNodeId(new_sequence_number_); |
| if (id != kInvalidNodeId) { |
| DCHECK(GetTransformTree().Node(id)); |
| return id; |
| } |
| |
| DCHECK(transform_node.Parent()); |
| int parent_id = |
| EnsureCompositorTransformNode(transform_node.Parent()->Unalias()); |
| id = GetTransformTree().Insert(cc::TransformNode(), parent_id); |
| |
| cc::TransformNode& compositor_node = *GetTransformTree().Node(id); |
| UpdateCcTransformLocalMatrix(compositor_node, transform_node); |
| compositor_node.transform_changed = transform_node.NodeChangeAffectsRaster(); |
| compositor_node.flattens_inherited_transform = |
| transform_node.FlattensInheritedTransform(); |
| compositor_node.sorting_context_id = transform_node.RenderingContextId(); |
| compositor_node.delegates_to_parent_for_backface = |
| transform_node.DelegatesToParentForBackface(); |
| |
| if (transform_node.IsAffectedByOuterViewportBoundsDelta()) { |
| compositor_node.moved_by_outer_viewport_bounds_delta_y = true; |
| GetTransformTree().AddNodeAffectedByOuterViewportBoundsDelta(id); |
| } |
| |
| compositor_node.in_subtree_of_page_scale_layer = |
| transform_node.IsInSubtreeOfPageScale(); |
| |
| compositor_node.will_change_transform = |
| transform_node.RequiresCompositingForWillChangeTransform() && |
| // cc assumes preference of performance over raster quality for |
| // will-change:transform, but for SVG we still prefer raster quality, so |
| // don't pass will-change:transform to cc for SVG. |
| // TODO(crbug.com/1186020): find a better way to handle this. |
| !transform_node.IsForSVGChild(); |
| |
| if (const auto* sticky_constraint = transform_node.GetStickyConstraint()) { |
| cc::StickyPositionNodeData& sticky_data = |
| GetTransformTree().EnsureStickyPositionData(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); |
| const auto& scroll_ancestor_compositor_node = |
| *GetScrollTree().Node(sticky_data.scroll_ancestor); |
| if (scroll_ancestor_compositor_node.scrolls_outer_viewport) |
| GetTransformTree().AddNodeAffectedByOuterViewportBoundsDelta(id); |
| if (auto 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 (auto 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; |
| } |
| } |
| |
| auto 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()) { |
| 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->HasRenderSurface() && |
| current_cc_effect->transform_id == parent_id && |
| transform_node.FlattensInheritedTransform()) { |
| const auto* parent = transform_node.UnaliasedParent(); |
| if (parent && parent->RenderingContextId() && |
| !parent->FlattensInheritedTransform()) { |
| current_cc_effect->render_surface_reason = |
| cc::RenderSurfaceReason::k3dTransformFlattening; |
| } |
| } |
| |
| compositor_node.visible_frame_element_id = |
| transform_node.GetVisibleFrameElementId(); |
| |
| // Attach the index of the nearest parent node associated with a frame. |
| int parent_frame_id = kInvalidNodeId; |
| if (const auto* parent = transform_node.UnaliasedParent()) { |
| if (parent->IsFramePaintOffsetTranslation()) { |
| parent_frame_id = parent_id; |
| } else { |
| const auto* parent_compositor_node = GetTransformTree().Node(parent_id); |
| DCHECK(parent_compositor_node); |
| parent_frame_id = parent_compositor_node->parent_frame_id; |
| } |
| } |
| compositor_node.parent_frame_id = parent_frame_id; |
| |
| transform_node.SetCcNodeId(new_sequence_number_, id); |
| GetTransformTree().set_needs_update(true); |
| |
| return id; |
| } |
| |
| int PropertyTreeManager::EnsureCompositorPageScaleTransformNode( |
| const TransformPaintPropertyNode& node) { |
| DCHECK(!node.IsInSubtreeOfPageScale()); |
| int id = EnsureCompositorTransformNode(node); |
| DCHECK(GetTransformTree().Node(id)); |
| cc::TransformNode& compositor_node = *GetTransformTree().Node(id); |
| SetTransformTreePageScaleFactor(&GetTransformTree(), &compositor_node); |
| GetTransformTree().set_needs_update(true); |
| return id; |
| } |
| |
| int PropertyTreeManager::EnsureCompositorClipNode( |
| const ClipPaintPropertyNode& clip_node) { |
| int id = clip_node.CcNodeId(new_sequence_number_); |
| if (id != kInvalidNodeId) { |
| DCHECK(GetClipTree().Node(id)); |
| return id; |
| } |
| |
| DCHECK(clip_node.UnaliasedParent()); |
| int parent_id = EnsureCompositorClipNode(*clip_node.UnaliasedParent()); |
| id = GetClipTree().Insert(cc::ClipNode(), parent_id); |
| |
| cc::ClipNode& compositor_node = *GetClipTree().Node(id); |
| |
| compositor_node.clip = clip_node.PixelSnappedClipRect().Rect(); |
| compositor_node.transform_id = |
| EnsureCompositorTransformNode(clip_node.LocalTransformSpace().Unalias()); |
| compositor_node.clip_type = cc::ClipNode::ClipType::APPLIES_LOCAL_CLIP; |
| |
| clip_node.SetCcNodeId(new_sequence_number_, id); |
| GetClipTree().set_needs_update(true); |
| return id; |
| } |
| |
| void PropertyTreeManager::CreateCompositorScrollNode( |
| const ScrollPaintPropertyNode& scroll_node, |
| const cc::TransformNode& scroll_offset_translation) { |
| DCHECK(!GetScrollTree().Node(scroll_node.CcNodeId(new_sequence_number_))); |
| |
| int parent_id = scroll_node.Parent()->CcNodeId(new_sequence_number_); |
| // 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(GetScrollTree().Node(parent_id)); |
| 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.prevent_viewport_scrolling_from_inner = |
| scroll_node.PreventViewportScrollingFromInner(); |
| |
| 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::Type>( |
| scroll_node.OverscrollBehaviorX()), |
| static_cast<cc::OverscrollBehavior::Type>( |
| 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; |
| |
| scroll_node.SetCcNodeId(new_sequence_number_, id); |
| |
| GetScrollTree().SetScrollOffset(compositor_element_id, |
| scroll_offset_translation.scroll_offset); |
| } |
| |
| int PropertyTreeManager::EnsureCompositorScrollNode( |
| const TransformPaintPropertyNode& scroll_offset_translation) { |
| const auto* scroll_node = scroll_offset_translation.ScrollNode(); |
| DCHECK(scroll_node); |
| EnsureCompositorTransformNode(scroll_offset_translation); |
| int id = scroll_node->CcNodeId(new_sequence_number_); |
| DCHECK(GetScrollTree().Node(id)); |
| return id; |
| } |
| |
| int PropertyTreeManager::EnsureCompositorInnerScrollNode( |
| const TransformPaintPropertyNode& scroll_offset_translation) { |
| int node_id = EnsureCompositorScrollNode(scroll_offset_translation); |
| GetScrollTree().Node(node_id)->scrolls_inner_viewport = true; |
| return node_id; |
| } |
| |
| int PropertyTreeManager::EnsureCompositorOuterScrollNode( |
| const TransformPaintPropertyNode& scroll_offset_translation) { |
| int node_id = EnsureCompositorScrollNode(scroll_offset_translation); |
| GetScrollTree().Node(node_id)->scrolls_outer_viewport = true; |
| return node_id; |
| } |
| |
| void PropertyTreeManager::EmitClipMaskLayer() { |
| cc::EffectNode* mask_isolation = GetEffectTree().Node(current_.effect_id); |
| DCHECK(mask_isolation); |
| bool needs_layer = |
| !pending_synthetic_mask_layers_.Contains(mask_isolation->id) && |
| mask_isolation->mask_filter_info.IsEmpty(); |
| |
| CompositorElementId mask_isolation_id, mask_effect_id; |
| SynthesizedClip& clip = client_.CreateOrReuseSynthesizedClipLayer( |
| *current_.clip, *current_.transform, needs_layer, mask_isolation_id, |
| mask_effect_id); |
| |
| // Now we know the actual mask_isolation.stable_id. |
| // This overrides the stable_id set in PopulateCcEffectNode() if the |
| // backdrop effect was moved up to |mask_isolation|. |
| mask_isolation->stable_id = mask_isolation_id.GetStableId(); |
| |
| if (!needs_layer) |
| return; |
| |
| cc::EffectNode& mask_effect = *GetEffectTree().Node( |
| GetEffectTree().Insert(cc::EffectNode(), current_.effect_id)); |
| // The address of mask_isolation may have changed when we insert |
| // |mask_effect| into the tree. |
| mask_isolation = GetEffectTree().Node(current_.effect_id); |
| |
| mask_effect.stable_id = mask_effect_id.GetStableId(); |
| mask_effect.clip_id = mask_isolation->clip_id; |
| mask_effect.blend_mode = SkBlendMode::kDstIn; |
| |
| cc::PictureLayer* mask_layer = clip.Layer(); |
| |
| layer_list_builder_.Add(mask_layer); |
| mask_layer->set_property_tree_sequence_number( |
| root_layer_.property_tree_sequence_number()); |
| mask_layer->SetTransformTreeIndex( |
| EnsureCompositorTransformNode(*current_.transform)); |
| // 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( |
| current_.transform->NearestScrollTranslationNode()); |
| mask_layer->SetScrollTreeIndex(scroll_id); |
| mask_layer->SetClipTreeIndex(mask_effect.clip_id); |
| mask_layer->SetEffectTreeIndex(mask_effect.id); |
| |
| if (!mask_isolation->backdrop_filters.IsEmpty()) { |
| mask_layer->SetIsBackdropFilterMask(true); |
| auto element_id = CompositorElementIdFromUniqueObjectId( |
| mask_effect.stable_id, CompositorElementIdNamespace::kEffectMask); |
| mask_layer->SetElementId(element_id); |
| mask_isolation->backdrop_mask_element_id = element_id; |
| } |
| } |
| |
| void PropertyTreeManager::CloseCcEffect() { |
| DCHECK(effect_stack_.size()); |
| const auto& previous_state = effect_stack_.back(); |
| |
| // A backdrop effect (exotic blending or backdrop filter) that is masked by a |
| // synthesized clip must have its effect to the outermost synthesized clip. |
| // These operations need access to the backdrop of the enclosing effect. With |
| // the isolation for a synthesized clip, a blank backdrop will be seen. |
| // Therefore the backdrop effect 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->HasBackdropEffect(); |
| |
| // 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(); |
| |
| if (IsCurrentCcEffectSynthetic()) |
| pending_synthetic_mask_layers_.erase(current_.effect_id); |
| |
| current_ = previous_state; |
| effect_stack_.pop_back(); |
| |
| if (clear_synthetic_effects) { |
| while (IsCurrentCcEffectSynthetic()) |
| CloseCcEffect(); |
| } |
| } |
| |
| int PropertyTreeManager::SwitchToEffectNodeWithSynthesizedClip( |
| const EffectPaintPropertyNode& next_effect, |
| const ClipPaintPropertyNode& next_clip, |
| bool layer_draws_content) { |
| // 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 = |
| current_.effect->LowestCommonAncestor(next_effect).Unalias(); |
| while (current_.effect != &ancestor) |
| CloseCcEffect(); |
| |
| BuildEffectNodesRecursively(next_effect); |
| SynthesizeCcEffectsForClipsIfNeeded(next_clip, /*next_effect*/ nullptr); |
| |
| if (layer_draws_content) |
| pending_synthetic_mask_layers_.clear(); |
| |
| 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->UnaliasedParent()) { |
| if (node == &find) |
| return true; |
| } |
| return false; |
| } |
| |
| bool PropertyTreeManager::EffectStateMayBe2dAxisMisalignedToRenderSurface( |
| EffectState& state, |
| wtf_size_t index) { |
| if (state.may_be_2d_axis_misaligned_to_render_surface == |
| EffectState::kUnknown) { |
| // The root effect has render surface, so it's always kAligned. |
| DCHECK_NE(0u, index); |
| if (EffectStateMayBe2dAxisMisalignedToRenderSurface( |
| effect_stack_[index - 1], index - 1)) { |
| state.may_be_2d_axis_misaligned_to_render_surface = |
| EffectState::kMisaligned; |
| } else { |
| state.may_be_2d_axis_misaligned_to_render_surface = |
| TransformsMayBe2dAxisMisaligned(*effect_stack_[index - 1].transform, |
| *current_.transform) |
| ? EffectState::kMisaligned |
| : EffectState::kAligned; |
| } |
| } |
| return state.may_be_2d_axis_misaligned_to_render_surface == |
| EffectState::kMisaligned; |
| } |
| |
| bool PropertyTreeManager::CurrentEffectMayBe2dAxisMisalignedToRenderSurface() { |
| // |current_| is virtually the top of effect_stack_. |
| return EffectStateMayBe2dAxisMisalignedToRenderSurface(current_, |
| effect_stack_.size()); |
| } |
| |
| PropertyTreeManager::CcEffectType PropertyTreeManager::SyntheticEffectType( |
| const ClipPaintPropertyNode& clip) { |
| unsigned effect_type = CcEffectType::kEffect; |
| if (clip.PixelSnappedClipRect().IsRounded() || clip.ClipPath()) |
| effect_type |= CcEffectType::kSyntheticForNonTrivialClip; |
| |
| // Cc requires that a rectangluar clip is 2d-axis-aligned with the render |
| // surface to correctly apply the clip. |
| if (CurrentEffectMayBe2dAxisMisalignedToRenderSurface() || |
| TransformsMayBe2dAxisMisaligned(clip.LocalTransformSpace().Unalias(), |
| *current_.transform)) |
| effect_type |= CcEffectType::kSyntheticFor2dAxisAlignment; |
| return static_cast<CcEffectType>(effect_type); |
| } |
| |
| void PropertyTreeManager::ForceRenderSurfaceIfSyntheticRoundedCornerClip( |
| PropertyTreeManager::EffectState& state) { |
| if (state.effect_type & CcEffectType::kSyntheticForNonTrivialClip) { |
| auto& effect_node = *GetEffectTree().Node(state.effect_id); |
| effect_node.render_surface_reason = cc::RenderSurfaceReason::kRoundedCorner; |
| } |
| } |
| |
| bool PropertyTreeManager::SupportsShaderBasedRoundedCorner( |
| const ClipPaintPropertyNode& clip, |
| PropertyTreeManager::CcEffectType type, |
| const EffectPaintPropertyNode* next_effect) { |
| if (type & CcEffectType::kSyntheticFor2dAxisAlignment) |
| return false; |
| |
| if (clip.ClipPath()) |
| return false; |
| |
| // Don't use shader based rounded corner if the next effect has backdrop |
| // filter and the clip is in different transform space, because we will use |
| // the effect's transform space for the mask isolation effect node. |
| if (next_effect && !next_effect->BackdropFilter().IsEmpty() && |
| &next_effect->LocalTransformSpace() != &clip.LocalTransformSpace()) |
| return false; |
| |
| auto WidthAndHeightAreTheSame = [](const FloatSize& size) { |
| return size.Width() == size.Height(); |
| }; |
| |
| const FloatRoundedRect::Radii& radii = clip.PixelSnappedClipRect().GetRadii(); |
| if (!WidthAndHeightAreTheSame(radii.TopLeft()) || |
| !WidthAndHeightAreTheSame(radii.TopRight()) || |
| !WidthAndHeightAreTheSame(radii.BottomRight()) || |
| !WidthAndHeightAreTheSame(radii.BottomLeft())) { |
| return false; |
| } |
| |
| // Rounded corners that differ are not supported by the CALayerOverlay system |
| // on Mac. Instead of letting it fall back to the (worse for memory and |
| // battery) non-CALayerOverlay system for such cases, fall back to a |
| // non-shader border-radius mask for the effect node. |
| #if defined(OS_MAC) |
| if (radii.TopLeft() != radii.TopRight() || |
| radii.TopLeft() != radii.BottomRight() || |
| radii.TopLeft() != radii.BottomLeft()) { |
| return false; |
| } |
| #endif |
| |
| return true; |
| } |
| |
| int PropertyTreeManager::SynthesizeCcEffectsForClipsIfNeeded( |
| const ClipPaintPropertyNode& target_clip, |
| const EffectPaintPropertyNode* next_effect) { |
| int backdrop_effect_clip_id = cc::ClipTree::kInvalidNodeId; |
| bool should_realize_backdrop_effect = false; |
| if (next_effect && next_effect->HasBackdropEffect()) { |
| // Exit all synthetic effect node if the next child has backdrop effect |
| // (exotic blending mode or backdrop filter) 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 backdrop |
| // effect, in order to define the scope of the backdrop. |
| SetCurrentEffectRenderSurfaceReason( |
| cc::RenderSurfaceReason::kBackdropScope); |
| should_realize_backdrop_effect = true; |
| backdrop_effect_clip_id = EnsureCompositorClipNode(target_clip); |
| } else { |
| // Exit synthetic effects until there are no more synthesized clips below |
| // our lowest common ancestor. |
| const auto& lca = |
| current_.clip->LowestCommonAncestor(target_clip).Unalias(); |
| while (current_.clip != &lca) { |
| if (!IsCurrentCcEffectSynthetic()) { |
| // This happens in pre-CompositeAfterPaint due to some clip-escaping |
| // corner cases that are very difficult to fix in legacy architecture. |
| // In CompositeAfterPaint this should never happen. |
| if (RuntimeEnabledFeatures::CompositeAfterPaintEnabled()) |
| NOTREACHED(); |
| return cc::EffectTree::kInvalidNodeId; |
| } |
| 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; |
| } |
| } |
| |
| struct PendingClip { |
| const ClipPaintPropertyNode* clip; |
| CcEffectType type; |
| }; |
| Vector<PendingClip> pending_clips; |
| const ClipPaintPropertyNode* clip = &target_clip; |
| for (; clip && clip != current_.clip; clip = clip->UnaliasedParent()) { |
| if (auto type = SyntheticEffectType(*clip)) |
| pending_clips.emplace_back(PendingClip{clip, type}); |
| } |
| |
| if (!clip) { |
| // This means that current_.clip is not an ancestor of the target clip. |
| // which happens in pre-CompositeAfterPaint due to some clip-escaping |
| // corner cases that are very difficult to fix in legacy architecture. |
| // In CompositeAfterPaint this should never happen. |
| if (RuntimeEnabledFeatures::CompositeAfterPaintEnabled()) |
| NOTREACHED(); |
| return cc::EffectTree::kInvalidNodeId; |
| } |
| |
| if (pending_clips.IsEmpty()) |
| return cc::EffectTree::kInvalidNodeId; |
| |
| int cc_effect_id_for_backdrop_effect = cc::EffectTree::kInvalidNodeId; |
| for (auto i = pending_clips.size(); i--;) { |
| const auto& pending_clip = pending_clips[i]; |
| int clip_id = backdrop_effect_clip_id; |
| |
| // 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) { |
| if (clip_id == cc::ClipTree::kInvalidNodeId) |
| 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(). |
| if (SupportsShaderBasedRoundedCorner(*pending_clip.clip, |
| pending_clip.type, next_effect)) { |
| synthetic_effect.mask_filter_info = gfx::MaskFilterInfo( |
| gfx::RRectF(pending_clip.clip->PixelSnappedClipRect())); |
| synthetic_effect.is_fast_rounded_corner = true; |
| |
| // Nested rounded corner clips need to force render surfaces for |
| // clips other than the leaf ones, because the compositor doesn't |
| // know how to apply two rounded clips to the same draw quad. |
| if (current_.contained_by_non_render_surface_synthetic_rounded_clip) { |
| ForceRenderSurfaceIfSyntheticRoundedCornerClip(current_); |
| for (auto effect_it = effect_stack_.rbegin(); |
| effect_it != effect_stack_.rend(); ++effect_it) { |
| auto& effect_node = *GetEffectTree().Node(effect_it->effect_id); |
| if (effect_node.HasRenderSurface()) |
| break; |
| ForceRenderSurfaceIfSyntheticRoundedCornerClip(*effect_it); |
| } |
| } |
| } else { |
| synthetic_effect.render_surface_reason = |
| pending_clip.clip->PixelSnappedClipRect().IsRounded() |
| ? cc::RenderSurfaceReason::kRoundedCorner |
| : cc::RenderSurfaceReason::kClipPath; |
| } |
| pending_synthetic_mask_layers_.insert(synthetic_effect.id); |
| } |
| |
| if (pending_clip.type & CcEffectType::kSyntheticFor2dAxisAlignment) { |
| synthetic_effect.stable_id = |
| CompositorElementIdFromUniqueObjectId(NewUniqueObjectId()) |
| .GetStableId(); |
| synthetic_effect.render_surface_reason = |
| cc::RenderSurfaceReason::kClipAxisAlignment; |
| // The clip of the synthetic effect is the parent of the clip, so that |
| // the clip itself will be applied in the render surface. |
| DCHECK(pending_clip.clip->UnaliasedParent()); |
| clip_id = EnsureCompositorClipNode(*pending_clip.clip->UnaliasedParent()); |
| } |
| |
| const TransformPaintPropertyNode* transform = nullptr; |
| if (should_realize_backdrop_effect) { |
| // Move the effect node containing backdrop effects up to the outermost |
| // synthetic effect to ensure the backdrop effects can access the correct |
| // backdrop. |
| DCHECK(next_effect); |
| DCHECK_EQ(cc_effect_id_for_backdrop_effect, |
| cc::EffectTree::kInvalidNodeId); |
| transform = &next_effect->LocalTransformSpace().Unalias(); |
| PopulateCcEffectNode(synthetic_effect, *next_effect, clip_id); |
| cc_effect_id_for_backdrop_effect = synthetic_effect.id; |
| should_realize_backdrop_effect = false; |
| } else { |
| transform = &pending_clip.clip->LocalTransformSpace().Unalias(); |
| synthetic_effect.clip_id = clip_id; |
| } |
| |
| synthetic_effect.transform_id = EnsureCompositorTransformNode(*transform); |
| synthetic_effect.double_sided = !transform->IsBackfaceHidden(); |
| |
| effect_stack_.emplace_back(current_); |
| SetCurrentEffectState(synthetic_effect, pending_clip.type, *current_.effect, |
| *pending_clip.clip, *transform); |
| } |
| |
| return cc_effect_id_for_backdrop_effect; |
| } |
| |
| void PropertyTreeManager::BuildEffectNodesRecursively( |
| const EffectPaintPropertyNode& next_effect) { |
| if (&next_effect == current_.effect) |
| return; |
| |
| DCHECK(next_effect.UnaliasedParent()); |
| BuildEffectNodesRecursively(*next_effect.UnaliasedParent()); |
| DCHECK_EQ(next_effect.UnaliasedParent(), current_.effect); |
| |
| bool has_multiple_groups = false; |
| if (GetEffectTree().Node(next_effect.CcNodeId(new_sequence_number_))) { |
| if (RuntimeEnabledFeatures::CompositeAfterPaintEnabled()) { |
| // TODO(crbug.com/1064341): We have to allow one blink effect node to |
| // apply to multiple groups in block fragments (multicol, etc.) due to |
| // the current FragmentClip implementation. This can only be fixed by |
| // LayoutNG block fragments. For now we'll create multiple cc effect |
| // nodes in the case. |
| has_multiple_groups = true; |
| } else { |
| NOTREACHED() << "Malformed paint artifact. Paint chunks under the same" |
| " effect should be contiguous."; |
| } |
| } |
| |
| int real_effect_node_id = cc::EffectTree::kInvalidNodeId; |
| int output_clip_id = 0; |
| const ClipPaintPropertyNode* output_clip = nullptr; |
| if (next_effect.OutputClip()) { |
| output_clip = &next_effect.OutputClip()->Unalias(); |
| real_effect_node_id = |
| SynthesizeCcEffectsForClipsIfNeeded(*output_clip, &next_effect); |
| output_clip_id = EnsureCompositorClipNode(*output_clip); |
| } else { |
| // If we don't have an output clip, then we'll use the clip of the last |
| // non-synthetic effect. This means we should close all synthetic effects |
| // on the stack first. |
| while (IsCurrentCcEffectSynthetic()) |
| CloseCcEffect(); |
| |
| output_clip = current_.clip; |
| DCHECK(output_clip); |
| output_clip_id = GetEffectTree().Node(current_.effect_id)->clip_id; |
| DCHECK_EQ(output_clip_id, EnsureCompositorClipNode(*output_clip)); |
| } |
| |
| const auto& transform = next_effect.LocalTransformSpace().Unalias(); |
| auto& effect_node = *GetEffectTree().Node( |
| GetEffectTree().Insert(cc::EffectNode(), current_.effect_id)); |
| if (real_effect_node_id == cc::EffectTree::kInvalidNodeId) { |
| real_effect_node_id = effect_node.id; |
| PopulateCcEffectNode(effect_node, next_effect, output_clip_id); |
| } else { |
| // We have used the outermost synthetic effect for |next_effect| in |
| // SynthesizeCcEffectsForClipsIfNeeded(), so |effect_node| is just a dummy |
| // node to mark the end of continuous synthetic effects for |next_effect|. |
| effect_node.clip_id = output_clip_id; |
| effect_node.transform_id = EnsureCompositorTransformNode(transform); |
| effect_node.stable_id = next_effect.GetCompositorElementId().GetStableId(); |
| } |
| |
| if (!has_multiple_groups) |
| next_effect.SetCcNodeId(new_sequence_number_, real_effect_node_id); |
| |
| CompositorElementId compositor_element_id = |
| next_effect.GetCompositorElementId(); |
| if (compositor_element_id && !has_multiple_groups) { |
| DCHECK(!property_trees_.element_id_to_effect_node_index.contains( |
| compositor_element_id)); |
| property_trees_.element_id_to_effect_node_index[compositor_element_id] = |
| real_effect_node_id; |
| } |
| |
| effect_stack_.emplace_back(current_); |
| SetCurrentEffectState(effect_node, CcEffectType::kEffect, next_effect, |
| *output_clip, transform); |
| } |
| |
| static cc::RenderSurfaceReason RenderSurfaceReasonForEffect( |
| const EffectPaintPropertyNode& effect) { |
| if (!effect.Filter().IsEmpty()) |
| return cc::RenderSurfaceReason::kFilter; |
| if (effect.HasActiveFilterAnimation()) |
| return cc::RenderSurfaceReason::kFilterAnimation; |
| if (!effect.BackdropFilter().IsEmpty()) |
| return cc::RenderSurfaceReason::kBackdropFilter; |
| if (effect.HasActiveBackdropFilterAnimation()) |
| return cc::RenderSurfaceReason::kBackdropFilterAnimation; |
| if (effect.BlendMode() != SkBlendMode::kSrcOver && |
| // For optimization, we will set render surface reason for DstIn later in |
| // PaintArtifactCompositor::UpdateRenderSurfaceForEffects() if it controls |
| // more than one layer. |
| effect.BlendMode() != SkBlendMode::kDstIn) { |
| return cc::RenderSurfaceReason::kBlendMode; |
| } |
| return cc::RenderSurfaceReason::kNone; |
| } |
| |
| void PropertyTreeManager::PopulateCcEffectNode( |
| cc::EffectNode& effect_node, |
| const EffectPaintPropertyNode& effect, |
| int output_clip_id) { |
| effect_node.stable_id = effect.GetCompositorElementId().GetStableId(); |
| effect_node.clip_id = output_clip_id; |
| effect_node.render_surface_reason = RenderSurfaceReasonForEffect(effect); |
| effect_node.opacity = effect.Opacity(); |
| const auto& transform = effect.LocalTransformSpace().Unalias(); |
| if (effect.GetColorFilter() != kColorFilterNone) { |
| // Currently color filter is only used by SVG masks. |
| // We are cutting corner here by support only specific configuration. |
| DCHECK_EQ(effect.GetColorFilter(), kColorFilterLuminanceToAlpha); |
| DCHECK_EQ(effect.BlendMode(), SkBlendMode::kDstIn); |
| DCHECK(effect.Filter().IsEmpty()); |
| effect_node.filters.Append(cc::FilterOperation::CreateReferenceFilter( |
| sk_make_sp<ColorFilterPaintFilter>(SkLumaColorFilter::Make(), |
| nullptr))); |
| effect_node.blend_mode = SkBlendMode::kDstIn; |
| } else { |
| effect_node.transform_id = EnsureCompositorTransformNode(transform); |
| if (effect.HasBackdropEffect()) { |
| // We never have backdrop effect and filter on the same effect node. |
| DCHECK(effect.Filter().IsEmpty()); |
| effect_node.backdrop_filters = |
| effect.BackdropFilter().AsCcFilterOperations(); |
| effect_node.backdrop_filter_bounds = effect.BackdropFilterBounds(); |
| effect_node.blend_mode = effect.BlendMode(); |
| effect_node.backdrop_mask_element_id = effect.BackdropMaskElementId(); |
| } else { |
| effect_node.filters = effect.Filter().AsCcFilterOperations(); |
| } |
| } |
| effect_node.double_sided = !transform.IsBackfaceHidden(); |
| effect_node.effect_changed = effect.NodeChangeAffectsRaster(); |
| } |
| |
| } // namespace blink |