| // 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 <utility> |
| |
| #include "base/feature_list.h" |
| #include "base/logging.h" |
| #include "base/memory/ptr_util.h" |
| #include "base/metrics/histogram_functions.h" |
| #include "base/trace_event/trace_event.h" |
| #include "ui/gfx/color_space_win.h" |
| #include "ui/gfx/geometry/rect_conversions.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) { |
| if (overlay->background_color.has_value()) { |
| return false; |
| } |
| CHECK(overlay->overlay_image); |
| return overlay->overlay_image->type() != |
| DCLayerOverlayType::kDCompVisualContent; |
| } |
| |
| // TODO(http://crbug.com/1380822): Implement dcomp visual tree optimization. |
| BASE_FEATURE(kDCVisualTreeOptimization, |
| "DCVisualTreeOptimization", |
| base::FEATURE_DISABLED_BY_DEFAULT); |
| } // namespace |
| |
| 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 force_dcomp_triple_buffer_video_swap_chain, |
| 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), |
| force_dcomp_triple_buffer_video_swap_chain_( |
| force_dcomp_triple_buffer_video_swap_chain), |
| 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; |
| } |
| |
| Microsoft::WRL::ComPtr<IDCompositionVisual2> dcomp_root_visual; |
| hr = dcomp_device_->CreateVisual(&dcomp_root_visual); |
| CHECK_EQ(hr, S_OK); |
| hr = dcomp_root_visual.As(&dcomp_root_visual_); |
| 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_ = 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) { |
| // This can fail if the D3D device is "Microsoft Basic Display Adapter". |
| 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; |
| // It might fail again next time. Disable overlay support so |
| // overlay processor will stop sending down overlay frames. |
| 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; |
| // It might fail again next time. Disable overlay support so |
| // overlay processor will stop sending down overlay frames. |
| DisableDirectCompositionOverlays(); |
| return nullptr; |
| } |
| // Auto stream processing (the default) can hurt power consumption. |
| 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; |
| } |
| |
| // Return properties of non root swap chain at given index. |
| 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, // IN-TEST |
| offset, clip_rect); |
| } |
| } |
| |
| DCLayerTree::VisualTree::VisualSubtree::VisualSubtree() = default; |
| DCLayerTree::VisualTree::VisualSubtree::~VisualSubtree() = default; |
| |
| 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, |
| absl::optional<SkColor4f> content_tint_color, |
| const gfx::Rect& content_rect, |
| 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 absl::optional<gfx::Rect>& clip_rect_in_root) { |
| bool needs_commit = false; |
| |
| // 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; |
| |
| if (!clip_visual_) { |
| needs_commit = true; |
| |
| // All the visual are created together on the first |Update|. |
| CHECK(!transform_visual_); |
| CHECK(!content_visual_); |
| |
| Microsoft::WRL::ComPtr<IDCompositionVisual2> clip_visual; |
| hr = dcomp_device->CreateVisual(&clip_visual); |
| CHECK_EQ(hr, S_OK); |
| hr = clip_visual.As(&clip_visual_); |
| CHECK_EQ(hr, S_OK); |
| |
| Microsoft::WRL::ComPtr<IDCompositionVisual2> rounded_corners_visual; |
| hr = dcomp_device->CreateVisual(&rounded_corners_visual); |
| CHECK_EQ(hr, S_OK); |
| hr = rounded_corners_visual.As(&rounded_corners_visual_); |
| CHECK_EQ(hr, S_OK); |
| |
| Microsoft::WRL::ComPtr<IDCompositionVisual2> transform_visual; |
| hr = dcomp_device->CreateVisual(&transform_visual); |
| CHECK_EQ(hr, S_OK); |
| hr = transform_visual.As(&transform_visual_); |
| CHECK_EQ(hr, S_OK); |
| |
| Microsoft::WRL::ComPtr<IDCompositionVisual2> content_visual; |
| hr = dcomp_device->CreateVisual(&content_visual); |
| CHECK_EQ(hr, S_OK); |
| hr = content_visual.As(&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(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 = content_visual_->SetBorderMode(DCOMPOSITION_BORDER_MODE_HARD); |
| 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()) { |
| // 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. |
| 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 (opacity_ != opacity) { |
| opacity_ = opacity; |
| needs_commit = true; |
| |
| if (opacity_ != 1) { |
| hr = 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 = 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_ != rounded_corner_bounds) { |
| rounded_corner_bounds_ = rounded_corner_bounds; |
| needs_commit = true; |
| |
| 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 (transform_ != quad_to_root_transform) { |
| transform_ = quad_to_root_transform; |
| needs_commit = true; |
| |
| DCHECK(transform_.IsFlat()); |
| D2D_MATRIX_3X2_F matrix = |
| // D2D_MATRIX_3x2_F is row-major. |
| 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 = Microsoft::WRL::ComPtr<IDCompositionVisual>(transform_visual_) |
| ->SetTransform(matrix); |
| CHECK_EQ(hr, S_OK); |
| } |
| |
| if (nearest_neighbor_filter_ != nearest_neighbor_filter) { |
| nearest_neighbor_filter_ = nearest_neighbor_filter; |
| needs_commit = true; |
| |
| 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_ != image_size || content_rect_ != content_rect || |
| quad_rect_ != quad_rect) { |
| image_size_ = image_size; |
| content_rect_ = content_rect; |
| quad_rect_ = quad_rect; |
| needs_commit = true; |
| |
| if (content_rect_.Contains(gfx::Rect(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 = quad_rect_.width() != content_rect_.width() || |
| quad_rect_.height() != content_rect_.height(); |
| if (needs_offset || needs_scale) { |
| const float scale_x = static_cast<float>(quad_rect_.width()) / |
| static_cast<float>(content_rect_.width()); |
| const float scale_y = static_cast<float>(quad_rect_.height()) / |
| static_cast<float>(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 (content_tint_color_ != content_tint_color) { |
| content_tint_color_ = content_tint_color; |
| needs_commit = true; |
| |
| SkColor4f color = content_tint_color_.value_or(SkColors::kWhite); |
| |
| if (color == SkColors::kWhite) { |
| // White-colored tint is the same as no effect. |
| hr = content_visual_->SetEffect(nullptr); |
| CHECK_EQ(hr, S_OK); |
| } else { |
| // Transforms an opaque white color to the background color. |
| D2D_MATRIX_5X4_F white_to_color = D2D1::Matrix5x4F(); |
| white_to_color._11 = color.fR; |
| white_to_color._22 = color.fG; |
| white_to_color._33 = color.fB; |
| white_to_color._44 = color.fA; |
| |
| Microsoft::WRL::ComPtr<IDCompositionColorMatrixEffect> effect; |
| hr = dcomp_device->CreateColorMatrixEffect(&effect); |
| CHECK_EQ(hr, S_OK); |
| hr = effect->SetMatrix(white_to_color); |
| CHECK_EQ(hr, S_OK); |
| hr = content_visual_->SetEffect(effect.Get()); |
| 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) { |
| dcomp_surface_serial_ = dcomp_surface_serial; |
| needs_commit = true; |
| |
| // The DComp surface has been drawn to and needs a commit to show its |
| // update. No visual changes are needed in this case. |
| } |
| |
| // Properties that derive from multiple other properties can only change if |
| // something else in the subtree has changed, so we can guard recalculating |
| // these behind |needs_commit|. |
| if (needs_commit) { |
| const float kNeedsSoftBorderTolerance = 0.001; |
| const bool content_soft_borders = |
| !transform_.Preserves2dAxisAlignment() || |
| !gfx::IsNearestRectWithinDistance( |
| transform_.MapRect(gfx::RectF(quad_rect_)), |
| kNeedsSoftBorderTolerance); |
| if (content_soft_borders_ != content_soft_borders) { |
| content_soft_borders_ = content_soft_borders; |
| |
| hr = content_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* transform, |
| gfx::Point* offset, |
| gfx::Rect* clip_rect) const { |
| *transform = transform_; |
| *offset = quad_rect_.origin(); |
| *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) { |
| // Grow or shrink list of visual subtrees to match pending overlays. |
| size_t old_visual_subtrees_size = visual_subtrees_.size(); |
| if (old_visual_subtrees_size != overlays.size()) { |
| needs_rebuild_visual_tree = true; |
| } |
| |
| // Visual for root surface. Cache it to add DelegatedInk visual if needed. |
| Microsoft::WRL::ComPtr<IDCompositionVisual3> root_surface_visual; |
| bool needs_commit = false; |
| std::vector<std::unique_ptr<VisualSubtree>> visual_subtrees; |
| visual_subtrees.resize(overlays.size()); |
| // Build or update visual subtree for each overlay. |
| for (size_t i = 0; i < overlays.size(); ++i) { |
| const bool is_root_plane = overlays[i]->z_order == 0; |
| if (!is_root_plane && overlays[i]->overlay_image) { |
| TRACE_EVENT2( |
| "gpu", "DCLayerTree::VisualTree::UpdateOverlay", "image_type", |
| DCLayerOverlayTypeToString(overlays[i]->overlay_image->type()), |
| "size", overlays[i]->content_rect.size().ToString()); |
| } |
| |
| IUnknown* dcomp_visual_content = |
| overlays[i]->overlay_image->dcomp_visual_content(); |
| // Find matching subtree for each overlay. If subtree is found, move it |
| // from visual subtrees of previous frame to visual subtrees of this frame. |
| 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()) { |
| // This overlay's visual content does not present in the old visual tree. |
| // Instantiate a new visual subtree. |
| visual_subtrees[i] = std::make_unique<VisualSubtree>(); |
| visual_subtrees[i]->set_z_order(overlays[i]->z_order); |
| needs_rebuild_visual_tree = true; |
| } else { |
| // Move visual subtree from the old subtrees to new subtrees. |
| 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); |
| // Z-order is a property of the root visual's child list, not any |
| // property on the subtree's nodes. If it changes, we need to rebuild |
| // the tree. |
| needs_rebuild_visual_tree = true; |
| } |
| } |
| // We don't need to set |needs_rebuild_visual_tree| here since that is only |
| // needed when the root visual's children need to be reordered. |Update| |
| // only affects the subtree for each child, so only a commit is needed in |
| // this case. |
| 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]->overlay_image->size(), overlays[i]->background_color, |
| overlays[i]->content_rect, overlays[i]->quad_rect, |
| overlays[i]->nearest_neighbor_filter, overlays[i]->transform, |
| overlays[i]->rounded_corner_bounds, overlays[i]->opacity, |
| overlays[i]->clip_rect); |
| |
| // Zero z_order represents root layer. |
| if (overlays[i]->z_order == 0) { |
| // Verify we have single root visual layer. |
| DCHECK(!root_surface_visual); |
| root_surface_visual = visual_subtrees[i]->content_visual(); |
| } |
| } |
| // Update visual_subtrees_ with new values. |
| visual_subtrees_ = std::move(visual_subtrees); |
| |
| // Note: needs_rebuild_visual_tree might be set in this method, |
| // |DCLayerTree::CommitAndClearPendingOverlays|, and can also be set in |
| // |DCLayerTree::SetDelegatedInkTrailStartPoint| to add a delegated ink visual |
| // into the root surface's visual. |
| if (needs_rebuild_visual_tree) { |
| TRACE_EVENT0( |
| "gpu", "DCLayerTree::CommitAndClearPendingOverlays::ReBuildVisualTree"); |
| |
| // Rebuild root visual's child list. |
| dc_layer_tree_->dcomp_root_visual_->RemoveAllVisuals(); |
| |
| for (size_t i = 0; i < visual_subtrees_.size(); ++i) { |
| // We call AddVisual with insertAbove FALSE and referenceVisual nullptr |
| // which is equivalent to saying that the visual should be below no |
| // other visual, or in other words it should be above all other visuals. |
| dc_layer_tree_->dcomp_root_visual_->AddVisual( |
| visual_subtrees_[i]->container_visual(), FALSE, nullptr); |
| } |
| |
| if (root_surface_visual) { |
| 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) { |
| // Skip root layer. |
| if (visual_subtrees_[i]->z_order() == 0) { |
| continue; |
| } |
| |
| if (swapchain_i == index) { |
| visual_subtrees_[i]->GetSwapChainVisualInfoForTesting( // IN-TEST |
| 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; |
| } |
| |
| // Add a placeholder overlay for the root surface, at a z-order of 0. |
| 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()); |
| root_params->content_rect = gfx::Rect(root_params->overlay_image->size()); |
| root_params->quad_rect = gfx::Rect(root_params->overlay_image->size()); |
| 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() && (*it)->overlay_image) { |
| 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 { |
| // Note: this is allowed in tests, but not expected otherwise. |
| 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); |
| |
| // Grow or shrink list of swap chain presenters to match pending overlays. |
| const size_t num_swap_chain_presenters = |
| std::count_if(overlays.begin(), overlays.end(), [](const auto& overlay) { |
| return NeedSwapChainPresenter(overlay.get()); |
| }); |
| // Grow or shrink list of swap chain presenters to match pending overlays. |
| if (video_swap_chains_.size() != num_swap_chain_presenters) { |
| video_swap_chains_.resize(num_swap_chain_presenters); |
| // If we need to grow or shrink swap chain presenters, we'll need to add or |
| // remove visuals. |
| needs_rebuild_visual_tree_ = true; |
| } |
| |
| // Sort layers by z-order. |
| std::sort(overlays.begin(), overlays.end(), |
| [](const auto& a, const auto& b) -> bool { |
| return a->z_order < b->z_order; |
| }); |
| |
| // |overlays| and |video_swap_chains_| do not have a 1:1 mapping because the |
| // root surface placeholder overlay does not have SwapChainPresenter, so there |
| // is one less element in |video_swap_chains_| than |overlays|. |
| auto video_swap_iter = video_swap_chains_.begin(); |
| |
| // Populate |overlays| with information required to build dcomp visual tree. |
| for (auto& overlay : overlays) { |
| if (NeedSwapChainPresenter(overlay.get())) { |
| // Present to swap chain and update the overlay with transform, clip |
| // and content. |
| auto& video_swap_chain = *(video_swap_iter++); |
| if (!video_swap_chain) { |
| // TODO(sunnyps): Try to find a matching swap chain based on size, type |
| // of swap chain, gl image, etc. |
| 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(*overlay, &transform, |
| &clip_rect)) { |
| DLOG(ERROR) << "PresentToSwapChain failed"; |
| return false; |
| } |
| // |SwapChainPresenter| may have changed the size of the overlay's quad |
| // rect, e.g. to present to a swap chain exactly the size of the display |
| // rect when the source video is larger. |
| overlay->transform = transform; |
| overlay->content_rect = gfx::Rect(video_swap_chain->content_size()); |
| overlay->quad_rect.set_size(video_swap_chain->content_size()); |
| if (overlay->clip_rect.has_value()) { |
| overlay->clip_rect = clip_rect; |
| } |
| overlay->overlay_image = DCLayerOverlayImage( |
| video_swap_chain->content_size(), video_swap_chain->content()); |
| } else if (overlay->background_color.has_value()) { |
| CHECK(!overlay->overlay_image.has_value()); |
| |
| gfx::Size content_size; |
| Microsoft::WRL::ComPtr<IDCompositionSurface> solid_color_surface = |
| GetOrCreateSolidWhiteTexture(content_size).get(); |
| if (!solid_color_surface) { |
| DLOG(ERROR) << "Could not get solid color surface."; |
| return false; |
| } |
| |
| // Solid color overlay case is special-cased in DCLayerTree as a shared |
| // opaque white surface that's tinted to the desired color. |
| overlay->overlay_image = |
| DCLayerOverlayImage(content_size, std::move(solid_color_surface)); |
| overlay->content_rect = gfx::Rect(content_size); |
| } |
| } |
| |
| 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) { |
| // TODO(http://crbug.com/1380822): Enable optimization when delegated ink |
| // trails is active. |
| bool use_visual_tree_optimization = |
| base::FeatureList::IsEnabled(kDCVisualTreeOptimization) && |
| !ink_renderer_->HasBeenInitialized(); |
| |
| // Optimized and not optimized trees are incompatible and cannot be reused |
| // for incremental updates. Rebuild visual tree if switching between optimized |
| // and not optimized trees or vice versa. It will be removed once delegated |
| // ink trails work with optimized DCOMP trees. |
| if (visual_tree_ && |
| use_visual_tree_optimization != visual_tree_->tree_optimized()) { |
| visual_tree_ = nullptr; |
| } |
| |
| // TODO(http://crbug.com/1380822): Implement tree optimization where the |
| // tree is built incrementally and does not require full rebuild. |
| 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( |
| IDCompositionVisual3* root_surface_visual) { |
| // Only add the ink visual to the tree if it has already been initialized. |
| // It will only have been initialized if delegated ink has been used, so |
| // this ensures the visual is only added when it is needed. The ink renderer |
| // must be updated so that if the root swap chain or dcomp device have |
| // changed the ink visual and delegated ink object can be updated |
| // accordingly. |
| if (!ink_renderer_->HasBeenInitialized()) { |
| return; |
| } |
| |
| // Reinitialize the ink renderer in case the root swap chain or dcomp |
| // device changed since initialization. |
| if (!InitializeInkRenderer()) { |
| return; |
| } |
| |
| DCHECK(SupportsDelegatedInk()); |
| root_surface_visual->AddVisual(ink_renderer_->GetInkVisual(), FALSE, nullptr); |
| // Adding the ink visual to a new visual tree invalidates all previously set |
| // properties. Therefore, force update. |
| ink_renderer_->SetNeedsDcompPropertiesUpdate(); |
| } |
| |
| void DCLayerTree::SetDelegatedInkTrailStartPoint( |
| std::unique_ptr<gfx::DelegatedInkMetadata> metadata) { |
| DCHECK(SupportsDelegatedInk()); |
| |
| if (!ink_renderer_->HasBeenInitialized()) { |
| if (!InitializeInkRenderer()) |
| return; |
| // This ensures that the delegated ink visual is added to the tree after |
| // the root visual is created, during |
| // DCLayerTree::CommitAndClearPendingOverlays |
| 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)); |
| } |
| |
| raw_ptr<IDCompositionSurface> DCLayerTree::GetOrCreateSolidWhiteTexture( |
| gfx::Size& resource_size_in_pixels) { |
| resource_size_in_pixels = gfx::Size(1, 1); |
| |
| if (!solid_color_texture_) { |
| Microsoft::WRL::ComPtr<IDCompositionSurface> solid_color_texture; |
| |
| HRESULT hr = S_OK; |
| hr = dcomp_device_->CreateSurface( |
| resource_size_in_pixels.width(), resource_size_in_pixels.height(), |
| gfx::ColorSpaceWin::GetDXGIFormat(gfx::ColorSpace::CreateSRGB()), |
| DXGI_ALPHA_MODE_IGNORE, &solid_color_texture); |
| if (FAILED(hr)) { |
| LOG(ERROR) << "CreateSurface failed: " |
| << logging::SystemErrorCodeToString(hr); |
| return nullptr; |
| } |
| |
| RECT update_rect = D2D1::Rect(0, 0, resource_size_in_pixels.width(), |
| resource_size_in_pixels.height()); |
| Microsoft::WRL::ComPtr<ID3D11Texture2D> draw_texture; |
| POINT update_offset; |
| hr = solid_color_texture->BeginDraw( |
| &update_rect, IID_PPV_ARGS(&draw_texture), &update_offset); |
| if (FAILED(hr)) { |
| LOG(ERROR) << "BeginDraw failed: " |
| << logging::SystemErrorCodeToString(hr); |
| return nullptr; |
| } |
| |
| Microsoft::WRL::ComPtr<ID3D11RenderTargetView> rtv; |
| hr = d3d11_device_->CreateRenderTargetView(draw_texture.Get(), nullptr, |
| &rtv); |
| if (FAILED(hr)) { |
| LOG(ERROR) << "CreateRenderTargetView failed: " |
| << logging::SystemErrorCodeToString(hr); |
| return nullptr; |
| } |
| |
| Microsoft::WRL::ComPtr<ID3D11DeviceContext> immediate_context; |
| d3d11_device_->GetImmediateContext(&immediate_context); |
| FLOAT white[4] = {1.0, 1.0, 1.0, 1.0}; |
| immediate_context->ClearRenderTargetView(rtv.Get(), white); |
| |
| hr = solid_color_texture->EndDraw(); |
| if (FAILED(hr)) { |
| LOG(ERROR) << "EndDraw failed: " << logging::SystemErrorCodeToString(hr); |
| return nullptr; |
| } |
| |
| solid_color_texture_ = std::move(solid_color_texture); |
| } |
| |
| return solid_color_texture_.Get(); |
| } |
| } // namespace gl |