blob: 8352e0322d6fa36d9ea8d892f07e91ba2a3f6852 [file]
// 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 "ui/gl/dc_layer_tree.h"
#include <d3d11_1.h>
#include <dxgi.h>
#include <utility>
#include "base/check_is_test.h"
#include "base/command_line.h"
#include "base/feature_list.h"
#include "base/logging.h"
#include "base/metrics/histogram_functions.h"
#include "base/metrics/histogram_macros.h"
#include "base/trace_event/trace_event.h"
#include "base/types/expected.h"
#include "base/types/expected_macros.h"
#include "ui/gfx/color_space_win.h"
#include "ui/gfx/geometry/rect_conversions.h"
#include "ui/gfx/geometry/transform_util.h"
#include "ui/gfx/overlay_layer_id.h"
#include "ui/gl/direct_composition_support.h"
#include "ui/gl/gl_switches.h"
#include "ui/gl/solid_color_pool_base.h"
#include "ui/gl/swap_chain_presenter.h"
namespace gl {
namespace {
constexpr size_t kVideoProcessorDimensionsWindowSize = 100;
bool NeedSwapChainPresenter(const DCLayerOverlayParams& overlay) {
return overlay.overlay_image && overlay.overlay_image->type() !=
DCLayerOverlayType::kDCompVisualContent;
}
// Unconditionally get a IDCompositionVisual2 as a IDCompositionVisual3.
//
// |IDCompositionVisual3| should be available since Windows 8.1, but we noticed
// crashes due to unconditionally casting to the interface on the earliest
// versions of Windows 10. This should only be used for features that are
// conditionally run above those versions of Windows.
//
// See: https://crbug.com/1455666
Microsoft::WRL::ComPtr<IDCompositionVisual3> CheckedCastToVisual3(
const Microsoft::WRL::ComPtr<IDCompositionVisual2>& visual2) {
Microsoft::WRL::ComPtr<IDCompositionVisual3> visual3;
HRESULT hr = visual2.As(&visual3);
CHECK_EQ(hr, S_OK);
CHECK(visual3);
return visual3;
}
D2D_MATRIX_3X2_F TransformToD2D_MATRIX_3X2_F(const gfx::Transform& transform) {
DCHECK(transform.Is2dTransform());
// See |TransformToD2D_MATRIX_4X4_F| for notes.
return D2D1::Matrix3x2F(transform.rc(0, 0), transform.rc(1, 0),
transform.rc(0, 1), transform.rc(1, 1),
transform.rc(0, 3), transform.rc(1, 3));
}
D2D_MATRIX_4X4_F TransformToD2D_MATRIX_4X4_F(const gfx::Transform& transform) {
// D2D matrices are stored with the translation portion in the last row,
// whereas Skia matrices are stored with the translation in the last column.
// We need to transpose the matrix during the conversion to account for this
// difference.
const gfx::Transform& t = transform;
return D2D1::Matrix4x4F(t.rc(0, 0), t.rc(1, 0), t.rc(2, 0), t.rc(3, 0),
t.rc(0, 1), t.rc(1, 1), t.rc(2, 1), t.rc(3, 1),
t.rc(0, 2), t.rc(1, 2), t.rc(2, 2), t.rc(3, 2),
t.rc(0, 3), t.rc(1, 3), t.rc(2, 3), t.rc(3, 3));
}
#if DCHECK_IS_ON()
bool VisualTreeValid(
std::vector<std::optional<size_t>>& subtree_index_to_overlay,
const std::vector<bool>& prev_subtree_is_attached_to_root) {
for (size_t i = 0; i < subtree_index_to_overlay.size(); i++) {
// Unused subtrees must be removed from the root.
if (!subtree_index_to_overlay[i] && prev_subtree_is_attached_to_root[i]) {
return false;
}
}
return true;
}
#endif // DCHECK_IS_ON()
} // namespace
VideoProcessorWrapper::VideoProcessorWrapper() = default;
VideoProcessorWrapper::~VideoProcessorWrapper() = default;
VideoProcessorWrapper::SizeSmoother::SizeSmoother()
: width_(kVideoProcessorDimensionsWindowSize),
height_(kVideoProcessorDimensionsWindowSize) {}
VideoProcessorWrapper::SizeSmoother::~SizeSmoother() = default;
void VideoProcessorWrapper::SizeSmoother::SizeSmoother::PutSize(
gfx::Size size) {
width_.AddSample(size.width());
height_.AddSample(size.height());
}
gfx::Size VideoProcessorWrapper::SizeSmoother::GetSize() const {
return gfx::Size(width_.Max(), height_.Max());
}
DCLayerTree::DCLayerTree(bool disable_nv12_dynamic_textures,
bool disable_vp_auto_hdr,
bool disable_vp_scaling,
bool disable_vp_super_resolution,
bool disable_dc_letterbox_video_optimization,
bool force_dcomp_triple_buffer_video_swap_chain,
bool no_downscaled_overlay_promotion)
: disable_nv12_dynamic_textures_(disable_nv12_dynamic_textures),
disable_vp_auto_hdr_(disable_vp_auto_hdr),
disable_vp_scaling_(disable_vp_scaling),
disable_vp_super_resolution_(disable_vp_super_resolution),
disable_dc_letterbox_video_optimization_(
disable_dc_letterbox_video_optimization),
force_dcomp_triple_buffer_video_swap_chain_(
force_dcomp_triple_buffer_video_swap_chain),
no_downscaled_overlay_promotion_(no_downscaled_overlay_promotion),
tint_video_layer_(base::CommandLine::ForCurrentProcess()->HasSwitch(
switches::kTintDcLayer)),
ink_renderer_(std::make_unique<DelegatedInkRenderer>()) {}
DCLayerTree::~DCLayerTree() = default;
void DCLayerTree::Initialize(
HWND window,
Microsoft::WRL::ComPtr<ID3D11Device> d3d11_device) {
window_ = window;
DCHECK(window_);
d3d11_device_ = std::move(d3d11_device);
DCHECK(d3d11_device_);
dcomp_device_ = GetDirectCompositionDevice();
DCHECK(dcomp_device_);
d3d12_command_queue_ = GetDirectCompositionD3D12CommandQueue();
if (d3d12_command_queue_) {
Microsoft::WRL::ComPtr<EXPERIMENTAL_IDCompositionDevice6> dcomp_device6;
HRESULT hr = dcomp_device_.As(&dcomp_device6);
CHECK_EQ(hr, S_OK);
dcomp_device_6_ = std::move(dcomp_device6);
}
SolidColorPoolFactory factory = GetDirectCompositionSolidColorPoolFactory();
CHECK(factory) << "DCLayerTree requires a solid-color content provider "
"factory; one must be registered via "
"InitializeDirectComposition.";
// The factory captures whatever GPU handles it needs (e.g. the Dawn
// `wgpu::Device` and `ID3D12CommandQueue`, or an `ID3D11Device`) by value
// at GPU service startup, so we just hand it the DComp device.
solid_color_content_provider_ = factory.Run(dcomp_device_.Get());
CHECK(solid_color_content_provider_);
Microsoft::WRL::ComPtr<IDCompositionDesktopDevice> desktop_device;
dcomp_device_.As(&desktop_device);
DCHECK(desktop_device);
HRESULT hr =
desktop_device->CreateTargetForHwnd(window_, TRUE, &dcomp_target_);
// |CreateTargetForHwnd| can fail if |window_| belongs to a different process
// (DCOMPOSITION_ERROR_ACCESS_DENIED) or we have already called
// |CreateTargetForHwnd| for this window
// (DCOMPOSITION_ERROR_WINDOW_ALREADY_COMPOSED). We don't expect either to be
// the case here.
CHECK_EQ(hr, S_OK);
hr = dcomp_device_->CreateVisual(&dcomp_root_visual_);
CHECK_EQ(hr, S_OK);
if (base::FeatureList::IsEnabled(features::kDCompDebugVisualization)) {
Microsoft::WRL::ComPtr<IDCompositionDeviceDebug> debug_device;
hr = dcomp_device_.As(&debug_device);
CHECK_EQ(hr, S_OK);
CHECK(debug_device);
DLOG(WARNING) << "DComp debug counters enabled, visible in the top right.";
DLOG(WARNING) << " - left: The composition engine FPS, averaged over the "
"last 60 composition frames";
DLOG(WARNING) << " - right: The overall CPU usage of the composition "
"thread, in milliseconds";
hr = debug_device->EnableDebugCounters();
CHECK_EQ(hr, S_OK);
Microsoft::WRL::ComPtr<IDCompositionVisualDebug> debug_visual;
hr = dcomp_root_visual_.As(&debug_visual);
CHECK_EQ(hr, S_OK);
CHECK(debug_visual);
hr = debug_visual->EnableRedrawRegions();
CHECK_EQ(hr, S_OK);
}
dcomp_target_->SetRoot(dcomp_root_visual_.Get());
// A visual inherits the interpolation mode of the parent visual by default.
// If no visuals set the interpolation mode, the default for the entire visual
// tree is nearest neighbor interpolation.
// Set the interpolation mode to Linear to get a better upscaling quality.
dcomp_root_visual_->SetBitmapInterpolationMode(
DCOMPOSITION_BITMAP_INTERPOLATION_MODE_LINEAR);
hdr_metadata_helper_ = HDRMetadataHelperWin::Create();
if (Microsoft::WRL::ComPtr<IDCompositionDevice5> dcomp_device5;
SUCCEEDED(dcomp_device_.As(&dcomp_device5))) {
hr = dcomp_device5->CreateDynamicTexture(&primary_plane_surface_);
if (FAILED(hr)) {
LOG(WARNING) << "Failed to create IDCompositionDynamicTexture: "
<< logging::SystemErrorCodeToString(hr);
}
}
}
base::expected<VideoProcessorWrapper*, CommitError>
DCLayerTree::InitializeVideoProcessor(const gfx::Size& input_size,
const gfx::Size& output_size,
bool is_hdr_output,
bool& video_processor_recreated) {
video_processor_recreated = false;
auto& video_processor_wrapper = is_hdr_output ? video_processor_wrapper_hdr_
: video_processor_wrapper_sdr_;
if (!video_processor_wrapper.video_device) {
// This can fail for software devices such as WARP or Microsoft Basic
// Display Adapter.
HRESULT hr = d3d11_device_.As(&video_processor_wrapper.video_device);
if (FAILED(hr)) {
DisableDirectCompositionOverlays();
return base::unexpected(
CommitError{CommitError::Reason::
kInitializeVideoProcessorD3D11DeviceAsVideoDevice,
hr});
}
DCHECK(video_processor_wrapper.video_device);
Microsoft::WRL::ComPtr<ID3D11DeviceContext> context;
d3d11_device_->GetImmediateContext(&context);
DCHECK(context);
CHECK_EQ(context.As(&video_processor_wrapper.video_context), S_OK);
}
// Calculate input and output size to be maximum in a sliding window.
video_processor_wrapper.input_size_smoother.PutSize(input_size);
video_processor_wrapper.output_size_smoother.PutSize(output_size);
gfx::Size effective_input_size =
video_processor_wrapper.input_size_smoother.GetSize();
gfx::Size effective_output_size =
video_processor_wrapper.output_size_smoother.GetSize();
// Reuse existing video processor only if it has exactly the computed size.
// Even if it may have bigger dimensions and may be reusable for requested
// sizes we will recreate it to reduce resource usage. Sliding window max
// above guarantees that this reduction will only happen after prolonged usage
// with smaller texture sizes.
if (video_processor_wrapper.video_processor &&
video_processor_wrapper.video_input_size == effective_input_size &&
video_processor_wrapper.video_output_size == effective_output_size) {
return &video_processor_wrapper;
}
TRACE_EVENT2("gpu", "DCLayerTree::InitializeVideoProcessor", "input_size",
input_size.ToString(), "output_size", output_size.ToString());
video_processor_wrapper.video_input_size = effective_input_size;
video_processor_wrapper.video_output_size = effective_output_size;
video_processor_wrapper.video_processor.Reset();
video_processor_wrapper.video_processor_enumerator.Reset();
D3D11_VIDEO_PROCESSOR_CONTENT_DESC desc = {};
desc.InputFrameFormat = D3D11_VIDEO_FRAME_FORMAT_PROGRESSIVE;
desc.InputFrameRate.Numerator = 60;
desc.InputFrameRate.Denominator = 1;
desc.InputWidth = input_size.width();
desc.InputHeight = input_size.height();
desc.OutputFrameRate.Numerator = 60;
desc.OutputFrameRate.Denominator = 1;
desc.OutputWidth = output_size.width();
desc.OutputHeight = output_size.height();
desc.Usage = D3D11_VIDEO_USAGE_PLAYBACK_NORMAL;
HRESULT hr =
video_processor_wrapper.video_device->CreateVideoProcessorEnumerator(
&desc, &video_processor_wrapper.video_processor_enumerator);
if (FAILED(hr)) {
// It might fail again next time. Disable overlay support so
// overlay processor will stop sending down overlay frames.
DisableDirectCompositionOverlays();
return base::unexpected(
CommitError{CommitError::Reason::
kInitializeVideoProcessorCreateVideoProcessorEnumerator,
hr});
}
hr = video_processor_wrapper.video_device->CreateVideoProcessor(
video_processor_wrapper.video_processor_enumerator.Get(), 0,
&video_processor_wrapper.video_processor);
if (FAILED(hr)) {
// It might fail again next time. Disable overlay support so
// overlay processor will stop sending down overlay frames.
DisableDirectCompositionOverlays();
return base::unexpected(CommitError{
CommitError::Reason::kInitializeVideoProcessorCreateVideoProcessor,
hr});
}
// Auto stream processing (the default) can hurt power consumption.
video_processor_wrapper.video_context
->VideoProcessorSetStreamAutoProcessingMode(
video_processor_wrapper.video_processor.Get(), 0, FALSE);
video_processor_recreated = true;
return &video_processor_wrapper;
}
IDXGISwapChain1* DCLayerTree::GetLayerSwapChainForTesting(
const gfx::OverlayLayerId& layer_id) const {
CHECK_IS_TEST();
if (video_swap_chains_.contains(layer_id)) {
return video_swap_chains_.at(layer_id)->swap_chain().Get();
}
return nullptr;
}
DCLayerTree::VisualTree::VisualSubtree*
DCLayerTree::GetFrontMostVideoVisualSubtreeForTesting() const {
CHECK_IS_TEST();
VisualTree::VisualSubtree* front_sub_tree =
visual_tree_->GetFrontMostVisualSubtreeForTesting(); // IN-TEST
// `dcomp_visual_content` on front-most subtree should match
// SwapChainPresenter::content() in `video_swap_chains`
for (const auto& video_swap_chain : video_swap_chains_) {
const auto& swap_chain_presenter = video_swap_chain.second;
if (swap_chain_presenter->content_for_testing().Get() == // IN-TEST
front_sub_tree->dcomp_visual_content()) {
return front_sub_tree;
}
}
return nullptr;
}
DCLayerTree::VisualTree::VisualSubtree::VisualSubtree() = default;
DCLayerTree::VisualTree::VisualSubtree::~VisualSubtree() {
if (content_visual_) {
// Explicitly null out the `content_visual_`'s content to ensure there are
// no unexpected references to e.g. `IDCompositionTexture`, in case there
// are lingering references to `content_visual_`.
HRESULT hr = content_visual_->SetContent(nullptr);
CHECK_EQ(S_OK, hr);
}
if (background_color_visual_) {
// Null out the content pointer to remove this reference to the
// IDCompositionTexture to make the availability fence available immediately
// after Commit, rather than some time later.
HRESULT hr = background_color_visual_->SetContent(nullptr);
CHECK_EQ(S_OK, hr);
}
}
bool DCLayerTree::VisualTree::VisualSubtree::Update(
IDCompositionDevice3* dcomp_device,
Microsoft::WRL::ComPtr<IUnknown> dcomp_visual_content,
uint64_t dcomp_surface_serial,
const gfx::Size& image_size,
const gfx::RectF& content_rect,
Microsoft::WRL::ComPtr<IUnknown> background_color_content,
const SkColor4f& background_color,
const gfx::Rect& quad_rect,
bool nearest_neighbor_filter,
const gfx::Transform& quad_to_root_transform,
const gfx::RRectF& rounded_corner_bounds,
float opacity,
const std::optional<gfx::Rect>& clip_rect_in_root,
bool allow_antialiasing) {
bool needs_commit = false;
// Helper function to set |field| to |parameter| and return whether it
// changed.
auto SetField = [&needs_commit](auto& field, auto& parameter) -> bool {
const bool changed = field != parameter;
if (changed) {
field = std::move(parameter);
// We assume that any change to the input of |Update| will result in some
// visual property change that requires a commit. If this is not true, an
// input is not needed.
needs_commit = true;
}
return changed;
};
// Fields on |VisualSubtree| should map 1:1 with parameters to |Update| (with
// the exception of the DComp device pointer, DComp visuals, and Z-order). To
// avoid issues with incremental computation, set fields to input parameters
// here with the helper function and read the member fields below only if
// guarded by the corresponding |*_changed| variable.
const bool dcomp_visual_content_changed =
SetField(dcomp_visual_content_, dcomp_visual_content);
const bool dcomp_surface_serial_changed =
SetField(dcomp_surface_serial_, dcomp_surface_serial);
const bool image_size_changed = SetField(image_size_, image_size);
const bool content_rect_changed = SetField(content_rect_, content_rect);
const bool background_color_content_changed =
SetField(background_color_content_, background_color_content);
const bool background_color_changed =
SetField(background_color_, background_color);
const bool quad_rect_changed = SetField(quad_rect_, quad_rect);
const bool nearest_neighbor_filter_changed =
SetField(nearest_neighbor_filter_, nearest_neighbor_filter);
const bool quad_to_root_transform_changed =
SetField(quad_to_root_transform_, quad_to_root_transform);
const bool rounded_corner_bounds_changed =
SetField(rounded_corner_bounds_, rounded_corner_bounds);
const bool opacity_changed = SetField(opacity_, opacity);
const bool clip_rect_in_root_changed =
SetField(clip_rect_in_root_, clip_rect_in_root);
const bool allow_antialiasing_changed =
SetField(allow_antialiasing_, allow_antialiasing);
// Methods that update the visual tree can only fail with OOM. We'll assert
// success in this function to aid in debugging.
HRESULT hr = S_OK;
// All the visual are created together on the first |Update|.
if (!clip_visual_) {
needs_commit = true;
CHECK(!rounded_corners_visual_);
CHECK(!transform_visual_);
CHECK(!background_color_visual_);
CHECK(!content_visual_);
hr = dcomp_device->CreateVisual(&clip_visual_);
CHECK_EQ(hr, S_OK);
hr = dcomp_device->CreateVisual(&rounded_corners_visual_);
CHECK_EQ(hr, S_OK);
hr = dcomp_device->CreateVisual(&transform_visual_);
CHECK_EQ(hr, S_OK);
hr = dcomp_device->CreateVisual(&background_color_visual_);
CHECK_EQ(hr, S_OK);
hr = dcomp_device->CreateVisual(&content_visual_);
CHECK_EQ(hr, S_OK);
hr = clip_visual_->AddVisual(rounded_corners_visual_.Get(), FALSE, nullptr);
CHECK_EQ(hr, S_OK);
hr = rounded_corners_visual_->AddVisual(transform_visual_.Get(), FALSE,
nullptr);
CHECK_EQ(hr, S_OK);
hr = transform_visual_->AddVisual(background_color_visual_.Get(), FALSE,
nullptr);
CHECK_EQ(hr, S_OK);
hr = transform_visual_->AddVisual(content_visual_.Get(), FALSE, nullptr);
CHECK_EQ(hr, S_OK);
// The default state for the border mode is INHERIT, so we need to force it
// to HARD.
hr = transform_visual_->SetBorderMode(DCOMPOSITION_BORDER_MODE_HARD);
CHECK_EQ(hr, S_OK);
}
if (clip_rect_in_root_changed) {
if (clip_rect_in_root_.has_value()) {
// DirectComposition clips happen in the pre-transform visual space, while
// cc/ clips happen post-transform. So the clip needs to go on a separate
// parent visual that's untransformed.
const gfx::Rect& clip_rect = clip_rect_in_root_.value();
hr = clip_visual_->SetClip(D2D1::RectF(
clip_rect.x(), clip_rect.y(), clip_rect.right(), clip_rect.bottom()));
CHECK_EQ(hr, S_OK);
} else {
hr = clip_visual_->SetClip(nullptr);
CHECK_EQ(hr, S_OK);
}
}
if (opacity_changed) {
if (opacity_ != 1) {
hr = CheckedCastToVisual3(clip_visual_)->SetOpacity(opacity_);
CHECK_EQ(hr, S_OK);
// Let all of this subtree's visuals blend as one, instead of
// individually
hr = clip_visual_->SetOpacityMode(DCOMPOSITION_OPACITY_MODE_LAYER);
CHECK_EQ(hr, S_OK);
} else {
hr = CheckedCastToVisual3(clip_visual_)->SetOpacity(1.0);
CHECK_EQ(hr, S_OK);
hr = clip_visual_->SetOpacityMode(DCOMPOSITION_OPACITY_MODE_MULTIPLY);
CHECK_EQ(hr, S_OK);
}
}
if (rounded_corner_bounds_changed) {
if (!rounded_corner_bounds_.IsEmpty()) {
Microsoft::WRL::ComPtr<IDCompositionRectangleClip> clip;
hr = dcomp_device->CreateRectangleClip(&clip);
CHECK_EQ(hr, S_OK);
CHECK(clip);
const gfx::RectF rect = rounded_corner_bounds_.rect();
hr = clip->SetLeft(rect.x());
CHECK_EQ(hr, S_OK);
hr = clip->SetRight(rect.right());
CHECK_EQ(hr, S_OK);
hr = clip->SetBottom(rect.bottom());
CHECK_EQ(hr, S_OK);
hr = clip->SetTop(rect.y());
CHECK_EQ(hr, S_OK);
const gfx::Vector2dF top_left = rounded_corner_bounds_.GetCornerRadii(
gfx::RRectF::Corner::kUpperLeft);
hr = clip->SetTopLeftRadiusX(top_left.x());
CHECK_EQ(hr, S_OK);
hr = clip->SetTopLeftRadiusY(top_left.y());
CHECK_EQ(hr, S_OK);
const gfx::Vector2dF top_right = rounded_corner_bounds_.GetCornerRadii(
gfx::RRectF::Corner::kUpperRight);
hr = clip->SetTopRightRadiusX(top_right.x());
CHECK_EQ(hr, S_OK);
hr = clip->SetTopRightRadiusY(top_right.y());
CHECK_EQ(hr, S_OK);
const gfx::Vector2dF bottom_left = rounded_corner_bounds_.GetCornerRadii(
gfx::RRectF::Corner::kLowerLeft);
hr = clip->SetBottomLeftRadiusX(bottom_left.x());
CHECK_EQ(hr, S_OK);
hr = clip->SetBottomLeftRadiusY(bottom_left.y());
CHECK_EQ(hr, S_OK);
const gfx::Vector2dF bottom_right = rounded_corner_bounds_.GetCornerRadii(
gfx::RRectF::Corner::kLowerRight);
hr = clip->SetBottomRightRadiusX(bottom_right.x());
CHECK_EQ(hr, S_OK);
hr = clip->SetBottomRightRadiusY(bottom_right.y());
CHECK_EQ(hr, S_OK);
hr = rounded_corners_visual_->SetClip(clip.Get());
CHECK_EQ(hr, S_OK);
// Enable anti-aliasing of the rounded corners.
hr =
rounded_corners_visual_->SetBorderMode(DCOMPOSITION_BORDER_MODE_SOFT);
CHECK_EQ(hr, S_OK);
} else {
hr = rounded_corners_visual_->SetClip(nullptr);
CHECK_EQ(hr, S_OK);
hr = rounded_corners_visual_->SetBorderMode(
DCOMPOSITION_BORDER_MODE_INHERIT);
CHECK_EQ(hr, S_OK);
}
}
if (quad_to_root_transform_changed) {
if (quad_to_root_transform_.Is2dTransform()) {
const D2D_MATRIX_3X2_F matrix =
TransformToD2D_MATRIX_3X2_F(quad_to_root_transform_);
hr = Microsoft::WRL::ComPtr<IDCompositionVisual>(transform_visual_)
->SetTransform(matrix);
CHECK_EQ(hr, S_OK);
} else {
const D2D_MATRIX_4X4_F matrix =
TransformToD2D_MATRIX_4X4_F(quad_to_root_transform_);
hr = CheckedCastToVisual3(transform_visual_)->SetTransform(matrix);
CHECK_EQ(hr, S_OK);
}
}
if (nearest_neighbor_filter_changed) {
hr = transform_visual_->SetBitmapInterpolationMode(
nearest_neighbor_filter_
? DCOMPOSITION_BITMAP_INTERPOLATION_MODE_NEAREST_NEIGHBOR
: DCOMPOSITION_BITMAP_INTERPOLATION_MODE_LINEAR);
CHECK_EQ(hr, S_OK);
}
if (image_size_changed || content_rect_changed || quad_rect_changed) {
if (content_rect_.Contains(gfx::RectF(image_size_))) {
// No need to set clip to content if the whole image is inside the content
// rect region.
hr = content_visual_->SetClip(nullptr);
CHECK_EQ(hr, S_OK);
} else {
// Exclude content outside the content rect region.
const auto content_clip =
D2D1::RectF(content_rect_.x(), content_rect_.y(),
content_rect_.right(), content_rect_.bottom());
hr = content_visual_->SetClip(content_clip);
CHECK_EQ(hr, S_OK);
}
// Transform the (clipped) content so that it fills |quad_rect_|'s bounds.
// |quad_rect_|'s offset is handled below, so we exclude it from the matrix.
const bool needs_offset = !content_rect_.OffsetFromOrigin().IsZero();
const bool needs_scale =
static_cast<float>(quad_rect_.width()) != content_rect_.width() ||
static_cast<float>(quad_rect_.height()) != content_rect_.height();
if (needs_offset || needs_scale) {
const float scale_x =
static_cast<float>(quad_rect_.width()) / content_rect_.width();
const float scale_y =
static_cast<float>(quad_rect_.height()) / content_rect_.height();
const D2D_MATRIX_3X2_F matrix =
D2D1::Matrix3x2F::Translation(-content_rect_.x(),
-content_rect_.y()) *
D2D1::Matrix3x2F::Scale(scale_x, scale_y);
hr = Microsoft::WRL::ComPtr<IDCompositionVisual>(content_visual_)
->SetTransform(matrix);
CHECK_EQ(hr, S_OK);
} else {
hr = content_visual_->SetTransform(nullptr);
CHECK_EQ(hr, S_OK);
}
// Visual offset is applied after transform so it is affected by the
// transform, which is consistent with how the compositor maps quad rects to
// their target space.
hr = content_visual_->SetOffsetX(quad_rect_.x());
CHECK_EQ(hr, S_OK);
hr = content_visual_->SetOffsetY(quad_rect_.y());
CHECK_EQ(hr, S_OK);
}
if (dcomp_visual_content_changed) {
hr = content_visual_->SetContent(dcomp_visual_content_.Get());
CHECK_EQ(hr, S_OK);
}
#if DCHECK_IS_ON()
dcomp_visual_content_changed_from_previous_frame_ =
dcomp_visual_content_changed;
#endif
if (dcomp_surface_serial_changed) {
// The DComp surface has been drawn to and needs a commit to show its
// update. No visual changes are needed in this case.
}
if (quad_rect_changed || background_color_content_changed ||
background_color_changed) {
if (!background_color_content_ || background_color.fA == 0.0) {
// A fully transparent color is the same as no background fill.
hr = background_color_visual_->SetContent(nullptr);
CHECK_EQ(hr, S_OK);
} else {
const D2D_MATRIX_3X2_F matrix =
TransformToD2D_MATRIX_3X2_F(gfx::TransformBetweenRects(
gfx::RectF(kSolidColorSurfaceSize), gfx::RectF(quad_rect_)));
hr = Microsoft::WRL::ComPtr<IDCompositionVisual>(background_color_visual_)
->SetTransform(matrix);
CHECK_EQ(hr, S_OK);
hr =
background_color_visual_->SetContent(background_color_content_.Get());
CHECK_EQ(hr, S_OK);
hr = CheckedCastToVisual3(background_color_visual_)
->SetOpacity(background_color.fA);
CHECK_EQ(hr, S_OK);
}
}
if (quad_to_root_transform_changed || quad_rect_changed ||
allow_antialiasing_changed) {
const float kNeedsSoftBorderTolerance = 0.001;
const bool content_soft_borders =
allow_antialiasing_ &&
(!quad_to_root_transform_.Preserves2dAxisAlignment() ||
!gfx::IsNearestRectWithinDistance(
quad_to_root_transform_.MapRect(gfx::RectF(quad_rect_)),
kNeedsSoftBorderTolerance));
// The border mode of the transform visual is set (instead of the content
// visual), so this setting can affect both the content and the background
// color, since both are are children of the transform visual.
hr = transform_visual_->SetBorderMode(content_soft_borders
? DCOMPOSITION_BORDER_MODE_SOFT
: DCOMPOSITION_BORDER_MODE_HARD);
CHECK_EQ(hr, S_OK);
}
return needs_commit;
}
void DCLayerTree::VisualTree::VisualSubtree::GetSwapChainVisualInfoForTesting(
gfx::Transform* out_transform,
gfx::Point* out_offset,
gfx::Rect* out_clip_rect) const {
CHECK_IS_TEST();
*out_transform = quad_to_root_transform_;
*out_offset = quad_rect_.origin();
*out_clip_rect = clip_rect_in_root_.value_or(gfx::Rect());
}
DCLayerTree::VisualTree::VisualTree(DCLayerTree* dc_layer_tree)
: dc_layer_tree_(dc_layer_tree) {}
DCLayerTree::VisualTree::~VisualTree() = default;
base::expected<void, CommitError> DCLayerTree::VisualTree::BuildTree(
const std::vector<DCLayerOverlayParams>& overlays) {
#if EXPENSIVE_DCHECKS_ARE_ON()
CHECK(std::ranges::is_sorted(overlays, {}, &DCLayerOverlayParams::z_order));
#endif
// Index into the subtree from the previous frame that is being reused in the
// current frame for the given overlay index.
// |overlay_index_to_reused_subtree| has an entry for every overlay in the
// current frame. Each entry indexes into |visual_subtrees_|, which are the
// subtrees for the previous frame. Initialized with std::nullopt,
// meaning not reused.
std::vector<std::optional<size_t>> overlay_index_to_reused_subtree(
overlays.size(), std::nullopt);
// Index into the current frame overlay that uses the subtree of the previous
// frame for the given subtree index. |subtree_index_to_overlay| has an entry
// for every subtree in the previous frame. Each entry indexes into |overlays|
// of the current frame. Initialized with std::nullopt, meaning the subtree
// is not being reused in the current frame.
std::vector<std::optional<size_t>> subtree_index_to_overlay(
visual_subtrees_.size(), std::nullopt);
// |visual_subtrees| will become |visual_subtrees_| of the current frame;
std::vector<std::unique_ptr<VisualSubtree>> visual_subtrees;
visual_subtrees.resize(overlays.size());
decltype(layer_ids_for_testing_) layer_ids_for_testing;
layer_ids_for_testing.reserve(overlays.size());
// Populate the map with visual content and assign matching subtrees to the
// overlays.
VisualSubtreeMap subtree_map = BuildMapAndAssignMatchingSubtrees(
overlays, visual_subtrees, overlay_index_to_reused_subtree,
subtree_index_to_overlay);
// Assign unused subtrees to the overlays that don't have a match.
const size_t first_prev_frame_subtree_unused_index =
ReuseUnmatchedSubtrees(visual_subtrees, overlay_index_to_reused_subtree,
subtree_index_to_overlay);
// Status for each subtree of the previous frame if it's attached to the root.
// Initialized with true, meaning attached.
std::vector<bool> prev_subtree_is_attached_to_root(visual_subtrees_.size(),
true);
bool needs_commit = DetachUnusedSubtreesFromRoot(
first_prev_frame_subtree_unused_index, prev_subtree_is_attached_to_root);
// Remove unused subtrees from the root that need repositioning.
needs_commit |= DetachReusedSubtreesThatNeedRepositioningFromRoot(
visual_subtrees, overlay_index_to_reused_subtree,
subtree_index_to_overlay, prev_subtree_is_attached_to_root);
#if DCHECK_IS_ON()
VisualTreeValid(subtree_index_to_overlay, prev_subtree_is_attached_to_root);
#endif // DCHECK_IS_ON()
IDCompositionVisual2* left_sibling_visual = nullptr;
base::flat_set<std::optional<gfx::OverlayLayerId::SharedQuadStateLayerId>>
layers_with_multiple_overlays;
for (size_t i = 1; i < overlays.size(); i++) {
const decltype(layers_with_multiple_overlays)::key_type sqs_layer_id =
overlays[i].layer_id.shared_quad_state_layer_id();
if (sqs_layer_id == decltype(layers_with_multiple_overlays)::key_type()) {
// A default layer ID implies no explicit layer, which should be treated
// as different from every other layer ID, including itself.
continue;
}
if (overlays[i].layer_id == overlays[i - 1].layer_id) {
// There were at least two contiguous quads in the same layer.
layers_with_multiple_overlays.emplace(sqs_layer_id);
}
}
size_t num_layers_modified = 0;
// This loop walks the overlays and builds or updates the visual subtree for
// each overlay. |left_sibling_visual| is required to properly stack visual
// subtrees that are detached from the root visual.
for (unsigned int i = 0; i < overlays.size(); i++) {
bool subtree_attached_to_root = false;
if (visual_subtrees[i]) {
DCHECK(overlay_index_to_reused_subtree[i]);
subtree_attached_to_root =
prev_subtree_is_attached_to_root[overlay_index_to_reused_subtree[i]
.value()];
} else {
// This overlay does not reuse a subtree from the previous frame.
// Instantiate a new one.
visual_subtrees[i] = std::make_unique<VisualSubtree>();
}
const uint64_t dcomp_surface_serial =
overlays[i].overlay_image.has_value()
? overlays[i].overlay_image->dcomp_surface_serial()
: 0;
const gfx::Size image_size = overlays[i].overlay_image.has_value()
? overlays[i].overlay_image->size()
: gfx::Size();
// Only get a background color content if we have a non-transparent
// background color.
IUnknown* background_color_content = nullptr;
if (overlays[i].background_color &&
overlays[i].background_color->fA != 0.0) {
// TODO(http://crbug.com/1380822): Refactor to remove early exits. They
// may leave visual_subtrees_ corrupted.
ASSIGN_OR_RETURN(
background_color_content,
dc_layer_tree_->solid_color_content_provider_->GetSolidColorContent(
overlays[i].background_color.value()));
}
VisualSubtree* visual_subtree = visual_subtrees[i].get();
visual_subtree->set_z_order(overlays[i].z_order);
IUnknown* dcomp_visual_content =
overlays[i].overlay_image
? overlays[i].overlay_image->dcomp_visual_content()
: nullptr;
// TODO(crbug.com/324460866): We turn off overlay edge antialiasing when
// there are multiple overlays in the same layer. This is a workaround to
// avoid seams when there is e.g. a complex transform applied to the layer.
// This works for partial delegation because we only expect non-trivial
// transforms in ephemeral (i.e. animation) states. To support arbitrary
// content in full delegation, we'll need to parent overlays in the same
// layer under the same transform visual.
const bool allow_antialiasing = !layers_with_multiple_overlays.contains(
overlays[i].layer_id.shared_quad_state_layer_id());
const bool visual_needs_commit = visual_subtrees[i]->Update(
dc_layer_tree_->dcomp_device_.Get(), dcomp_visual_content,
dcomp_surface_serial, image_size, overlays[i].content_rect,
background_color_content,
overlays[i].background_color.value_or(SkColors::kTransparent),
overlays[i].quad_rect, overlays[i].nearest_neighbor_filter,
overlays[i].transform, overlays[i].rounded_corner_bounds,
overlays[i].opacity, overlays[i].clip_rect, allow_antialiasing);
if (!subtree_attached_to_root) {
HRESULT hr = dc_layer_tree_->dcomp_root_visual_.Get()->AddVisual(
visual_subtree->container_visual(), TRUE, left_sibling_visual);
CHECK_EQ(hr, S_OK);
}
left_sibling_visual = visual_subtree->container_visual();
if (visual_needs_commit || !subtree_attached_to_root) {
num_layers_modified++;
needs_commit = true;
}
layer_ids_for_testing.push_back(overlays[i].layer_id);
}
// Update subtree_map_ and visual_subtrees_ with new values.
subtree_map_ = std::move(subtree_map);
visual_subtrees_ = std::move(visual_subtrees);
layer_ids_for_testing_ = std::move(layer_ids_for_testing);
UMA_HISTOGRAM_COUNTS("GPU.OsCompositor.NumLayersModified",
num_layers_modified);
if (needs_commit) {
// Submit any GPU work recorded by `GetSolidColorContent` calls earlier in
// the frame, so the queue we pass to `PresentCompositionTextures` already
// holds the fences signaling fill completion. No-op for the D3D11 path.
RETURN_IF_ERROR(dc_layer_tree_->solid_color_content_provider_
->FlushPendingFillsBeforeCommit());
if (dc_layer_tree_->dcomp_device_6_) {
// First call `PresentCompositionTextures` to specify the queue that the
// next `Commit` call uses to actually present the composition textures.
IUnknown* command_queue_unk = dc_layer_tree_->d3d12_command_queue_.Get();
HRESULT hr = dc_layer_tree_->dcomp_device_6_->PresentCompositionTextures(
&command_queue_unk, 1);
if (FAILED(hr)) {
return base::unexpected(
CommitError{CommitError::Reason::
kIDCompositionDevice6PresentCompositionTextures,
hr});
}
}
TRACE_EVENT("gpu", "DCLayerTree::CommitAndClearPendingOverlays::Commit",
"num_layers_modified", num_layers_modified);
base::ScopedUmaHistogramTimer scoped_timer(
"GPU.DirectComposition.DCompCommitDuration",
base::ScopedUmaHistogramTimer::ScopedHistogramTiming::
kMicrosecondTimes);
HRESULT hr = dc_layer_tree_->dcomp_device_->Commit();
if (FAILED(hr)) {
return base::unexpected(
CommitError{CommitError::Reason::kIDCompositionDeviceCommit, hr});
}
}
return base::ok();
}
DCLayerTree::VisualTree::VisualSubtreeMap
DCLayerTree::VisualTree::BuildMapAndAssignMatchingSubtrees(
const std::vector<DCLayerOverlayParams>& overlays,
std::vector<std::unique_ptr<VisualSubtree>>& new_visual_subtrees,
std::vector<std::optional<size_t>>& overlay_index_to_reused_subtree,
std::vector<std::optional<size_t>>& subtree_index_to_overlay) {
CHECK_EQ(overlay_index_to_reused_subtree.size(), overlays.size());
CHECK_EQ(new_visual_subtrees.size(), overlays.size());
CHECK_EQ(subtree_index_to_overlay.size(), visual_subtrees_.size());
// Contains {visual content, overlay index} pairs for this frame overlays.
// This structure has entries for overlays that have visual content.
// No entry is inserted for the overlays with no visual content.
std::vector<std::pair<raw_ptr<IUnknown>, size_t>> map_results;
// For each overlay populate |map_results| with visual content and indices
// of overlays from this frame and find the matching subtree from the
// previous frame.
for (size_t i = 0; i < overlays.size(); i++) {
if (!overlays[i].overlay_image) {
continue;
}
IUnknown* dcomp_visual_content =
overlays[i].overlay_image->dcomp_visual_content();
if (!dcomp_visual_content) {
continue;
}
map_results.emplace_back(dcomp_visual_content, i);
// Find matching visual content from the previous frame.
auto it = subtree_map_.find(dcomp_visual_content);
if (it == subtree_map_.end()) {
continue;
}
size_t matched_index = it->second;
if (visual_subtrees_[matched_index]) {
// Assign the matched index to the corresponding overlay.
overlay_index_to_reused_subtree[i] = matched_index;
// Assign overlay index to the matched subtree.
subtree_index_to_overlay[matched_index] = i;
// Move visual subtree from the old subtrees to new subtrees.
new_visual_subtrees[i] = std::move(visual_subtrees_[matched_index]);
}
}
// This converts to a flat_map on returning. We're doing this on purpose to
// go from O(N^2) to O(N*logN) for building the map.
return map_results;
}
size_t DCLayerTree::VisualTree::ReuseUnmatchedSubtrees(
std::vector<std::unique_ptr<VisualSubtree>>& new_visual_subtrees,
std::vector<std::optional<size_t>>& overlay_index_to_reused_subtree,
std::vector<std::optional<size_t>>& subtree_index_to_overlay) {
CHECK_EQ(new_visual_subtrees.size(), overlay_index_to_reused_subtree.size());
CHECK_EQ(subtree_index_to_overlay.size(), visual_subtrees_.size());
// No further actions are needed if the previous frame is empty.
if (visual_subtrees_.empty()) {
return 0;
}
// Index into |visual_subtrees_|.
size_t prev_frame_subtree_index = 0;
// Assign unused subtrees from previous frames to overlays that don't have
// a match.
for (size_t i = 0; i < new_visual_subtrees.size() &&
prev_frame_subtree_index < visual_subtrees_.size();
i++) {
if (new_visual_subtrees[i]) {
// Skip overlay that has a match.
continue;
}
// Find next unused subtree and assign it to the overlay at index |i|.
for (; prev_frame_subtree_index < visual_subtrees_.size();
prev_frame_subtree_index++) {
if (!visual_subtrees_[prev_frame_subtree_index]) {
continue;
}
// Assign the found index to the corresponding overlay.
overlay_index_to_reused_subtree[i] = prev_frame_subtree_index;
// Assign the overlay index to the found subtree.
subtree_index_to_overlay[prev_frame_subtree_index] = i;
// Move visual subtree from the old subtrees to new subtrees.
new_visual_subtrees[i] =
std::move(visual_subtrees_[prev_frame_subtree_index]);
prev_frame_subtree_index++;
break;
}
}
return prev_frame_subtree_index;
}
bool DCLayerTree::VisualTree::DetachUnusedSubtreesFromRoot(
size_t first_prev_frame_subtree_unused_index,
std::vector<bool>& prev_subtree_is_attached_to_root) {
CHECK_EQ(prev_subtree_is_attached_to_root.size(), visual_subtrees_.size());
bool needs_commit = false;
// Detach the remaining unused subtrees from the root.
for (size_t i = first_prev_frame_subtree_unused_index;
i < visual_subtrees_.size(); i++) {
if (!visual_subtrees_[i]) {
continue;
}
DetachSubtreeFromRoot(visual_subtrees_[i].get());
prev_subtree_is_attached_to_root[i] = false;
needs_commit = true;
}
return needs_commit;
}
bool DCLayerTree::VisualTree::DetachReusedSubtreesThatNeedRepositioningFromRoot(
const std::vector<std::unique_ptr<VisualSubtree>>& new_visual_subtrees,
const std::vector<std::optional<size_t>>& overlay_index_to_reused_subtree,
const std::vector<std::optional<size_t>>& subtree_index_to_overlay,
std::vector<bool>& prev_subtree_is_attached_to_root) {
CHECK_EQ(new_visual_subtrees.size(), overlay_index_to_reused_subtree.size());
CHECK_EQ(subtree_index_to_overlay.size(), visual_subtrees_.size());
CHECK_EQ(prev_subtree_is_attached_to_root.size(), visual_subtrees_.size());
// No further actions are needed if the previous frame is empty.
if (visual_subtrees_.empty()) {
return false;
}
bool needs_commit = false;
// Index into |visual_subtrees_|.
size_t prev_frame_subtree_index = 0;
// This loop walks the overlay indices and detaches from the root any
// subtrees that need repositioning in the current frame.
for (size_t i = 0; i < overlay_index_to_reused_subtree.size(); i++) {
if (!overlay_index_to_reused_subtree[i]) {
continue;
}
size_t reused_subtree_index = overlay_index_to_reused_subtree[i].value();
DCHECK_EQ(i, subtree_index_to_overlay[reused_subtree_index].value());
// If the overlay at index |i| has a match, detach from the root any
// subtrees that appear before the matching subtree and the previous match.
for (; prev_frame_subtree_index < reused_subtree_index;
prev_frame_subtree_index++) {
if (!prev_subtree_is_attached_to_root[prev_frame_subtree_index]) {
continue;
}
VisualSubtree* subtree =
new_visual_subtrees[subtree_index_to_overlay[prev_frame_subtree_index]
.value()]
.get();
DetachSubtreeFromRoot(subtree);
prev_subtree_is_attached_to_root[prev_frame_subtree_index] = false;
needs_commit = true;
}
if (reused_subtree_index == prev_frame_subtree_index) {
++prev_frame_subtree_index;
}
}
return needs_commit;
}
void DCLayerTree::VisualTree::DetachSubtreeFromRoot(VisualSubtree* subtree) {
HRESULT hr = dc_layer_tree_->dcomp_root_visual_.Get()->RemoveVisual(
subtree->container_visual());
CHECK_EQ(hr, S_OK);
}
DCLayerTree::VisualTree::VisualSubtree*
DCLayerTree::VisualTree::GetFrontMostVisualSubtreeForTesting() const {
CHECK_IS_TEST();
return visual_subtrees_.back().get();
}
void DCLayerTree::UpdateCurrentOutput() {
const HMONITOR monitor = MonitorFromWindow(window_, MONITOR_DEFAULTTONEAREST);
// |factory| is populated from the cached output's adapter chain when
// current_output_ is valid. It is reused in the enumeration below when the
// factory is still current, avoiding an unnecessary CreateDXGIFactory1 call.
Microsoft::WRL::ComPtr<IDXGIFactory1> factory;
// Invalidate the cached IDXGIOutput if the DXGI factory has become stale
// (e.g. after a display topology change or lock/unlock) or if the window
// moved to a different monitor.
if (current_output_) {
Microsoft::WRL::ComPtr<IDXGIAdapter> adapter;
CHECK_EQ(current_output_->GetParent(IID_PPV_ARGS(&adapter)), S_OK);
Microsoft::WRL::ComPtr<IDXGIFactory1> factory_local;
CHECK_EQ(adapter->GetParent(IID_PPV_ARGS(&factory_local)), S_OK);
if (factory_local->IsCurrent()) {
// Factory is current; only reset output if window moved to a new monitor.
factory = std::move(factory_local);
DXGI_OUTPUT_DESC desc = {};
CHECK_EQ(current_output_->GetDesc(&desc), S_OK);
if (desc.Monitor != monitor) {
current_output_.Reset();
// If only the monitor changed and the factory is still current,
// reuse it below.
}
} else {
// Factory is stale; skip GetDesc() (result would be discarded). Reset
// output and leave factory null so CreateDXGIFactory1 is called below.
current_output_.Reset();
}
}
// Find the IDXGIOutput for |monitor| by enumerating all adapters.
// This correctly handles the case where the window is on a monitor driven by
// a different adapter than d3d11_device_
if (!current_output_ && monitor) {
if (!factory && FAILED(::CreateDXGIFactory1(IID_PPV_ARGS(&factory)))) {
return;
}
Microsoft::WRL::ComPtr<IDXGIAdapter1> adapter;
for (UINT i = 0; SUCCEEDED(factory->EnumAdapters1(i, &adapter)); i++) {
Microsoft::WRL::ComPtr<IDXGIOutput> output;
for (UINT j = 0; SUCCEEDED(adapter->EnumOutputs(j, &output)); j++) {
DXGI_OUTPUT_DESC desc = {};
CHECK_EQ(output->GetDesc(&desc), S_OK);
if (desc.Monitor == monitor) {
current_output_ = output;
return;
}
}
}
}
}
base::expected<void, CommitError> DCLayerTree::CommitAndClearPendingOverlays(
std::vector<DCLayerOverlayParams> overlays) {
TRACE_EVENT1("gpu", "DCLayerTree::CommitAndClearPendingOverlays",
"num_overlays", overlays.size());
base::ScopedUmaHistogramTimer scoped_timer(
"GPU.DirectComposition.CommitAndClearPendingOverlaysDuration",
base::ScopedUmaHistogramTimer::ScopedHistogramTiming::kMicrosecondTimes);
UpdateCurrentOutput();
// If delegated ink metadata exists for this frame, attempt to make an overlay
// so that a visual subtree can be created for a delegated ink visual.
// TODO(crbug.com/335553727) Consider clearing ink_renderer_ when there's no
// metadata.
if (pending_delegated_ink_metadata_) {
Microsoft::WRL::ComPtr<IDXGISwapChain1> root_swap_chain;
auto it = std::ranges::find(overlays, 0, &DCLayerOverlayParams::z_order);
if (it != overlays.end() && (*it).overlay_image) {
Microsoft::WRL::ComPtr<IUnknown> root_visual_content =
(*it).overlay_image->dcomp_visual_content();
CHECK(root_visual_content);
HRESULT hr = root_visual_content.As(&root_swap_chain);
if (hr == E_NOINTERFACE) {
DCHECK_EQ(root_swap_chain, nullptr);
} else {
CHECK_EQ(S_OK, hr);
CHECK_NE(root_swap_chain, nullptr);
}
}
if (auto ink_layer = ink_renderer_->MakeDelegatedInkOverlay(
dcomp_device_.Get(), root_swap_chain.Get(),
std::move(pending_delegated_ink_metadata_))) {
overlays.push_back(std::move(*ink_layer));
}
}
// Move unused video swap chains to `unused_video_swap_chains` for potential
// reuse (when adjacent frames have a videos that have different layer IDs
// which can sometimes happen when a video's src changes), then cleanup.
decltype(video_swap_chains_) unused_video_swap_chains;
{
// Move all video swap chains to `unused_video_swap_chains` and the move
// ones that are in the current frame back into `video_swap_chains_`.
unused_video_swap_chains = std::move(video_swap_chains_);
// We may be moving up to all of the swap chains back.
video_swap_chains_.reserve(unused_video_swap_chains.size());
size_t num_swap_chain_presenters = 0;
for (auto& overlay : overlays) {
if (NeedSwapChainPresenter(overlay)) {
auto reused_video_swap_chain_it =
unused_video_swap_chains.find(overlay.layer_id);
if (reused_video_swap_chain_it != unused_video_swap_chains.end()) {
video_swap_chains_.insert(std::move(*reused_video_swap_chain_it));
unused_video_swap_chains.erase(reused_video_swap_chain_it);
}
num_swap_chain_presenters++;
}
}
// If there are more videos this frame, reserve enough space for them.
video_swap_chains_.reserve(num_swap_chain_presenters);
}
bool did_update_primary_plane_damage = false;
bool need_background_layer = false;
// Populate |overlays| with information required to build dcomp visual tree.
for (auto it = overlays.begin(); it != overlays.end(); it++) {
auto& overlay = *it;
if (NeedSwapChainPresenter(overlay)) {
// Present to swap chain and update the overlay with transform, clip
// and content.
auto& video_swap_chain = video_swap_chains_[overlay.layer_id];
if (!video_swap_chain) {
// TODO(sunnyps): Try to find a matching swap chain based on size, type
// of swap chain, gl image, etc.
auto unused_video_swap_chain_it = unused_video_swap_chains.begin();
if (unused_video_swap_chain_it != unused_video_swap_chains.end()) {
video_swap_chain = std::move(unused_video_swap_chain_it->second);
unused_video_swap_chains.erase(unused_video_swap_chain_it);
} else {
video_swap_chain = std::make_unique<SwapChainPresenter>(
this, d3d11_device_, dcomp_device_);
}
}
std::optional<SwapChainPresenter::OverlayPositionAdjustment>
overlay_position_adjustment;
ASSIGN_OR_RETURN(DCLayerOverlayImage video_image,
video_swap_chain->PresentToSwapChain(
overlay, overlay_position_adjustment));
overlay.overlay_image = std::move(video_image);
overlay.content_rect = gfx::RectF(overlay.overlay_image->size());
if (overlay_position_adjustment) {
overlay.transform.MakeIdentity();
overlay.quad_rect =
gfx::Rect(overlay_position_adjustment->monitor_size);
if (overlay.clip_rect) {
overlay.clip_rect =
gfx::Rect(overlay_position_adjustment->monitor_size);
}
}
if (overlay.video_params.is_full_screen_video &&
!overlay_position_adjustment) {
// If we failed to disable the desktop plane, we need to manually add
// a solid color layer to act as the video background mat.
need_background_layer = true;
}
if (tint_video_layer_) {
SkColor4f tint_color;
switch (video_swap_chain->GetLastPresentationMode()) {
case SwapChainPresenter::PresentationMode::kDecodeSwapChain:
tint_color = SkColors::kBlue;
break;
case SwapChainPresenter::PresentationMode::kVpBlt:
tint_color = SkColors::kMagenta;
break;
case SwapChainPresenter::PresentationMode::kVpBltWithStagingTexture:
tint_color = SkColor4f(1.0, 0.5, 0.0, 1.0);
break;
case SwapChainPresenter::PresentationMode::kMfSurfaceProxy:
tint_color = SkColors::kGreen;
break;
}
DCLayerOverlayParams tint_overlay;
tint_overlay.quad_rect = it->quad_rect;
tint_overlay.transform = it->transform;
tint_overlay.clip_rect = it->clip_rect;
tint_overlay.rounded_corner_bounds = it->rounded_corner_bounds;
tint_overlay.z_order = it->z_order;
tint_overlay.opacity = 0.25;
tint_overlay.background_color = tint_color;
tint_overlay.layer_id =
it->layer_id.MakeForChildOfSharedQuadStateLayer(1);
it = overlays.insert(std::next(it), std::move(tint_overlay));
// Do not access `overlay` after this point since it is invalidated.
}
} else if (primary_plane_surface_) {
// If supported, "present" the primary plane buffer to a surface with
// incremental damage.
if (overlay.z_order == 0 && overlay.overlay_image) {
if (Microsoft::WRL::ComPtr<IDCompositionTexture> dcomp_texture;
SUCCEEDED(Microsoft::WRL::ComPtr<IUnknown>(
overlay.overlay_image->dcomp_visual_content())
.As(&dcomp_texture))) {
DVLOG(1) << "Set primary_plane_surface_ damage: "
<< overlay.damage_rect.ToString();
const RECT damage_rect =
gfx::ToEnclosingRect(overlay.damage_rect).ToRECT();
HRESULT hr = primary_plane_surface_->SetTexture(dcomp_texture.Get(),
&damage_rect, 1);
CHECK_EQ(hr, S_OK);
overlay.overlay_image = DCLayerOverlayImage(
overlay.overlay_image->size(), primary_plane_surface_,
primary_plane_surface_serial_++);
did_update_primary_plane_damage = true;
} else {
// Primary plane is not backed by `BufferQueue`.
}
} else {
// Overlay is not the primary plane.
}
}
}
if (primary_plane_surface_ && primary_plane_surface_serial_ &&
!did_update_primary_plane_damage) {
// We need to commit the visual tree after `SetTexture`. We expect the
// primary plane overlay to be removed from the visual tree this frame,
// which will cause commit to happen.
DVLOG(1) << "Reset primary_plane_surface_ damage.";
primary_plane_surface_->SetTexture(nullptr);
primary_plane_surface_serial_ = 0;
}
if (need_background_layer) {
DCLayerOverlayParams background_mat;
background_mat.quad_rect = gfx::Rect(GetMonitorSizeForWindow(window()));
background_mat.z_order = INT_MIN;
background_mat.background_color = SkColors::kBlack;
background_mat.layer_id = gfx::OverlayLayerId::MakeVizInternal(
gfx::OverlayLayerId::VizInternalId::kBackgroundColorLayer);
overlays.insert(overlays.begin(), std::move(background_mat));
}
if (!visual_tree_) {
visual_tree_ = std::make_unique<VisualTree>(this);
}
const base::expected<void, CommitError> status =
visual_tree_->BuildTree(overlays);
ink_renderer_->ReportPointsDrawn();
// Clean up excess entries so the pool will not grow unbounded. Called on
// both success and failure paths so that providers that defer GPU work can
// discard pending state from a failed/short-circuited frame.
solid_color_content_provider_->TrimAfterCommit();
return status;
}
bool DCLayerTree::SupportsDelegatedInk() {
return ink_renderer_->DelegatedInkIsSupported(dcomp_device_);
}
void DCLayerTree::SetDelegatedInkTrailStartPoint(
std::unique_ptr<gfx::DelegatedInkMetadata> metadata) {
DCHECK(SupportsDelegatedInk());
pending_delegated_ink_metadata_ = std::move(metadata);
}
void DCLayerTree::InitDelegatedInkPointRendererReceiver(
mojo::PendingReceiver<gfx::mojom::DelegatedInkPointRenderer>
pending_receiver) {
DCHECK(SupportsDelegatedInk());
ink_renderer_->InitMessagePipeline(std::move(pending_receiver));
}
// Return properties of non root swap chain at given index.
void DCLayerTree::GetSwapChainVisualInfoForTesting(
const gfx::OverlayLayerId& layer_id,
gfx::Transform* out_transform,
gfx::Point* out_offset,
gfx::Rect* out_clip_rect) const {
CHECK_IS_TEST();
visual_tree_->GetSwapChainVisualInfoForTesting( // IN-TEST
layer_id, out_transform, out_offset, out_clip_rect);
}
size_t DCLayerTree::GetSwapChainPresenterCountForTesting() const {
CHECK_IS_TEST();
return video_swap_chains_.size();
}
size_t DCLayerTree::GetDcompLayerCountForTesting() const {
CHECK_IS_TEST();
return visual_tree_->GetDcompLayerCountForTesting(); // IN-TEST
}
IDCompositionVisual2* DCLayerTree::GetContentVisualForTesting(
const gfx::OverlayLayerId& layer_id) const {
CHECK_IS_TEST();
return visual_tree_->GetContentVisualForTesting(layer_id); // IN-TEST
}
IUnknown* DCLayerTree::GetBackgroundColorContentForTesting(
const gfx::OverlayLayerId& layer_id) const {
CHECK_IS_TEST();
return visual_tree_->GetBackgroundColorContentForTesting( // IN-TEST
layer_id);
}
size_t DCLayerTree::GetNumEntriesInSolidColorPoolForTesting() const {
CHECK_IS_TEST();
return solid_color_content_provider_->GetNumEntriesForTesting(); // IN-TEST
}
#if DCHECK_IS_ON()
bool DCLayerTree::DcompVisualContentChangedFromPreviousFrameForTesting(
const gfx::OverlayLayerId& layer_id) const {
CHECK_IS_TEST();
return visual_tree_
->DcompVisualContentChangedFromPreviousFrameForTesting( // IN-TEST
layer_id);
}
#endif // DCHECK_IS_ON()
const DCLayerTree::VisualTree::VisualSubtree*
DCLayerTree::VisualTree::GetSubtreeFromLayerIdForTesting(
const gfx::OverlayLayerId& layer_id) const {
CHECK_IS_TEST();
const auto it = std::ranges::find(layer_ids_for_testing_, layer_id);
CHECK(it != layer_ids_for_testing_.end());
const size_t index =
std::ranges::distance(layer_ids_for_testing_.begin(), it);
return visual_subtrees_.at(index).get();
}
// Return properties of non root swap chain at given index.
void DCLayerTree::VisualTree::GetSwapChainVisualInfoForTesting(
const gfx::OverlayLayerId& layer_id,
gfx::Transform* out_transform,
gfx::Point* out_offset,
gfx::Rect* out_clip_rect) const {
CHECK_IS_TEST();
return GetSubtreeFromLayerIdForTesting(layer_id) // IN-TEST
->GetSwapChainVisualInfoForTesting(out_transform, out_offset, // IN-TEST
out_clip_rect);
}
size_t DCLayerTree::VisualTree::GetDcompLayerCountForTesting() const {
CHECK_IS_TEST();
return visual_subtrees_.size();
}
IDCompositionVisual2* DCLayerTree::VisualTree::GetContentVisualForTesting(
const gfx::OverlayLayerId& layer_id) const {
CHECK_IS_TEST();
return GetSubtreeFromLayerIdForTesting(layer_id) // IN-TEST
->container_visual();
}
IUnknown* DCLayerTree::VisualTree::GetBackgroundColorContentForTesting(
const gfx::OverlayLayerId& layer_id) const {
CHECK_IS_TEST();
return GetSubtreeFromLayerIdForTesting(layer_id) // IN-TEST
->background_color_content_for_testing(); // IN-TEST
}
#if DCHECK_IS_ON()
bool DCLayerTree::VisualTree::
DcompVisualContentChangedFromPreviousFrameForTesting(
const gfx::OverlayLayerId& layer_id) const {
CHECK_IS_TEST();
return GetSubtreeFromLayerIdForTesting(layer_id) // IN-TEST
->DcompVisualContentChangedFromPreviousFrameForTesting(); // IN-TEST
}
#endif // DCHECK_IS_ON()
} // namespace gl