#include <d3d11_1.h>
#include "ui/gl/dc_layer_tree.h"
#include <utility>
#include "base/feature_list.h"
#include "base/memory/ptr_util.h"
#include "base/metrics/histogram_functions.h"
#include "base/trace_event/trace_event.h"
#include "ui/gl/direct_composition_child_surface_win.h"
#include "ui/gl/direct_composition_support.h"
#include "ui/gl/gl_angle_util_win.h"
#include "ui/gl/swap_chain_presenter.h"
namespace gl {
namespace {
bool SizeContains(const gfx::Size& a, const gfx::Size& b) {
return gfx::Rect(a).Contains(gfx::Rect(b));
}
bool NeedSwapChainPresenter(const DCLayerOverlayParams* overlay) {
switch (overlay->overlay_image->type()) {
case DCLayerOverlayType::kNV12Texture:
case DCLayerOverlayType::kNV12Pixmap:
case DCLayerOverlayType::kDCompSurfaceProxy:
return true;
case DCLayerOverlayType::kDCompVisualContent:
return overlay->z_order != 0;
}
}
BASE_FEATURE(kDCVisualTreeOptimization,
"DCVisualTreeOptimization",
base::FEATURE_DISABLED_BY_DEFAULT);
}
VideoProcessorWrapper::VideoProcessorWrapper() = default;
VideoProcessorWrapper::~VideoProcessorWrapper() = default;
VideoProcessorWrapper::VideoProcessorWrapper(VideoProcessorWrapper&& other) =
default;
VideoProcessorWrapper& VideoProcessorWrapper::operator=(
VideoProcessorWrapper&& other) = default;
DCLayerTree::DCLayerTree(bool disable_nv12_dynamic_textures,
bool disable_vp_scaling,
bool disable_vp_super_resolution,
bool no_downscaled_overlay_promotion)
: disable_nv12_dynamic_textures_(disable_nv12_dynamic_textures),
disable_vp_scaling_(disable_vp_scaling),
disable_vp_super_resolution_(disable_vp_super_resolution),
no_downscaled_overlay_promotion_(no_downscaled_overlay_promotion),
ink_renderer_(std::make_unique<DelegatedInkRenderer>()) {}
DCLayerTree::~DCLayerTree() = default;
bool DCLayerTree::Initialize(HWND window) {
window_ = window;
DCHECK(window_);
d3d11_device_ = QueryD3D11DeviceObjectFromANGLE();
DCHECK(d3d11_device_);
dcomp_device_ = GetDirectCompositionDevice();
DCHECK(dcomp_device_);
Microsoft::WRL::ComPtr<IDCompositionDesktopDevice> desktop_device;
dcomp_device_.As(&desktop_device);
DCHECK(desktop_device);
HRESULT hr =
desktop_device->CreateTargetForHwnd(window_, TRUE, &dcomp_target_);
if (FAILED(hr)) {
DLOG(ERROR) << "CreateTargetForHwnd failed with error 0x" << std::hex << hr;
return false;
}
dcomp_device_->CreateVisual(&dcomp_root_visual_);
DCHECK(dcomp_root_visual_);
dcomp_target_->SetRoot(dcomp_root_visual_.Get());
dcomp_root_visual_->SetBitmapInterpolationMode(
DCOMPOSITION_BITMAP_INTERPOLATION_MODE_LINEAR);
hdr_metadata_helper_ = std::make_unique<HDRMetadataHelperWin>(d3d11_device_);
return true;
}
VideoProcessorWrapper* DCLayerTree::InitializeVideoProcessor(
const gfx::Size& input_size,
const gfx::Size& output_size,
bool is_hdr_output) {
VideoProcessorWrapper& video_processor_wrapper =
GetOrCreateVideoProcessor(is_hdr_output);
if (!video_processor_wrapper.video_device) {
if (FAILED(d3d11_device_.As(&video_processor_wrapper.video_device))) {
DLOG(ERROR) << "Failed to retrieve video device from D3D11 device";
DCHECK(false);
DisableDirectCompositionOverlays();
return nullptr;
}
DCHECK(video_processor_wrapper.video_device);
Microsoft::WRL::ComPtr<ID3D11DeviceContext> context;
d3d11_device_->GetImmediateContext(&context);
DCHECK(context);
context.As(&video_processor_wrapper.video_context);
DCHECK(video_processor_wrapper.video_context);
}
if (video_processor_wrapper.video_processor &&
SizeContains(video_processor_wrapper.video_input_size, input_size) &&
SizeContains(video_processor_wrapper.video_output_size, 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 = input_size;
video_processor_wrapper.video_output_size = 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)) {
DLOG(ERROR) << "CreateVideoProcessorEnumerator failed with error 0x"
<< std::hex << hr;
DisableDirectCompositionOverlays();
return nullptr;
}
hr = video_processor_wrapper.video_device->CreateVideoProcessor(
video_processor_wrapper.video_processor_enumerator.Get(), 0,
&video_processor_wrapper.video_processor);
if (FAILED(hr)) {
DLOG(ERROR) << "CreateVideoProcessor failed with error 0x" << std::hex
<< hr;
DisableDirectCompositionOverlays();
return nullptr;
}
video_processor_wrapper.video_context
->VideoProcessorSetStreamAutoProcessingMode(
video_processor_wrapper.video_processor.Get(), 0, FALSE);
return &video_processor_wrapper;
}
VideoProcessorWrapper& DCLayerTree::GetOrCreateVideoProcessor(bool is_hdr) {
VideoProcessorType video_processor_type =
is_hdr ? VideoProcessorType::kHDR : VideoProcessorType::kSDR;
return video_processor_map_
.try_emplace(video_processor_type, VideoProcessorWrapper())
.first->second;
}
Microsoft::WRL::ComPtr<IDXGISwapChain1>
DCLayerTree::GetLayerSwapChainForTesting(size_t index) const {
if (index < video_swap_chains_.size())
return video_swap_chains_[index]->swap_chain();
return nullptr;
}
void DCLayerTree::GetSwapChainVisualInfoForTesting(size_t index,
gfx::Transform* transform,
gfx::Point* offset,
gfx::Rect* clip_rect) const {
if (visual_tree_) {
visual_tree_->GetSwapChainVisualInfoForTesting(index, transform,
offset, clip_rect);
}
}
DCLayerTree::VisualTree::VisualSubtree::VisualSubtree() = default;
DCLayerTree::VisualTree::VisualSubtree::~VisualSubtree() = default;
bool DCLayerTree::VisualTree::VisualSubtree::Update(
IDCompositionDevice2* dcomp_device,
Microsoft::WRL::ComPtr<IUnknown> dcomp_visual_content,
uint64_t dcomp_surface_serial,
const gfx::Vector2d& quad_rect_offset,
const gfx::Transform& quad_to_root_transform,
const absl::optional<gfx::Rect>& clip_rect_in_root) {
bool needs_commit = false;
HRESULT hr = S_OK;
if (!clip_visual_) {
needs_commit = true;
CHECK(!transform_visual_);
CHECK(!content_visual_);
hr = dcomp_device->CreateVisual(&clip_visual_);
CHECK_EQ(hr, S_OK);
hr = dcomp_device->CreateVisual(&transform_visual_);
CHECK_EQ(hr, S_OK);
hr = dcomp_device->CreateVisual(&content_visual_);
CHECK_EQ(hr, S_OK);
hr = clip_visual_->AddVisual(transform_visual_.Get(), FALSE, nullptr);
CHECK_EQ(hr, S_OK);
hr = transform_visual_->AddVisual(content_visual_.Get(), FALSE, nullptr);
CHECK_EQ(hr, S_OK);
}
if (clip_rect_ != clip_rect_in_root) {
clip_rect_ = clip_rect_in_root;
needs_commit = true;
if (clip_rect_.has_value()) {
gfx::Rect clip_rect = clip_rect_.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 (transform_ != quad_to_root_transform) {
transform_ = quad_to_root_transform;
needs_commit = true;
DCHECK(transform_.IsFlat());
D2D_MATRIX_3X2_F matrix =
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));
hr = transform_visual_->SetTransform(matrix);
CHECK_EQ(hr, S_OK);
}
if (offset_ != quad_rect_offset) {
offset_ = quad_rect_offset;
needs_commit = true;
hr = content_visual_->SetOffsetX(offset_.x());
CHECK_EQ(hr, S_OK);
hr = content_visual_->SetOffsetY(offset_.y());
CHECK_EQ(hr, S_OK);
}
if (dcomp_visual_content_ != dcomp_visual_content) {
dcomp_visual_content_ = std::move(dcomp_visual_content);
needs_commit = true;
hr = content_visual_->SetContent(dcomp_visual_content_.Get());
CHECK_EQ(hr, S_OK);
}
if (dcomp_surface_serial_ != dcomp_surface_serial) {
needs_commit = true;
dcomp_surface_serial_ = dcomp_surface_serial;
}
#if DCHECK_IS_ON()
if (dcomp_surface_serial_ > 0)
DCHECK_EQ(z_order_, 0);
#endif
return needs_commit;
}
void DCLayerTree::VisualTree::VisualSubtree::GetSwapChainVisualInfoForTesting(
gfx::Transform* transform,
gfx::Point* offset,
gfx::Rect* clip_rect) const {
*transform = transform_;
*offset = gfx::Point() + offset_;
*clip_rect = clip_rect_.value_or(gfx::Rect());
}
DCLayerTree::VisualTree::VisualTree(DCLayerTree* dc_layer_tree)
: dc_layer_tree_(dc_layer_tree) {}
DCLayerTree::VisualTree::~VisualTree() = default;
bool DCLayerTree::VisualTree::UpdateTree(
const std::vector<std::unique_ptr<DCLayerOverlayParams>>& overlays,
bool needs_rebuild_visual_tree) {
size_t old_visual_subtrees_size = visual_subtrees_.size();
if (old_visual_subtrees_size != overlays.size()) {
needs_rebuild_visual_tree = true;
}
Microsoft::WRL::ComPtr<IDCompositionVisual2> root_surface_visual;
bool needs_commit = false;
std::vector<std::unique_ptr<VisualSubtree>> visual_subtrees;
visual_subtrees.resize(overlays.size());
for (size_t i = 0; i < overlays.size(); ++i) {
IUnknown* dcomp_visual_content =
overlays[i]->overlay_image->dcomp_visual_content();
auto it = std::find_if(
visual_subtrees_.begin(), visual_subtrees_.end(),
[dcomp_visual_content](const std::unique_ptr<VisualSubtree>& subtree) {
return subtree &&
subtree->dcomp_visual_content() == dcomp_visual_content;
});
if (it == visual_subtrees_.end()) {
visual_subtrees[i] = std::make_unique<VisualSubtree>();
visual_subtrees[i]->set_z_order(overlays[i]->z_order);
needs_rebuild_visual_tree = true;
} else {
visual_subtrees[i] = std::move(*it);
if (visual_subtrees[i]->z_order() != overlays[i]->z_order) {
visual_subtrees[i]->set_z_order(overlays[i]->z_order);
needs_rebuild_visual_tree = true;
}
}
needs_commit |= visual_subtrees[i]->Update(
dc_layer_tree_->dcomp_device_.Get(),
overlays[i]->overlay_image->dcomp_visual_content(),
overlays[i]->overlay_image->dcomp_surface_serial(),
overlays[i]->quad_rect.OffsetFromOrigin(), overlays[i]->transform,
overlays[i]->clip_rect);
if (overlays[i]->z_order == 0) {
DCHECK(!root_surface_visual);
root_surface_visual = visual_subtrees[i]->content_visual();
}
}
visual_subtrees_ = std::move(visual_subtrees);
if (needs_rebuild_visual_tree) {
TRACE_EVENT0(
"gpu", "DCLayerTree::CommitAndClearPendingOverlays::ReBuildVisualTree");
dc_layer_tree_->dcomp_root_visual_->RemoveAllVisuals();
for (size_t i = 0; i < visual_subtrees_.size(); ++i) {
dc_layer_tree_->dcomp_root_visual_->AddVisual(
visual_subtrees_[i]->container_visual(), FALSE, nullptr);
}
dc_layer_tree_->AddDelegatedInkVisualToTreeIfNeeded(
root_surface_visual.Get());
needs_commit = true;
}
if (needs_commit) {
TRACE_EVENT0("gpu", "DCLayerTree::CommitAndClearPendingOverlays::Commit");
HRESULT hr = dc_layer_tree_->dcomp_device_->Commit();
if (FAILED(hr)) {
DLOG(ERROR) << "Commit failed with error 0x" << std::hex << hr;
return false;
}
}
return true;
}
void DCLayerTree::VisualTree::GetSwapChainVisualInfoForTesting(
size_t index,
gfx::Transform* transform,
gfx::Point* offset,
gfx::Rect* clip_rect) const {
for (size_t i = 0, swapchain_i = 0; i < visual_subtrees_.size(); ++i) {
if (visual_subtrees_[i]->z_order() == 0) {
continue;
}
if (swapchain_i == index) {
visual_subtrees_[i]->GetSwapChainVisualInfoForTesting(
transform, offset, clip_rect);
return;
}
swapchain_i++;
}
}
bool DCLayerTree::CommitAndClearPendingOverlays(
DirectCompositionChildSurfaceWin* root_surface) {
TRACE_EVENT1("gpu", "DCLayerTree::CommitAndClearPendingOverlays",
"num_pending_overlays", pending_overlays_.size());
DCHECK(!needs_rebuild_visual_tree_ || ink_renderer_->HasBeenInitialized());
{
Microsoft::WRL::ComPtr<IDXGISwapChain1> root_swap_chain;
Microsoft::WRL::ComPtr<IDCompositionSurface> root_dcomp_surface;
if (root_surface) {
root_swap_chain = root_surface->swap_chain();
root_dcomp_surface = root_surface->dcomp_surface();
Microsoft::WRL::ComPtr<IUnknown> root_visual_content;
if (root_swap_chain) {
root_visual_content = root_swap_chain;
} else {
root_visual_content = root_dcomp_surface;
}
auto root_params = std::make_unique<DCLayerOverlayParams>();
root_params->z_order = 0;
root_params->overlay_image = DCLayerOverlayImage(
root_surface->GetSize(), std::move(root_visual_content),
root_surface->dcomp_surface_serial());
ScheduleDCLayer(std::move(root_params));
} else {
auto it = std::find_if(
pending_overlays_.begin(), pending_overlays_.end(),
[](const std::unique_ptr<DCLayerOverlayParams>& overlay) {
return overlay->z_order == 0;
});
if (it != pending_overlays_.end()) {
Microsoft::WRL::ComPtr<IUnknown> root_visual_content =
(*it)->overlay_image->dcomp_visual_content();
HRESULT hr = root_visual_content.As(&root_swap_chain);
if (hr == E_NOINTERFACE) {
DCHECK_EQ(nullptr, root_swap_chain);
hr = root_visual_content.As(&root_dcomp_surface);
}
CHECK_EQ(S_OK, hr);
} else {
DLOG(WARNING) << "No root surface in overlay list";
}
}
if (root_swap_chain != root_swap_chain_ ||
root_dcomp_surface != root_dcomp_surface_) {
DCHECK(!(root_swap_chain && root_dcomp_surface));
root_swap_chain_ = std::move(root_swap_chain);
root_dcomp_surface_ = std::move(root_dcomp_surface);
needs_rebuild_visual_tree_ = true;
}
}
std::vector<std::unique_ptr<DCLayerOverlayParams>> overlays;
std::swap(pending_overlays_, overlays);
const size_t num_swap_chain_presenters =
std::count_if(overlays.begin(), overlays.end(), [](const auto& overlay) {
return NeedSwapChainPresenter(overlay.get());
});
if (video_swap_chains_.size() != num_swap_chain_presenters) {
video_swap_chains_.resize(num_swap_chain_presenters);
needs_rebuild_visual_tree_ = true;
}
std::sort(overlays.begin(), overlays.end(),
[](const auto& a, const auto& b) -> bool {
return a->z_order < b->z_order;
});
auto video_swap_iter = video_swap_chains_.begin();
for (size_t i = 0; i < overlays.size(); ++i) {
if (!NeedSwapChainPresenter(overlays[i].get())) {
continue;
}
auto& video_swap_chain = *(video_swap_iter++);
if (!video_swap_chain) {
video_swap_chain = std::make_unique<SwapChainPresenter>(
this, window_, d3d11_device_, dcomp_device_);
if (frame_rate_ > 0)
video_swap_chain->SetFrameRate(frame_rate_);
}
gfx::Transform transform;
gfx::Rect clip_rect;
if (!video_swap_chain->PresentToSwapChain(*overlays[i], &transform,
&clip_rect)) {
DLOG(ERROR) << "PresentToSwapChain failed";
return false;
}
overlays[i]->transform = transform;
if (overlays[i]->clip_rect.has_value())
overlays[i]->clip_rect = clip_rect;
overlays[i]->overlay_image = DCLayerOverlayImage(
video_swap_chain->content_size(), video_swap_chain->content());
}
bool status = BuildVisualTreeHelper(overlays, needs_rebuild_visual_tree_);
needs_rebuild_visual_tree_ = false;
return status;
}
bool DCLayerTree::BuildVisualTreeHelper(
const std::vector<std::unique_ptr<DCLayerOverlayParams>>& overlays,
bool needs_rebuild_visual_tree) {
bool use_visual_tree_optimization =
base::FeatureList::IsEnabled(kDCVisualTreeOptimization) &&
!ink_renderer_->HasBeenInitialized();
if (visual_tree_ &&
use_visual_tree_optimization != visual_tree_->tree_optimized()) {
visual_tree_ = nullptr;
}
if (use_visual_tree_optimization) {
NOTREACHED();
return false;
}
if (!visual_tree_) {
visual_tree_ = std::make_unique<VisualTree>(this);
}
return visual_tree_->UpdateTree(overlays, needs_rebuild_visual_tree);
}
bool DCLayerTree::ScheduleDCLayer(
std::unique_ptr<DCLayerOverlayParams> params) {
pending_overlays_.push_back(std::move(params));
return true;
}
void DCLayerTree::SetFrameRate(float frame_rate) {
frame_rate_ = frame_rate;
for (size_t ii = 0; ii < video_swap_chains_.size(); ++ii)
video_swap_chains_[ii]->SetFrameRate(frame_rate);
}
bool DCLayerTree::SupportsDelegatedInk() {
return ink_renderer_->DelegatedInkIsSupported(dcomp_device_);
}
bool DCLayerTree::InitializeInkRenderer() {
return ink_renderer_->Initialize(dcomp_device_, root_swap_chain_);
}
void DCLayerTree::AddDelegatedInkVisualToTreeIfNeeded(
IDCompositionVisual2* root_surface_visual) {
if (!ink_renderer_->HasBeenInitialized()) {
return;
}
if (!InitializeInkRenderer()) {
return;
}
DCHECK(SupportsDelegatedInk());
root_surface_visual->AddVisual(ink_renderer_->GetInkVisual(), FALSE, nullptr);
ink_renderer_->SetNeedsDcompPropertiesUpdate();
}
void DCLayerTree::SetDelegatedInkTrailStartPoint(
std::unique_ptr<gfx::DelegatedInkMetadata> metadata) {
DCHECK(SupportsDelegatedInk());
if (!ink_renderer_->HasBeenInitialized()) {
if (!InitializeInkRenderer())
return;
needs_rebuild_visual_tree_ = true;
}
ink_renderer_->SetDelegatedInkTrailStartPoint(std::move(metadata));
}
void DCLayerTree::InitDelegatedInkPointRendererReceiver(
mojo::PendingReceiver<gfx::mojom::DelegatedInkPointRenderer>
pending_receiver) {
DCHECK(SupportsDelegatedInk());
ink_renderer_->InitMessagePipeline(std::move(pending_receiver));
}
}