| // Copyright 2017 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "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 "base/threading/thread_task_runner_handle.h" |
| #include "build/build_config.h" |
| #include "cc/base/math_util.h" |
| #include "components/viz/common/display/renderer_settings.h" |
| #include "components/viz/common/quads/aggregated_render_pass_draw_quad.h" |
| #include "components/viz/common/quads/debug_border_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/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 "ui/gfx/geometry/insets.h" |
| #include "ui/gfx/geometry/rect.h" |
| #include "ui/gfx/geometry/rect_conversions.h" |
| #include "ui/gl/gl_switches.h" |
| #include "ui/gl/gl_utils.h" |
| #include "ui/gl/gpu_switching_manager.h" |
| |
| namespace viz { |
| |
| namespace { |
| |
| constexpr int kDCLayerDebugBorderWidth = 4; |
| constexpr gfx::Insets kDCLayerDebugBorderInsets = gfx::Insets(-2); |
| |
| // 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 [[deprecated]] = 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, // not recorded |
| DC_LAYER_FAILED_TOO_MANY_OVERLAYS = 8, |
| DC_LAYER_FAILED_NO_HW_OVERLAY_SUPPORT [[deprecated]] = 9, |
| DC_LAYER_FAILED_ROUNDED_CORNERS = 10, |
| DC_LAYER_FAILED_BACKDROP_FILTERS = 11, |
| kMaxValue = DC_LAYER_FAILED_BACKDROP_FILTERS, |
| }; |
| |
| enum : size_t { |
| kTextureResourceIndex = 0, |
| kYPlaneResourceIndex = 0, |
| kUVPlaneResourceIndex = 1, |
| }; |
| |
| // This returns the smallest rectangle in target space that contains the quad. |
| gfx::RectF ClippedQuadRectangle(const DrawQuad* quad) { |
| gfx::RectF quad_rect = cc::MathUtil::MapClippedRect( |
| quad->shared_quad_state->quad_to_target_transform, |
| gfx::RectF(quad->rect)); |
| if (quad->shared_quad_state->clip_rect) |
| quad_rect.Intersect(gfx::RectF(*quad->shared_quad_state->clip_rect)); |
| return quad_rect; |
| } |
| |
| gfx::RectF GetExpandedRectWithPixelMovingFilter( |
| const AggregatedRenderPassDrawQuad* rpdq, |
| float max_pixel_movement) { |
| const SharedQuadState* shared_quad_state = rpdq->shared_quad_state; |
| gfx::RectF expanded_rect(rpdq->rect); |
| expanded_rect.Inset(-max_pixel_movement, -max_pixel_movement); |
| |
| // expanded_rect in the target space |
| return cc::MathUtil::MapClippedRect( |
| shared_quad_state->quad_to_target_transform, expanded_rect); |
| } |
| |
| 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; |
| |
| // Rounded corner on overlays are not supported. |
| if (quad->shared_quad_state->mask_filter_info.HasRoundedCorners()) |
| return DC_LAYER_FAILED_ROUNDED_CORNERS; |
| |
| auto quad_target_rect = gfx::ToEnclosingRect(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; |
| } |
| |
| return DC_LAYER_SUCCESS; |
| } |
| |
| void FromYUVQuad(const YUVVideoDrawQuad* quad, |
| const gfx::Transform& transform_to_root_target, |
| DCLayerOverlay* dc_layer) { |
| // Direct composition path only supports single NV12 buffer, or two buffers |
| // one each for Y and UV planes. |
| 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->resources[kYPlaneResourceIndex] = quad->y_plane_resource_id(); |
| dc_layer->resources[kUVPlaneResourceIndex] = quad->u_plane_resource_id(); |
| |
| dc_layer->z_order = 1; |
| dc_layer->content_rect = gfx::ToNearestRect(quad->ya_tex_coord_rect); |
| dc_layer->quad_rect = 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( |
| quad->shared_quad_state->quad_to_target_transform); |
| quad_to_root_transform.ConcatTransform(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.FlattenTo2d(); |
| 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. |
| gfx::RectF clip_rect = gfx::RectF(*quad->shared_quad_state->clip_rect); |
| transform_to_root_target.TransformRect(&clip_rect); |
| dc_layer->clip_rect = gfx::ToEnclosingRect(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; |
| } |
| |
| DCLayerResult ValidateTextureQuad( |
| const TextureDrawQuad* quad, |
| const std::vector<gfx::Rect>& backdrop_filter_rects, |
| 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; |
| } |
| |
| // Rounded corner on overlays are not supported. |
| if (quad->shared_quad_state->mask_filter_info.HasRoundedCorners()) |
| return DC_LAYER_FAILED_ROUNDED_CORNERS; |
| |
| auto quad_target_rect = gfx::ToEnclosingRect(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; |
| } |
| |
| return DC_LAYER_SUCCESS; |
| } |
| |
| void FromTextureQuad(const TextureDrawQuad* quad, |
| const gfx::Transform& transform_to_root_target, |
| DCLayerOverlay* dc_layer) { |
| dc_layer->resources[kTextureResourceIndex] = quad->resource_id(); |
| dc_layer->z_order = 1; |
| dc_layer->content_rect = gfx::Rect(quad->resource_size_in_pixels()); |
| dc_layer->quad_rect = 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->content_rect.height()); |
| } |
| quad_to_root_transform.ConcatTransform( |
| quad->shared_quad_state->quad_to_target_transform); |
| quad_to_root_transform.ConcatTransform(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.FlattenTo2d(); |
| 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. |
| gfx::RectF clip_rect = gfx::RectF(*quad->shared_quad_state->clip_rect); |
| transform_to_root_target.TransformRect(&clip_rect); |
| dc_layer->clip_rect = gfx::ToEnclosingRect(clip_rect); |
| } |
| dc_layer->color_space = gfx::ColorSpace::CreateSRGB(); |
| } |
| |
| bool IsProtectedVideo(const QuadList::Iterator& 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; |
| } |
| return false; |
| } |
| |
| DCLayerResult IsUnderlayAllowed(const QuadList::Iterator& it) { |
| if (it->ShouldDrawWithBlending()) { |
| return DC_LAYER_FAILED_TRANSPARENT; |
| } |
| return DC_LAYER_SUCCESS; |
| } |
| |
| // Any occluding quads in the quad list on top of the overlay/underlay |
| bool HasOccludingQuads( |
| const gfx::RectF& target_quad, |
| QuadList::ConstIterator quad_list_begin, |
| QuadList::ConstIterator quad_list_end, |
| const DCLayerOverlayProcessor::FilterOperationsMap& render_pass_filters) { |
| 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; |
| float max_pixel_movement = filters->MaximumPixelMovement(); |
| overlap_rect = |
| GetExpandedRectWithPixelMovingFilter(rpdq, max_pixel_movement); |
| has_pixel_moving_filter = true; |
| } |
| } |
| |
| if (!has_pixel_moving_filter) |
| overlap_rect = ClippedQuadRectangle(quad); |
| |
| if (quad->material == DrawQuad::Material::kSolidColor) { |
| SkColor color = SolidColorDrawQuad::MaterialCast(quad)->color; |
| float alpha = (SkColorGetA(color) * (1.0f / 255.0f)) * opacity; |
| if (quad->ShouldDrawWithBlending() && |
| alpha < std::numeric_limits<float>::epsilon()) |
| continue; |
| } |
| if (overlap_rect.Intersects(target_quad)) |
| return true; |
| } |
| return false; |
| } |
| |
| gfx::Rect CalculateOccludingDamageRect( |
| const SharedQuadState* shared_quad_state, |
| const SurfaceDamageRectList& surface_damage_rect_list, |
| const gfx::Rect& quad_rect_in_root_target_space) { |
| if (!shared_quad_state->overlay_damage_index.has_value()) |
| return quad_rect_in_root_target_space; |
| |
| 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_target_space); |
| |
| return occluding_damage_rect; |
| } |
| |
| 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::Iterator 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: |
| UMA_HISTOGRAM_ENUMERATION("GPU.DirectComposition.DCLayerResult.Texture", |
| result); |
| break; |
| default: |
| break; |
| } |
| } |
| |
| // This function records the damage rect rect of the current frame. |
| void RecordOverlayHistograms(DCLayerOverlayList* 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.z_order != 1) { |
| is_overlay = false; |
| break; |
| } |
| } |
| |
| OverlayProcessorInterface::RecordOverlayDamageRectHistograms( |
| is_overlay, has_occluding_surface_damage, damage_rect->IsEmpty()); |
| } |
| } // namespace |
| |
| DCLayerOverlay::DCLayerOverlay() = default; |
| DCLayerOverlay::DCLayerOverlay(const DCLayerOverlay& other) = default; |
| DCLayerOverlay& DCLayerOverlay::operator=(const DCLayerOverlay& other) = |
| default; |
| DCLayerOverlay::~DCLayerOverlay() = default; |
| |
| DCLayerOverlayProcessor::DCLayerOverlayProcessor( |
| const DebugRendererSettings* debug_settings, |
| 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), |
| debug_settings_(debug_settings), |
| viz_task_runner_(skip_initialization_for_testing |
| ? nullptr |
| : base::ThreadTaskRunnerHandle::Get()) { |
| if (!skip_initialization_for_testing) { |
| UpdateHasHwOverlaySupport(); |
| ui::GpuSwitchingManager::GetInstance()->AddObserver(this); |
| } |
| } |
| |
| DCLayerOverlayProcessor::~DCLayerOverlayProcessor() { |
| ui::GpuSwitchingManager::GetInstance()->RemoveObserver(this); |
| } |
| |
| // Called on the Viz Compositor thread |
| void DCLayerOverlayProcessor::UpdateHasHwOverlaySupport() { |
| DCHECK(viz_task_runner_->BelongsToCurrentThread()); |
| has_overlay_support_ = gl::AreOverlaysSupportedWin(); |
| } |
| |
| // Not on the Viz Compositor thread |
| void DCLayerOverlayProcessor::OnDisplayAdded() { |
| viz_task_runner_->PostTask( |
| FROM_HERE, |
| base::BindOnce(&DCLayerOverlayProcessor::UpdateHasHwOverlaySupport, |
| base::Unretained(this))); |
| } |
| |
| // Not on the Viz Compositor thread |
| void DCLayerOverlayProcessor::OnDisplayRemoved() { |
| viz_task_runner_->PostTask( |
| FROM_HERE, |
| base::BindOnce(&DCLayerOverlayProcessor::UpdateHasHwOverlaySupport, |
| base::Unretained(this))); |
| } |
| |
| void DCLayerOverlayProcessor::ClearOverlayState() { |
| previous_frame_overlay_rects_.clear(); |
| } |
| |
| 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, |
| const gfx::Rect& quad_rectangle, |
| const gfx::Rect& occluding_damage_rect, |
| gfx::Rect* damage_rect) { |
| // 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(); |
| } |
| |
| void DCLayerOverlayProcessor::InsertDebugBorderDrawQuad( |
| const DCLayerOverlayList* dc_layer_overlays, |
| AggregatedRenderPass* render_pass, |
| const gfx::RectF& display_rect, |
| gfx::Rect* damage_rect) { |
| auto* shared_quad_state = render_pass->CreateAndAppendSharedQuadState(); |
| auto& quad_list = render_pass->quad_list; |
| |
| // Add debug borders for the root damage rect after overlay promotion. |
| SkColor border_color = SK_ColorGREEN; |
| auto it = quad_list.InsertBeforeAndInvalidateAllPointers<DebugBorderDrawQuad>( |
| quad_list.begin(), 1u); |
| auto* debug_quad = static_cast<DebugBorderDrawQuad*>(*it); |
| |
| gfx::Rect rect = *damage_rect; |
| rect.Inset(kDCLayerDebugBorderInsets); |
| debug_quad->SetNew(shared_quad_state, rect, rect, border_color, |
| kDCLayerDebugBorderWidth); |
| |
| // Add debug borders for overlays/underlays |
| for (const auto& dc_layer : *dc_layer_overlays) { |
| gfx::RectF overlay_rect(dc_layer.quad_rect); |
| dc_layer.transform.TransformRect(&overlay_rect); |
| if (dc_layer.clip_rect) |
| overlay_rect.Intersect(gfx::RectF(*dc_layer.clip_rect)); |
| |
| // Overlay:red, Underlay:blue. |
| SkColor border_color = dc_layer.z_order > 0 ? SK_ColorRED : SK_ColorBLUE; |
| auto it = |
| quad_list.InsertBeforeAndInvalidateAllPointers<DebugBorderDrawQuad>( |
| quad_list.begin(), 1u); |
| auto* debug_quad = static_cast<DebugBorderDrawQuad*>(*it); |
| |
| gfx::Rect rect = gfx::ToEnclosingRect(overlay_rect); |
| rect.Inset(kDCLayerDebugBorderInsets); |
| debug_quad->SetNew(shared_quad_state, rect, rect, border_color, |
| kDCLayerDebugBorderWidth); |
| } |
| |
| // Mark the entire output as damaged because the border quads might not be |
| // inside the current damage rect. It's far simpler to mark the entire output |
| // as damaged instead of accounting for individual border quads which can |
| // change positions across frames. |
| damage_rect->Union(gfx::ToEnclosingRect(display_rect)); |
| } |
| |
| 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::Process( |
| DisplayResourceProvider* resource_provider, |
| const gfx::RectF& display_rect, |
| const FilterOperationsMap& render_pass_filters, |
| const FilterOperationsMap& render_pass_backdrop_filters, |
| AggregatedRenderPassList* render_pass_list, |
| gfx::Rect* damage_rect, |
| SurfaceDamageRectList surface_damage_rect_list, |
| DCLayerOverlayList* dc_layer_overlays) { |
| 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; |
| |
| auto* root_render_pass = render_pass_list->back().get(); |
| if (render_pass_list->back()->is_color_conversion_pass) { |
| DCHECK_GT(render_pass_list->size(), 1u); |
| root_render_pass = (*render_pass_list)[render_pass_list->size() - 2].get(); |
| } |
| |
| // Used for generating the candidate index list. |
| QuadList* quad_list = &root_render_pass->quad_list; |
| std::vector<size_t> candidate_index_list; |
| size_t index = 0; |
| |
| // Used for looping through candidate_index_list to UpdateDCLayerOverlays() |
| size_t prev_index = 0; |
| auto prev_it = quad_list->begin(); |
| |
| // Used for whether overlay should be skipped |
| int 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, ++index) { |
| 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( |
| gfx::ToEnclosingRect(ClippedQuadRectangle(rpdq))); |
| } |
| continue; |
| } |
| |
| DCLayerResult result; |
| 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); |
| yuv_quads_in_quad_list++; |
| if (result == DC_LAYER_SUCCESS) |
| processed_yuv_overlay_count_++; |
| break; |
| case DrawQuad::Material::kTextureContent: |
| result = ValidateTextureQuad(TextureDrawQuad::MaterialCast(*it), |
| backdrop_filter_rects, resource_provider); |
| break; |
| default: |
| result = DC_LAYER_FAILED_UNSUPPORTED_QUAD; |
| } |
| |
| 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 = index; |
| prev_it = it; |
| } |
| |
| candidate_index_list.push_back(index); |
| } |
| |
| // 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. |
| bool reject_overlays = false; |
| if (yuv_quads_in_quad_list != processed_yuv_overlay_count_ && |
| !has_protected_video_or_texture_overlays) { |
| 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; |
| |
| // 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; |
| } |
| gfx::Rect quad_rectangle_in_target_space = |
| gfx::ToEnclosingRect(ClippedQuadRectangle(*it)); |
| |
| // Quad is considered an "overlay" if it has no occluders. |
| bool is_overlay = |
| !HasOccludingQuads(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; |
| } |
| } |
| |
| // Get the occluding damage rect for underlay. |
| gfx::Rect occluding_damage_rect; |
| if (!is_overlay) { |
| occluding_damage_rect = CalculateOccludingDamageRect( |
| it->shared_quad_state, surface_damage_rect_list_, |
| quad_rectangle_in_target_space); |
| |
| // Used by a histogram. |
| if (!occluding_damage_rect.IsEmpty()) |
| this_frame_has_occluding_damage_rect = true; |
| } |
| |
| UpdateDCLayerOverlays(display_rect, root_render_pass, it, |
| quad_rectangle_in_target_space, occluding_damage_rect, |
| is_overlay, &prev_it, &prev_index, damage_rect, |
| dc_layer_overlays); |
| } |
| |
| // 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 (!dc_layer_overlays->empty()) { |
| 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); |
| } |
| |
| if (debug_settings_->show_dc_layer_debug_borders) { |
| InsertDebugBorderDrawQuad(dc_layer_overlays, root_render_pass, display_rect, |
| damage_rect); |
| } |
| } |
| |
| void DCLayerOverlayProcessor::UpdateDCLayerOverlays( |
| const gfx::RectF& display_rect, |
| AggregatedRenderPass* render_pass, |
| const QuadList::Iterator& it, |
| const gfx::Rect& quad_rectangle_in_target_space, |
| const gfx::Rect& occluding_damage_rect, |
| bool is_overlay, |
| QuadList::Iterator* new_it, |
| size_t* new_index, |
| gfx::Rect* damage_rect, |
| DCLayerOverlayList* dc_layer_overlays) { |
| // Record the result first before ProcessForOverlay(). |
| RecordDCLayerResult(DC_LAYER_SUCCESS, it); |
| |
| DCLayerOverlay dc_layer; |
| 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: |
| FromTextureQuad(TextureDrawQuad::MaterialCast(*it), |
| render_pass->transform_to_root_target, &dc_layer); |
| break; |
| default: |
| NOTREACHED(); |
| } |
| |
| // Underlays are less efficient, so attempt regular overlays first. Only |
| // check root render pass because we can only check for occlusion within a |
| // render pass. |
| if (is_overlay) { |
| *new_it = |
| ProcessForOverlay(display_rect, render_pass, |
| quad_rectangle_in_target_space, it, damage_rect); |
| (*new_index)++; |
| } else { |
| ProcessForUnderlay(display_rect, render_pass, |
| quad_rectangle_in_target_space, occluding_damage_rect, |
| it, dc_layer_overlays->size(), damage_rect, &dc_layer); |
| } |
| |
| gfx::Rect rect_in_root = cc::MathUtil::MapEnclosingClippedRect( |
| render_pass->transform_to_root_target, quad_rectangle_in_target_space); |
| current_frame_overlay_rects_.push_back({rect_in_root, 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 gfx::Rect& quad_rectangle, |
| const QuadList::Iterator& it, |
| gfx::Rect* damage_rect) { |
| // 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, quad_rectangle, |
| /*occluding_damage_rect=*/gfx::Rect(), damage_rect); |
| } |
| |
| return render_pass->quad_list.EraseAndInvalidateAllPointers(it); |
| } |
| |
| void DCLayerOverlayProcessor::ProcessForUnderlay( |
| const gfx::RectF& display_rect, |
| AggregatedRenderPass* render_pass, |
| const gfx::Rect& quad_rectangle, |
| const gfx::Rect& occluding_damage_rect, |
| const QuadList::Iterator& it, |
| size_t processed_overlay_count, |
| gfx::Rect* damage_rect, |
| DCLayerOverlay* 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->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 |
| bool is_opaque = false; |
| |
| if (it->ShouldDrawWithBlending() && |
| it->shared_quad_state->blend_mode == SkBlendMode::kSrcOver) { |
| render_pass->ReplaceExistingQuadWithSolidColor(it, SK_ColorBLACK, |
| 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, SK_ColorTRANSPARENT, |
| SkBlendMode::kSrcOver); |
| is_opaque = true; |
| } |
| |
| const bool display_rect_changed = (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(); |
| |
| if (is_axis_aligned && is_opaque && underlay_rect_unchanged && |
| !display_rect_changed) { |
| // 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, quad_rectangle, occluding_damage_rect, |
| damage_rect); |
| } else { |
| // Entire replacement quad must be redrawn. |
| damage_rect->Union(quad_rectangle); |
| surface_damage_rect_list_.push_back(quad_rectangle); |
| } |
| } |
| |
| } // namespace viz |