| // Copyright 2022 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/overlay_candidate_factory.h" |
| |
| #include "base/containers/contains.h" |
| #include "build/build_config.h" |
| #include "cc/base/math_util.h" |
| #include "components/viz/common/quads/aggregated_render_pass_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/texture_draw_quad.h" |
| #include "components/viz/common/quads/tile_draw_quad.h" |
| #include "components/viz/common/quads/video_hole_draw_quad.h" |
| #include "components/viz/common/quads/yuv_video_draw_quad.h" |
| #include "components/viz/common/resources/resource_id.h" |
| #include "components/viz/common/viz_utils.h" |
| #include "components/viz/service/debugger/viz_debugger.h" |
| #include "components/viz/service/display/display_resource_provider.h" |
| #include "third_party/skia/include/core/SkColor.h" |
| #include "ui/gfx/buffer_format_util.h" |
| #include "ui/gfx/geometry/rect.h" |
| #include "ui/gfx/geometry/rect_conversions.h" |
| #include "ui/gfx/geometry/rect_f.h" |
| #include "ui/gfx/geometry/size.h" |
| #include "ui/gfx/geometry/size_conversions.h" |
| #include "ui/gfx/overlay_transform_utils.h" |
| #include "ui/gfx/video_types.h" |
| |
| namespace viz { |
| |
| namespace { |
| |
| const gfx::BufferFormat kOverlayFormats[] = { |
| gfx::BufferFormat::RGBX_8888, gfx::BufferFormat::RGBA_8888, |
| gfx::BufferFormat::BGRX_8888, gfx::BufferFormat::BGRA_8888, |
| gfx::BufferFormat::BGR_565, gfx::BufferFormat::YUV_420_BIPLANAR, |
| gfx::BufferFormat::P010}; |
| |
| enum Axis { NONE, AXIS_POS_X, AXIS_NEG_X, AXIS_POS_Y, AXIS_NEG_Y }; |
| |
| Axis VectorToAxis(const gfx::Vector3dF& vec) { |
| if (!cc::MathUtil::IsWithinEpsilon(vec.z(), 0.f)) |
| return NONE; |
| const bool x_zero = cc::MathUtil::IsWithinEpsilon(vec.x(), 0.f); |
| const bool y_zero = cc::MathUtil::IsWithinEpsilon(vec.y(), 0.f); |
| if (x_zero && !y_zero) |
| return (vec.y() > 0.f) ? AXIS_POS_Y : AXIS_NEG_Y; |
| else if (y_zero && !x_zero) |
| return (vec.x() > 0.f) ? AXIS_POS_X : AXIS_NEG_X; |
| else |
| return NONE; |
| } |
| |
| gfx::OverlayTransform GetOverlayTransform(const gfx::Transform& quad_transform, |
| bool y_flipped) { |
| if (!quad_transform.Preserves2dAxisAlignment()) { |
| return gfx::OVERLAY_TRANSFORM_INVALID; |
| } |
| |
| gfx::Vector3dF x_axis = cc::MathUtil::GetXAxis(quad_transform); |
| gfx::Vector3dF y_axis = cc::MathUtil::GetYAxis(quad_transform); |
| if (y_flipped) { |
| y_axis.Scale(-1.f); |
| } |
| |
| Axis x_to = VectorToAxis(x_axis); |
| Axis y_to = VectorToAxis(y_axis); |
| |
| if (x_to == AXIS_POS_X && y_to == AXIS_POS_Y) |
| return gfx::OVERLAY_TRANSFORM_NONE; |
| else if (x_to == AXIS_NEG_X && y_to == AXIS_POS_Y) |
| return gfx::OVERLAY_TRANSFORM_FLIP_HORIZONTAL; |
| else if (x_to == AXIS_POS_X && y_to == AXIS_NEG_Y) |
| return gfx::OVERLAY_TRANSFORM_FLIP_VERTICAL; |
| else if (x_to == AXIS_NEG_Y && y_to == AXIS_POS_X) |
| return gfx::OVERLAY_TRANSFORM_ROTATE_270; |
| else if (x_to == AXIS_NEG_X && y_to == AXIS_NEG_Y) |
| return gfx::OVERLAY_TRANSFORM_ROTATE_180; |
| else if (x_to == AXIS_POS_Y && y_to == AXIS_NEG_X) |
| return gfx::OVERLAY_TRANSFORM_ROTATE_90; |
| else |
| return gfx::OVERLAY_TRANSFORM_INVALID; |
| } |
| |
| constexpr double kEpsilon = 0.0001; |
| |
| // Determine why the transformation isn't axis aligned. A transform with z |
| // components or perspective would require a full 4x4 matrix to delegate, a |
| // transform with a shear component would require a 2x2 matrix to delegate, and |
| // a 2d rotation transform could be delegated with an angle. |
| // This is only useful for delegated compositing. |
| OverlayCandidate::CandidateStatus GetReasonForTransformNotAxisAligned( |
| const gfx::Transform& transform) { |
| if (transform.HasPerspective() || !transform.IsFlat()) |
| return OverlayCandidate::CandidateStatus::kFailNotAxisAligned3dTransform; |
| |
| // The transform has a shear component if the x and y sub-vectors are not |
| // perpendicular (have a non-zero dot product). |
| gfx::Vector2dF x_part(transform.rc(0, 0), transform.rc(1, 0)); |
| gfx::Vector2dF y_part(transform.rc(0, 1), transform.rc(1, 1)); |
| // Normalize to avoid numerical issues. |
| x_part.InvScale(x_part.Length()); |
| y_part.InvScale(y_part.Length()); |
| if (std::abs(gfx::DotProduct(x_part, y_part)) > kEpsilon) |
| return OverlayCandidate::CandidateStatus::kFailNotAxisAligned2dShear; |
| |
| return OverlayCandidate::CandidateStatus::kFailNotAxisAligned2dRotation; |
| } |
| |
| } // namespace |
| |
| OverlayCandidate::CandidateStatus OverlayCandidateFactory::FromDrawQuad( |
| const DrawQuad* quad, |
| OverlayCandidate& candidate) const { |
| // It is currently not possible to set a color conversion matrix on an HW |
| // overlay plane. |
| // TODO(https://crbug.com/792757): Remove this check once the bug is resolved. |
| if (has_custom_color_matrix_) { |
| return CandidateStatus::kFailColorMatrix; |
| } |
| |
| const SharedQuadState* sqs = quad->shared_quad_state; |
| |
| // We don't support an opacity value different than one for an overlay plane. |
| // Render pass quads should have their |sqs| opacity integrated directly into |
| // their final output buffers. |
| if (!is_delegated_context_ && |
| !cc::MathUtil::IsWithinEpsilon(sqs->opacity, 1.0f)) { |
| return CandidateStatus::kFailOpacity; |
| } |
| candidate.opacity = sqs->opacity; |
| candidate.rounded_corners = sqs->mask_filter_info.rounded_corner_bounds(); |
| |
| // We support only kSrc (no blending) and kSrcOver (blending with premul). |
| if (!(sqs->blend_mode == SkBlendMode::kSrc || |
| sqs->blend_mode == SkBlendMode::kSrcOver)) { |
| return CandidateStatus::kFailBlending; |
| } |
| |
| candidate.requires_overlay = OverlayCandidate::RequiresOverlay(quad); |
| candidate.overlay_damage_index = |
| sqs->overlay_damage_index.value_or(OverlayCandidate::kInvalidDamageIndex); |
| |
| switch (quad->material) { |
| case DrawQuad::Material::kTextureContent: |
| return FromTextureQuad(TextureDrawQuad::MaterialCast(quad), candidate); |
| case DrawQuad::Material::kVideoHole: |
| return FromVideoHoleQuad(VideoHoleDrawQuad::MaterialCast(quad), |
| candidate); |
| case DrawQuad::Material::kSolidColor: |
| if (!is_delegated_context_) |
| return CandidateStatus::kFailQuadNotSupported; |
| return FromSolidColorQuad(SolidColorDrawQuad::MaterialCast(quad), |
| candidate); |
| case DrawQuad::Material::kAggregatedRenderPass: |
| if (!is_delegated_context_) |
| return CandidateStatus::kFailQuadNotSupported; |
| return FromAggregateQuad(AggregatedRenderPassDrawQuad::MaterialCast(quad), |
| candidate); |
| case DrawQuad::Material::kTiledContent: |
| if (!is_delegated_context_) |
| return CandidateStatus::kFailQuadNotSupported; |
| return FromTileQuad(TileDrawQuad::MaterialCast(quad), candidate); |
| default: |
| break; |
| } |
| |
| return CandidateStatus::kFailQuadNotSupported; |
| } |
| |
| OverlayCandidateFactory::OverlayCandidateFactory( |
| const AggregatedRenderPass* render_pass, |
| DisplayResourceProvider* resource_provider, |
| const SurfaceDamageRectList* surface_damage_rect_list, |
| const SkM44* output_color_matrix, |
| const gfx::RectF primary_rect, |
| const OverlayProcessorInterface::FilterOperationsMap* render_pass_filters, |
| bool is_delegated_context, |
| bool supports_clip_rect, |
| bool supports_arbitrary_transform, |
| bool supports_rounded_corner_masks) |
| : render_pass_(render_pass), |
| resource_provider_(resource_provider), |
| surface_damage_rect_list_(surface_damage_rect_list), |
| primary_rect_(primary_rect), |
| render_pass_filters_(render_pass_filters), |
| is_delegated_context_(is_delegated_context), |
| supports_clip_rect_(supports_clip_rect), |
| supports_arbitrary_transform_(supports_arbitrary_transform), |
| supports_rounded_display_masks_(supports_rounded_corner_masks) { |
| DCHECK(supports_clip_rect_ || !supports_arbitrary_transform_); |
| |
| has_custom_color_matrix_ = *output_color_matrix != SkM44(); |
| |
| // TODO(crbug.com/1323002): Replace this set with a simple ordered linear |
| // search when this bug is resolved. |
| base::flat_set<size_t> indices_with_quad_damage; |
| for (auto* sqs : render_pass_->shared_quad_state_list) { |
| // If a |sqs| has a damage index it will only be associated with a single |
| // draw quad. |
| if (sqs->overlay_damage_index.has_value()) { |
| indices_with_quad_damage.insert(sqs->overlay_damage_index.value()); |
| } |
| } |
| |
| for (size_t i = 0; i < (*surface_damage_rect_list_).size(); i++) { |
| // Add this damage only if it does not correspond to a specific quad. |
| // Ideally any damage that we might want to separate out (think overlays) |
| // will not end up in this |unassigned_surface_damage_| rect. |
| if (!indices_with_quad_damage.contains(i)) { |
| unassigned_surface_damage_.Union((*surface_damage_rect_list_)[i]); |
| } |
| } |
| } |
| |
| OverlayCandidateFactory::~OverlayCandidateFactory() = default; |
| |
| float OverlayCandidateFactory::EstimateVisibleDamage( |
| const DrawQuad* quad, |
| const OverlayCandidate& candidate, |
| QuadList::ConstIterator quad_list_begin, |
| QuadList::ConstIterator quad_list_end) const { |
| gfx::Rect quad_damage = gfx::ToEnclosingRect(GetDamageEstimate(candidate)); |
| float occluded_damage_estimate_total = 0.f; |
| for (auto overlap_iter = quad_list_begin; overlap_iter != quad_list_end; |
| ++overlap_iter) { |
| gfx::Rect overlap_rect = gfx::ToEnclosingRect(cc::MathUtil::MapClippedRect( |
| overlap_iter->shared_quad_state->quad_to_target_transform, |
| gfx::RectF(overlap_iter->rect))); |
| |
| // Opaque quad that (partially) occludes this candidate. |
| if (!OverlayCandidate::IsInvisibleQuad(*overlap_iter) && |
| !overlap_iter->ShouldDrawWithBlending()) { |
| overlap_rect.Intersect(quad_damage); |
| occluded_damage_estimate_total += overlap_rect.size().GetArea(); |
| } |
| } |
| // In the case of overlapping UI the |occluded_damage_estimate_total| may |
| // exceed the |quad|'s damage rect that is in consideration. This is the |
| // reason why this computation is an estimate and why we have the max clamping |
| // below. |
| return std::max( |
| 0.f, quad_damage.size().GetArea() - occluded_damage_estimate_total); |
| } |
| |
| bool OverlayCandidateFactory::IsOccludedByFilteredQuad( |
| const OverlayCandidate& candidate, |
| QuadList::ConstIterator quad_list_begin, |
| QuadList::ConstIterator quad_list_end, |
| const base::flat_map<AggregatedRenderPassId, cc::FilterOperations*>& |
| render_pass_backdrop_filters) const { |
| gfx::RectF target_rect = |
| OverlayCandidate::DisplayRectInTargetSpace(candidate); |
| for (auto overlap_iter = quad_list_begin; overlap_iter != quad_list_end; |
| ++overlap_iter) { |
| if (auto* render_pass_draw_quad = |
| overlap_iter->DynamicCast<AggregatedRenderPassDrawQuad>()) { |
| gfx::RectF overlap_rect = cc::MathUtil::MapClippedRect( |
| overlap_iter->shared_quad_state->quad_to_target_transform, |
| gfx::RectF(overlap_iter->rect)); |
| |
| if (target_rect.Intersects(overlap_rect) && |
| render_pass_backdrop_filters.count( |
| render_pass_draw_quad->render_pass_id)) { |
| return true; |
| } |
| } |
| } |
| return false; |
| } |
| |
| bool OverlayCandidateFactory::IsOccluded( |
| const OverlayCandidate& candidate, |
| QuadList::ConstIterator quad_list_begin, |
| QuadList::ConstIterator quad_list_end) const { |
| // The rects are rounded as they're snapped by the compositor to pixel unless |
| // it is AA'ed, in which case, it won't be overlaid. |
| gfx::Rect target_rect = |
| gfx::ToRoundedRect(OverlayCandidate::DisplayRectInTargetSpace(candidate)); |
| |
| // Check that no visible quad overlaps the candidate. |
| for (auto overlap_iter = quad_list_begin; overlap_iter != quad_list_end; |
| ++overlap_iter) { |
| gfx::Rect overlap_rect = gfx::ToRoundedRect(cc::MathUtil::MapClippedRect( |
| overlap_iter->shared_quad_state->quad_to_target_transform, |
| gfx::RectF(overlap_iter->rect))); |
| |
| if (!OverlayCandidate::IsInvisibleQuad(*overlap_iter) && |
| target_rect.Intersects(overlap_rect)) { |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| OverlayCandidate::CandidateStatus OverlayCandidateFactory::FromDrawQuadResource( |
| const DrawQuad* quad, |
| ResourceId resource_id, |
| bool y_flipped, |
| OverlayCandidate& candidate) const { |
| if (resource_id != kInvalidResourceId && |
| !resource_provider_->IsOverlayCandidate(resource_id)) |
| return CandidateStatus::kFailNotOverlay; |
| |
| if (quad->visible_rect.IsEmpty()) |
| return CandidateStatus::kFailVisible; |
| |
| if (resource_id != kInvalidResourceId) { |
| candidate.format = resource_provider_->GetBufferFormat(resource_id); |
| // TODO(b/181974042): We should probably also propagate the |
| // resource_provider_->GetSamplerColorSpace() -- while the display |
| // controller is not expected to use the GPU sampler, some hardware can do |
| // per-plane color management. We just don't have the API for it yet (at |
| // least on ChromeOS). |
| candidate.color_space = |
| resource_provider_->GetOverlayColorSpace(resource_id); |
| candidate.hdr_metadata = resource_provider_->GetHDRMetadata(resource_id); |
| |
| if (!base::Contains(kOverlayFormats, candidate.format)) |
| return CandidateStatus::kFailBufferFormat; |
| } |
| |
| const SharedQuadState* sqs = quad->shared_quad_state; |
| |
| candidate.display_rect = gfx::RectF(quad->rect); |
| if (supports_arbitrary_transform_) { |
| gfx::Transform transform = sqs->quad_to_target_transform; |
| if (y_flipped) { |
| transform.PreConcat(gfx::OverlayTransformToTransform( |
| gfx::OVERLAY_TRANSFORM_FLIP_VERTICAL, candidate.display_rect.size())); |
| } |
| candidate.transform = transform; |
| } else { |
| gfx::OverlayTransform overlay_transform = |
| GetOverlayTransform(sqs->quad_to_target_transform, y_flipped); |
| if (overlay_transform == gfx::OVERLAY_TRANSFORM_INVALID) { |
| return is_delegated_context_ ? GetReasonForTransformNotAxisAligned( |
| sqs->quad_to_target_transform) |
| : CandidateStatus::kFailNotAxisAligned; |
| } |
| candidate.transform = overlay_transform; |
| |
| candidate.display_rect = |
| sqs->quad_to_target_transform.MapRect(candidate.display_rect); |
| } |
| |
| candidate.clip_rect = sqs->clip_rect; |
| candidate.is_opaque = |
| !quad->ShouldDrawWithBlendingForReasonOtherThanMaskFilter(); |
| candidate.has_mask_filter = !sqs->mask_filter_info.IsEmpty(); |
| |
| if (resource_id != kInvalidResourceId) { |
| candidate.resource_size_in_pixels = |
| resource_provider_->GetResourceBackedSize(resource_id); |
| } else { |
| // The resource size is used to calculate the damage rect, so we set it here |
| // even if there is no resource. For resource-less overlays it's defined in |
| // a target space. |
| // It is unclear how to support arbitrary transforms in this case, since an |
| // e.g. rotation could make the target space bounds non-axis-aligned. |
| DCHECK(absl::holds_alternative<gfx::OverlayTransform>(candidate.transform)); |
| candidate.resource_size_in_pixels = |
| gfx::Size(candidate.display_rect.size().width(), |
| candidate.display_rect.size().height()); |
| } |
| |
| AssignDamage(quad, candidate); |
| candidate.resource_id = resource_id; |
| |
| if (resource_id != kInvalidResourceId) { |
| candidate.mailbox = resource_provider_->GetMailbox(resource_id); |
| |
| if (!is_delegated_context_) { |
| struct TrackingIdData { |
| gfx::Rect rect; |
| FrameSinkId frame_sink_id; |
| }; |
| |
| TrackingIdData track_data{ |
| quad->rect, |
| resource_provider_->GetSurfaceId(resource_id).frame_sink_id()}; |
| candidate.tracking_id = base::Hash(&track_data, sizeof(track_data)); |
| } |
| } |
| |
| if (is_delegated_context_) { |
| // Lacros cannot currently delegate clip rects on quads that extend outside |
| // the primary rect. This is because there are bugs that cause the Lacros |
| // window and drop shadow to move incorrectly in that case. |
| bool quad_within_window = primary_rect_.Contains(candidate.display_rect); |
| bool transform_supports_clipping = |
| supports_arbitrary_transform_ || |
| absl::holds_alternative<gfx::OverlayTransform>(candidate.transform); |
| bool has_content_clipping = quad->visible_rect != quad->rect; |
| bool can_delegate_clipping = supports_clip_rect_ && quad_within_window && |
| transform_supports_clipping && |
| !has_content_clipping; |
| if (can_delegate_clipping) { |
| if (candidate.clip_rect.has_value() && candidate.clip_rect->IsEmpty()) { |
| return CandidateStatus::kFailVisible; |
| } |
| } else { |
| // Apply clipping to the |display_rect| and |uv_rect| directly. |
| auto status = DoGeometricClipping(quad, candidate); |
| if (status != CandidateStatus::kSuccess) { |
| return status; |
| } |
| } |
| } |
| |
| return CandidateStatus::kSuccess; |
| } |
| |
| OverlayCandidate::CandidateStatus OverlayCandidateFactory::DoGeometricClipping( |
| const DrawQuad* quad, |
| OverlayCandidate& candidate) const { |
| gfx::RectF clip_to_apply = candidate.display_rect; |
| |
| auto* rpdq = quad->DynamicCast<AggregatedRenderPassDrawQuad>(); |
| if (rpdq) { |
| auto filter_it = render_pass_filters_->find(rpdq->render_pass_id); |
| if (filter_it != render_pass_filters_->end()) { |
| clip_to_apply = gfx::RectF(GetExpandedRectWithPixelMovingForegroundFilter( |
| *rpdq, *filter_it->second)); |
| } |
| } |
| |
| if (candidate.clip_rect.has_value()) { |
| clip_to_apply.Intersect(gfx::RectF(*candidate.clip_rect)); |
| } |
| |
| // TODO(rivr): Apply the same |visible_rect| and |display_rect| clip logic |
| // when delegating |clip_rect|. |
| if (quad->visible_rect != quad->rect) { |
| auto visible_rect = gfx::RectF(quad->visible_rect); |
| visible_rect = |
| quad->shared_quad_state->quad_to_target_transform.MapRect(visible_rect); |
| clip_to_apply.Intersect(visible_rect); |
| } |
| |
| // TODO(https://crbug.com/1300552) : Tile quads can overlay other quads |
| // and the window by one pixel. Exo does not yet clip these quads so we |
| // need to clip here with the |primary_rect|. |
| clip_to_apply.Intersect(primary_rect_); |
| |
| if (clip_to_apply.IsEmpty()) { |
| return CandidateStatus::kFailVisible; |
| } |
| |
| // Render passes must be clipped after drawing in 'PrepareRenderPassOverlay' |
| // as filters can expand their display size. |
| if (!rpdq) { |
| OverlayCandidate::ApplyClip(candidate, clip_to_apply); |
| candidate.clip_rect = absl::nullopt; |
| } |
| |
| return CandidateStatus::kSuccess; |
| } |
| |
| OverlayCandidate::CandidateStatus OverlayCandidateFactory::FromAggregateQuad( |
| const AggregatedRenderPassDrawQuad* quad, |
| OverlayCandidate& candidate) const { |
| auto rtn = FromDrawQuadResource(quad, kInvalidResourceId, false, candidate); |
| if (rtn == CandidateStatus::kSuccess) { |
| candidate.rpdq = quad; |
| } |
| return rtn; |
| } |
| |
| OverlayCandidate::CandidateStatus OverlayCandidateFactory::FromSolidColorQuad( |
| const SolidColorDrawQuad* quad, |
| OverlayCandidate& candidate) const { |
| auto rtn = FromDrawQuadResource(quad, kInvalidResourceId, false, candidate); |
| |
| if (rtn == CandidateStatus::kSuccess) { |
| candidate.color = quad->color; |
| // Mark this candidate a solid color as the |color| member can be either a |
| // background of the overlay or a color of the solid color quad. |
| candidate.is_solid_color = true; |
| } |
| return rtn; |
| } |
| |
| // For VideoHoleDrawQuad, only calculate geometry information and put it in the |
| // |candidate|. |
| OverlayCandidate::CandidateStatus OverlayCandidateFactory::FromVideoHoleQuad( |
| const VideoHoleDrawQuad* quad, |
| OverlayCandidate& candidate) const { |
| candidate.display_rect = gfx::RectF(quad->rect); |
| const SharedQuadState* sqs = quad->shared_quad_state; |
| if (supports_arbitrary_transform_) { |
| candidate.transform = sqs->quad_to_target_transform; |
| } else { |
| gfx::OverlayTransform overlay_transform = |
| GetOverlayTransform(sqs->quad_to_target_transform, false); |
| if (overlay_transform == gfx::OVERLAY_TRANSFORM_INVALID) |
| return CandidateStatus::kFailNotAxisAligned; |
| candidate.transform = overlay_transform; |
| candidate.display_rect = |
| sqs->quad_to_target_transform.MapRect(candidate.display_rect); |
| } |
| candidate.is_opaque = |
| !quad->ShouldDrawWithBlendingForReasonOtherThanMaskFilter(); |
| candidate.has_mask_filter = |
| !quad->shared_quad_state->mask_filter_info.IsEmpty(); |
| |
| AssignDamage(quad, candidate); |
| candidate.tracking_id = base::FastHash(quad->overlay_plane_id.AsBytes()); |
| |
| return CandidateStatus::kSuccess; |
| } |
| |
| OverlayCandidate::CandidateStatus OverlayCandidateFactory::FromTileQuad( |
| const TileDrawQuad* quad, |
| OverlayCandidate& candidate) const { |
| if (quad->nearest_neighbor) |
| return CandidateStatus::kFailNearFilter; |
| |
| candidate.resource_size_in_pixels = |
| resource_provider_->GetResourceBackedSize(quad->resource_id()); |
| candidate.uv_rect = gfx::ScaleRect( |
| quad->tex_coord_rect, 1.f / candidate.resource_size_in_pixels.width(), |
| 1.f / candidate.resource_size_in_pixels.height()); |
| |
| auto rtn = FromDrawQuadResource(quad, quad->resource_id(), false, candidate); |
| return rtn; |
| } |
| |
| OverlayCandidate::CandidateStatus OverlayCandidateFactory::FromTextureQuad( |
| const TextureDrawQuad* quad, |
| OverlayCandidate& candidate) const { |
| if (!is_delegated_context_ && |
| quad->overlay_priority_hint == OverlayPriority::kLow) { |
| // For current implementation low priority means this does not promote to |
| // overlay. |
| return CandidateStatus::kFailPriority; |
| } |
| |
| if (!quad->rounded_display_masks_info.IsEmpty() && |
| !supports_rounded_display_masks_) { |
| DCHECK(!is_delegated_context_); |
| return CandidateStatus::kFailRoundedDisplayMasksNotSupported; |
| } |
| |
| if (quad->nearest_neighbor) |
| return CandidateStatus::kFailNearFilter; |
| |
| if (is_delegated_context_) { |
| // Always convey |background_color| even when transparent. This allows for |
| // the wayland server to make blending optimizations even when the quad is |
| // considered opaque. Specifically Exo will try to ensure the opaqueness of |
| // alpha formats by adding a black background which can cause difficulty in |
| // overlay promotion (see the code in the lines below). |
| candidate.color = quad->background_color; |
| } else if (quad->background_color != SkColors::kTransparent && |
| (quad->background_color != SkColors::kBlack || |
| quad->ShouldDrawWithBlending())) { |
| // The condition above is very specific to the implementation of DRM/KMS |
| // scanout. An opaque plane with buffer that has buffer element component |
| // alpha will default black for the blend. Basically we can simulate a black |
| // background using the default color when blending an opaque overlay. This |
| // trick, of course, only works for black. |
| return CandidateStatus::kFailBlending; |
| } |
| |
| candidate.uv_rect = BoundingRect(quad->uv_top_left, quad->uv_bottom_right); |
| |
| auto rtn = FromDrawQuadResource(quad, quad->resource_id(), quad->y_flipped, |
| candidate); |
| if (rtn == CandidateStatus::kSuccess) { |
| // Only handle clip rect for required overlays |
| if (!is_delegated_context_ && candidate.requires_overlay) |
| HandleClipAndSubsampling(candidate); |
| |
| // Texture quads for UI elements like scroll bars have empty |
| // |size_in_pixels| as 'set_resource_size_in_pixels' is not called as these |
| // quads are not intended to become overlays. |
| if (!quad->resource_size_in_pixels().IsEmpty()) { |
| if (candidate.requires_overlay) { |
| candidate.priority_hint = gfx::OverlayPriorityHint::kHardwareProtection; |
| } else if (quad->is_video_frame) { |
| candidate.priority_hint = gfx::OverlayPriorityHint::kVideo; |
| } else { |
| candidate.priority_hint = gfx::OverlayPriorityHint::kRegular; |
| } |
| } |
| |
| #if BUILDFLAG(IS_ANDROID) |
| if (quad->is_stream_video) { |
| // StreamVideoDrawQuad used to set the resource_size_in_pixels directly |
| // from the quad rather than from the resource. |
| candidate.resource_size_in_pixels = quad->resource_size_in_pixels(); |
| candidate.is_backed_by_surface_texture = |
| resource_provider_->IsBackedBySurfaceTexture(quad->resource_id()); |
| } |
| #endif |
| |
| // SkiaRenderer requires overlays to be backed by SharedImages. |
| if (!candidate.mailbox.IsSharedImage()) |
| return CandidateStatus::kFailNotSharedImage; |
| |
| candidate.has_rounded_display_masks = |
| !quad->rounded_display_masks_info.IsEmpty(); |
| } |
| return rtn; |
| } |
| |
| void OverlayCandidateFactory::HandleClipAndSubsampling( |
| OverlayCandidate& candidate) const { |
| // The purpose of this is to enable overlays that are required (i.e. protected |
| // content) to be able to be shown in all cases. This will allow them to pass |
| // the clipping check and also the 2x alignment requirement for subsampling in |
| // the Intel DRM driver. This should not be used in cases where the surface |
| // will not always be promoted to an overlay as it will lead to shifting of |
| // the content when it switches between composition and overlay. |
| if (!candidate.clip_rect) |
| return; |
| |
| // Make sure it's in a format we can deal with, we only support YUV and P010. |
| if (candidate.format != gfx::BufferFormat::YUV_420_BIPLANAR && |
| candidate.format != gfx::BufferFormat::P010) { |
| return; |
| } |
| // Clip the clip rect to the primary plane. An overlay will only be shown on |
| // a single display, so we want to perform our calculations within the bounds |
| // of that display. |
| if (!primary_rect_.IsEmpty()) |
| candidate.clip_rect->Intersect(gfx::ToNearestRect(primary_rect_)); |
| |
| // Baking |clip_rect| into the |uv_rect| and |display_rect| doesn't make sense |
| // when there is an arbitrary transform between the two because the transform |
| // may not preserve axis alignment. |
| DCHECK(absl::holds_alternative<gfx::OverlayTransform>(candidate.transform)); |
| |
| // Calculate |uv_rect| of |clip_rect| in |display_rect| |
| // TODO(rivr): Handle candidates with an overlay transform applied. |
| gfx::RectF uv_rect = cc::MathUtil::ScaleRectProportional( |
| candidate.uv_rect, candidate.display_rect, |
| gfx::RectF(*candidate.clip_rect)); |
| |
| // In case that |uv_rect| of candidate is not (0, 0, 1, 1) |
| candidate.uv_rect.Intersect(uv_rect); |
| |
| // Update |display_rect| to avoid unexpected scaling and the candidate should |
| // not be regarded as clippped after this. |
| candidate.display_rect.Intersect(gfx::RectF(*candidate.clip_rect)); |
| candidate.clip_rect.reset(); |
| gfx::Rect rounded_display_rect = gfx::ToRoundedRect(candidate.display_rect); |
| candidate.display_rect.SetRect( |
| rounded_display_rect.x(), rounded_display_rect.y(), |
| rounded_display_rect.width(), rounded_display_rect.height()); |
| |
| // Now correct |uv_rect| if required so that the source rect aligns on a pixel |
| // boundary that is a multiple of the chroma subsampling. |
| |
| // Get the rect for the source coordinates. |
| gfx::RectF src_rect = gfx::ScaleRect( |
| candidate.uv_rect, candidate.resource_size_in_pixels.width(), |
| candidate.resource_size_in_pixels.height()); |
| // Make it an integral multiple of the subsampling factor. |
| auto subsample_round = [](float val) { |
| constexpr int kSubsamplingFactor = 2; |
| return (std::lround(val) / kSubsamplingFactor) * kSubsamplingFactor; |
| }; |
| |
| src_rect.set_x(subsample_round(src_rect.x())); |
| src_rect.set_y(subsample_round(src_rect.y())); |
| src_rect.set_width(subsample_round(src_rect.width())); |
| src_rect.set_height(subsample_round(src_rect.height())); |
| // Scale it back into UV space and set it in the candidate. |
| candidate.uv_rect = |
| gfx::ScaleRect(src_rect, 1.0f / candidate.resource_size_in_pixels.width(), |
| 1.0f / candidate.resource_size_in_pixels.height()); |
| } |
| |
| void OverlayCandidateFactory::AssignDamage(const DrawQuad* quad, |
| OverlayCandidate& candidate) const { |
| candidate.damage_rect = GetDamageRect(quad, candidate); |
| // For underlays the function 'EstimateVisibleDamage()' is called to update |
| // |damage_area_estimate| to more accurately reflect the actual visible |
| // damage. |
| if (!is_delegated_context_) { |
| candidate.damage_area_estimate = |
| GetDamageEstimate(candidate).size().GetArea(); |
| } |
| } |
| |
| gfx::RectF OverlayCandidateFactory::GetDamageEstimate( |
| const OverlayCandidate& candidate) const { |
| // If we have assigned damage we can trust that. |
| if (!candidate.damage_rect.IsEmpty()) { |
| return candidate.damage_rect; |
| } |
| |
| // Otherwise we will see how much unassigned damage covers the display_rect. |
| return gfx::IntersectRects( |
| OverlayCandidate::DisplayRectInTargetSpace(candidate), |
| gfx::RectF(unassigned_surface_damage_)); |
| } |
| |
| gfx::RectF OverlayCandidateFactory::GetDamageRect( |
| const DrawQuad* quad, |
| const OverlayCandidate& candidate) const { |
| const SharedQuadState* sqs = quad->shared_quad_state; |
| if (!sqs->overlay_damage_index.has_value()) { |
| return gfx::RectF(); |
| } |
| |
| size_t overlay_damage_index = sqs->overlay_damage_index.value(); |
| // Invalid index. |
| if (overlay_damage_index >= surface_damage_rect_list_->size()) { |
| DCHECK(false); |
| return gfx::RectF(); |
| } |
| |
| // Assigned damage assumes that |candidate.display_rect| is already in target |
| // space, but that isn't true for transformation matrices. |
| if (absl::holds_alternative<gfx::Transform>(candidate.transform)) { |
| return gfx::RectF(); |
| } |
| |
| // Ash can't overlay candidates that aren't pixel-aligned so don't bother |
| // assigning damage to them. This would also be a challenge because |
| // |OverlayCandidate.damage_rect| is only a gfx::Rect. |
| if (!candidate.display_rect.IsExpressibleAsRect()) { |
| return gfx::RectF(); |
| } |
| |
| auto damage = gfx::RectF((*surface_damage_rect_list_)[overlay_damage_index]); |
| DBG_DRAW_RECT("damage_assigned", damage); |
| return damage; |
| } |
| |
| } // namespace viz |