| // 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/paint_artifact_compositor.h" |
| |
| #include <memory> |
| #include <utility> |
| |
| #include "base/logging.h" |
| #include "cc/document_transition/document_transition_request.h" |
| #include "cc/layers/scrollbar_layer_base.h" |
| #include "cc/paint/display_item_list.h" |
| #include "cc/trees/effect_node.h" |
| #include "cc/trees/layer_tree_host.h" |
| #include "cc/trees/mutator_host.h" |
| #include "third_party/blink/public/platform/platform.h" |
| #include "third_party/blink/renderer/platform/geometry/geometry_as_json.h" |
| #include "third_party/blink/renderer/platform/graphics/compositing/content_layer_client_impl.h" |
| #include "third_party/blink/renderer/platform/graphics/compositing/paint_chunks_to_cc_layer.h" |
| #include "third_party/blink/renderer/platform/graphics/graphics_context.h" |
| #include "third_party/blink/renderer/platform/graphics/graphics_layer.h" |
| #include "third_party/blink/renderer/platform/graphics/paint/clip_paint_property_node.h" |
| #include "third_party/blink/renderer/platform/graphics/paint/display_item.h" |
| #include "third_party/blink/renderer/platform/graphics/paint/foreign_layer_display_item.h" |
| #include "third_party/blink/renderer/platform/graphics/paint/geometry_mapper.h" |
| #include "third_party/blink/renderer/platform/graphics/paint/paint_artifact.h" |
| #include "third_party/blink/renderer/platform/graphics/paint/paint_chunk_subset.h" |
| #include "third_party/blink/renderer/platform/graphics/paint/paint_flags.h" |
| #include "third_party/blink/renderer/platform/graphics/paint/property_tree_state.h" |
| #include "third_party/blink/renderer/platform/graphics/paint/raster_invalidation_tracking.h" |
| #include "third_party/blink/renderer/platform/graphics/paint/scroll_paint_property_node.h" |
| #include "third_party/blink/renderer/platform/graphics/paint/scrollbar_display_item.h" |
| #include "third_party/blink/renderer/platform/graphics/paint/transform_paint_property_node.h" |
| #include "third_party/blink/renderer/platform/runtime_enabled_features.h" |
| #include "third_party/blink/renderer/platform/wtf/hash_map.h" |
| #include "third_party/blink/renderer/platform/wtf/hash_set.h" |
| #include "ui/gfx/geometry/rect.h" |
| |
| namespace blink { |
| |
| // cc property trees make use of a sequence number to identify when tree |
| // topology changes. For now we naively increment the sequence number each time |
| // we update the property trees. We should explore optimizing our management of |
| // the sequence number through the use of a dirty bit or similar. See |
| // http://crbug.com/692842#c4. |
| static int g_s_property_tree_sequence_number = 1; |
| |
| PaintArtifactCompositor::PaintArtifactCompositor( |
| base::WeakPtr<CompositorScrollCallbacks> scroll_callbacks) |
| : scroll_callbacks_(std::move(scroll_callbacks)), |
| tracks_raster_invalidations_(VLOG_IS_ON(3)) { |
| root_layer_ = cc::Layer::Create(); |
| } |
| |
| PaintArtifactCompositor::~PaintArtifactCompositor() {} |
| |
| void PaintArtifactCompositor::SetTracksRasterInvalidations(bool should_track) { |
| tracks_raster_invalidations_ = should_track || VLOG_IS_ON(3); |
| for (auto& client : content_layer_clients_) |
| client->GetRasterInvalidator().SetTracksRasterInvalidations(should_track); |
| } |
| |
| void PaintArtifactCompositor::WillBeRemovedFromFrame() { |
| root_layer_->RemoveAllChildren(); |
| } |
| |
| void PaintArtifactCompositor::SetPrefersLCDText(bool prefers) { |
| if (prefers_lcd_text_ == prefers) |
| return; |
| SetNeedsUpdate(); |
| prefers_lcd_text_ = prefers; |
| } |
| |
| std::unique_ptr<JSONArray> PaintArtifactCompositor::GetPendingLayersAsJSON() |
| const { |
| std::unique_ptr<JSONArray> result = std::make_unique<JSONArray>(); |
| for (const PendingLayer& pending_layer : pending_layers_) |
| result->PushObject(pending_layer.ToJSON()); |
| return result; |
| } |
| |
| // Get a JSON representation of what layers exist for this PAC. |
| std::unique_ptr<JSONObject> PaintArtifactCompositor::GetLayersAsJSON( |
| LayerTreeFlags flags) const { |
| if (RuntimeEnabledFeatures::CompositeAfterPaintEnabled() && |
| !tracks_raster_invalidations_) { |
| flags &= ~(kLayerTreeIncludesInvalidations | |
| kLayerTreeIncludesDetailedInvalidations); |
| } |
| |
| LayersAsJSON layers_as_json(flags); |
| for (const auto& layer : root_layer_->children()) { |
| const LayerAsJSONClient* json_client = nullptr; |
| const TransformPaintPropertyNode* transform = nullptr; |
| for (const auto& client : content_layer_clients_) { |
| if (&client->Layer() == layer.get()) { |
| json_client = client.get(); |
| transform = &client->State().Transform(); |
| break; |
| } |
| } |
| if (!RuntimeEnabledFeatures::CompositeAfterPaintEnabled() && !json_client) { |
| // The cc::Layer may come from a GraphicsLayer. |
| for (const auto& pending_layer : pending_layers_) { |
| if (pending_layer.GetCompositingType() == |
| PendingLayer::kPreCompositedLayer) { |
| if (&pending_layer.GetGraphicsLayer()->CcLayer() == layer.get()) { |
| json_client = pending_layer.GetGraphicsLayer(); |
| break; |
| } |
| } |
| } |
| } |
| if (!transform) { |
| for (const auto& pending_layer : pending_layers_) { |
| if (pending_layer.GetPropertyTreeState().Transform().CcNodeId( |
| layer->property_tree_sequence_number()) == |
| layer->transform_tree_index()) { |
| transform = &pending_layer.GetPropertyTreeState().Transform(); |
| break; |
| } |
| } |
| } |
| DCHECK(transform); |
| layers_as_json.AddLayer(*layer, *transform, json_client); |
| } |
| return layers_as_json.Finalize(); |
| } |
| |
| scoped_refptr<cc::Layer> PaintArtifactCompositor::WrappedCcLayerForPendingLayer( |
| const PendingLayer& pending_layer) { |
| if (pending_layer.GetCompositingType() != PendingLayer::kForeignLayer && |
| pending_layer.GetCompositingType() != PendingLayer::kPreCompositedLayer) |
| return nullptr; |
| |
| cc::Layer* layer = nullptr; |
| gfx::Vector2dF layer_offset; |
| if (pending_layer.GetCompositingType() == PendingLayer::kPreCompositedLayer) { |
| DCHECK(pending_layer.GetGraphicsLayer()); |
| DCHECK(!pending_layer.GetGraphicsLayer()->ShouldCreateLayersAfterPaint()); |
| layer = &pending_layer.GetGraphicsLayer()->CcLayer(); |
| layer_offset = gfx::Vector2dF(ToGfxVector2d( |
| pending_layer.GetGraphicsLayer()->GetOffsetFromTransformNode())); |
| } else { |
| DCHECK_EQ(pending_layer.GetCompositingType(), PendingLayer::kForeignLayer); |
| // UpdateTouchActionRects() depends on the layer's offset, but when the |
| // layer's offset changes, we do not call SetNeedsUpdate() (this is an |
| // optimization because the update would only cause an extra commit) |
| // This is only OK if the ForeignLayer doesn't have hit test data. |
| DCHECK(!pending_layer.FirstPaintChunk().hit_test_data); |
| const auto& foreign_layer_display_item = |
| To<ForeignLayerDisplayItem>(pending_layer.FirstDisplayItem()); |
| layer = foreign_layer_display_item.GetLayer(); |
| layer_offset = gfx::Vector2dF( |
| foreign_layer_display_item.VisualRect().OffsetFromOrigin()); |
| } |
| layer->SetOffsetToTransformParent( |
| layer_offset + pending_layer.OffsetOfDecompositedTransforms()); |
| return layer; |
| } |
| |
| const TransformPaintPropertyNode& |
| PaintArtifactCompositor::NearestScrollTranslationForLayer( |
| const PendingLayer& pending_layer) { |
| if (pending_layer.GetCompositingType() == PendingLayer::kScrollHitTestLayer) |
| return pending_layer.ScrollTranslationForScrollHitTestLayer(); |
| |
| const auto& transform = pending_layer.GetPropertyTreeState().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. |
| return transform.NearestScrollTranslationNode(); |
| } |
| |
| scoped_refptr<cc::Layer> |
| PaintArtifactCompositor::ScrollHitTestLayerForPendingLayer( |
| const PendingLayer& pending_layer) { |
| if (pending_layer.GetCompositingType() != PendingLayer::kScrollHitTestLayer) |
| return nullptr; |
| |
| // We shouldn't decomposite scroll transform nodes. |
| DCHECK_EQ(gfx::Vector2dF(), pending_layer.OffsetOfDecompositedTransforms()); |
| |
| const auto& scroll_node = |
| *pending_layer.ScrollTranslationForScrollHitTestLayer().ScrollNode(); |
| |
| scoped_refptr<cc::Layer> scroll_layer; |
| auto scroll_element_id = scroll_node.GetCompositorElementId(); |
| for (auto& existing_layer : scroll_hit_test_layers_) { |
| if (existing_layer && existing_layer->element_id() == scroll_element_id) { |
| scroll_layer = std::move(existing_layer); |
| break; |
| } |
| } |
| |
| if (scroll_layer) { |
| DCHECK_EQ(scroll_layer->element_id(), scroll_node.GetCompositorElementId()); |
| } else { |
| scroll_layer = cc::Layer::Create(); |
| scroll_layer->SetElementId(scroll_node.GetCompositorElementId()); |
| scroll_layer->SetHitTestable(true); |
| } |
| |
| scroll_layer->SetOffsetToTransformParent( |
| gfx::Vector2dF(scroll_node.ContainerRect().OffsetFromOrigin())); |
| // TODO(pdr): The scroll layer's bounds are currently set to the clipped |
| // container bounds but this does not include the border. We may want to |
| // change this behavior to make non-composited and composited hit testing |
| // match (see: crbug.com/753124). To do this, use |
| // |scroll_hit_test->scroll_container_bounds|. Set the layer's bounds equal |
| // to the container because the scroll layer does not scroll. |
| scroll_layer->SetBounds(scroll_node.ContainerRect().size()); |
| |
| if (scroll_node.NodeChanged() != PaintPropertyChangeType::kUnchanged) { |
| scroll_layer->SetNeedsPushProperties(); |
| scroll_layer->SetNeedsCommit(); |
| } |
| |
| return scroll_layer; |
| } |
| |
| scoped_refptr<cc::ScrollbarLayerBase> |
| PaintArtifactCompositor::ScrollbarLayerForPendingLayer( |
| const PendingLayer& pending_layer) { |
| if (pending_layer.GetCompositingType() != PendingLayer::kScrollbarLayer) |
| return nullptr; |
| |
| const auto& item = pending_layer.FirstDisplayItem(); |
| DCHECK(item.IsScrollbar()); |
| |
| const auto& scrollbar_item = To<ScrollbarDisplayItem>(item); |
| scoped_refptr<cc::ScrollbarLayerBase> scrollbar_layer; |
| for (auto& layer : scrollbar_layers_) { |
| if (layer && layer->element_id() == scrollbar_item.ElementId()) { |
| scrollbar_layer = std::move(layer); |
| break; |
| } |
| } |
| |
| scrollbar_layer = scrollbar_item.CreateOrReuseLayer(scrollbar_layer.get()); |
| scrollbar_layer->SetOffsetToTransformParent( |
| scrollbar_layer->offset_to_transform_parent() + |
| gfx::Vector2dF(pending_layer.OffsetOfDecompositedTransforms())); |
| return scrollbar_layer; |
| } |
| |
| std::unique_ptr<ContentLayerClientImpl> |
| PaintArtifactCompositor::ClientForPaintChunk(const PaintChunk& paint_chunk) { |
| // TODO(chrishtr): for now, just using a linear walk. In the future we can |
| // optimize this by using the same techniques used in PaintController for |
| // display lists. |
| for (auto& client : content_layer_clients_) { |
| if (client && client->Matches(paint_chunk)) |
| return std::move(client); |
| } |
| |
| auto client = std::make_unique<ContentLayerClientImpl>(); |
| client->GetRasterInvalidator().SetTracksRasterInvalidations( |
| tracks_raster_invalidations_); |
| return client; |
| } |
| |
| scoped_refptr<cc::Layer> |
| PaintArtifactCompositor::CompositedLayerForPendingLayer( |
| const PendingLayer& pending_layer, |
| Vector<std::unique_ptr<ContentLayerClientImpl>>& new_content_layer_clients, |
| Vector<scoped_refptr<cc::Layer>>& new_scroll_hit_test_layers, |
| Vector<scoped_refptr<cc::ScrollbarLayerBase>>& new_scrollbar_layers) { |
| // If the paint chunk is a foreign layer or pre-composited layer, just return |
| // its cc::Layer. |
| if (auto cc_layer = WrappedCcLayerForPendingLayer(pending_layer)) |
| return cc_layer; |
| |
| // If the paint chunk is a scroll hit test layer, lookup/create the layer. |
| if (auto scroll_layer = ScrollHitTestLayerForPendingLayer(pending_layer)) { |
| new_scroll_hit_test_layers.push_back(scroll_layer); |
| return scroll_layer; |
| } |
| |
| if (auto scrollbar_layer = ScrollbarLayerForPendingLayer(pending_layer)) { |
| new_scrollbar_layers.push_back(scrollbar_layer); |
| return scrollbar_layer; |
| } |
| |
| // The common case: create or reuse a PictureLayer for painted content. |
| std::unique_ptr<ContentLayerClientImpl> content_layer_client = |
| ClientForPaintChunk(pending_layer.FirstPaintChunk()); |
| |
| gfx::Vector2dF layer_offset = pending_layer.LayerOffset(); |
| gfx::Size layer_bounds = pending_layer.LayerBounds(); |
| auto cc_layer = content_layer_client->UpdateCcPictureLayer( |
| pending_layer.Chunks(), layer_offset, layer_bounds, |
| pending_layer.GetPropertyTreeState()); |
| |
| new_content_layer_clients.push_back(std::move(content_layer_client)); |
| |
| // Set properties that foreign layers would normally control for themselves |
| // here to avoid changing foreign layers. This includes things set by |
| // GraphicsLayer on the ContentsLayer() or by video clients etc. |
| bool contents_opaque = pending_layer.RectKnownToBeOpaque().Contains( |
| gfx::RectF(gfx::PointAtOffsetFromOrigin(layer_offset), |
| gfx::SizeF(layer_bounds))); |
| cc_layer->SetContentsOpaque(contents_opaque); |
| if (!contents_opaque) { |
| cc_layer->SetContentsOpaqueForText( |
| pending_layer.TextKnownToBeOnOpaqueBackground()); |
| } |
| |
| return cc_layer; |
| } |
| |
| namespace { |
| |
| cc::Layer* ForeignLayer(const PaintChunk& chunk, |
| const PaintArtifact& artifact) { |
| if (chunk.size() != 1) |
| return nullptr; |
| const auto& first_display_item = |
| artifact.GetDisplayItemList()[chunk.begin_index]; |
| auto* foreign_layer = DynamicTo<ForeignLayerDisplayItem>(first_display_item); |
| return foreign_layer ? foreign_layer->GetLayer() : nullptr; |
| } |
| |
| // True if the paint chunk change affects the result of |Update|, such as the |
| // compositing decisions in |CollectPendingLayers|. This will return false for |
| // repaint updates that can be handled by |UpdateRepaintedLayers|, such as |
| // background color changes. |
| bool NeedsFullUpdateAfterPaintingChunk( |
| const PaintChunk& previous, |
| const PaintArtifact& previous_artifact, |
| const PaintChunk& repainted, |
| const PaintArtifact& repainted_artifact) { |
| if (!repainted.Matches(previous)) |
| return true; |
| |
| if (repainted.is_moved_from_cached_subsequence) { |
| DCHECK_EQ(previous.bounds, repainted.bounds); |
| DCHECK_EQ(previous.rect_known_to_be_opaque, |
| repainted.rect_known_to_be_opaque); |
| DCHECK_EQ(previous.text_known_to_be_on_opaque_background, |
| repainted.text_known_to_be_on_opaque_background); |
| DCHECK_EQ(previous.has_text, repainted.has_text); |
| |
| // Debugging for https://crbug.com/1237389 and https://crbug.com/1230104. |
| // Before returning that a full update is not needed, check that the |
| // properties are changed, which would indicate a missing call to |
| // SetNeedsUpdate. |
| if (previous.properties != repainted.properties) { |
| NOTREACHED(); |
| return true; |
| } |
| |
| // Not checking ForeignLayer() here because the old ForeignDisplayItem |
| // was set to 0 when we moved the cached subsequence. This is also the |
| // reason why we check is_moved_from_cached_subsequence before checking |
| // ForeignLayer(). |
| return false; |
| } |
| |
| // Bounds are used in overlap testing. |
| // TODO(pdr): If the bounds shrink, that does affect overlap testing but we |
| // could return false to continue using less-than-optimal overlap testing in |
| // order to save a full compositing update. |
| if (previous.bounds != repainted.bounds) |
| return true; |
| |
| // Changing foreign layers requires a full update to push the new cc::Layers. |
| if (ForeignLayer(previous, previous_artifact) != |
| ForeignLayer(repainted, repainted_artifact)) { |
| return true; |
| } |
| |
| // Opaqueness of individual chunks is used to set the cc::Layer's contents |
| // opaque property. |
| if (previous.rect_known_to_be_opaque != repainted.rect_known_to_be_opaque) |
| return true; |
| // Similar to opaqueness, opaqueness for text is used to set the cc::Layer's |
| // contents opaque for text property. |
| if (previous.text_known_to_be_on_opaque_background != |
| repainted.text_known_to_be_on_opaque_background) { |
| return true; |
| } |
| // |has_text| affects compositing decisions (see: |
| // |PendingLayer::MergeInternal|). |
| if (previous.has_text != repainted.has_text) |
| return true; |
| |
| // Debugging for https://crbug.com/1237389 and https://crbug.com/1230104. |
| // Before returning that a full update is not needed, check that the |
| // properties are changed, which would indicate a missing call to |
| // SetNeedsUpdate. |
| if (previous.properties != repainted.properties) { |
| NOTREACHED(); |
| return true; |
| } |
| |
| return false; |
| } |
| |
| } // namespace |
| |
| void PaintArtifactCompositor::SetNeedsFullUpdateAfterPaintIfNeeded( |
| const PaintChunkSubset& previous, |
| const PaintChunkSubset& repainted) { |
| if (needs_update_) |
| return; |
| |
| // Adding or removing chunks requires a full update to add/remove cc::layers. |
| if (previous.size() != repainted.size()) { |
| SetNeedsUpdate(); |
| return; |
| } |
| |
| // Loop over both paint chunk subsets in order. |
| auto previous_chunk_it = previous.begin(); |
| auto repainted_chunk_it = repainted.begin(); |
| for (; previous_chunk_it != previous.end(); |
| ++previous_chunk_it, ++repainted_chunk_it) { |
| const auto& previous_chunk = *previous_chunk_it; |
| const auto& repainted_chunk = *repainted_chunk_it; |
| if (NeedsFullUpdateAfterPaintingChunk( |
| previous_chunk, previous.GetPaintArtifact(), repainted_chunk, |
| repainted.GetPaintArtifact())) { |
| SetNeedsUpdate(); |
| return; |
| } |
| } |
| } |
| |
| bool PaintArtifactCompositor::HasComposited( |
| CompositorElementId element_id) const { |
| // |Update| creates PropertyTrees on the LayerTreeHost to represent the |
| // composited page state. Check if it has created a property tree node for |
| // the given |element_id|. |
| DCHECK(!NeedsUpdate()) << "This should only be called after an update"; |
| return root_layer_->layer_tree_host()->property_trees()->HasElement( |
| element_id); |
| } |
| |
| // Returns nullptr if 'ancestor' is not a strict ancestor of 'node'. |
| // Otherwise, return the child of 'ancestor' that is an ancestor of 'node' or |
| // 'node' itself. |
| static const EffectPaintPropertyNode* StrictUnaliasedChildOfAlongPath( |
| const EffectPaintPropertyNode& ancestor, |
| const EffectPaintPropertyNode& node) { |
| const auto* n = &node; |
| while (n) { |
| const auto* parent = n->UnaliasedParent(); |
| if (parent == &ancestor) |
| return n; |
| n = parent; |
| } |
| return nullptr; |
| } |
| |
| bool PaintArtifactCompositor::DecompositeEffect( |
| const EffectPaintPropertyNode& parent_effect, |
| wtf_size_t first_layer_in_parent_group_index, |
| const EffectPaintPropertyNode& effect, |
| wtf_size_t layer_index) { |
| // The layer must be the last layer in pending_layers_. |
| DCHECK_EQ(layer_index, pending_layers_.size() - 1); |
| |
| // If the effect associated with the layer is deeper than than the effect |
| // we are attempting to decomposite, than implies some previous decision |
| // did not allow to decomposite intermediate effects. |
| PendingLayer& layer = pending_layers_[layer_index]; |
| if (&layer.GetPropertyTreeState().Effect() != &effect) |
| return false; |
| if (layer.RequiresOwnLayer()) |
| return false; |
| if (effect.HasDirectCompositingReasons()) |
| return false; |
| |
| PropertyTreeState group_state(effect.LocalTransformSpace().Unalias(), |
| effect.OutputClip() |
| ? effect.OutputClip()->Unalias() |
| : layer.GetPropertyTreeState().Clip(), |
| effect); |
| absl::optional<PropertyTreeState> upcast_state = |
| group_state.CanUpcastWith(layer.GetPropertyTreeState()); |
| if (!upcast_state) |
| return false; |
| |
| upcast_state->SetEffect(parent_effect); |
| |
| // Exotic blending layer can be decomposited only if its parent group |
| // (which defines the scope of the blending) has zero or one layer before it, |
| // and it can be merged into that layer. However, a layer not drawing content |
| // at the beginning of the parent group doesn't count, as the blending mode |
| // doesn't apply to it. |
| if (effect.BlendMode() != SkBlendMode::kSrcOver) { |
| auto num_previous_siblings = |
| layer_index - first_layer_in_parent_group_index; |
| if (num_previous_siblings) { |
| if (num_previous_siblings > 2) |
| return false; |
| if (num_previous_siblings == 2 && |
| pending_layers_[first_layer_in_parent_group_index].MayDrawContent()) |
| return false; |
| const auto& previous_sibling = pending_layers_[layer_index - 1]; |
| if (previous_sibling.MayDrawContent() && |
| !previous_sibling.CanMerge(layer, *upcast_state, prefers_lcd_text_)) |
| return false; |
| } |
| } |
| |
| layer.Upcast(*upcast_state); |
| return true; |
| } |
| |
| void PaintArtifactCompositor::LayerizeGroup( |
| const PaintChunkSubset& chunks, |
| const EffectPaintPropertyNode& current_group, |
| PaintChunkIterator& chunk_cursor) { |
| wtf_size_t first_layer_in_current_group = pending_layers_.size(); |
| // The worst case time complexity of the algorithm is O(pqd), where |
| // p = the number of paint chunks. |
| // q = average number of trials to find a squash layer or rejected |
| // for overlapping. |
| // d = (sum of) the depth of property trees. |
| // The analysis as follows: |
| // Every paint chunk will be visited by the main loop below for exactly |
| // once, except for chunks that enter or exit groups (case B & C below). For |
| // normal chunk visit (case A), the only cost is determining squash, which |
| // costs O(qd), where d came from |CanUpcastWith| and geometry mapping. |
| // Subtotal: O(pqd) |
| // For group entering and exiting, it could cost O(d) for each group, for |
| // searching the shallowest subgroup (StrictChildOfAlongPath), thus O(d^2) |
| // in total. |
| // Also when exiting group, the group may be decomposited and squashed to a |
| // previous layer. Again finding the host costs O(qd). Merging would cost |
| // O(p) due to copying the chunk list. Subtotal: O((qd + p)d) = O(qd^2 + pd) |
| // Assuming p > d, the total complexity would be O(pqd + qd^2 + pd) = O(pqd) |
| while (chunk_cursor != chunks.end()) { |
| // Look at the effect node of the next chunk. There are 3 possible cases: |
| // A. The next chunk belongs to the current group but no subgroup. |
| // B. The next chunk does not belong to the current group. |
| // C. The next chunk belongs to some subgroup of the current group. |
| const auto& chunk_effect = chunk_cursor->properties.Effect().Unalias(); |
| if (&chunk_effect == ¤t_group) { |
| pending_layers_.emplace_back(chunks, chunk_cursor); |
| ++chunk_cursor; |
| if (pending_layers_.back().RequiresOwnLayer()) |
| continue; |
| } else { |
| const EffectPaintPropertyNode* subgroup = |
| StrictUnaliasedChildOfAlongPath(current_group, chunk_effect); |
| // Case B: This means we need to close the current group without |
| // processing the next chunk. |
| if (!subgroup) |
| break; |
| // Case C: The following chunks belong to a subgroup. Process them by |
| // a recursion call. |
| wtf_size_t first_layer_in_subgroup = pending_layers_.size(); |
| LayerizeGroup(chunks, *subgroup, chunk_cursor); |
| // The above LayerizeGroup generated new layers in pending_layers_ |
| // [first_layer_in_subgroup .. pending_layers.size() - 1]. If it |
| // generated 2 or more layer that we already know can't be merged |
| // together, we should not decomposite and try to merge any of them into |
| // the previous layers. |
| if (first_layer_in_subgroup != pending_layers_.size() - 1) |
| continue; |
| if (!DecompositeEffect(current_group, first_layer_in_current_group, |
| *subgroup, first_layer_in_subgroup)) |
| continue; |
| } |
| // At this point pending_layers_.back() is the either a layer from a |
| // "decomposited" subgroup or a layer created from a chunk we just |
| // processed. Now determine whether it could be merged into a previous |
| // layer. |
| PendingLayer& new_layer = pending_layers_.back(); |
| DCHECK(!new_layer.RequiresOwnLayer()); |
| DCHECK_EQ(¤t_group, &new_layer.GetPropertyTreeState().Effect()); |
| // This iterates pending_layers_[first_layer_in_current_group:-1] in |
| // reverse. |
| for (wtf_size_t candidate_index = pending_layers_.size() - 1; |
| candidate_index-- > first_layer_in_current_group;) { |
| PendingLayer& candidate_layer = pending_layers_[candidate_index]; |
| if (candidate_layer.Merge(new_layer, prefers_lcd_text_)) { |
| pending_layers_.pop_back(); |
| break; |
| } |
| if (new_layer.MightOverlap(candidate_layer)) { |
| new_layer.SetCompositingType(PendingLayer::kOverlap); |
| break; |
| } |
| } |
| } |
| } |
| |
| void PaintArtifactCompositor::CollectPendingLayers( |
| const HeapVector<PreCompositedLayerInfo>& pre_composited_layers) { |
| // Shrink, but do not release the backing. Re-use it from the last frame. |
| pending_layers_.Shrink(0); |
| for (auto& layer : pre_composited_layers) { |
| if (layer.graphics_layer && |
| !layer.graphics_layer->ShouldCreateLayersAfterPaint()) { |
| pending_layers_.emplace_back(layer); |
| continue; |
| } |
| auto cursor = layer.chunks.begin(); |
| LayerizeGroup(layer.chunks, EffectPaintPropertyNode::Root(), cursor); |
| DCHECK(cursor == layer.chunks.end()); |
| } |
| pending_layers_.ShrinkToReasonableCapacity(); |
| } |
| |
| void SynthesizedClip::UpdateLayer(bool needs_layer, |
| const ClipPaintPropertyNode& clip, |
| const TransformPaintPropertyNode& transform) { |
| if (!needs_layer) { |
| layer_.reset(); |
| return; |
| } |
| if (!layer_) { |
| layer_ = cc::PictureLayer::Create(this); |
| layer_->SetIsDrawable(true); |
| // The clip layer must be hit testable because the compositor may not know |
| // whether the hit test is clipped out. |
| // See: cc::LayerTreeHostImpl::IsInitialScrollHitTestReliable(). |
| layer_->SetHitTestable(true); |
| } |
| |
| const RefCountedPath* path = clip.ClipPath(); |
| SkRRect new_rrect = clip.PaintClipRect(); |
| IntRect layer_bounds = EnclosingIntRect(clip.PaintClipRect().Rect()); |
| bool needs_display = false; |
| |
| auto new_translation_2d_or_matrix = |
| GeometryMapper::SourceToDestinationProjection(clip.LocalTransformSpace(), |
| transform); |
| new_translation_2d_or_matrix.MapRect(layer_bounds); |
| new_translation_2d_or_matrix.PostTranslate(-layer_bounds.x(), |
| -layer_bounds.y()); |
| |
| if (!path && new_translation_2d_or_matrix.IsIdentityOr2DTranslation()) { |
| const auto& translation = new_translation_2d_or_matrix.Translation2D(); |
| new_rrect.offset(translation.x(), translation.y()); |
| needs_display = !rrect_is_local_ || new_rrect != rrect_; |
| translation_2d_or_matrix_ = GeometryMapper::Translation2DOrMatrix(); |
| rrect_is_local_ = true; |
| } else { |
| needs_display = rrect_is_local_ || new_rrect != rrect_ || |
| new_translation_2d_or_matrix != translation_2d_or_matrix_ || |
| (path_ != path && (!path_ || !path || *path_ != *path)); |
| translation_2d_or_matrix_ = new_translation_2d_or_matrix; |
| rrect_is_local_ = false; |
| } |
| |
| if (needs_display) |
| layer_->SetNeedsDisplay(); |
| |
| layer_->SetOffsetToTransformParent( |
| gfx::Vector2dF(layer_bounds.x(), layer_bounds.y())); |
| layer_->SetBounds(ToGfxSize(layer_bounds.size())); |
| rrect_ = new_rrect; |
| path_ = path; |
| } |
| |
| scoped_refptr<cc::DisplayItemList> |
| SynthesizedClip::PaintContentsToDisplayList() { |
| auto cc_list = base::MakeRefCounted<cc::DisplayItemList>( |
| cc::DisplayItemList::kTopLevelDisplayItemList); |
| PaintFlags flags; |
| flags.setAntiAlias(true); |
| cc_list->StartPaint(); |
| if (rrect_is_local_) { |
| cc_list->push<cc::DrawRRectOp>(rrect_, flags); |
| } else { |
| cc_list->push<cc::SaveOp>(); |
| if (translation_2d_or_matrix_.IsIdentityOr2DTranslation()) { |
| const auto& translation = translation_2d_or_matrix_.Translation2D(); |
| cc_list->push<cc::TranslateOp>(translation.x(), translation.y()); |
| } else { |
| cc_list->push<cc::ConcatOp>( |
| TransformationMatrix::ToSkM44(translation_2d_or_matrix_.Matrix())); |
| } |
| if (path_) { |
| cc_list->push<cc::ClipPathOp>(path_->GetSkPath(), SkClipOp::kIntersect, |
| true); |
| } |
| cc_list->push<cc::DrawRRectOp>(rrect_, flags); |
| cc_list->push<cc::RestoreOp>(); |
| } |
| cc_list->EndPaintOfUnpaired(gfx::Rect(layer_->bounds())); |
| cc_list->Finalize(); |
| return cc_list; |
| } |
| |
| SynthesizedClip& PaintArtifactCompositor::CreateOrReuseSynthesizedClipLayer( |
| const ClipPaintPropertyNode& clip, |
| const TransformPaintPropertyNode& transform, |
| bool needs_layer, |
| CompositorElementId& mask_isolation_id, |
| CompositorElementId& mask_effect_id) { |
| auto* entry = |
| std::find_if(synthesized_clip_cache_.begin(), |
| synthesized_clip_cache_.end(), [&clip](const auto& entry) { |
| return entry.key == &clip && !entry.in_use; |
| }); |
| if (entry == synthesized_clip_cache_.end()) { |
| synthesized_clip_cache_.push_back(SynthesizedClipEntry{ |
| &clip, std::make_unique<SynthesizedClip>(), false}); |
| entry = synthesized_clip_cache_.end() - 1; |
| } |
| |
| entry->in_use = true; |
| SynthesizedClip& synthesized_clip = *entry->synthesized_clip; |
| if (needs_layer) { |
| synthesized_clip.UpdateLayer(needs_layer, clip, transform); |
| synthesized_clip.Layer()->SetLayerTreeHost(root_layer_->layer_tree_host()); |
| if (layer_debug_info_enabled_ && !synthesized_clip.Layer()->debug_info()) |
| synthesized_clip.Layer()->SetDebugName("Synthesized Clip"); |
| } |
| mask_isolation_id = synthesized_clip.GetMaskIsolationId(); |
| mask_effect_id = synthesized_clip.GetMaskEffectId(); |
| return synthesized_clip; |
| } |
| |
| static void UpdateCompositorViewportProperties( |
| const PaintArtifactCompositor::ViewportProperties& properties, |
| PropertyTreeManager& property_tree_manager, |
| cc::LayerTreeHost* layer_tree_host) { |
| // The inner and outer viewports' existence is linked. That is, either they're |
| // both null or they both exist. |
| DCHECK_EQ(static_cast<bool>(properties.outer_scroll_translation), |
| static_cast<bool>(properties.inner_scroll_translation)); |
| DCHECK(!properties.outer_clip || |
| static_cast<bool>(properties.inner_scroll_translation)); |
| |
| cc::ViewportPropertyIds ids; |
| if (properties.overscroll_elasticity_transform) { |
| ids.overscroll_elasticity_transform = |
| property_tree_manager.EnsureCompositorTransformNode( |
| *properties.overscroll_elasticity_transform); |
| } |
| if (properties.overscroll_elasticity_effect) { |
| ids.overscroll_elasticity_effect = |
| properties.overscroll_elasticity_effect->GetCompositorElementId(); |
| } |
| if (properties.page_scale) { |
| ids.page_scale_transform = |
| property_tree_manager.EnsureCompositorPageScaleTransformNode( |
| *properties.page_scale); |
| } |
| if (properties.inner_scroll_translation) { |
| ids.inner_scroll = property_tree_manager.EnsureCompositorInnerScrollNode( |
| *properties.inner_scroll_translation); |
| if (properties.outer_clip) { |
| ids.outer_clip = property_tree_manager.EnsureCompositorClipNode( |
| *properties.outer_clip); |
| } |
| if (properties.outer_scroll_translation) { |
| ids.outer_scroll = property_tree_manager.EnsureCompositorOuterScrollNode( |
| *properties.outer_scroll_translation); |
| } |
| } |
| |
| layer_tree_host->RegisterViewportPropertyIds(ids); |
| } |
| |
| void PaintArtifactCompositor::Update( |
| const HeapVector<PreCompositedLayerInfo>& pre_composited_layers, |
| const ViewportProperties& viewport_properties, |
| const Vector<const TransformPaintPropertyNode*>& scroll_translation_nodes, |
| Vector<std::unique_ptr<cc::DocumentTransitionRequest>> |
| transition_requests) { |
| // See: |UpdateRepaintedLayers| for repaint updates. |
| DCHECK(needs_update_); |
| DCHECK(scroll_translation_nodes.IsEmpty() || |
| RuntimeEnabledFeatures::ScrollUnificationEnabled()); |
| DCHECK(root_layer_); |
| |
| TRACE_EVENT0("blink", "PaintArtifactCompositor::Update"); |
| |
| // The tree will be null after detaching and this update can be ignored. |
| // See: WebViewImpl::detachPaintArtifactCompositor(). |
| cc::LayerTreeHost* host = root_layer_->layer_tree_host(); |
| if (!host) |
| return; |
| |
| for (auto& request : transition_requests) |
| host->AddDocumentTransitionRequest(std::move(request)); |
| |
| host->property_trees()->scroll_tree.SetScrollCallbacks(scroll_callbacks_); |
| root_layer_->set_property_tree_sequence_number( |
| g_s_property_tree_sequence_number); |
| |
| // Make compositing decisions, storing the result in |pending_layers_|. |
| CollectPendingLayers(pre_composited_layers); |
| PendingLayer::DecompositeTransforms(pending_layers_); |
| |
| LayerListBuilder layer_list_builder; |
| PropertyTreeManager property_tree_manager(*this, *host->property_trees(), |
| *root_layer_, layer_list_builder, |
| g_s_property_tree_sequence_number); |
| |
| UpdateCompositorViewportProperties(viewport_properties, property_tree_manager, |
| host); |
| |
| // With ScrollUnification, we ensure a cc::ScrollNode for all |
| // |scroll_translation_nodes|. |
| if (RuntimeEnabledFeatures::ScrollUnificationEnabled()) |
| property_tree_manager.EnsureCompositorScrollNodes(scroll_translation_nodes); |
| |
| Vector<std::unique_ptr<ContentLayerClientImpl>> new_content_layer_clients; |
| new_content_layer_clients.ReserveCapacity(pending_layers_.size()); |
| Vector<scoped_refptr<cc::Layer>> new_scroll_hit_test_layers; |
| Vector<scoped_refptr<cc::ScrollbarLayerBase>> new_scrollbar_layers; |
| |
| // Maps from cc effect id to blink effects. Containing only the effects |
| // having composited layers. |
| Vector<const EffectPaintPropertyNode*> blink_effects; |
| |
| for (auto& entry : synthesized_clip_cache_) |
| entry.in_use = false; |
| |
| cc::LayerSelection layer_selection; |
| for (const auto& pending_layer : pending_layers_) { |
| const auto& property_state = pending_layer.GetPropertyTreeState(); |
| const auto& transform = property_state.Transform(); |
| const auto& clip = property_state.Clip(); |
| |
| scoped_refptr<cc::Layer> layer = CompositedLayerForPendingLayer( |
| pending_layer, new_content_layer_clients, new_scroll_hit_test_layers, |
| new_scrollbar_layers); |
| |
| UpdateLayerProperties(*layer, pending_layer); |
| UpdateLayerSelection(*layer, pending_layer, layer_selection); |
| |
| layer->SetLayerTreeHost(root_layer_->layer_tree_host()); |
| |
| int transform_id = |
| property_tree_manager.EnsureCompositorTransformNode(transform); |
| int clip_id = property_tree_manager.EnsureCompositorClipNode(clip); |
| |
| int effect_id = property_tree_manager.SwitchToEffectNodeWithSynthesizedClip( |
| property_state.Effect(), clip, layer->DrawsContent()); |
| if (blink_effects.size() <= static_cast<wtf_size_t>(effect_id)) |
| blink_effects.resize(effect_id + 1); |
| if (!blink_effects[effect_id]) { |
| blink_effects[effect_id] = &property_state.Effect(); |
| // We need additional bookkeeping for backdrop-filter mask. |
| if (property_state.Effect().RequiresCompositingForBackdropFilterMask()) { |
| static_cast<cc::PictureLayer*>(layer.get()) |
| ->SetIsBackdropFilterMask(true); |
| layer->SetElementId(property_state.Effect().GetCompositorElementId()); |
| auto& effect_tree = host->property_trees()->effect_tree; |
| auto* cc_node = effect_tree.Node(effect_id); |
| effect_tree.Node(cc_node->parent_id)->backdrop_mask_element_id = |
| property_state.Effect().GetCompositorElementId(); |
| } |
| } else { |
| DCHECK_EQ(blink_effects[effect_id], &property_state.Effect()); |
| } |
| |
| // The compositor scroll node is not directly stored in the property tree |
| // state but can be created via the scroll offset translation node. |
| const auto& scroll_translation = |
| NearestScrollTranslationForLayer(pending_layer); |
| // TODO(ScrollUnification): We may combine the following two calls to |
| // property_tree_manager.SetCcScrollNodeIsComposited(scroll_translation); |
| int scroll_id = |
| property_tree_manager.EnsureCompositorScrollNode(scroll_translation); |
| if (RuntimeEnabledFeatures::ScrollUnificationEnabled()) |
| property_tree_manager.SetCcScrollNodeIsComposited(scroll_id); |
| |
| layer_list_builder.Add(layer); |
| |
| layer->set_property_tree_sequence_number( |
| root_layer_->property_tree_sequence_number()); |
| layer->SetTransformTreeIndex(transform_id); |
| layer->SetScrollTreeIndex(scroll_id); |
| layer->SetClipTreeIndex(clip_id); |
| layer->SetEffectTreeIndex(effect_id); |
| bool backface_hidden = property_state.Transform().IsBackfaceHidden(); |
| layer->SetShouldCheckBackfaceVisibility(backface_hidden); |
| |
| // If the property tree state has changed between the layer and the root, |
| // we need to inform the compositor so damage can be calculated. Calling |
| // |PropertyTreeStateChanged| for every pending layer is O(|property |
| // nodes|^2) and could be optimized by caching the lookup of nodes known |
| // to be changed/unchanged. |
| if (layer->subtree_property_changed() || |
| pending_layer.PropertyTreeStateChanged()) { |
| layer->SetSubtreePropertyChanged(); |
| root_layer_->SetNeedsCommit(); |
| } |
| } |
| |
| if (RuntimeEnabledFeatures::CompositeAfterPaintEnabled()) |
| root_layer_->layer_tree_host()->RegisterSelection(layer_selection); |
| |
| property_tree_manager.Finalize(); |
| content_layer_clients_.swap(new_content_layer_clients); |
| scroll_hit_test_layers_.swap(new_scroll_hit_test_layers); |
| scrollbar_layers_.swap(new_scrollbar_layers); |
| |
| auto* new_end = std::remove_if( |
| synthesized_clip_cache_.begin(), synthesized_clip_cache_.end(), |
| [](const auto& entry) { return !entry.in_use; }); |
| synthesized_clip_cache_.Shrink( |
| static_cast<wtf_size_t>(new_end - synthesized_clip_cache_.begin())); |
| |
| // This should be done before UpdateRenderSurfaceForEffects() for which to |
| // get property tree node ids from the layers. |
| host->property_trees()->sequence_number = g_s_property_tree_sequence_number; |
| |
| auto layers = layer_list_builder.Finalize(); |
| UpdateRenderSurfaceForEffects(host->property_trees()->effect_tree, layers, |
| blink_effects); |
| root_layer_->SetChildLayerList(std::move(layers)); |
| |
| // Update the host's active registered elements from the new property tree. |
| host->UpdateActiveElements(); |
| |
| // Mark the property trees as having been rebuilt. |
| host->property_trees()->needs_rebuild = false; |
| host->property_trees()->ResetCachedData(); |
| previous_update_for_testing_ = PreviousUpdateType::kFull; |
| needs_update_ = false; |
| |
| UpdateDebugInfo(); |
| |
| g_s_property_tree_sequence_number++; |
| |
| DVLOG(2) << "PaintArtifactCompositor::Update() done\n" |
| << "Composited layers:\n" |
| << GetLayersAsJSON(VLOG_IS_ON(3) ? 0xffffffff : 0) |
| ->ToPrettyJSONString() |
| .Utf8(); |
| } |
| |
| void PaintArtifactCompositor::UpdateLayerProperties( |
| cc::Layer& layer, |
| const PendingLayer& pending_layer) { |
| // Properties of foreign layers are managed by their owners. |
| if (pending_layer.GetCompositingType() == PendingLayer::kForeignLayer) |
| return; |
| |
| if (pending_layer.GetGraphicsLayer() && |
| pending_layer.GetGraphicsLayer()->PaintsContentOrHitTest()) { |
| PaintChunkSubset chunks(pending_layer.GetGraphicsLayer() |
| ->GetPaintController() |
| .GetPaintArtifactShared()); |
| PaintChunksToCcLayer::UpdateLayerProperties( |
| layer, pending_layer.GetPropertyTreeState(), chunks); |
| } else { |
| PaintChunksToCcLayer::UpdateLayerProperties( |
| layer, pending_layer.GetPropertyTreeState(), pending_layer.Chunks()); |
| } |
| } |
| |
| void PaintArtifactCompositor::UpdateLayerSelection( |
| cc::Layer& layer, |
| const PendingLayer& pending_layer, |
| cc::LayerSelection& layer_selection) { |
| // Foreign layers cannot contain selection. |
| if (pending_layer.GetCompositingType() == PendingLayer::kForeignLayer) |
| return; |
| |
| if (pending_layer.GetGraphicsLayer() && |
| pending_layer.GetGraphicsLayer()->PaintsContentOrHitTest()) { |
| PaintChunkSubset chunks(pending_layer.GetGraphicsLayer() |
| ->GetPaintController() |
| .GetPaintArtifactShared()); |
| PaintChunksToCcLayer::UpdateLayerSelection( |
| layer, pending_layer.GetPropertyTreeState(), chunks, layer_selection); |
| } else { |
| PaintChunksToCcLayer::UpdateLayerSelection( |
| layer, pending_layer.GetPropertyTreeState(), pending_layer.Chunks(), |
| layer_selection); |
| } |
| } |
| |
| void PaintArtifactCompositor::UpdateRepaintedContentLayerClient( |
| const PendingLayer& pending_layer, |
| bool pending_layer_chunks_unchanged, |
| ContentLayerClientImpl& content_layer_client) { |
| // Checking |pending_layer_chunks_unchanged| is an optimization to avoid the |
| // expensive call to |UpdateCcPictureLayer| when no repainting occurs for this |
| // PendingLayer. |
| if (pending_layer_chunks_unchanged) { |
| // See RasterInvalidator::SetOldPaintArtifact() for the reason for this. |
| content_layer_client.GetRasterInvalidator().SetOldPaintArtifact( |
| &pending_layer.Chunks().GetPaintArtifact()); |
| } else { |
| content_layer_client.UpdateCcPictureLayer( |
| pending_layer.Chunks(), pending_layer.LayerOffset(), |
| pending_layer.LayerBounds(), pending_layer.GetPropertyTreeState()); |
| } |
| } |
| |
| namespace { |
| |
| // This class iterates forward over the PaintChunks in a vector of |
| // |PreCompositedLayerInfo|s. |
| class PreCompositedLayerPaintChunkFinder { |
| STACK_ALLOCATED(); |
| |
| public: |
| explicit PreCompositedLayerPaintChunkFinder( |
| HeapVector<PreCompositedLayerInfo>& pre_composited_layers) |
| : pre_composited_layers_(pre_composited_layers), |
| pre_composited_layer_it_(pre_composited_layers_.begin()), |
| subset_iterator_(pre_composited_layer_it_->chunks.begin()) {} |
| |
| const PaintChunk& current_chunk() { |
| CHECK(pre_composited_layer_it_ != pre_composited_layers_.end()); |
| CHECK(subset_iterator_ != pre_composited_layer_it_->chunks.end()); |
| return *subset_iterator_; |
| } |
| |
| const PaintArtifact& current_artifact() { |
| CHECK(pre_composited_layer_it_ != pre_composited_layers_.end()); |
| return pre_composited_layer_it_->chunks.GetPaintArtifact(); |
| } |
| |
| bool AdvanceToMatching(const PaintChunk& chunk) { |
| while (pre_composited_layer_it_ != pre_composited_layers_.end()) { |
| while (subset_iterator_ != pre_composited_layer_it_->chunks.end()) { |
| if (subset_iterator_->Matches(chunk)) |
| return true; |
| ++subset_iterator_; |
| } |
| pre_composited_layer_it_++; |
| if (pre_composited_layer_it_ == pre_composited_layers_.end()) |
| break; |
| subset_iterator_ = pre_composited_layer_it_->chunks.begin(); |
| } |
| // Unable to find a matching paint chunk. |
| return false; |
| } |
| |
| private: |
| HeapVector<PreCompositedLayerInfo>& pre_composited_layers_; |
| HeapVector<PreCompositedLayerInfo>::iterator pre_composited_layer_it_; |
| PaintChunkSubset::Iterator subset_iterator_; |
| }; |
| |
| } // namespace |
| |
| void PaintArtifactCompositor::UpdateRepaintedLayers( |
| HeapVector<PreCompositedLayerInfo>& pre_composited_layers) { |
| // |Update| should be used for full updates. |
| DCHECK(!needs_update_); |
| |
| #if DCHECK_IS_ON() |
| // Any property tree state change should have caused a full update. |
| for (const auto& pre_composited_layer : pre_composited_layers) { |
| if (pre_composited_layer.graphics_layer) { |
| DCHECK(!RuntimeEnabledFeatures::CompositeAfterPaintEnabled()); |
| continue; |
| } |
| for (const auto& chunk : pre_composited_layer.chunks) { |
| // If this fires, a property tree value has changed but we are missing a |
| // call to |PaintArtifactCompositor::SetNeedsUpdate|. |
| DCHECK(!chunk.properties.GetPropertyTreeState().Unalias().ChangedToRoot( |
| PaintPropertyChangeType::kChangedOnlyNonRerasterValues)); |
| } |
| } |
| #endif |
| |
| cc::LayerSelection layer_selection; |
| |
| // The loop below iterates over the existing PendingLayers and issues updates. |
| PreCompositedLayerPaintChunkFinder repainted_chunk_finder( |
| pre_composited_layers); |
| auto* content_layer_client_it = content_layer_clients_.begin(); |
| auto* scroll_hit_test_layer_it = scroll_hit_test_layers_.begin(); |
| auto* scrollbar_layer_it = scrollbar_layers_.begin(); |
| for (auto* pending_layer_it = pending_layers_.begin(); |
| pending_layer_it != pending_layers_.end(); pending_layer_it++) { |
| auto compositing_type = pending_layer_it->GetCompositingType(); |
| if (compositing_type == PendingLayer::kForeignLayer && |
| !RuntimeEnabledFeatures::CompositeAfterPaintEnabled()) { |
| // These layers are fully managed externally and do not need an update. |
| } else if (compositing_type == PendingLayer::kPreCompositedLayer) { |
| // These are Pre-CompositeAfterPaint layers where raster invalidation has |
| // already occurred and we just need to update layer properties&selection. |
| DCHECK(pending_layer_it->GetGraphicsLayer()); |
| if (pending_layer_it->GetGraphicsLayer()->Repainted()) { |
| UpdateLayerProperties(pending_layer_it->GetGraphicsLayer()->CcLayer(), |
| *pending_layer_it); |
| UpdateLayerSelection(pending_layer_it->GetGraphicsLayer()->CcLayer(), |
| *pending_layer_it, layer_selection); |
| } |
| } else { |
| // These are CompositeAfterPaint (or CompositeSVG) layers and we need to |
| // both copy the repainted paint chunks and update the cc::Layer. To do |
| // this, we need the previous PaintChunks (from the PendingLayer) and the |
| // matching repainted PaintChunks (from |pre_composited_layers|). Because |
| // repaint-only updates cannot add, remove, or re-order PaintChunks, |
| // |repainted_chunk_finder| searches forward in |pre_composited_layers| |
| // for the matching paint chunk, ensuring this function is O(chunks). |
| const PaintChunk& first = *pending_layer_it->Chunks().begin(); |
| bool did_advance = repainted_chunk_finder.AdvanceToMatching(first); |
| |
| // If we do not find a matching PaintChunk, PaintChunks must have been |
| // added, removed, or re-ordered, and we should be doing a full update |
| // instead of a repaint update. |
| CHECK(did_advance); |
| |
| // Essentially replace the paint chunks of the pending layer with the |
| // repainted chunks in |repainted_artifact|. The pending layer's paint |
| // chunks (a |PaintChunkSubset|) actually store indices to |PaintChunk|s |
| // in a |PaintArtifact|. In repaint updates, chunks are not added, |
| // removed, or re-ordered, so we can simply swap in a repainted |
| // |PaintArtifact| instead of copying |PaintChunk|s individually. |
| const PaintArtifact& previous_artifact = |
| pending_layer_it->Chunks().GetPaintArtifact(); |
| const PaintArtifact& repainted_artifact = |
| repainted_chunk_finder.current_artifact(); |
| DCHECK_EQ(previous_artifact.PaintChunks().size(), |
| repainted_artifact.PaintChunks().size()); |
| pending_layer_it->SetPaintArtifact(&repainted_artifact); |
| |
| bool pending_layer_chunks_unchanged = true; |
| for (const auto& chunk : pending_layer_it->Chunks()) { |
| if (!chunk.is_moved_from_cached_subsequence) { |
| pending_layer_chunks_unchanged = false; |
| break; |
| } |
| } |
| |
| cc::Layer* cc_layer = nullptr; |
| switch (pending_layer_it->GetCompositingType()) { |
| case PendingLayer::kPreCompositedLayer: |
| NOTREACHED(); |
| break; |
| case PendingLayer::kForeignLayer: |
| continue; |
| case PendingLayer::kScrollbarLayer: |
| cc_layer = scrollbar_layer_it->get(); |
| ++scrollbar_layer_it; |
| break; |
| case PendingLayer::kScrollHitTestLayer: |
| cc_layer = scroll_hit_test_layer_it->get(); |
| ++scroll_hit_test_layer_it; |
| break; |
| default: |
| UpdateRepaintedContentLayerClient(*pending_layer_it, |
| pending_layer_chunks_unchanged, |
| **content_layer_client_it); |
| cc_layer = &(*content_layer_client_it)->Layer(); |
| ++content_layer_client_it; |
| break; |
| } |
| DCHECK(cc_layer); |
| |
| if (!pending_layer_chunks_unchanged) |
| UpdateLayerProperties(*cc_layer, *pending_layer_it); |
| UpdateLayerSelection(*cc_layer, *pending_layer_it, layer_selection); |
| } |
| } |
| |
| if (RuntimeEnabledFeatures::CompositeAfterPaintEnabled()) |
| root_layer_->layer_tree_host()->RegisterSelection(layer_selection); |
| |
| UpdateDebugInfo(); |
| |
| previous_update_for_testing_ = PreviousUpdateType::kRepaint; |
| needs_update_ = false; |
| } |
| |
| bool PaintArtifactCompositor::CanDirectlyUpdateProperties() const { |
| // Don't try to retrieve property trees if we need an update. The full |
| // update will update all of the nodes, so a direct update doesn't need to |
| // do anything. |
| if (needs_update_) |
| return false; |
| |
| return root_layer_ && root_layer_->layer_tree_host(); |
| } |
| |
| bool PaintArtifactCompositor::DirectlyUpdateCompositedOpacityValue( |
| const EffectPaintPropertyNode& effect) { |
| // We can only directly-update compositor values if all content associated |
| // with the node is known to be composited. |
| DCHECK(effect.HasDirectCompositingReasons()); |
| if (CanDirectlyUpdateProperties()) { |
| return PropertyTreeManager::DirectlyUpdateCompositedOpacityValue( |
| *root_layer_->layer_tree_host(), effect); |
| } |
| return false; |
| } |
| |
| bool PaintArtifactCompositor::DirectlyUpdateScrollOffsetTransform( |
| const TransformPaintPropertyNode& transform) { |
| if (RuntimeEnabledFeatures::CompositeAfterPaintEnabled()) { |
| // We can only directly-update compositor values if all content associated |
| // with the node is known to be composited. We cannot DCHECK this pre- |
| // CompositeAfterPaint because we cannot query CompositedLayerMapping here. |
| DCHECK(transform.HasDirectCompositingReasons()); |
| } |
| if (CanDirectlyUpdateProperties()) { |
| return PropertyTreeManager::DirectlyUpdateScrollOffsetTransform( |
| *root_layer_->layer_tree_host(), transform); |
| } |
| return false; |
| } |
| |
| bool PaintArtifactCompositor::DirectlyUpdateTransform( |
| const TransformPaintPropertyNode& transform) { |
| // We can only directly-update compositor values if all content associated |
| // with the node is known to be composited. |
| DCHECK(transform.HasDirectCompositingReasons()); |
| if (CanDirectlyUpdateProperties()) { |
| return PropertyTreeManager::DirectlyUpdateTransform( |
| *root_layer_->layer_tree_host(), transform); |
| } |
| return false; |
| } |
| |
| bool PaintArtifactCompositor::DirectlyUpdatePageScaleTransform( |
| const TransformPaintPropertyNode& transform) { |
| // We can only directly-update compositor values if all content associated |
| // with the node is known to be composited. |
| DCHECK(transform.HasDirectCompositingReasons()); |
| if (CanDirectlyUpdateProperties()) { |
| return PropertyTreeManager::DirectlyUpdatePageScaleTransform( |
| *root_layer_->layer_tree_host(), transform); |
| } |
| return false; |
| } |
| |
| bool PaintArtifactCompositor::DirectlySetScrollOffset( |
| CompositorElementId element_id, |
| const FloatPoint& scroll_offset) { |
| if (!root_layer_ || !root_layer_->layer_tree_host()) |
| return false; |
| auto* property_trees = root_layer_->layer_tree_host()->property_trees(); |
| if (!property_trees->element_id_to_scroll_node_index.contains(element_id)) |
| return false; |
| PropertyTreeManager::DirectlySetScrollOffset(*root_layer_->layer_tree_host(), |
| element_id, |
| ToGfxVector2dF(scroll_offset)); |
| return true; |
| } |
| |
| static cc::RenderSurfaceReason GetRenderSurfaceCandidateReason( |
| const cc::EffectNode& effect, |
| const Vector<const EffectPaintPropertyNode*>& blink_effects) { |
| if (effect.HasRenderSurface()) |
| return cc::RenderSurfaceReason::kNone; |
| if (effect.blend_mode != SkBlendMode::kSrcOver) |
| return cc::RenderSurfaceReason::kBlendModeDstIn; |
| if (effect.opacity != 1.f) |
| return cc::RenderSurfaceReason::kOpacity; |
| if (static_cast<wtf_size_t>(effect.id) < blink_effects.size() && |
| blink_effects[effect.id] && |
| blink_effects[effect.id]->HasActiveOpacityAnimation()) |
| return cc::RenderSurfaceReason::kOpacityAnimation; |
| // Applying a rounded corner clip to more than one layer descendant |
| // with highest quality requires a render surface, due to the possibility |
| // of antialiasing issues on the rounded corner edges. |
| // is_fast_rounded_corner means to intentionally prefer faster compositing |
| // and less memory over highest quality. |
| if (effect.mask_filter_info.HasRoundedCorners() && |
| !effect.is_fast_rounded_corner) |
| return cc::RenderSurfaceReason::kRoundedCorner; |
| return cc::RenderSurfaceReason::kNone; |
| } |
| |
| // Every effect is supposed to have render surface enabled for grouping, but |
| // we can omit one if the effect is opacity- or blend-mode-only, render |
| // surface is not forced, and the effect has only one compositing child. This |
| // is both for optimization and not introducing sub-pixel differences in web |
| // tests. |
| // 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. |
| void PaintArtifactCompositor::UpdateRenderSurfaceForEffects( |
| cc::EffectTree& effect_tree, |
| const cc::LayerList& layers, |
| const Vector<const EffectPaintPropertyNode*>& blink_effects) { |
| // This vector is indexed by effect node id. The value is the number of |
| // layers and sub-render-surfaces controlled by this effect. |
| Vector<int> effect_layer_counts(static_cast<wtf_size_t>(effect_tree.size())); |
| // Initialize the vector to count directly controlled layers. |
| for (const auto& layer : layers) { |
| if (layer->DrawsContent()) |
| effect_layer_counts[layer->effect_tree_index()]++; |
| } |
| |
| // In the effect tree, parent always has lower id than children, so the |
| // following loop will check descendants before parents and accumulate |
| // effect_layer_counts. |
| for (int id = static_cast<int>(effect_tree.size() - 1); |
| id > cc::EffectTree::kSecondaryRootNodeId; id--) { |
| auto* effect = effect_tree.Node(id); |
| if (effect_layer_counts[id] > 1) { |
| auto reason = GetRenderSurfaceCandidateReason(*effect, blink_effects); |
| if (reason != cc::RenderSurfaceReason::kNone) { |
| // The render surface candidate needs a render surface because it |
| // controls more than 1 layer. |
| effect->render_surface_reason = reason; |
| } |
| } |
| |
| // We should not have visited the parent. |
| DCHECK_NE(-1, effect_layer_counts[effect->parent_id]); |
| if (effect->HasRenderSurface()) { |
| // A sub-render-surface counts as one controlled layer of the parent. |
| effect_layer_counts[effect->parent_id]++; |
| } else { |
| // Otherwise all layers count as controlled layers of the parent. |
| effect_layer_counts[effect->parent_id] += effect_layer_counts[id]; |
| } |
| |
| #if DCHECK_IS_ON() |
| // Mark we have visited this effect. |
| effect_layer_counts[id] = -1; |
| #endif |
| } |
| } |
| |
| void PaintArtifactCompositor::SetLayerDebugInfoEnabled(bool enabled) { |
| if (enabled == layer_debug_info_enabled_) |
| return; |
| |
| DCHECK(needs_update_); |
| layer_debug_info_enabled_ = enabled; |
| |
| if (enabled) { |
| root_layer_->SetDebugName("root"); |
| } else { |
| root_layer_->ClearDebugInfo(); |
| for (auto& layer : root_layer_->children()) |
| layer->ClearDebugInfo(); |
| } |
| } |
| |
| static void UpdateLayerDebugInfo( |
| cc::Layer& layer, |
| const PaintChunk::Id& id, |
| const String& name, |
| DOMNodeId owner_node_id, |
| CompositingReasons compositing_reasons, |
| RasterInvalidationTracking* raster_invalidation_tracking) { |
| cc::LayerDebugInfo& debug_info = layer.EnsureDebugInfo(); |
| |
| debug_info.name = name.Utf8(); |
| if (id.type == DisplayItem::kForeignLayerContentsWrapper) { |
| // This is for backward compatibility in pre-CompositeAfterPaint mode. |
| DCHECK(!RuntimeEnabledFeatures::CompositeAfterPaintEnabled()); |
| debug_info.name = std::string("ContentsLayer for ") + debug_info.name; |
| } |
| |
| debug_info.compositing_reasons = |
| CompositingReason::Descriptions(compositing_reasons); |
| debug_info.compositing_reason_ids = |
| CompositingReason::ShortNames(compositing_reasons); |
| debug_info.owner_node_id = owner_node_id; |
| |
| if (RasterInvalidationTracking::IsTracingRasterInvalidations() && |
| raster_invalidation_tracking) { |
| raster_invalidation_tracking->AddToLayerDebugInfo(debug_info); |
| raster_invalidation_tracking->ClearInvalidations(); |
| } |
| } |
| |
| static void UpdateLayerDebugInfo( |
| cc::Layer& layer, |
| const PaintChunk::Id& id, |
| const PaintArtifact& paint_artifact, |
| CompositingReasons compositing_reasons, |
| RasterInvalidationTracking* raster_invalidation_tracking) { |
| UpdateLayerDebugInfo(layer, id, paint_artifact.ClientDebugName(id.client_id), |
| paint_artifact.ClientOwnerNodeId(id.client_id), |
| compositing_reasons, raster_invalidation_tracking); |
| } |
| |
| void PaintArtifactCompositor::UpdateDebugInfo() const { |
| if (!layer_debug_info_enabled_) |
| return; |
| |
| auto* content_layer_client_it = content_layer_clients_.begin(); |
| auto* scroll_hit_test_layer_it = scroll_hit_test_layers_.begin(); |
| auto* scrollbar_layer_it = scrollbar_layers_.begin(); |
| const PendingLayer* previous_pending_layer = nullptr; |
| for (const auto& pending_layer : pending_layers_) { |
| cc::Layer* layer; |
| RasterInvalidationTracking* tracking = nullptr; |
| switch (pending_layer.GetCompositingType()) { |
| case PendingLayer::kPreCompositedLayer: |
| tracking = |
| pending_layer.GetGraphicsLayer()->GetRasterInvalidationTracking(); |
| layer = &pending_layer.GetGraphicsLayer()->CcLayer(); |
| break; |
| case PendingLayer::kForeignLayer: |
| layer = To<ForeignLayerDisplayItem>(pending_layer.FirstDisplayItem()) |
| .GetLayer(); |
| break; |
| case PendingLayer::kScrollbarLayer: |
| layer = scrollbar_layer_it->get(); |
| ++scrollbar_layer_it; |
| break; |
| case PendingLayer::kScrollHitTestLayer: |
| layer = scroll_hit_test_layer_it->get(); |
| ++scroll_hit_test_layer_it; |
| break; |
| default: |
| tracking = |
| (*content_layer_client_it)->GetRasterInvalidator().GetTracking(); |
| layer = &(*content_layer_client_it)->Layer(); |
| ++content_layer_client_it; |
| break; |
| } |
| if (pending_layer.GetGraphicsLayer()) { |
| UpdateLayerDebugInfo( |
| *layer, |
| PaintChunk::Id(pending_layer.GetGraphicsLayer()->Id(), |
| DisplayItem::kUninitializedType), |
| pending_layer.GetGraphicsLayer()->DebugName(), |
| pending_layer.GetGraphicsLayer()->OwnerNodeId(), |
| GetCompositingReasons(pending_layer, previous_pending_layer), |
| tracking); |
| } else { |
| UpdateLayerDebugInfo( |
| *layer, pending_layer.FirstPaintChunk().id, |
| pending_layer.Chunks().GetPaintArtifact(), |
| GetCompositingReasons(pending_layer, previous_pending_layer), |
| tracking); |
| } |
| previous_pending_layer = &pending_layer; |
| } |
| } |
| |
| CompositingReasons PaintArtifactCompositor::GetCompositingReasons( |
| const PendingLayer& layer, |
| const PendingLayer* previous_layer) const { |
| DCHECK(layer_debug_info_enabled_); |
| |
| if (layer.GetGraphicsLayer()) |
| return layer.GetGraphicsLayer()->GetCompositingReasons(); |
| |
| if (layer.RequiresOwnLayer()) { |
| if (layer.GetCompositingType() == PendingLayer::kScrollHitTestLayer) |
| return CompositingReason::kOverflowScrolling; |
| switch (layer.FirstDisplayItem().GetType()) { |
| case DisplayItem::kForeignLayerCanvas: |
| return CompositingReason::kCanvas; |
| case DisplayItem::kForeignLayerPlugin: |
| return CompositingReason::kPlugin; |
| case DisplayItem::kForeignLayerVideo: |
| return CompositingReason::kVideo; |
| case DisplayItem::kScrollbarHorizontal: |
| return CompositingReason::kLayerForHorizontalScrollbar; |
| case DisplayItem::kScrollbarVertical: |
| return CompositingReason::kLayerForVerticalScrollbar; |
| default: |
| return CompositingReason::kLayerForOther; |
| } |
| } |
| |
| CompositingReasons reasons = CompositingReason::kNone; |
| if (!previous_layer || |
| &layer.GetPropertyTreeState().Transform() != |
| &previous_layer->GetPropertyTreeState().Transform()) { |
| reasons |= layer.GetPropertyTreeState() |
| .Transform() |
| .DirectCompositingReasonsForDebugging(); |
| if (!layer.GetPropertyTreeState() |
| .Transform() |
| .BackfaceVisibilitySameAsParent()) |
| reasons |= CompositingReason::kBackfaceVisibilityHidden; |
| } |
| |
| if (!previous_layer || &layer.GetPropertyTreeState().Effect() != |
| &previous_layer->GetPropertyTreeState().Effect()) { |
| const auto& effect = layer.GetPropertyTreeState().Effect(); |
| if (effect.HasDirectCompositingReasons()) |
| reasons |= effect.DirectCompositingReasonsForDebugging(); |
| if (reasons == CompositingReason::kNone && |
| layer.GetCompositingType() == PendingLayer::kOther) { |
| if (effect.Opacity() != 1.0f) |
| reasons |= CompositingReason::kOpacityWithCompositedDescendants; |
| if (!effect.Filter().IsEmpty()) |
| reasons |= CompositingReason::kFilterWithCompositedDescendants; |
| if (effect.BlendMode() == SkBlendMode::kDstIn) |
| reasons |= CompositingReason::kMaskWithCompositedDescendants; |
| else if (effect.BlendMode() != SkBlendMode::kSrcOver) |
| reasons |= CompositingReason::kBlendingWithCompositedDescendants; |
| } |
| } |
| |
| if (reasons == CompositingReason::kNone && |
| layer.GetCompositingType() == PendingLayer::kOverlap) |
| reasons = CompositingReason::kOverlap; |
| |
| return reasons; |
| } |
| |
| Vector<cc::Layer*> PaintArtifactCompositor::SynthesizedClipLayersForTesting() |
| const { |
| Vector<cc::Layer*> synthesized_clip_layers; |
| for (const auto& entry : synthesized_clip_cache_) |
| synthesized_clip_layers.push_back(entry.synthesized_clip->Layer()); |
| return synthesized_clip_layers; |
| } |
| |
| void PaintArtifactCompositor::ClearPropertyTreeChangedState() { |
| for (auto& layer : pending_layers_) { |
| if (RuntimeEnabledFeatures::CompositeAfterPaintEnabled()) { |
| // The chunks ref-counted property tree state keeps the |layer|'s non-ref |
| // property tree pointers alive and all chunk property tree states should |
| // be descendants of the |layer|'s. Therefore, we can just CHECK that the |
| // first chunk's references are keeping the |layer|'s property tree state |
| // alive. |
| CHECK(!layer.Chunks().IsEmpty()); |
| const auto& layer_state = layer.GetPropertyTreeState(); |
| const auto& first_chunk_state = |
| layer.Chunks().begin()->properties.GetPropertyTreeState(); |
| CHECK( |
| layer_state.Transform().IsAncestorOf(first_chunk_state.Transform())); |
| CHECK(layer_state.Clip().IsAncestorOf(first_chunk_state.Clip())); |
| CHECK(layer_state.Effect().IsAncestorOf(first_chunk_state.Effect())); |
| } |
| |
| layer.GetPropertyTreeState().ClearChangedTo(PropertyTreeState::Root()); |
| |
| PaintChunkSubset chunks = |
| layer.GetGraphicsLayer() && |
| layer.GetGraphicsLayer()->PaintsContentOrHitTest() |
| ? PaintChunkSubset(layer.GetGraphicsLayer() |
| ->GetPaintController() |
| .GetPaintArtifactShared()) |
| : layer.Chunks(); |
| for (auto& chunk : chunks) { |
| // Calling |ClearChangedTo| for every chunk could be O(|property nodes|^2) |
| // in the worst case and could be optimized by caching which nodes that |
| // have already been cleared. |
| chunk.properties.GetPropertyTreeState().ClearChangedTo( |
| layer.GetPropertyTreeState()); |
| } |
| } |
| } |
| |
| size_t PaintArtifactCompositor::ApproximateUnsharedMemoryUsage() const { |
| size_t result = sizeof(*this) + content_layer_clients_.CapacityInBytes() + |
| synthesized_clip_cache_.CapacityInBytes() + |
| scroll_hit_test_layers_.CapacityInBytes() + |
| scrollbar_layers_.CapacityInBytes() + |
| pending_layers_.CapacityInBytes(); |
| |
| for (auto& client : content_layer_clients_) |
| result += client->ApproximateUnsharedMemoryUsage(); |
| |
| for (auto& layer : pending_layers_) { |
| size_t chunks_size = layer.Chunks().ApproximateUnsharedMemoryUsage(); |
| DCHECK_GE(chunks_size, sizeof(layer.Chunks())); |
| result += chunks_size - sizeof(layer.Chunks()); |
| } |
| return result; |
| } |
| |
| void PaintArtifactCompositor::SetScrollbarNeedsDisplay( |
| CompositorElementId element_id) { |
| for (auto& layer : scrollbar_layers_) { |
| if (layer->element_id() == element_id) { |
| layer->SetNeedsDisplay(); |
| return; |
| } |
| } |
| } |
| |
| void LayerListBuilder::Add(scoped_refptr<cc::Layer> layer) { |
| #if DCHECK_IS_ON() |
| DCHECK(list_valid_); |
| DCHECK(!layer_ids_.Contains(layer->id())); |
| layer_ids_.insert(layer->id()); |
| #endif |
| list_.push_back(layer); |
| } |
| |
| cc::LayerList LayerListBuilder::Finalize() { |
| DCHECK(list_valid_); |
| list_valid_ = false; |
| return std::move(list_); |
| } |
| |
| #if DCHECK_IS_ON() |
| void PaintArtifactCompositor::ShowDebugData() { |
| LOG(INFO) << GetLayersAsJSON(kLayerTreeIncludesDebugInfo | |
| kLayerTreeIncludesDetailedInvalidations) |
| ->ToPrettyJSONString() |
| .Utf8(); |
| } |
| #endif |
| |
| } // namespace blink |