| // Copyright 2025 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #ifndef CC_LAYERS_TILE_BASED_LAYER_IMPL_H_ |
| #define CC_LAYERS_TILE_BASED_LAYER_IMPL_H_ |
| |
| #include <algorithm> |
| #include <vector> |
| |
| #include "cc/base/math_util.h" |
| #include "cc/cc_export.h" |
| #include "cc/debug/debug_colors.h" |
| #include "cc/layers/append_quads_context.h" |
| #include "cc/layers/layer_impl.h" |
| #include "cc/layers/solid_color_layer_impl.h" |
| #include "cc/tiles/tiling_set_coverage_iterator.h" |
| #include "cc/trees/layer_tree_impl.h" |
| #include "components/viz/common/quads/debug_border_draw_quad.h" |
| |
| namespace cc { |
| |
| // Base class for layer impls that manipulate tiles (e.g., PictureLayerImpl |
| // and TileDisplayLayerImpl). |
| template <typename Tiling> |
| class CC_EXPORT TileBasedLayerImpl : public LayerImpl { |
| public: |
| enum class TilingResolution { |
| kHigh, |
| kAboveHigh, |
| kBelowHigh, |
| }; |
| |
| TileBasedLayerImpl(const TileBasedLayerImpl&) = delete; |
| ~TileBasedLayerImpl() override = default; |
| |
| TileBasedLayerImpl& operator=(const TileBasedLayerImpl&) = delete; |
| |
| void SetIsBackdropFilterMask(bool is_backdrop_filter_mask) { |
| if (this->is_backdrop_filter_mask() == is_backdrop_filter_mask) { |
| return; |
| } |
| is_backdrop_filter_mask_ = is_backdrop_filter_mask; |
| SetNeedsPushProperties(); |
| } |
| |
| bool is_backdrop_filter_mask() const { return is_backdrop_filter_mask_; } |
| |
| // LayerImpl overrides: |
| void AppendQuads(const AppendQuadsContext& context, |
| viz::CompositorRenderPass* render_pass, |
| AppendQuadsData* append_quads_data) override; |
| gfx::Rect GetEnclosingVisibleRectInTargetSpace() const override { |
| return GetScaledEnclosingVisibleRectInTargetSpace( |
| GetMaximumContentsScaleForUseInAppendQuads()); |
| } |
| |
| void SetSolidColor(std::optional<SkColor4f> color) { solid_color_ = color; } |
| |
| std::vector<float>& GetLastAppendQuadsScalesForTesting() { |
| return last_append_quads_scales_; |
| } |
| |
| protected: |
| TileBasedLayerImpl(LayerTreeImpl* tree_impl, int id) |
| : LayerImpl(tree_impl, id) {} |
| |
| std::optional<SkColor4f> solid_color() const { return solid_color_; } |
| |
| std::optional<gfx::Rect> CalculateScaledCullRect( |
| float max_contents_scale) const; |
| |
| // A helper for AppendQuadsSpecialization() that returns true if the current |
| // tile should be skipped. `visible_geometry_rect` is an out-param that will |
| // be populated when the tile should not be skipped. |
| bool ShouldSkipTile(const gfx::Rect& geometry_rect, |
| const gfx::Rect& scaled_recorded_bounds, |
| const Occlusion& scaled_occlusion, |
| gfx::Rect& visible_geometry_rect) const; |
| |
| void ClearLastAppendQuadsScales() { last_append_quads_scales_.clear(); } |
| |
| void AddScaleToLastAppendQuadsScales(float scale) { |
| if (last_append_quads_scales_.empty() || |
| last_append_quads_scales_.back() != scale) { |
| last_append_quads_scales_.push_back(scale); |
| } |
| } |
| |
| bool LastAppendQuadsScalesContains(float scale) const { |
| return std::ranges::contains(last_append_quads_scales_, scale); |
| } |
| |
| private: |
| // Invoked when the draw mode is DRAW_MODE_RESOURCELESS_SOFTWARE. |
| virtual void AppendQuadsForResourcelessSoftwareDraw( |
| const AppendQuadsContext& context, |
| viz::CompositorRenderPass* render_pass, |
| AppendQuadsData* append_quads_data, |
| viz::SharedQuadState* shared_quad_state, |
| const Occlusion& scaled_occlusion) = 0; |
| |
| // Called when AppendQuads() goes through a flow for which behavior is |
| // subclass-specific (i.e., not defined in TileBasedLayerImpl::AppendQuads() |
| // itself). `quad_offset` is the offset by which appended quads should be |
| // adjusted. |
| // NOTE: `shared_quad_state` is *not* adjusted by `quad_offset` when passed |
| // into this method to allow implementations to operate on the original state |
| // (e.g., to locate tiles in layer space). However, it will be properly |
| // adjusted before AppendQuads() returns to the caller. |
| virtual void AppendQuadsSpecialization( |
| const AppendQuadsContext& context, |
| viz::CompositorRenderPass* render_pass, |
| AppendQuadsData* append_quads_data, |
| viz::SharedQuadState* shared_quad_state, |
| const Occlusion& scaled_occlusion, |
| const gfx::Vector2d& quad_offset, |
| float max_contents_scale) = 0; |
| |
| virtual float GetMaximumContentsScaleForUseInAppendQuads() const = 0; |
| |
| virtual bool IsDirectlyCompositedImage() const = 0; |
| |
| virtual TilingResolution GetTilingResolutionForDebugBorders( |
| const Tiling* tiling) const = 0; |
| |
| virtual TilingSetCoverageIterator<Tiling> Cover( |
| const gfx::Rect& coverage_rect, |
| float coverage_scale, |
| float ideal_contents_scale) = 0; |
| |
| virtual float GetIdealContentsScaleKey() const = 0; |
| |
| // Appends a solid-color quad with color `color`. |
| void AppendSolidQuad(viz::CompositorRenderPass* render_pass, |
| AppendQuadsData* append_quads_data, |
| SkColor4f color); |
| |
| bool is_backdrop_filter_mask_ : 1 = false; |
| std::optional<SkColor4f> solid_color_; |
| |
| // List of tiling scales that were used last time we appended quads. This is |
| // used as an optimization not to remove tilings if they are still being |
| // drawn. |
| std::vector<float> last_append_quads_scales_; |
| }; |
| |
| template <typename Tiling> |
| void TileBasedLayerImpl<Tiling>::AppendQuads( |
| const AppendQuadsContext& context, |
| viz::CompositorRenderPass* render_pass, |
| AppendQuadsData* append_quads_data) { |
| // RenderSurfaceImpl::AppendQuads sets mask properties in the DrawQuad for |
| // the masked surface, which will apply to both the backdrop filter and the |
| // contents of the masked surface, so we should not append quads of the mask |
| // layer in DstIn blend mode which would apply the mask in another codepath. |
| if (is_backdrop_filter_mask()) { |
| return; |
| } |
| |
| if (solid_color()) { |
| AppendSolidQuad(render_pass, append_quads_data, *solid_color()); |
| return; |
| } |
| |
| viz::SharedQuadState* shared_quad_state = |
| render_pass->CreateAndAppendSharedQuadState(); |
| PopulateScaledSharedQuadState(shared_quad_state, |
| GetMaximumContentsScaleForUseInAppendQuads(), |
| contents_opaque()); |
| |
| if (IsDirectlyCompositedImage()) { |
| // Directly composited images should be clipped to the layer's content rect. |
| // When a PictureLayerTiling is created for a directly composited image, the |
| // layer bounds are multiplied by the raster scale in order to compute the |
| // tile size. If the aspect ratio of the layer doesn't match that of the |
| // image, it's possible that one of the dimensions of the resulting size |
| // (layer bounds * raster scale) is a fractional number, as raster scale |
| // does not scale x and y independently. |
| // When this happens, the ToEnclosingRect() operation in |
| // |PictureLayerTiling::EnclosingContentsRectFromLayer()| will |
| // create a tiling that, when scaled by |max_contents_scale| above, is |
| // larger than the layer bounds by a fraction of a pixel. |
| gfx::Rect bounds_in_target_space = MathUtil::MapEnclosingClippedRect( |
| draw_properties().target_space_transform, gfx::Rect(bounds())); |
| if (is_clipped()) { |
| bounds_in_target_space.Intersect(draw_properties().clip_rect); |
| } |
| |
| if (shared_quad_state->clip_rect) { |
| bounds_in_target_space.Intersect(*shared_quad_state->clip_rect); |
| } |
| |
| shared_quad_state->clip_rect = bounds_in_target_space; |
| } |
| |
| const Occlusion scaled_occlusion = |
| draw_properties() |
| .occlusion_in_content_space.GetOcclusionWithGivenDrawTransform( |
| shared_quad_state->quad_to_target_transform); |
| |
| if (context.draw_mode == DRAW_MODE_RESOURCELESS_SOFTWARE) { |
| AppendQuadsForResourcelessSoftwareDraw(context, render_pass, |
| append_quads_data, shared_quad_state, |
| scaled_occlusion); |
| return; |
| } |
| |
| // If the visible rect is scrolled far enough away, then we may run into a |
| // floating point precision in AA calculations in the renderer. See |
| // crbug.com/765297. In order to avoid this, we shift the quads up from where |
| // they logically reside and adjust the shared_quad_state's transform instead. |
| // We only do this in scale/translate matrices to ensure the math is correct. |
| // NOTE: Implementations of AppendQuadsSpecialization() need to use the |
| // original state in `shared_quad_state` to correctly locate the tiles to |
| // draw. For this reason, we delay adjusting `shared_quad_state` itself until |
| // the bottom of the method below. |
| gfx::Vector2d quad_offset; |
| if (shared_quad_state->quad_to_target_transform.IsScaleOrTranslation()) { |
| const auto& visible_rect = shared_quad_state->visible_quad_layer_rect; |
| quad_offset = gfx::Vector2d(-visible_rect.x(), -visible_rect.y()); |
| } |
| |
| gfx::Rect debug_border_rect(shared_quad_state->quad_layer_rect); |
| debug_border_rect.Offset(quad_offset); |
| AppendDebugBorderQuad(render_pass, debug_border_rect, shared_quad_state, |
| append_quads_data); |
| |
| const float device_scale_factor = layer_tree_impl()->device_scale_factor(); |
| const float max_contents_scale = GetMaximumContentsScaleForUseInAppendQuads(); |
| const float ideal_scale_key = GetIdealContentsScaleKey(); |
| |
| // Append debug borders for the quads in this layer. |
| if (ShowDebugBorders(DebugBorderType::LAYER)) { |
| for (auto iter = Cover(shared_quad_state->visible_quad_layer_rect, |
| max_contents_scale, ideal_scale_key); |
| iter; ++iter) { |
| SkColor4f color; |
| float width; |
| if (*iter && iter->IsReadyToDraw()) { |
| TileDrawInfo::Mode mode = iter->draw_mode(); |
| if (mode == TileDrawInfo::SOLID_COLOR_MODE) { |
| color = DebugColors::SolidColorTileBorderColor(); |
| width = DebugColors::SolidColorTileBorderWidth(device_scale_factor); |
| } else if (mode == TileDrawInfo::OOM_MODE) { |
| color = DebugColors::OOMTileBorderColor(); |
| width = DebugColors::OOMTileBorderWidth(device_scale_factor); |
| } else { |
| switch (GetTilingResolutionForDebugBorders(iter.CurrentTiling())) { |
| case TilingResolution::kHigh: |
| color = DebugColors::HighResTileBorderColor(); |
| width = DebugColors::HighResTileBorderWidth(device_scale_factor); |
| break; |
| case TilingResolution::kAboveHigh: |
| color = DebugColors::AboveHighResTileBorderColor(); |
| width = |
| DebugColors::AboveHighResTileBorderWidth(device_scale_factor); |
| break; |
| case TilingResolution::kBelowHigh: |
| color = DebugColors::BelowHighResTileBorderColor(); |
| width = |
| DebugColors::BelowHighResTileBorderWidth(device_scale_factor); |
| break; |
| } |
| } |
| } else { |
| color = DebugColors::MissingTileBorderColor(); |
| width = DebugColors::MissingTileBorderWidth(device_scale_factor); |
| } |
| |
| auto* debug_border_quad = |
| render_pass->CreateAndAppendDrawQuad<viz::DebugBorderDrawQuad>(); |
| gfx::Rect geometry_rect = iter.geometry_rect(); |
| geometry_rect.Offset(quad_offset); |
| gfx::Rect visible_geometry_rect = geometry_rect; |
| debug_border_quad->SetNew(shared_quad_state, geometry_rect, |
| visible_geometry_rect, color, width); |
| } |
| } |
| |
| // Clear the set of scales that were used in the previous |
| // AppendQuadsSpecialization() so that subclasses can track the scales used in |
| // *this* invocation in order to determine which scales are now unused and can |
| // be considered for removal. |
| ClearLastAppendQuadsScales(); |
| |
| AppendQuadsSpecialization(context, render_pass, append_quads_data, |
| shared_quad_state, scaled_occlusion, quad_offset, |
| max_contents_scale); |
| |
| // Adjust shared_quad_state with the quad_offset, since by contract |
| // AppendQuadsSpecialization() has adjusted each quad appended by that offset. |
| shared_quad_state->quad_to_target_transform.Translate(-quad_offset); |
| shared_quad_state->quad_layer_rect.Offset(quad_offset); |
| shared_quad_state->visible_quad_layer_rect.Offset(quad_offset); |
| } |
| |
| template <typename Tiling> |
| void TileBasedLayerImpl<Tiling>::AppendSolidQuad( |
| viz::CompositorRenderPass* render_pass, |
| AppendQuadsData* append_quads_data, |
| SkColor4f color) { |
| // TODO(crbug.com/41468388): This is still hard-coded at 1.0. This has some |
| // history: |
| // - for crbug.com/769319, the contents scale was allowed to change, to |
| // avoid blurring on high-dpi screens. |
| // - for crbug.com/796558, the max device scale was hard-coded back to 1.0 |
| // for single-tile masks, to avoid problems with transforms. |
| // To avoid those transform/scale bugs, this is currently left at 1.0. See |
| // crbug.com/979672 for more context and test links. |
| float max_contents_scale = 1; |
| |
| // The downstream CA layers use shared_quad_state to generate resources of |
| // the right size even if it is a solid color picture layer. |
| viz::SharedQuadState* shared_quad_state = |
| render_pass->CreateAndAppendSharedQuadState(); |
| PopulateScaledSharedQuadState(shared_quad_state, max_contents_scale, |
| contents_opaque()); |
| |
| AppendDebugBorderQuad(render_pass, gfx::Rect(bounds()), shared_quad_state, |
| append_quads_data); |
| |
| gfx::Rect scaled_visible_layer_rect = |
| shared_quad_state->visible_quad_layer_rect; |
| Occlusion occlusion = draw_properties().occlusion_in_content_space; |
| |
| EffectNode* effect_node = GetEffectTree().Node(effect_tree_index()); |
| SolidColorLayerImpl::AppendSolidQuads( |
| render_pass, occlusion, shared_quad_state, scaled_visible_layer_rect, |
| color, !layer_tree_impl()->settings().enable_edge_anti_aliasing, |
| effect_node->blend_mode, append_quads_data); |
| } |
| |
| template <typename Tiling> |
| std::optional<gfx::Rect> TileBasedLayerImpl<Tiling>::CalculateScaledCullRect( |
| float max_contents_scale) const { |
| const ScrollTree& scroll_tree = |
| layer_tree_impl()->property_trees()->scroll_tree(); |
| if (const ScrollNode* scroll_node = scroll_tree.Node(scroll_tree_index())) { |
| if (transform_tree_index() == scroll_node->transform_id) { |
| if (const gfx::Rect* cull_rect = |
| scroll_tree.ScrollingContentsCullRect(scroll_node->element_id)) { |
| return gfx::ToEnclosingRect(gfx::ScaleRect( |
| // Convert into layer space. |
| gfx::RectF(*cull_rect) - offset_to_transform_parent(), |
| max_contents_scale)); |
| } |
| } |
| } |
| return std::nullopt; |
| } |
| |
| template <typename Tiling> |
| bool TileBasedLayerImpl<Tiling>::ShouldSkipTile( |
| const gfx::Rect& geometry_rect, |
| const gfx::Rect& scaled_recorded_bounds, |
| const Occlusion& scaled_occlusion, |
| gfx::Rect& visible_geometry_rect) const { |
| if (!scaled_recorded_bounds.Intersects(geometry_rect)) { |
| // This happens when the tiling rect is snapped to be bigger than the |
| // recorded bounds, and CoverageIterator returns a "missing" tile |
| // to cover some of the empty area. The tile should be ignored, otherwise |
| // it would be mistakenly treated as checkerboarded and drawn with the |
| // safe background color. |
| // TODO(crbug.com/328677988): Ideally we should check intersection with |
| // visible_geometry_rect and remove the visible_geometry_rect.IsEmpty() |
| // condition below. |
| return true; |
| } |
| visible_geometry_rect = |
| scaled_occlusion.GetUnoccludedContentRect(geometry_rect); |
| return visible_geometry_rect.IsEmpty(); |
| } |
| |
| } // namespace cc |
| |
| #endif // CC_LAYERS_TILE_BASED_LAYER_IMPL_H_ |