// Copyright 2019 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "components/viz/service/display/overlay_processor_win.h"
#include <memory>
#include <optional>
#include <utility>
#include <vector>
#include "base/check.h"
#include "base/feature_list.h"
#include "base/memory/raw_ptr_exclusion.h"
#include "base/ranges/algorithm.h"
#include "base/trace_event/trace_event.h"
#include "base/types/expected.h"
#include "components/viz/common/display/renderer_settings.h"
#include "components/viz/common/features.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/service/debugger/viz_debugger.h"
#include "components/viz/service/display/display_resource_provider.h"
#include "components/viz/service/display/output_surface.h"
#include "components/viz/service/display/overlay_candidate.h"
#include "components/viz/service/display/overlay_candidate_factory.h"
#include "components/viz/service/display/overlay_processor_delegated_support.h"
#include "ui/gfx/geometry/rect_conversions.h"
namespace viz {
namespace {
constexpr int kDCLayerDebugBorderWidth = 4;
constexpr gfx::Insets kDCLayerDebugBorderInsets = gfx::Insets(-2);
// Switching between enabling DC layers and not is expensive, so only
// switch away after a large number of frames not needing DC layers have
// been produced.
constexpr int kNumberOfFramesBeforeDisablingDCLayers = 60;
// The maximum number of quads to attempt for delegated compositing. This is an
// arbitrary conservative value picked from experimentation. We don't expect to
// hit these limits in practice, but this guards against degenerate cases.
constexpr size_t kTooManyQuads = 2048;
// Rounded corners have a higher performance cost in DWM so this value is lower
// than |kTooManyQuads|.
constexpr int kTooManyQuadsWithRoundedCorners = 256;
gfx::Rect UpdateRenderPassFromOverlayData(
const DCLayerOverlayProcessor::RenderPassOverlayData& overlay_data,
AggregatedRenderPass* render_pass,
base::flat_map<AggregatedRenderPassId, int>&
frames_since_using_dc_layers_map,
const bool frame_has_delegated_ink) {
bool was_using_dc_layers =
frames_since_using_dc_layers_map.contains(render_pass->id);
// Force a swap chain when there is a copy request, since read back is
// impossible with a DComp surface.
//
// Normally, |DCLayerOverlayProcessor::Process| prevents overlays (and thus
// forces a swap chain) when there is a copy request, but
// |frames_since_using_dc_layers_map| implements a one-sided hysteresis that
// keeps us on DComp surfaces a little after we stop having overlays. If a
// client issues a copy request while we're in this timeout, we end up asking
// read back from a DComp surface, which fails later in
// |SkiaOutputSurfaceImplOnGpu::CopyOutput|.
const bool force_swap_chain_due_to_copy_request = render_pass->HasCapture();
bool using_dc_layers;
// Force DCompSurfaces during delegated ink in order to synchronize the
// delegated ink visual updates with DComp commits. Doing so eliminates the
// need to identify the correct swap chain in complicated delegated
// compositing scenarios.
if (!overlay_data.promoted_overlays.empty() ||
(frame_has_delegated_ink &&
features::ShouldUseDCompSurfacesForDelegatedInk())) {
frames_since_using_dc_layers_map[render_pass->id] = 0;
using_dc_layers = true;
} else if ((was_using_dc_layers &&
++frames_since_using_dc_layers_map[render_pass->id] >=
kNumberOfFramesBeforeDisablingDCLayers) ||
force_swap_chain_due_to_copy_request) {
frames_since_using_dc_layers_map.erase(render_pass->id);
using_dc_layers = false;
} else {
using_dc_layers = was_using_dc_layers;
}
if (using_dc_layers) {
// We have overlays, so our root surface requires a backing that
// synchronizes with DComp commit. A swap chain's Present does not
// synchronize with the DComp tree updates and would result in minor desync
// during e.g. scrolling videos.
render_pass->needs_synchronous_dcomp_commit = true;
// We only need to have a transparent backing if there's underlays, but we
// unconditionally ask for transparency to avoid thrashing allocations if a
// video alternated between overlay and underlay.
render_pass->has_transparent_background = true;
} else {
CHECK(!render_pass->needs_synchronous_dcomp_commit);
}
if (was_using_dc_layers != using_dc_layers) {
// The entire surface has to be redrawn if switching from or to direct
// composition layers, because the previous contents are discarded and some
// contents would otherwise be undefined.
return render_pass->output_rect;
} else {
// |DCLayerOverlayProcessor::Process| can modify the damage rect of the
// render pass. We don't modify the damage on the render pass directly since
// the root pass special-cases this.
return overlay_data.damage_rect;
}
}
OverlayCandidateFactory::OverlayContext WindowsDelegatedOverlayContext() {
OverlayCandidateFactory::OverlayContext context;
context.is_delegated_context = true;
context.disable_wire_size_optimization = true;
context.supports_clip_rect = true;
context.supports_out_of_window_clip_rect = true;
context.supports_arbitrary_transform = true;
context.supports_rounded_display_masks = true;
context.supports_mask_filter = true;
context.transform_and_clip_rpdq = true;
return context;
}
} // anonymous namespace
OverlayProcessorWin::OverlayProcessorWin(
OutputSurface::DCSupportLevel dc_support_level,
const DebugRendererSettings* debug_settings,
std::unique_ptr<DCLayerOverlayProcessor> dc_layer_overlay_processor)
: dc_support_level_(dc_support_level),
debug_settings_(debug_settings),
dc_layer_overlay_processor_(std::move(dc_layer_overlay_processor)) {
DCHECK_GT(dc_support_level_, OutputSurface::DCSupportLevel::kNone);
}
OverlayProcessorWin::~OverlayProcessorWin() = default;
bool OverlayProcessorWin::IsOverlaySupported() const {
return true;
}
gfx::Rect OverlayProcessorWin::GetPreviousFrameOverlaysBoundingRect() const {
if (features::IsDelegatedCompositingEnabled()) {
return previous_frame_overlay_rect_;
}
// TODO(dcastagna): Implement me.
NOTIMPLEMENTED();
return gfx::Rect();
}
gfx::Rect OverlayProcessorWin::GetAndResetOverlayDamage() {
return std::exchange(overlay_damage_rect_, gfx::Rect());
}
void OverlayProcessorWin::AdjustOutputSurfaceOverlay(
std::optional<OutputSurfaceOverlayPlane>* output_surface_plane) {
if (delegation_succeeded_last_frame_) {
output_surface_plane->reset();
}
}
void OverlayProcessorWin::ProcessForOverlays(
DisplayResourceProvider* resource_provider,
AggregatedRenderPassList* render_passes,
const SkM44& output_color_matrix,
const OverlayProcessorInterface::FilterOperationsMap& render_pass_filters,
const OverlayProcessorInterface::FilterOperationsMap&
render_pass_backdrop_filters,
SurfaceDamageRectList surface_damage_rect_list_in_root_space,
OutputSurfaceOverlayPlane* output_surface_plane,
CandidateList* candidates,
gfx::Rect* root_damage_rect,
std::vector<gfx::Rect>* content_bounds) {
TRACE_EVENT0("viz", "OverlayProcessorWin::ProcessForOverlays");
DebugLogBeforeDelegation(*root_damage_rect,
surface_damage_rect_list_in_root_space);
DelegationStatus status = ProcessOverlaysForDelegation(
resource_provider, render_passes, output_color_matrix,
render_pass_filters, render_pass_backdrop_filters,
surface_damage_rect_list_in_root_space, candidates, root_damage_rect);
if (status != DelegationStatus::kFullDelegation) {
// Fall back to promoting overlays from the output surface plane.
ProcessOverlaysFromOutputSurfacePlane(
resource_provider, render_passes, output_color_matrix,
render_pass_filters, render_pass_backdrop_filters,
surface_damage_rect_list_in_root_space, output_surface_plane,
candidates, root_damage_rect);
}
DebugLogAfterDelegation(status, *candidates, *root_damage_rect);
frame_has_delegated_ink_ = false;
delegation_succeeded_last_frame_ =
status == DelegationStatus::kFullDelegation;
}
DelegationStatus OverlayProcessorWin::ProcessOverlaysForDelegation(
DisplayResourceProvider* resource_provider,
AggregatedRenderPassList* render_passes,
const SkM44& output_color_matrix,
const OverlayProcessorInterface::FilterOperationsMap& render_pass_filters,
const OverlayProcessorInterface::FilterOperationsMap&
render_pass_backdrop_filters,
const SurfaceDamageRectList& surface_damage_rect_list_in_root_space,
CandidateList* candidates,
gfx::Rect* root_damage_rect) {
// Do not attempt delegated compositing if we do not support DComp textures
// (and therefore cannot possibly scanout quad resources) or if the feature is
// disabled.
if (dc_support_level_ < OutputSurface::DCSupportLevel::kDCompTexture ||
!features::IsDelegatedCompositingEnabled() || ForceDisableDelegation()) {
return DelegationStatus::kCompositedFeatureDisabled;
}
const bool is_full_delegated_compositing =
features::kDelegatedCompositingModeParam.Get() ==
features::DelegatedCompositingMode::kFull;
OverlayCandidateFactory factory(
render_passes->back().get(), resource_provider,
&surface_damage_rect_list_in_root_space, &output_color_matrix,
gfx::RectF(render_passes->back()->output_rect), &render_pass_filters,
WindowsDelegatedOverlayContext());
base::expected<DelegatedCompositingResult, DelegationStatus>
delegation_result = TryDelegatedCompositing(
is_full_delegated_compositing, *render_passes, factory,
render_pass_backdrop_filters, resource_provider);
if (delegation_result.has_value()) {
OverlayCandidateList delegated_candidates =
std::move(delegation_result.value().candidates);
PromotedRenderPassesInfo promoted_render_passes_info =
std::move(delegation_result.value().promoted_render_passes_info);
DCLayerOverlayProcessor::RenderPassOverlayDataMap
surface_content_render_passes =
UpdatePromotedRenderPassPropertiesAndGetSurfaceContentPasses(
is_full_delegated_compositing, *render_passes,
promoted_render_passes_info);
dc_layer_overlay_processor_->Process(
resource_provider, render_pass_filters, render_pass_backdrop_filters,
surface_damage_rect_list_in_root_space, is_page_fullscreen_mode_,
surface_content_render_passes);
// Remove entries that were not seen this frame. These counters are used
// to avoid thrashing between swap chain and DComp surface allocations,
// but are not useful when the render pass backing itself doesn't exist.
base::EraseIf(
frames_since_using_dc_layers_map_,
[&surface_content_render_passes](const auto& frames_since_kv) {
const auto& [pass_id, _num_frames] = frames_since_kv;
return base::ranges::none_of(surface_content_render_passes,
[&pass_id](const auto& overlay_data_kv) {
const auto& [pass, _data] =
overlay_data_kv;
return pass_id == pass->id;
});
});
for (auto& [render_pass, overlay_data] : surface_content_render_passes) {
render_pass->damage_rect = UpdateRenderPassFromOverlayData(
overlay_data, render_pass, frames_since_using_dc_layers_map_,
frame_has_delegated_ink_);
DBG_LOG_OPT("delegated.overlay.log", DBG_OPT_BLUE,
"Partially delegated pass{id: %llu, damage: %s}, "
"overlay_data{overlays: %zu, damage: %s}",
render_pass->id.value(),
render_pass->damage_rect.ToString().c_str(),
overlay_data.promoted_overlays.size(),
overlay_data.damage_rect.ToString().c_str());
if (debug_settings_->show_dc_layer_debug_borders) {
InsertDebugBorderDrawQuadsForOverlayCandidates(
overlay_data.promoted_overlays, render_pass,
render_pass->damage_rect);
}
}
previous_frame_overlay_rect_ =
InsertSurfaceContentOverlaysAndSetPlaneZOrder(
std::move(surface_content_render_passes), delegated_candidates);
// Set this to the full output rect unconditionally on success. This is
// unioned with the next frame's damage (via |GetAndResetOverlayDamage|)
// to fully damage the root surface if the next frame fails delegation.
// Since delegated compositing succeeded here, the previous frame's
// |overlay_damage_rect_| influence on |root_damage_rect| is cleared
// below.
// In the case of resize, we will be correctly damaged from another
// source.
overlay_damage_rect_ = render_passes->back()->output_rect;
delegation_succeeded_last_frame_ = true;
*candidates = std::move(delegated_candidates);
*root_damage_rect = gfx::Rect();
return DelegationStatus::kFullDelegation;
} else {
return delegation_result.error();
}
}
void OverlayProcessorWin::ProcessOverlaysFromOutputSurfacePlane(
DisplayResourceProvider* resource_provider,
AggregatedRenderPassList* render_passes,
const SkM44& output_color_matrix,
const OverlayProcessorInterface::FilterOperationsMap& render_pass_filters,
const OverlayProcessorInterface::FilterOperationsMap&
render_pass_backdrop_filters,
const SurfaceDamageRectList& surface_damage_rect_list_in_root_space,
OutputSurfaceOverlayPlane* output_surface_plane,
CandidateList* candidates,
gfx::Rect* root_damage_rect) {
auto* root_render_pass = render_passes->back().get();
if (render_passes->back()->is_color_conversion_pass) {
DCHECK_GT(render_passes->size(), 1u);
root_render_pass = (*render_passes)[render_passes->size() - 2].get();
}
DCLayerOverlayProcessor::RenderPassOverlayDataMap
render_pass_overlay_data_map;
auto emplace_pair = render_pass_overlay_data_map.emplace(
root_render_pass, DCLayerOverlayProcessor::RenderPassOverlayData());
DCHECK(emplace_pair.second); // Verify insertion occurred.
DCHECK_EQ(emplace_pair.first->first, root_render_pass);
DCLayerOverlayProcessor::RenderPassOverlayData&
root_render_pass_overlay_data = emplace_pair.first->second;
root_render_pass_overlay_data.damage_rect = *root_damage_rect;
dc_layer_overlay_processor_->Process(
resource_provider, render_pass_filters, render_pass_backdrop_filters,
surface_damage_rect_list_in_root_space, is_page_fullscreen_mode_,
render_pass_overlay_data_map);
if (frames_since_using_dc_layers_map_.size() > 1 ||
!frames_since_using_dc_layers_map_.contains(root_render_pass->id)) {
// We're switching off of delegated compositing or the root render pass ID
// has changed and we only expect |UpdateRenderPassFromOverlayData| to
// insert a single entry for the root pass, so we can remove all other
// entries.
frames_since_using_dc_layers_map_.clear();
}
*root_damage_rect = UpdateRenderPassFromOverlayData(
root_render_pass_overlay_data, root_render_pass,
frames_since_using_dc_layers_map_, frame_has_delegated_ink_);
*candidates = std::move(root_render_pass_overlay_data.promoted_overlays);
if (!root_render_pass->copy_requests.empty()) {
// A DComp surface is not readable by viz.
// |DCLayerOverlayProcessor::Process| should avoid overlay candidates if
// there are e.g. copy output requests present.
CHECK(!root_render_pass->needs_synchronous_dcomp_commit);
}
// |root_render_pass| will be promoted to overlay only if
// |output_surface_plane| is present.
DCHECK_NE(output_surface_plane, nullptr);
output_surface_plane->enable_blending =
root_render_pass->has_transparent_background;
if (debug_settings_->show_dc_layer_debug_borders) {
InsertDebugBorderDrawQuadsForOverlayCandidates(
*candidates, root_render_pass, *root_damage_rect);
}
}
void OverlayProcessorWin::SetFrameHasDelegatedInk() {
frame_has_delegated_ink_ = true;
}
void OverlayProcessorWin::SetUsingDCLayersForTesting(
AggregatedRenderPassId render_pass_id,
bool value) {
CHECK_IS_TEST();
if (value) {
frames_since_using_dc_layers_map_[render_pass_id] = 0;
} else {
frames_since_using_dc_layers_map_.erase(render_pass_id);
}
}
void OverlayProcessorWin::InsertDebugBorderDrawQuadsForOverlayCandidates(
const OverlayCandidateList& dc_layer_overlays,
AggregatedRenderPass* render_pass,
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.
{
SkColor4f border_color = SkColors::kGreen;
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);
}
// We assume the render pass transform is invertible, otherwise we could not
// have promoted overlays from it.
const gfx::Transform root_target_to_pass =
render_pass->transform_to_root_target.GetCheckedInverse();
// Add debug borders for overlays/underlays
for (const auto& dc_layer : dc_layer_overlays) {
gfx::Rect overlay_rect = gfx::ToEnclosingRect(
OverlayCandidate::DisplayRectInTargetSpace(dc_layer));
if (dc_layer.clip_rect) {
overlay_rect.Intersect(*dc_layer.clip_rect);
}
overlay_rect = root_target_to_pass.MapRect(overlay_rect);
// Overlay:red, Underlay:blue.
SkColor4f border_color =
dc_layer.plane_z_order > 0 ? SkColors::kRed : SkColors::kBlue;
auto it =
quad_list.InsertBeforeAndInvalidateAllPointers<DebugBorderDrawQuad>(
quad_list.begin(), 1u);
auto* debug_quad = static_cast<DebugBorderDrawQuad*>(*it);
overlay_rect.Inset(kDCLayerDebugBorderInsets);
debug_quad->SetNew(shared_quad_state, overlay_rect, overlay_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 = render_pass->output_rect;
}
bool OverlayProcessorWin::NeedsSurfaceDamageRectList() const {
return true;
}
void OverlayProcessorWin::SetIsPageFullscreen(bool enabled) {
is_page_fullscreen_mode_ = enabled;
}
OverlayProcessorWin::PromotedRenderPassesInfo::PromotedRenderPassesInfo() =
default;
OverlayProcessorWin::PromotedRenderPassesInfo::~PromotedRenderPassesInfo() =
default;
OverlayProcessorWin::PromotedRenderPassesInfo::PromotedRenderPassesInfo(
OverlayProcessorWin::PromotedRenderPassesInfo&&) = default;
OverlayProcessorWin::PromotedRenderPassesInfo&
OverlayProcessorWin::PromotedRenderPassesInfo::operator=(
OverlayProcessorWin::PromotedRenderPassesInfo&&) = default;
OverlayProcessorWin::DelegatedCompositingResult::DelegatedCompositingResult() =
default;
OverlayProcessorWin::DelegatedCompositingResult::~DelegatedCompositingResult() =
default;
OverlayProcessorWin::DelegatedCompositingResult::DelegatedCompositingResult(
OverlayProcessorWin::DelegatedCompositingResult&&) = default;
OverlayProcessorWin::DelegatedCompositingResult&
OverlayProcessorWin::DelegatedCompositingResult::operator=(
OverlayProcessorWin::DelegatedCompositingResult&&) = default;
base::expected<OverlayProcessorWin::DelegatedCompositingResult,
DelegationStatus>
OverlayProcessorWin::TryDelegatedCompositing(
bool is_full_delegated_compositing,
const AggregatedRenderPassList& render_passes,
const OverlayCandidateFactory& factory,
const OverlayProcessorInterface::FilterOperationsMap&
render_pass_backdrop_filters,
const DisplayResourceProvider* resource_provider) const {
const AggregatedRenderPass* root_render_pass = render_passes.back().get();
if (root_render_pass->HasCapture()) {
DBG_LOG_OPT(
"delegated.overlay.log", DBG_OPT_RED,
"Root pass has capture: copy_requests = %zu, video_capture_enabled "
"= %d",
root_render_pass->copy_requests.size(),
root_render_pass->video_capture_enabled);
return base::unexpected(DelegationStatus::kCompositedCopyRequest);
}
if (root_render_pass->quad_list.size() > kTooManyQuads) {
return base::unexpected(DelegationStatus::kCompositedTooManyQuads);
}
if (root_render_pass->is_color_conversion_pass) {
// We don't expect to handle a color conversion pass (e.g. for frames with
// HDR content) with delegated compositing. See: crbug.com/41497086
return base::unexpected(DelegationStatus::kCompositedOther);
}
DelegatedCompositingResult result;
result.candidates.reserve(root_render_pass->quad_list.size());
int draw_quad_rounded_corner_count = 0;
// Try to promote all the quads in the root pass to overlay.
for (auto it = root_render_pass->quad_list.begin();
it != root_render_pass->quad_list.end(); ++it) {
const DrawQuad* quad = *it;
std::optional<OverlayCandidate> dc_layer;
if (is_full_delegated_compositing) {
// Try to promote videos like DCLayerOverlay does first, then fall back to
// OverlayCandidateFactory. This is because Windows has some specific
// details on how it promotes e.g. protected videos that we want to
// preserve.
dc_layer = dc_layer_overlay_processor_->FromTextureOrYuvQuad(
resource_provider, root_render_pass, it, is_page_fullscreen_mode_);
} else {
// In the partial delegated compositing case, we don't expect
// video/canvas/etc content in the UI.
}
if (!dc_layer.has_value()) {
if (auto candidate_result =
TryPromoteDrawQuadForDelegation(factory, quad);
candidate_result.has_value()) {
if (auto& candidate = candidate_result.value()) {
dc_layer = std::move(candidate);
} else {
// This quad can be intentionally skipped.
continue;
}
} else {
return base::unexpected(candidate_result.error());
}
}
if (factory.IsOccludedByFilteredQuad(
dc_layer.value(), root_render_pass->quad_list.begin(),
root_render_pass->quad_list.end(), render_pass_backdrop_filters)) {
return base::unexpected(DelegationStatus::kCompositedBackdropFilter);
}
// Store metadata on RPDQ overlays for post-processing in
// |UpdatePromotedRenderPassProperties| to support partially delegated
// compositing.
if (dc_layer->rpdq) {
auto render_pass_it =
base::ranges::find(render_passes, dc_layer->rpdq->render_pass_id,
&AggregatedRenderPass::id);
CHECK(render_pass_it != render_passes.end());
result.promoted_render_passes_info.promoted_render_passes.insert(
raw_ref<AggregatedRenderPass>::from_ptr(render_pass_it->get()));
result.promoted_render_passes_info.promoted_rpdqs.push_back(
raw_ref<const AggregatedRenderPassDrawQuad>::from_ptr(
dc_layer->rpdq.get()));
}
result.candidates.push_back(std::move(dc_layer).value());
const auto& candidate = result.candidates.back();
if (!candidate.rounded_corners.IsEmpty()) {
draw_quad_rounded_corner_count++;
if (draw_quad_rounded_corner_count > kTooManyQuadsWithRoundedCorners) {
return base::unexpected(DelegationStatus::kCompositedTooManyQuads);
}
}
}
return base::ok(std::move(result));
}
// static
DCLayerOverlayProcessor::RenderPassOverlayDataMap OverlayProcessorWin::
UpdatePromotedRenderPassPropertiesAndGetSurfaceContentPasses(
bool is_full_delegated_compositing,
const AggregatedRenderPassList& render_passes,
const PromotedRenderPassesInfo& promoted_render_passes_info) {
struct Embedder {
// RAW_PTR_EXCLUSION: Stack-scoped.
RAW_PTR_EXCLUSION const AggregatedRenderPassDrawQuad* rpdq = nullptr;
bool is_overlay = false;
};
// Returns true if the |render_pass| or a RPDQ that embeds it will require viz
// to read the render pass' backing to compose the frame.
const auto BackingWillBeReadInViz =
[](const AggregatedRenderPass& render_pass,
const std::vector<Embedder>& embedders) {
if (render_pass.HasCapture()) {
return true;
}
// Filters require an intermediate surface to be applied.
if (!render_pass.filters.IsEmpty() ||
!render_pass.backdrop_filters.IsEmpty()) {
return true;
}
// Resolving mipmaps requires reading the backing.
if (render_pass.generate_mipmap) {
return true;
}
// Check if any embedders need to read the backing.
if (base::ranges::any_of(embedders, [](const auto& embedder) {
if (!embedder.is_overlay) {
// Non-overlay embedders need to be read in viz
return true;
}
if (!embedder.rpdq->mask_resource_id().is_null() ||
embedder.rpdq->shared_quad_state->mask_filter_info
.HasGradientMask()) {
return true;
}
return false;
})) {
return true;
}
return false;
};
// The root render pass will never have embedders, but may e.g. have a copy
// request that requires it to be read.
render_passes.back()->will_backing_be_read_by_viz =
BackingWillBeReadInViz(*render_passes.back().get(), {});
if (promoted_render_passes_info.promoted_render_passes.empty()) {
return {};
}
// A map that give us backwards pointers from a render pass overlay to its
// embedders.
base::flat_map<AggregatedRenderPassId, std::vector<Embedder>> embedders;
for (const auto& pass : render_passes) {
if (pass == render_passes.front()) {
// The first pass cannot embed other render passes.
continue;
}
for (const auto* quad : pass->quad_list) {
if (const auto* rpdq =
quad->DynamicCast<AggregatedRenderPassDrawQuad>()) {
auto it = base::ranges::find(
promoted_render_passes_info.promoted_render_passes,
rpdq->render_pass_id, &AggregatedRenderPass ::id);
if (it == promoted_render_passes_info.promoted_render_passes.end()) {
// We don't need to track embedders of render passes that are not
// going to overlay since we can assume those will be read by viz.
continue;
}
embedders[(*it)->id].push_back(Embedder{
.rpdq = rpdq,
.is_overlay = base::ranges::find(
promoted_render_passes_info.promoted_rpdqs, rpdq,
[](const auto& rpdq) { return &rpdq.get(); }) !=
promoted_render_passes_info.promoted_rpdqs.end(),
});
}
}
}
DCLayerOverlayProcessor::RenderPassOverlayDataMap
surface_content_render_passes;
for (auto render_pass : promoted_render_passes_info.promoted_render_passes) {
render_pass->will_backing_be_read_by_viz =
BackingWillBeReadInViz(render_pass.get(), embedders[render_pass->id]);
// If we're in partial delegation, we want to promote video quads out of
// e.g. web contents surfaces as if they were the root surface.
if (!is_full_delegated_compositing &&
render_pass->is_from_surface_root_pass) {
DCLayerOverlayProcessor::RenderPassOverlayData overlay_data;
overlay_data.damage_rect = render_pass->damage_rect;
surface_content_render_passes.insert(
{&render_pass.get(), std::move(overlay_data)});
} else {
render_pass->needs_synchronous_dcomp_commit = true;
}
}
// If we are not doing partial delegation, we don't expect any surface content
// render passes.
CHECK(!is_full_delegated_compositing ||
surface_content_render_passes.empty());
return surface_content_render_passes;
}
// static
gfx::Rect OverlayProcessorWin::InsertSurfaceContentOverlaysAndSetPlaneZOrder(
DCLayerOverlayProcessor::RenderPassOverlayDataMap
surface_content_render_passes,
OverlayCandidateList& candidates) {
gfx::Rect overlay_union_rect;
// Returns the entry in |surface_content_render_passes| corresponding to
// |candidate| if it is a RPDQ candidate that had child overlays promoted from
// it. Returns nullptr otherwise.
const auto TryGetSurfaceContentOverlayData =
[](DCLayerOverlayProcessor::RenderPassOverlayDataMap&
surface_content_render_passes,
const OverlayCandidate& candidate)
-> DCLayerOverlayProcessor::RenderPassOverlayDataMap::value_type* {
if (candidate.rpdq) {
if (auto it = base::ranges::find(
surface_content_render_passes, candidate.rpdq->render_pass_id,
[](const auto& kv) { return kv.first->id; });
it != surface_content_render_passes.end()) {
if (!it->second.promoted_overlays.empty()) {
return &*it;
} else {
// If the surface had no promoted overlays, we don't need to process
// them.
}
} else {
// RPDQ was not from a surface quad and therefore not a candidate for
// overlay promotion.
}
}
return nullptr;
};
// Assign properties on |child| that can be inherited from |rpdq_parent|, such
// as clip rect(s).
const auto InheritOverlayPropertiesFromParent =
[](const gfx::Rect& surface_bounds_in_root,
const OverlayCandidate& rpdq_parent, OverlayCandidate& child) {
// Ensure that the candidate is contained by the surface it was promoted
// from.
gfx::Rect candidate_clip = surface_bounds_in_root;
if (rpdq_parent.clip_rect.has_value()) {
// If the parent has a clip rect, let this candidate inherit that clip
// rect.
candidate_clip.Intersect(rpdq_parent.clip_rect.value());
}
if (child.clip_rect) {
child.clip_rect->Intersect(candidate_clip);
} else {
child.clip_rect = candidate_clip;
}
// If the parent has rounded corners, let this candidate inherit that
// rounded corner clip so it will be correctly clipped if it's
// positioned at one of the corners.
if (!rpdq_parent.rounded_corners.IsEmpty()) {
// We don't expect |DCLayerOverlayProcessor| to set the rounded
// corners of its candidates.
CHECK(child.rounded_corners.IsEmpty());
// The rounded corners from the original quad are painted into its
// parent surface, making it safe for us to use the candidates'
// rounded corners to store its parent's rounded corners.
child.rounded_corners = rpdq_parent.rounded_corners;
}
};
// We inserted into candidates in front-to-back order, but |plane_z_order|s
// increment back-to-front, so we want to invert the iteration so we can
// insert in ascending z-order.
int current_z_index = 1;
// We don't use an iterator since we're pushing to the end of |candidates|
// during our iteration, which may invalidate iterators.
for (int rpdq_index = candidates.size() - 1; rpdq_index >= 0; rpdq_index--) {
auto* surface_content_overlay_data = TryGetSurfaceContentOverlayData(
surface_content_render_passes, candidates[rpdq_index]);
if (!surface_content_overlay_data) {
// This is a regular delegated overlay candidate, assign it the next
// z-index and move on.
candidates[rpdq_index].plane_z_order = current_z_index++;
continue;
}
// |candidates[rpdq_index]| is a RPDQ candidate with child overlays (e.g.
// videos, canvas, etc). We need to add the child overlays to |candidates|
// and assign them z-indexes relative to their parent RPDQ candidate. In
// back-to-front order, we will assign:
// 1. z-indexes for the underlays
// 2. a z-index for the RPDQ candidate itself
// 3. and z-indexes for the overlays.
auto& [render_pass, overlay_data] = *surface_content_overlay_data;
// Sort the child overlays so we can iterate them back-to-front.
base::ranges::sort(overlay_data.promoted_overlays, base::ranges::less(),
&OverlayCandidate::plane_z_order);
const gfx::Rect surface_bounds_in_root = gfx::ToRoundedRect(
OverlayCandidate::DisplayRectInTargetSpace(candidates[rpdq_index]));
bool rpdq_handled = false;
candidates.reserve(candidates.size() +
overlay_data.promoted_overlays.size());
for (auto& overlay : overlay_data.promoted_overlays) {
CHECK_NE(overlay.plane_z_order, 0);
if (!rpdq_handled && overlay.plane_z_order > 0) {
// Assign the current z-index to the RPDQ candidate to place it between
// its underlays and overlays.
candidates[rpdq_index].plane_z_order = current_z_index++;
rpdq_handled = true;
}
candidates.push_back(std::move(overlay));
// Overwrite the previous |plane_z_order| that was relative to the RPDQ
// candidate with a z-index relative to the full candidates list.
candidates.back().plane_z_order = current_z_index++;
InheritOverlayPropertiesFromParent(surface_bounds_in_root,
/*rpdq_parent=*/candidates[rpdq_index],
/*child=*/candidates.back());
overlay_union_rect.Union(gfx::ToEnclosingRect(
OverlayCandidate::DisplayRectInTargetSpace(overlay)));
}
if (!rpdq_handled) {
// Handle fencepost problem: insert the RPDQ candidate in the case that
// there were only child underlays.
candidates[rpdq_index].plane_z_order = current_z_index++;
}
}
return overlay_union_rect;
}
// static
gfx::Rect
OverlayProcessorWin::InsertSurfaceContentOverlaysAndSetPlaneZOrderForTesting(
DCLayerOverlayProcessor::RenderPassOverlayDataMap
surface_content_render_passes,
OverlayCandidateList& candidates) {
CHECK_IS_TEST();
return InsertSurfaceContentOverlaysAndSetPlaneZOrder(
std::move(surface_content_render_passes), candidates);
}
} // namespace viz