| // Copyright 2017 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/dc_layer_overlay.h" |
| |
| #include <limits> |
| #include <utility> |
| |
| #include "base/metrics/histogram_functions.h" |
| #include "base/metrics/histogram_macros.h" |
| #include "build/build_config.h" |
| #include "cc/base/math_util.h" |
| #include "components/viz/common/overlay_state/win/overlay_state_service.h" |
| #include "components/viz/common/quads/aggregated_render_pass_draw_quad.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/yuv_video_draw_quad.h" |
| #include "components/viz/common/viz_utils.h" |
| #include "components/viz/service/display/display_resource_provider.h" |
| #include "components/viz/service/display/overlay_processor_interface.h" |
| #include "gpu/GLES2/gl2extchromium.h" |
| #include "gpu/config/gpu_finch_features.h" |
| #include "media/base/media_switches.h" |
| #include "media/base/win/mf_feature_checks.h" |
| #include "ui/gfx/geometry/insets.h" |
| #include "ui/gfx/geometry/rect.h" |
| #include "ui/gfx/geometry/rect_conversions.h" |
| #include "ui/gfx/video_types.h" |
| #include "ui/gl/gl_switches.h" |
| |
| namespace viz { |
| |
| namespace { |
| |
| // This is the number of frames we should wait before actual overlay promotion |
| // under multi-video cases. |
| constexpr int kDCLayerFramesDelayedBeforeOverlay = 5; |
| |
| // This is used for a histogram to determine why overlays are or aren't used, |
| // so don't remove entries and make sure to update enums.xml if it changes. |
| enum DCLayerResult { |
| DC_LAYER_SUCCESS = 0, |
| DC_LAYER_FAILED_UNSUPPORTED_QUAD = 1, // not recorded |
| DC_LAYER_FAILED_QUAD_BLEND_MODE = 2, |
| DC_LAYER_FAILED_TEXTURE_NOT_CANDIDATE = 3, |
| DC_LAYER_FAILED_OCCLUDED [[deprecated]] = 4, |
| DC_LAYER_FAILED_COMPLEX_TRANSFORM = 5, |
| DC_LAYER_FAILED_TRANSPARENT = 6, |
| DC_LAYER_FAILED_NON_ROOT [[deprecated]] = 7, |
| DC_LAYER_FAILED_TOO_MANY_OVERLAYS = 8, |
| DC_LAYER_FAILED_NO_HW_OVERLAY_SUPPORT [[deprecated]] = 9, |
| DC_LAYER_FAILED_ROUNDED_CORNERS [[deprecated]] = 10, |
| DC_LAYER_FAILED_BACKDROP_FILTERS = 11, |
| DC_LAYER_FAILED_COPY_REQUESTS = 12, |
| DC_LAYER_FAILED_VIDEO_CAPTURE_ENABLED = 13, |
| DC_LAYER_FAILED_OUTPUT_HDR = 14, |
| DC_LAYER_FAILED_NOT_DAMAGED = 15, |
| DC_LAYER_FAILED_YUV_VIDEO_QUAD_MOVED = 16, |
| DC_LAYER_FAILED_HDR_TONE_MAPPING = 17, |
| DC_LAYER_FAILED_YUV_VIDEO_QUAD_NO_HDR_METADATA = 18, |
| DC_LAYER_FAILED_YUV_VIDEO_QUAD_HLG = 19, |
| kMaxValue = DC_LAYER_FAILED_YUV_VIDEO_QUAD_HLG, |
| }; |
| |
| DCLayerResult ValidateYUVOverlay( |
| const gfx::ProtectedVideoType& protected_video_type, |
| const gfx::ColorSpace& video_color_space, |
| const absl::optional<gfx::HDRMetadata>& hdr_metadata, |
| bool has_overlay_support, |
| int allowed_yuv_overlay_count, |
| int processed_yuv_overlay_count) { |
| // Note: Do not override this value based on base::Feature values. It is the |
| // result after the GPU blocklist has been consulted. |
| if (!has_overlay_support) { |
| return DC_LAYER_FAILED_UNSUPPORTED_QUAD; |
| } |
| |
| // Hardware protected video must use Direct Composition Overlay |
| if (protected_video_type == gfx::ProtectedVideoType::kHardwareProtected) { |
| return DC_LAYER_SUCCESS; |
| } |
| |
| if (processed_yuv_overlay_count >= allowed_yuv_overlay_count) { |
| return DC_LAYER_FAILED_TOO_MANY_OVERLAYS; |
| } |
| |
| // HLG shouldn't have the hdr metadata, but we don't want to promote it to |
| // overlay, as VideoProcessor doesn't support HLG tone mapping well between |
| // different gpu vendors, see: https://crbug.com/1144260#c6. |
| // Some HLG streams may carry hdr metadata, see: https://crbug.com/1429172. |
| if (video_color_space.GetTransferID() == gfx::ColorSpace::TransferID::HLG) { |
| return DC_LAYER_FAILED_YUV_VIDEO_QUAD_HLG; |
| } |
| |
| // Otherwise, it could be a parser bug like https://crbug.com/1362288 if the |
| // hdr metadata is still missing. We shouldn't promote too for that case. |
| if (video_color_space.IsHDR() && !hdr_metadata.has_value()) { |
| return DC_LAYER_FAILED_YUV_VIDEO_QUAD_NO_HDR_METADATA; |
| } |
| |
| return DC_LAYER_SUCCESS; |
| } |
| |
| DCLayerResult ValidateYUVQuad( |
| const YUVVideoDrawQuad* quad, |
| const std::vector<gfx::Rect>& backdrop_filter_rects, |
| bool has_overlay_support, |
| int allowed_yuv_overlay_count, |
| int processed_yuv_overlay_count, |
| DisplayResourceProvider* resource_provider) { |
| // Note: Do not override this value based on base::Feature values. It is the |
| // result after the GPU blocklist has been consulted. |
| if (!has_overlay_support) |
| return DC_LAYER_FAILED_UNSUPPORTED_QUAD; |
| |
| // Check that resources are overlay compatible first so that subsequent |
| // assumptions are valid. |
| for (const auto& resource : quad->resources) { |
| if (!resource_provider->IsOverlayCandidate(resource)) |
| return DC_LAYER_FAILED_TEXTURE_NOT_CANDIDATE; |
| } |
| |
| // Hardware protected video must use Direct Composition Overlay |
| if (quad->protected_video_type == gfx::ProtectedVideoType::kHardwareProtected) |
| return DC_LAYER_SUCCESS; |
| |
| if (quad->shared_quad_state->blend_mode != SkBlendMode::kSrcOver) |
| return DC_LAYER_FAILED_QUAD_BLEND_MODE; |
| |
| if (!quad->shared_quad_state->quad_to_target_transform |
| .Preserves2dAxisAlignment()) { |
| return DC_LAYER_FAILED_COMPLEX_TRANSFORM; |
| } |
| |
| if (processed_yuv_overlay_count >= allowed_yuv_overlay_count) |
| return DC_LAYER_FAILED_TOO_MANY_OVERLAYS; |
| |
| auto quad_target_rect = ClippedQuadRectangle(quad); |
| for (const auto& filter_target_rect : backdrop_filter_rects) { |
| if (filter_target_rect.Intersects(quad_target_rect)) |
| return DC_LAYER_FAILED_BACKDROP_FILTERS; |
| } |
| |
| // HLG shouldn't have the hdr metadata, but we don't want to promote it to |
| // overlay, as VideoProcessor doesn't support HLG tone mapping well between |
| // different gpu vendors, see: https://crbug.com/1144260#c6. |
| // Some HLG streams may carry hdr metadata, see: https://crbug.com/1429172. |
| if (quad->video_color_space.GetTransferID() == |
| gfx::ColorSpace::TransferID::HLG) { |
| return DC_LAYER_FAILED_YUV_VIDEO_QUAD_HLG; |
| } |
| |
| // Otherwise, it could be a parser bug like https://crbug.com/1362288 if the |
| // hdr metadata is still missing. We shouldn't promote too for that case. |
| if (quad->video_color_space.IsHDR() && !quad->hdr_metadata.has_value()) { |
| return DC_LAYER_FAILED_YUV_VIDEO_QUAD_NO_HDR_METADATA; |
| } |
| |
| return DC_LAYER_SUCCESS; |
| } |
| |
| void FromYUVQuad(const YUVVideoDrawQuad* quad, |
| const gfx::Transform& transform_to_root_target, |
| OverlayCandidate* dc_layer) { |
| // Direct composition path only supports a single NV12 buffer. |
| DCHECK(quad->y_plane_resource_id() && quad->u_plane_resource_id()); |
| DCHECK_EQ(quad->u_plane_resource_id(), quad->v_plane_resource_id()); |
| dc_layer->resource_id = quad->y_plane_resource_id(); |
| |
| dc_layer->plane_z_order = 1; |
| dc_layer->display_rect = gfx::RectF(quad->rect); |
| dc_layer->resource_size_in_pixels = quad->ya_tex_size(); |
| dc_layer->uv_rect = |
| gfx::ScaleRect(quad->ya_tex_coord_rect(), |
| 1.f / dc_layer->resource_size_in_pixels.width(), |
| 1.f / dc_layer->resource_size_in_pixels.height()); |
| |
| // Quad rect is in quad content space so both quad to target, and target to |
| // root transforms must be applied to it. |
| gfx::Transform quad_to_root_transform( |
| quad->shared_quad_state->quad_to_target_transform); |
| quad_to_root_transform.PostConcat(transform_to_root_target); |
| // Flatten transform to 2D since DirectComposition doesn't support 3D |
| // transforms. This only applies when non axis aligned overlays are enabled. |
| quad_to_root_transform.Flatten(); |
| dc_layer->transform = quad_to_root_transform; |
| |
| if (quad->shared_quad_state->clip_rect) { |
| // Clip rect is in quad target space, and must be transformed to root target |
| // space. |
| dc_layer->clip_rect = |
| transform_to_root_target.MapRect(*quad->shared_quad_state->clip_rect); |
| } |
| dc_layer->color_space = quad->video_color_space; |
| dc_layer->protected_video_type = quad->protected_video_type; |
| dc_layer->hdr_metadata = quad->hdr_metadata.value_or(gfx::HDRMetadata()); |
| } |
| |
| DCLayerResult ValidateTextureQuad( |
| const TextureDrawQuad* quad, |
| const std::vector<gfx::Rect>& backdrop_filter_rects, |
| bool has_overlay_support, |
| int allowed_yuv_overlay_count, |
| int processed_yuv_overlay_count, |
| DisplayResourceProvider* resource_provider) { |
| // Check that resources are overlay compatible first so that subsequent |
| // assumptions are valid. |
| for (const auto& resource : quad->resources) { |
| if (!resource_provider->IsOverlayCandidate(resource)) |
| return DC_LAYER_FAILED_TEXTURE_NOT_CANDIDATE; |
| } |
| |
| if (quad->shared_quad_state->blend_mode != SkBlendMode::kSrcOver) |
| return DC_LAYER_FAILED_QUAD_BLEND_MODE; |
| |
| if (!quad->shared_quad_state->quad_to_target_transform |
| .Preserves2dAxisAlignment()) { |
| return DC_LAYER_FAILED_COMPLEX_TRANSFORM; |
| } |
| |
| auto quad_target_rect = ClippedQuadRectangle(quad); |
| for (const auto& filter_target_rect : backdrop_filter_rects) { |
| if (filter_target_rect.Intersects(quad_target_rect)) |
| return DC_LAYER_FAILED_BACKDROP_FILTERS; |
| } |
| |
| auto si_format = resource_provider->GetSharedImageFormat(quad->resource_id()); |
| if (media::IsMultiPlaneFormatForHardwareVideoEnabled() && |
| si_format.is_multi_plane()) { |
| auto color_space = |
| resource_provider->GetOverlayColorSpace(quad->resource_id()); |
| auto result = ValidateYUVOverlay(quad->protected_video_type, color_space, |
| quad->hdr_metadata, has_overlay_support, |
| allowed_yuv_overlay_count, |
| processed_yuv_overlay_count); |
| return result; |
| } |
| |
| return DC_LAYER_SUCCESS; |
| } |
| |
| void FromTextureQuad(const TextureDrawQuad* quad, |
| const gfx::Transform& transform_to_root_target, |
| DisplayResourceProvider* resource_provider, |
| OverlayCandidate* dc_layer) { |
| dc_layer->resource_id = quad->resource_id(); |
| dc_layer->plane_z_order = 1; |
| dc_layer->resource_size_in_pixels = quad->resource_size_in_pixels(); |
| dc_layer->uv_rect = |
| gfx::BoundingRect(quad->uv_top_left, quad->uv_bottom_right); |
| dc_layer->display_rect = gfx::RectF(quad->rect); |
| // Quad rect is in quad content space so both quad to target, and target to |
| // root transforms must be applied to it. |
| gfx::Transform quad_to_root_transform; |
| if (quad->y_flipped) { |
| quad_to_root_transform.Scale(1.0, -1.0); |
| quad_to_root_transform.PostTranslate( |
| 0.0, dc_layer->resource_size_in_pixels.height()); |
| } |
| quad_to_root_transform.PostConcat( |
| quad->shared_quad_state->quad_to_target_transform); |
| quad_to_root_transform.PostConcat(transform_to_root_target); |
| // Flatten transform to 2D since DirectComposition doesn't support 3D |
| // transforms. This only applies when non axis aligned overlays are enabled. |
| quad_to_root_transform.Flatten(); |
| dc_layer->transform = quad_to_root_transform; |
| |
| if (quad->shared_quad_state->clip_rect) { |
| // Clip rect is in quad target space, and must be transformed to root target |
| // space. |
| dc_layer->clip_rect = transform_to_root_target.MapRect( |
| quad->shared_quad_state->clip_rect.value_or(gfx::Rect())); |
| } |
| |
| dc_layer->color_space = |
| resource_provider->GetOverlayColorSpace(quad->resource_id()); |
| // Both color space and protected_video_type are hard-coded for stream video. |
| // TODO(crbug.com/1384544): Consider using quad->protected_video_type. |
| if (quad->is_stream_video) { |
| dc_layer->color_space = gfx::ColorSpace(gfx::ColorSpace::PrimaryID::BT709, |
| gfx::ColorSpace::TransferID::BT709); |
| dc_layer->protected_video_type = |
| gfx::ProtectedVideoType::kHardwareProtected; |
| } |
| } |
| |
| bool IsProtectedVideo(const QuadList::ConstIterator& it) { |
| if (it->material == DrawQuad::Material::kYuvVideoContent) { |
| const auto* yuv_quad = YUVVideoDrawQuad::MaterialCast(*it); |
| return yuv_quad->protected_video_type == |
| gfx::ProtectedVideoType::kHardwareProtected || |
| yuv_quad->protected_video_type == |
| gfx::ProtectedVideoType::kSoftwareProtected; |
| } else if (it->material == DrawQuad::Material::kTextureContent && |
| TextureDrawQuad::MaterialCast(*it)->is_stream_video) { |
| return true; |
| } else { |
| return false; |
| } |
| } |
| |
| DCLayerResult IsUnderlayAllowed(const QuadList::Iterator& it) { |
| if (it->ShouldDrawWithBlending() && |
| !it->shared_quad_state->mask_filter_info.HasRoundedCorners()) { |
| return DC_LAYER_FAILED_TRANSPARENT; |
| } |
| |
| return DC_LAYER_SUCCESS; |
| } |
| |
| // Any occluding quads in the quad list on top of the overlay/underlay |
| bool IsOccluded( |
| const gfx::RectF& target_quad, |
| QuadList::ConstIterator quad_list_begin, |
| QuadList::ConstIterator quad_list_end, |
| const DCLayerOverlayProcessor::FilterOperationsMap& render_pass_filters) { |
| // If the current quad |quad_list_end| has rounded corners, force it |
| // to underlay mode. |
| if (quad_list_end->shared_quad_state->mask_filter_info.HasRoundedCorners()) { |
| return true; |
| } |
| |
| for (auto overlap_iter = quad_list_begin; overlap_iter != quad_list_end; |
| ++overlap_iter) { |
| float opacity = overlap_iter->shared_quad_state->opacity; |
| if (opacity < std::numeric_limits<float>::epsilon()) |
| continue; |
| |
| const DrawQuad* quad = *overlap_iter; |
| gfx::RectF overlap_rect; |
| // Expand the overlap_rect for the render pass draw quad with pixel moving |
| // foreground filters. |
| bool has_pixel_moving_filter = false; |
| if (!render_pass_filters.empty() && |
| quad->material == DrawQuad::Material::kAggregatedRenderPass) { |
| const auto* rpdq = AggregatedRenderPassDrawQuad::MaterialCast(quad); |
| auto render_pass_it = render_pass_filters.find(rpdq->render_pass_id); |
| if (render_pass_it != render_pass_filters.end()) { |
| auto* filters = render_pass_it->second; |
| overlap_rect = gfx::RectF( |
| GetExpandedRectWithPixelMovingForegroundFilter(*rpdq, *filters)); |
| has_pixel_moving_filter = true; |
| } |
| } |
| |
| if (!has_pixel_moving_filter) |
| overlap_rect = ClippedQuadRectangleF(quad); |
| |
| if (quad->material == DrawQuad::Material::kSolidColor) { |
| SkColor4f color = SolidColorDrawQuad::MaterialCast(quad)->color; |
| float alpha = color.fA * opacity; |
| if (quad->ShouldDrawWithBlending() && |
| alpha < std::numeric_limits<float>::epsilon()) |
| continue; |
| } |
| if (overlap_rect.Intersects(target_quad)) |
| return true; |
| } |
| return false; |
| } |
| |
| bool HasOccludingDamageRect( |
| const SharedQuadState* shared_quad_state, |
| const SurfaceDamageRectList& surface_damage_rect_list, |
| const gfx::Rect& quad_rect_in_root_space) { |
| if (!shared_quad_state->overlay_damage_index.has_value()) |
| return !quad_rect_in_root_space.IsEmpty(); |
| |
| size_t overlay_damage_index = shared_quad_state->overlay_damage_index.value(); |
| if (overlay_damage_index >= surface_damage_rect_list.size()) { |
| DCHECK(false); |
| } |
| |
| // Damage rects in surface_damage_rect_list are arranged from top to bottom. |
| // surface_damage_rect_list[0] is the one on the very top. |
| // surface_damage_rect_list[overlay_damage_index] is the damage rect of |
| // this overlay surface. |
| gfx::Rect occluding_damage_rect; |
| for (size_t i = 0; i < overlay_damage_index; ++i) { |
| occluding_damage_rect.Union(surface_damage_rect_list[i]); |
| } |
| occluding_damage_rect.Intersect(quad_rect_in_root_space); |
| |
| return !occluding_damage_rect.IsEmpty(); |
| } |
| |
| bool IsPossibleFullScreenLetterboxing(const QuadList::Iterator& it, |
| QuadList::ConstIterator quad_list_end, |
| const gfx::RectF& display_rect) { |
| // Two cases are considered as possible fullscreen letterboxing: |
| // 1. If the quad beneath the overlay quad is DrawQuad::Material::kSolidColor |
| // with black, and it touches two sides of the screen, while starting at |
| // display origin (0, 0). |
| // 2. If the quad beneath the overlay quad is |
| // DrawQuad::Material::kTiledContent, and it touches two sides of the screen, |
| // while starting at display origin (0, 0). |
| // For YouTube with F11 page fullscreen mode, the kTiledContent beneath the |
| // overlay does not touch the right edge due to the existing of a scrolling |
| // bar. |
| auto beneath_overlay_it = it; |
| beneath_overlay_it++; |
| |
| if (beneath_overlay_it != quad_list_end) { |
| if (beneath_overlay_it->material == DrawQuad::Material::kTiledContent || |
| (beneath_overlay_it->material == DrawQuad::Material::kSolidColor && |
| SolidColorDrawQuad::MaterialCast(*beneath_overlay_it)->color == |
| SkColors::kBlack)) { |
| gfx::RectF beneath_rect = ClippedQuadRectangleF(*beneath_overlay_it); |
| return (beneath_rect.origin() == display_rect.origin() && |
| (beneath_rect.width() == display_rect.width() || |
| beneath_rect.height() == display_rect.height())); |
| } |
| } |
| |
| return false; |
| } |
| |
| void RecordVideoDCLayerResult(DCLayerResult result, |
| gfx::ProtectedVideoType protected_video_type) { |
| switch (protected_video_type) { |
| case gfx::ProtectedVideoType::kClear: |
| UMA_HISTOGRAM_ENUMERATION( |
| "GPU.DirectComposition.DCLayerResult.Video.Clear", result); |
| break; |
| case gfx::ProtectedVideoType::kSoftwareProtected: |
| UMA_HISTOGRAM_ENUMERATION( |
| "GPU.DirectComposition.DCLayerResult.Video.SoftwareProtected", |
| result); |
| break; |
| case gfx::ProtectedVideoType::kHardwareProtected: |
| UMA_HISTOGRAM_ENUMERATION( |
| "GPU.DirectComposition.DCLayerResult.Video.HardwareProtected", |
| result); |
| break; |
| } |
| } |
| |
| void RecordDCLayerResult(DCLayerResult result, QuadList::ConstIterator it) { |
| // Skip recording unsupported quads since that'd dwarf the data we care about. |
| if (result == DC_LAYER_FAILED_UNSUPPORTED_QUAD) |
| return; |
| |
| switch (it->material) { |
| case DrawQuad::Material::kYuvVideoContent: |
| RecordVideoDCLayerResult( |
| result, YUVVideoDrawQuad::MaterialCast(*it)->protected_video_type); |
| break; |
| case DrawQuad::Material::kTextureContent: { |
| if (TextureDrawQuad::MaterialCast(*it)->is_stream_video) { |
| UMA_HISTOGRAM_ENUMERATION( |
| "GPU.DirectComposition.DCLayerResult.StreamVideo", result); |
| } else { |
| UMA_HISTOGRAM_ENUMERATION("GPU.DirectComposition.DCLayerResult.Texture", |
| result); |
| } |
| break; |
| } |
| default: |
| break; |
| } |
| } |
| |
| // This function records the damage rect rect of the current frame. |
| void RecordOverlayHistograms(OverlayCandidateList* dc_layer_overlays, |
| bool has_occluding_surface_damage, |
| const gfx::Rect* damage_rect) { |
| // If an underlay is found, we record the damage rect of this frame as an |
| // underlay. |
| bool is_overlay = true; |
| for (const auto& dc_layer : *dc_layer_overlays) { |
| if (dc_layer.plane_z_order != 1) { |
| is_overlay = false; |
| break; |
| } |
| } |
| |
| OverlayProcessorInterface::RecordOverlayDamageRectHistograms( |
| is_overlay, has_occluding_surface_damage, damage_rect->IsEmpty()); |
| } |
| |
| QuadList::Iterator FindAnOverlayCandidate(QuadList& quad_list) { |
| for (auto it = quad_list.begin(); it != quad_list.end(); ++it) { |
| if (it->material == DrawQuad::Material::kYuvVideoContent || |
| it->material == DrawQuad::Material::kTextureContent) |
| return it; |
| } |
| return quad_list.end(); |
| } |
| |
| QuadList::Iterator FindAnOverlayCandidateExcludingMediaFoundationVideoContent( |
| QuadList& quad_list) { |
| QuadList::Iterator it = quad_list.end(); |
| for (auto quad_it = quad_list.begin(); quad_it != quad_list.end(); |
| ++quad_it) { |
| if (quad_it->material == DrawQuad::Material::kTextureContent && |
| TextureDrawQuad::MaterialCast(*quad_it)->is_stream_video) { |
| return quad_list.end(); |
| } |
| if (it == quad_list.end() && |
| (quad_it->material == DrawQuad::Material::kYuvVideoContent || |
| quad_it->material == DrawQuad::Material::kTextureContent)) |
| it = quad_it; |
| } |
| return it; |
| } |
| |
| bool IsClearVideoQuad(const QuadList::ConstIterator& it) { |
| return it->material == DrawQuad::Material::kYuvVideoContent && |
| !IsProtectedVideo(it); |
| } |
| |
| } // namespace |
| |
| DCLayerOverlayProcessor::DCLayerOverlayProcessor( |
| int allowed_yuv_overlay_count, |
| bool skip_initialization_for_testing) |
| : has_overlay_support_(skip_initialization_for_testing), |
| allowed_yuv_overlay_count_(allowed_yuv_overlay_count), |
| no_undamaged_overlay_promotion_(base::FeatureList::IsEnabled( |
| features::kNoUndamagedOverlayPromotion)) { |
| if (!skip_initialization_for_testing) { |
| UpdateHasHwOverlaySupport(); |
| UpdateSystemHDRStatus(); |
| gl::DirectCompositionOverlayCapsMonitor::GetInstance()->AddObserver(this); |
| } |
| allow_promotion_hinting_ = media::SupportMediaFoundationClearPlayback(); |
| } |
| |
| DCLayerOverlayProcessor::~DCLayerOverlayProcessor() { |
| gl::DirectCompositionOverlayCapsMonitor::GetInstance()->RemoveObserver(this); |
| } |
| |
| void DCLayerOverlayProcessor::UpdateHasHwOverlaySupport() { |
| has_overlay_support_ = gl::DirectCompositionOverlaysSupported(); |
| } |
| |
| void DCLayerOverlayProcessor::UpdateSystemHDRStatus() { |
| bool hdr_enabled = false; |
| auto dxgi_info = gl::GetDirectCompositionHDRMonitorDXGIInfo(); |
| for (const auto& output_desc : dxgi_info->output_descs) |
| hdr_enabled |= output_desc->hdr_enabled; |
| system_hdr_enabled_ = hdr_enabled; |
| } |
| |
| // Called on the Viz Compositor thread. |
| void DCLayerOverlayProcessor::OnOverlayCapsChanged() { |
| DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); |
| UpdateHasHwOverlaySupport(); |
| UpdateSystemHDRStatus(); |
| } |
| |
| void DCLayerOverlayProcessor::ClearOverlayState() { |
| previous_frame_overlay_rects_.clear(); |
| previous_frame_underlay_is_opaque_ = true; |
| } |
| |
| gfx::Rect DCLayerOverlayProcessor::PreviousFrameOverlayDamageContribution() { |
| gfx::Rect rects_union; |
| for (const auto& overlay : previous_frame_overlay_rects_) |
| rects_union.Union(overlay.rect); |
| return rects_union; |
| } |
| |
| void DCLayerOverlayProcessor::RemoveOverlayDamageRect( |
| const QuadList::Iterator& it) { |
| // This is done by setting the overlay surface damage rect in the |
| // |surface_damage_rect_list_| to zero. |
| if (it->shared_quad_state->overlay_damage_index.has_value()) { |
| size_t overlay_damage_index = |
| it->shared_quad_state->overlay_damage_index.value(); |
| if (overlay_damage_index >= surface_damage_rect_list_.size()) |
| DCHECK(false); |
| else |
| damages_to_be_removed_.push_back(overlay_damage_index); |
| } |
| } |
| |
| // This is called at the end of Process(). The goal is to get an empty root |
| // damage rect if the overlays are the only damages in the frame. |
| void DCLayerOverlayProcessor::UpdateRootDamageRect( |
| const gfx::RectF& display_rect, |
| gfx::Rect* damage_rect) { |
| // Check whether the overlay rect union from the previous frame should be |
| // added to the current frame and whether the overlay damages can be removed |
| // from the current damage rect. |
| |
| bool should_add_previous_frame_overlay_damage = true; |
| size_t current_frame_overlay_count = current_frame_overlay_rects_.size(); |
| if (current_frame_overlay_count > 0 && |
| current_frame_overlay_count == previous_frame_overlay_rects_.size() && |
| display_rect == previous_display_rect_) { |
| bool same_overlays = true; |
| for (size_t i = 0; i < current_frame_overlay_count; ++i) { |
| if (previous_frame_overlay_rects_[i] != current_frame_overlay_rects_[i]) { |
| same_overlays = false; |
| break; |
| } |
| } |
| |
| if (same_overlays) { |
| // No need to add back the overlay rect union from the previous frame |
| // if no changes in overlays. |
| should_add_previous_frame_overlay_damage = false; |
| |
| // The final root damage rect is computed by add up all surface damages |
| // except for the overlay surface damages and the damages right below |
| // the overlays. |
| gfx::Rect root_damage_rect; |
| size_t surface_index = 0; |
| for (auto surface_damage_rect : surface_damage_rect_list_) { |
| // We only support at most two overlays. The size of |
| // damages_to_be_removed_ will not be bigger than 2. We should |
| // revisit this damages_to_be_removed_ for-loop if we try to support |
| // many overlays. |
| // See capabilities.supports_two_yuv_hardware_overlays. |
| for (const auto index_to_be_removed : damages_to_be_removed_) { |
| // The overlay damages and the damages right below them will not be |
| // added to the root damage rect. |
| if (surface_index == index_to_be_removed) { |
| // This is the overlay surface. |
| surface_damage_rect = gfx::Rect(); |
| break; |
| } else if (surface_index > index_to_be_removed) { |
| // This is the surface below the overlays. |
| surface_damage_rect.Subtract( |
| surface_damage_rect_list_[index_to_be_removed]); |
| } |
| } |
| root_damage_rect.Union(surface_damage_rect); |
| ++surface_index; |
| } |
| |
| *damage_rect = root_damage_rect; |
| } |
| } |
| |
| if (should_add_previous_frame_overlay_damage) { |
| damage_rect->Union(PreviousFrameOverlayDamageContribution()); |
| } |
| damage_rect->Intersect(gfx::ToEnclosingRect(display_rect)); |
| damages_to_be_removed_.clear(); |
| } |
| |
| bool DCLayerOverlayProcessor::IsPreviousFrameUnderlayRect( |
| const gfx::Rect& quad_rectangle, |
| size_t index) { |
| if (index >= previous_frame_overlay_rects_.size()) { |
| return false; |
| } else { |
| // Although we can loop through the list to find out if there is an |
| // underlay with the same size from the previous frame, checking |
| // _rectx_[index] is the quickest way to do it. If we cannot find a match |
| // with the same index, there is probably a change in the number of |
| // overlays or layout. Then we won't be able to get a zero root damage |
| // rect in this case. Looping through the list won't give better power. |
| return (previous_frame_overlay_rects_[index].rect == quad_rectangle) && |
| (previous_frame_overlay_rects_[index].is_overlay == false); |
| } |
| } |
| |
| void DCLayerOverlayProcessor::RemoveClearVideoQuadCandidatesIfMoving( |
| const QuadList* quad_list, |
| std::vector<size_t>* candidate_index_list) { |
| // The number of frames all overlay candidates need to be stable before we |
| // allow overlays again. This number was chosen experimentally. |
| constexpr int kFramesOfStabilityForOverlayPromotion = 5; |
| |
| std::vector<gfx::Rect> current_frame_overlay_candidate_rects; |
| current_frame_overlay_candidate_rects.reserve(candidate_index_list->size()); |
| |
| for (auto index : *candidate_index_list) { |
| auto candidate_it = std::next(quad_list->begin(), index); |
| if (IsClearVideoQuad(candidate_it)) { |
| gfx::Rect quad_rectangle_in_target_space = |
| ClippedQuadRectangle(*candidate_it); |
| current_frame_overlay_candidate_rects.push_back( |
| quad_rectangle_in_target_space); |
| } |
| } |
| |
| if (previous_frame_overlay_candidate_rects_ != |
| current_frame_overlay_candidate_rects) { |
| frames_since_last_overlay_candidate_rects_change_ = 0; |
| |
| std::swap(previous_frame_overlay_candidate_rects_, |
| current_frame_overlay_candidate_rects); |
| } else { |
| frames_since_last_overlay_candidate_rects_change_++; |
| } |
| |
| if (frames_since_last_overlay_candidate_rects_change_ <= |
| kFramesOfStabilityForOverlayPromotion) { |
| // Remove all video quad candidates if any of them moved recently |
| auto candidate_index_it = candidate_index_list->begin(); |
| while (candidate_index_it != candidate_index_list->end()) { |
| auto candidate_it = std::next(quad_list->begin(), *candidate_index_it); |
| if (IsClearVideoQuad(candidate_it)) { |
| candidate_index_it = candidate_index_list->erase(candidate_index_it); |
| RecordDCLayerResult(DC_LAYER_FAILED_YUV_VIDEO_QUAD_MOVED, candidate_it); |
| } else { |
| candidate_index_it++; |
| } |
| } |
| } |
| } |
| |
| void DCLayerOverlayProcessor::Process( |
| DisplayResourceProvider* resource_provider, |
| const gfx::RectF& display_rect, |
| const FilterOperationsMap& render_pass_filters, |
| const FilterOperationsMap& render_pass_backdrop_filters, |
| AggregatedRenderPass* render_pass, |
| gfx::Rect* damage_rect, |
| SurfaceDamageRectList surface_damage_rect_list, |
| OverlayCandidateList* dc_layer_overlays, |
| bool is_video_capture_enabled, |
| bool is_page_fullscreen_mode) { |
| bool this_frame_has_occluding_damage_rect = false; |
| processed_yuv_overlay_count_ = 0; |
| surface_damage_rect_list_ = std::move(surface_damage_rect_list); |
| |
| // Output rects of child render passes that have backdrop filters in target |
| // space. These rects are used to determine if the overlay rect could be read |
| // by backdrop filters. |
| std::vector<gfx::Rect> backdrop_filter_rects; |
| |
| // Skip overlay for copy request, video capture or HDR P010 format. |
| if (ShouldSkipOverlay(render_pass, is_video_capture_enabled)) { |
| // Update damage rect before calling ClearOverlayState, otherwise |
| // previous_frame_overlay_rect_union will be empty. |
| damage_rect->Union(PreviousFrameOverlayDamageContribution()); |
| ClearOverlayState(); |
| |
| return; |
| } |
| |
| // Used for generating the candidate index list. |
| std::vector<size_t> candidate_index_list; |
| size_t candidate = 0; |
| |
| // Used for looping through candidate_index_list to UpdateDCLayerOverlays() |
| size_t prev_index = 0; |
| QuadList* quad_list = &render_pass->quad_list; |
| auto prev_it = quad_list->begin(); |
| |
| // Used for whether overlay should be skipped |
| int yuv_quads_in_quad_list = 0; |
| int damaged_yuv_quads_in_quad_list = 0; |
| bool has_protected_video_or_texture_overlays = false; |
| |
| for (auto it = quad_list->begin(); it != quad_list->end(); |
| ++it, ++candidate) { |
| if (it->material == DrawQuad::Material::kAggregatedRenderPass) { |
| const auto* rpdq = AggregatedRenderPassDrawQuad::MaterialCast(*it); |
| auto render_pass_it = |
| render_pass_backdrop_filters.find(rpdq->render_pass_id); |
| if (render_pass_it != render_pass_backdrop_filters.end()) { |
| backdrop_filter_rects.push_back(ClippedQuadRectangle(rpdq)); |
| } |
| continue; |
| } |
| |
| gpu::Mailbox promotion_hint_mailbox; |
| DCLayerResult result; |
| bool is_yuv_overlay = false; |
| switch (it->material) { |
| case DrawQuad::Material::kYuvVideoContent: |
| result = ValidateYUVQuad( |
| YUVVideoDrawQuad::MaterialCast(*it), backdrop_filter_rects, |
| has_overlay_support_, allowed_yuv_overlay_count_, |
| processed_yuv_overlay_count_, resource_provider); |
| is_yuv_overlay = true; |
| break; |
| case DrawQuad::Material::kTextureContent: { |
| const TextureDrawQuad* tex_quad = TextureDrawQuad::MaterialCast(*it); |
| |
| if (tex_quad->is_stream_video) { |
| // Stream video quads contain Media Foundation dcomp surface which is |
| // always presented as overlay. |
| result = DC_LAYER_SUCCESS; |
| } else { |
| result = ValidateTextureQuad( |
| tex_quad, backdrop_filter_rects, has_overlay_support_, |
| allowed_yuv_overlay_count_, processed_yuv_overlay_count_, |
| resource_provider); |
| } |
| auto si_format = |
| resource_provider->GetSharedImageFormat(tex_quad->resource_id()); |
| if (media::IsMultiPlaneFormatForHardwareVideoEnabled() && |
| si_format.is_multi_plane()) { |
| is_yuv_overlay = true; |
| } |
| |
| if (allow_promotion_hinting_) { |
| // If this quad has marked itself as wanting promotion hints then get |
| // the associated mailbox. |
| ResourceId id = tex_quad->resource_id(); |
| if (resource_provider->DoesResourceWantPromotionHint(id)) { |
| promotion_hint_mailbox = resource_provider->GetMailbox(id); |
| } |
| } |
| } break; |
| default: |
| result = DC_LAYER_FAILED_UNSUPPORTED_QUAD; |
| } |
| |
| if (is_yuv_overlay) { |
| yuv_quads_in_quad_list++; |
| if (no_undamaged_overlay_promotion_) { |
| if (it->shared_quad_state->overlay_damage_index.has_value() && |
| !surface_damage_rect_list_[it->shared_quad_state |
| ->overlay_damage_index.value()] |
| .IsEmpty()) { |
| damaged_yuv_quads_in_quad_list++; |
| if (result == DC_LAYER_SUCCESS) { |
| processed_yuv_overlay_count_++; |
| } |
| } |
| } else { |
| if (result == DC_LAYER_SUCCESS) { |
| processed_yuv_overlay_count_++; |
| } |
| } |
| } |
| |
| if (!promotion_hint_mailbox.IsZero()) { |
| DCHECK(allow_promotion_hinting_); |
| bool promoted = result == DC_LAYER_SUCCESS; |
| auto* overlay_state_service = OverlayStateService::GetInstance(); |
| // The OverlayStateService should always be initialized by GpuServiceImpl |
| // at creation - DCHECK here just to assert there aren't any corner cases |
| // where this isn't true. |
| DCHECK(overlay_state_service->IsInitialized()); |
| overlay_state_service->SetPromotionHint(promotion_hint_mailbox, promoted); |
| } |
| |
| if (result != DC_LAYER_SUCCESS) { |
| RecordDCLayerResult(result, it); |
| continue; |
| } |
| |
| const bool is_protected_video = IsProtectedVideo(it); |
| const bool is_texture_quad = |
| it->material == DrawQuad::Material::kTextureContent; |
| if (is_protected_video || is_texture_quad) |
| has_protected_video_or_texture_overlays = true; |
| |
| if (candidate_index_list.size() == 0) { |
| prev_index = candidate; |
| prev_it = it; |
| } |
| |
| candidate_index_list.push_back(candidate); |
| } |
| |
| // We might not save power if there are more than one videos and only part of |
| // them are promoted to overlay. Skip overlays for this frame unless there are |
| // protected video or texture overlays. |
| // In case of videos being paused or not started yet, we will allow multiple |
| // overlays if the number of damaged overlays doesn't exceed |
| // |allowed_yuv_overlay_count|. However, videos are not always damaged in |
| // every frame during video playback. To prevent overlay promotion from being |
| // switched between on and off, we wait for |
| // |kDCLayerFramesDelayedBeforeOverlay| frames before allowing multiple |
| // overlays |
| bool reject_overlays = false; |
| if (yuv_quads_in_quad_list > 1 && !has_protected_video_or_texture_overlays) { |
| if (no_undamaged_overlay_promotion_) { |
| if (damaged_yuv_quads_in_quad_list == processed_yuv_overlay_count_) { |
| frames_since_last_qualified_multi_overlays_++; |
| } else { |
| frames_since_last_qualified_multi_overlays_ = 0; |
| } |
| reject_overlays = frames_since_last_qualified_multi_overlays_ <= |
| kDCLayerFramesDelayedBeforeOverlay; |
| } else { |
| if (yuv_quads_in_quad_list != processed_yuv_overlay_count_) |
| reject_overlays = true; |
| } |
| } |
| |
| // A YUV quad might be rejected later due to not allowed as an underlay. |
| // Recount the YUV overlays when they are added to the overlay list |
| // successfully. |
| processed_yuv_overlay_count_ = 0; |
| |
| if (base::FeatureList::IsEnabled(features::kDisableVideoOverlayIfMoving)) { |
| RemoveClearVideoQuadCandidatesIfMoving(quad_list, &candidate_index_list); |
| } |
| |
| // Copy the overlay quad info to dc_layer_overlays and replace/delete overlay |
| // quads in quad_list. |
| for (auto index : candidate_index_list) { |
| auto it = std::next(prev_it, index - prev_index); |
| prev_it = it; |
| prev_index = index; |
| |
| if (reject_overlays) { |
| RecordDCLayerResult(DC_LAYER_FAILED_TOO_MANY_OVERLAYS, it); |
| continue; |
| } |
| |
| // Do not promote undamaged video to overlays. |
| bool undamaged = |
| it->shared_quad_state->overlay_damage_index.has_value() && |
| surface_damage_rect_list_[it->shared_quad_state->overlay_damage_index |
| .value()] |
| .IsEmpty(); |
| |
| if (yuv_quads_in_quad_list > allowed_yuv_overlay_count_ && |
| !has_protected_video_or_texture_overlays && undamaged && |
| no_undamaged_overlay_promotion_ && |
| it->material == DrawQuad::Material::kYuvVideoContent) { |
| RecordDCLayerResult(DC_LAYER_FAILED_NOT_DAMAGED, it); |
| continue; |
| } |
| |
| gfx::Rect quad_rectangle_in_target_space = ClippedQuadRectangle(*it); |
| |
| // Quad is considered an "overlay" if it has no occluders. |
| bool is_overlay = !IsOccluded(gfx::RectF(quad_rectangle_in_target_space), |
| quad_list->begin(), it, render_pass_filters); |
| |
| // Protected video is always put in an overlay, but texture quads can be |
| // skipped if they're not underlay compatible. |
| const bool requires_overlay = IsProtectedVideo(it); |
| |
| // TODO(magchen@): Since we reject underlays here, the max number of YUV |
| // overlays we can promote might not be accurate. We should allow all YUV |
| // quads to be put into candidate_index_list, but only |
| // |allowed_yuv_overlay_count_| YUV quads should be promoted to |
| // overlays/underlays from that list. |
| |
| // Skip quad if it's an underlay and underlays are not allowed. |
| if (!is_overlay && !requires_overlay) { |
| DCLayerResult result = IsUnderlayAllowed(it); |
| |
| if (result != DC_LAYER_SUCCESS) { |
| RecordDCLayerResult(result, it); |
| continue; |
| } |
| } |
| |
| gfx::Rect quad_rectangle_in_root_space = |
| cc::MathUtil::MapEnclosingClippedRect( |
| render_pass->transform_to_root_target, |
| quad_rectangle_in_target_space); |
| |
| // Used by a histogram. |
| this_frame_has_occluding_damage_rect = |
| !is_overlay && |
| HasOccludingDamageRect(it->shared_quad_state, surface_damage_rect_list_, |
| quad_rectangle_in_root_space); |
| |
| UpdateDCLayerOverlays(resource_provider, display_rect, render_pass, it, |
| quad_rectangle_in_root_space, is_overlay, &prev_it, |
| &prev_index, damage_rect, dc_layer_overlays, |
| is_page_fullscreen_mode); |
| } |
| |
| // Update previous frame state after processing root pass. If there is no |
| // overlay in this frame, previous_frame_overlay_rect_union will be added |
| // to the damage_rect here for GL composition because the overlay image from |
| // the previous frame is missing in the GL composition path. If any overlay is |
| // found in this frame, the previous overlay rects would have been handled |
| // above and previous_frame_overlay_rect_union becomes empty. |
| UpdateRootDamageRect(display_rect, damage_rect); |
| |
| std::swap(previous_frame_overlay_rects_, current_frame_overlay_rects_); |
| current_frame_overlay_rects_.clear(); |
| previous_display_rect_ = display_rect; |
| |
| if (processed_yuv_overlay_count_ > 0) { |
| base::UmaHistogramExactLinear( |
| "GPU.DirectComposition.DCLayer.YUVOverlayCount", |
| /*sample=*/processed_yuv_overlay_count_, |
| /*value_max=*/10); |
| |
| RecordOverlayHistograms(dc_layer_overlays, |
| this_frame_has_occluding_damage_rect, damage_rect); |
| } |
| } |
| |
| bool DCLayerOverlayProcessor::ShouldSkipOverlay( |
| AggregatedRenderPass* render_pass, |
| bool is_video_capture_enabled) { |
| QuadList* quad_list = &render_pass->quad_list; |
| |
| // Skip overlay processing if we have copy request or video capture is |
| // enabled. When video capture is enabled, some frames might not have copy |
| // request. |
| if (!render_pass->copy_requests.empty() || is_video_capture_enabled) { |
| // Find a valid overlay candidate from quad_list. |
| QuadList::Iterator it = FindAnOverlayCandidate(*quad_list); |
| if (it != quad_list->end()) { |
| is_video_capture_enabled |
| ? RecordDCLayerResult(DC_LAYER_FAILED_VIDEO_CAPTURE_ENABLED, it) |
| : RecordDCLayerResult(DC_LAYER_FAILED_COPY_REQUESTS, it); |
| } |
| return true; |
| } |
| |
| if (render_pass->content_color_usage == gfx::ContentColorUsage::kHDR) { |
| // Media Foundation always uses overlays to render video, so do not skip. |
| QuadList::Iterator it = |
| FindAnOverlayCandidateExcludingMediaFoundationVideoContent(*quad_list); |
| if (it != quad_list->end()) { |
| // Skip overlay processing if output colorspace is HDR and rgb10a2 overlay |
| // is not supported. Since most of overlay only supports NV12 and YUY2 |
| // now, HDR content (usually P010 format) cannot output through overlay |
| // without format degrading. In some Intel's platforms (Icelake or above), |
| // Overlay can play HDR content by supporting RGB10 format. Let overlay |
| // deal with HDR content in this situation. |
| bool supports_rgb10a2_overlay = |
| gl::GetDirectCompositionOverlaySupportFlags( |
| DXGI_FORMAT_R10G10B10A2_UNORM) != 0; |
| if (!supports_rgb10a2_overlay) { |
| RecordDCLayerResult(DC_LAYER_FAILED_OUTPUT_HDR, it); |
| return true; |
| } |
| // Skip overlay processing if output colorspace is HDR and system HDR is |
| // not enabled. Since we always want to use Viz do HDR tone mapping, to |
| // avoid a visual difference between Viz and video processor, do not allow |
| // overlay. |
| if (!system_hdr_enabled_) { |
| RecordDCLayerResult(DC_LAYER_FAILED_HDR_TONE_MAPPING, it); |
| return true; |
| } |
| } |
| } |
| |
| return false; |
| } |
| |
| void DCLayerOverlayProcessor::UpdateDCLayerOverlays( |
| DisplayResourceProvider* resource_provider, |
| const gfx::RectF& display_rect, |
| AggregatedRenderPass* render_pass, |
| const QuadList::Iterator& it, |
| const gfx::Rect& quad_rectangle_in_root_space, |
| bool is_overlay, |
| QuadList::Iterator* new_it, |
| size_t* new_index, |
| gfx::Rect* damage_rect, |
| OverlayCandidateList* dc_layer_overlays, |
| bool is_page_fullscreen_mode) { |
| // Record the result first before ProcessForOverlay(). |
| RecordDCLayerResult(DC_LAYER_SUCCESS, it); |
| |
| OverlayCandidate dc_layer; |
| dc_layer.possible_video_fullscreen_letterboxing = |
| is_page_fullscreen_mode |
| ? IsPossibleFullScreenLetterboxing(it, render_pass->quad_list.end(), |
| display_rect) |
| : false; |
| switch (it->material) { |
| case DrawQuad::Material::kYuvVideoContent: |
| FromYUVQuad(YUVVideoDrawQuad::MaterialCast(*it), |
| render_pass->transform_to_root_target, &dc_layer); |
| processed_yuv_overlay_count_++; |
| break; |
| case DrawQuad::Material::kTextureContent: { |
| const TextureDrawQuad* tex_quad = TextureDrawQuad::MaterialCast(*it); |
| FromTextureQuad(tex_quad, render_pass->transform_to_root_target, |
| resource_provider, &dc_layer); |
| auto si_format = |
| resource_provider->GetSharedImageFormat(tex_quad->resource_id()); |
| if (media::IsMultiPlaneFormatForHardwareVideoEnabled() && |
| si_format.is_multi_plane()) { |
| processed_yuv_overlay_count_++; |
| } |
| } break; |
| default: |
| NOTREACHED(); |
| } |
| |
| // Underlays are less efficient, so attempt regular overlays first. We can |
| // only check for occlusion within a render pass. |
| if (is_overlay) { |
| *new_it = ProcessForOverlay(display_rect, render_pass, it); |
| (*new_index)++; |
| } else { |
| ProcessForUnderlay(display_rect, render_pass, quad_rectangle_in_root_space, |
| it, dc_layer_overlays->size(), damage_rect, &dc_layer); |
| } |
| |
| current_frame_overlay_rects_.push_back( |
| {quad_rectangle_in_root_space, is_overlay}); |
| |
| dc_layer_overlays->push_back(dc_layer); |
| |
| // Recorded for each overlay. |
| UMA_HISTOGRAM_BOOLEAN("GPU.DirectComposition.IsUnderlay", !is_overlay); |
| } |
| |
| QuadList::Iterator DCLayerOverlayProcessor::ProcessForOverlay( |
| const gfx::RectF& display_rect, |
| AggregatedRenderPass* render_pass, |
| const QuadList::Iterator& it) { |
| // The quad is on top, so promote it to an overlay and remove all damage |
| // underneath it. |
| const bool display_rect_changed = (display_rect != previous_display_rect_); |
| const bool is_axis_aligned = it->shared_quad_state->quad_to_target_transform |
| .Preserves2dAxisAlignment(); |
| const bool needs_blending = it->ShouldDrawWithBlending(); |
| |
| if (is_axis_aligned && !display_rect_changed && !needs_blending) { |
| RemoveOverlayDamageRect(it); |
| } |
| |
| return render_pass->quad_list.EraseAndInvalidateAllPointers(it); |
| } |
| |
| void DCLayerOverlayProcessor::ProcessForUnderlay( |
| const gfx::RectF& display_rect, |
| AggregatedRenderPass* render_pass, |
| const gfx::Rect& quad_rectangle, |
| const QuadList::Iterator& it, |
| size_t processed_overlay_count, |
| gfx::Rect* damage_rect, |
| OverlayCandidate* dc_layer) { |
| // Assign decreasing z-order so that underlays processed earlier, and hence |
| // which are above the subsequent underlays, are placed above in the direct |
| // composition visual tree. |
| dc_layer->plane_z_order = -1 - processed_overlay_count; |
| |
| // If the video is translucent and uses SrcOver blend mode, we can achieve the |
| // same result as compositing with video on top if we replace video quad with |
| // a solid color quad with DstOut blend mode, and rely on SrcOver blending |
| // of the root surface with video on bottom. Essentially, |
| // |
| // SrcOver_quad(V, B, V_alpha) = SrcOver_premul(DstOut(BLACK, B, V_alpha), V) |
| // where |
| // V is the video quad |
| // B is the background |
| // SrcOver_quad uses opacity of source quad (V_alpha) |
| // SrcOver_premul uses alpha channel and assumes premultipled alpha |
| // |
| // This also applies to quads with a mask filter for rounded corners. |
| bool is_opaque = false; |
| |
| if (it->ShouldDrawWithBlending() && |
| it->shared_quad_state->blend_mode == SkBlendMode::kSrcOver) { |
| render_pass->ReplaceExistingQuadWithSolidColor(it, SkColors::kBlack, |
| SkBlendMode::kDstOut); |
| } else { |
| // When the opacity == 1.0, drawing with transparent will be done without |
| // blending and will have the proper effect of completely clearing the |
| // layer. |
| render_pass->ReplaceExistingQuadWithSolidColor(it, SkColors::kTransparent, |
| SkBlendMode::kSrcOver); |
| is_opaque = true; |
| } |
| |
| const bool display_rect_unchanged = (display_rect == previous_display_rect_); |
| const bool underlay_rect_unchanged = |
| IsPreviousFrameUnderlayRect(quad_rectangle, processed_overlay_count); |
| const bool is_axis_aligned = it->shared_quad_state->quad_to_target_transform |
| .Preserves2dAxisAlignment(); |
| bool opacity_unchanged = (is_opaque == previous_frame_underlay_is_opaque_); |
| previous_frame_underlay_is_opaque_ = is_opaque; |
| |
| if (is_axis_aligned && opacity_unchanged && underlay_rect_unchanged && |
| display_rect_unchanged) { |
| // If this underlay rect is the same as for last frame, Remove its area |
| // from the damage of the main surface, as the cleared area was already |
| // cleared last frame. |
| |
| // If none of the quads on top give any damage, we can skip compositing |
| // these quads. The output root damage rect might be empty after we remove |
| // the damage from the video quad. We can save power if the root damage |
| // rect is empty. |
| RemoveOverlayDamageRect(it); |
| } else { |
| // Entire replacement quad must be redrawn. |
| damage_rect->Union(quad_rectangle); |
| surface_damage_rect_list_.push_back(quad_rectangle); |
| } |
| } |
| |
| } // namespace viz |