// 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 "base/notreached.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/color_space.h"
#include "ui/gfx/color_space_win.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"
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_YUV_VIDEO_QUAD_HDR_TONE_MAPPING = 17,
DC_LAYER_FAILED_YUV_VIDEO_QUAD_NO_HDR_METADATA = 18,
DC_LAYER_FAILED_YUV_VIDEO_QUAD_HLG = 19,
DC_LAYER_FAILED_YUV_VIDEO_QUAD_NO_P010_VIDEO_PROCESSOR_SUPPORT = 20,
DC_LAYER_FAILED_YUV_VIDEO_QUAD_HDR_NON_FULLSCREEN [[deprecated]] = 21,
DC_LAYER_FAILED_YUV_VIDEO_QUAD_HDR_NON_P010 = 22,
DC_LAYER_FAILED_YUV_VIDEO_QUAD_UNSUPPORTED_COLORSPACE = 23,
kMaxValue = DC_LAYER_FAILED_YUV_VIDEO_QUAD_UNSUPPORTED_COLORSPACE,
};
bool IsCompatibleHDRMetadata(
const std::optional<gfx::HDRMetadata>& hdr_metadata) {
return hdr_metadata &&
((hdr_metadata->smpte_st_2086 &&
hdr_metadata->smpte_st_2086->IsValid()) ||
(hdr_metadata->cta_861_3 && hdr_metadata->cta_861_3->IsValid()));
}
DCLayerResult ValidateYUVOverlay(
const gfx::ProtectedVideoType& protected_video_type,
const gfx::ColorSpace& video_color_space,
const SharedImageFormat si_format,
const std::optional<gfx::HDRMetadata>& hdr_metadata,
bool has_overlay_support,
bool has_p010_video_processor_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;
}
// For YUV color spaces that VP couldn't handle, stop promote overlay.
if ((video_color_space.GetMatrixID() != gfx::ColorSpace::MatrixID::RGB) &&
!gfx::ColorSpaceWin::CanConvertToDXGIColorSpace(video_color_space)) {
return DC_LAYER_FAILED_YUV_VIDEO_QUAD_UNSUPPORTED_COLORSPACE;
}
// 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;
}
if (video_color_space.IsHDR()) {
// Otherwise, it could be a parser bug like https://crbug.com/1362288 if the
// hdr metadata is still missing. Missing `smpte_st_2086` or `cta_861_3`
// could always causes intel driver crash when in HDR overlay mode, and
// technically as long as one of the `smpte_st_2086` or `cta_861_3` exists
// could solve the crash issue.
if (!IsCompatibleHDRMetadata(hdr_metadata)) {
return DC_LAYER_FAILED_YUV_VIDEO_QUAD_NO_HDR_METADATA;
}
// Do not promote hdr overlay if buffer is not in 10bit P010 format. as this
// may cause blue output result if content is NV12 8bit HDR10.
if (si_format != MultiPlaneFormat::kP010) {
return DC_LAYER_FAILED_YUV_VIDEO_QUAD_HDR_NON_P010;
}
}
// Only promote overlay for 10bit+ contents when video processor can
// handle P010 contents, otherwise disable overlay.
if (si_format == MultiPlaneFormat::kP010 &&
!has_p010_video_processor_support) {
return DC_LAYER_FAILED_YUV_VIDEO_QUAD_NO_P010_VIDEO_PROCESSOR_SUPPORT;
}
return DC_LAYER_SUCCESS;
}
DCLayerResult ValidateYUVQuad(
const YUVVideoDrawQuad* quad,
const std::vector<gfx::Rect>& backdrop_filter_rects,
bool has_overlay_support,
bool has_p010_video_processor_support,
int allowed_yuv_overlay_count,
int processed_yuv_overlay_count,
const 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;
}
// For YUV color spaces that VP couldn't handle, stop promote overlay.
if ((quad->video_color_space.GetMatrixID() !=
gfx::ColorSpace::MatrixID::RGB) &&
!gfx::ColorSpaceWin::CanConvertToDXGIColorSpace(
quad->video_color_space)) {
return DC_LAYER_FAILED_YUV_VIDEO_QUAD_UNSUPPORTED_COLORSPACE;
}
// 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;
}
if (quad->video_color_space.IsHDR()) {
// Otherwise, it could be a parser bug like https://crbug.com/1362288 if the
// hdr metadata is still missing. Missing `smpte_st_2086` or `cta_861_3`
// could always causes intel driver crash when in HDR overlay mode, and
// technically as long as one of the `smpte_st_2086` or `cta_861_3` exists
// could solve the crash issue.
if (!IsCompatibleHDRMetadata(quad->hdr_metadata)) {
return DC_LAYER_FAILED_YUV_VIDEO_QUAD_NO_HDR_METADATA;
}
// Do not promote hdr overlay if buffer is not in 10bit P010 format. as this
// may cause blue output result if content is NV12 8bit HDR10.
if (quad->bits_per_channel < 10) {
return DC_LAYER_FAILED_YUV_VIDEO_QUAD_HDR_NON_P010;
}
}
// Only promote overlay for 10bit+ contents when video processor can
// handle P010 contents, otherwise disable overlay.
if (quad->bits_per_channel >= 10 && !has_p010_video_processor_support) {
return DC_LAYER_FAILED_YUV_VIDEO_QUAD_NO_P010_VIDEO_PROCESSOR_SUPPORT;
}
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,
bool has_p010_video_processor_support,
int allowed_yuv_overlay_count,
int processed_yuv_overlay_count,
const 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;
}
if (quad->is_video_frame) {
const auto& color_space =
resource_provider->GetColorSpace(quad->resource_id());
const auto& hdr_metadata =
resource_provider->GetHDRMetadata(quad->resource_id());
auto si_format =
resource_provider->GetSharedImageFormat(quad->resource_id());
auto result = ValidateYUVOverlay(
quad->protected_video_type, color_space, si_format, hdr_metadata,
has_overlay_support, has_p010_video_processor_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,
const 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->GetColorSpace(quad->resource_id());
dc_layer->hdr_metadata =
resource_provider->GetHDRMetadata(quad->resource_id());
dc_layer->protected_video_type = quad->protected_video_type;
// Both color space and protected_video_type are hard-coded for stream video.
// TODO(crbug.com/40878556): 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;
}
}
DCLayerResult IsUnderlayAllowed(const DrawQuad* quad) {
if (quad->ShouldDrawWithBlending() &&
!quad->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_target_space) {
if (!shared_quad_state->overlay_damage_index.has_value())
return !quad_rect_in_target_space.IsEmpty();
size_t overlay_damage_index = shared_quad_state->overlay_damage_index.value();
CHECK_LT(overlay_damage_index, surface_damage_rect_list.size());
// 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_target_space);
return !occluding_damage_rect.IsEmpty();
}
bool IsPossibleFullScreenLetterboxing(const QuadList::ConstIterator& it,
QuadList::ConstIterator quad_list_end,
const gfx::Rect& 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() == gfx::PointF(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, const DrawQuad* quad) {
// Skip recording unsupported quads since that'd dwarf the data we care about.
if (result == DC_LAYER_FAILED_UNSUPPORTED_QUAD)
return;
switch (quad->material) {
case DrawQuad::Material::kYuvVideoContent:
RecordVideoDCLayerResult(
result, YUVVideoDrawQuad::MaterialCast(quad)->protected_video_type);
break;
case DrawQuad::Material::kTextureContent: {
auto* tex_quad = TextureDrawQuad::MaterialCast(quad);
if (tex_quad->is_stream_video) {
UMA_HISTOGRAM_ENUMERATION(
"GPU.DirectComposition.DCLayerResult.StreamVideo", result);
} else if (tex_quad->is_video_frame) {
RecordVideoDCLayerResult(result, tex_quad->protected_video_type);
} 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(
const DCLayerOverlayProcessor::RenderPassOverlayDataMap&
render_pass_overlay_data_map,
bool has_occluding_surface_damage) {
// If an underlay is found, we record the damage rect of this frame as an
// underlay.
bool is_overlay = true;
for (auto& [render_pass, overlay_data] : render_pass_overlay_data_map) {
is_overlay = base::ranges::all_of(
overlay_data.promoted_overlays,
[](const auto& dc_layer) { return dc_layer.plane_z_order > 0; });
if (!is_overlay) {
break;
}
}
bool damage_rects_empty = base::ranges::all_of(
render_pass_overlay_data_map,
[](const auto& data) { return data.second.damage_rect.IsEmpty(); });
OverlayProcessorInterface::RecordOverlayDamageRectHistograms(
is_overlay, has_occluding_surface_damage, damage_rects_empty);
}
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 IsVideoQuad(const DrawQuad* quad) {
return quad->material == DrawQuad::Material::kYuvVideoContent ||
(quad->material == DrawQuad::Material::kTextureContent &&
TextureDrawQuad::MaterialCast(quad)->is_video_frame);
}
gfx::ProtectedVideoType GetProtectedVideoType(const DrawQuad* quad) {
if (quad->material == DrawQuad::Material::kYuvVideoContent) {
return YUVVideoDrawQuad::MaterialCast(quad)->protected_video_type;
} else if (quad->material == DrawQuad::Material::kTextureContent) {
return TextureDrawQuad::MaterialCast(quad)->protected_video_type;
} else {
return gfx::ProtectedVideoType::kClear;
}
}
bool IsOverlayRequiredForQuad(const DrawQuad* quad) {
// Hardware protected video always requires overlays, and for software
// protected video we prefer it for the protection benefits of overlays.
if (GetProtectedVideoType(quad) != gfx::ProtectedVideoType::kClear) {
return true;
}
// As do stream video textures e.g. when MediaFoundationRenderer is used for
// clear video with direct composition.
return quad->material == DrawQuad::Material::kTextureContent &&
TextureDrawQuad::MaterialCast(quad)->is_stream_video;
}
// A bit of a misnomer, but these are all the "standard" no overlay required
// (which implies) clear video quads.
bool IsClearVideoQuad(const DrawQuad* quad) {
return IsVideoQuad(quad) && !IsOverlayRequiredForQuad(quad);
}
bool AllowRemoveClearVideoQuadCandidatesWhenMoving(
const DisplayResourceProvider* resource_provider,
const DrawQuad* quad,
bool force_overlay_for_auto_hdr) {
if (!IsClearVideoQuad(quad)) {
return false;
}
// Do not allow remove clear video quad candidates for HDR videos or SDR to
// HDR videos, since there will always be a huge visual difference between
// compositor tone-mapping (by Chrome) and MPO tone-mapping (by Driver).
switch (quad->material) {
case DrawQuad::Material::kYuvVideoContent: {
const YUVVideoDrawQuad* yuv_quad = YUVVideoDrawQuad::MaterialCast(quad);
return !(yuv_quad->video_color_space.IsHDR() ||
force_overlay_for_auto_hdr);
}
case DrawQuad::Material::kTextureContent: {
const TextureDrawQuad* texture_quad = TextureDrawQuad::MaterialCast(quad);
return !(resource_provider->GetColorSpace(texture_quad->resource_id())
.IsHDR() ||
force_overlay_for_auto_hdr);
}
default:
NOTREACHED();
}
}
// This is the damage contribution due to previous frame's overlays which can
// be empty.
gfx::Rect PreviousFrameOverlayDamageContribution(
const std::vector<DCLayerOverlayProcessor::OverlayRect>&
previous_frame_overlay_rects) {
gfx::Rect rects_union;
for (const auto& overlay : previous_frame_overlay_rects) {
rects_union.Union(overlay.rect);
}
return rects_union;
}
bool IsPreviousFrameUnderlayRect(
const std::vector<DCLayerOverlayProcessor::OverlayRect>&
previous_frame_overlay_rects,
const gfx::Rect& quad_rect,
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
// previous_frame_overlay_rects[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
// damage rect in this case. Looping through the list won't give better
// power.
return (previous_frame_overlay_rects[index].rect == quad_rect) &&
(previous_frame_overlay_rects[index].is_overlay == false);
}
}
// Return value of |ValidateDrawQuad|.
struct ValidateDrawQuadResult {
DCLayerResult code = DC_LAYER_FAILED_UNSUPPORTED_QUAD;
bool is_yuv_overlay = false;
gpu::Mailbox promotion_hint_mailbox;
};
ValidateDrawQuadResult ValidateDrawQuad(
const DisplayResourceProvider* resource_provider,
const QuadList::ConstIterator& it,
const std::vector<gfx::Rect>& backdrop_filter_rects,
const bool has_overlay_support,
const bool has_p010_video_processor_support,
const int allowed_yuv_overlay_count,
const int processed_yuv_overlay_count,
const bool allow_promotion_hinting) {
ValidateDrawQuadResult result;
switch (it->material) {
case DrawQuad::Material::kYuvVideoContent:
result.code = ValidateYUVQuad(
YUVVideoDrawQuad::MaterialCast(*it), backdrop_filter_rects,
has_overlay_support, has_p010_video_processor_support,
allowed_yuv_overlay_count, processed_yuv_overlay_count,
resource_provider);
result.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.code = DC_LAYER_SUCCESS;
} else {
result.code = ValidateTextureQuad(
tex_quad, backdrop_filter_rects, has_overlay_support,
has_p010_video_processor_support, allowed_yuv_overlay_count,
processed_yuv_overlay_count, resource_provider);
}
result.is_yuv_overlay = tex_quad->is_video_frame;
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)) {
result.promotion_hint_mailbox = resource_provider->GetMailbox(id);
}
}
} break;
default:
result.code = DC_LAYER_FAILED_UNSUPPORTED_QUAD;
break;
}
return result;
}
void FromDrawQuad(const DisplayResourceProvider* resource_provider,
const AggregatedRenderPass* render_pass,
bool is_page_fullscreen_mode,
const QuadList::ConstIterator& it,
int& processed_yuv_overlay_count,
OverlayCandidate& dc_layer) {
dc_layer.possible_video_fullscreen_letterboxing =
is_page_fullscreen_mode
? IsPossibleFullScreenLetterboxing(it, render_pass->quad_list.end(),
render_pass->output_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);
if (tex_quad->is_video_frame) {
processed_yuv_overlay_count++;
}
} break;
default:
NOTREACHED_IN_MIGRATION();
}
}
} // namespace
std::optional<OverlayCandidate> DCLayerOverlayProcessor::FromTextureOrYuvQuad(
const DisplayResourceProvider* resource_provider,
const AggregatedRenderPass* render_pass,
const QuadList::ConstIterator& it,
bool is_page_fullscreen_mode) const {
// Backdrop filter occlusion is checked in |OverlayProcessorWin| via
// |OverlayCandidate::IsOccludedByFilteredQuad|, so we don't need to populate
// this vector.
const std::vector<gfx::Rect> backdrop_filter_rects;
ValidateDrawQuadResult result = ValidateDrawQuad(
resource_provider, it, backdrop_filter_rects, true,
has_p010_video_processor_support_, INT_MAX, INT_MIN, false);
if (result.code != DC_LAYER_SUCCESS) {
RecordDCLayerResult(result.code, *it);
return std::nullopt;
}
OverlayCandidate candidate;
int ignore_processed_yuv_overlay_count = 0;
FromDrawQuad(resource_provider, render_pass, is_page_fullscreen_mode, it,
ignore_processed_yuv_overlay_count, candidate);
// Once we've promoted the video as normal, add extra properties required for
// delegated compositing.
if (it->shared_quad_state->mask_filter_info.HasRoundedCorners()) {
gfx::MaskFilterInfo mask_filter_info =
it->shared_quad_state->mask_filter_info;
mask_filter_info.ApplyTransform(render_pass->transform_to_root_target);
candidate.rounded_corners = mask_filter_info.rounded_corner_bounds();
}
candidate.opacity = it->shared_quad_state->opacity;
// We don't expect quads promoted by |DCLayerOverlayProcessor| to have a
// differing |visible_rect|, but we handle it here just in case.
if (it->visible_rect != it->rect) {
// |OverlayCandidate| does not support clipping a candidate via
// |visible_rect|, but we can get the same effect by clipping its buffer via
// |uv_rect| and resizing its |display_rect|. This is similar to how
// |OverlayCandidateFactory| handles |visible_rect|.
candidate.uv_rect = gfx::MapRect(gfx::RectF(it->visible_rect),
gfx::RectF(it->rect), candidate.uv_rect);
candidate.display_rect = gfx::RectF(it->visible_rect);
}
return candidate;
}
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),
is_on_battery_power_(
base::PowerMonitor::AddPowerStateObserverAndReturnOnBatteryState(
this)),
no_undamaged_overlay_promotion_(base::FeatureList::IsEnabled(
features::kNoUndamagedOverlayPromotion)) {
if (!skip_initialization_for_testing) {
UpdateHasHwOverlaySupport();
UpdateSystemHDRStatus();
UpdateP010VideoProcessorSupport();
UpdateAutoHDRVideoProcessorSupport();
gl::DirectCompositionOverlayCapsMonitor::GetInstance()->AddObserver(this);
}
allow_promotion_hinting_ = media::SupportMediaFoundationClearPlayback();
}
DCLayerOverlayProcessor::~DCLayerOverlayProcessor() {
gl::DirectCompositionOverlayCapsMonitor::GetInstance()->RemoveObserver(this);
base::PowerMonitor::RemovePowerStateObserver(this);
}
void DCLayerOverlayProcessor::UpdateHasHwOverlaySupport() {
has_overlay_support_ = gl::DirectCompositionOverlaysSupported();
}
void DCLayerOverlayProcessor::UpdateSystemHDRStatus() {
bool hdr_enabled_on_any_display = false;
bool hdr_disabled_on_any_display = false;
auto dxgi_info = gl::GetDirectCompositionHDRMonitorDXGIInfo();
for (const auto& output_desc : dxgi_info->output_descs) {
hdr_enabled_on_any_display |= output_desc->hdr_enabled;
hdr_disabled_on_any_display |= !output_desc->hdr_enabled;
}
system_hdr_enabled_on_any_display_ = hdr_enabled_on_any_display;
// If there is no monitor connected, treat it as if there is one SDR monitor.
system_hdr_disabled_on_any_display_ =
dxgi_info->output_descs.size() > 0 ? hdr_disabled_on_any_display : true;
}
void DCLayerOverlayProcessor::UpdateP010VideoProcessorSupport() {
has_p010_video_processor_support_ =
gl::CheckVideoProcessorFormatSupport(DXGI_FORMAT_P010);
}
void DCLayerOverlayProcessor::UpdateAutoHDRVideoProcessorSupport() {
has_auto_hdr_video_processor_support_ = gl::VideoProcessorAutoHDRSupported();
}
void DCLayerOverlayProcessor::OnPowerStateChange(bool on_battery_power) {
is_on_battery_power_ = on_battery_power;
}
// Called on the Viz Compositor thread.
void DCLayerOverlayProcessor::OnOverlayCapsChanged() {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
UpdateHasHwOverlaySupport();
UpdateSystemHDRStatus();
UpdateP010VideoProcessorSupport();
UpdateAutoHDRVideoProcessorSupport();
}
void DCLayerOverlayProcessor::RemoveOverlayDamageRect(
const QuadList::Iterator& it,
RenderPassCurrentFrameState& render_pass_state) const {
// 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();
CHECK_LT(overlay_damage_index,
render_pass_state.surface_damage_rect_list.size());
render_pass_state.damages_to_be_removed.push_back(overlay_damage_index);
}
}
// This is called at the end of Process(). The goal is to get an empty damage
// rect if the overlays are the only damages in the frame.
void DCLayerOverlayProcessor::UpdateDamageRect(
AggregatedRenderPass* render_pass,
const RenderPassPreviousFrameState& previous_frame_state,
RenderPassOverlayData& overlay_data,
RenderPassCurrentFrameState& current_frame_state) const {
// 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.
const std::vector<OverlayRect>& previous_frame_overlay_rects =
previous_frame_state.overlay_rects;
bool should_add_previous_frame_overlay_damage = true;
if (!current_frame_state.overlay_rects.empty() &&
current_frame_state.overlay_rects == previous_frame_overlay_rects &&
render_pass->output_rect == previous_frame_state.display_rect) {
// 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;
// Only perform this optimization if the transform is axis aligned.
// Transforms that are not axis aligned make the original rect larger when
// the transformation is applied. Since we transform the damage rects
// between root space and render pass space (SurfaceAggregator converts
// to root space and Process() converts to render pass space), the damage
// rects will be larger than the original rects. This would result in
// subtracting a larger damage than the overlay itself.
if (render_pass->transform_to_root_target.Preserves2dAxisAlignment()) {
// The final 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 final_damage_rect;
size_t surface_index = 0;
for (auto surface_damage_rect :
current_frame_state.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.allowed_yuv_overlay_count.
for (const auto index_to_be_removed :
current_frame_state.damages_to_be_removed) {
// The overlay damages and the damages right below them will not be
// added to the 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(
current_frame_state
.surface_damage_rect_list[index_to_be_removed]);
}
}
final_damage_rect.Union(surface_damage_rect);
++surface_index;
}
overlay_data.damage_rect = final_damage_rect;
}
}
if (should_add_previous_frame_overlay_damage) {
overlay_data.damage_rect.Union(
PreviousFrameOverlayDamageContribution(previous_frame_overlay_rects));
}
overlay_data.damage_rect.Intersect(render_pass->output_rect);
current_frame_state.damages_to_be_removed.clear();
}
void DCLayerOverlayProcessor::RemoveClearVideoQuadCandidatesIfMoving(
const DisplayResourceProvider* resource_provider,
RenderPassOverlayDataMap& render_pass_overlay_data_map,
RenderPassCurrentFrameStateMap& render_pass_state_map) {
// 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_overlay_candidate_rects;
for (auto& [render_pass, overlay_data] : render_pass_overlay_data_map) {
std::vector<QuadList::Iterator>& candidates =
render_pass_state_map[render_pass].candidates;
current_overlay_candidate_rects.reserve(
current_overlay_candidate_rects.size() + candidates.size());
for (auto candidate_it : candidates) {
if (AllowRemoveClearVideoQuadCandidatesWhenMoving(
resource_provider, *candidate_it, force_overlay_for_auto_hdr())) {
gfx::Rect quad_rect_in_target_space =
ClippedQuadRectangle(*candidate_it);
gfx::Rect quad_rect_in_root_space =
cc::MathUtil::MapEnclosingClippedRect(
render_pass->transform_to_root_target,
quad_rect_in_target_space);
current_overlay_candidate_rects.push_back(quad_rect_in_root_space);
}
}
}
if (previous_frame_overlay_candidate_rects_ !=
current_overlay_candidate_rects) {
frames_since_last_overlay_candidate_rects_change_ = 0;
std::swap(previous_frame_overlay_candidate_rects_,
current_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
for (auto& [render_pass, overlay_data] : render_pass_overlay_data_map) {
std::vector<QuadList::Iterator>& candidates =
render_pass_state_map[render_pass].candidates;
auto candidate_it = candidates.begin();
while (candidate_it != candidates.end()) {
if (AllowRemoveClearVideoQuadCandidatesWhenMoving(
resource_provider, **candidate_it,
force_overlay_for_auto_hdr())) {
RecordDCLayerResult(DC_LAYER_FAILED_YUV_VIDEO_QUAD_MOVED,
**candidate_it);
candidate_it = candidates.erase(candidate_it);
} else {
candidate_it++;
}
}
}
}
}
void DCLayerOverlayProcessor::CollectCandidates(
const DisplayResourceProvider* resource_provider,
AggregatedRenderPass* render_pass,
const FilterOperationsMap& render_pass_backdrop_filters,
RenderPassOverlayData& overlay_data,
RenderPassCurrentFrameState& render_pass_state,
GlobalOverlayState& global_overlay_state) {
// 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)) {
auto it = previous_frame_render_pass_states_.find(render_pass->id);
if (it != previous_frame_render_pass_states_.end()) {
// Add any overlay damage from the previous frame. Since we're not
// promoting overlays this frame, damages that may have been removed in
// the previous frame's UpdateDamageRect() now needs to be accounted
// for.
overlay_data.damage_rect.Union(
PreviousFrameOverlayDamageContribution(it->second.overlay_rects));
previous_frame_render_pass_states_.erase(it);
}
return;
}
QuadList* quad_list = &render_pass->quad_list;
for (auto it = quad_list->begin(); it != quad_list->end(); ++it) {
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;
}
ValidateDrawQuadResult result = ValidateDrawQuad(
resource_provider, it, backdrop_filter_rects, has_overlay_support_,
has_p010_video_processor_support_, allowed_yuv_overlay_count_,
global_overlay_state.processed_yuv_overlay_count,
allow_promotion_hinting_);
if (result.is_yuv_overlay) {
global_overlay_state.yuv_quads++;
if (no_undamaged_overlay_promotion_) {
if (it->shared_quad_state->overlay_damage_index.has_value() &&
!render_pass_state
.surface_damage_rect_list[it->shared_quad_state
->overlay_damage_index.value()]
.IsEmpty()) {
global_overlay_state.damaged_yuv_quads++;
if (result.code == DC_LAYER_SUCCESS) {
global_overlay_state.processed_yuv_overlay_count++;
}
}
} else {
if (result.code == DC_LAYER_SUCCESS) {
global_overlay_state.processed_yuv_overlay_count++;
}
}
}
if (!result.promotion_hint_mailbox.IsZero()) {
DCHECK(allow_promotion_hinting_);
bool promoted = result.code == 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(result.promotion_hint_mailbox,
promoted);
}
if (result.code != DC_LAYER_SUCCESS) {
RecordDCLayerResult(result.code, *it);
continue;
}
if (!IsClearVideoQuad(*it)) {
global_overlay_state.has_non_clear_video_overlays = true;
}
render_pass_state.candidates.push_back(it);
}
}
void DCLayerOverlayProcessor::PromoteCandidates(
const DisplayResourceProvider* resource_provider,
AggregatedRenderPass* render_pass,
const FilterOperationsMap& render_pass_filters,
const RenderPassPreviousFrameState& previous_frame_state,
bool is_page_fullscreen_mode,
RenderPassOverlayData& overlay_data,
RenderPassCurrentFrameState& current_frame_state,
GlobalOverlayState& global_overlay_state) {
QuadList* quad_list = &render_pass->quad_list;
// Copy the overlay quad info to dc_layer_overlays and replace/delete overlay
// quads in quad_list.
for (auto& it : current_frame_state.candidates) {
if (global_overlay_state.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() &&
current_frame_state
.surface_damage_rect_list[it->shared_quad_state
->overlay_damage_index.value()]
.IsEmpty();
if (global_overlay_state.yuv_quads > allowed_yuv_overlay_count_ &&
!global_overlay_state.has_non_clear_video_overlays && undamaged &&
no_undamaged_overlay_promotion_ && IsVideoQuad(*it)) {
RecordDCLayerResult(DC_LAYER_FAILED_NOT_DAMAGED, *it);
continue;
}
gfx::Rect quad_rect_in_target_space = ClippedQuadRectangle(*it);
// Quad is considered an "overlay" if it has no occluders.
bool is_overlay = !IsOccluded(gfx::RectF(quad_rect_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 = IsOverlayRequiredForQuad(*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;
}
}
// Used by a histogram.
global_overlay_state.has_occluding_damage_rect =
global_overlay_state.has_occluding_damage_rect ||
(!is_overlay &&
HasOccludingDamageRect(it->shared_quad_state,
current_frame_state.surface_damage_rect_list,
quad_rect_in_target_space));
UpdateDCLayerOverlays(resource_provider, render_pass, it,
quad_rect_in_target_space, previous_frame_state,
is_overlay, is_page_fullscreen_mode, overlay_data,
current_frame_state, global_overlay_state);
}
// Update previous frame state after processing render 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.
UpdateDamageRect(render_pass, previous_frame_state, overlay_data,
current_frame_state);
RenderPassPreviousFrameState& previous_frame_render_pass_state =
previous_frame_render_pass_states_.at(render_pass->id);
std::swap(previous_frame_render_pass_state.overlay_rects,
current_frame_state.overlay_rects);
previous_frame_render_pass_state.display_rect = render_pass->output_rect;
}
void DCLayerOverlayProcessor::Process(
const DisplayResourceProvider* resource_provider,
const FilterOperationsMap& render_pass_filters,
const FilterOperationsMap& render_pass_backdrop_filters,
const SurfaceDamageRectList& surface_damage_rect_list_in_root_space,
bool is_page_fullscreen_mode,
RenderPassOverlayDataMap& render_pass_overlay_data_map) {
GlobalOverlayState global_overlay_state;
RenderPassCurrentFrameStateMap render_pass_state_map;
render_pass_state_map.reserve(render_pass_overlay_data_map.size());
for (auto& [render_pass, overlay_data] : render_pass_overlay_data_map) {
if (!render_pass->transform_to_root_target.IsInvertible()) {
// We can skip render passes that do not have an invertible transform
// since it isn't visible.
continue;
}
RenderPassCurrentFrameState& current_frame_state =
render_pass_state_map[render_pass];
// Convert the surface damage rects from root space to render pass space.
// |surface_damage_rect_list_in_root_space| contains surface damages for all
// surfaces in the frame across all render passes. We only need the surface
// damage rects for the current render pass, but since we don't expect this
// list to be large, this keeps the entire list for the simplicity.
current_frame_state.surface_damage_rect_list =
surface_damage_rect_list_in_root_space;
for (auto& rect : current_frame_state.surface_damage_rect_list) {
rect = render_pass->transform_to_root_target.InverseMapRect(rect).value();
rect.Intersect(render_pass->output_rect);
}
CollectCandidates(resource_provider, render_pass,
render_pass_backdrop_filters, overlay_data,
current_frame_state, global_overlay_state);
}
// 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
if (global_overlay_state.yuv_quads > 1 &&
!global_overlay_state.has_non_clear_video_overlays) {
if (no_undamaged_overlay_promotion_) {
if (global_overlay_state.damaged_yuv_quads ==
global_overlay_state.processed_yuv_overlay_count) {
frames_since_last_qualified_multi_overlays_++;
} else {
frames_since_last_qualified_multi_overlays_ = 0;
}
global_overlay_state.reject_overlays =
frames_since_last_qualified_multi_overlays_ <=
kDCLayerFramesDelayedBeforeOverlay;
} else {
if (global_overlay_state.yuv_quads !=
global_overlay_state.processed_yuv_overlay_count) {
global_overlay_state.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.
global_overlay_state.processed_yuv_overlay_count = 0;
if (base::FeatureList::IsEnabled(features::kDisableVideoOverlayIfMoving)) {
RemoveClearVideoQuadCandidatesIfMoving(
resource_provider, render_pass_overlay_data_map, render_pass_state_map);
}
// Swap the entire map into a local variable. For the rest of this function,
// information about the current frame is populated into the member variable,
// while the local variable is used to access information about the previous
// frame. Clearing the member variable allows us to remove render passes that
// don't exist in the current frame.
base::flat_map<AggregatedRenderPassId, RenderPassPreviousFrameState>
previous_frame_render_pass_states;
std::swap(previous_frame_render_pass_states_,
previous_frame_render_pass_states);
for (auto& [render_pass, overlay_data] : render_pass_overlay_data_map) {
// Create an entry for the current frame. This is the only place where an
// entry is created in this map.
previous_frame_render_pass_states_.emplace(render_pass->id,
RenderPassPreviousFrameState());
PromoteCandidates(resource_provider, render_pass, render_pass_filters,
previous_frame_render_pass_states[render_pass->id],
is_page_fullscreen_mode, overlay_data,
render_pass_state_map[render_pass], global_overlay_state);
}
if (global_overlay_state.processed_yuv_overlay_count > 0) {
base::UmaHistogramExactLinear(
"GPU.DirectComposition.DCLayer.YUVOverlayCount",
/*sample=*/global_overlay_state.processed_yuv_overlay_count,
/*exclusive_max=*/10);
RecordOverlayHistograms(render_pass_overlay_data_map,
global_overlay_state.has_occluding_damage_rect);
}
}
bool DCLayerOverlayProcessor::ShouldSkipOverlay(
AggregatedRenderPass* render_pass) const {
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->HasCapture()) {
// Find a valid overlay candidate from quad_list.
QuadList::Iterator it = FindAnOverlayCandidate(*quad_list);
if (it != quad_list->end()) {
render_pass->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 any
// non-HDR-enabled display exists. Technically we should use HWND detect
// if HDR is enabled on the current display or not, if it is enabled
// then promote overlay, otherwise not, but since currently we can't
// retrieve HWND in DCLayerOverlayProcessor, in case of very bad
// tone-mapping result by video processor on non-HDR-enabled display, we
// tend to be strict about the overlay promotion and always let Viz do HDR
// tone mapping to avoid a visual difference between Viz and video
// processor.
if (system_hdr_disabled_on_any_display_) {
RecordDCLayerResult(DC_LAYER_FAILED_YUV_VIDEO_QUAD_HDR_TONE_MAPPING,
*it);
return true;
}
}
}
return false;
}
void DCLayerOverlayProcessor::UpdateDCLayerOverlays(
const DisplayResourceProvider* resource_provider,
AggregatedRenderPass* render_pass,
const QuadList::Iterator& it,
const gfx::Rect& quad_rect_in_target_space,
const RenderPassPreviousFrameState& previous_frame_state,
bool is_overlay,
bool is_page_fullscreen_mode,
RenderPassOverlayData& overlay_data,
RenderPassCurrentFrameState& current_frame_state,
GlobalOverlayState& global_overlay_state) {
// Record the result first before ProcessForOverlay().
RecordDCLayerResult(DC_LAYER_SUCCESS, *it);
OverlayCandidate dc_layer;
FromDrawQuad(resource_provider, render_pass, is_page_fullscreen_mode, it,
global_overlay_state.processed_yuv_overlay_count, dc_layer);
// Underlays are less efficient, so attempt regular overlays first. We can
// only check for occlusion within a render pass.
if (is_overlay) {
ProcessForOverlay(render_pass, it, previous_frame_state,
current_frame_state);
} else {
ProcessForUnderlay(render_pass, it, quad_rect_in_target_space,
previous_frame_state, global_overlay_state, overlay_data,
current_frame_state, dc_layer);
}
current_frame_state.overlay_rects.push_back(
{quad_rect_in_target_space, is_overlay});
overlay_data.promoted_overlays.push_back(dc_layer);
// Recorded for each overlay.
UMA_HISTOGRAM_BOOLEAN("GPU.DirectComposition.IsUnderlay", !is_overlay);
}
void DCLayerOverlayProcessor::ProcessForOverlay(
AggregatedRenderPass* render_pass,
const QuadList::Iterator& it,
const RenderPassPreviousFrameState& previous_frame_state,
RenderPassCurrentFrameState& current_frame_state) const {
// The quad is on top, so promote it to an overlay and remove all damage
// underneath it.
const bool display_rect_changed =
render_pass->output_rect != previous_frame_state.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, current_frame_state);
}
// Overlay quads should not be drawn. Removing the quads from the quad list
// creates extra complexity since we would be traversing the list while
// removing them. Instead, we can just make the visible rect empty, which
// would then be skipped by DirectRenderer::ShouldSkipQuad.
it->visible_rect = gfx::Rect();
}
void DCLayerOverlayProcessor::ProcessForUnderlay(
AggregatedRenderPass* render_pass,
const QuadList::Iterator& it,
const gfx::Rect& quad_rect_in_target_space,
const RenderPassPreviousFrameState& previous_frame_state,
const GlobalOverlayState& global_overlay_state,
RenderPassOverlayData& overlay_data,
RenderPassCurrentFrameState& current_frame_state,
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. The z-orders are assigned relative to other
// underlays in its render pass, not relative to the total number of underlays
// across all render passes.
dc_layer.plane_z_order = -1 - overlay_data.promoted_overlays.size();
// 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 =
render_pass->output_rect == previous_frame_state.display_rect;
const bool underlay_rect_unchanged = IsPreviousFrameUnderlayRect(
previous_frame_state.overlay_rects, quad_rect_in_target_space,
overlay_data.promoted_overlays.size());
const bool is_axis_aligned = it->shared_quad_state->quad_to_target_transform
.Preserves2dAxisAlignment();
bool opacity_unchanged =
(is_opaque == previous_frame_state.underlay_is_opaque);
previous_frame_render_pass_states_.at(render_pass->id).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 damage rect might be empty after we remove the
// the damage from the video quad. We can save power if the damage rect is
// empty.
RemoveOverlayDamageRect(it, current_frame_state);
} else {
// Entire replacement quad must be redrawn.
overlay_data.damage_rect.Union(quad_rect_in_target_space);
current_frame_state.surface_damage_rect_list.push_back(
quad_rect_in_target_space);
}
}
DCLayerOverlayProcessor::RenderPassOverlayData::RenderPassOverlayData() =
default;
DCLayerOverlayProcessor::RenderPassOverlayData::~RenderPassOverlayData() =
default;
DCLayerOverlayProcessor::RenderPassOverlayData::RenderPassOverlayData(
RenderPassOverlayData&&) = default;
DCLayerOverlayProcessor::RenderPassOverlayData&
DCLayerOverlayProcessor::RenderPassOverlayData::operator=(
RenderPassOverlayData&&) = default;
DCLayerOverlayProcessor::RenderPassPreviousFrameState::
RenderPassPreviousFrameState() = default;
DCLayerOverlayProcessor::RenderPassPreviousFrameState::
~RenderPassPreviousFrameState() = default;
DCLayerOverlayProcessor::RenderPassPreviousFrameState::
RenderPassPreviousFrameState(RenderPassPreviousFrameState&&) = default;
DCLayerOverlayProcessor::RenderPassPreviousFrameState&
DCLayerOverlayProcessor::RenderPassPreviousFrameState::operator=(
RenderPassPreviousFrameState&&) = default;
DCLayerOverlayProcessor::RenderPassCurrentFrameState::
RenderPassCurrentFrameState() = default;
DCLayerOverlayProcessor::RenderPassCurrentFrameState::
~RenderPassCurrentFrameState() = default;
DCLayerOverlayProcessor::RenderPassCurrentFrameState::
RenderPassCurrentFrameState(RenderPassCurrentFrameState&&) = default;
DCLayerOverlayProcessor::RenderPassCurrentFrameState&
DCLayerOverlayProcessor::RenderPassCurrentFrameState::operator=(
RenderPassCurrentFrameState&&) = default;
} // namespace viz