| // Copyright 2014 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "components/viz/service/display/surface_aggregator.h" |
| |
| #include <stddef.h> |
| |
| #include <algorithm> |
| #include <map> |
| #include <utility> |
| |
| #include "base/auto_reset.h" |
| #include "base/check_op.h" |
| #include "base/containers/adapters.h" |
| #include "base/containers/cxx20_erase.h" |
| #include "base/functional/bind.h" |
| #include "base/logging.h" |
| #include "base/metrics/histogram_macros.h" |
| #include "base/numerics/ranges.h" |
| #include "base/timer/elapsed_timer.h" |
| #include "base/trace_event/trace_event.h" |
| #include "cc/base/math_util.h" |
| #include "components/viz/common/features.h" |
| #include "components/viz/common/quads/aggregated_render_pass.h" |
| #include "components/viz/common/quads/aggregated_render_pass_draw_quad.h" |
| #include "components/viz/common/quads/compositor_frame.h" |
| #include "components/viz/common/quads/compositor_render_pass_draw_quad.h" |
| #include "components/viz/common/quads/debug_border_draw_quad.h" |
| #include "components/viz/common/quads/draw_quad.h" |
| #include "components/viz/common/quads/shared_quad_state.h" |
| #include "components/viz/common/quads/solid_color_draw_quad.h" |
| #include "components/viz/common/quads/surface_draw_quad.h" |
| #include "components/viz/common/quads/texture_draw_quad.h" |
| #include "components/viz/common/quads/yuv_video_draw_quad.h" |
| #include "components/viz/common/surfaces/surface_range.h" |
| #include "components/viz/common/viz_utils.h" |
| #include "components/viz/service/debugger/viz_debugger.h" |
| #include "components/viz/service/display/aggregated_frame.h" |
| #include "components/viz/service/display/display_resource_provider.h" |
| #include "components/viz/service/display/renderer_utils.h" |
| #include "components/viz/service/display/resolved_frame_data.h" |
| #include "components/viz/service/surfaces/surface.h" |
| #include "components/viz/service/surfaces/surface_allocation_group.h" |
| #include "components/viz/service/surfaces/surface_client.h" |
| #include "components/viz/service/surfaces/surface_manager.h" |
| #include "ui/gfx/geometry/angle_conversions.h" |
| #include "ui/gfx/geometry/rect.h" |
| #include "ui/gfx/geometry/rect_conversions.h" |
| #include "ui/gfx/overlay_transform_utils.h" |
| |
| namespace viz { |
| |
| struct MaskFilterInfoExt { |
| MaskFilterInfoExt() = default; |
| MaskFilterInfoExt(const gfx::MaskFilterInfo& mask_filter_info_arg, |
| bool is_fast_rounded_corner_arg, |
| const gfx::Transform target_transform) |
| : mask_filter_info(mask_filter_info_arg), |
| is_fast_rounded_corner(is_fast_rounded_corner_arg) { |
| mask_filter_info.ApplyTransform(target_transform); |
| } |
| |
| // Returns true if the quads from |merge_render_pass| can be merged into |
| // the embedding render pass based on mask filter info. |
| bool CanMergeMaskFilterInfo( |
| const CompositorRenderPass& merge_render_pass) const { |
| // If the embedding quad has no mask filter, then we do not have to block |
| // merging. |
| if (mask_filter_info.IsEmpty()) |
| return true; |
| |
| // If the embedding quad has rounded corner and it is not a fast rounded |
| // corner, we cannot merge. |
| if (mask_filter_info.HasRoundedCorners() && !is_fast_rounded_corner) |
| return false; |
| |
| // If any of the quads in the render pass to merged has a mask filter of its |
| // own, then we cannot merge. |
| for (const auto* sqs : merge_render_pass.shared_quad_state_list) { |
| if (!sqs->mask_filter_info.IsEmpty()) |
| return false; |
| } |
| return true; |
| } |
| |
| gfx::MaskFilterInfo mask_filter_info; |
| bool is_fast_rounded_corner; |
| }; |
| |
| namespace { |
| |
| // Used for determine when to treat opacity close to 1.f as opaque. The value is |
| // chosen to be smaller than 1/255. |
| constexpr float kOpacityEpsilon = 0.001f; |
| |
| void MoveMatchingRequests( |
| CompositorRenderPassId render_pass_id, |
| std::multimap<CompositorRenderPassId, std::unique_ptr<CopyOutputRequest>>* |
| copy_requests, |
| std::vector<std::unique_ptr<CopyOutputRequest>>* output_requests) { |
| auto request_range = copy_requests->equal_range(render_pass_id); |
| for (auto it = request_range.first; it != request_range.second; ++it) { |
| DCHECK(it->second); |
| output_requests->push_back(std::move(it->second)); |
| } |
| copy_requests->erase(request_range.first, request_range.second); |
| } |
| |
| // Returns true if the damage rect is valid. |
| bool CalculateQuadSpaceDamageRect( |
| const gfx::Transform& quad_to_target_transform, |
| const gfx::Transform& target_to_root_transform, |
| const gfx::Rect& root_damage_rect, |
| gfx::Rect* quad_space_damage_rect) { |
| gfx::Transform quad_to_root_transform = |
| target_to_root_transform * quad_to_target_transform; |
| gfx::Transform inverse_transform; |
| bool inverse_valid = quad_to_root_transform.GetInverse(&inverse_transform); |
| if (!inverse_valid) |
| return false; |
| |
| *quad_space_damage_rect = cc::MathUtil::ProjectEnclosingClippedRect( |
| inverse_transform, root_damage_rect); |
| return true; |
| } |
| |
| // Create a clip rect for an aggregated quad from the original clip rect and |
| // the clip rect from the surface it's on. |
| absl::optional<gfx::Rect> CalculateClipRect( |
| const absl::optional<gfx::Rect>& surface_clip, |
| const absl::optional<gfx::Rect>& quad_clip, |
| const gfx::Transform& target_transform) { |
| absl::optional<gfx::Rect> out_clip; |
| if (surface_clip) |
| out_clip = surface_clip; |
| |
| if (quad_clip) { |
| // TODO(jamesr): This only works if target_transform maps integer |
| // rects to integer rects. |
| gfx::Rect final_clip = |
| cc::MathUtil::MapEnclosingClippedRect(target_transform, *quad_clip); |
| if (out_clip) |
| out_clip->Intersect(final_clip); |
| else |
| out_clip = final_clip; |
| } |
| |
| return out_clip; |
| } |
| |
| // Creates a new SharedQuadState in |dest_render_pass| based on |source_sqs| |
| // plus additional modified values. |
| // - |source_sqs| is the SharedQuadState to copy from. |
| // - |quad_to_target_transform| replaces the equivalent |source_sqs| value. |
| // - |target_transform| is an additional transform to add. Used when merging the |
| // root render pass of a surface into the embedding render pass. |
| // - |quad_layer_rect| replaces the equivalent |source_sqs| value. |
| // - |visible_quad_layer_rect| replaces the equivalent |source_sqs| value. |
| // - |mask_filter_info_ext| replaces the equivalent |source_sqs| values. |
| // - |added_clip_rect| is an additional clip rect added to the quad clip rect. |
| // - |dest_render_pass| is where the new SharedQuadState will be created. |
| SharedQuadState* CopyAndScaleSharedQuadState( |
| const SharedQuadState* source_sqs, |
| uint32_t client_namespace_id, |
| const gfx::Transform& quad_to_target_transform, |
| const gfx::Transform& target_transform, |
| const gfx::Rect& quad_layer_rect, |
| const gfx::Rect& visible_quad_layer_rect, |
| const absl::optional<gfx::Rect>& added_clip_rect, |
| const MaskFilterInfoExt& mask_filter_info_ext, |
| AggregatedRenderPass* dest_render_pass) { |
| auto* shared_quad_state = dest_render_pass->CreateAndAppendSharedQuadState(); |
| auto new_clip_rect = CalculateClipRect(added_clip_rect, source_sqs->clip_rect, |
| target_transform); |
| |
| // target_transform contains any transformation that may exist |
| // between the context that these quads are being copied from (i.e. the |
| // surface's draw transform when aggregated from within a surface) to the |
| // target space of the pass. This will be identity except when copying the |
| // root draw pass from a surface into a pass when the surface draw quad's |
| // transform is not identity. |
| gfx::Transform new_transform = quad_to_target_transform; |
| new_transform.PostConcat(target_transform); |
| |
| shared_quad_state->SetAll( |
| new_transform, quad_layer_rect, visible_quad_layer_rect, |
| mask_filter_info_ext.mask_filter_info, new_clip_rect, |
| source_sqs->are_contents_opaque, source_sqs->opacity, |
| source_sqs->blend_mode, source_sqs->sorting_context_id, |
| source_sqs->layer_id); |
| shared_quad_state->layer_namespace_id = client_namespace_id; |
| shared_quad_state->is_fast_rounded_corner = |
| mask_filter_info_ext.is_fast_rounded_corner; |
| return shared_quad_state; |
| } |
| |
| // Creates a new SharedQuadState in |dest_render_pass| and copies |source_sqs| |
| // into it. See CopyAndScaleSharedQuadState() for full documentation. |
| SharedQuadState* CopySharedQuadState( |
| const SharedQuadState* source_sqs, |
| uint32_t client_namespace_id, |
| const gfx::Transform& target_transform, |
| const absl::optional<gfx::Rect>& added_clip_rect, |
| const MaskFilterInfoExt& mask_filter_info, |
| AggregatedRenderPass* dest_render_pass) { |
| return CopyAndScaleSharedQuadState( |
| source_sqs, client_namespace_id, source_sqs->quad_to_target_transform, |
| target_transform, source_sqs->quad_layer_rect, |
| source_sqs->visible_quad_layer_rect, added_clip_rect, mask_filter_info, |
| dest_render_pass); |
| } |
| |
| // Returns true if |resolved_pass| needs full damage. This is because: |
| // 1. The render pass pixels will be saved, either by a copy request or into a |
| // cached render pass. This avoids a partially drawn render pass being saved. |
| // 2. The render pass pixels will have a pixel moving foreground filter applied |
| // to them. In this case pixels outside the damage_rect can be moved inside |
| // the damage_rect by the filter. |
| bool RenderPassNeedsFullDamage(const ResolvedPassData& resolved_pass) { |
| auto& aggregation = resolved_pass.aggregation(); |
| return aggregation.in_cached_render_pass || |
| aggregation.in_copy_request_pass || |
| aggregation.in_pixel_moving_filter_pass; |
| } |
| |
| // Computes an enclosing rect in target render pass coordinate space that bounds |
| // where |quad| may contribute pixels. This rect is computed by transforming the |
| // quads |visible_rect|, which is known to be contained by the quads |rect|, and |
| // transforming it into target render pass coordinate space. The rect is then |
| // clipped by SharedQuadState |clip_rect| if one exists. |
| // |
| // Since a quad can only damage pixels it can draw to, the drawable rect is also |
| // the maximum damage rect a quad can contribute (ignoring pixel-moving |
| // filters). |
| gfx::Rect ComputeDrawableRectForQuad(const DrawQuad* quad) { |
| const SharedQuadState* sqs = quad->shared_quad_state; |
| |
| gfx::Rect drawable_rect = cc::MathUtil::MapEnclosingClippedRect( |
| sqs->quad_to_target_transform, quad->visible_rect); |
| if (sqs->clip_rect) |
| drawable_rect.Intersect(*sqs->clip_rect); |
| |
| return drawable_rect; |
| } |
| |
| // This function transforms a rect from its target space to the destination |
| // root target space. If clip_rect is valid, clipping is applied after |
| // transform. |
| gfx::Rect TransformRectToDestRootTargetSpace( |
| const gfx::Rect& rect_in_target_space, |
| const gfx::Transform& target_to_dest_transform, |
| const gfx::Transform& dest_to_root_target_transform, |
| const absl::optional<gfx::Rect>& dest_root_target_clip_rect) { |
| gfx::Transform target_to_dest_root_target_transform = |
| dest_to_root_target_transform * target_to_dest_transform; |
| |
| gfx::Rect rect_in_root_target_space = cc::MathUtil::MapEnclosingClippedRect( |
| target_to_dest_root_target_transform, rect_in_target_space); |
| |
| if (dest_root_target_clip_rect) { |
| rect_in_root_target_space.Intersect(*dest_root_target_clip_rect); |
| } |
| |
| return rect_in_root_target_space; |
| } |
| |
| } // namespace |
| |
| constexpr base::TimeDelta SurfaceAggregator::kHistogramMinTime; |
| constexpr base::TimeDelta SurfaceAggregator::kHistogramMaxTime; |
| |
| struct SurfaceAggregator::PrewalkResult { |
| // This is the set of Surfaces that were referenced by another Surface, but |
| // not included in a SurfaceDrawQuad. |
| base::flat_set<SurfaceId> undrawn_surfaces; |
| bool video_capture_enabled = false; |
| bool frame_sinks_changed = false; |
| bool page_fullscreen_mode = false; |
| gfx::ContentColorUsage content_color_usage = gfx::ContentColorUsage::kSRGB; |
| }; |
| |
| SurfaceAggregator::SurfaceAggregator( |
| SurfaceManager* manager, |
| DisplayResourceProvider* provider, |
| bool aggregate_only_damaged, |
| bool needs_surface_damage_rect_list, |
| ExtraPassForReadbackOption extra_pass_option) |
| : manager_(manager), |
| provider_(provider), |
| aggregate_only_damaged_(aggregate_only_damaged), |
| needs_surface_damage_rect_list_(needs_surface_damage_rect_list), |
| extra_pass_for_readback_option_(extra_pass_option) { |
| DCHECK(manager_); |
| DCHECK(provider_); |
| manager_->AddObserver(this); |
| } |
| |
| SurfaceAggregator::~SurfaceAggregator() { |
| manager_->RemoveObserver(this); |
| |
| contained_surfaces_.clear(); |
| contained_frame_sinks_.clear(); |
| |
| // Notify client of all surfaces being removed. |
| ProcessAddedAndRemovedSurfaces(); |
| } |
| |
| // This function is called at each render pass - CopyQuadsToPass(). |
| void SurfaceAggregator::AddRenderPassFilterDamageToDamageList( |
| const ResolvedFrameData& resolved_frame, |
| const CompositorRenderPassDrawQuad* render_pass_quad, |
| const gfx::Transform& parent_target_transform, |
| const absl::optional<gfx::Rect>& dest_root_target_clip_rect, |
| const gfx::Transform& dest_transform_to_root_target) { |
| const CompositorRenderPassId child_pass_id = render_pass_quad->render_pass_id; |
| const ResolvedPassData& child_resolved_pass = |
| resolved_frame.GetRenderPassDataById(child_pass_id); |
| const CompositorRenderPass& child_render_pass = |
| child_resolved_pass.render_pass(); |
| |
| // Add damages from render passes with pixel-moving foreground filters or |
| // backdrop filters to the surface damage list. |
| if (!child_render_pass.filters.HasFilterThatMovesPixels() && |
| !child_render_pass.backdrop_filters.HasFilterThatMovesPixels()) { |
| return; |
| } |
| |
| gfx::Rect damage_rect = render_pass_quad->rect; |
| gfx::Rect damage_rect_in_target_space; |
| if (child_render_pass.filters.HasFilterThatMovesPixels()) { |
| // The size of pixel-moving foreground filter is allowed to expand. |
| // No intersecting shared_quad_state->clip_rect for the expanded rect. |
| damage_rect_in_target_space = |
| GetExpandedRectWithPixelMovingForegroundFilter( |
| *render_pass_quad, child_render_pass.filters); |
| } else if (child_render_pass.backdrop_filters.HasFilterThatMovesPixels()) { |
| const auto* shared_quad_state = render_pass_quad->shared_quad_state; |
| damage_rect_in_target_space = cc::MathUtil::MapEnclosingClippedRect( |
| shared_quad_state->quad_to_target_transform, damage_rect); |
| if (shared_quad_state->clip_rect) { |
| damage_rect_in_target_space.Intersect( |
| shared_quad_state->clip_rect.value()); |
| } |
| } |
| |
| gfx::Rect damage_rect_in_root_target_space = |
| TransformRectToDestRootTargetSpace( |
| damage_rect_in_target_space, parent_target_transform, |
| dest_transform_to_root_target, dest_root_target_clip_rect); |
| |
| // The whole render pass rect with pixel-moving foreground filters or |
| // backdrop filters is considered damaged if it intersects with the other |
| // damages. |
| if (damage_rect_in_root_target_space.Intersects(root_damage_rect_)) { |
| // Since |damage_rect_in_root_target_space| is available, just pass this |
| // rect and reset the other arguments. |
| AddSurfaceDamageToDamageList( |
| damage_rect_in_root_target_space, |
| /*parent_target_transform*/ gfx::Transform(), |
| /*dest_root_target_clip_rect*/ {}, |
| /*dest_transform_to_root_target*/ gfx::Transform(), |
| /*resolved_frame=*/nullptr); |
| } |
| } |
| |
| void SurfaceAggregator::AddSurfaceDamageToDamageList( |
| const gfx::Rect& default_damage_rect, |
| const gfx::Transform& parent_target_transform, |
| const absl::optional<gfx::Rect>& dest_root_target_clip_rect, |
| const gfx::Transform& dest_transform_to_root_target, |
| const ResolvedFrameData* resolved_frame) { |
| gfx::Rect damage_rect; |
| if (!resolved_frame) { |
| // When the surface is null, it's either the surface is lost or it comes |
| // from a render pass with filters. |
| damage_rect = default_damage_rect; |
| } else { |
| if (RenderPassNeedsFullDamage(resolved_frame->GetRootRenderPassData())) { |
| damage_rect = resolved_frame->GetOutputRect(); |
| } else { |
| damage_rect = resolved_frame->GetSurfaceDamage(); |
| } |
| } |
| |
| if (damage_rect.IsEmpty()) { |
| current_zero_damage_rect_is_not_recorded_ = true; |
| return; |
| } |
| current_zero_damage_rect_is_not_recorded_ = false; |
| |
| gfx::Rect damage_rect_in_root_target_space = |
| TransformRectToDestRootTargetSpace( |
| /*rect_in_target_space=*/damage_rect, parent_target_transform, |
| dest_transform_to_root_target, dest_root_target_clip_rect); |
| |
| surface_damage_rect_list_->push_back(damage_rect_in_root_target_space); |
| } |
| |
| // This function returns the overlay candidate quad ptr which has an |
| // overlay_damage_index pointing to the its damage rect in |
| // surface_damage_rect_list_. |overlay_damage_index| will be saved in the shared |
| // quad state later. |
| // This function is called at CopyQuadsToPass(). |
| const DrawQuad* SurfaceAggregator::FindQuadWithOverlayDamage( |
| const CompositorRenderPass& source_pass, |
| AggregatedRenderPass* dest_pass, |
| const gfx::Transform& parent_target_transform, |
| const Surface* surface, |
| size_t* overlay_damage_index) { |
| // If we have damage from a surface animation, then we shouldn't have an |
| // overlay candidate from the root render pass, since that's an interpolated |
| // pass with "artificial" damage. |
| if (surface->HasSurfaceAnimationDamage()) |
| return nullptr; |
| |
| // Only process the damage rect at the root render pass, once per surface. |
| const CompositorFrame& frame = surface->GetActiveFrame(); |
| bool is_last_pass_on_src_surface = |
| &source_pass == frame.render_pass_list.back().get(); |
| if (!is_last_pass_on_src_surface) |
| return nullptr; |
| |
| // The occluding damage optimization currently relies on two things - there |
| // can't be any damage above the quad within the surface, and the quad needs |
| // its own SQS for the occluding_damage_rect metadata. |
| const DrawQuad* target_quad = nullptr; |
| for (auto* quad : source_pass.quad_list) { |
| // Quads with |per_quad_damage| do not contribute to the |damage_rect| in |
| // the |source_pass|. These quads are also assumed to have unique SQS |
| // objects. |
| if (source_pass.has_per_quad_damage) { |
| auto optional_damage = GetOptionalDamageRectFromQuad(quad); |
| if (optional_damage.has_value()) { |
| continue; |
| } |
| } |
| |
| if (target_quad == nullptr) { |
| target_quad = quad; |
| } else { |
| // More that one quad without per_quad_damage. |
| target_quad = nullptr; |
| break; |
| } |
| } |
| |
| // No overlay candidate is found. |
| if (!target_quad) |
| return nullptr; |
| |
| // Surface damage for a render pass quad does not include damage from its |
| // children. We skip this quad to avoid the incomplete damage association. |
| if (target_quad->material == DrawQuad::Material::kCompositorRenderPass || |
| target_quad->material == DrawQuad::Material::kSurfaceContent) |
| return nullptr; |
| |
| // Zero damage is not recorded in the surface_damage_rect_list_. |
| // In this case, add an empty damage rect to the list so |
| // |overlay_damage_index| can save this index. |
| if (current_zero_damage_rect_is_not_recorded_) { |
| current_zero_damage_rect_is_not_recorded_ = false; |
| surface_damage_rect_list_->push_back(gfx::Rect()); |
| } |
| |
| // Before assigning a surface damage rect to this quad, make sure that it is |
| // not larger than the quad itself. This is possible when a quad is smaller |
| // than it was last frame, or when it moves. The damage should be the size of |
| // larger rect from last frame because we need to damage what's underneath the |
| // quad. So if we promote the now smaller quad to an overlay this frame we |
| // should not remove this damage rect. i.e. we should not assign the damage |
| // rect to this quad. |
| auto& damage_rect_in_target_space = surface_damage_rect_list_->back(); |
| if (!damage_rect_in_target_space.IsEmpty()) { |
| gfx::Transform transform = |
| parent_target_transform * |
| target_quad->shared_quad_state->quad_to_target_transform; |
| gfx::Rect rect_in_target_space = |
| cc::MathUtil::MapEnclosingClippedRect(transform, target_quad->rect); |
| if (!rect_in_target_space.Contains(damage_rect_in_target_space)) { |
| return nullptr; |
| } |
| } |
| |
| // The latest surface damage rect. |
| *overlay_damage_index = surface_damage_rect_list_->size() - 1; |
| |
| return target_quad; |
| } |
| |
| bool SurfaceAggregator::CanPotentiallyMergePass( |
| const SurfaceDrawQuad& surface_quad) { |
| const SharedQuadState* sqs = surface_quad.shared_quad_state; |
| return surface_quad.allow_merge && |
| base::IsApproximatelyEqual(sqs->opacity, 1.f, kOpacityEpsilon); |
| } |
| |
| void SurfaceAggregator::OnSurfaceDestroyed(const SurfaceId& surface_id) { |
| DCHECK(!is_inside_aggregate_); |
| |
| auto iter = resolved_frames_.find(surface_id); |
| if (iter != resolved_frames_.end()) { |
| TRACE_EVENT0("viz", "SurfaceAggregator::SurfaceDestroyed"); |
| resolved_frames_.erase(iter); |
| } |
| } |
| |
| const ResolvedFrameData* SurfaceAggregator::GetLatestFrameData( |
| const SurfaceId& surface_id) { |
| DCHECK(!is_inside_aggregate_); |
| return GetResolvedFrame(surface_id); |
| } |
| |
| ResolvedFrameData* SurfaceAggregator::GetResolvedFrame( |
| const SurfaceRange& range) { |
| // Find latest in flight surface and cache that result for the duration of |
| // this aggregation, then find ResolvedFrameData for that surface. |
| auto iter = resolved_surface_ranges_.find(range); |
| if (iter == resolved_surface_ranges_.end()) { |
| auto* surface = manager_->GetLatestInFlightSurface(range); |
| SurfaceId surface_id = surface ? surface->surface_id() : SurfaceId(); |
| iter = resolved_surface_ranges_.emplace(range, surface_id).first; |
| } |
| |
| if (!iter->second.is_valid()) { |
| // There is no surface for `range`. |
| return nullptr; |
| } |
| |
| return GetResolvedFrame(iter->second); |
| } |
| |
| ResolvedFrameData* SurfaceAggregator::GetResolvedFrame( |
| const SurfaceId& surface_id) { |
| DCHECK(surface_id.is_valid()); |
| |
| auto iter = resolved_frames_.find(surface_id); |
| if (iter == resolved_frames_.end()) { |
| auto* surface = manager_->GetSurfaceForId(surface_id); |
| if (!surface || !surface->HasActiveFrame()) { |
| // If there is no resolved surface or the surface has no active frame |
| // there is no resolved frame data to return. |
| return nullptr; |
| } |
| |
| uint64_t prev_frame_index = 0u; |
| // If this is the first frame in a new surface there might be damage |
| // compared to the previous frame in a different surface. |
| if (surface->surface_id() != surface->previous_frame_surface_id()) { |
| auto prev_resolved_frame_iter = |
| resolved_frames_.find(surface->previous_frame_surface_id()); |
| if (prev_resolved_frame_iter != resolved_frames_.end()) { |
| prev_frame_index = |
| prev_resolved_frame_iter->second.previous_frame_index(); |
| } |
| } |
| |
| iter = resolved_frames_ |
| .emplace( |
| std::piecewise_construct, std::forward_as_tuple(surface_id), |
| std::forward_as_tuple(provider_, surface, prev_frame_index)) |
| .first; |
| } |
| |
| ResolvedFrameData& resolved_frame = iter->second; |
| Surface* surface = resolved_frame.surface(); |
| |
| if (is_inside_aggregate_ && !resolved_frame.WasUsedInAggregation()) { |
| // Mark the frame as used this aggregation so it persists. |
| resolved_frame.MarkAsUsedInAggregation(); |
| |
| // If there is a new CompositorFrame for `surface` compute resolved frame |
| // data for the new resolved CompositorFrame. |
| if (resolved_frame.previous_frame_index() != |
| surface->GetActiveFrameIndex() || |
| surface->HasSurfaceAnimationDamage()) { |
| base::ElapsedTimer timer; |
| ProcessResolvedFrame(resolved_frame); |
| stats_->declare_resources_time += timer.Elapsed(); |
| } |
| } |
| |
| return &resolved_frame; |
| } |
| |
| void SurfaceAggregator::HandleSurfaceQuad( |
| const CompositorRenderPass& source_pass, |
| const SurfaceDrawQuad* surface_quad, |
| uint32_t embedder_client_namespace_id, |
| float parent_device_scale_factor, |
| const gfx::Transform& target_transform, |
| const absl::optional<gfx::Rect>& added_clip_rect, |
| const absl::optional<gfx::Rect>& dest_root_target_clip_rect, |
| AggregatedRenderPass* dest_pass, |
| bool ignore_undamaged, |
| gfx::Rect* damage_rect_in_quad_space, |
| bool* damage_rect_in_quad_space_valid, |
| const MaskFilterInfoExt& mask_filter_info) { |
| DCHECK(target_transform.Preserves2dAxisAlignment()); |
| |
| SurfaceId primary_surface_id = surface_quad->surface_range.end(); |
| const ResolvedFrameData* resolved_frame = |
| GetResolvedFrame(surface_quad->surface_range); |
| |
| // |added_clip_rect| should be bounded by the output_rect of the render pass |
| // that contains |surface_quad|. |
| absl::optional<gfx::Rect> surface_clip_rect = CalculateClipRect( |
| added_clip_rect, source_pass.output_rect, target_transform); |
| |
| // If a new surface is going to be emitted, add the surface_quad rect to |
| // |surface_damage_rect_list_| for overlays. The whole quad is considered |
| // damaged. |
| absl::optional<gfx::Rect> combined_clip_rect; |
| if (needs_surface_damage_rect_list_) { |
| gfx::Rect surface_in_target_space = |
| ComputeDrawableRectForQuad(surface_quad); |
| surface_in_target_space.Intersect(source_pass.output_rect); |
| |
| if (!resolved_frame || resolved_frame->surface_id() != primary_surface_id) { |
| // If using a fallback surface the surface content may be stretched or |
| // have gutter. If the surface is missing the content will be filled |
| // with a solid color. In both cases we no longer have frame-to-frame |
| // damage so treat the entire SurfaceDrawQuad visible_rect as damaged. |
| // |combined_clip_rect| is the transforming and clipping result of the |
| // entire SurfaceDrawQuad visible_rect on the root target space of the |
| // root surface. |
| AddSurfaceDamageToDamageList(surface_in_target_space, target_transform, |
| dest_root_target_clip_rect, |
| dest_pass->transform_to_root_target, |
| /*resolved_frame=*/nullptr); |
| } |
| |
| // combined_clip_rect is the result of |dest_root_target_clip_rect| |
| // intersecting |surface_quad| on the root target space of the root surface. |
| combined_clip_rect = TransformRectToDestRootTargetSpace( |
| /*rect_in_target_space=*/surface_in_target_space, target_transform, |
| dest_pass->transform_to_root_target, dest_root_target_clip_rect); |
| } |
| |
| // If there's no fallback surface ID available, then simply emit a |
| // SolidColorDrawQuad with the provided default background color. This |
| // can happen after a Viz process crash. |
| if (!resolved_frame) { |
| EmitDefaultBackgroundColorQuad(surface_quad, embedder_client_namespace_id, |
| target_transform, surface_clip_rect, |
| dest_pass, mask_filter_info); |
| return; |
| } |
| |
| if (resolved_frame->surface_id() != primary_surface_id && |
| !surface_quad->stretch_content_to_fill_bounds) { |
| const CompositorFrame& fallback_frame = |
| resolved_frame->surface()->GetActiveOrInterpolatedFrame(); |
| |
| gfx::Rect fallback_rect(fallback_frame.size_in_pixels()); |
| |
| float scale_ratio = |
| parent_device_scale_factor / fallback_frame.device_scale_factor(); |
| fallback_rect = |
| gfx::ScaleToEnclosingRect(fallback_rect, scale_ratio, scale_ratio); |
| fallback_rect = |
| gfx::IntersectRects(fallback_rect, surface_quad->visible_rect); |
| |
| // TODO(crbug.com/1308932): CompositorFrameMetadata to SkColor4f |
| EmitGutterQuadsIfNecessary(surface_quad->visible_rect, fallback_rect, |
| surface_quad->shared_quad_state, |
| embedder_client_namespace_id, target_transform, |
| surface_clip_rect, |
| fallback_frame.metadata.root_background_color, |
| dest_pass, mask_filter_info); |
| } |
| |
| EmitSurfaceContent(*resolved_frame, parent_device_scale_factor, surface_quad, |
| embedder_client_namespace_id, target_transform, |
| surface_clip_rect, combined_clip_rect, dest_pass, |
| ignore_undamaged, damage_rect_in_quad_space, |
| damage_rect_in_quad_space_valid, mask_filter_info); |
| } |
| |
| void SurfaceAggregator::EmitSurfaceContent( |
| const ResolvedFrameData& resolved_frame, |
| float parent_device_scale_factor, |
| const SurfaceDrawQuad* surface_quad, |
| uint32_t embedder_client_namespace_id, |
| const gfx::Transform& target_transform, |
| const absl::optional<gfx::Rect>& added_clip_rect, |
| const absl::optional<gfx::Rect>& dest_root_target_clip_rect, |
| AggregatedRenderPass* dest_pass, |
| bool ignore_undamaged, |
| gfx::Rect* damage_rect_in_quad_space, |
| bool* damage_rect_in_quad_space_valid, |
| const MaskFilterInfoExt& mask_filter_info) { |
| Surface* surface = resolved_frame.surface(); |
| |
| // If this surface's id is already in our referenced set then it creates |
| // a cycle in the graph and should be dropped. |
| SurfaceId surface_id = surface->surface_id(); |
| if (referenced_surfaces_.count(surface_id)) |
| return; |
| |
| ++stats_->copied_surface_count; |
| |
| const CompositorFrame& frame = surface->GetActiveOrInterpolatedFrame(); |
| |
| // If we are stretching content to fill the SurfaceDrawQuad, or if the device |
| // scale factor mismatches between content and SurfaceDrawQuad, we appply an |
| // additional scale. |
| float extra_content_scale_x, extra_content_scale_y; |
| if (surface_quad->stretch_content_to_fill_bounds) { |
| const gfx::Rect& surface_quad_rect = surface_quad->rect; |
| // Stretches the surface contents to exactly fill the layer bounds, |
| // regardless of scale or aspect ratio differences. |
| extra_content_scale_x = surface_quad_rect.width() / |
| static_cast<float>(frame.size_in_pixels().width()); |
| extra_content_scale_y = surface_quad_rect.height() / |
| static_cast<float>(frame.size_in_pixels().height()); |
| } else { |
| extra_content_scale_x = extra_content_scale_y = |
| parent_device_scale_factor / frame.device_scale_factor(); |
| } |
| float inverse_extra_content_scale_x = SK_Scalar1 / extra_content_scale_x; |
| float inverse_extra_content_scale_y = SK_Scalar1 / extra_content_scale_y; |
| |
| const SharedQuadState* surface_quad_sqs = surface_quad->shared_quad_state; |
| gfx::Transform scaled_quad_to_target_transform( |
| surface_quad_sqs->quad_to_target_transform); |
| scaled_quad_to_target_transform.Scale(extra_content_scale_x, |
| extra_content_scale_y); |
| |
| TRACE_EVENT_WITH_FLOW2( |
| "viz,benchmark", "Graphics.Pipeline", |
| TRACE_ID_GLOBAL(frame.metadata.begin_frame_ack.trace_id), |
| TRACE_EVENT_FLAG_FLOW_IN | TRACE_EVENT_FLAG_FLOW_OUT, "step", |
| "SurfaceAggregation", "display_trace", display_trace_id_); |
| |
| const gfx::Rect& surface_quad_visible_rect = surface_quad->visible_rect; |
| if (ignore_undamaged) { |
| gfx::Transform quad_to_target_transform = |
| target_transform * surface_quad_sqs->quad_to_target_transform; |
| *damage_rect_in_quad_space_valid = CalculateQuadSpaceDamageRect( |
| quad_to_target_transform, dest_pass->transform_to_root_target, |
| root_damage_rect_, damage_rect_in_quad_space); |
| if (*damage_rect_in_quad_space_valid && |
| !damage_rect_in_quad_space->Intersects(surface_quad_visible_rect)) { |
| return; |
| } |
| } |
| |
| // A map keyed by RenderPass id. |
| Surface::CopyRequestsMap copy_requests; |
| if (take_copy_requests_) |
| surface->TakeCopyOutputRequests(©_requests); |
| |
| const CompositorRenderPassList& render_pass_list = frame.render_pass_list; |
| if (!resolved_frame.is_valid()) { |
| // As |copy_requests| goes out-of-scope, all copy requests in that container |
| // will auto-send an empty result upon destruction. |
| return; |
| } |
| |
| referenced_surfaces_.insert(surface_id); |
| |
| gfx::Transform combined_transform = scaled_quad_to_target_transform; |
| combined_transform.PostConcat(target_transform); |
| |
| // If the SurfaceDrawQuad is marked as being reflected and surface contents |
| // are going to be scaled then keep the RenderPass. This allows the reflected |
| // surface to be drawn with AA enabled for smooth scaling and preserves the |
| // original reflector scaling behaviour which scaled a TextureLayer. |
| bool reflected_and_scaled = |
| surface_quad->is_reflection && |
| !scaled_quad_to_target_transform.IsIdentityOrTranslation(); |
| |
| bool merge_pass = |
| CanPotentiallyMergePass(*surface_quad) && !reflected_and_scaled && |
| copy_requests.empty() && combined_transform.Preserves2dAxisAlignment() && |
| mask_filter_info.CanMergeMaskFilterInfo(*render_pass_list.back()); |
| |
| absl::optional<gfx::Rect> surface_quad_clip; |
| if (merge_pass) { |
| // Compute a clip rect in |dest_pass| coordinate space to ensure merged |
| // surface cannot draw outside where a non-merged surface would draw. An |
| // enclosing rect in |surface_quad| target render pass coordinate space is |
| // computed, then transformed into |dest_pass| coordinate space and finally |
| // that is intersected with existing |added_clip_rect|. |
| surface_quad_clip = CalculateClipRect( |
| added_clip_rect, ComputeDrawableRectForQuad(surface_quad), |
| target_transform); |
| } |
| |
| if (needs_surface_damage_rect_list_ && resolved_frame.WillDraw()) { |
| AddSurfaceDamageToDamageList( |
| /*default_damage_rect =*/gfx::Rect(), combined_transform, |
| dest_root_target_clip_rect, dest_pass->transform_to_root_target, |
| &resolved_frame); |
| } |
| |
| if (frame.metadata.delegated_ink_metadata) { |
| // Copy delegated ink metadata from the compositor frame metadata. This |
| // prevents the delegated ink trail from flickering if a compositor frame |
| // is not generated due to a delayed main frame. |
| TransformAndStoreDelegatedInkMetadata( |
| dest_pass->transform_to_root_target * combined_transform, |
| frame.metadata.delegated_ink_metadata.get()); |
| } |
| |
| // TODO(fsamuel): Move this to a separate helper function. |
| const auto& resolved_passes = resolved_frame.GetResolvedPasses(); |
| size_t num_render_passes = resolved_passes.size(); |
| size_t passes_to_copy = |
| merge_pass ? num_render_passes - 1 : num_render_passes; |
| for (size_t j = 0; j < passes_to_copy; ++j) { |
| const ResolvedPassData& resolved_pass = resolved_passes[j]; |
| const CompositorRenderPass& source = resolved_pass.render_pass(); |
| |
| size_t sqs_size = source.shared_quad_state_list.size(); |
| size_t dq_size = source.quad_list.size(); |
| auto copy_pass = std::make_unique<AggregatedRenderPass>(sqs_size, dq_size); |
| |
| gfx::Rect output_rect = source.output_rect; |
| if (max_render_target_size_ > 0) { |
| output_rect.set_width( |
| std::min(output_rect.width(), max_render_target_size_)); |
| output_rect.set_height( |
| std::min(output_rect.height(), max_render_target_size_)); |
| } |
| copy_pass->SetAll( |
| resolved_pass.remapped_id(), output_rect, output_rect, |
| source.transform_to_root_target, source.filters, |
| source.backdrop_filters, source.backdrop_filter_bounds, |
| root_content_color_usage_, source.has_transparent_background, |
| source.cache_render_pass, resolved_pass.aggregation().has_damage, |
| source.generate_mipmap); |
| |
| MoveMatchingRequests(source.id, ©_requests, ©_pass->copy_requests); |
| |
| // Contributing passes aggregated in to the pass list need to take the |
| // transform of the surface quad into account to update their transform to |
| // the root surface. |
| copy_pass->transform_to_root_target.PostConcat(combined_transform); |
| copy_pass->transform_to_root_target.PostConcat( |
| dest_pass->transform_to_root_target); |
| |
| CopyQuadsToPass(resolved_frame, resolved_pass, copy_pass.get(), |
| frame.device_scale_factor(), gfx::Transform(), {}, |
| dest_root_target_clip_rect, surface, MaskFilterInfoExt()); |
| |
| // If the render pass has copy requests, or should be cached, or has |
| // moving-pixel filters, or in a moving-pixel surface, we should damage the |
| // whole output rect so that we always drawn the full content. Otherwise, we |
| // might have incompleted copy request, or cached patially drawn render |
| // pass. |
| if (!RenderPassNeedsFullDamage(resolved_pass)) { |
| gfx::Transform inverse_transform; |
| if (copy_pass->transform_to_root_target.GetInverse(&inverse_transform)) { |
| gfx::Rect damage_rect_in_render_pass_space = |
| cc::MathUtil::ProjectEnclosingClippedRect(inverse_transform, |
| root_damage_rect_); |
| copy_pass->damage_rect.Intersect(damage_rect_in_render_pass_space); |
| } |
| } |
| |
| dest_pass_list_->push_back(std::move(copy_pass)); |
| } |
| |
| const auto& last_pass = *render_pass_list.back(); |
| const auto& resolved_root_pass = resolved_frame.GetRootRenderPassData(); |
| |
| if (merge_pass) { |
| CopyQuadsToPass(resolved_frame, resolved_root_pass, dest_pass, |
| frame.device_scale_factor(), combined_transform, |
| surface_quad_clip, dest_root_target_clip_rect, surface, |
| mask_filter_info); |
| } else { |
| auto* shared_quad_state = CopyAndScaleSharedQuadState( |
| surface_quad_sqs, embedder_client_namespace_id, |
| scaled_quad_to_target_transform, target_transform, |
| gfx::ScaleToEnclosingRect(surface_quad_sqs->quad_layer_rect, |
| inverse_extra_content_scale_x, |
| inverse_extra_content_scale_y), |
| gfx::ScaleToEnclosingRect(surface_quad_sqs->visible_quad_layer_rect, |
| inverse_extra_content_scale_x, |
| inverse_extra_content_scale_y), |
| added_clip_rect, mask_filter_info, dest_pass); |
| |
| // At this point, we need to calculate three values in order to construct |
| // the CompositorRenderPassDrawQuad: |
| |
| // |quad_rect| - A rectangle representing the RenderPass's output area in |
| // content space. This is equal to the root render pass (|last_pass|) |
| // output rect. |
| gfx::Rect quad_rect = last_pass.output_rect; |
| |
| // |quad_visible_rect| - A rectangle representing the visible portion of |
| // the RenderPass, in content space. As the SurfaceDrawQuad being |
| // embedded may be clipped further than its root render pass, we use the |
| // surface quad's value - |source_visible_rect|. |
| // |
| // There may be an |extra_content_scale_x| applied when going from this |
| // render pass's content space to the surface's content space, we remove |
| // this so that |quad_visible_rect| is in the render pass's content |
| // space. |
| gfx::Rect quad_visible_rect(gfx::ScaleToEnclosingRect( |
| surface_quad_visible_rect, inverse_extra_content_scale_x, |
| inverse_extra_content_scale_y)); |
| |
| // |tex_coord_rect| - A rectangle representing the bounds of the texture |
| // in the RenderPass's |quad_rect|. Not in content space, instead as an |
| // offset within |quad_rect|. |
| gfx::RectF tex_coord_rect = gfx::RectF(gfx::SizeF(quad_rect.size())); |
| |
| // We can't produce content outside of |quad_rect|, so clip the visible |
| // rect if necessary. |
| quad_visible_rect.Intersect(quad_rect); |
| auto remapped_pass_id = resolved_root_pass.remapped_id(); |
| if (quad_visible_rect.IsEmpty()) { |
| base::EraseIf(*dest_pass_list_, |
| [&remapped_pass_id]( |
| const std::unique_ptr<AggregatedRenderPass>& pass) { |
| return pass->id == remapped_pass_id; |
| }); |
| } else { |
| auto* quad = |
| dest_pass->CreateAndAppendDrawQuad<AggregatedRenderPassDrawQuad>(); |
| quad->SetNew(shared_quad_state, quad_rect, quad_visible_rect, |
| remapped_pass_id, kInvalidResourceId, gfx::RectF(), |
| gfx::Size(), gfx::Vector2dF(), gfx::PointF(), tex_coord_rect, |
| /*force_anti_aliasing_off=*/false, |
| /* backdrop_filter_quality*/ 1.0f); |
| } |
| } |
| |
| referenced_surfaces_.erase(surface_id); |
| surface->DidAggregate(); |
| } |
| |
| void SurfaceAggregator::EmitDefaultBackgroundColorQuad( |
| const SurfaceDrawQuad* surface_quad, |
| uint32_t embedder_client_namespace_id, |
| const gfx::Transform& target_transform, |
| const absl::optional<gfx::Rect>& clip_rect, |
| AggregatedRenderPass* dest_pass, |
| const MaskFilterInfoExt& mask_filter_info) { |
| TRACE_EVENT1("viz", "SurfaceAggregator::EmitDefaultBackgroundColorQuad", |
| "surface_range", surface_quad->surface_range.ToString()); |
| |
| // No matching surface was found so create a SolidColorDrawQuad with the |
| // SurfaceDrawQuad default background color. |
| SkColor4f background_color = surface_quad->default_background_color; |
| auto* shared_quad_state = CopySharedQuadState( |
| surface_quad->shared_quad_state, embedder_client_namespace_id, |
| target_transform, clip_rect, mask_filter_info, dest_pass); |
| |
| auto* solid_color_quad = |
| dest_pass->CreateAndAppendDrawQuad<SolidColorDrawQuad>(); |
| solid_color_quad->SetNew(shared_quad_state, surface_quad->rect, |
| surface_quad->visible_rect, background_color, false); |
| } |
| |
| void SurfaceAggregator::EmitGutterQuadsIfNecessary( |
| const gfx::Rect& primary_rect, |
| const gfx::Rect& fallback_rect, |
| const SharedQuadState* primary_shared_quad_state, |
| uint32_t embedder_client_namespace_id, |
| const gfx::Transform& target_transform, |
| const absl::optional<gfx::Rect>& clip_rect, |
| SkColor4f background_color, |
| AggregatedRenderPass* dest_pass, |
| const MaskFilterInfoExt& mask_filter_info) { |
| bool has_transparent_background = background_color == SkColors::kTransparent; |
| |
| // If the fallback Surface's active CompositorFrame has a non-transparent |
| // background then compute gutter. |
| if (has_transparent_background) |
| return; |
| |
| if (fallback_rect.width() < primary_rect.width()) { |
| // The right gutter also includes the bottom-right corner, if necessary. |
| gfx::Rect right_gutter_rect(fallback_rect.right(), primary_rect.y(), |
| primary_rect.width() - fallback_rect.width(), |
| primary_rect.height()); |
| |
| SharedQuadState* shared_quad_state = CopyAndScaleSharedQuadState( |
| primary_shared_quad_state, embedder_client_namespace_id, |
| primary_shared_quad_state->quad_to_target_transform, target_transform, |
| right_gutter_rect, right_gutter_rect, clip_rect, mask_filter_info, |
| dest_pass); |
| |
| auto* right_gutter = |
| dest_pass->CreateAndAppendDrawQuad<SolidColorDrawQuad>(); |
| right_gutter->SetNew(shared_quad_state, right_gutter_rect, |
| right_gutter_rect, background_color, false); |
| } |
| |
| if (fallback_rect.height() < primary_rect.height()) { |
| gfx::Rect bottom_gutter_rect( |
| primary_rect.x(), fallback_rect.bottom(), fallback_rect.width(), |
| primary_rect.height() - fallback_rect.height()); |
| |
| SharedQuadState* shared_quad_state = CopyAndScaleSharedQuadState( |
| primary_shared_quad_state, embedder_client_namespace_id, |
| primary_shared_quad_state->quad_to_target_transform, target_transform, |
| bottom_gutter_rect, bottom_gutter_rect, clip_rect, mask_filter_info, |
| dest_pass); |
| |
| auto* bottom_gutter = |
| dest_pass->CreateAndAppendDrawQuad<SolidColorDrawQuad>(); |
| bottom_gutter->SetNew(shared_quad_state, bottom_gutter_rect, |
| bottom_gutter_rect, background_color, false); |
| } |
| } |
| |
| void SurfaceAggregator::AddColorConversionPass() { |
| auto* root_render_pass = dest_pass_list_->back().get(); |
| gfx::Rect output_rect = root_render_pass->output_rect; |
| |
| // An extra color conversion pass is only done if the display's color |
| // space is unsuitable as a blending color space and the root render pass |
| // requires blending. |
| bool needs_color_conversion_pass = |
| !display_color_spaces_ |
| .GetOutputColorSpace(root_render_pass->content_color_usage, |
| root_render_pass->has_transparent_background) |
| .IsSuitableForBlending(); |
| needs_color_conversion_pass &= root_render_pass->ShouldDrawWithBlending(); |
| |
| // If we added or removed the color conversion pass, we need to add full |
| // damage to the current-root renderpass (and also the new-root renderpass, |
| // if the current-root renderpass becomes and intermediate renderpass). |
| if (needs_color_conversion_pass != last_frame_had_color_conversion_pass_) |
| root_render_pass->damage_rect = output_rect; |
| |
| last_frame_had_color_conversion_pass_ = needs_color_conversion_pass; |
| if (!needs_color_conversion_pass) |
| return; |
| CHECK(root_render_pass->transform_to_root_target == gfx::Transform()); |
| |
| if (!color_conversion_render_pass_id_) { |
| color_conversion_render_pass_id_ = |
| render_pass_id_generator_.GenerateNextId(); |
| } |
| |
| AddRenderPassHelper(color_conversion_render_pass_id_, output_rect, |
| root_render_pass->damage_rect, root_content_color_usage_, |
| root_render_pass->has_transparent_background, |
| /*pass_is_color_conversion_pass=*/true, |
| /*quad_state_to_target_transform=*/gfx::Transform(), |
| /*quad_state_contents_opaque=*/false, SkBlendMode::kSrc, |
| root_render_pass->id); |
| } |
| |
| void SurfaceAggregator::AddRootReadbackPass() { |
| if (extra_pass_for_readback_option_ == ExtraPassForReadbackOption::kNone) { |
| return; |
| } |
| |
| auto* root_render_pass = dest_pass_list_->back().get(); |
| gfx::Rect output_rect = root_render_pass->output_rect; |
| CHECK(root_render_pass->transform_to_root_target == gfx::Transform()); |
| bool needs_readback_pass = false; |
| // Check if there are any render passes that draw into the root pass with |
| // a backdrop filter. |
| base::flat_set<AggregatedRenderPassId> pass_ids_drawing_to_root; |
| for (auto* quad : root_render_pass->quad_list) { |
| if (auto* render_pass_quad = |
| quad->DynamicCast<AggregatedRenderPassDrawQuad>()) { |
| pass_ids_drawing_to_root.insert(render_pass_quad->render_pass_id); |
| } |
| } |
| if (!pass_ids_drawing_to_root.empty()) { |
| for (auto& render_pass : *dest_pass_list_) { |
| if (!pass_ids_drawing_to_root.contains(render_pass->id)) |
| continue; |
| if (!render_pass->backdrop_filters.IsEmpty()) { |
| needs_readback_pass = true; |
| break; |
| } |
| } |
| } |
| |
| if (extra_pass_for_readback_option_ == |
| ExtraPassForReadbackOption::kAlwaysAddPass) { |
| needs_readback_pass = true; |
| } |
| |
| if (needs_readback_pass != last_frame_had_readback_pass_) |
| root_render_pass->damage_rect = output_rect; |
| |
| last_frame_had_readback_pass_ = needs_readback_pass; |
| if (!last_frame_had_readback_pass_) |
| return; |
| |
| if (!readback_render_pass_id_) { |
| readback_render_pass_id_ = render_pass_id_generator_.GenerateNextId(); |
| } |
| |
| // Ensure the root-that's-non-root pass is cleared to fully transparent first. |
| bool has_transparent_background = |
| root_render_pass->has_transparent_background; |
| root_render_pass->has_transparent_background = true; |
| AddRenderPassHelper(readback_render_pass_id_, output_rect, |
| root_render_pass->damage_rect, root_content_color_usage_, |
| has_transparent_background, |
| /*pass_is_color_conversion_pass=*/false, |
| /*quad_state_to_target_transform=*/gfx::Transform(), |
| /*quad_state_contents_opaque=*/false, |
| SkBlendMode::kSrcOver, root_render_pass->id); |
| } |
| |
| void SurfaceAggregator::AddDisplayTransformPass() { |
| if (dest_pass_list_->empty()) |
| return; |
| |
| auto* root_render_pass = dest_pass_list_->back().get(); |
| DCHECK(root_render_pass->transform_to_root_target == root_surface_transform_); |
| |
| if (!display_transform_render_pass_id_) { |
| display_transform_render_pass_id_ = |
| render_pass_id_generator_.GenerateNextId(); |
| } |
| |
| bool are_contents_opaque = true; |
| for (const auto* sqs : root_render_pass->shared_quad_state_list) { |
| if (!sqs->are_contents_opaque) { |
| are_contents_opaque = false; |
| break; |
| } |
| } |
| |
| AddRenderPassHelper( |
| display_transform_render_pass_id_, |
| cc::MathUtil::MapEnclosedRectWith2dAxisAlignedTransform( |
| root_surface_transform_, root_render_pass->output_rect), |
| cc::MathUtil::MapEnclosedRectWith2dAxisAlignedTransform( |
| root_surface_transform_, root_render_pass->damage_rect), |
| root_render_pass->content_color_usage, |
| root_render_pass->has_transparent_background, |
| /*pass_is_color_conversion_pass=*/false, root_surface_transform_, |
| are_contents_opaque, SkBlendMode::kSrcOver, root_render_pass->id); |
| } |
| |
| void SurfaceAggregator::AddRenderPassHelper( |
| AggregatedRenderPassId render_pass_id, |
| const gfx::Rect& render_pass_output_rect, |
| const gfx::Rect& render_pass_damage_rect, |
| gfx::ContentColorUsage pass_color_usage, |
| bool pass_has_transparent_background, |
| bool pass_is_color_conversion_pass, |
| const gfx::Transform& quad_state_to_target_transform, |
| bool quad_state_contents_opaque, |
| SkBlendMode quad_state_blend_mode, |
| AggregatedRenderPassId quad_pass_id) { |
| gfx::Rect current_output_rect = dest_pass_list_->back()->output_rect; |
| |
| auto render_pass = std::make_unique<AggregatedRenderPass>(1, 1); |
| render_pass->SetAll(render_pass_id, render_pass_output_rect, |
| render_pass_damage_rect, gfx::Transform(), |
| /*filters=*/cc::FilterOperations(), |
| /*backdrop_filters=*/cc::FilterOperations(), |
| /*backdrop_filter_bounds=*/gfx::RRectF(), |
| pass_color_usage, pass_has_transparent_background, |
| /*cache_render_pass=*/false, |
| /*has_damage_from_contributing_content=*/false, |
| /*generate_mipmap=*/false); |
| render_pass->is_color_conversion_pass = pass_is_color_conversion_pass; |
| |
| auto* shared_quad_state = render_pass->CreateAndAppendSharedQuadState(); |
| shared_quad_state->SetAll( |
| quad_state_to_target_transform, |
| /*layer_rect=*/current_output_rect, |
| /*visible_layer_rect=*/current_output_rect, gfx::MaskFilterInfo(), |
| /*clip=*/absl::nullopt, quad_state_contents_opaque, /*opacity_f=*/1.f, |
| quad_state_blend_mode, /*sorting_context=*/0); |
| |
| auto* quad = |
| render_pass->CreateAndAppendDrawQuad<AggregatedRenderPassDrawQuad>(); |
| quad->SetNew(shared_quad_state, current_output_rect, current_output_rect, |
| quad_pass_id, kInvalidResourceId, gfx::RectF(), gfx::Size(), |
| gfx::Vector2dF(), gfx::PointF(), gfx::RectF(current_output_rect), |
| /*force_anti_aliasing_off=*/false, |
| /*backdrop_filter_quality*/ 1.0f); |
| dest_pass_list_->push_back(std::move(render_pass)); |
| } |
| |
| void SurfaceAggregator::CopyQuadsToPass( |
| const ResolvedFrameData& resolved_frame, |
| const ResolvedPassData& resolved_pass, |
| AggregatedRenderPass* dest_pass, |
| float parent_device_scale_factor, |
| const gfx::Transform& target_transform, |
| const absl::optional<gfx::Rect>& clip_rect, |
| const absl::optional<gfx::Rect>& dest_root_target_clip_rect, |
| const Surface* surface, |
| const MaskFilterInfoExt& parent_mask_filter_info_ext) { |
| const CompositorRenderPass& source_pass = resolved_pass.render_pass(); |
| const QuadList& source_quad_list = source_pass.quad_list; |
| const SharedQuadState* last_copied_source_shared_quad_state = nullptr; |
| |
| // If the current frame has copy requests or cached render passes, then |
| // aggregate the entire thing, as otherwise parts of the copy requests may be |
| // ignored and we could cache partially drawn render pass. |
| // If there are pixel-moving backdrop filters then the damage rect might be |
| // expanded later, so we can't drop quads that are outside the current damage |
| // rect safely. |
| // If overlay/underlay is enabled then the underlay rect might be added to the |
| // damage rect later. We are not able to predict right here which draw quad |
| // candidate will be promoted to overlay/underlay. Also, we might drop quads |
| // which are on top of an underlay and cause the overlay processor to |
| // present the quad as an overlay instead of an underlay. |
| const bool ignore_undamaged = |
| aggregate_only_damaged_ && !has_copy_requests_ && |
| !has_pixel_moving_backdrop_filter_ && |
| !resolved_pass.aggregation().in_cached_render_pass && |
| !resolved_pass.aggregation().in_pixel_moving_filter_pass; |
| // TODO(kylechar): For copy render passes we only need to draw all quads if |
| // those attributes are set on the current render pass' aggregation data. The |
| // complication is if a SurfaceDrawQuad is dropped and that surface has a copy |
| // request on it then we still need to draw the surface. |
| |
| // Damage rect in the quad space of the current shared quad state. |
| // TODO(jbauman): This rect may contain unnecessary area if |
| // transform isn't axis-aligned. |
| gfx::Rect damage_rect_in_quad_space; |
| bool damage_rect_in_quad_space_valid = false; |
| |
| #if DCHECK_IS_ON() |
| const SharedQuadStateList& source_shared_quad_state_list = |
| source_pass.shared_quad_state_list; |
| // If quads have come in with SharedQuadState out of order, or when quads have |
| // invalid SharedQuadState pointer, it should DCHECK. |
| auto sqs_iter = source_shared_quad_state_list.cbegin(); |
| for (auto* quad : source_quad_list) { |
| while (sqs_iter != source_shared_quad_state_list.cend() && |
| quad->shared_quad_state != *sqs_iter) { |
| ++sqs_iter; |
| } |
| DCHECK(sqs_iter != source_shared_quad_state_list.cend()); |
| } |
| #endif |
| |
| size_t overlay_damage_index = 0; |
| const DrawQuad* quad_with_overlay_damage_index = nullptr; |
| if (needs_surface_damage_rect_list_ && |
| resolved_pass.aggregation().will_draw) { |
| // TODO(crbug.com/1323002): If there is one specific quad for this pass's |
| // damage we should move the allocation of the damage index below to be |
| // consistent with quad ordering. |
| quad_with_overlay_damage_index = |
| FindQuadWithOverlayDamage(source_pass, dest_pass, target_transform, |
| surface, &overlay_damage_index); |
| } |
| |
| MaskFilterInfoExt new_mask_filter_info_ext = parent_mask_filter_info_ext; |
| |
| size_t quad_index = 0; |
| auto& resolved_draw_quads = resolved_pass.draw_quads(); |
| |
| uint32_t client_namespace_id = resolved_frame.GetClientNamespaceId(); |
| |
| for (auto* quad : source_quad_list) { |
| const ResolvedQuadData& quad_data = resolved_draw_quads[quad_index++]; |
| |
| // Both cannot be set at once. If this happens then a surface is being |
| // merged when it should not. |
| DCHECK(quad->shared_quad_state->mask_filter_info.IsEmpty() || |
| parent_mask_filter_info_ext.mask_filter_info.IsEmpty()); |
| |
| if (quad->material == DrawQuad::Material::kSharedElement) { |
| // SharedElement quads should've been resolved before aggregation. |
| continue; |
| } else if (const auto* surface_quad = |
| quad->DynamicCast<SurfaceDrawQuad>()) { |
| // HandleSurfaceQuad may add other shared quad state, so reset the |
| // current data. |
| last_copied_source_shared_quad_state = nullptr; |
| |
| if (!surface_quad->surface_range.end().is_valid()) |
| continue; |
| |
| if (parent_mask_filter_info_ext.mask_filter_info.IsEmpty()) { |
| new_mask_filter_info_ext = MaskFilterInfoExt( |
| quad->shared_quad_state->mask_filter_info, |
| quad->shared_quad_state->is_fast_rounded_corner, target_transform); |
| } |
| |
| HandleSurfaceQuad(source_pass, surface_quad, client_namespace_id, |
| parent_device_scale_factor, target_transform, clip_rect, |
| dest_root_target_clip_rect, dest_pass, ignore_undamaged, |
| &damage_rect_in_quad_space, |
| &damage_rect_in_quad_space_valid, |
| new_mask_filter_info_ext); |
| } else { |
| if (quad->shared_quad_state != last_copied_source_shared_quad_state) { |
| if (parent_mask_filter_info_ext.mask_filter_info.IsEmpty()) { |
| new_mask_filter_info_ext = |
| MaskFilterInfoExt(quad->shared_quad_state->mask_filter_info, |
| quad->shared_quad_state->is_fast_rounded_corner, |
| target_transform); |
| } |
| SharedQuadState* dest_shared_quad_state = CopySharedQuadState( |
| quad->shared_quad_state, client_namespace_id, target_transform, |
| clip_rect, new_mask_filter_info_ext, dest_pass); |
| // Here we output the optional quad's |per_quad_damage| to the |
| // |surface_damage_rect_list_|. Any non per quad damage associated with |
| // this |source_pass| will have been added to the |
| // |surface_damage_rect_list_| before this phase. |
| if (source_pass.has_per_quad_damage && |
| GetOptionalDamageRectFromQuad(quad).has_value() && |
| resolved_pass.aggregation().will_draw) { |
| auto damage_rect_in_target_space = |
| GetOptionalDamageRectFromQuad(quad); |
| dest_shared_quad_state->overlay_damage_index = |
| surface_damage_rect_list_->size(); |
| AddSurfaceDamageToDamageList( |
| damage_rect_in_target_space.value(), target_transform, |
| dest_root_target_clip_rect, dest_pass->transform_to_root_target, |
| /*resolved_frame=*/nullptr); |
| } else if (quad == quad_with_overlay_damage_index) { |
| dest_shared_quad_state->overlay_damage_index = overlay_damage_index; |
| } |
| |
| last_copied_source_shared_quad_state = quad->shared_quad_state; |
| if (ignore_undamaged) { |
| damage_rect_in_quad_space_valid = CalculateQuadSpaceDamageRect( |
| dest_shared_quad_state->quad_to_target_transform, |
| dest_pass->transform_to_root_target, root_damage_rect_, |
| &damage_rect_in_quad_space); |
| } |
| } |
| |
| if (ignore_undamaged) { |
| if (damage_rect_in_quad_space_valid && |
| !damage_rect_in_quad_space.Intersects(quad->visible_rect)) |
| continue; |
| } |
| |
| DrawQuad* dest_quad = nullptr; |
| if (const auto* pass_quad = |
| quad->DynamicCast<CompositorRenderPassDrawQuad>()) { |
| CompositorRenderPassId original_pass_id = pass_quad->render_pass_id; |
| AggregatedRenderPassId remapped_pass_id = |
| resolved_frame.GetRenderPassDataById(original_pass_id) |
| .remapped_id(); |
| |
| dest_quad = dest_pass->CopyFromAndAppendRenderPassDrawQuad( |
| pass_quad, remapped_pass_id); |
| |
| if (needs_surface_damage_rect_list_ && |
| resolved_pass.aggregation().will_draw) { |
| AddRenderPassFilterDamageToDamageList( |
| resolved_frame, pass_quad, target_transform, |
| dest_root_target_clip_rect, dest_pass->transform_to_root_target); |
| } |
| } else if (const auto* texture_quad = |
| quad->DynamicCast<TextureDrawQuad>()) { |
| if (texture_quad->secure_output_only && |
| (!output_is_secure_ || |
| resolved_pass.aggregation().in_copy_request_pass)) { |
| // If TextureDrawQuad requires secure output and the output is not |
| // secure then replace it with solid black. |
| auto* solid_color_quad = |
| dest_pass->CreateAndAppendDrawQuad<SolidColorDrawQuad>(); |
| solid_color_quad->SetNew(dest_pass->shared_quad_state_list.back(), |
| quad->rect, quad->visible_rect, |
| SkColors::kBlack, false); |
| } else { |
| dest_quad = dest_pass->CopyFromAndAppendDrawQuad(quad); |
| } |
| } else { |
| dest_quad = dest_pass->CopyFromAndAppendDrawQuad(quad); |
| } |
| if (dest_quad) { |
| dest_quad->resources = quad_data.remapped_resources; |
| } |
| } |
| } |
| } |
| |
| void SurfaceAggregator::CopyPasses(const ResolvedFrameData& resolved_frame) { |
| Surface* surface = resolved_frame.surface(); |
| const CompositorFrame& frame = surface->GetActiveOrInterpolatedFrame(); |
| |
| // The root surface is allowed to have copy output requests, so grab them |
| // off its render passes. This map contains a set of CopyOutputRequests |
| // keyed by each RenderPass id. |
| Surface::CopyRequestsMap copy_requests; |
| if (take_copy_requests_) |
| surface->TakeCopyOutputRequests(©_requests); |
| |
| const auto& source_pass_list = frame.render_pass_list; |
| if (!resolved_frame.is_valid()) |
| return; |
| |
| ++stats_->copied_surface_count; |
| |
| const gfx::Transform surface_transform = |
| IsRootSurface(surface) ? root_surface_transform_ : gfx::Transform(); |
| |
| if (frame.metadata.delegated_ink_metadata) { |
| DCHECK(surface->GetActiveFrameMetadata().delegated_ink_metadata == |
| frame.metadata.delegated_ink_metadata); |
| // Copy delegated ink metadata from the compositor frame metadata. This |
| // prevents the delegated ink trail from flickering if a compositor frame |
| // is not generated due to a delayed main frame. |
| TransformAndStoreDelegatedInkMetadata( |
| source_pass_list.back()->transform_to_root_target * surface_transform, |
| frame.metadata.delegated_ink_metadata.get()); |
| } |
| |
| bool apply_surface_transform_to_root_pass = true; |
| for (auto& resolved_pass : resolved_frame.GetResolvedPasses()) { |
| const auto& source = resolved_pass.render_pass(); |
| |
| size_t sqs_size = source.shared_quad_state_list.size(); |
| size_t dq_size = source.quad_list.size(); |
| auto copy_pass = std::make_unique<AggregatedRenderPass>(sqs_size, dq_size); |
| |
| MoveMatchingRequests(source.id, ©_requests, ©_pass->copy_requests); |
| |
| // We add an additional render pass for the transform if the root render |
| // pass has any copy requests. |
| apply_surface_transform_to_root_pass = |
| resolved_pass.is_root() && |
| (copy_pass->copy_requests.empty() || surface_transform.IsIdentity()); |
| |
| gfx::Rect output_rect = source.output_rect; |
| gfx::Transform transform_to_root_target = source.transform_to_root_target; |
| if (apply_surface_transform_to_root_pass) { |
| // If we don't need an additional render pass to apply the surface |
| // transform, adjust the root pass's rects to account for it. |
| output_rect = cc::MathUtil::MapEnclosedRectWith2dAxisAlignedTransform( |
| surface_transform, output_rect); |
| } else { |
| // For the non-root render passes, the transform to root target needs to |
| // be adjusted to include the root surface transform. This is also true if |
| // we will be adding another render pass for the surface transform, in |
| // which this will no longer be the root. |
| transform_to_root_target = |
| surface_transform * source.transform_to_root_target; |
| } |
| |
| copy_pass->SetAll( |
| resolved_pass.remapped_id(), output_rect, output_rect, |
| transform_to_root_target, source.filters, source.backdrop_filters, |
| source.backdrop_filter_bounds, root_content_color_usage_, |
| source.has_transparent_background, source.cache_render_pass, |
| resolved_pass.aggregation().has_damage, source.generate_mipmap); |
| |
| if (needs_surface_damage_rect_list_ && resolved_pass.is_root()) { |
| AddSurfaceDamageToDamageList( |
| /*default_damage_rect=*/gfx::Rect(), |
| /*parent_target_transform=*/surface_transform, |
| /*dest_root_target_clip_rect=*/{}, |
| copy_pass->transform_to_root_target, &resolved_frame); |
| } |
| |
| CopyQuadsToPass(resolved_frame, resolved_pass, copy_pass.get(), |
| frame.device_scale_factor(), |
| apply_surface_transform_to_root_pass ? surface_transform |
| : gfx::Transform(), |
| {}, /*dest_root_target_clip_rect=*/{}, surface, |
| MaskFilterInfoExt()); |
| |
| // If the render pass has copy requests, or should be cached, or has |
| // moving-pixel filters, or in a moving-pixel surface, we should damage the |
| // whole output rect so that we always drawn the full content. Otherwise, we |
| // might have incompleted copy request, or cached patially drawn render |
| // pass. |
| if (!RenderPassNeedsFullDamage(resolved_pass)) { |
| gfx::Transform inverse_transform; |
| if (copy_pass->transform_to_root_target.GetInverse(&inverse_transform)) { |
| gfx::Rect damage_rect_in_render_pass_space = |
| cc::MathUtil::ProjectEnclosingClippedRect(inverse_transform, |
| root_damage_rect_); |
| copy_pass->damage_rect.Intersect(damage_rect_in_render_pass_space); |
| } |
| } |
| dest_pass_list_->push_back(std::move(copy_pass)); |
| } |
| |
| if (!apply_surface_transform_to_root_pass) |
| AddDisplayTransformPass(); |
| } |
| |
| void SurfaceAggregator::ProcessAddedAndRemovedSurfaces() { |
| // Delete resolved frame data that wasn't used this aggregation. This releases |
| // resources associated with those resolved frames. |
| base::EraseIf(resolved_frames_, [](auto& entry) { |
| return !entry.second.WasUsedInAggregation(); |
| }); |
| } |
| |
| gfx::Rect SurfaceAggregator::PrewalkRenderPass( |
| ResolvedFrameData& resolved_frame, |
| ResolvedPassData& resolved_pass, |
| const gfx::Rect& damage_from_parent, |
| const gfx::Transform& target_to_root_transform, |
| const ResolvedPassData* parent_pass, |
| PrewalkResult& result) { |
| const CompositorRenderPass& render_pass = resolved_pass.render_pass(); |
| |
| if (render_pass.backdrop_filters.HasFilterThatMovesPixels()) { |
| has_pixel_moving_backdrop_filter_ = true; |
| } |
| |
| if (parent_pass && parent_pass->aggregation().will_draw) |
| resolved_pass.aggregation().will_draw = true; |
| |
| // Populate state for about cached render passes and pixel moving filters. |
| // These attributes apply transitively to all child render passes embedded by |
| // the CompositorRenderPass with the attribute. |
| if (render_pass.cache_render_pass || |
| (parent_pass && parent_pass->aggregation().in_cached_render_pass)) { |
| resolved_pass.aggregation().in_cached_render_pass = true; |
| } |
| |
| if (render_pass.filters.HasFilterThatMovesPixels() || |
| (parent_pass && parent_pass->aggregation().in_pixel_moving_filter_pass)) { |
| resolved_pass.aggregation().in_pixel_moving_filter_pass = true; |
| } |
| |
| const FrameDamageType damage_type = resolved_frame.GetFrameDamageType(); |
| if (damage_type == FrameDamageType::kFull) { |
| resolved_pass.aggregation().has_damage = true; |
| } else if (damage_type == FrameDamageType::kFrame && |
| render_pass.has_damage_from_contributing_content) { |
| resolved_pass.aggregation().has_damage = true; |
| } |
| |
| // The damage on the root render pass of the surface comes from damage |
| // accumulated from all quads in the surface, and needs to be expanded by any |
| // pixel-moving backdrop filter in the render pass if intersecting. Transform |
| // this damage into the local space of the render pass for this purpose. |
| // TODO(kylechar): If this render pass isn't reachable from the surfaces root |
| // render pass then surface damage can't be transformed into this render pass |
| // coordinate space. We should use the actual damage for the render pass, |
| // which isn't included in the CompositorFrame right now. |
| gfx::Rect surface_root_rp_damage = resolved_frame.GetSurfaceDamage(); |
| if (!surface_root_rp_damage.IsEmpty()) { |
| gfx::Transform root_to_target_transform; |
| if (target_to_root_transform.GetInverse(&root_to_target_transform)) { |
| surface_root_rp_damage = cc::MathUtil::ProjectEnclosingClippedRect( |
| root_to_target_transform, surface_root_rp_damage); |
| } |
| } |
| |
| gfx::Rect damage_rect; |
| // Iterate through the quad list back-to-front and accumulate damage from |
| // all quads (only SurfaceDrawQuads and RenderPassDrawQuads can have damage |
| // at this point). |damage_rect| has damage from all quads below the current |
| // iterated quad, and can be used to determine if there's any intersection |
| // with the current quad when needed. |
| for (const DrawQuad* quad : base::Reversed(resolved_pass.prewalk_quads())) { |
| gfx::Rect quad_damage_rect; |
| gfx::Rect quad_target_space_damage_rect; |
| if (quad->material == DrawQuad::Material::kSurfaceContent) { |
| const auto* surface_quad = SurfaceDrawQuad::MaterialCast(quad); |
| ResolvedFrameData* child_resolved_frame = |
| GetResolvedFrame(surface_quad->surface_range); |
| |
| // If the primary surface is not available then we assume the damage is |
| // the full size of the SurfaceDrawQuad because we might need to introduce |
| // gutter. |
| if (!child_resolved_frame || child_resolved_frame->surface_id() != |
| surface_quad->surface_range.end()) { |
| quad_damage_rect = quad->rect; |
| } |
| |
| if (child_resolved_frame) { |
| float x_scale = SK_Scalar1; |
| float y_scale = SK_Scalar1; |
| if (surface_quad->stretch_content_to_fill_bounds) { |
| const gfx::Size& child_size = |
| child_resolved_frame->surface()->size_in_pixels(); |
| if (!child_size.IsEmpty()) { |
| x_scale = static_cast<float>(surface_quad->rect.width()) / |
| child_size.width(); |
| y_scale = static_cast<float>(surface_quad->rect.height()) / |
| child_size.height(); |
| } |
| } else { |
| // If not stretching to fit bounds then scale to adjust to device |
| // scale factor differences between child and parent surface. This |
| // scale factor is later applied to quads in the aggregated frame. |
| x_scale = y_scale = |
| resolved_frame.surface()->device_scale_factor() / |
| child_resolved_frame->surface()->device_scale_factor(); |
| } |
| // If the surface quad is to be merged potentially, the current |
| // effective accumulated damage needs to be taken into account. This |
| // includes the damage from quads under the surface quad, i.e. |
| // |damage_rect|, |surface_root_rp_damage|, which can contain damage |
| // contributed by quads under the surface quad in the previous stage |
| // (cc), and |damage_from_parent|. The damage is first transformed into |
| // the local space of the surface quad and then passed to the embedding |
| // surface. The condition for deciding if the surface quad will merge is |
| // loose here, so for those quads passed this condition but eventually |
| // don't merge, there is over-contribution of the damage passed from |
| // parent, but this shouldn't affect correctness. |
| gfx::Rect accumulated_damage_in_child_space; |
| |
| if (CanPotentiallyMergePass(*surface_quad)) { |
| accumulated_damage_in_child_space.Union(damage_rect); |
| accumulated_damage_in_child_space.Union(damage_from_parent); |
| accumulated_damage_in_child_space.Union(surface_root_rp_damage); |
| if (!accumulated_damage_in_child_space.IsEmpty()) { |
| gfx::Transform inverse = |
| quad->shared_quad_state->quad_to_target_transform |
| .GetCheckedInverse(); |
| inverse.PostScale(SK_Scalar1 / x_scale, SK_Scalar1 / y_scale); |
| accumulated_damage_in_child_space = |
| cc::MathUtil::ProjectEnclosingClippedRect( |
| inverse, accumulated_damage_in_child_space); |
| } |
| } |
| gfx::Rect child_rect = |
| PrewalkSurface(*child_resolved_frame, &resolved_pass, |
| accumulated_damage_in_child_space, result); |
| child_rect = gfx::ScaleToEnclosingRect(child_rect, x_scale, y_scale); |
| quad_damage_rect.Union(child_rect); |
| } |
| |
| // Only check for root render pass on the root surface. |
| if (parent_pass == nullptr && resolved_pass.is_root() && |
| !result.page_fullscreen_mode) { |
| gfx::Rect surface_quad_on_target_space = ClippedQuadRectangle(quad); |
| // Often time the surface_quad_on_target_space is not exactly the same |
| // as the output_rect after the math operations, although they are meant |
| // to be the same. Set the delta tolerance to 8 pixels. |
| if (surface_quad_on_target_space.ApproximatelyEqual( |
| render_pass.output_rect, /*tolerance=*/8)) { |
| result.page_fullscreen_mode = true; |
| } |
| } |
| } else if (auto* render_pass_quad = |
| quad->DynamicCast<CompositorRenderPassDrawQuad>()) { |
| CompositorRenderPassId child_pass_id = render_pass_quad->render_pass_id; |
| |
| ResolvedPassData& child_resolved_pass = |
| resolved_frame.GetRenderPassDataById(child_pass_id); |
| const CompositorRenderPass& child_render_pass = |
| child_resolved_pass.render_pass(); |
| |
| gfx::Rect rect_in_target_space = cc::MathUtil::MapEnclosingClippedRect( |
| quad->shared_quad_state->quad_to_target_transform, quad->rect); |
| |
| // |damage_rect|, |damage_from_parent| and |surface_root_rp_damage| |
| // either are or can possible contain damage from under the quad, so if |
| // they intersect the quad render pass output rect, we have to invalidate |
| // the |intersects_damage_under| flag. Note the intersection test can be |
| // done against backdrop filter bounds as an improvement. |
| bool intersects_current_damage = |
| rect_in_target_space.Intersects(damage_rect); |
| bool intersects_damage_from_parent = |
| rect_in_target_space.Intersects(damage_from_parent); |
| // The |intersects_damage_under| flag hints if the current quad intersects |
| // any damage from any quads below in the same surface. If the flag is |
| // false, it means the intersecting damage is from quads above it or from |
| // itself. |
| bool intersects_damage_from_surface = |
| rect_in_target_space.Intersects(surface_root_rp_damage); |
| if (intersects_current_damage || intersects_damage_from_parent || |
| intersects_damage_from_surface) { |
| render_pass_quad->intersects_damage_under = true; |
| |
| if (child_render_pass.backdrop_filters.HasFilterThatMovesPixels()) { |
| // The damage from under the quad intersects quad render pass output |
| // rect and it has to be expanded because of the pixel-moving |
| // backdrop filters. We expand the |damage_rect| to include quad |
| // render pass output rect (which can be optimized to be backdrop |
| // filter bounds). |damage_from_parent| and |surface_root_rp_damage| |
| // only have to be included when they also have intersection with the |
| // quad. |
| damage_rect.Union(rect_in_target_space); |
| if (intersects_damage_from_parent) { |
| damage_rect.Union(damage_from_parent); |
| } |
| if (intersects_damage_from_surface) { |
| damage_rect.Union(surface_root_rp_damage); |
| } |
| } |
| } |
| // For the pixel-moving backdrop filters, all effects are limited to the |
| // size of the RenderPassDrawQuad rect. Therefore when we find the damage |
| // under the quad intersects quad render pass output rect, we extend the |
| // damage rect to include the rpdq->rect. |
| // TODO(crbug/1379125): Work out how to correctly compute damage when |
| // offset backdrop filters may be involved. |
| |
| // For the pixel-moving foreground filters, all effects can be expanded |
| // outside the RenderPassDrawQuad rect based on filter pixel movement. |
| // Therefore, we have to check if the expanded rpdq->rect intersects the |
| // damage under it. Then we extend the damage rect to include the expanded |
| // rpdq->rect. |
| |
| // Expand the damage to cover entire |output_rect| if the |render_pass| |
| // has pixel-moving foreground filter. |
| if (child_render_pass.filters.HasFilterThatMovesPixels()) { |
| gfx::Rect expanded_rect_in_target_space = |
| GetExpandedRectWithPixelMovingForegroundFilter( |
| *render_pass_quad, child_render_pass.filters); |
| |
| if (expanded_rect_in_target_space.Intersects(damage_rect) || |
| expanded_rect_in_target_space.Intersects(damage_from_parent) || |
| expanded_rect_in_target_space.Intersects(surface_root_rp_damage)) { |
| damage_rect.Union(expanded_rect_in_target_space); |
| } |
| } |
| |
| resolved_pass.aggregation().embedded_passes.insert(&child_resolved_pass); |
| |
| const gfx::Transform child_to_root_transform = |
| target_to_root_transform * |
| quad->shared_quad_state->quad_to_target_transform; |
| quad_damage_rect = |
| PrewalkRenderPass(resolved_frame, child_resolved_pass, gfx::Rect(), |
| child_to_root_transform, &resolved_pass, result); |
| |
| } else { |
| // If this the next frame in sequence from last aggregation then per quad |
| // damage_rects are valid so add them here. If not, either this is the |
| // same frame as last aggregation and there is no damage OR there is |
| // already full damage for the surface. |
| if (damage_type == FrameDamageType::kFrame) { |
| auto& per_quad_damage_rect = GetOptionalDamageRectFromQuad(quad); |
| DCHECK(per_quad_damage_rect.has_value()); |
| // The DrawQuad `per_quad_damage_rect` is already in the render pass |
| // coordinate space instead of quad rect coordinate space. |
| quad_target_space_damage_rect = per_quad_damage_rect.value(); |
| } |
| } |
| |
| // Clip the quad damage to the quad visible before converting back to |
| // render pass coordinate space. Expanded damage outside the quad rect for |
| // filters are added to |damage_rect| directly so this only clips damage |
| // from drawing the quad itself. |
| quad_damage_rect.Intersect(quad->visible_rect); |
| |
| if (!quad_damage_rect.IsEmpty()) { |
| // Convert the quad damage rect into its target space and clip it if |
| // needed. Ignore tiny errors to avoid artificially inflating the |
| // damage due to floating point math. |
| constexpr float kEpsilon = 0.001f; |
| quad_target_space_damage_rect = |
| cc::MathUtil::MapEnclosingClippedRectIgnoringError( |
| quad->shared_quad_state->quad_to_target_transform, |
| quad_damage_rect, kEpsilon); |
| } |
| |
| if (!quad_target_space_damage_rect.IsEmpty()) { |
| if (quad->shared_quad_state->clip_rect) { |
| quad_target_space_damage_rect.Intersect( |
| *quad->shared_quad_state->clip_rect); |
| } |
| damage_rect.Union(quad_target_space_damage_rect); |
| } |
| } |
| |
| if (!damage_rect.IsEmpty()) { |
| // There is extra damage for this render pass. This is damage that the |
| // client that submitted this render pass didn't know about and isn't |
| // included in the surface damage or `has_damage_from_contributing_content`. |
| resolved_pass.aggregation().has_damage = true; |
| |
| if (render_pass.filters.HasFilterThatMovesPixels()) { |
| // Expand the damage to cover entire |output_rect| if the |render_pass| |
| // has pixel-moving foreground filter. |
| damage_rect.Union(render_pass.output_rect); |
| } |
| |
| // The added damage from quads in the render pass is transformed back into |
| // the render pass coordinate space without clipping, so it can extend |
| // beyond the edge of the current render pass. Coordinates outside the |
| // output_rect are invalid in this render passes coordinate space but they |
| // may be valid coordinates in the embedder coordinate space, causing |
| // unnecessary damage expansion. |
| damage_rect.Intersect(render_pass.output_rect); |
| } |
| |
| return damage_rect; |
| } |
| |
| void SurfaceAggregator::ProcessResolvedFrame( |
| ResolvedFrameData& resolved_frame) { |
| Surface* surface = resolved_frame.surface(); |
| const CompositorFrame& compositor_frame = |
| surface->GetActiveOrInterpolatedFrame(); |
| |
| // Ref the resources in the surface, and let the provider know we've received |
| // new resources from the compositor frame. |
| if (surface->client()) |
| surface->client()->RefResources(compositor_frame.resource_list); |
| |
| resolved_frame.UpdateForActiveFrame(render_pass_id_generator_); |
| } |
| |
| bool SurfaceAggregator::CheckFrameSinksChanged(const Surface* surface) { |
| contained_surfaces_.insert(surface->surface_id()); |
| LocalSurfaceId& local_surface_id = |
| contained_frame_sinks_[surface->surface_id().frame_sink_id()]; |
| bool frame_sinks_changed = (!previous_contained_frame_sinks_.contains( |
| surface->surface_id().frame_sink_id())); |
| local_surface_id = |
| std::max(surface->surface_id().local_surface_id(), local_surface_id); |
| return frame_sinks_changed; |
| } |
| |
| gfx::Rect SurfaceAggregator::PrewalkSurface(ResolvedFrameData& resolved_frame, |
| ResolvedPassData* parent_pass, |
| const gfx::Rect& damage_from_parent, |
| PrewalkResult& result) { |
| Surface* surface = resolved_frame.surface(); |
| DCHECK(surface->HasActiveFrame()); |
| |
| if (referenced_surfaces_.count(surface->surface_id())) |
| return gfx::Rect(); |
| |
| result.frame_sinks_changed |= CheckFrameSinksChanged(surface); |
| |
| if (!resolved_frame.is_valid()) |
| return gfx::Rect(); |
| |
| DebugLogSurface(surface, resolved_frame.WillDraw()); |
| ++stats_->prewalked_surface_count; |
| |
| auto& root_resolved_pass = resolved_frame.GetRootRenderPassData(); |
| if (parent_pass) { |
| parent_pass->aggregation().embedded_passes.insert(&root_resolved_pass); |
| } |
| |
| gfx::Rect damage_rect = resolved_frame.GetSurfaceDamage(); |
| |
| // Avoid infinite recursion by adding current surface to |
| // |referenced_surfaces_|. |
| referenced_surfaces_.insert(surface->surface_id()); |
| |
| for (auto& resolved_pass : resolved_frame.GetResolvedPasses()) { |
| // Prewalk any render passes that aren't reachable from the root pass. The |
| // damage produced isn't correct since there is no transform between damage |
| // in the root render passes coordinate space and the unembedded render |
| // pass, but other attributes related to the embedding hierarchy are still |
| // important to propagate. |
| if (resolved_pass.IsUnembedded()) { |
| PrewalkRenderPass(resolved_frame, resolved_pass, |
| /*damage_from_parent=*/gfx::Rect(), |
| /*target_to_root_transform=*/gfx::Transform(), |
| /*parent_pass=*/nullptr, result); |
| } |
| } |
| |
| damage_rect.Union(PrewalkRenderPass(resolved_frame, root_resolved_pass, |
| damage_from_parent, gfx::Transform(), |
| parent_pass, result)); |
| |
| if (!damage_rect.IsEmpty()) { |
| auto damage_rect_surface_space = damage_rect; |
| if (IsRootSurface(surface)) { |
| // The damage reported to the surface is in pre-display transform space |
| // since it is used by clients which are not aware of the display |
| // transform. |
| damage_rect = cc::MathUtil::MapEnclosedRectWith2dAxisAlignedTransform( |
| root_surface_transform_, damage_rect); |
| gfx::Transform inverse = root_surface_transform_.GetCheckedInverse(); |
| damage_rect_surface_space = |
| cc::MathUtil::MapEnclosedRectWith2dAxisAlignedTransform(inverse, |
| damage_rect); |
| } |
| |
| // The following call can cause one or more copy requests to be added to the |
| // Surface. Therefore, no code before this point should have assumed |
| // anything about the presence or absence of copy requests after this point. |
| surface->NotifyAggregatedDamage(damage_rect_surface_space, |
| expected_display_time_); |
| } |
| |
| // If any CopyOutputRequests were made at FrameSink level, make sure we grab |
| // them too. |
| surface->TakeCopyOutputRequestsFromClient(); |
| if (surface->IsVideoCaptureOnFromClient()) |
| result.video_capture_enabled = true; |
| |
| if (root_resolved_pass.aggregation().will_draw) |
| surface->OnWillBeDrawn(); |
| |
| const CompositorFrame& frame = surface->GetActiveOrInterpolatedFrame(); |
| for (const SurfaceRange& surface_range : frame.metadata.referenced_surfaces) { |
| damage_ranges_[surface_range.end().frame_sink_id()].push_back( |
| surface_range); |
| if (surface_range.HasDifferentFrameSinkIds()) { |
| damage_ranges_[surface_range.start()->frame_sink_id()].push_back( |
| surface_range); |
| } |
| } |
| |
| for (const SurfaceId& surface_id : surface->active_referenced_surfaces()) { |
| if (!contained_surfaces_.count(surface_id)) { |
| result.undrawn_surfaces.insert(surface_id); |
| ResolvedFrameData* undrawn_surface = GetResolvedFrame(surface_id); |
| if (undrawn_surface) { |
| PrewalkSurface(*undrawn_surface, /*parent_pass=*/nullptr, gfx::Rect(), |
| result); |
| } |
| } |
| } |
| |
| for (auto& resolved_pass : resolved_frame.GetResolvedPasses()) { |
| auto& render_pass = resolved_pass.render_pass(); |
| |
| // Checking for copy requests need to be done after the prewalk because |
| // copy requests can get added after damage is computed. |
| if (!render_pass.copy_requests.empty()) { |
| has_copy_requests_ = true; |
| MarkAndPropagateCopyRequestPasses(resolved_pass); |
| } |
| } |
| |
| referenced_surfaces_.erase(surface->surface_id()); |
| result.content_color_usage = |
| std::max(result.content_color_usage, frame.metadata.content_color_usage); |
| |
| return damage_rect; |
| } |
| |
| void SurfaceAggregator::CopyUndrawnSurfaces(PrewalkResult* prewalk_result) { |
| // undrawn_surfaces are Surfaces that were identified by prewalk as being |
| // referenced by a drawn Surface, but aren't contained in a SurfaceDrawQuad. |
| // They need to be iterated over to ensure that any copy requests on them |
| // (or on Surfaces they reference) are executed. |
| std::vector<SurfaceId> surfaces_to_copy( |
| prewalk_result->undrawn_surfaces.begin(), |
| prewalk_result->undrawn_surfaces.end()); |
| DCHECK(referenced_surfaces_.empty()); |
| |
| for (size_t i = 0; i < surfaces_to_copy.size(); i++) { |
| SurfaceId surface_id = surfaces_to_copy[i]; |
| const ResolvedFrameData* resolved_frame = GetResolvedFrame(surface_id); |
| if (!resolved_frame) |
| continue; |
| |
| Surface* surface = resolved_frame->surface(); |
| if (!surface->HasCopyOutputRequests()) { |
| // Children are not necessarily included in undrawn_surfaces (because |
| // they weren't referenced directly from a drawn surface), but may have |
| // copy requests, so make sure to check them as well. |
| for (const SurfaceId& child_id : surface->active_referenced_surfaces()) { |
| // Don't iterate over the child Surface if it was already listed as a |
| // child of a different Surface, or in the case where there's infinite |
| // recursion. |
| if (!prewalk_result->undrawn_surfaces.count(child_id)) { |
| surfaces_to_copy.push_back(child_id); |
| prewalk_result->undrawn_surfaces.insert(child_id); |
| } |
| } |
| } else { |
| prewalk_result->undrawn_surfaces.erase(surface_id); |
| referenced_surfaces_.insert(surface_id); |
| CopyPasses(*resolved_frame); |
| referenced_surfaces_.erase(surface_id); |
| } |
| } |
| } |
| |
| void SurfaceAggregator::MarkAndPropagateCopyRequestPasses( |
| ResolvedPassData& resolved_pass) { |
| if (resolved_pass.aggregation().in_copy_request_pass) |
| return; |
| |
| resolved_pass.aggregation().in_copy_request_pass = true; |
| for (auto* child_pass : resolved_pass.aggregation().embedded_passes) { |
| MarkAndPropagateCopyRequestPasses(*child_pass); |
| } |
| } |
| |
| AggregatedFrame SurfaceAggregator::Aggregate( |
| const SurfaceId& surface_id, |
| base::TimeTicks expected_display_time, |
| gfx::OverlayTransform display_transform, |
| const gfx::Rect& target_damage, |
| int64_t display_trace_id) { |
| DCHECK(!expected_display_time.is_null()); |
| DCHECK(contained_surfaces_.empty()); |
| |
| DCHECK(!is_inside_aggregate_); |
| is_inside_aggregate_ = true; |
| |
| root_surface_id_ = surface_id; |
| |
| // Start recording new stats for this aggregation. |
| stats_.emplace(); |
| |
| base::ElapsedTimer prewalk_timer; |
| ResolvedFrameData* resolved_frame = GetResolvedFrame(surface_id); |
| |
| if (!resolved_frame || !resolved_frame->is_valid()) { |
| ResetAfterAggregate(); |
| return {}; |
| } |
| |
| Surface* surface = resolved_frame->surface(); |
| CheckFrameSinksChanged(surface); |
| |
| display_trace_id_ = display_trace_id; |
| expected_display_time_ = expected_display_time; |
| |
| const CompositorFrame& root_surface_frame = |
| surface->GetActiveOrInterpolatedFrame(); |
| TRACE_EVENT_WITH_FLOW2( |
| "viz,benchmark", "Graphics.Pipeline", |
| TRACE_ID_GLOBAL(root_surface_frame.metadata.begin_frame_ack.trace_id), |
| TRACE_EVENT_FLAG_FLOW_IN | TRACE_EVENT_FLAG_FLOW_OUT, "step", |
| "SurfaceAggregation", "display_trace", display_trace_id_); |
| |
| AggregatedFrame frame; |
| frame.top_controls_visible_height = |
| root_surface_frame.metadata.top_controls_visible_height; |
| |
| dest_pass_list_ = &frame.render_pass_list; |
| surface_damage_rect_list_ = &frame.surface_damage_rect_list_; |
| |
| auto& root_render_pass = root_surface_frame.render_pass_list.back(); |
| |
| // The root render pass on the root surface can not have backdrop filters. |
| DCHECK(!root_render_pass->backdrop_filters.HasFilterThatMovesPixels()); |
| |
| const gfx::Size viewport_bounds = root_render_pass->output_rect.size(); |
| root_surface_transform_ = gfx::OverlayTransformToTransform( |
| display_transform, gfx::SizeF(viewport_bounds)); |
| |
| // Reset state that couldn't be reset in ResetAfterAggregate(). |
| damage_ranges_.clear(); |
| |
| DCHECK(referenced_surfaces_.empty()); |
| |
| // The root surface root render pass is the start of the embedding tree. |
| resolved_frame->GetRootRenderPassData().aggregation().will_draw = true; |
| |
| PrewalkResult prewalk_result; |
| gfx::Rect prewalk_damage_rect = |
| PrewalkSurface(*resolved_frame, |
| /*parent_pass=*/nullptr, |
| /*damage_from_parent=*/gfx::Rect(), prewalk_result); |
| stats_->prewalk_time = prewalk_timer.Elapsed(); |
| |
| root_damage_rect_ = prewalk_damage_rect; |
| // |root_damage_rect_| is used to restrict aggregating quads only if they |
| // intersect this area. |
| root_damage_rect_.Union(target_damage); |
| |
| // Changing color usage will cause the renderer to reshape the output surface, |
| // therefore the renderer might expand the damage to the whole frame. The |
| // following makes sure SA will produce all the quads to cover the full frame. |
| bool color_usage_changed = |
| root_content_color_usage_ != prewalk_result.content_color_usage; |
| if (color_usage_changed) { |
| root_damage_rect_ = cc::MathUtil::MapEnclosedRectWith2dAxisAlignedTransform( |
| root_surface_transform_, |
| gfx::Rect(root_surface_frame.size_in_pixels())); |
| root_content_color_usage_ = prewalk_result.content_color_usage; |
| } |
| |
| if (prewalk_result.frame_sinks_changed) |
| manager_->AggregatedFrameSinksChanged(); |
| |
| frame.has_copy_requests = has_copy_requests_ && take_copy_requests_; |
| frame.video_capture_enabled = prewalk_result.video_capture_enabled; |
| frame.content_color_usage = prewalk_result.content_color_usage; |
| frame.page_fullscreen_mode = prewalk_result.page_fullscreen_mode; |
| |
| base::ElapsedTimer copy_timer; |
| CopyUndrawnSurfaces(&prewalk_result); |
| referenced_surfaces_.insert(surface_id); |
| CopyPasses(*resolved_frame); |
| referenced_surfaces_.erase(surface_id); |
| DCHECK(referenced_surfaces_.empty()); |
| stats_->copy_time = copy_timer.Elapsed(); |
| |
| RecordStatHistograms(); |
| |
| if (dest_pass_list_->empty()) { |
| ResetAfterAggregate(); |
| return {}; |
| } |
| |
| // The root render pass damage might have been expanded by target_damage (the |
| // area that might need to be recomposited on the target surface). We restrict |
| // the damage_rect of the root render pass to the one caused by the source |
| // surfaces, except when drawing delegated ink trails. |
| // The damage on the root render pass should not include the expanded area |
| // since Renderer and OverlayProcessor expect the non expanded damage. The |
| // only exception is when delegated ink trails are being drawn, in which case |
| // the root render pass needs to contain the expanded area, as |target_damage| |
| // also reflects the delegated ink trail damage rect. |
| auto* last_pass = dest_pass_list_->back().get(); |
| |
| if (!color_usage_changed && !last_frame_had_delegated_ink_ && |
| !RenderPassNeedsFullDamage(resolved_frame->GetRootRenderPassData())) { |
| last_pass->damage_rect.Intersect(prewalk_damage_rect); |
| } |
| |
| AddColorConversionPass(); |
| AddRootReadbackPass(); |
| |
| ProcessAddedAndRemovedSurfaces(); |
| contained_surfaces_.swap(previous_contained_surfaces_); |
| contained_frame_sinks_.swap(previous_contained_frame_sinks_); |
| |
| ResetAfterAggregate(); |
| |
| for (auto& contained_surface_id : previous_contained_surfaces_) { |
| surface = manager_->GetSurfaceForId(contained_surface_id); |
| if (surface) { |
| surface->allocation_group()->TakeAggregatedLatencyInfoUpTo( |
| surface, &frame.latency_info); |
| } |
| if (!ui::LatencyInfo::Verify(frame.latency_info, |
| "SurfaceAggregator::Aggregate")) { |
| break; |
| } |
| } |
| |
| if (delegated_ink_metadata_) { |
| frame.delegated_ink_metadata = std::move(delegated_ink_metadata_); |
| last_frame_had_delegated_ink_ = true; |
| } else { |
| last_frame_had_delegated_ink_ = false; |
| } |
| |
| if (frame_annotator_) |
| frame_annotator_->AnnotateAggregatedFrame(&frame); |
| |
| return frame; |
| } |
| |
| void SurfaceAggregator::RecordStatHistograms() { |
| UMA_HISTOGRAM_COUNTS_100( |
| "Compositing.SurfaceAggregator.PrewalkedSurfaceCount", |
| stats_->prewalked_surface_count); |
| UMA_HISTOGRAM_COUNTS_100("Compositing.SurfaceAggregator.CopiedSurfaceCount", |
| stats_->copied_surface_count); |
| |
| UMA_HISTOGRAM_CUSTOM_MICROSECONDS_TIMES( |
| "Compositing.SurfaceAggregator.PrewalkUs", stats_->prewalk_time, |
| kHistogramMinTime, kHistogramMaxTime, kHistogramTimeBuckets); |
| UMA_HISTOGRAM_CUSTOM_MICROSECONDS_TIMES( |
| "Compositing.SurfaceAggregator.CopyUs", stats_->copy_time, |
| kHistogramMinTime, kHistogramMaxTime, kHistogramTimeBuckets); |
| UMA_HISTOGRAM_CUSTOM_MICROSECONDS_TIMES( |
| "Compositing.SurfaceAggregator.DeclareResourcesUs", |
| stats_->declare_resources_time, kHistogramMinTime, kHistogramMaxTime, |
| kHistogramTimeBuckets); |
| |
| stats_.reset(); |
| } |
| |
| void SurfaceAggregator::ResetAfterAggregate() { |
| DCHECK(is_inside_aggregate_); |
| |
| is_inside_aggregate_ = false; |
| dest_pass_list_ = nullptr; |
| surface_damage_rect_list_ = nullptr; |
| current_zero_damage_rect_is_not_recorded_ = false; |
| expected_display_time_ = base::TimeTicks(); |
| display_trace_id_ = -1; |
| has_pixel_moving_backdrop_filter_ = false; |
| has_copy_requests_ = false; |
| resolved_surface_ranges_.clear(); |
| contained_surfaces_.clear(); |
| contained_frame_sinks_.clear(); |
| |
| // Reset resolved frame data from this aggregation. |
| for (auto& [surface_id, resolved_frame] : resolved_frames_) |
| resolved_frame.ResetAfterAggregation(); |
| } |
| |
| void SurfaceAggregator::SetFullDamageForSurface(const SurfaceId& surface_id) { |
| auto iter = resolved_frames_.find(surface_id); |
| if (iter != resolved_frames_.end()) |
| iter->second.SetFullDamageForNextAggregation(); |
| } |
| |
| void SurfaceAggregator::SetDisplayColorSpaces( |
| const gfx::DisplayColorSpaces& display_color_spaces) { |
| display_color_spaces_ = display_color_spaces; |
| } |
| |
| void SurfaceAggregator::SetMaxRenderTargetSize(int max_size) { |
| DCHECK_GE(max_size, 0); |
| max_render_target_size_ = max_size; |
| } |
| |
| bool SurfaceAggregator::NotifySurfaceDamageAndCheckForDisplayDamage( |
| const SurfaceId& surface_id) { |
| auto iter = resolved_frames_.find(surface_id); |
| if (iter != resolved_frames_.end()) { |
| auto& resolved_frame = iter->second; |
| DCHECK(resolved_frame.surface()->HasActiveFrame()); |
| if (resolved_frame.surface() |
| ->GetActiveOrInterpolatedFrame() |
| .resource_list.empty()) { |
| // When a client submits a CompositorFrame without resources it's |
| // typically done to force return of existing resources to the client. |
| resolved_frame.ForceReleaseResource(); |
| } |
| return true; |
| } |
| |
| auto it = damage_ranges_.find(surface_id.frame_sink_id()); |
| if (it == damage_ranges_.end()) |
| return false; |
| |
| for (const SurfaceRange& surface_range : it->second) { |
| if (surface_range.IsInRangeInclusive(surface_id)) |
| return true; |
| } |
| |
| return false; |
| } |
| |
| bool SurfaceAggregator::HasFrameAnnotator() const { |
| return !!frame_annotator_; |
| } |
| |
| void SurfaceAggregator::SetFrameAnnotator( |
| std::unique_ptr<FrameAnnotator> frame_annotator) { |
| DCHECK(!frame_annotator_); |
| frame_annotator_ = std::move(frame_annotator); |
| } |
| |
| void SurfaceAggregator::DestroyFrameAnnotator() { |
| DCHECK(frame_annotator_); |
| frame_annotator_.reset(); |
| } |
| |
| bool SurfaceAggregator::IsRootSurface(const Surface* surface) const { |
| return surface->surface_id() == root_surface_id_; |
| } |
| |
| // Transform the point and presentation area of the metadata to be in the root |
| // target space. They need to be in the root target space because they will |
| // eventually be drawn directly onto the buffer just before being swapped onto |
| // the screen, so root target space is required so that they are positioned |
| // correctly. After transforming, they are stored in the |
| // |delegated_ink_metadata_| member in order to be placed on the final |
| // aggregated frame, after which the member is then cleared. |
| void SurfaceAggregator::TransformAndStoreDelegatedInkMetadata( |
| const gfx::Transform& parent_quad_to_root_target_transform, |
| const gfx::DelegatedInkMetadata* metadata) { |
| if (delegated_ink_metadata_) { |
| // This member could already be populated in two scenarios: |
| // 1. The delegated ink metadata was committed to a frame's metadata that |
| // wasn't ultimately used to produce a frame, but is now being used. |
| // 2. There are two or more ink strokes requesting a delegated ink trail |
| // simultaneously. |
| // In both cases, we want to default to using a "last write wins" strategy |
| // to determine the metadata to put on the final aggregated frame. This |
| // avoids potential issues of using stale ink metadata in the first scenario |
| // by always using the newest one. For the second scenario, it would be a |
| // very niche use case to have more than one at a time, so the explainer |
| // specifies using last write wins to decide. |
| base::TimeTicks stored_time = delegated_ink_metadata_->timestamp(); |
| base::TimeTicks new_time = metadata->timestamp(); |
| if (new_time < stored_time) |
| return; |
| } |
| |
| gfx::PointF point = |
| parent_quad_to_root_target_transform.MapPoint(metadata->point()); |
| gfx::RectF area = parent_quad_to_root_target_transform.MapRect( |
| metadata->presentation_area()); |
| delegated_ink_metadata_ = std::make_unique<gfx::DelegatedInkMetadata>( |
| point, metadata->diameter(), metadata->color(), metadata->timestamp(), |
| area, metadata->frame_time(), metadata->is_hovering()); |
| |
| TRACE_EVENT_INSTANT2( |
| "viz", "SurfaceAggregator::TransformAndStoreDelegatedInkMetadata", |
| TRACE_EVENT_SCOPE_THREAD, "original metadata", metadata->ToString(), |
| "transformed metadata", delegated_ink_metadata_->ToString()); |
| } |
| |
| void SurfaceAggregator::DebugLogSurface(const Surface* surface, |
| bool will_draw) { |
| DBG_LOG("aggregator.surface.log", "D%d - %s, %s draws=%s", |
| static_cast<int>(referenced_surfaces_.size()), |
| surface->surface_id().ToString().c_str(), |
| surface->size_in_pixels().ToString().c_str(), |
| will_draw ? "true" : "false"); |
| } |
| |
| } // namespace viz |