| // Copyright 2022 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/dcomp_presenter.h" |
| |
| #include <wrl/client.h> |
| #include <wrl/implements.h> |
| |
| #include <limits> |
| #include <memory> |
| #include <tuple> |
| #include <type_traits> |
| #include <variant> |
| |
| #include "base/compiler_specific.h" |
| #include "base/containers/flat_set.h" |
| #include "base/containers/span.h" |
| #include "base/functional/callback_helpers.h" |
| #include "base/memory/raw_ptr.h" |
| #include "base/run_loop.h" |
| #include "base/strings/string_number_conversions.h" |
| #include "base/strings/string_util.h" |
| #include "base/synchronization/waitable_event.h" |
| #include "base/test/power_monitor_test.h" |
| #include "base/test/scoped_feature_list.h" |
| #include "base/win/windows_version.h" |
| #include "testing/gmock/include/gmock/gmock.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| #include "ui/base/test/skia_gold_matching_algorithm.h" |
| #include "ui/base/test/skia_gold_pixel_diff.h" |
| #include "ui/base/win/hidden_window.h" |
| #include "ui/gfx/frame_data.h" |
| #include "ui/gfx/geometry/rect_conversions.h" |
| #include "ui/gfx/geometry/size.h" |
| #include "ui/gfx/geometry/test/geometry_util.h" |
| #include "ui/gfx/geometry/transform.h" |
| #include "ui/gfx/geometry/vector2d_conversions.h" |
| #include "ui/gfx/geometry/vector2d_f.h" |
| #include "ui/gfx/overlay_layer_id.h" |
| #include "ui/gfx/test/sk_color_eq.h" |
| #include "ui/gl/dc_layer_overlay_params.h" |
| #include "ui/gl/dc_layer_tree.h" |
| #include "ui/gl/dcomp_surface_proxy.h" |
| #include "ui/gl/direct_composition_support.h" |
| #include "ui/gl/gl_angle_util_win.h" |
| #include "ui/gl/gl_bindings.h" |
| #include "ui/gl/gl_context.h" |
| #include "ui/gl/gl_switches.h" |
| #include "ui/gl/gl_version_info.h" |
| #include "ui/gl/init/gl_factory.h" |
| #include "ui/gl/swap_chain_presenter.h" |
| #include "ui/gl/test/gl_test_helper.h" |
| #include "ui/gl/test/gl_test_support.h" |
| #include "ui/platform_window/platform_window_delegate.h" |
| #include "ui/platform_window/win/win_window.h" |
| |
| namespace gfx { |
| // Used by gtest to print expectation values. |
| void PrintTo(const SwapResult& swap_result, ::std::ostream* os) { |
| switch (swap_result) { |
| case SwapResult::SWAP_ACK: |
| *os << "SWAP_ACK"; |
| return; |
| case SwapResult::SWAP_FAILED: |
| *os << "SWAP_FAILED"; |
| return; |
| case SwapResult::SWAP_SKIPPED: |
| *os << "SWAP_SKIPPED"; |
| return; |
| case SwapResult::SWAP_NAK_RECREATE_BUFFERS: |
| *os << "SWAP_NAK_RECREATE_BUFFERS"; |
| return; |
| case SwapResult::SWAP_NON_SIMPLE_OVERLAYS_FAILED: |
| *os << "SWAP_NON_SIMPLE_OVERLAYS_FAILED"; |
| return; |
| } |
| NOTREACHED(); |
| } |
| } // namespace gfx |
| |
| namespace gl { |
| namespace { |
| |
| constexpr const char* kSkiaGoldPixelDiffCorpus = "chrome-gpu-gtest"; |
| |
| class TestPlatformDelegate : public ui::PlatformWindowDelegate { |
| public: |
| // ui::PlatformWindowDelegate implementation. |
| void OnBoundsChanged(const BoundsChange& change) override {} |
| void OnDamageRect(const gfx::Rect& damaged_region) override {} |
| void DispatchEvent(ui::Event* event) override {} |
| void OnCloseRequest() override {} |
| void OnClosed() override {} |
| void OnWindowStateChanged(ui::PlatformWindowState old_state, |
| ui::PlatformWindowState new_state) override {} |
| void OnLostCapture() override {} |
| void OnAcceleratedWidgetAvailable(gfx::AcceleratedWidget widget) override {} |
| void OnWillDestroyAcceleratedWidget() override {} |
| void OnAcceleratedWidgetDestroyed() override {} |
| void OnActivationChanged(bool active) override {} |
| void OnCursorUpdate() override {} |
| }; |
| |
| void RunPendingTasks(scoped_refptr<base::TaskRunner> task_runner) { |
| base::WaitableEvent done(base::WaitableEvent::ResetPolicy::AUTOMATIC, |
| base::WaitableEvent::InitialState::NOT_SIGNALED); |
| task_runner->PostTask( |
| FROM_HERE, BindOnce(&base::WaitableEvent::Signal, Unretained(&done))); |
| done.Wait(); |
| } |
| |
| void DestroyPresenter(scoped_refptr<DCompPresenter> presenter) { |
| scoped_refptr<base::TaskRunner> task_runner = |
| presenter->GetWindowTaskRunnerForTesting(); |
| DCHECK(presenter->HasOneRef()); |
| |
| presenter.reset(); |
| |
| // Ensure that the ChildWindowWin posts the task to delete the thread to the |
| // main loop before doing RunUntilIdle. Otherwise the child threads could |
| // outlive the main thread. |
| RunPendingTasks(task_runner); |
| |
| base::RunLoop().RunUntilIdle(); |
| } |
| |
| Microsoft::WRL::ComPtr<ID3D11Texture2D> CreateNV12Texture( |
| const Microsoft::WRL::ComPtr<ID3D11Device>& d3d11_device, |
| const gfx::Size& size) { |
| D3D11_TEXTURE2D_DESC desc = {}; |
| desc.Width = size.width(); |
| desc.Height = size.height(); |
| desc.MipLevels = 1; |
| desc.ArraySize = 1; |
| desc.Format = DXGI_FORMAT_NV12; |
| desc.Usage = D3D11_USAGE_DEFAULT; |
| desc.SampleDesc.Count = 1; |
| desc.BindFlags = 0; |
| desc.MiscFlags = 0; |
| |
| std::vector<char> image_data(size.width() * size.height() * 3 / 2); |
| // Y, U, and V should all be 160. Output color should be pink. |
| UNSAFE_TODO( |
| memset(&image_data[0], 160, size.width() * size.height() * 3 / 2)); |
| |
| D3D11_SUBRESOURCE_DATA data = {}; |
| data.pSysMem = (const void*)&image_data[0]; |
| data.SysMemPitch = size.width(); |
| |
| Microsoft::WRL::ComPtr<ID3D11Texture2D> texture; |
| HRESULT hr = d3d11_device->CreateTexture2D(&desc, &data, &texture); |
| EXPECT_HRESULT_SUCCEEDED(hr); |
| return texture; |
| } |
| |
| Microsoft::WRL::ComPtr<ID3D11Texture2D> CreateP010Texture( |
| const Microsoft::WRL::ComPtr<ID3D11Device>& d3d11_device, |
| const gfx::Size& size) { |
| D3D11_TEXTURE2D_DESC desc = {}; |
| desc.Width = size.width(); |
| desc.Height = size.height(); |
| desc.MipLevels = 1; |
| desc.ArraySize = 1; |
| desc.Format = DXGI_FORMAT_P010; |
| desc.Usage = D3D11_USAGE_DEFAULT; |
| desc.SampleDesc.Count = 1; |
| desc.BindFlags = 0; |
| desc.MiscFlags = 0; |
| |
| // Y, U, and V should all be 160. Output color should be pink. |
| std::vector<uint16_t> image_data(size.width() * size.height() * 3 / 2, |
| 0xA000); |
| D3D11_SUBRESOURCE_DATA data = {}; |
| data.pSysMem = (const void*)&image_data[0]; |
| data.SysMemPitch = size.width() * 2; |
| |
| Microsoft::WRL::ComPtr<ID3D11Texture2D> texture; |
| HRESULT hr = d3d11_device->CreateTexture2D(&desc, &data, &texture); |
| EXPECT_HRESULT_SUCCEEDED(hr); |
| return texture; |
| } |
| |
| // The precise colors may differ depending on the video processor, so allow a |
| // margin for error. |
| const int kMaxColorChannelDeviation = 10; |
| |
| void ClearRect(IDCompositionSurface* surface, |
| const gfx::Rect& update_rect, |
| SkColor4f update_color) { |
| Microsoft::WRL::ComPtr<ID3D11Device> d3d11_device = |
| GetDirectCompositionD3D11Device(); |
| Microsoft::WRL::ComPtr<ID3D11DeviceContext> immediate_context; |
| d3d11_device->GetImmediateContext(&immediate_context); |
| |
| HRESULT hr = S_OK; |
| |
| Microsoft::WRL::ComPtr<ID3D11Texture2D> update_texture; |
| RECT rect = update_rect.ToRECT(); |
| POINT update_offset; |
| hr = surface->BeginDraw(&rect, IID_PPV_ARGS(&update_texture), &update_offset); |
| CHECK_EQ(S_OK, hr); |
| |
| Microsoft::WRL::ComPtr<ID3D11RenderTargetView> rtv; |
| hr = |
| d3d11_device->CreateRenderTargetView(update_texture.Get(), nullptr, &rtv); |
| CHECK_EQ(S_OK, hr); |
| |
| immediate_context->ClearRenderTargetView(rtv.Get(), |
| update_color.premul().vec()); |
| |
| hr = surface->EndDraw(); |
| CHECK_EQ(S_OK, hr); |
| } |
| |
| // Create an overlay image with an initial color and rectangles, drawn using the |
| // painter's algorithm. |
| DCLayerOverlayImage CreateDCompSurface( |
| const gfx::Size& surface_size, |
| SkColor4f initial_color, |
| std::vector<std::pair<gfx::Rect, SkColor4f>> rectangles_back_to_front = |
| {}) { |
| HRESULT hr = S_OK; |
| |
| Microsoft::WRL::ComPtr<IDCompositionDevice2> dcomp_device = |
| GetDirectCompositionDevice(); |
| |
| Microsoft::WRL::ComPtr<IDCompositionSurface> surface; |
| hr = dcomp_device->CreateSurface( |
| surface_size.width(), surface_size.height(), DXGI_FORMAT_B8G8R8A8_UNORM, |
| initial_color.isOpaque() ? DXGI_ALPHA_MODE_IGNORE |
| : DXGI_ALPHA_MODE_PREMULTIPLIED, |
| &surface); |
| CHECK_EQ(S_OK, hr); |
| |
| // Add a rect that initializes the whole surface to |initial_color|. |
| rectangles_back_to_front.insert(rectangles_back_to_front.begin(), |
| {gfx::Rect(surface_size), initial_color}); |
| |
| for (const auto& [draw_rect, color] : rectangles_back_to_front) { |
| CHECK(gfx::Rect(surface_size).Contains(draw_rect)); |
| ClearRect(surface.Get(), draw_rect, color); |
| } |
| |
| return DCLayerOverlayImage(surface_size, surface); |
| } |
| |
| // Create a |DCLayerOverlayParams| from an |image| and set the |content_rect| to |
| // the bounds of |image|, or |content_rect_override|, if set. |
| DCLayerOverlayParams CreateParamsFromImage( |
| DCLayerOverlayImage image, |
| std::optional<gfx::RectF> content_rect_override = {}) { |
| DCLayerOverlayParams params; |
| params.content_rect = |
| content_rect_override.value_or(gfx::RectF(image.size())); |
| params.overlay_image = std::move(image); |
| return params; |
| } |
| |
| } // namespace |
| |
| // Test parameters that affect all DCompPresenter tests. |
| struct GlobalParam { |
| bool use_gpu_vsync = false; |
| }; |
| |
| void PrintTo(const GlobalParam& param, std::ostream* os) { |
| if (param.use_gpu_vsync) { |
| *os << "GpuVsyncOn"; |
| } else { |
| *os << "GpuVsyncOff"; |
| } |
| } |
| |
| // Base class that provides test parameterization intended for all |
| // DCompPresenter tests. |
| // |
| // If a test suite does not need its own parameterization, it should extend |
| // `DCompPresenterTestBase<>`. Otherwise, it should provide a `Param` type and a |
| // way for gtest to print it. |
| // |
| // Instantiations of derived test suites should look like: |
| // |
| // INSTANTIATE_TEST_SUITE_P(, |
| // DCompPresenterTest, |
| // DCompPresenterTest::GetValues(), |
| // &DCompPresenterTest::GetParamName); |
| template <class Param = std::monostate> |
| class DCompPresenterTestBase |
| : public testing::TestWithParam<std::tuple<GlobalParam, Param>> { |
| public: |
| // Combine the values generator for `Param` (if present) with the generator |
| // for the global parameters. |
| template <typename... Generator> |
| static auto GetValues(const Generator&... g) { |
| if constexpr (std::is_same_v<Param, std::monostate>) { |
| static_assert(sizeof...(g) == 0, |
| "GetValues should take no parameters because the test " |
| "suite is not parameterized."); |
| return testing::Combine(testing::ConvertGenerator(testing::Bool()), |
| testing::Values(std::monostate())); |
| } else { |
| static_assert(sizeof...(g) == 1, |
| "`GetValues` requires a values generator for `Param`."); |
| return testing::Combine(testing::ConvertGenerator(testing::Bool()), g...); |
| } |
| } |
| |
| static const Param& GetTestParam() { |
| return std::get<1>(DCompPresenterTestBase<Param>::GetParam()); |
| } |
| |
| // Helper to generate human-friendly test parametrization names. This expects |
| // `Param` to be a type that is gtest-printable to a valid param string. |
| static std::string GetParamName( |
| const testing::TestParamInfo< |
| typename DCompPresenterTestBase<Param>::ParamType>& info) { |
| const std::string shared_param_name = |
| testing::PrintToString(std::get<0>(info.param)); |
| if constexpr (std::is_same_v<Param, std::monostate>) { |
| return shared_param_name; |
| } else { |
| return base::JoinString( |
| { |
| shared_param_name, |
| testing::PrintToString(std::get<1>(info.param)), |
| }, |
| "_"); |
| } |
| } |
| |
| DCompPresenterTestBase() : parent_window_(ui::GetHiddenWindow()) {} |
| |
| protected: |
| void SetUp() override { |
| if (std::get<0>(this->GetParam()).use_gpu_vsync) { |
| EnableFeature(features::kGpuVsync); |
| } else { |
| DisableFeature(features::kGpuVsync); |
| } |
| |
| enabled_features_.InitWithFeatures(enabled_features_list_, |
| disabled_features_list_); |
| display_ = GLTestSupport::InitializeGL(std::nullopt); |
| std::tie(gl_surface_, context_) = |
| GLTestHelper::CreateOffscreenGLSurfaceAndContext(); |
| |
| // These tests are assumed to run on battery. |
| fake_power_monitor_source_.SetBatteryPowerStatus( |
| base::PowerStateObserver::BatteryPowerStatus::kBatteryPower); |
| |
| // All bots run on non-blocklisted hardware that supports DComp (>Win7) |
| Microsoft::WRL::ComPtr<ID3D11Device> d3d11_device = |
| QueryD3D11DeviceObjectFromANGLE(); |
| InitializeDirectComposition(d3d11_device.Get()); |
| ASSERT_TRUE(DirectCompositionSupported()); |
| |
| presenter_ = CreateDCompPresenter(); |
| |
| SetDirectCompositionOverlayFormatUsedForTesting(DXGI_FORMAT_NV12); |
| SetDirectCompositionScaledOverlaysSupportedForTesting(false); |
| } |
| |
| void TearDown() override { |
| if (presenter_) { |
| DestroyPresenter(std::move(presenter_)); |
| } |
| |
| context_.reset(); |
| gl_surface_.reset(); |
| init::ShutdownGL(display_, false); |
| display_ = nullptr; |
| } |
| |
| scoped_refptr<DCompPresenter> CreateDCompPresenter() { |
| DCompPresenter::Settings settings; |
| scoped_refptr<DCompPresenter> presenter = |
| base::MakeRefCounted<DCompPresenter>(settings); |
| |
| // ImageTransportSurfaceDelegate::AddChildWindowToBrowser() is called in |
| // production code here. However, to remove dependency from |
| // gpu/ipc/service/image_transport_presenter_delegate.h, here we directly |
| // executes the required minimum code. |
| if (parent_window_) { |
| ::SetParent(presenter->GetWindow(), parent_window_); |
| } |
| |
| return presenter; |
| } |
| |
| void ScheduleOverlay(DCLayerOverlayParams overlay) { |
| EXPECT_NE(overlay.layer_id, gfx::OverlayLayerId()); |
| pending_overlays_.push_back(std::move(overlay)); |
| } |
| |
| void ScheduleOverlays(std::vector<DCLayerOverlayParams> overlays) { |
| EXPECT_THAT(overlays, testing::Each(testing::Field( |
| "layer_id", &DCLayerOverlayParams::layer_id, |
| testing::Ne(gfx::OverlayLayerId())))); |
| std::ranges::move(overlays, std::back_inserter(pending_overlays_)); |
| } |
| |
| static gfx::OverlayLayerId GetRootSurfaceId() { |
| // Use an arbitrary render pass ID. The render passes themselves have been |
| // forgotten by the time we reach DCompPresenter, so we just need any unique |
| // identifier to represent the root surface. |
| return gfx::OverlayLayerId::MakeVizInternalRenderPass( |
| gfx::OverlayLayerId::RenderPassId(1)); |
| } |
| |
| // DCompPresenter is surfaceless--it's root surface is achieved via an |
| // overlay the size of the window. |
| // We can also present a manual initialized root surface with specific size |
| // and color. |
| void InitializeRootAndScheduleRootSurface(const gfx::Size& window_size, |
| SkColor4f initial_color) { |
| // Schedule the root surface as a normal overlay |
| auto params = |
| CreateParamsFromImage(CreateDCompSurface(window_size, initial_color)); |
| params.z_order = 0; |
| params.quad_rect = gfx::Rect(window_size); |
| params.overlay_image = CreateDCompSurface(window_size, initial_color); |
| params.layer_id = GetRootSurfaceId(); |
| ScheduleOverlay(std::move(params)); |
| } |
| |
| // Wait for `presenter_` to present asynchronously and return the swap result. |
| gfx::SwapResult PresentAndGetSwapResult() { |
| presenter_->ScheduleDCLayers(std::move(pending_overlays_)); |
| |
| std::optional<gfx::SwapResult> result; |
| |
| base::RunLoop wait_for_present; |
| presenter_->Present( |
| base::BindOnce( |
| [](base::RepeatingClosure quit_closure, |
| std::optional<gfx::SwapResult>* out_result, |
| gfx::SwapCompletionResult result) { |
| *out_result = result.swap_result; |
| quit_closure.Run(); |
| }, |
| wait_for_present.QuitClosure(), base::Unretained(&result)), |
| base::DoNothing(), gfx::FrameData()); |
| wait_for_present.Run(); |
| |
| return result.value(); |
| } |
| |
| void EnableFeature(const base::test::FeatureRef& feature) { |
| enabled_features_list_.push_back(feature); |
| } |
| |
| void DisableFeature(const base::test::FeatureRef& feature) { |
| disabled_features_list_.push_back(feature); |
| } |
| |
| raw_ptr<GLDisplay> display_ = nullptr; |
| scoped_refptr<GLSurface> gl_surface_; |
| scoped_refptr<GLContext> context_; |
| |
| base::test::ScopedPowerMonitorTestSource fake_power_monitor_source_; |
| HWND parent_window_; |
| scoped_refptr<DCompPresenter> presenter_; |
| base::test::ScopedFeatureList enabled_features_; |
| std::vector<base::test::FeatureRef> enabled_features_list_; |
| std::vector<base::test::FeatureRef> disabled_features_list_; |
| |
| std::vector<DCLayerOverlayParams> pending_overlays_; |
| }; |
| |
| class DCompPresenterTest : public DCompPresenterTestBase<> {}; |
| |
| // Ensure that the overlay image isn't presented again unless it changes. |
| TEST_P(DCompPresenterTest, NoPresentTwice) { |
| Microsoft::WRL::ComPtr<ID3D11Device> d3d11_device = |
| GetDirectCompositionD3D11Device(); |
| |
| gfx::Size texture_size(50, 50); |
| Microsoft::WRL::ComPtr<ID3D11Texture2D> texture = |
| CreateNV12Texture(d3d11_device, texture_size); |
| ASSERT_NE(texture, nullptr); |
| |
| { |
| auto params = |
| CreateParamsFromImage(DCLayerOverlayImage(texture_size, texture)); |
| params.quad_rect = gfx::Rect(100, 100); |
| params.layer_id = gfx::OverlayLayerId::MakeForTesting(0); |
| params.video_params.color_space = gfx::ColorSpace::CreateREC709(); |
| ScheduleOverlay(std::move(params)); |
| } |
| |
| Microsoft::WRL::ComPtr<IDXGISwapChain1> swap_chain = |
| presenter_->GetLayerSwapChainForTesting( |
| gfx::OverlayLayerId::MakeForTesting(0)); |
| ASSERT_FALSE(swap_chain); |
| |
| ASSERT_EQ(PresentAndGetSwapResult(), gfx::SwapResult::SWAP_ACK); |
| |
| swap_chain = presenter_->GetLayerSwapChainForTesting( |
| gfx::OverlayLayerId::MakeForTesting(0)); |
| ASSERT_TRUE(swap_chain); |
| |
| UINT last_present_count = 0; |
| EXPECT_HRESULT_SUCCEEDED( |
| swap_chain->GetLastPresentCount(&last_present_count)); |
| |
| // One present is normal, and a second present because it's the first frame |
| // and the other buffer needs to be drawn to. |
| EXPECT_EQ(2u, last_present_count); |
| |
| { |
| auto params = |
| CreateParamsFromImage(DCLayerOverlayImage(texture_size, texture)); |
| params.quad_rect = gfx::Rect(100, 100); |
| params.layer_id = gfx::OverlayLayerId::MakeForTesting(0); |
| params.video_params.color_space = gfx::ColorSpace::CreateREC709(); |
| ScheduleOverlay(std::move(params)); |
| } |
| |
| ASSERT_EQ(PresentAndGetSwapResult(), gfx::SwapResult::SWAP_ACK); |
| |
| Microsoft::WRL::ComPtr<IDXGISwapChain1> swap_chain2 = |
| presenter_->GetLayerSwapChainForTesting( |
| gfx::OverlayLayerId::MakeForTesting(0)); |
| EXPECT_EQ(swap_chain2.Get(), swap_chain.Get()); |
| |
| // It's the same image, so it should have the same swapchain. |
| EXPECT_HRESULT_SUCCEEDED( |
| swap_chain->GetLastPresentCount(&last_present_count)); |
| EXPECT_EQ(2u, last_present_count); |
| |
| // The image changed, we should get a new present. |
| texture = CreateNV12Texture(d3d11_device, texture_size); |
| ASSERT_NE(texture, nullptr); |
| |
| { |
| auto params = |
| CreateParamsFromImage(DCLayerOverlayImage(texture_size, texture)); |
| params.quad_rect = gfx::Rect(100, 100); |
| params.layer_id = gfx::OverlayLayerId::MakeForTesting(0); |
| params.video_params.color_space = gfx::ColorSpace::CreateREC709(); |
| ScheduleOverlay(std::move(params)); |
| } |
| |
| ASSERT_EQ(PresentAndGetSwapResult(), gfx::SwapResult::SWAP_ACK); |
| |
| Microsoft::WRL::ComPtr<IDXGISwapChain1> swap_chain3 = |
| presenter_->GetLayerSwapChainForTesting( |
| gfx::OverlayLayerId::MakeForTesting(0)); |
| EXPECT_HRESULT_SUCCEEDED( |
| swap_chain3->GetLastPresentCount(&last_present_count)); |
| // The present count should increase with the new present |
| EXPECT_EQ(3u, last_present_count); |
| } |
| |
| // Ensure the swapchain size is set to the correct size if HW overlay scaling |
| // is support - swapchain should be set to the onscreen video size. |
| TEST_P(DCompPresenterTest, SwapchainSizeWithScaledOverlays) { |
| Microsoft::WRL::ComPtr<ID3D11Device> d3d11_device = |
| GetDirectCompositionD3D11Device(); |
| |
| gfx::Size texture_size(64, 64); |
| Microsoft::WRL::ComPtr<ID3D11Texture2D> texture = |
| CreateNV12Texture(d3d11_device, texture_size); |
| ASSERT_NE(texture, nullptr); |
| |
| // HW supports scaled overlays. |
| // The input texture size is maller than the window size. |
| SetDirectCompositionScaledOverlaysSupportedForTesting(true); |
| |
| // Onscreen quad. |
| gfx::Rect quad_rect = gfx::Rect(100, 100); |
| |
| { |
| auto params = |
| CreateParamsFromImage(DCLayerOverlayImage(texture_size, texture)); |
| params.quad_rect = quad_rect; |
| params.layer_id = gfx::OverlayLayerId::MakeForTesting(0); |
| params.video_params.color_space = gfx::ColorSpace::CreateREC709(); |
| ScheduleOverlay(std::move(params)); |
| } |
| |
| ASSERT_EQ(PresentAndGetSwapResult(), gfx::SwapResult::SWAP_ACK); |
| Microsoft::WRL::ComPtr<IDXGISwapChain1> swap_chain = |
| presenter_->GetLayerSwapChainForTesting( |
| gfx::OverlayLayerId::MakeForTesting(0)); |
| ASSERT_TRUE(swap_chain); |
| |
| DXGI_SWAP_CHAIN_DESC desc; |
| EXPECT_HRESULT_SUCCEEDED(swap_chain->GetDesc(&desc)); |
| // Onscreen quad_rect.size is (100, 100). |
| EXPECT_EQ(100u, desc.BufferDesc.Width); |
| EXPECT_EQ(100u, desc.BufferDesc.Height); |
| |
| // Clear SwapChainPresenters |
| // Must do Clear first because the swap chain won't resize immediately if |
| // a new size is given unless this is the very first time after Clear. |
| ASSERT_EQ(PresentAndGetSwapResult(), gfx::SwapResult::SWAP_ACK); |
| |
| // The input texture size is bigger than the window size. |
| quad_rect = gfx::Rect(32, 48); |
| |
| { |
| auto params = |
| CreateParamsFromImage(DCLayerOverlayImage(texture_size, texture)); |
| params.quad_rect = quad_rect; |
| params.layer_id = gfx::OverlayLayerId::MakeForTesting(0); |
| params.video_params.color_space = gfx::ColorSpace::CreateREC709(); |
| ScheduleOverlay(std::move(params)); |
| } |
| |
| ASSERT_EQ(PresentAndGetSwapResult(), gfx::SwapResult::SWAP_ACK); |
| |
| Microsoft::WRL::ComPtr<IDXGISwapChain1> swap_chain2 = |
| presenter_->GetLayerSwapChainForTesting( |
| gfx::OverlayLayerId::MakeForTesting(0)); |
| ASSERT_TRUE(swap_chain2); |
| |
| EXPECT_HRESULT_SUCCEEDED(swap_chain2->GetDesc(&desc)); |
| // Onscreen quad_rect.size is (32, 48). |
| EXPECT_EQ(32u, desc.BufferDesc.Width); |
| EXPECT_EQ(48u, desc.BufferDesc.Height); |
| } |
| |
| // Ensure the swapchain size is set to the correct size if HW overlay scaling |
| // is not support - swapchain should be the onscreen video size. |
| TEST_P(DCompPresenterTest, SwapchainSizeWithoutScaledOverlays) { |
| Microsoft::WRL::ComPtr<ID3D11Device> d3d11_device = |
| GetDirectCompositionD3D11Device(); |
| |
| gfx::Size texture_size(80, 80); |
| Microsoft::WRL::ComPtr<ID3D11Texture2D> texture = |
| CreateNV12Texture(d3d11_device, texture_size); |
| ASSERT_NE(texture, nullptr); |
| |
| gfx::Rect quad_rect = gfx::Rect(42, 42); |
| |
| { |
| auto params = |
| CreateParamsFromImage(DCLayerOverlayImage(texture_size, texture)); |
| params.quad_rect = quad_rect; |
| params.layer_id = gfx::OverlayLayerId::MakeForTesting(0); |
| params.video_params.color_space = gfx::ColorSpace::CreateREC709(); |
| ScheduleOverlay(std::move(params)); |
| } |
| |
| ASSERT_EQ(PresentAndGetSwapResult(), gfx::SwapResult::SWAP_ACK); |
| Microsoft::WRL::ComPtr<IDXGISwapChain1> swap_chain = |
| presenter_->GetLayerSwapChainForTesting( |
| gfx::OverlayLayerId::MakeForTesting(0)); |
| ASSERT_TRUE(swap_chain); |
| |
| DXGI_SWAP_CHAIN_DESC desc; |
| EXPECT_HRESULT_SUCCEEDED(swap_chain->GetDesc(&desc)); |
| // Onscreen quad_rect.size is (42, 42). |
| EXPECT_EQ(42u, desc.BufferDesc.Width); |
| EXPECT_EQ(42u, desc.BufferDesc.Height); |
| |
| // The input texture size is smaller than the window size. |
| quad_rect = gfx::Rect(124, 136); |
| |
| { |
| auto params = |
| CreateParamsFromImage(DCLayerOverlayImage(texture_size, texture)); |
| params.quad_rect = quad_rect; |
| params.layer_id = gfx::OverlayLayerId::MakeForTesting(0); |
| params.video_params.color_space = gfx::ColorSpace::CreateREC709(); |
| ScheduleOverlay(std::move(params)); |
| } |
| |
| ASSERT_EQ(PresentAndGetSwapResult(), gfx::SwapResult::SWAP_ACK); |
| |
| Microsoft::WRL::ComPtr<IDXGISwapChain1> swap_chain2 = |
| presenter_->GetLayerSwapChainForTesting( |
| gfx::OverlayLayerId::MakeForTesting(0)); |
| ASSERT_TRUE(swap_chain2); |
| |
| EXPECT_HRESULT_SUCCEEDED(swap_chain2->GetDesc(&desc)); |
| // Onscreen quad_rect.size is (124, 136). |
| EXPECT_EQ(124u, desc.BufferDesc.Width); |
| EXPECT_EQ(136u, desc.BufferDesc.Height); |
| } |
| |
| // Test protected video flags |
| TEST_P(DCompPresenterTest, ProtectedVideos) { |
| Microsoft::WRL::ComPtr<ID3D11Device> d3d11_device = |
| GetDirectCompositionD3D11Device(); |
| |
| gfx::Size texture_size(1280, 720); |
| Microsoft::WRL::ComPtr<ID3D11Texture2D> texture = |
| CreateNV12Texture(d3d11_device, texture_size); |
| ASSERT_NE(texture, nullptr); |
| |
| gfx::Size window_size(640, 360); |
| |
| // Clear video |
| { |
| auto params = |
| CreateParamsFromImage(DCLayerOverlayImage(texture_size, texture)); |
| params.quad_rect = gfx::Rect(window_size); |
| params.layer_id = gfx::OverlayLayerId::MakeForTesting(0); |
| params.video_params.color_space = gfx::ColorSpace::CreateREC709(); |
| params.video_params.protected_video_type = gfx::ProtectedVideoType::kClear; |
| |
| ScheduleOverlay(std::move(params)); |
| ASSERT_EQ(PresentAndGetSwapResult(), gfx::SwapResult::SWAP_ACK); |
| Microsoft::WRL::ComPtr<IDXGISwapChain1> swap_chain = |
| presenter_->GetLayerSwapChainForTesting( |
| gfx::OverlayLayerId::MakeForTesting(0)); |
| ASSERT_TRUE(swap_chain); |
| |
| DXGI_SWAP_CHAIN_DESC desc; |
| EXPECT_HRESULT_SUCCEEDED(swap_chain->GetDesc(&desc)); |
| auto display_only_flag = desc.Flags & DXGI_SWAP_CHAIN_FLAG_DISPLAY_ONLY; |
| auto hw_protected_flag = desc.Flags & DXGI_SWAP_CHAIN_FLAG_HW_PROTECTED; |
| EXPECT_EQ(0u, display_only_flag); |
| EXPECT_EQ(0u, hw_protected_flag); |
| } |
| |
| // Software protected video |
| { |
| auto params = |
| CreateParamsFromImage(DCLayerOverlayImage(texture_size, texture)); |
| params.quad_rect = gfx::Rect(window_size); |
| params.layer_id = gfx::OverlayLayerId::MakeForTesting(0); |
| params.video_params.color_space = gfx::ColorSpace::CreateREC709(); |
| params.video_params.protected_video_type = |
| gfx::ProtectedVideoType::kSoftwareProtected; |
| |
| ScheduleOverlay(std::move(params)); |
| ASSERT_EQ(PresentAndGetSwapResult(), gfx::SwapResult::SWAP_ACK); |
| Microsoft::WRL::ComPtr<IDXGISwapChain1> swap_chain = |
| presenter_->GetLayerSwapChainForTesting( |
| gfx::OverlayLayerId::MakeForTesting(0)); |
| ASSERT_TRUE(swap_chain); |
| |
| DXGI_SWAP_CHAIN_DESC Desc; |
| EXPECT_HRESULT_SUCCEEDED(swap_chain->GetDesc(&Desc)); |
| auto display_only_flag = Desc.Flags & DXGI_SWAP_CHAIN_FLAG_DISPLAY_ONLY; |
| auto hw_protected_flag = Desc.Flags & DXGI_SWAP_CHAIN_FLAG_HW_PROTECTED; |
| EXPECT_EQ(DXGI_SWAP_CHAIN_FLAG_DISPLAY_ONLY, display_only_flag); |
| EXPECT_EQ(0u, hw_protected_flag); |
| } |
| |
| // TODO(magchen): Add a hardware protected video test when hardware protected |
| // video support is enabled by default in the Intel driver and Chrome |
| } |
| |
| TEST_P(DCompPresenterTest, NoBackgroundColorSurfaceForNonColorOverlays) { |
| const gfx::Size window_size(100, 100); |
| EXPECT_TRUE(presenter_->Resize(window_size, 1.0, gfx::ColorSpace(), true)); |
| |
| auto root_surface = |
| CreateParamsFromImage(CreateDCompSurface(window_size, SkColors::kBlack)); |
| root_surface.quad_rect = gfx::Rect(window_size); |
| root_surface.z_order = 1; |
| root_surface.layer_id = gfx::OverlayLayerId::MakeForTesting(1); |
| ScheduleOverlay(std::move(root_surface)); |
| |
| ASSERT_EQ(PresentAndGetSwapResult(), gfx::SwapResult::SWAP_ACK); |
| |
| const DCLayerTree* layer_tree = presenter_->GetLayerTreeForTesting(); |
| EXPECT_EQ(1u, layer_tree->GetDcompLayerCountForTesting()); |
| EXPECT_EQ(0u, layer_tree->GetNumSurfacesInPoolForTesting()); |
| } |
| |
| TEST_P(DCompPresenterTest, BackgroundColorSurfaceTrim) { |
| const gfx::Size window_size(100, 100); |
| EXPECT_TRUE(presenter_->Resize(window_size, 1.0, gfx::ColorSpace(), true)); |
| |
| const DCLayerTree* layer_tree = presenter_->GetLayerTreeForTesting(); |
| |
| // See |TrimAfterCommit|. |
| static constexpr size_t kMaxSolidColorBuffers = 12; |
| |
| // From an empty state, the surface pool will allocate surfaces on demand and |
| // retain as many that are in use (and unused surfaces, up to |
| // |kMaxSolidColorBuffers| total). We iterate to |kMaxSolidColorBuffers + 1| |
| // to exceed this threshold. |
| for (size_t num_buffers = 1; num_buffers <= kMaxSolidColorBuffers + 1; |
| num_buffers++) { |
| // We expect as many retained surfaces as there are unique solid color |
| // overlays in the frame. |
| { |
| for (size_t i = 0; i < num_buffers; i++) { |
| DCLayerOverlayParams params; |
| params.quad_rect = gfx::Rect(window_size); |
| params.background_color = SkColor4f::FromColor(SkColorSetRGB(i, 0, 0)); |
| params.z_order = i + 1; |
| params.layer_id = gfx::OverlayLayerId::MakeForTesting(i); |
| ScheduleOverlay(std::move(params)); |
| } |
| ASSERT_EQ(PresentAndGetSwapResult(), gfx::SwapResult::SWAP_ACK); |
| EXPECT_EQ(num_buffers, layer_tree->GetNumSurfacesInPoolForTesting()); |
| } |
| |
| // We expect retained surfaces even after we present a frame with no solid |
| // color overlays. |
| { |
| ASSERT_EQ(PresentAndGetSwapResult(), gfx::SwapResult::SWAP_ACK); |
| EXPECT_EQ(std::min(num_buffers, kMaxSolidColorBuffers), |
| layer_tree->GetNumSurfacesInPoolForTesting()); |
| } |
| } |
| } |
| |
| // Check that when there's multiple background color surfaces, the correct one |
| // is reused even if the order they're requested in changes. |
| TEST_P(DCompPresenterTest, BackgroundColorSurfaceMultipleReused) { |
| const gfx::Size window_size(100, 100); |
| EXPECT_TRUE(presenter_->Resize(window_size, 1.0, gfx::ColorSpace(), true)); |
| |
| std::vector<SkColor4f> colors = {SkColors::kRed, SkColors::kGreen}; |
| std::vector<IDCompositionSurface*> surfaces_frame1(2, nullptr); |
| std::vector<IDCompositionSurface*> surfaces_frame2(2, nullptr); |
| |
| const DCLayerTree* layer_tree = presenter_->GetLayerTreeForTesting(); |
| |
| { |
| for (size_t i = 0; i < colors.size(); i++) { |
| DCLayerOverlayParams params; |
| params.quad_rect = gfx::Rect(window_size); |
| params.background_color = colors[i]; |
| params.z_order = i + 1; |
| params.layer_id = gfx::OverlayLayerId::MakeForTesting(i); |
| ScheduleOverlay(std::move(params)); |
| } |
| |
| ASSERT_EQ(PresentAndGetSwapResult(), gfx::SwapResult::SWAP_ACK); |
| EXPECT_EQ(2u, layer_tree->GetNumSurfacesInPoolForTesting()); |
| |
| surfaces_frame1[0] = layer_tree->GetBackgroundColorSurfaceForTesting( |
| gfx::OverlayLayerId::MakeForTesting(0)); |
| surfaces_frame1[1] = layer_tree->GetBackgroundColorSurfaceForTesting( |
| gfx::OverlayLayerId::MakeForTesting(1)); |
| // The overlays should have different background color surfaces since they |
| // have different background colors. |
| EXPECT_NE(surfaces_frame1[0], surfaces_frame1[1]); |
| } |
| |
| { |
| // Swap the colors so they appear as overlays in a different order for the |
| // next frame. |
| std::swap(colors[0], colors[1]); |
| |
| for (size_t i = 0; i < colors.size(); i++) { |
| DCLayerOverlayParams params; |
| params.quad_rect = gfx::Rect(window_size); |
| params.background_color = colors[i]; |
| params.z_order = i + 1; |
| params.layer_id = gfx::OverlayLayerId::MakeForTesting(i); |
| ScheduleOverlay(std::move(params)); |
| } |
| |
| ASSERT_EQ(PresentAndGetSwapResult(), gfx::SwapResult::SWAP_ACK); |
| EXPECT_EQ(2u, layer_tree->GetNumSurfacesInPoolForTesting()); |
| |
| surfaces_frame2[0] = layer_tree->GetBackgroundColorSurfaceForTesting( |
| gfx::OverlayLayerId::MakeForTesting(0)); |
| surfaces_frame2[1] = layer_tree->GetBackgroundColorSurfaceForTesting( |
| gfx::OverlayLayerId::MakeForTesting(1)); |
| EXPECT_NE(surfaces_frame2[0], surfaces_frame2[1]); |
| |
| // We reversed the order of the color overlays. We expect the background |
| // color surfaces to be reused, but reversed. |
| EXPECT_EQ(surfaces_frame1[0], surfaces_frame2[1]); |
| EXPECT_EQ(surfaces_frame1[1], surfaces_frame2[0]); |
| } |
| } |
| |
| // Check that the delegated ink visual gets added to the DC Layer tree |
| // if there is no root surface. |
| TEST_P(DCompPresenterTest, DelegatedInkVisualAddedWithRootSurfaceVisualNull) { |
| std::unique_ptr<gfx::DelegatedInkMetadata> metadata = |
| std::make_unique<gfx::DelegatedInkMetadata>( |
| gfx::PointF(12, 12), /*diameter=*/3, SK_ColorBLACK, |
| base::TimeTicks::Now(), gfx::RectF(10, 10, 90, 90), |
| /*hovering=*/false); |
| |
| // Set start point to initialize ink renderer. |
| DCLayerTree* layer_tree = presenter_->GetLayerTreeForTesting(); |
| if (!layer_tree->SupportsDelegatedInk()) { |
| return; |
| } |
| layer_tree->SetDelegatedInkTrailStartPoint(std::move(metadata)); |
| |
| EXPECT_TRUE(layer_tree->CommitAndClearPendingOverlays({}).has_value()); |
| |
| // Despite no overlays, there should be one visual subtree for the delegated |
| // ink trail. |
| EXPECT_EQ(1u, layer_tree->GetDcompLayerCountForTesting()); |
| } |
| |
| // Ensure that swap chains stay attached to the same visual between subsequent |
| // frames. |
| // Please ensure this test is not broken. Re-attaching swapchains between |
| // subsequent frames may cause flickering under certain conditions that include |
| // specific Intel drivers, custom present duration etc. |
| // See https://bugs.chromium.org/p/chromium/issues/detail?id=1421175. |
| TEST_P(DCompPresenterTest, VisualsReused) { |
| constexpr gfx::Size window_size(100, 100); |
| EXPECT_TRUE(presenter_->Resize(window_size, 1.0, gfx::ColorSpace(), true)); |
| |
| Microsoft::WRL::ComPtr<ID3D11Device> d3d11_device = |
| GetDirectCompositionD3D11Device(); |
| |
| gfx::Size texture_size(50, 50); |
| Microsoft::WRL::ComPtr<ID3D11Texture2D> texture = |
| CreateNV12Texture(d3d11_device, texture_size); |
| EXPECT_NE(texture, nullptr); |
| |
| // Frame 1: |
| // overlay 0: root dcomp surface |
| // overlay 1: swapchain z-order = 1 (overlay) |
| InitializeRootAndScheduleRootSurface(window_size, SkColors::kBlue); |
| { |
| DCLayerOverlayParams params; |
| params.overlay_image.emplace(texture_size, texture); |
| params.content_rect = gfx::RectF(texture_size); |
| params.quad_rect = gfx::Rect(100, 100); |
| params.video_params.color_space = gfx::ColorSpace::CreateREC709(); |
| // Overlay |
| params.z_order = 1; |
| params.layer_id = gfx::OverlayLayerId::MakeForTesting(0); |
| ScheduleOverlay(std::move(params)); |
| } |
| |
| ASSERT_EQ(PresentAndGetSwapResult(), gfx::SwapResult::SWAP_ACK); |
| |
| DCLayerTree* dcLayerTree = presenter_->GetLayerTreeForTesting(); |
| |
| #if DCHECK_IS_ON() |
| EXPECT_TRUE(dcLayerTree->DcompVisualContentChangedFromPreviousFrameForTesting( |
| gfx::OverlayLayerId::MakeForTesting(0))); |
| #endif // DCHECK_IS_ON() |
| |
| EXPECT_EQ(2u, dcLayerTree->GetDcompLayerCountForTesting()); |
| Microsoft::WRL::ComPtr<IDCompositionVisual2> visual0 = |
| dcLayerTree->GetContentVisualForTesting(GetRootSurfaceId()); |
| Microsoft::WRL::ComPtr<IDCompositionVisual2> visual1 = |
| dcLayerTree->GetContentVisualForTesting( |
| gfx::OverlayLayerId::MakeForTesting(0)); |
| |
| // Frame 2: |
| // overlay 0: root dcomp surface |
| // overlay 1: swapchain z-order = -1 (underlay) |
| InitializeRootAndScheduleRootSurface(window_size, SkColors::kBlue); |
| { |
| DCLayerOverlayParams params; |
| params.overlay_image.emplace(texture_size, texture); |
| params.content_rect = gfx::RectF(texture_size); |
| params.quad_rect = gfx::Rect(100, 100); |
| params.video_params.color_space = gfx::ColorSpace::CreateREC709(); |
| // Underlay |
| params.z_order = -1; |
| params.layer_id = gfx::OverlayLayerId::MakeForTesting(0); |
| ScheduleOverlay(std::move(params)); |
| } |
| |
| ASSERT_EQ(PresentAndGetSwapResult(), gfx::SwapResult::SWAP_ACK); |
| EXPECT_EQ(2u, dcLayerTree->GetDcompLayerCountForTesting()); |
| // Verify that the visuals are reused from the previous frame but attached |
| // to the root visual in a reversed order. |
| EXPECT_EQ(visual0.Get(), |
| dcLayerTree->GetContentVisualForTesting(GetRootSurfaceId())); |
| EXPECT_EQ(visual1.Get(), dcLayerTree->GetContentVisualForTesting( |
| gfx::OverlayLayerId::MakeForTesting(0))); |
| #if DCHECK_IS_ON() |
| EXPECT_FALSE( |
| dcLayerTree->DcompVisualContentChangedFromPreviousFrameForTesting( |
| gfx::OverlayLayerId::MakeForTesting(0))); |
| #endif // DCHECK_IS_ON() |
| } |
| |
| DCLayerOverlayParams CreateOverlayWithSwapChain( |
| Microsoft::WRL::ComPtr<IDXGISwapChain1> swap_chain, |
| const gfx::Size& swap_chain_size, |
| int z_order) { |
| DCLayerOverlayParams params; |
| params.overlay_image = DCLayerOverlayImage(swap_chain_size, swap_chain); |
| params.content_rect = gfx::RectF(swap_chain_size); |
| params.quad_rect = gfx::Rect(100, 100); |
| params.video_params.color_space = gfx::ColorSpace::CreateSRGB(); |
| params.z_order = z_order; |
| params.layer_id = gfx::OverlayLayerId::MakeForTesting(z_order); |
| return params; |
| } |
| |
| void CreateSwapChain(IDXGIFactory2* dxgi_factory, |
| ID3D11Device* d3d11_device, |
| const DXGI_SWAP_CHAIN_DESC1& desc, |
| Microsoft::WRL::ComPtr<IDXGISwapChain1>& swap_chain) { |
| ASSERT_HRESULT_SUCCEEDED(dxgi_factory->CreateSwapChainForComposition( |
| d3d11_device, &desc, nullptr, &swap_chain)); |
| ASSERT_TRUE(swap_chain); |
| } |
| |
| TEST_P(DCompPresenterTest, MatchedAndUnmatchedVisualsReused) { |
| constexpr gfx::Size window_size(100, 100); |
| EXPECT_TRUE(presenter_->Resize(window_size, 1.0, gfx::ColorSpace(), true)); |
| |
| InitializeRootAndScheduleRootSurface(window_size, SkColors::kBlue); |
| |
| Microsoft::WRL::ComPtr<ID3D11Device> d3d11_device = |
| QueryD3D11DeviceObjectFromANGLE(); |
| ASSERT_TRUE(d3d11_device); |
| Microsoft::WRL::ComPtr<IDXGIDevice> dxgi_device; |
| ASSERT_HRESULT_SUCCEEDED(d3d11_device.As(&dxgi_device)); |
| ASSERT_TRUE(dxgi_device); |
| Microsoft::WRL::ComPtr<IDXGIAdapter> dxgi_adapter; |
| ASSERT_HRESULT_SUCCEEDED(dxgi_device->GetAdapter(&dxgi_adapter)); |
| ASSERT_TRUE(dxgi_adapter); |
| Microsoft::WRL::ComPtr<IDXGIFactory2> dxgi_factory; |
| ASSERT_HRESULT_SUCCEEDED( |
| dxgi_adapter->GetParent(IID_PPV_ARGS(&dxgi_factory))); |
| ASSERT_TRUE(dxgi_factory); |
| |
| gfx::Size swap_chain_size(50, 50); |
| DXGI_SWAP_CHAIN_DESC1 desc = {}; |
| desc.Width = swap_chain_size.width(); |
| desc.Height = swap_chain_size.height(); |
| desc.Format = DXGI_FORMAT_B8G8R8A8_UNORM; |
| desc.Stereo = FALSE; |
| desc.SampleDesc.Count = 1; |
| desc.BufferCount = 2; |
| desc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT | DXGI_USAGE_SHADER_INPUT; |
| desc.Scaling = DXGI_SCALING_STRETCH; |
| desc.SwapEffect = DXGI_SWAP_EFFECT_FLIP_SEQUENTIAL; |
| desc.Flags = 0; |
| |
| Microsoft::WRL::ComPtr<IDXGISwapChain1> swap_chainA; |
| CreateSwapChain(dxgi_factory.Get(), d3d11_device.Get(), desc, swap_chainA); |
| |
| Microsoft::WRL::ComPtr<IDXGISwapChain1> swap_chainB; |
| CreateSwapChain(dxgi_factory.Get(), d3d11_device.Get(), desc, swap_chainB); |
| |
| Microsoft::WRL::ComPtr<IDXGISwapChain1> swap_chainC; |
| CreateSwapChain(dxgi_factory.Get(), d3d11_device.Get(), desc, swap_chainC); |
| |
| Microsoft::WRL::ComPtr<IDXGISwapChain1> swap_chainD; |
| CreateSwapChain(dxgi_factory.Get(), d3d11_device.Get(), desc, swap_chainD); |
| |
| Microsoft::WRL::ComPtr<IDXGISwapChain1> swap_chainE; |
| CreateSwapChain(dxgi_factory.Get(), d3d11_device.Get(), desc, swap_chainE); |
| |
| Microsoft::WRL::ComPtr<IDXGISwapChain1> swap_chainF; |
| CreateSwapChain(dxgi_factory.Get(), d3d11_device.Get(), desc, swap_chainF); |
| |
| Microsoft::WRL::ComPtr<IDXGISwapChain1> swap_chainL; |
| CreateSwapChain(dxgi_factory.Get(), d3d11_device.Get(), desc, swap_chainL); |
| |
| Microsoft::WRL::ComPtr<IDXGISwapChain1> swap_chainM; |
| CreateSwapChain(dxgi_factory.Get(), d3d11_device.Get(), desc, swap_chainM); |
| |
| // Frame 1: RootSurface, A B C D E F |
| ScheduleOverlay(CreateOverlayWithSwapChain(swap_chainA, swap_chain_size, 1)); |
| ScheduleOverlay(CreateOverlayWithSwapChain(swap_chainB, swap_chain_size, 2)); |
| ScheduleOverlay(CreateOverlayWithSwapChain(swap_chainC, swap_chain_size, 3)); |
| ScheduleOverlay(CreateOverlayWithSwapChain(swap_chainD, swap_chain_size, 4)); |
| ScheduleOverlay(CreateOverlayWithSwapChain(swap_chainE, swap_chain_size, 5)); |
| ScheduleOverlay(CreateOverlayWithSwapChain(swap_chainF, swap_chain_size, 6)); |
| |
| ASSERT_EQ(PresentAndGetSwapResult(), gfx::SwapResult::SWAP_ACK); |
| |
| DCLayerTree* dc_layer_tree = presenter_->GetLayerTreeForTesting(); |
| |
| #if DCHECK_IS_ON() |
| for (int i = 1; i <= 6; i++) { |
| EXPECT_TRUE( |
| dc_layer_tree->DcompVisualContentChangedFromPreviousFrameForTesting( |
| gfx::OverlayLayerId::MakeForTesting(i))); |
| } |
| #endif // DCHECK_IS_ON() |
| |
| EXPECT_EQ(7u, dc_layer_tree->GetDcompLayerCountForTesting()); |
| Microsoft::WRL::ComPtr<IDCompositionVisual2> visualRS = |
| dc_layer_tree->GetContentVisualForTesting(GetRootSurfaceId()); |
| EXPECT_NE(visualRS, nullptr); |
| Microsoft::WRL::ComPtr<IDCompositionVisual2> visualA = |
| dc_layer_tree->GetContentVisualForTesting( |
| gfx::OverlayLayerId::MakeForTesting(1)); |
| EXPECT_NE(visualA, nullptr); |
| Microsoft::WRL::ComPtr<IDCompositionVisual2> visualB = |
| dc_layer_tree->GetContentVisualForTesting( |
| gfx::OverlayLayerId::MakeForTesting(2)); |
| EXPECT_NE(visualB, nullptr); |
| Microsoft::WRL::ComPtr<IDCompositionVisual2> visualC = |
| dc_layer_tree->GetContentVisualForTesting( |
| gfx::OverlayLayerId::MakeForTesting(3)); |
| EXPECT_NE(visualC, nullptr); |
| Microsoft::WRL::ComPtr<IDCompositionVisual2> visualD = |
| dc_layer_tree->GetContentVisualForTesting( |
| gfx::OverlayLayerId::MakeForTesting(4)); |
| EXPECT_NE(visualD, nullptr); |
| Microsoft::WRL::ComPtr<IDCompositionVisual2> visualE = |
| dc_layer_tree->GetContentVisualForTesting( |
| gfx::OverlayLayerId::MakeForTesting(5)); |
| EXPECT_NE(visualE, nullptr); |
| Microsoft::WRL::ComPtr<IDCompositionVisual2> visualF = |
| dc_layer_tree->GetContentVisualForTesting( |
| gfx::OverlayLayerId::MakeForTesting(6)); |
| EXPECT_NE(visualF, nullptr); |
| |
| // Frame 2: RootSurface, A L D C M |
| InitializeRootAndScheduleRootSurface(window_size, SkColors::kBlue); |
| ScheduleOverlay(CreateOverlayWithSwapChain(swap_chainA, swap_chain_size, 1)); |
| ScheduleOverlay(CreateOverlayWithSwapChain(swap_chainL, swap_chain_size, 2)); |
| ScheduleOverlay(CreateOverlayWithSwapChain(swap_chainD, swap_chain_size, 3)); |
| ScheduleOverlay(CreateOverlayWithSwapChain(swap_chainC, swap_chain_size, 4)); |
| ScheduleOverlay(CreateOverlayWithSwapChain(swap_chainM, swap_chain_size, 5)); |
| |
| ASSERT_EQ(PresentAndGetSwapResult(), gfx::SwapResult::SWAP_ACK); |
| |
| EXPECT_EQ(6u, dc_layer_tree->GetDcompLayerCountForTesting()); |
| |
| // Verify: |
| // RootSurface is matched to RootSurface and kept attached to the root. |
| // A is matched to A and kept attached to the root. |
| // L is reused from B and kept attached to the root. |
| // D is matched to D and kept attached to the root. |
| // C is matched to C and reattached to the root. |
| // M is reused from E and kept attached to the root. |
| EXPECT_EQ(visualRS.Get(), dc_layer_tree->GetContentVisualForTesting( |
| GetRootSurfaceId()) /*RS*/); |
| EXPECT_EQ(visualA.Get(), dc_layer_tree->GetContentVisualForTesting( |
| gfx::OverlayLayerId::MakeForTesting(1)) /*A*/); |
| EXPECT_EQ(visualB.Get(), dc_layer_tree->GetContentVisualForTesting( |
| gfx::OverlayLayerId::MakeForTesting(2)) /*L*/); |
| EXPECT_EQ(visualD.Get(), dc_layer_tree->GetContentVisualForTesting( |
| gfx::OverlayLayerId::MakeForTesting(3)) /*D*/); |
| EXPECT_EQ(visualC.Get(), dc_layer_tree->GetContentVisualForTesting( |
| gfx::OverlayLayerId::MakeForTesting(4)) /*C*/); |
| EXPECT_EQ(visualE.Get(), dc_layer_tree->GetContentVisualForTesting( |
| gfx::OverlayLayerId::MakeForTesting(5)) /*M*/); |
| #if DCHECK_IS_ON() |
| EXPECT_FALSE( |
| dc_layer_tree->DcompVisualContentChangedFromPreviousFrameForTesting( |
| gfx::OverlayLayerId::MakeForTesting(1))); |
| EXPECT_TRUE( |
| dc_layer_tree->DcompVisualContentChangedFromPreviousFrameForTesting( |
| gfx::OverlayLayerId::MakeForTesting(2))); |
| EXPECT_FALSE( |
| dc_layer_tree->DcompVisualContentChangedFromPreviousFrameForTesting( |
| gfx::OverlayLayerId::MakeForTesting(3))); |
| EXPECT_FALSE( |
| dc_layer_tree->DcompVisualContentChangedFromPreviousFrameForTesting( |
| gfx::OverlayLayerId::MakeForTesting(4))); |
| EXPECT_TRUE( |
| dc_layer_tree->DcompVisualContentChangedFromPreviousFrameForTesting( |
| gfx::OverlayLayerId::MakeForTesting(5))); |
| #endif // DCHECK_IS_ON() |
| } |
| |
| // `SwapChainPresenter` internally tries to allocates a swap chain that is the |
| // size of the onscreen size. This is because some Intel devices do not support |
| // scaling overlays in HW. We do not account for the clip rect in the onscreen |
| // size calculation, so a clipped video can end up with a very large "onscreen" |
| // size even though most of it is not visible. |
| TEST_P(DCompPresenterTest, VeryLargeOnscreenSize) { |
| Microsoft::WRL::ComPtr<ID3D11Device> d3d11_device = |
| GetDirectCompositionD3D11Device(); |
| |
| gfx::Size texture_size(50, 50); |
| Microsoft::WRL::ComPtr<ID3D11Texture2D> texture = |
| CreateNV12Texture(d3d11_device, texture_size); |
| ASSERT_NE(texture, nullptr); |
| |
| { |
| auto params = |
| CreateParamsFromImage(DCLayerOverlayImage(texture_size, texture)); |
| params.quad_rect = gfx::Rect(1, 1); |
| params.layer_id = gfx::OverlayLayerId::MakeForTesting(0); |
| |
| // This transform will make us have an onscreen size with a dimension larger |
| // than the D3D11 max texture size. |
| params.transform = |
| gfx::Transform::MakeScale(D3D11_REQ_TEXTURE2D_U_OR_V_DIMENSION + 1, 10); |
| |
| params.video_params.color_space = gfx::ColorSpace::CreateREC709(); |
| ScheduleOverlay(std::move(params)); |
| } |
| |
| { |
| auto params = |
| CreateParamsFromImage(DCLayerOverlayImage(texture_size, texture)); |
| params.quad_rect = gfx::Rect(1, 1); |
| |
| // This transform will make us have an onscreen size with a dimension larger |
| // than the D3D11 max texture size. |
| params.transform = |
| gfx::Transform::MakeScale(10, D3D11_REQ_TEXTURE2D_U_OR_V_DIMENSION + 1); |
| |
| params.layer_id = gfx::OverlayLayerId::MakeForTesting(1); |
| params.video_params.color_space = gfx::ColorSpace::CreateREC709(); |
| ScheduleOverlay(std::move(params)); |
| } |
| |
| EXPECT_FALSE(presenter_->GetLayerSwapChainForTesting( |
| gfx::OverlayLayerId::MakeForTesting(0))); |
| EXPECT_FALSE(presenter_->GetLayerSwapChainForTesting( |
| gfx::OverlayLayerId::MakeForTesting(1))); |
| |
| ASSERT_EQ(PresentAndGetSwapResult(), gfx::SwapResult::SWAP_ACK); |
| |
| auto ExpectSwapChainAndMaintainsAspectRatio = [&](gfx::OverlayLayerId id, |
| gfx::SizeF expected_size) { |
| Microsoft::WRL::ComPtr<IDXGISwapChain1> swap_chain = |
| presenter_->GetLayerSwapChainForTesting(id); |
| EXPECT_TRUE(swap_chain); |
| |
| DXGI_SWAP_CHAIN_DESC1 desc; |
| EXPECT_HRESULT_SUCCEEDED(swap_chain->GetDesc1(&desc)); |
| |
| const gfx::SizeF swap_chain_size = gfx::SizeF(desc.Width, desc.Height); |
| |
| // Calculate the closeness based on the magnitude of the aspect ratios. |
| // `SwapChainPresenter` can slightly adjust the swap chain size, so the |
| // aspect ratios will never be exact. In this test, it can also be very |
| // large or very small. |
| EXPECT_NEAR(std::log10(expected_size.AspectRatio()), |
| std::log10(swap_chain_size.AspectRatio()), 0.00003); |
| }; |
| |
| // Maintaining the aspect ratio is not a requirement for the video to appear, |
| // but doing so will improve the quality of the resulting image. |
| ExpectSwapChainAndMaintainsAspectRatio( |
| gfx::OverlayLayerId::MakeForTesting(0), |
| gfx::SizeF(D3D11_REQ_TEXTURE2D_U_OR_V_DIMENSION + 1, 10)); |
| ExpectSwapChainAndMaintainsAspectRatio( |
| gfx::OverlayLayerId::MakeForTesting(1), |
| gfx::SizeF(10, D3D11_REQ_TEXTURE2D_U_OR_V_DIMENSION + 1)); |
| } |
| |
| INSTANTIATE_TEST_SUITE_P(, |
| DCompPresenterTest, |
| DCompPresenterTest::GetValues(), |
| &DCompPresenterTest::GetParamName); |
| |
| template <class Param = std::monostate> |
| class DCompPresenterPixelTestBase : public DCompPresenterTestBase<Param> { |
| public: |
| DCompPresenterPixelTestBase() |
| : window_(&platform_delegate_, gfx::Rect(100, 100)) { |
| this->parent_window_ = window_.hwnd(); |
| } |
| |
| protected: |
| void SetUp() override { |
| static_cast<ui::PlatformWindow*>(&window_)->Show(); |
| DCompPresenterTestBase<Param>::SetUp(); |
| } |
| |
| void TearDown() override { |
| // Test harness times out without DestroyWindow() here. |
| if (IsWindow(this->parent_window_)) { |
| DestroyWindow(this->parent_window_); |
| } |
| DCompPresenterTestBase<Param>::TearDown(); |
| } |
| |
| void InitializeForPixelTest(const gfx::Size& window_size, |
| const gfx::Size& texture_size, |
| const gfx::Rect& content_rect, |
| const gfx::Rect& quad_rect, |
| const bool is_p010) { |
| EXPECT_TRUE( |
| this->presenter_->Resize(window_size, 1.0, gfx::ColorSpace(), true)); |
| |
| this->InitializeRootAndScheduleRootSurface(window_size, SkColors::kBlack); |
| |
| Microsoft::WRL::ComPtr<ID3D11Device> d3d11_device = |
| GetDirectCompositionD3D11Device(); |
| |
| Microsoft::WRL::ComPtr<ID3D11Texture2D> texture = |
| is_p010 ? CreateP010Texture(d3d11_device, texture_size) |
| : CreateNV12Texture(d3d11_device, texture_size); |
| ASSERT_NE(texture, nullptr); |
| |
| auto params = CreateParamsFromImage( |
| DCLayerOverlayImage(texture_size, texture), |
| /*content_rect_override=*/gfx::RectF(content_rect)); |
| params.quad_rect = quad_rect; |
| params.layer_id = gfx::OverlayLayerId::MakeForTesting(0); |
| params.video_params.color_space = gfx::ColorSpace::CreateREC709(); |
| params.video_params.is_p010_content = is_p010; |
| this->ScheduleOverlay(std::move(params)); |
| |
| ASSERT_EQ(this->PresentAndGetSwapResult(), gfx::SwapResult::SWAP_ACK); |
| |
| Sleep(1000); |
| } |
| |
| // If |scale_via_buffer| is true, use the content/quad rects to scale the |
| // buffer. If it is false, use the overlay's transform to scale the visual. |
| void RunNearestNeighborTest(bool scale_via_buffer) { |
| const gfx::Size window_size(100, 100); |
| |
| EXPECT_TRUE( |
| this->presenter_->Resize(window_size, 1.0, gfx::ColorSpace(), true)); |
| |
| this->InitializeRootAndScheduleRootSurface(window_size, SkColors::kBlack); |
| |
| auto dc_layer_params = CreateParamsFromImage( |
| CreateDCompSurface(gfx::Size(2, 2), SkColors::kBlack, |
| {{gfx::Rect(0, 0, 1, 1), SkColors::kRed}, |
| {gfx::Rect(1, 0, 1, 1), SkColors::kGreen}, |
| {gfx::Rect(0, 1, 1, 1), SkColors::kBlue}, |
| {gfx::Rect(1, 1, 1, 1), SkColors::kBlack}})); |
| dc_layer_params.z_order = 1; |
| dc_layer_params.nearest_neighbor_filter = true; |
| dc_layer_params.layer_id = gfx::OverlayLayerId::MakeForTesting(0); |
| |
| if (scale_via_buffer) { |
| // Pick a large quad rect so the buffer is scaled up |
| dc_layer_params.quad_rect = gfx::Rect(window_size); |
| } else { |
| // Pick a small quad rect and assign a transform so the quad rect is |
| // scaled up |
| dc_layer_params.quad_rect = |
| gfx::ToNearestRect(dc_layer_params.content_rect); |
| dc_layer_params.transform = gfx::Transform::MakeScale( |
| window_size.width() / dc_layer_params.quad_rect.width(), |
| window_size.height() / dc_layer_params.quad_rect.height()); |
| } |
| |
| this->ScheduleOverlay(std::move(dc_layer_params)); |
| ASSERT_EQ(this->PresentAndGetSwapResult(), gfx::SwapResult::SWAP_ACK); |
| |
| SkBitmap pixels = GLTestHelper::ReadBackWindow(window_.hwnd(), window_size); |
| |
| EXPECT_SKCOLOR_EQ( |
| SK_ColorRED, GLTestHelper::GetColorAtPoint(pixels, gfx::Point(49, 49))); |
| EXPECT_SKCOLOR_EQ(SK_ColorGREEN, GLTestHelper::GetColorAtPoint( |
| pixels, gfx::Point(51, 49))); |
| EXPECT_SKCOLOR_EQ(SK_ColorBLUE, GLTestHelper::GetColorAtPoint( |
| pixels, gfx::Point(49, 51))); |
| EXPECT_SKCOLOR_EQ(SK_ColorBLACK, GLTestHelper::GetColorAtPoint( |
| pixels, gfx::Point(51, 51))); |
| } |
| |
| // These colors are used for |CheckOverlayExactlyFillsHole|. |
| // The initial root surface color |
| const SkColor4f kRootSurfaceInitialColor = SkColors::kBlack; |
| // The "hole" in the root surface that we expect the overlay to completely |
| // cover. |
| const SkColor4f kRootSurfaceHiddenColor = SkColors::kRed; |
| // The color of the visible portion of the overlay image. |
| const SkColor4f kOverlayExpectedColor = SkColors::kBlue; |
| // The color of the portion of the overlay image hidden by the content rect. |
| const SkColor4f kOverlayImageHiddenColor = SkColors::kGreen; |
| |
| const char* CheckOverlayExactlyFillsHoleColorToString(SkColor4f c) { |
| if (c == kRootSurfaceInitialColor) { |
| return "RootSurfaceInitialColor"; |
| } else if (c == kRootSurfaceHiddenColor) { |
| return "RootSurfaceHiddenColor"; |
| } else if (c == kOverlayExpectedColor) { |
| return "OverlayExpectedColor"; |
| } else if (c == kOverlayImageHiddenColor) { |
| return "OverlayImageHiddenColor"; |
| } |
| return "unexpected color"; |
| } |
| |
| // Check that |fit_in_hole_overlay| exactly covers |root_surface_hole|. |
| // This test uses the colors defined above to test for coverage: the resulting |
| // image should only contain |kOverlayExpectedColor| where the hole was and |
| // |kRootSurfaceInitialColor| elsewhere. |
| void CheckOverlayExactlyFillsHole(const gfx::Size& window_size, |
| const gfx::Rect& root_surface_hole, |
| DCLayerOverlayParams fit_in_hole_overlay) { |
| EXPECT_TRUE(gfx::Rect(window_size).Contains(root_surface_hole)); |
| |
| EXPECT_TRUE( |
| this->presenter_->Resize(window_size, 1.0, gfx::ColorSpace(), true)); |
| |
| auto root_surface = CreateParamsFromImage( |
| CreateDCompSurface(window_size, kRootSurfaceInitialColor, |
| {{root_surface_hole, kRootSurfaceHiddenColor}})); |
| root_surface.quad_rect = gfx::Rect(window_size); |
| root_surface.z_order = 0; |
| root_surface.layer_id = this->GetRootSurfaceId(); |
| this->ScheduleOverlay(std::move(root_surface)); |
| |
| this->ScheduleOverlay(std::move(fit_in_hole_overlay)); |
| |
| ASSERT_EQ(this->PresentAndGetSwapResult(), gfx::SwapResult::SWAP_ACK); |
| |
| auto pixels = GLTestHelper::ReadBackWindow(window_.hwnd(), window_size); |
| |
| for (int y = 0; y < window_size.height(); y++) { |
| for (int x = 0; x < window_size.width(); x++) { |
| gfx::Point location(x, y); |
| bool in_hole = root_surface_hole.Contains(location); |
| SkColor actual_color = GLTestHelper::GetColorAtPoint(pixels, location); |
| SkColor expected_color = |
| (in_hole ? kOverlayExpectedColor : kRootSurfaceInitialColor) |
| .toSkColor(); |
| if (actual_color != expected_color) { |
| ADD_FAILURE() << "Unexpected pixel at " << location.ToString() |
| << " (in_hole=" << in_hole << ")\n" |
| << "Expected:\n " << std::hex << "0x" << expected_color |
| << " (" |
| << CheckOverlayExactlyFillsHoleColorToString( |
| SkColor4f::FromColor(expected_color)) |
| << ")\n" |
| << "But got:\n " |
| << "0x" << actual_color << " (" |
| << CheckOverlayExactlyFillsHoleColorToString( |
| SkColor4f::FromColor(actual_color)) |
| << ")"; |
| return; |
| } |
| } |
| } |
| } |
| |
| void InitializeSwapChainForTest( |
| gfx::Size swap_chain_size, |
| Microsoft::WRL::ComPtr<IDXGISwapChain1>& swap_chain, |
| Microsoft::WRL::ComPtr<ID3D11RenderTargetView>& rtv) { |
| Microsoft::WRL::ComPtr<ID3D11Device> d3d11_device = |
| GetDirectCompositionD3D11Device(); |
| ASSERT_TRUE(d3d11_device); |
| Microsoft::WRL::ComPtr<IDXGIDevice> dxgi_device; |
| ASSERT_HRESULT_SUCCEEDED(d3d11_device.As(&dxgi_device)); |
| ASSERT_TRUE(dxgi_device); |
| Microsoft::WRL::ComPtr<IDXGIAdapter> dxgi_adapter; |
| ASSERT_HRESULT_SUCCEEDED(dxgi_device->GetAdapter(&dxgi_adapter)); |
| ASSERT_TRUE(dxgi_adapter); |
| Microsoft::WRL::ComPtr<IDXGIFactory2> dxgi_factory; |
| ASSERT_HRESULT_SUCCEEDED( |
| dxgi_adapter->GetParent(IID_PPV_ARGS(&dxgi_factory))); |
| ASSERT_TRUE(dxgi_factory); |
| |
| DXGI_SWAP_CHAIN_DESC1 desc = {}; |
| desc.Width = swap_chain_size.width(); |
| desc.Height = swap_chain_size.height(); |
| desc.Format = DXGI_FORMAT_B8G8R8A8_UNORM; |
| desc.Stereo = FALSE; |
| desc.SampleDesc.Count = 1; |
| desc.BufferCount = 2; |
| desc.BufferUsage = |
| DXGI_USAGE_RENDER_TARGET_OUTPUT | DXGI_USAGE_SHADER_INPUT; |
| desc.Scaling = DXGI_SCALING_STRETCH; |
| desc.SwapEffect = DXGI_SWAP_EFFECT_FLIP_SEQUENTIAL; |
| desc.Flags = 0; |
| |
| ASSERT_HRESULT_SUCCEEDED(dxgi_factory->CreateSwapChainForComposition( |
| d3d11_device.Get(), &desc, nullptr, &swap_chain)); |
| ASSERT_TRUE(swap_chain); |
| Microsoft::WRL::ComPtr<ID3D11Texture2D> front_buffer_texture; |
| ASSERT_HRESULT_SUCCEEDED( |
| swap_chain->GetBuffer(1u, IID_PPV_ARGS(&front_buffer_texture))); |
| ASSERT_TRUE(front_buffer_texture); |
| Microsoft::WRL::ComPtr<ID3D11Texture2D> back_buffer_texture; |
| ASSERT_TRUE(SUCCEEDED( |
| swap_chain->GetBuffer(0u, IID_PPV_ARGS(&back_buffer_texture)))); |
| ASSERT_TRUE(back_buffer_texture); |
| |
| d3d11_device->CreateRenderTargetView(back_buffer_texture.Get(), nullptr, |
| &rtv); |
| } |
| |
| [[nodiscard]] HRESULT ClearRenderTargetViewAndPresent( |
| const SkColor4f& clear_color, |
| IDXGISwapChain1* swap_chain, |
| ID3D11RenderTargetView* rtv) { |
| GetImmediateDeviceContext()->ClearRenderTargetView(rtv, clear_color.vec()); |
| DXGI_PRESENT_PARAMETERS present_params = {}; |
| present_params.DirtyRectsCount = 0; |
| present_params.pDirtyRects = nullptr; |
| return swap_chain->Present1(0, 0, &present_params); |
| } |
| |
| Microsoft::WRL::ComPtr<ID3D11DeviceContext> GetImmediateDeviceContext() { |
| Microsoft::WRL::ComPtr<ID3D11DeviceContext> device_context; |
| GetDirectCompositionD3D11Device()->GetImmediateContext(&device_context); |
| EXPECT_TRUE(device_context); |
| return device_context; |
| } |
| |
| TestPlatformDelegate platform_delegate_; |
| ui::WinWindow window_; |
| }; |
| |
| class DCompPresenterVideoPixelTest : public DCompPresenterPixelTestBase<> { |
| protected: |
| void TestVideo(const gfx::ColorSpace& color_space, |
| SkColor expected_color, |
| bool check_color) { |
| if (!presenter_) { |
| return; |
| } |
| |
| gfx::Size window_size(100, 100); |
| EXPECT_TRUE(presenter_->Resize(window_size, 1.0, gfx::ColorSpace(), true)); |
| |
| Microsoft::WRL::ComPtr<ID3D11Device> d3d11_device = |
| GetDirectCompositionD3D11Device(); |
| |
| gfx::Size texture_size(50, 50); |
| Microsoft::WRL::ComPtr<ID3D11Texture2D> texture = |
| CreateNV12Texture(d3d11_device, texture_size); |
| ASSERT_NE(texture, nullptr); |
| |
| { |
| auto params = |
| CreateParamsFromImage(DCLayerOverlayImage(texture_size, texture)); |
| params.quad_rect = gfx::Rect(texture_size); |
| params.video_params.color_space = color_space; |
| params.layer_id = gfx::OverlayLayerId::MakeForTesting(0); |
| ScheduleOverlay(std::move(params)); |
| } |
| |
| ASSERT_EQ(PresentAndGetSwapResult(), gfx::SwapResult::SWAP_ACK); |
| |
| // Scaling up the swapchain with the same image should cause it to be |
| // transformed again, but not presented again. |
| { |
| auto params = |
| CreateParamsFromImage(DCLayerOverlayImage(texture_size, texture)); |
| params.quad_rect = gfx::Rect(window_size); |
| params.video_params.color_space = color_space; |
| params.layer_id = gfx::OverlayLayerId::MakeForTesting(0); |
| ScheduleOverlay(std::move(params)); |
| } |
| |
| ASSERT_EQ(PresentAndGetSwapResult(), gfx::SwapResult::SWAP_ACK); |
| Sleep(1000); |
| |
| if (check_color) { |
| EXPECT_SKCOLOR_CLOSE( |
| expected_color, |
| GLTestHelper::ReadBackWindowPixel(window_.hwnd(), gfx::Point(75, 75)), |
| kMaxColorChannelDeviation); |
| } |
| } |
| }; |
| |
| INSTANTIATE_TEST_SUITE_P(, |
| DCompPresenterVideoPixelTest, |
| DCompPresenterVideoPixelTest::GetValues(), |
| &DCompPresenterVideoPixelTest::GetParamName); |
| |
| TEST_P(DCompPresenterVideoPixelTest, BT601) { |
| TestVideo(gfx::ColorSpace::CreateREC601(), SkColorSetRGB(0xdb, 0x81, 0xe8), |
| true); |
| } |
| |
| TEST_P(DCompPresenterVideoPixelTest, BT709) { |
| TestVideo(gfx::ColorSpace::CreateREC709(), SkColorSetRGB(0xe1, 0x90, 0xeb), |
| true); |
| } |
| |
| TEST_P(DCompPresenterVideoPixelTest, SRGB) { |
| // SRGB doesn't make sense on an NV12 input, but don't crash. |
| TestVideo(gfx::ColorSpace::CreateSRGB(), SK_ColorTRANSPARENT, false); |
| } |
| |
| TEST_P(DCompPresenterVideoPixelTest, SCRGBLinear) { |
| // SCRGB doesn't make sense on an NV12 input, but don't crash. |
| TestVideo(gfx::ColorSpace::CreateSRGBLinear(), SK_ColorTRANSPARENT, false); |
| } |
| |
| TEST_P(DCompPresenterVideoPixelTest, InvalidColorSpace) { |
| // Invalid color space should be treated as BT.709 |
| TestVideo(gfx::ColorSpace(), SkColorSetRGB(0xe1, 0x90, 0xeb), true); |
| } |
| |
| class DCompPresenterPixelTest : public DCompPresenterPixelTestBase<> {}; |
| |
| INSTANTIATE_TEST_SUITE_P(, |
| DCompPresenterPixelTest, |
| DCompPresenterPixelTest::GetValues(), |
| &DCompPresenterPixelTest::GetParamName); |
| |
| TEST_P(DCompPresenterPixelTest, SoftwareVideoSwapchain) { |
| gfx::Size window_size(100, 100); |
| EXPECT_TRUE(presenter_->Resize(window_size, 1.0, gfx::ColorSpace(), true)); |
| |
| Microsoft::WRL::ComPtr<ID3D11Device> d3d11_device = |
| GetDirectCompositionD3D11Device(); |
| |
| gfx::Size y_size(50, 50); |
| size_t stride = y_size.width(); |
| |
| std::vector<uint8_t> nv12_pixmap(stride * 3 * y_size.height() / 2, 0xff); |
| |
| auto params = CreateParamsFromImage( |
| DCLayerOverlayImage(y_size, base::span(nv12_pixmap), stride)); |
| params.quad_rect = gfx::Rect(window_size); |
| params.video_params.color_space = gfx::ColorSpace::CreateREC709(); |
| params.layer_id = gfx::OverlayLayerId::MakeForTesting(0); |
| ScheduleOverlay(std::move(params)); |
| |
| ASSERT_EQ(PresentAndGetSwapResult(), gfx::SwapResult::SWAP_ACK); |
| Sleep(1000); |
| |
| SkColor expected_color = SkColorSetRGB(0xff, 0xb7, 0xff); |
| EXPECT_SKCOLOR_CLOSE( |
| expected_color, |
| GLTestHelper::ReadBackWindowPixel(window_.hwnd(), gfx::Point(75, 75)), |
| kMaxColorChannelDeviation); |
| } |
| |
| TEST_P(DCompPresenterPixelTest, VideoHandleSwapchain) { |
| gfx::Size window_size(100, 100); |
| gfx::Size texture_size(50, 50); |
| gfx::Rect content_rect(texture_size); |
| gfx::Rect quad_rect(window_size); |
| InitializeForPixelTest(window_size, texture_size, content_rect, quad_rect, |
| false); |
| |
| SkColor expected_color = SkColorSetRGB(0xe1, 0x90, 0xeb); |
| EXPECT_SKCOLOR_CLOSE( |
| expected_color, |
| GLTestHelper::ReadBackWindowPixel(window_.hwnd(), gfx::Point(75, 75)), |
| kMaxColorChannelDeviation); |
| } |
| |
| TEST_P(DCompPresenterPixelTest, SkipVideoLayerEmptyBoundsRect) { |
| gfx::Size window_size(100, 100); |
| gfx::Size texture_size(50, 50); |
| gfx::Rect content_rect(texture_size); |
| gfx::Rect quad_rect; // Layer with empty bounds rect. |
| InitializeForPixelTest(window_size, texture_size, content_rect, quad_rect, |
| false); |
| |
| // No color is written since the visual committed to DirectComposition has no |
| // content. |
| SkColor expected_color = SK_ColorBLACK; |
| EXPECT_SKCOLOR_CLOSE( |
| expected_color, |
| GLTestHelper::ReadBackWindowPixel(window_.hwnd(), gfx::Point(75, 75)), |
| kMaxColorChannelDeviation); |
| } |
| |
| TEST_P(DCompPresenterPixelTest, SkipVideoLayerEmptyContentsRect) { |
| // Swap chain size is overridden to onscreen size only if scaled overlays |
| // are supported. |
| SetDirectCompositionScaledOverlaysSupportedForTesting(true); |
| |
| gfx::Size window_size(100, 100); |
| EXPECT_TRUE(presenter_->Resize(window_size, 1.0, gfx::ColorSpace(), true)); |
| |
| InitializeRootAndScheduleRootSurface(window_size, SkColors::kBlack); |
| |
| Microsoft::WRL::ComPtr<ID3D11Device> d3d11_device = |
| GetDirectCompositionD3D11Device(); |
| |
| gfx::Size texture_size(50, 50); |
| Microsoft::WRL::ComPtr<ID3D11Texture2D> texture = |
| CreateNV12Texture(d3d11_device, texture_size); |
| ASSERT_NE(texture, nullptr); |
| |
| // Layer with empty content rect. |
| auto params = |
| CreateParamsFromImage(DCLayerOverlayImage(texture_size, texture), |
| /*content_rect_override=*/gfx::RectF()); |
| params.quad_rect = gfx::Rect(window_size); |
| params.video_params.color_space = gfx::ColorSpace::CreateREC709(); |
| params.layer_id = gfx::OverlayLayerId::MakeForTesting(0); |
| ScheduleOverlay(std::move(params)); |
| |
| ASSERT_EQ(PresentAndGetSwapResult(), gfx::SwapResult::SWAP_ACK); |
| |
| Sleep(1000); |
| |
| // No color is written since the visual committed to DirectComposition has no |
| // content. |
| SkColor expected_color = SK_ColorBLACK; |
| EXPECT_SKCOLOR_CLOSE( |
| expected_color, |
| GLTestHelper::ReadBackWindowPixel(window_.hwnd(), gfx::Point(75, 75)), |
| kMaxColorChannelDeviation); |
| } |
| |
| TEST_P(DCompPresenterPixelTest, BGRASwapChain) { |
| // By default NV12 is used, so set it to BGRA explicitly. |
| SetDirectCompositionOverlayFormatUsedForTesting(DXGI_FORMAT_B8G8R8A8_UNORM); |
| // Swap chain size is overridden to onscreen rect size only if scaled overlays |
| // are supported. |
| SetDirectCompositionScaledOverlaysSupportedForTesting(true); |
| |
| gfx::Size window_size(100, 100); |
| gfx::Size texture_size(50, 50); |
| // Pass content rect with odd with and height. Surface should round up |
| // width and height when creating swap chain. |
| gfx::Rect content_rect(0, 0, 49, 49); |
| gfx::Rect quad_rect(window_size); |
| InitializeForPixelTest(window_size, texture_size, content_rect, quad_rect, |
| false); |
| |
| Microsoft::WRL::ComPtr<IDXGISwapChain1> swap_chain = |
| presenter_->GetLayerSwapChainForTesting( |
| gfx::OverlayLayerId::MakeForTesting(0)); |
| ASSERT_TRUE(swap_chain); |
| |
| DXGI_SWAP_CHAIN_DESC1 desc; |
| EXPECT_HRESULT_SUCCEEDED(swap_chain->GetDesc1(&desc)); |
| // Onscreen window_size is (100, 100). |
| EXPECT_EQ(DXGI_FORMAT_B8G8R8A8_UNORM, desc.Format); |
| EXPECT_EQ(100u, desc.Width); |
| EXPECT_EQ(100u, desc.Height); |
| |
| SkColor expected_color = SkColorSetRGB(0xe1, 0x90, 0xeb); |
| EXPECT_SKCOLOR_CLOSE( |
| expected_color, |
| GLTestHelper::ReadBackWindowPixel(window_.hwnd(), gfx::Point(75, 75)), |
| kMaxColorChannelDeviation); |
| } |
| |
| TEST_P(DCompPresenterPixelTest, NV12SwapChain) { |
| // Swap chain size is overridden to onscreen rect size only if scaled overlays |
| // are supported. |
| SetDirectCompositionScaledOverlaysSupportedForTesting(true); |
| |
| gfx::Size window_size(100, 100); |
| gfx::Size texture_size(50, 50); |
| // Pass content rect with odd with and height. Surface should round up |
| // width and height when creating swap chain. |
| gfx::Rect content_rect(0, 0, 49, 49); |
| gfx::Rect quad_rect(window_size); |
| InitializeForPixelTest(window_size, texture_size, content_rect, quad_rect, |
| false); |
| |
| Microsoft::WRL::ComPtr<IDXGISwapChain1> swap_chain = |
| presenter_->GetLayerSwapChainForTesting( |
| gfx::OverlayLayerId::MakeForTesting(0)); |
| ASSERT_TRUE(swap_chain); |
| |
| DXGI_SWAP_CHAIN_DESC1 desc; |
| EXPECT_HRESULT_SUCCEEDED(swap_chain->GetDesc1(&desc)); |
| // Onscreen window_size is (100, 100). |
| EXPECT_EQ(DXGI_FORMAT_NV12, desc.Format); |
| EXPECT_EQ(100u, desc.Width); |
| EXPECT_EQ(100u, desc.Height); |
| |
| SkColor expected_color = SkColorSetRGB(0xe1, 0x90, 0xeb); |
| EXPECT_SKCOLOR_CLOSE( |
| expected_color, |
| GLTestHelper::ReadBackWindowPixel(window_.hwnd(), gfx::Point(75, 75)), |
| kMaxColorChannelDeviation); |
| } |
| |
| TEST_P(DCompPresenterPixelTest, YUY2SwapChain) { |
| // By default NV12 is used, so set it to YUY2 explicitly. |
| SetDirectCompositionOverlayFormatUsedForTesting(DXGI_FORMAT_YUY2); |
| // Swap chain size is overridden to onscreen rect size only if scaled overlays |
| // are supported. |
| SetDirectCompositionScaledOverlaysSupportedForTesting(true); |
| |
| gfx::Size window_size(100, 100); |
| gfx::Size texture_size(50, 50); |
| // Pass content rect with odd with and height. Surface should round up |
| // width and height when creating swap chain. |
| gfx::Rect content_rect(0, 0, 49, 49); |
| gfx::Rect quad_rect(window_size); |
| InitializeForPixelTest(window_size, texture_size, content_rect, quad_rect, |
| false); |
| |
| Microsoft::WRL::ComPtr<IDXGISwapChain1> swap_chain = |
| presenter_->GetLayerSwapChainForTesting( |
| gfx::OverlayLayerId::MakeForTesting(0)); |
| ASSERT_TRUE(swap_chain); |
| |
| DXGI_SWAP_CHAIN_DESC1 desc; |
| EXPECT_HRESULT_SUCCEEDED(swap_chain->GetDesc1(&desc)); |
| // Onscreen window_size is (100, 100). |
| EXPECT_EQ(DXGI_FORMAT_YUY2, desc.Format); |
| EXPECT_EQ(100u, desc.Width); |
| EXPECT_EQ(100u, desc.Height); |
| |
| SkColor expected_color = SkColorSetRGB(0xe1, 0x90, 0xeb); |
| EXPECT_SKCOLOR_CLOSE( |
| expected_color, |
| GLTestHelper::ReadBackWindowPixel(window_.hwnd(), gfx::Point(75, 75)), |
| kMaxColorChannelDeviation); |
| } |
| |
| TEST_P(DCompPresenterPixelTest, P010SwapChain) { |
| if (GetDirectCompositionOverlaySupportFlagsForTesting(DXGI_FORMAT_P010) == |
| 0) { |
| GTEST_SKIP() << "P010 overlay is not supported on this test system."; |
| } |
| |
| // Swap chain size is overridden to onscreen rect size only if scaled overlays |
| // are supported. |
| SetDirectCompositionScaledOverlaysSupportedForTesting(true); |
| // By default NV12 is used, so set it to P010 explicitly. |
| SetDirectCompositionOverlayFormatUsedForTesting(DXGI_FORMAT_P010); |
| |
| gfx::Size window_size(100, 100); |
| gfx::Size texture_size(50, 50); |
| // Pass content rect with odd with and height. Surface should round up |
| // width and height when creating swap chain. |
| gfx::Rect content_rect(0, 0, 49, 49); |
| gfx::Rect quad_rect(window_size); |
| InitializeForPixelTest(window_size, texture_size, content_rect, quad_rect, |
| true); |
| |
| Microsoft::WRL::ComPtr<IDXGISwapChain1> swap_chain = |
| presenter_->GetLayerSwapChainForTesting( |
| gfx::OverlayLayerId::MakeForTesting(0)); |
| ASSERT_TRUE(swap_chain); |
| |
| DXGI_SWAP_CHAIN_DESC1 desc; |
| EXPECT_HRESULT_SUCCEEDED(swap_chain->GetDesc1(&desc)); |
| // Onscreen window_size is (100, 100). |
| EXPECT_EQ(DXGI_FORMAT_P010, desc.Format); |
| EXPECT_EQ(100u, desc.Width); |
| EXPECT_EQ(100u, desc.Height); |
| |
| SkColor expected_color = SkColorSetRGB(0xe1, 0x90, 0xeb); |
| EXPECT_SKCOLOR_CLOSE( |
| expected_color, |
| GLTestHelper::ReadBackWindowPixel(window_.hwnd(), gfx::Point(75, 75)), |
| kMaxColorChannelDeviation); |
| } |
| |
| TEST_P(DCompPresenterPixelTest, NonZeroBoundsOffset) { |
| // Swap chain size is overridden to onscreen rect size only if scaled overlays |
| // are supported. |
| SetDirectCompositionScaledOverlaysSupportedForTesting(true); |
| |
| gfx::Size window_size(100, 100); |
| gfx::Size texture_size(50, 50); |
| gfx::Rect content_rect(texture_size); |
| gfx::Rect quad_rect(gfx::Point(25, 25), texture_size); |
| InitializeForPixelTest(window_size, texture_size, content_rect, quad_rect, |
| false); |
| |
| SkColor video_color = SkColorSetRGB(0xe1, 0x90, 0xeb); |
| struct { |
| gfx::Point point; |
| SkColor expected_color; |
| } test_cases[] = { |
| // Outside bounds |
| {{24, 24}, SK_ColorBLACK}, |
| {{75, 75}, SK_ColorBLACK}, |
| // Inside bounds |
| {{25, 25}, video_color}, |
| {{74, 74}, video_color}, |
| }; |
| |
| auto pixels = GLTestHelper::ReadBackWindow(window_.hwnd(), window_size); |
| |
| for (const auto& test_case : test_cases) { |
| const auto& point = test_case.point; |
| const auto& expected_color = test_case.expected_color; |
| EXPECT_SKCOLOR_CLOSE(expected_color, |
| GLTestHelper::GetColorAtPoint(pixels, point), |
| kMaxColorChannelDeviation) |
| << " at " << point.ToString(); |
| } |
| } |
| |
| TEST_P(DCompPresenterPixelTest, ResizeVideoLayer) { |
| // Swap chain size is overridden to onscreen rect size only if scaled overlays |
| // are supported. |
| SetDirectCompositionScaledOverlaysSupportedForTesting(true); |
| |
| gfx::Size window_size(100, 100); |
| EXPECT_TRUE(presenter_->Resize(window_size, 1.0, gfx::ColorSpace(), true)); |
| |
| InitializeRootAndScheduleRootSurface(window_size, SkColors::kBlack); |
| |
| Microsoft::WRL::ComPtr<ID3D11Device> d3d11_device = |
| GetDirectCompositionD3D11Device(); |
| |
| gfx::Size texture_size(50, 50); |
| Microsoft::WRL::ComPtr<ID3D11Texture2D> texture = |
| CreateNV12Texture(d3d11_device, texture_size); |
| ASSERT_NE(texture, nullptr); |
| |
| // (1) Test if swap chain is overridden to window size (100, 100). |
| { |
| auto params = |
| CreateParamsFromImage(DCLayerOverlayImage(texture_size, texture)); |
| params.quad_rect = gfx::Rect(window_size); |
| params.layer_id = gfx::OverlayLayerId::MakeForTesting(0); |
| params.video_params.color_space = gfx::ColorSpace::CreateREC709(); |
| ScheduleOverlay(std::move(params)); |
| |
| ASSERT_EQ(PresentAndGetSwapResult(), gfx::SwapResult::SWAP_ACK); |
| } |
| |
| Microsoft::WRL::ComPtr<IDXGISwapChain1> swap_chain = |
| presenter_->GetLayerSwapChainForTesting( |
| gfx::OverlayLayerId::MakeForTesting(0)); |
| ASSERT_TRUE(swap_chain); |
| |
| DXGI_SWAP_CHAIN_DESC1 desc; |
| EXPECT_HRESULT_SUCCEEDED(swap_chain->GetDesc1(&desc)); |
| // Onscreen window_size is (100, 100). |
| EXPECT_EQ(100u, desc.Width); |
| EXPECT_EQ(100u, desc.Height); |
| |
| // (2) Test if swap chain is overridden to window size (100, 100). |
| { |
| auto params = |
| CreateParamsFromImage(DCLayerOverlayImage(texture_size, texture), |
| /*content_rect_override=*/gfx::RectF(30, 30)); |
| params.quad_rect = gfx::Rect(window_size); |
| params.layer_id = gfx::OverlayLayerId::MakeForTesting(0); |
| params.video_params.color_space = gfx::ColorSpace::CreateREC709(); |
| ScheduleOverlay(std::move(params)); |
| |
| ASSERT_EQ(PresentAndGetSwapResult(), gfx::SwapResult::SWAP_ACK); |
| } |
| swap_chain = presenter_->GetLayerSwapChainForTesting( |
| gfx::OverlayLayerId::MakeForTesting(0)); |
| EXPECT_HRESULT_SUCCEEDED(swap_chain->GetDesc1(&desc)); |
| // Onscreen window_size is (100, 100). |
| EXPECT_EQ(100u, desc.Width); |
| EXPECT_EQ(100u, desc.Height); |
| |
| if (base::FeatureList::IsEnabled( |
| features::kEarlyFullScreenVideoOptimization)) { |
| // The rest of this test checks that video overlays are adjusted for full |
| // screen. EarlyFullScreenVideoOptimization handles adjustment of overlay |
| // position during overlay processing. |
| return; |
| } |
| |
| // (3) Test if swap chain is adjusted to fit the monitor when overlay scaling |
| // is not supported and video on-screen size is slightly smaller than the |
| // monitor. Clipping is on. |
| SetDirectCompositionScaledOverlaysSupportedForTesting(false); |
| gfx::Size monitor_size = window_size; |
| SetDirectCompositionMonitorInfoForTesting(1, window_size); |
| gfx::Rect on_screen_rect = |
| gfx::Rect(0, 0, monitor_size.width() - 2, monitor_size.height() - 2); |
| { |
| auto params = |
| CreateParamsFromImage(DCLayerOverlayImage(texture_size, texture)); |
| params.quad_rect = on_screen_rect; |
| params.clip_rect = on_screen_rect; |
| params.layer_id = gfx::OverlayLayerId::MakeForTesting(0); |
| params.video_params.color_space = gfx::ColorSpace::CreateREC709(); |
| ScheduleOverlay(std::move(params)); |
| |
| ASSERT_EQ(PresentAndGetSwapResult(), gfx::SwapResult::SWAP_ACK); |
| } |
| |
| // Swap chain is set to monitor/onscreen size. |
| swap_chain = presenter_->GetLayerSwapChainForTesting( |
| gfx::OverlayLayerId::MakeForTesting(0)); |
| EXPECT_HRESULT_SUCCEEDED(swap_chain->GetDesc1(&desc)); |
| EXPECT_EQ(static_cast<UINT>(monitor_size.width()), desc.Width); |
| EXPECT_EQ(static_cast<UINT>(monitor_size.height()), desc.Height); |
| |
| gfx::Transform transform; |
| gfx::Point offset; |
| gfx::Rect clip_rect; |
| presenter_->GetSwapChainVisualInfoForTesting( |
| gfx::OverlayLayerId::MakeForTesting(0), &transform, &offset, &clip_rect); |
| EXPECT_TRUE(transform.IsIdentity()); |
| EXPECT_EQ(gfx::Rect(monitor_size), clip_rect); |
| |
| // (4) Test if the final on-screen size is adjusted to fit the monitor when |
| // overlay scaling is supported and video on-screen size is slightly bigger |
| // than the monitor. Clipping is off. |
| SetDirectCompositionScaledOverlaysSupportedForTesting(true); |
| on_screen_rect = |
| gfx::Rect(0, 0, monitor_size.width() + 2, monitor_size.height() + 2); |
| { |
| auto params = |
| CreateParamsFromImage(DCLayerOverlayImage(texture_size, texture)); |
| params.quad_rect = on_screen_rect; |
| params.layer_id = gfx::OverlayLayerId::MakeForTesting(0); |
| params.video_params.color_space = gfx::ColorSpace::CreateREC709(); |
| ScheduleOverlay(std::move(params)); |
| |
| ASSERT_EQ(PresentAndGetSwapResult(), gfx::SwapResult::SWAP_ACK); |
| } |
| |
| // Swap chain is set to monitor size (100, 100). |
| swap_chain = presenter_->GetLayerSwapChainForTesting( |
| gfx::OverlayLayerId::MakeForTesting(0)); |
| EXPECT_HRESULT_SUCCEEDED(swap_chain->GetDesc1(&desc)); |
| EXPECT_EQ(100u, desc.Width); |
| EXPECT_EQ(100u, desc.Height); |
| |
| // Make sure the new transform matrix is adjusted, so it transforms the swap |
| // chain to |new_on_screen_rect| which fits the monitor. |
| presenter_->GetSwapChainVisualInfoForTesting( |
| gfx::OverlayLayerId::MakeForTesting(0), &transform, &offset, &clip_rect); |
| EXPECT_EQ(gfx::Rect(monitor_size), transform.MapRect(gfx::Rect(100, 100))); |
| } |
| |
| TEST_P(DCompPresenterPixelTest, SwapChainImage) { |
| gfx::Size swap_chain_size(50, 50); |
| Microsoft::WRL::ComPtr<IDXGISwapChain1> swap_chain; |
| Microsoft::WRL::ComPtr<ID3D11RenderTargetView> rtv; |
| InitializeSwapChainForTest(swap_chain_size, swap_chain, rtv); |
| ASSERT_TRUE(swap_chain); |
| ASSERT_TRUE(rtv); |
| |
| gfx::Size window_size(100, 100); |
| EXPECT_TRUE(presenter_->Resize(window_size, 1.0, gfx::ColorSpace(), true)); |
| |
| InitializeRootAndScheduleRootSurface(window_size, SkColors::kBlack); |
| |
| // Clear to red and present. |
| { |
| ASSERT_HRESULT_SUCCEEDED(ClearRenderTargetViewAndPresent( |
| SkColors::kRed, swap_chain.Get(), rtv.Get())); |
| |
| auto dc_layer_params = |
| CreateParamsFromImage(DCLayerOverlayImage(swap_chain_size, swap_chain)); |
| dc_layer_params.quad_rect = gfx::Rect(window_size); |
| dc_layer_params.z_order = 1; |
| dc_layer_params.layer_id = gfx::OverlayLayerId::MakeForTesting(0); |
| |
| ScheduleOverlay(std::move(dc_layer_params)); |
| ASSERT_EQ(PresentAndGetSwapResult(), gfx::SwapResult::SWAP_ACK); |
| |
| SkColor expected_color = SK_ColorRED; |
| EXPECT_SKCOLOR_CLOSE( |
| expected_color, |
| GLTestHelper::ReadBackWindowPixel(window_.hwnd(), gfx::Point(75, 75)), |
| kMaxColorChannelDeviation); |
| } |
| |
| // Clear to green and present. |
| { |
| ASSERT_HRESULT_SUCCEEDED(ClearRenderTargetViewAndPresent( |
| SkColors::kGreen, swap_chain.Get(), rtv.Get())); |
| |
| auto dc_layer_params = |
| CreateParamsFromImage(DCLayerOverlayImage(swap_chain_size, swap_chain)); |
| dc_layer_params.quad_rect = gfx::Rect(window_size); |
| dc_layer_params.layer_id = gfx::OverlayLayerId::MakeForTesting(0); |
| |
| ScheduleOverlay(std::move(dc_layer_params)); |
| ASSERT_EQ(PresentAndGetSwapResult(), gfx::SwapResult::SWAP_ACK); |
| |
| SkColor expected_color = SK_ColorGREEN; |
| EXPECT_SKCOLOR_CLOSE( |
| expected_color, |
| GLTestHelper::ReadBackWindowPixel(window_.hwnd(), gfx::Point(75, 75)), |
| kMaxColorChannelDeviation); |
| } |
| |
| // Present without clearing. This will flip front and back buffers so the |
| // previous rendered contents (red) will become visible again. |
| { |
| DXGI_PRESENT_PARAMETERS present_params = {}; |
| present_params.DirtyRectsCount = 0; |
| present_params.pDirtyRects = nullptr; |
| ASSERT_HRESULT_SUCCEEDED(swap_chain->Present1(0, 0, &present_params)); |
| |
| auto dc_layer_params = |
| CreateParamsFromImage(DCLayerOverlayImage(swap_chain_size, swap_chain)); |
| dc_layer_params.quad_rect = gfx::Rect(window_size); |
| dc_layer_params.layer_id = gfx::OverlayLayerId::MakeForTesting(0); |
| |
| ScheduleOverlay(std::move(dc_layer_params)); |
| ASSERT_EQ(PresentAndGetSwapResult(), gfx::SwapResult::SWAP_ACK); |
| |
| SkColor expected_color = SK_ColorRED; |
| EXPECT_SKCOLOR_CLOSE( |
| expected_color, |
| GLTestHelper::ReadBackWindowPixel(window_.hwnd(), gfx::Point(75, 75)), |
| kMaxColorChannelDeviation); |
| } |
| |
| // Clear to blue without present. |
| { |
| float clear_color[] = {0.0, 0.0, 1.0, 1.0}; |
| GetImmediateDeviceContext()->ClearRenderTargetView(rtv.Get(), clear_color); |
| |
| auto dc_layer_params = |
| CreateParamsFromImage(DCLayerOverlayImage(swap_chain_size, swap_chain)); |
| dc_layer_params.quad_rect = gfx::Rect(window_size); |
| dc_layer_params.layer_id = gfx::OverlayLayerId::MakeForTesting(0); |
| |
| ScheduleOverlay(std::move(dc_layer_params)); |
| ASSERT_EQ(PresentAndGetSwapResult(), gfx::SwapResult::SWAP_ACK); |
| |
| SkColor expected_color = SK_ColorRED; |
| EXPECT_SKCOLOR_CLOSE( |
| expected_color, |
| GLTestHelper::ReadBackWindowPixel(window_.hwnd(), gfx::Point(75, 75)), |
| kMaxColorChannelDeviation); |
| } |
| } |
| |
| // Test that the overlay quad rect's offset is affected by its transform. |
| TEST_P(DCompPresenterPixelTest, QuadOffsetAppliedAfterTransform) { |
| // Our overlay quad rect is at 0,50 50x50 and scaled down by 1/2. Since we |
| // expect the transform to affect the quad rect offset, we expect the output |
| // rect to be at 0,25 25x25. |
| const gfx::Rect quad_rect(gfx::Point(0, 50), gfx::Size(50, 50)); |
| const gfx::Transform quad_to_root_transform( |
| gfx::AxisTransform2d(0.5, gfx::Vector2dF())); |
| |
| gfx::Size window_size(100, 100); |
| EXPECT_TRUE(presenter_->Resize(window_size, 1.0, gfx::ColorSpace(), true)); |
| |
| InitializeRootAndScheduleRootSurface(window_size, SkColors::kBlack); |
| |
| auto dc_layer_params = CreateParamsFromImage( |
| CreateDCompSurface(quad_rect.size(), SkColors::kRed)); |
| dc_layer_params.quad_rect = quad_rect; |
| dc_layer_params.transform = quad_to_root_transform; |
| dc_layer_params.z_order = 1; |
| dc_layer_params.layer_id = gfx::OverlayLayerId::MakeForTesting(0); |
| |
| ScheduleOverlay(std::move(dc_layer_params)); |
| ASSERT_EQ(PresentAndGetSwapResult(), gfx::SwapResult::SWAP_ACK); |
| |
| // We expect DComp to display the overlay with the same bounds as if viz were |
| // to composite it. |
| const gfx::Rect mapped_quad_rect = quad_to_root_transform.MapRect(quad_rect); |
| |
| SkBitmap pixels = GLTestHelper::ReadBackWindow(window_.hwnd(), window_size); |
| |
| // Check the top edge of the scaled overlay |
| EXPECT_SKCOLOR_CLOSE(SK_ColorBLACK, |
| GLTestHelper::GetColorAtPoint( |
| pixels, gfx::Point(0, mapped_quad_rect.y() - 1)), |
| kMaxColorChannelDeviation); |
| EXPECT_SKCOLOR_CLOSE(SK_ColorRED, |
| GLTestHelper::ReadBackWindowPixel( |
| window_.hwnd(), gfx::Point(0, mapped_quad_rect.y())), |
| kMaxColorChannelDeviation); |
| |
| // Check the bottom edge of the scaled overlay |
| EXPECT_SKCOLOR_CLOSE( |
| SK_ColorRED, |
| GLTestHelper::GetColorAtPoint( |
| pixels, gfx::Point(0, mapped_quad_rect.bottom() - 1)), |
| kMaxColorChannelDeviation); |
| EXPECT_SKCOLOR_CLOSE( |
| |
| SK_ColorBLACK, |
| GLTestHelper::GetColorAtPoint(pixels, |
| gfx::Point(0, mapped_quad_rect.bottom())), |
| kMaxColorChannelDeviation); |
| } |
| |
| // Test that scaling a (very) small texture up works with nearest neighbor |
| // filtering using the content rect and quad rects. |
| TEST_P(DCompPresenterPixelTest, NearestNeighborFilteringScaleViaBuffer) { |
| RunNearestNeighborTest(true); |
| } |
| |
| // Test that scaling a (very) small texture up works with nearest neighbor |
| // filtering using the overlay's transform. |
| TEST_P(DCompPresenterPixelTest, NearestNeighborFilteringScaleViaTransform) { |
| RunNearestNeighborTest(false); |
| } |
| |
| // Test that the |content_rect| of an overlay scales the buffer to fit the |
| // display rect, if needed. |
| TEST_P(DCompPresenterPixelTest, ContentRectScalesUpBuffer) { |
| const gfx::Size window_size(100, 100); |
| const gfx::Rect root_surface_hole = gfx::Rect(5, 10, 50, 75); |
| |
| // Provide an overlay that's smaller than the hole it needs to fill |
| auto overlay = CreateParamsFromImage( |
| CreateDCompSurface(gfx::Size(1, 1), kOverlayExpectedColor)); |
| overlay.quad_rect = root_surface_hole; |
| overlay.z_order = 1; |
| overlay.layer_id = gfx::OverlayLayerId::MakeForTesting(0); |
| CheckOverlayExactlyFillsHole(window_size, root_surface_hole, |
| std::move(overlay)); |
| } |
| |
| // Test that the |content_rect| of an overlay scales the buffer to fit the |
| // display rect, if needed. |
| TEST_P(DCompPresenterPixelTest, ContentRectScalesDownBuffer) { |
| const gfx::Size window_size(100, 100); |
| const gfx::Rect root_surface_hole = gfx::Rect(5, 10, 50, 75); |
| |
| // Provide an overlay that's larger than the hole it needs to fill |
| auto overlay = CreateParamsFromImage( |
| CreateDCompSurface(gfx::Size(75, 100), kOverlayExpectedColor)); |
| overlay.quad_rect = root_surface_hole; |
| overlay.z_order = 1; |
| overlay.layer_id = gfx::OverlayLayerId::MakeForTesting(0); |
| CheckOverlayExactlyFillsHole(window_size, root_surface_hole, |
| std::move(overlay)); |
| } |
| |
| // Test that the |content_rect| of an overlay clips portions of the buffer. |
| TEST_P(DCompPresenterPixelTest, ContentRectClipsBuffer) { |
| const gfx::Size window_size(100, 100); |
| const gfx::Rect tex_coord = gfx::Rect(1, 2, 50, 60); |
| const gfx::Rect root_surface_hole = |
| gfx::Rect(gfx::Point(20, 25), tex_coord.size()); |
| |
| // Ensure the overlay is not scaled. |
| EXPECT_EQ(root_surface_hole.width(), tex_coord.width()); |
| EXPECT_EQ(root_surface_hole.height(), tex_coord.height()); |
| |
| // Provide an overlay that is the right size, but has extra data that is |
| // clipped via content rect |
| auto overlay = CreateParamsFromImage( |
| CreateDCompSurface(window_size, kOverlayImageHiddenColor, |
| {{tex_coord, kOverlayExpectedColor}}), |
| /*content_rect_override=*/gfx::RectF(tex_coord)); |
| overlay.quad_rect = root_surface_hole; |
| overlay.z_order = 1; |
| overlay.layer_id = gfx::OverlayLayerId::MakeForTesting(0); |
| CheckOverlayExactlyFillsHole(window_size, root_surface_hole, |
| std::move(overlay)); |
| } |
| |
| // Test that the |content_rect| of an overlay can clip a buffer and scale it's |
| // contents. |
| TEST_P(DCompPresenterPixelTest, ContentRectClipsAndScalesBuffer) { |
| const gfx::Size window_size(100, 100); |
| const gfx::Rect tex_coord = gfx::Rect(5, 10, 15, 20); |
| const gfx::Rect root_surface_hole = |
| gfx::Rect(gfx::Point(20, 25), gfx::Size(50, 60)); |
| |
| // Ensure the overlay is scaled |
| EXPECT_NE(root_surface_hole.width(), tex_coord.width()); |
| EXPECT_NE(root_surface_hole.height(), tex_coord.height()); |
| |
| // Provide an overlay that needs to be scaled and has extra data that is |
| // clipped via content rect |
| auto overlay = CreateParamsFromImage( |
| CreateDCompSurface(window_size, kOverlayImageHiddenColor, |
| {{tex_coord, kOverlayExpectedColor}}), |
| /*content_rect_override=*/gfx::RectF(tex_coord)); |
| overlay.quad_rect = root_surface_hole; |
| overlay.z_order = 1; |
| overlay.layer_id = gfx::OverlayLayerId::MakeForTesting(0); |
| |
| // Use nearest neighbor to avoid interpolation at the edges of the content |
| // rect |
| overlay.nearest_neighbor_filter = true; |
| |
| CheckOverlayExactlyFillsHole(window_size, root_surface_hole, |
| std::move(overlay)); |
| } |
| |
| // Check that the surface backing solid color overlays is reused across frames. |
| // This can happen e.g. with a solid color draw quad animating its color. |
| TEST_P(DCompPresenterPixelTest, BackgroundColorSurfaceReuse) { |
| const gfx::Size window_size(100, 100); |
| EXPECT_TRUE(presenter_->Resize(window_size, 1.0, gfx::ColorSpace(), true)); |
| |
| SkColor4f colors[] = { |
| SkColors::kRed, SkColors::kGreen, SkColors::kBlue, |
| SkColors::kYellow, SkColors::kCyan, SkColors::kMagenta, |
| }; |
| |
| IDCompositionSurface* background_color_surface = nullptr; |
| |
| for (const SkColor4f& color : colors) { |
| DCLayerOverlayParams params; |
| params.quad_rect = gfx::Rect(window_size); |
| params.background_color = color; |
| params.z_order = 1; |
| params.layer_id = gfx::OverlayLayerId::MakeForTesting(0); |
| ScheduleOverlay(std::move(params)); |
| |
| ASSERT_EQ(PresentAndGetSwapResult(), gfx::SwapResult::SWAP_ACK); |
| |
| EXPECT_SKCOLOR_EQ(color.toSkColor(), GLTestHelper::ReadBackWindowPixel( |
| window_.hwnd(), gfx::Point(0, 0))); |
| |
| const DCLayerTree* layer_tree = presenter_->GetLayerTreeForTesting(); |
| |
| EXPECT_EQ(1u, layer_tree->GetDcompLayerCountForTesting()); |
| EXPECT_EQ(1u, layer_tree->GetNumSurfacesInPoolForTesting()); |
| |
| if (background_color_surface == nullptr) { |
| background_color_surface = |
| layer_tree->GetBackgroundColorSurfaceForTesting( |
| gfx::OverlayLayerId::MakeForTesting(0)); |
| } |
| EXPECT_NE(background_color_surface, nullptr); |
| EXPECT_EQ(background_color_surface, |
| layer_tree->GetBackgroundColorSurfaceForTesting( |
| gfx::OverlayLayerId::MakeForTesting(0))) |
| << "DComp content for solid color overlay expected to be reused across " |
| "frames"; |
| } |
| } |
| |
| struct EarlyFullScreenVideoOptimizationParam { |
| bool use_early_full_screen_video_optimization = false; |
| }; |
| |
| void PrintTo(const EarlyFullScreenVideoOptimizationParam& param, |
| std::ostream* os) { |
| if (param.use_early_full_screen_video_optimization) { |
| *os << "EarlyFullScreenOptOn"; |
| } else { |
| *os << "EarlyFullScreenOptOff"; |
| } |
| } |
| |
| class DCompPresenterFullScreenOptimizationPixelTest |
| : public DCompPresenterPixelTestBase< |
| EarlyFullScreenVideoOptimizationParam> { |
| public: |
| void SetUp() override { |
| static_cast<ui::PlatformWindow*>(&window_)->Show(); |
| EnableFeature(features::kDirectCompositionLetterboxVideoOptimization); |
| if (GetTestParam().use_early_full_screen_video_optimization) { |
| EnableFeature(features::kEarlyFullScreenVideoOptimization); |
| } else { |
| DisableFeature(features::kEarlyFullScreenVideoOptimization); |
| } |
| DCompPresenterPixelTestBase::SetUp(); |
| } |
| |
| void SetMonitorSize(const gfx::Size& monitor_size) { |
| SetDirectCompositionScaledOverlaysSupportedForTesting(true); |
| SetDirectCompositionMonitorInfoForTesting(1, monitor_size); |
| EXPECT_TRUE( |
| this->presenter_->Resize(monitor_size, 1.0, gfx::ColorSpace(), true)); |
| static_cast<ui::PlatformWindow*>(&window_)->SetBoundsInPixels( |
| gfx::Rect(monitor_size)); |
| } |
| |
| void DoOneFrame(const gfx::Size& monitor_size, |
| Microsoft::WRL::ComPtr<ID3D11Texture2D> texture, |
| bool is_full_screen) { |
| ASSERT_NE(texture, nullptr); |
| |
| // Use red for full screen because we expect the root surface to be fully |
| // occluded by the "full screen"-ed swap chain. We can use the presence of |
| // red as an indication that the full screening failed. |
| // |
| // We don't expect the video to be either of these background colors. |
| const SkColor4f expected_background_color = SkColors::kBlack; |
| const SkColor4f unexpected_background_color = SkColors::kRed; |
| const SkColor4f root_surface_color = is_full_screen |
| ? unexpected_background_color |
| : expected_background_color; |
| InitializeRootAndScheduleRootSurface(monitor_size, root_surface_color); |
| |
| D3D11_TEXTURE2D_DESC desc; |
| texture->GetDesc(&desc); |
| const gfx::Size texture_size = gfx::Size(desc.Width, desc.Height); |
| auto params = |
| CreateParamsFromImage(DCLayerOverlayImage(texture_size, texture)); |
| params.layer_id = gfx::OverlayLayerId::MakeForTesting(0); |
| if (is_full_screen) { |
| const float scale_factor = |
| std::min(static_cast<float>(monitor_size.width()) / |
| static_cast<float>(texture_size.width()), |
| static_cast<float>(monitor_size.height()) / |
| static_cast<float>(texture_size.height())); |
| params.quad_rect = |
| gfx::ScaleToEnclosingRect(gfx::Rect(texture_size), scale_factor); |
| params.transform = gfx::Transform::MakeTranslation( |
| (monitor_size.width() - params.quad_rect.width()) / 2.0, |
| (monitor_size.height() - params.quad_rect.height()) / 2.0); |
| if (GetTestParam().use_early_full_screen_video_optimization) { |
| params.video_params.is_full_screen_video = true; |
| } else { |
| params.video_params.possible_video_fullscreen_letterboxing = true; |
| } |
| } else { |
| // An arbitrary rect that is not the full screen. |
| params.quad_rect = gfx::Rect(gfx::ScaleToCeiledSize(monitor_size, 0.5)); |
| } |
| const gfx::Rect expected_target_rect = |
| params.transform.MapRect(params.quad_rect); |
| params.clip_rect = expected_target_rect; |
| params.video_params.color_space = gfx::ColorSpace::CreateREC709(); |
| ScheduleOverlay(std::move(params)); |
| |
| ASSERT_EQ(this->PresentAndGetSwapResult(), gfx::SwapResult::SWAP_ACK); |
| |
| const SkBitmap actual_pixels = |
| GLTestHelper::ReadBackWindow(window_.hwnd(), monitor_size); |
| gfx::Rect actual_video_bounds; |
| bool already_asserted_wrong_background_color = false; |
| for (int y = 0; y < actual_pixels.height(); y++) { |
| for (int x = 0; x < actual_pixels.width(); x++) { |
| const SkColor4f actual_color = actual_pixels.getColor4f(x, y); |
| if (actual_color == expected_background_color) { |
| // If the color matches the background mat color, no action. |
| } else if (actual_color == unexpected_background_color) { |
| // If `actual_color` is `root_surface_color`, then we are missing |
| // the video background mat. |
| if (!already_asserted_wrong_background_color) { |
| EXPECT_SKCOLOR_EQ(actual_color.toSkColor(), |
| expected_background_color.toSkColor()) |
| << "background mat is the wrong color!"; |
| // Avoid spamming the log output for every pixel. |
| already_asserted_wrong_background_color = true; |
| } |
| } else { |
| // Any other color will be conservatively called "part of the video". |
| actual_video_bounds.Union(gfx::Rect(x, y, 1, 1)); |
| } |
| } |
| } |
| |
| // Check that the video appears in the center of the window. If something |
| // went wrong, the video may be stretched across the whole window or may |
| // appear un-centered at the top left. |
| EXPECT_EQ(actual_video_bounds, expected_target_rect); |
| } |
| }; |
| |
| // Check that the first frame of a full screen optimized swap chain appears in |
| // the correct place. |
| TEST_P(DCompPresenterFullScreenOptimizationPixelTest, FirstFrame) { |
| // Test a fraction of a 1920x1200 monitor to speed up the pixel readback. |
| const float scale_factor = 0.25; |
| const gfx::Size monitor_size(1920 * scale_factor, 1200 * scale_factor); |
| const gfx::Size texture_size(2, 2); |
| |
| SetMonitorSize(monitor_size); |
| |
| auto texture = |
| CreateNV12Texture(GetDirectCompositionD3D11Device(), texture_size); |
| |
| DoOneFrame(monitor_size, texture, /*is_full_screen=*/true); |
| } |
| |
| // Check that two contiguous frames that share a `SwapChainPresenter` but do not |
| // update the video texture will maintain the full screen optimization. |
| TEST_P(DCompPresenterFullScreenOptimizationPixelTest, KeepFullScreenSameImage) { |
| // Test a fraction of a 1920x1200 monitor to speed up the pixel readback. |
| const float scale_factor = 0.25; |
| const gfx::Size monitor_size(1920 * scale_factor, 1200 * scale_factor); |
| const gfx::Size texture_size(2, 2); |
| |
| SetMonitorSize(monitor_size); |
| |
| auto texture = |
| CreateNV12Texture(GetDirectCompositionD3D11Device(), texture_size); |
| |
| DoOneFrame(monitor_size, texture, /*is_full_screen=*/true); |
| DoOneFrame(monitor_size, texture, /*is_full_screen=*/true); |
| } |
| |
| // Check that two contiguous frames that share a `SwapChainPresenter` that |
| // changes the video texture will maintain the full screen optimization. |
| TEST_P(DCompPresenterFullScreenOptimizationPixelTest, |
| KeepFullScreenDifferentImage) { |
| // Test a fraction of a 1920x1200 monitor to speed up the pixel readback. |
| const float scale_factor = 0.25; |
| const gfx::Size monitor_size(1920 * scale_factor, 1200 * scale_factor); |
| const gfx::Size texture_size(2, 2); |
| |
| SetMonitorSize(monitor_size); |
| |
| DoOneFrame(monitor_size, |
| CreateNV12Texture(GetDirectCompositionD3D11Device(), texture_size), |
| /*is_full_screen=*/true); |
| |
| DoOneFrame(monitor_size, |
| CreateNV12Texture(GetDirectCompositionD3D11Device(), texture_size), |
| /*is_full_screen=*/true); |
| } |
| |
| // Check entering full screen mode succeeds when we do not change the video |
| // texture. |
| TEST_P(DCompPresenterFullScreenOptimizationPixelTest, |
| EnterFullScreenSameImage) { |
| // Test a fraction of a 1920x1200 monitor to speed up the pixel readback. |
| const float scale_factor = 0.25; |
| const gfx::Size monitor_size(1920 * scale_factor, 1200 * scale_factor); |
| const gfx::Size texture_size(2, 2); |
| |
| SetMonitorSize(monitor_size); |
| |
| auto texture = |
| CreateNV12Texture(GetDirectCompositionD3D11Device(), texture_size); |
| |
| DoOneFrame(monitor_size, texture, /*is_full_screen=*/false); |
| DoOneFrame(monitor_size, texture, /*is_full_screen=*/true); |
| } |
| |
| // Check exiting full screen mode succeeds when we do not change the video |
| // texture. |
| TEST_P(DCompPresenterFullScreenOptimizationPixelTest, ExitFullScreenSameImage) { |
| // Test a fraction of a 1920x1200 monitor to speed up the pixel readback. |
| const float scale_factor = 0.25; |
| const gfx::Size monitor_size(1920 * scale_factor, 1200 * scale_factor); |
| const gfx::Size texture_size(2, 2); |
| |
| SetMonitorSize(monitor_size); |
| |
| auto texture = |
| CreateNV12Texture(GetDirectCompositionD3D11Device(), texture_size); |
| |
| DoOneFrame(monitor_size, texture, /*is_full_screen=*/true); |
| DoOneFrame(monitor_size, texture, /*is_full_screen=*/false); |
| } |
| |
| INSTANTIATE_TEST_SUITE_P( |
| , |
| DCompPresenterFullScreenOptimizationPixelTest, |
| DCompPresenterFullScreenOptimizationPixelTest::GetValues( |
| testing::ConvertGenerator(testing::Bool())), |
| &DCompPresenterFullScreenOptimizationPixelTest::GetParamName); |
| |
| class DCompPresenterSkiaGoldTest : public DCompPresenterPixelTest { |
| protected: |
| void SetUp() override { |
| DCompPresenterPixelTest::SetUp(); |
| ASSERT_TRUE(context_); |
| const ui::test::TestEnvironmentMap test_environment = { |
| {ui::test::TestEnvironmentKey::kSystemVersion, |
| base::win::OSInfo::GetInstance()->release_id()}, |
| {ui::test::TestEnvironmentKey::kGpuDriverVendor, |
| context_->GetVersionInfo()->driver_vendor}, |
| {ui::test::TestEnvironmentKey::kGpuDriverVersion, |
| context_->GetVersionInfo()->driver_version}, |
| {ui::test::TestEnvironmentKey::kGlRenderer, context_->GetGLRenderer()}, |
| |
| }; |
| |
| pixel_diff_ = ui::test::SkiaGoldPixelDiff::GetSession( |
| kSkiaGoldPixelDiffCorpus, test_environment); |
| } |
| |
| void TearDown() override { |
| DCompPresenterPixelTest::TearDown(); |
| test_initialized_ = false; |
| } |
| |
| void InitializeTest(const gfx::Size& window_size) { |
| ASSERT_FALSE(test_initialized_) |
| << "InitializeTest should only be called once per test"; |
| test_initialized_ = true; |
| |
| ResizeWindow(window_size); |
| |
| capture_names_in_test_.clear(); |
| } |
| |
| // An offset to move the test output off the top-left edges so that we don't |
| // need to dilate the edges of |SobelSkiaGoldMatchingAlgorithm|. |
| static const int kPaddingFromEdgeForAntiAliasedOutput = 5; |
| |
| void ResizeWindow(const gfx::Size& window_size) { |
| EXPECT_TRUE(presenter_->Resize(window_size, 1.0, gfx::ColorSpace(), true)); |
| window_size_ = window_size; |
| } |
| |
| // Strips out the parameterization parts of the test case. This is to ensure |
| // that the golden test names match. |
| std::string GetGoldenTestName(const std::string& test_name) { |
| std::string golden_test_name = test_name; |
| |
| // Find the position of the first '/' |
| size_t first_slash_pos = golden_test_name.find('/'); |
| |
| // If '/' exists, remove everything before it |
| if (first_slash_pos != std::string::npos) { |
| golden_test_name = golden_test_name.substr(first_slash_pos + 1); |
| } |
| |
| return golden_test_name; |
| } |
| |
| // Strips out the parameterization parts of the test case. This is to ensure |
| // that the golden test case names match. |
| std::string GetGoldenTestCaseName(const std::string& test_case_name) { |
| std::string golden_test_case_name = test_case_name; |
| |
| // Find the position of the last '/' |
| size_t last_slash_pos = golden_test_case_name.find_last_of('/'); |
| |
| // If '/' exists, remove everything after it |
| if (last_slash_pos != std::string::npos) { |
| golden_test_case_name = golden_test_case_name.substr(0, last_slash_pos); |
| } |
| |
| return golden_test_case_name; |
| } |
| |
| // |capture_name| identifies this screenshot and is appended to the skia gold |
| // remote test name. Empty string is allowed, e.g. for tests that only have |
| // one screenshot. |
| // Tests should consider passing meaningful capture names if it helps make |
| // them easier to understand and debug. |
| // Unique capture names are required if a test checks multiple screenshots. |
| void PresentAndCheckScreenshot( |
| std::string capture_name = std::string(), |
| const base::Location& caller_location = FROM_HERE) { |
| ASSERT_TRUE(test_initialized_) << "Must call InitializeTest first"; |
| |
| if (capture_names_in_test_.contains(capture_name)) { |
| ADD_FAILURE_AT(caller_location.file_name(), caller_location.line_number()) |
| << "Capture names must be unique in a test. Capture name \"" |
| << capture_name << "\" is already used."; |
| return; |
| } |
| capture_names_in_test_.insert(capture_name); |
| |
| ASSERT_EQ(PresentAndGetSwapResult(), gfx::SwapResult::SWAP_ACK); |
| |
| SkBitmap window_readback = |
| GLTestHelper::ReadBackWindow(window_.hwnd(), window_size_); |
| CHECK(pixel_diff_); |
| |
| std::string test_name = GetGoldenTestName(::testing::UnitTest::GetInstance() |
| ->current_test_info() |
| ->test_suite_name()); |
| std::string test_case_name = GetGoldenTestCaseName( |
| ::testing::UnitTest::GetInstance()->current_test_info()->name()); |
| |
| if (!pixel_diff_->CompareScreenshot( |
| ui::test::SkiaGoldPixelDiff::GetGoldenImageName( |
| test_name, test_case_name, |
| capture_name.empty() ? std::nullopt |
| : std::make_optional(capture_name)), |
| window_readback, matching_algorithm_.get())) { |
| ADD_FAILURE_AT(caller_location.file_name(), caller_location.line_number()) |
| << "Screenshot mismatch for " |
| << (capture_name.empty() ? "(unnamed capture)" : capture_name); |
| } |
| } |
| |
| const gfx::Size& current_window_size() const { return window_size_; } |
| |
| void AddOverlaysForOpacityTest( |
| base::RepeatingCallback<DCLayerOverlayParams(const gfx::Rect&, float)> |
| get_overlay_for_opacity) { |
| const int kOverlayCount = 10; |
| for (int i = 0; i < kOverlayCount; i++) { |
| const int width = current_window_size().width() / kOverlayCount; |
| const gfx::Rect quad_rect = |
| gfx::Rect(i * width, 0, width, current_window_size().height()); |
| const float opacity = |
| static_cast<float>(i) / static_cast<float>(kOverlayCount); |
| |
| auto overlay = get_overlay_for_opacity.Run(quad_rect, opacity); |
| overlay.z_order = i + 1; |
| overlay.layer_id = gfx::OverlayLayerId::MakeForTesting(i); |
| |
| ScheduleOverlay(std::move(overlay)); |
| } |
| } |
| |
| private: |
| raw_ptr<ui::test::SkiaGoldPixelDiff> pixel_diff_ = nullptr; |
| |
| // The matching algorithm for goldctl to use. |
| std::unique_ptr<ui::test::SkiaGoldMatchingAlgorithm> matching_algorithm_; |
| |
| // |true|, if |InitializeTest| has been called. |
| bool test_initialized_ = false; |
| |
| // The size of the window and screenshots, in pixels. |
| gfx::Size window_size_; |
| |
| // The values of the |capture_name| parameter of |PresentAndCheckScreenshot| |
| // seen in the test so far. |
| base::flat_set<std::string> capture_names_in_test_; |
| }; |
| |
| INSTANTIATE_TEST_SUITE_P(, |
| DCompPresenterSkiaGoldTest, |
| DCompPresenterSkiaGoldTest::GetValues(), |
| &DCompPresenterSkiaGoldTest::GetParamName); |
| |
| // Check that a translation transform works. |
| TEST_P(DCompPresenterSkiaGoldTest, TransformTranslate) { |
| InitializeTest(gfx::Size(100, 100)); |
| |
| InitializeRootAndScheduleRootSurface(current_window_size(), SkColors::kBlack); |
| |
| auto overlay = CreateParamsFromImage( |
| CreateDCompSurface(gfx::Size(50, 50), SkColors::kWhite)); |
| overlay.quad_rect = gfx::Rect(50, 50); |
| overlay.z_order = 1; |
| overlay.layer_id = gfx::OverlayLayerId::MakeForTesting(0); |
| |
| overlay.transform.Translate(25, 25); |
| |
| ScheduleOverlay(std::move(overlay)); |
| |
| PresentAndCheckScreenshot(); |
| } |
| |
| // Check that a scaling transform works. |
| TEST_P(DCompPresenterSkiaGoldTest, TransformScale) { |
| InitializeTest(gfx::Size(100, 100)); |
| |
| InitializeRootAndScheduleRootSurface(current_window_size(), SkColors::kBlack); |
| |
| auto overlay = CreateParamsFromImage( |
| CreateDCompSurface(gfx::Size(50, 50), SkColors::kWhite)); |
| overlay.quad_rect = gfx::Rect(50, 50); |
| overlay.z_order = 1; |
| overlay.layer_id = gfx::OverlayLayerId::MakeForTesting(0); |
| |
| overlay.transform.Translate(50, 50); |
| overlay.transform.Scale(1.2); |
| overlay.transform.Translate(-25, -25); |
| |
| ScheduleOverlay(std::move(overlay)); |
| |
| PresentAndCheckScreenshot(); |
| } |
| |
| // Check that a rotation transform works. |
| TEST_P(DCompPresenterSkiaGoldTest, TransformRotation) { |
| InitializeTest(gfx::Size(100, 100)); |
| |
| InitializeRootAndScheduleRootSurface(current_window_size(), SkColors::kBlack); |
| |
| auto overlay = CreateParamsFromImage( |
| CreateDCompSurface(gfx::Size(50, 50), SkColors::kWhite)); |
| overlay.quad_rect = gfx::Rect(50, 50); |
| overlay.z_order = 1; |
| overlay.layer_id = gfx::OverlayLayerId::MakeForTesting(0); |
| |
| // Center and partially rotate the overlay |
| overlay.transform.Translate(50, 50); |
| overlay.transform.Rotate(15); |
| overlay.transform.Translate(-25, -25); |
| |
| ScheduleOverlay(std::move(overlay)); |
| |
| PresentAndCheckScreenshot(); |
| } |
| |
| // Check that a complex transform (i.e. non-flat) works. |
| TEST_P(DCompPresenterSkiaGoldTest, Transform3D) { |
| InitializeTest(gfx::Size(100, 100)); |
| |
| InitializeRootAndScheduleRootSurface(current_window_size(), SkColors::kBlack); |
| |
| DCLayerOverlayParams overlay; |
| |
| overlay.quad_rect = gfx::Rect(120, 75); |
| |
| overlay.background_color = SkColors::kGreen; |
| |
| overlay.z_order = 1; |
| overlay.layer_id = gfx::OverlayLayerId::MakeForTesting(0); |
| |
| overlay.transform.Translate(50, 50); |
| overlay.transform.ApplyPerspectiveDepth(100); |
| overlay.transform.RotateAboutYAxis(45); |
| overlay.transform.RotateAboutXAxis(30); |
| overlay.transform.Translate(-25, -25); |
| |
| ScheduleOverlay(std::move(overlay)); |
| |
| PresentAndCheckScreenshot(); |
| } |
| |
| // This kind of transform is uncommon, but should be supported when rotations |
| // are supported. |
| TEST_P(DCompPresenterSkiaGoldTest, TransformShear) { |
| InitializeTest(gfx::Size(100, 100)); |
| |
| InitializeRootAndScheduleRootSurface(current_window_size(), SkColors::kBlack); |
| |
| auto overlay = CreateParamsFromImage( |
| CreateDCompSurface(gfx::Size(50, 50), SkColors::kWhite)); |
| overlay.quad_rect = gfx::Rect(50, 50); |
| overlay.z_order = 1; |
| overlay.layer_id = gfx::OverlayLayerId::MakeForTesting(0); |
| overlay.transform.Translate(50, 50); |
| overlay.transform.Skew(15, 30); |
| overlay.transform.Translate(-25, -25); |
| ScheduleOverlay(std::move(overlay)); |
| |
| PresentAndCheckScreenshot(); |
| } |
| |
| // Test that solid color overlays completely fill their display rect. |
| TEST_P(DCompPresenterSkiaGoldTest, SolidColorSimpleOpaque) { |
| InitializeTest(gfx::Size(100, 100)); |
| |
| const SkColor4f root_surface_color = SkColors::kBlack; |
| |
| InitializeRootAndScheduleRootSurface(current_window_size(), |
| root_surface_color); |
| |
| const std::vector<std::pair<SkColor4f, gfx::Rect>> colors = { |
| {SkColors::kRed, gfx::Rect(5, 10, 15, 20)}, |
| {SkColors::kGreen, gfx::Rect(15, 12, 15, 20)}, |
| {SkColors::kBlue, gfx::Rect(25, 14, 15, 20)}, |
| {SkColors::kWhite, gfx::Rect(35, 16, 15, 20)}, |
| }; |
| |
| for (size_t i = 0; i < colors.size(); i++) { |
| auto& [color, bounds] = colors[i]; |
| DCLayerOverlayParams overlay; |
| overlay.quad_rect = bounds; |
| overlay.background_color = std::optional<SkColor4f>(color); |
| overlay.z_order = i + 1; |
| overlay.layer_id = gfx::OverlayLayerId::MakeForTesting(i); |
| ScheduleOverlay(std::move(overlay)); |
| } |
| |
| PresentAndCheckScreenshot(); |
| } |
| |
| // Test that opacity works when originating from DComp tree parameter. |
| TEST_P(DCompPresenterSkiaGoldTest, OpacityFromOverlay) { |
| InitializeTest(gfx::Size(100, 100)); |
| |
| InitializeRootAndScheduleRootSurface(current_window_size(), SkColors::kBlack); |
| |
| AddOverlaysForOpacityTest( |
| base::BindRepeating([](const gfx::Rect& quad_rect, float opacity) { |
| auto overlay = CreateParamsFromImage( |
| CreateDCompSurface(quad_rect.size(), SkColors::kWhite)); |
| overlay.quad_rect = quad_rect; |
| overlay.opacity = opacity; |
| return overlay; |
| })); |
| |
| PresentAndCheckScreenshot(); |
| } |
| |
| // Test that opacity works when originating from the overlay image itself. |
| TEST_P(DCompPresenterSkiaGoldTest, OpacityFromImage) { |
| InitializeTest(gfx::Size(100, 100)); |
| |
| InitializeRootAndScheduleRootSurface(current_window_size(), SkColors::kBlack); |
| |
| AddOverlaysForOpacityTest( |
| base::BindRepeating([](const gfx::Rect& quad_rect, float opacity) { |
| SkColor4f overlay_color = SkColors::kWhite; |
| overlay_color.fA = opacity; |
| |
| auto overlay = CreateParamsFromImage( |
| CreateDCompSurface(quad_rect.size(), overlay_color)); |
| overlay.quad_rect = quad_rect; |
| return overlay; |
| })); |
| |
| PresentAndCheckScreenshot(); |
| } |
| |
| // Test that opacity works when originating from a solid color overlay. |
| TEST_P(DCompPresenterSkiaGoldTest, OpacityFromSolidColor) { |
| InitializeTest(gfx::Size(100, 100)); |
| |
| InitializeRootAndScheduleRootSurface(current_window_size(), SkColors::kBlack); |
| |
| AddOverlaysForOpacityTest( |
| base::BindRepeating([](const gfx::Rect& quad_rect, float opacity) { |
| SkColor4f overlay_color = SkColors::kWhite; |
| overlay_color.fA = opacity; |
| |
| DCLayerOverlayParams overlay; |
| overlay.quad_rect = quad_rect; |
| overlay.background_color = std::optional<SkColor4f>(overlay_color); |
| return overlay; |
| })); |
| |
| PresentAndCheckScreenshot(); |
| } |
| |
| // Check that an overlay with a DComp surface will visually reflect draws to the |
| // surface if its dcomp_surface_serial changes. This requires DCLayerTree to |
| // call Commit, even if no other tree properties change. |
| TEST_P(DCompPresenterSkiaGoldTest, SurfaceSerialForcesCommit) { |
| InitializeTest(gfx::Size(100, 100)); |
| |
| const std::vector<SkColor4f> colors = {SkColors::kRed, SkColors::kGreen, |
| SkColors::kBlue, SkColors::kWhite}; |
| |
| Microsoft::WRL::ComPtr<IDCompositionDevice2> dcomp_device = |
| GetDirectCompositionDevice(); |
| |
| Microsoft::WRL::ComPtr<IDCompositionSurface> surface; |
| ASSERT_HRESULT_SUCCEEDED(dcomp_device->CreateSurface( |
| current_window_size().width(), current_window_size().height(), |
| DXGI_FORMAT_B8G8R8A8_UNORM, DXGI_ALPHA_MODE_IGNORE, &surface)); |
| uint64_t surface_serial = 0; |
| |
| ClearRect(surface.Get(), gfx::Rect(current_window_size()), SkColors::kBlack); |
| |
| for (size_t i = 0; i < colors.size(); i++) { |
| const auto color = colors[i]; |
| |
| ClearRect(surface.Get(), gfx::Rect(i * 10, i * 5, 15, 15), color); |
| surface_serial++; |
| |
| auto overlay = CreateParamsFromImage( |
| DCLayerOverlayImage(current_window_size(), surface, surface_serial)); |
| overlay.quad_rect = gfx::Rect(current_window_size()); |
| overlay.z_order = 0; |
| overlay.layer_id = gfx::OverlayLayerId::MakeForTesting(0); |
| ScheduleOverlay(std::move(overlay)); |
| |
| PresentAndCheckScreenshot(base::NumberToString(i)); |
| } |
| } |
| |
| // Check that we support simple rounded corners. |
| TEST_P(DCompPresenterSkiaGoldTest, RoundedCornerSimple) { |
| InitializeTest(gfx::Size(100, 100)); |
| |
| InitializeRootAndScheduleRootSurface(current_window_size(), SkColors::kBlack); |
| |
| auto overlay = CreateParamsFromImage( |
| CreateDCompSurface(current_window_size(), SkColors::kWhite)); |
| overlay.quad_rect = gfx::Rect(current_window_size()); |
| overlay.quad_rect.Inset(kPaddingFromEdgeForAntiAliasedOutput); |
| overlay.z_order = 1; |
| overlay.layer_id = gfx::OverlayLayerId::MakeForTesting(0); |
| overlay.rounded_corner_bounds = |
| gfx::RRectF(gfx::RectF(overlay.quad_rect), 25.f); |
| ScheduleOverlay(std::move(overlay)); |
| |
| PresentAndCheckScreenshot(); |
| } |
| |
| // Check that we support rounded corners with complex radii. |
| TEST_P(DCompPresenterSkiaGoldTest, RoundedCornerNonUniformRadii) { |
| InitializeTest(gfx::Size(100, 100)); |
| |
| InitializeRootAndScheduleRootSurface(current_window_size(), SkColors::kBlack); |
| |
| auto overlay = CreateParamsFromImage( |
| CreateDCompSurface(current_window_size(), SkColors::kWhite)); |
| overlay.quad_rect = gfx::Rect(current_window_size()); |
| overlay.quad_rect.Inset(kPaddingFromEdgeForAntiAliasedOutput); |
| overlay.z_order = 1; |
| overlay.layer_id = gfx::OverlayLayerId::MakeForTesting(0); |
| |
| gfx::RRectF bounds = gfx::RRectF(gfx::RectF(overlay.quad_rect)); |
| bounds.SetCornerRadii(gfx::RRectF::Corner::kUpperLeft, gfx::Vector2dF(5, 40)); |
| bounds.SetCornerRadii(gfx::RRectF::Corner::kUpperRight, |
| gfx::Vector2dF(15, 30)); |
| bounds.SetCornerRadii(gfx::RRectF::Corner::kLowerRight, |
| gfx::Vector2dF(25, 20)); |
| bounds.SetCornerRadii(gfx::RRectF::Corner::kLowerLeft, |
| gfx::Vector2dF(35, 10)); |
| overlay.rounded_corner_bounds = bounds; |
| ScheduleOverlay(std::move(overlay)); |
| |
| PresentAndCheckScreenshot(); |
| } |
| |
| // Check that there are no seams between solid color quads when there is a |
| // rounded corner clip present. Seams can appear since the solid color visual |
| // uses a shared image that is scaled to fit the overlay. The combination of |
| // scaling and soft borders implied by rounded corners can cause seams. |
| // This is a common case in e.g. the omnibox. |
| TEST_P(DCompPresenterSkiaGoldTest, |
| NoSeamsBetweenAdjacentSolidColorsWithSharedRoundedCorner) { |
| // We specifically don't want to ignore anti-aliasing in this test |
| InitializeTest(gfx::Size(100, 100)); |
| |
| InitializeRootAndScheduleRootSurface(current_window_size(), SkColors::kBlack); |
| |
| gfx::RRectF bounds = gfx::RRectF(gfx::RectF(current_window_size()), 0); |
| // Give the rounded rect a radius, but ensure that it is not visible so AA |
| // doesn't affect this test. |
| bounds.Outset(5); |
| |
| std::vector<gfx::Rect> quads = { |
| gfx::Rect(55, 45, 45, 55), |
| gfx::Rect(0, 45, 55, 55), |
| |
| gfx::Rect(45, 0, 55, 45), |
| gfx::Rect(0, 0, 45, 45), |
| }; |
| |
| int overlay_z_order = 1; |
| for (auto& quad : quads) { |
| DCLayerOverlayParams overlay; |
| overlay.quad_rect = quad; |
| overlay.background_color = std::optional<SkColor4f>(SkColors::kWhite); |
| overlay.z_order = overlay_z_order; |
| overlay.layer_id = gfx::OverlayLayerId::MakeForTesting(overlay_z_order); |
| overlay.rounded_corner_bounds = bounds; |
| ScheduleOverlay(std::move(overlay)); |
| |
| overlay_z_order++; |
| } |
| |
| PresentAndCheckScreenshot(); |
| } |
| |
| // Check that we get a soft border when we translate the overlay so that both |
| // the right and left edges cover half a pixel. |
| TEST_P(DCompPresenterSkiaGoldTest, SoftBordersFromNonIntegralTranslation) { |
| InitializeTest(gfx::Size(100, 100)); |
| |
| InitializeRootAndScheduleRootSurface(current_window_size(), SkColors::kBlack); |
| |
| auto overlay = CreateParamsFromImage( |
| CreateDCompSurface(gfx::Size(20, 20), SkColors::kWhite)); |
| overlay.quad_rect = gfx::Rect(overlay.overlay_image->size()); |
| overlay.transform.Translate(kPaddingFromEdgeForAntiAliasedOutput, |
| kPaddingFromEdgeForAntiAliasedOutput); |
| overlay.transform.Translate(0.5, 0); |
| overlay.z_order = 1; |
| overlay.layer_id = gfx::OverlayLayerId::MakeForTesting(0); |
| ScheduleOverlay(std::move(overlay)); |
| |
| PresentAndCheckScreenshot(); |
| } |
| |
| // Check that we get a soft border when we scale the overlay so the right edge |
| // covers half a pixel. |
| TEST_P(DCompPresenterSkiaGoldTest, SoftBordersFromNonIntegralScaling) { |
| InitializeTest(gfx::Size(100, 100)); |
| |
| InitializeRootAndScheduleRootSurface(current_window_size(), SkColors::kBlack); |
| |
| auto overlay = CreateParamsFromImage( |
| CreateDCompSurface(gfx::Size(20, 20), SkColors::kWhite)); |
| overlay.quad_rect = gfx::Rect(overlay.overlay_image->size()); |
| overlay.transform.Translate(kPaddingFromEdgeForAntiAliasedOutput, |
| kPaddingFromEdgeForAntiAliasedOutput); |
| overlay.transform.Scale( |
| (static_cast<float>(overlay.quad_rect.width()) + 0.5) / |
| static_cast<float>(overlay.quad_rect.width()), |
| 1); |
| overlay.z_order = 1; |
| overlay.layer_id = gfx::OverlayLayerId::MakeForTesting(0); |
| ScheduleOverlay(std::move(overlay)); |
| |
| PresentAndCheckScreenshot(); |
| } |
| |
| // Check that we get a soft border when we create a non-integral rounded corner |
| // bounds so the right edge covers half a pixel. |
| TEST_P(DCompPresenterSkiaGoldTest, |
| SoftBordersFromNonIntegralRoundedCornerBounds) { |
| InitializeTest(gfx::Size(100, 100)); |
| |
| InitializeRootAndScheduleRootSurface(current_window_size(), SkColors::kBlack); |
| |
| auto overlay = CreateParamsFromImage( |
| CreateDCompSurface(gfx::Size(21, 20), SkColors::kWhite)); |
| overlay.quad_rect = gfx::Rect(overlay.overlay_image->size()); |
| |
| // DComp seems to not actually use soft borders unless there's a non-zero |
| // radius. |
| const double kForceDCompRoundedCornerSoftBorder = |
| std::numeric_limits<float>::epsilon(); |
| |
| overlay.rounded_corner_bounds = gfx::RRectF( |
| gfx::RectF(0, 0, 20.5, 20), kForceDCompRoundedCornerSoftBorder); |
| overlay.rounded_corner_bounds.Offset(kPaddingFromEdgeForAntiAliasedOutput, |
| kPaddingFromEdgeForAntiAliasedOutput); |
| overlay.transform.Translate(kPaddingFromEdgeForAntiAliasedOutput, |
| kPaddingFromEdgeForAntiAliasedOutput); |
| overlay.z_order = 1; |
| overlay.layer_id = gfx::OverlayLayerId::MakeForTesting(0); |
| ScheduleOverlay(std::move(overlay)); |
| |
| PresentAndCheckScreenshot(); |
| } |
| |
| // Check that DCLayerTree sorts overlays by their z-order instead of using the |
| // schedule order. |
| TEST_P(DCompPresenterSkiaGoldTest, OverlaysAreSortedByZOrder) { |
| InitializeTest(gfx::Size(100, 100)); |
| |
| // Insert overlays out of order with respect to z-ordering |
| std::vector<std::pair<SkColor4f, int>> color_and_z_order = { |
| {SkColors::kGreen, 2}, |
| {SkColors::kGreen, -1}, |
| {SkColors::kRed, -2}, |
| {SkColors::kRed, 1}, |
| }; |
| |
| for (const auto& [color, z_order] : color_and_z_order) { |
| gfx::Rect quad_rect = gfx::Rect(15 + z_order * 5, 15 + z_order * 5, 30, 30); |
| auto overlay = |
| CreateParamsFromImage(CreateDCompSurface(quad_rect.size(), color)); |
| overlay.quad_rect = quad_rect; |
| overlay.z_order = z_order; |
| overlay.layer_id = gfx::OverlayLayerId::MakeForTesting(z_order); |
| |
| ScheduleOverlay(std::move(overlay)); |
| } |
| |
| // Insert a translucent root plane so that we can easily see underlays |
| SkColor4f translucent_blue = SkColors::kBlue; |
| translucent_blue.fA = 0.5; |
| InitializeRootAndScheduleRootSurface(current_window_size(), translucent_blue); |
| |
| { |
| // Insert a black backdrop since our root surface is not opaque. This is not |
| // strictly required, but it ensures that we explicitly make all pixels in |
| // our output opaque. |
| auto overlay = CreateParamsFromImage( |
| CreateDCompSurface(current_window_size(), SkColors::kBlack)); |
| overlay.quad_rect = gfx::Rect(current_window_size()); |
| overlay.z_order = INT_MIN; |
| overlay.layer_id = gfx::OverlayLayerId::MakeForTesting(0); |
| ScheduleOverlay(std::move(overlay)); |
| } |
| |
| PresentAndCheckScreenshot(); |
| } |
| |
| // Check that an overlay with a non-opaque image can show a background color. |
| TEST_P(DCompPresenterSkiaGoldTest, ImageWithBackgroundColor) { |
| InitializeTest(gfx::Size(100, 100)); |
| |
| InitializeRootAndScheduleRootSurface(current_window_size(), SkColors::kBlack); |
| |
| auto overlay = CreateParamsFromImage(CreateDCompSurface( |
| gfx::Size(100, 50), SkColors::kTransparent, |
| { |
| {gfx::Rect(5, 5, 20, 20), |
| SkColor4f::FromColor(SkColorSetA(SK_ColorRED, 0x80))}, |
| {gfx::Rect(15, 15, 20, 20), |
| SkColor4f::FromColor(SkColorSetA(SK_ColorBLUE, 0x80))}, |
| })); |
| overlay.quad_rect = gfx::Rect(100, 50); |
| overlay.background_color = SkColors::kGreen; |
| overlay.z_order = 1; |
| overlay.layer_id = gfx::OverlayLayerId::MakeForTesting(0); |
| |
| ScheduleOverlay(std::move(overlay)); |
| |
| PresentAndCheckScreenshot(); |
| } |
| |
| // Test that we support sampling from overlay images with non-integral content |
| // rects. This test should output a blue square with a faint green outline. |
| TEST_P(DCompPresenterSkiaGoldTest, NonIntegralContentRectHalfCoverage) { |
| InitializeTest(gfx::Size(100, 100)); |
| |
| InitializeRootAndScheduleRootSurface(current_window_size(), SkColors::kBlack); |
| |
| gfx::Size image_size = gfx::Size(50, 50); |
| gfx::Rect image_inner_rect = gfx::Rect(image_size); |
| image_inner_rect.Inset(1); |
| auto overlay = CreateParamsFromImage(CreateDCompSurface( |
| image_size, SkColors::kGreen, {{image_inner_rect, SkColors::kBlue}})); |
| overlay.content_rect.Inset(0.5); |
| overlay.quad_rect = gfx::Rect( |
| gfx::Point(20, 20), |
| gfx::Size(overlay.content_rect.width(), overlay.content_rect.height())); |
| overlay.z_order = 1; |
| overlay.layer_id = gfx::OverlayLayerId::MakeForTesting(0); |
| ScheduleOverlay(std::move(overlay)); |
| |
| PresentAndCheckScreenshot(); |
| } |
| |
| std::vector<DCLayerOverlayParams> GetOverlaysForSeamsWithComplexTransformTest( |
| base::RepeatingCallback<void(int x, int y, DCLayerOverlayParams&)> |
| update_overlay) { |
| gfx::Transform non_integral_transform; |
| non_integral_transform.Scale(0.7301, 0.773); |
| non_integral_transform.Skew(3, 5); |
| non_integral_transform.Translate(10.25, 5.15); |
| |
| std::vector<DCLayerOverlayParams> overlays; |
| |
| const gfx::Size tile_size = gfx::Size(25, 25); |
| for (int y = 0; y < 4; y++) { |
| for (int x = 0; x < 4; x++) { |
| DCLayerOverlayParams& overlay = overlays.emplace_back(); |
| overlay.quad_rect = |
| gfx::Rect(x * tile_size.width(), y * tile_size.height(), |
| tile_size.width(), tile_size.height()); |
| overlay.content_rect = gfx::RectF(tile_size); |
| overlay.overlay_image = CreateDCompSurface(tile_size, SkColors::kWhite); |
| overlay.transform = non_integral_transform; |
| overlay.z_order = x + y * 4 + 1; |
| |
| update_overlay.Run(x, y, overlay); |
| } |
| } |
| |
| return overlays; |
| } |
| |
| // Check that DCLayerTree does not introduce seams from edge AA on adjacent |
| // overlays that are transformed. The result should be a solid quadrilateral. |
| TEST_P(DCompPresenterSkiaGoldTest, EdgeAANoSeamsOnSameLayerComplexTransform) { |
| InitializeTest(gfx::Size(100, 100)); |
| |
| ScheduleOverlays(GetOverlaysForSeamsWithComplexTransformTest( |
| base::BindRepeating([](int x, int y, DCLayerOverlayParams& overlay) { |
| // All on the same layer. |
| overlay.layer_id = gfx::OverlayLayerId::MakeForTesting(0); |
| }))); |
| |
| PresentAndCheckScreenshot(); |
| } |
| |
| // Check that we always have edge AA turned on for overlay transforms when there |
| // is only one overlay per contiguous layer ID in the overlay list. This should |
| // have the same output as |NoSeamsOnNonIntegralTransformSameLayer| but may |
| // include seams between the overlays. |
| TEST_P(DCompPresenterSkiaGoldTest, EdgeAASeamsOnNotSameLayerComplexTransform) { |
| InitializeTest(gfx::Size(100, 100)); |
| |
| ScheduleOverlays(GetOverlaysForSeamsWithComplexTransformTest( |
| base::BindRepeating([](int x, int y, DCLayerOverlayParams& overlay) { |
| // Reuse layer IDs but have no two adjacent overlays have the same ID. |
| overlay.layer_id = gfx::OverlayLayerId::MakeForTesting((x + y * 4) % 2); |
| }))); |
| |
| PresentAndCheckScreenshot(); |
| } |
| |
| class DCompPresenterDelegatedInkSkiaGoldTest |
| : public DCompPresenterSkiaGoldTest { |
| protected: |
| void SetUp() override { |
| DCompPresenterSkiaGoldTest::SetUp(); |
| if (!presenter_->GetLayerTreeForTesting()->SupportsDelegatedInk()) { |
| GTEST_SKIP() << "Delegated ink is not supported due to lack of gpu " |
| "support or availability of api on OS."; |
| } |
| } |
| void ScheduleInkTrail(const gfx::RectF& presentation_area, |
| const SkColor& color, |
| float diameter, |
| base::span<const gfx::DelegatedInkPoint> points) { |
| DCLayerTree* layer_tree = presenter_->GetLayerTreeForTesting(); |
| // Metadata timestamp and point should match the first DelegatedInkPoint. |
| gfx::DelegatedInkMetadata metadata(points[0].point(), diameter, color, |
| points[0].timestamp(), presentation_area, |
| /*hovering=*/false); |
| for (auto point : points) { |
| layer_tree->GetInkRendererForTesting()->StoreDelegatedInkPoint(point); |
| } |
| layer_tree->SetDelegatedInkTrailStartPoint( |
| std::make_unique<gfx::DelegatedInkMetadata>(metadata)); |
| } |
| }; |
| |
| constexpr base::TimeTicks kEarliestTimestamp = base::TimeTicks(); |
| constexpr base::TimeDelta kMicrosecondsBetweenEachPoint = |
| base::Microseconds(10); |
| |
| INSTANTIATE_TEST_SUITE_P(All, |
| DCompPresenterDelegatedInkSkiaGoldTest, |
| DCompPresenterDelegatedInkSkiaGoldTest::GetValues(), |
| &DCompPresenterDelegatedInkSkiaGoldTest::GetParamName); |
| |
| // This test validates the following: |
| // The presentation area with a non-zero and non-integer origin is |
| // rendered correctly. An ink trail is drawn at the edge of the |
| // presentation area, just within the bounds. It should be clipped to the |
| // presentation area and placed correctly within the root frame - window |
| // coordinates. Note that although the presentation area in the metadata |
| // is a gfx::RectF, an integral enclosed rect size is used to build |
| // the content rect and the quad rect of the overlay. |
| TEST_P(DCompPresenterDelegatedInkSkiaGoldTest, NonIntegerPresentationArea) { |
| gfx::Size window_size(200, 200); |
| InitializeTest(window_size); |
| InitializeRootAndScheduleRootSurface(window_size, SkColors::kWhite); |
| |
| // Create and send some delegated ink points + metadata. |
| const int kPointerId = 2; |
| gfx::DelegatedInkPoint points[] = { |
| {gfx::PointF(10.7, 10.7), kEarliestTimestamp, kPointerId}, |
| {gfx::PointF(10.7, 30), |
| kEarliestTimestamp + kMicrosecondsBetweenEachPoint, kPointerId}}; |
| gfx::RectF presentation_area(10.4, 10.4, 100, 100); |
| ScheduleInkTrail(presentation_area, SK_ColorRED, /* diameter= */ 10.0, |
| points); |
| // Commit. Delegated ink overlay will be created here and its visual subtree |
| // will be added to the dcomp root visual. |
| PresentAndCheckScreenshot("ink-trail-present"); |
| } |
| |
| // This test checks whether the delegated ink trail is synchronized with the |
| // root swap chain. The ink trail should be absent if the swap chain is |
| // presented and no delegated ink visual subtree is added to the tree. |
| TEST_P(DCompPresenterDelegatedInkSkiaGoldTest, TrailSyncedToSwapChainPresent) { |
| // Initialize the swap chain that will be used as the root overlay |
| // image. |
| gfx::Size swap_chain_size(200, 200); |
| InitializeTest(swap_chain_size); |
| Microsoft::WRL::ComPtr<IDXGISwapChain1> swap_chain; |
| Microsoft::WRL::ComPtr<ID3D11RenderTargetView> rtv; |
| InitializeSwapChainForTest(swap_chain_size, swap_chain, rtv); |
| ASSERT_TRUE(swap_chain); |
| ASSERT_TRUE(rtv); |
| |
| // Define 200x200 monitor size. |
| const gfx::Size monitor_size(200, 200); |
| |
| // Create and send some delegated ink points slightly outside the bounds. |
| const int kPointerId = 2; |
| gfx::DelegatedInkPoint points[] = { |
| {gfx::PointF(12, 12), kEarliestTimestamp, kPointerId}, |
| {gfx::PointF(12, 30), kEarliestTimestamp + kMicrosecondsBetweenEachPoint, |
| kPointerId}}; |
| gfx::RectF presentation_area(10, 10, 100, 100); |
| ScheduleInkTrail(presentation_area, SK_ColorRED, /* diameter= */ 10.0, |
| points); |
| |
| // Make root overlay. |
| auto dc_layer_params = |
| CreateParamsFromImage(DCLayerOverlayImage(swap_chain_size, swap_chain)); |
| dc_layer_params.quad_rect = gfx::Rect(monitor_size); |
| dc_layer_params.z_order = 0; |
| dc_layer_params.layer_id = GetRootSurfaceId(); |
| ScheduleOverlay(std::move(dc_layer_params)); |
| |
| ASSERT_HRESULT_SUCCEEDED(ClearRenderTargetViewAndPresent( |
| SkColors::kGreen, swap_chain.Get(), rtv.Get())); |
| // Verify trail present. |
| PresentAndCheckScreenshot("ink-trail-present"); |
| |
| // Clear swap chain to blue and make sure the delegated ink trail is not |
| // present for this next frame. |
| ASSERT_HRESULT_SUCCEEDED(ClearRenderTargetViewAndPresent( |
| SkColors::kBlue, swap_chain.Get(), rtv.Get())); |
| dc_layer_params = |
| CreateParamsFromImage(DCLayerOverlayImage(swap_chain_size, swap_chain)); |
| dc_layer_params.quad_rect = gfx::Rect(monitor_size); |
| dc_layer_params.layer_id = GetRootSurfaceId(); |
| ScheduleOverlay(std::move(dc_layer_params)); |
| PresentAndCheckScreenshot("cleared-swapchain"); |
| } |
| |
| // This test checks whether a delegated ink trail renders correctly |
| // for a root surface with a dcomp surface image, and is correctly removed |
| // when no metadata is present for a frame. |
| TEST_P(DCompPresenterDelegatedInkSkiaGoldTest, RootSurfaceIsDCompSurface) { |
| gfx::Size window_size(200, 200); |
| InitializeTest(window_size); |
| |
| InitializeRootAndScheduleRootSurface(window_size, SkColors::kWhite); |
| |
| // Create and send some delegated ink points + metadata. |
| const int kPointerId = 2; |
| gfx::DelegatedInkPoint points[] = { |
| {gfx::PointF(11, 11), kEarliestTimestamp, kPointerId}, |
| {gfx::PointF(30, 30), kEarliestTimestamp + kMicrosecondsBetweenEachPoint, |
| kPointerId}, |
| {gfx::PointF(49, 11), |
| kEarliestTimestamp + 2 * kMicrosecondsBetweenEachPoint, kPointerId}}; |
| gfx::RectF presentation_area(10, 10, 100, 100); |
| ScheduleInkTrail(presentation_area, SK_ColorRED, /* diameter= */ 10.0, |
| points); |
| |
| // Commit. Delegated ink overlay will be created here and its visual subtree |
| // will be added to the dcomp root visual. |
| PresentAndCheckScreenshot("ink-trail-present"); |
| |
| // Create another surface and make sure there's no ink trail in the next |
| // frame. |
| auto overlay = |
| CreateParamsFromImage(CreateDCompSurface(window_size, SkColors::kWhite)); |
| overlay.quad_rect = gfx::Rect(200, 200); |
| overlay.z_order = 0; |
| overlay.layer_id = GetRootSurfaceId(); |
| ScheduleOverlay(std::move(overlay)); |
| PresentAndCheckScreenshot("no-ink-trail"); |
| } |
| |
| // This test verifies that the ink trail renders correctly when the delegated |
| // ink metadata changes properties such as color and diameter. |
| TEST_P(DCompPresenterDelegatedInkSkiaGoldTest, MetadataChangesProperties) { |
| gfx::Size window_size(200, 200); |
| InitializeTest(window_size); |
| |
| InitializeRootAndScheduleRootSurface(window_size, SkColors::kWhite); |
| |
| // Create and send some delegated ink points + metadata. |
| const int kPointerId = 2; |
| gfx::DelegatedInkPoint points[] = { |
| {gfx::PointF(11, 11), kEarliestTimestamp, kPointerId}, |
| {gfx::PointF(30, 30), kEarliestTimestamp + kMicrosecondsBetweenEachPoint, |
| kPointerId}, |
| {gfx::PointF(49, 11), |
| kEarliestTimestamp + 2 * kMicrosecondsBetweenEachPoint, kPointerId}}; |
| gfx::RectF presentation_area(10, 10, 100, 100); |
| ScheduleInkTrail(presentation_area, SK_ColorRED, /* diameter= */ 10.0, |
| points); |
| |
| // Commit. Delegated ink overlay will be created here and its visual subtree |
| // will be added to the dcomp root visual. |
| PresentAndCheckScreenshot("red-ink-trail"); |
| |
| InitializeRootAndScheduleRootSurface(window_size, SkColors::kWhite); |
| // Create and send some delegated ink points + metadata with different |
| // properties. |
| const int kPointerId2 = 3; |
| gfx::DelegatedInkPoint blue_points[] = { |
| {gfx::PointF(11, 11), |
| kEarliestTimestamp + 3 * kMicrosecondsBetweenEachPoint, kPointerId2}, |
| {gfx::PointF(30, 30), |
| kEarliestTimestamp + 4 * kMicrosecondsBetweenEachPoint, kPointerId2}, |
| {gfx::PointF(49, 11), |
| kEarliestTimestamp + 5 * kMicrosecondsBetweenEachPoint, kPointerId2}}; |
| ScheduleInkTrail(presentation_area, SK_ColorBLUE, /* diameter= */ 5.0, |
| blue_points); |
| |
| PresentAndCheckScreenshot("thin-blue-ink-trail"); |
| } |
| |
| // This test validates that the Ink trail does not render when metadata is |
| // outside presentation area by a fraction. The metadata corresponds to |
| // the first ink point in this case. |
| TEST_P(DCompPresenterDelegatedInkSkiaGoldTest, |
| MetadataOutsidePresentationArea) { |
| gfx::Size window_size(200, 200); |
| InitializeTest(window_size); |
| |
| InitializeRootAndScheduleRootSurface(window_size, SkColors::kWhite); |
| |
| // Create and send the first ink point out of bounds. The first ink point is |
| // transmitted as the metadata. The rest of the points are within bounds. |
| const int kPointerId = 2; |
| gfx::DelegatedInkPoint points[] = { |
| {gfx::PointF(10.1, 10.1), kEarliestTimestamp, kPointerId}, |
| {gfx::PointF(10.6, 30), |
| kEarliestTimestamp + kMicrosecondsBetweenEachPoint, kPointerId}, |
| {gfx::PointF(10.6, 60), |
| kEarliestTimestamp + 2 * kMicrosecondsBetweenEachPoint, kPointerId}}; |
| gfx::RectF presentation_area(10.4, 10.4, 100, 100); |
| ScheduleInkTrail(presentation_area, SK_ColorRED, /* diameter= */ 10.0, |
| points); |
| |
| // Commit. Delegated ink overlay will be created here and its visual subtree |
| // will be added to the dcomp root visual. |
| PresentAndCheckScreenshot("no-ink-trail"); |
| } |
| |
| // The test verifies that the ink trail is correctly clipped when part of it is |
| // outside the presentation area. |
| TEST_P(DCompPresenterDelegatedInkSkiaGoldTest, InkTrailPortionClipped) { |
| gfx::Size window_size(200, 200); |
| InitializeTest(window_size); |
| |
| InitializeRootAndScheduleRootSurface(window_size, SkColors::kWhite); |
| |
| // Draw a red trail. The trail has some points outside of the presentation |
| // area which should be clipped. |
| const int kPointerId = 2; |
| gfx::DelegatedInkPoint points[] = { |
| // Inside presentation area. |
| {gfx::PointF(55, 55), kEarliestTimestamp, kPointerId}, |
| // Outside presentation area. |
| {gfx::PointF(75, 120), kEarliestTimestamp + kMicrosecondsBetweenEachPoint, |
| kPointerId}, |
| // Inside presentation area. |
| {gfx::PointF(90, 55), |
| kEarliestTimestamp + 2 * kMicrosecondsBetweenEachPoint, kPointerId}}; |
| gfx::RectF presentation_area(50, 50, 100, 100); |
| ScheduleInkTrail(presentation_area, SK_ColorRED, /* diameter= */ 10.0, |
| points); |
| |
| // Commit. Delegated ink overlay will be created here and its visual subtree |
| // will be added to the dcomp root visual. Trail should resemble a "V" cut |
| // from the bottom. |
| PresentAndCheckScreenshot("red-ink-trail-10"); |
| } |
| |
| // The test verifies that the ink trail is correctly clipped when the points are |
| // within the presentation area but its thickness causes a portion of it to be |
| // outside the bounds. |
| TEST_P(DCompPresenterDelegatedInkSkiaGoldTest, InkTrailClippedDueToThickness) { |
| gfx::Size window_size(200, 200); |
| InitializeTest(window_size); |
| |
| InitializeRootAndScheduleRootSurface(window_size, SkColors::kWhite); |
| const int kPointerId = 2; |
| |
| // Draw a thicker blue trail in the next frame. This trail should also be |
| // clipped at the bottom; while the point is within the bounds, the diameter |
| // will cause the ink trail to extend beyond. |
| gfx::DelegatedInkPoint points[] = { |
| {gfx::PointF(55, 55), kEarliestTimestamp, kPointerId}, |
| {gfx::PointF(55, 85), kEarliestTimestamp + kMicrosecondsBetweenEachPoint, |
| kPointerId}, |
| {gfx::PointF(55, 100), |
| kEarliestTimestamp + 2 * kMicrosecondsBetweenEachPoint, kPointerId}}; |
| gfx::RectF presentation_area(50, 50, 100, 100); |
| |
| ScheduleInkTrail(presentation_area, SK_ColorBLUE, /* diameter= */ 40.0, |
| points); |
| |
| // Commit. Delegated ink overlay will be created here and its visual subtree |
| // will be added to the dcomp root visual. |
| PresentAndCheckScreenshot("blue-ink-trail-40"); |
| } |
| |
| struct TripleBufferParam { |
| bool use_triple_buffer_video_swap_chain = false; |
| }; |
| |
| void PrintTo(const TripleBufferParam& param, std::ostream* os) { |
| if (param.use_triple_buffer_video_swap_chain) { |
| *os << "DCompTripleBufferVideoSwapChain"; |
| } else { |
| *os << "DcompTripleBufferVideoSwapChain_default"; |
| } |
| } |
| |
| class DCompPresenterBufferCountTest |
| : public DCompPresenterTestBase<TripleBufferParam> { |
| protected: |
| void SetUp() override { |
| if (GetTestParam().use_triple_buffer_video_swap_chain) { |
| DCompPresenterTestBase::EnableFeature( |
| features::kDCompTripleBufferVideoSwapChain); |
| } else { |
| DCompPresenterTestBase::DisableFeature( |
| features::kDCompTripleBufferVideoSwapChain); |
| } |
| DCompPresenterTestBase::SetUp(); |
| } |
| }; |
| |
| TEST_P(DCompPresenterBufferCountTest, VideoSwapChainBufferCount) { |
| SetDirectCompositionScaledOverlaysSupportedForTesting(true); |
| |
| constexpr gfx::Size window_size(100, 100); |
| EXPECT_TRUE(presenter_->Resize(window_size, 1.0, gfx::ColorSpace(), true)); |
| |
| constexpr gfx::Size texture_size(50, 50); |
| |
| Microsoft::WRL::ComPtr<ID3D11Device> d3d11_device = |
| GetDirectCompositionD3D11Device(); |
| ASSERT_TRUE(d3d11_device); |
| |
| Microsoft::WRL::ComPtr<ID3D11Texture2D> texture = |
| CreateNV12Texture(d3d11_device, texture_size); |
| ASSERT_NE(texture, nullptr); |
| |
| auto params = |
| CreateParamsFromImage(DCLayerOverlayImage(texture_size, texture)); |
| params.quad_rect = gfx::Rect(window_size); |
| params.layer_id = gfx::OverlayLayerId::MakeForTesting(0); |
| params.video_params.color_space = gfx::ColorSpace::CreateREC709(); |
| ScheduleOverlay(std::move(params)); |
| |
| ASSERT_EQ(PresentAndGetSwapResult(), gfx::SwapResult::SWAP_ACK); |
| |
| auto swap_chain = presenter_->GetLayerSwapChainForTesting( |
| gfx::OverlayLayerId::MakeForTesting(0)); |
| ASSERT_TRUE(swap_chain); |
| |
| DXGI_SWAP_CHAIN_DESC1 desc; |
| EXPECT_HRESULT_SUCCEEDED(swap_chain->GetDesc1(&desc)); |
| // The expected size is window_size(100, 100). |
| EXPECT_EQ(100u, desc.Width); |
| EXPECT_EQ(100u, desc.Height); |
| if (GetTestParam().use_triple_buffer_video_swap_chain) { |
| EXPECT_EQ(3u, desc.BufferCount); |
| } else { |
| EXPECT_EQ(2u, desc.BufferCount); |
| } |
| } |
| |
| INSTANTIATE_TEST_SUITE_P(, |
| DCompPresenterBufferCountTest, |
| DCompPresenterBufferCountTest::GetValues( |
| testing::ConvertGenerator(testing::Bool())), |
| &DCompPresenterBufferCountTest::GetParamName); |
| |
| enum class SwapChainPresentationMode { |
| kDecodeSwapChain, |
| kMFDCompSurface, |
| }; |
| |
| struct LetterboxingTestParams { |
| bool use_letterbox_video_optimization = false; |
| SwapChainPresentationMode presentation_mode = |
| SwapChainPresentationMode::kDecodeSwapChain; |
| bool early_full_screen_video_optimization = false; |
| }; |
| |
| void PrintTo(const LetterboxingTestParams& param, std::ostream* os) { |
| if (param.use_letterbox_video_optimization) { |
| *os << "LetterboxOptOn"; |
| } else { |
| *os << "LetterboxOptOff"; |
| } |
| |
| *os << "_"; |
| |
| switch (param.presentation_mode) { |
| case SwapChainPresentationMode::kDecodeSwapChain: |
| *os << "DecodeSwapChain"; |
| break; |
| case SwapChainPresentationMode::kMFDCompSurface: |
| *os << "MFDCompSurface"; |
| break; |
| } |
| |
| *os << "_"; |
| |
| if (param.early_full_screen_video_optimization) { |
| *os << "EarlyFullScreenOptOn"; |
| } else { |
| *os << "EarlyFullScreenOptOff"; |
| } |
| } |
| |
| class DCompPresenterLetterboxingTest |
| : public DCompPresenterTestBase<LetterboxingTestParams> { |
| protected: |
| void SetUp() override { |
| SetupScopedFeatureList(); |
| DCompPresenterTestBase::SetUp(); |
| } |
| |
| virtual void SetupScopedFeatureList() { |
| std::vector<base::test::FeatureRef> enabled_features; |
| std::vector<base::test::FeatureRef> disabled_features; |
| |
| // TODO(crbug.com/428158600): For now set up |
| // kDesktopPlaneRemovalForMFFullScreenLetterbox flag by |
| // following kDirectCompositionLetterboxVideoOptimization flag. |
| if (GetTestParam().use_letterbox_video_optimization) { |
| DCompPresenterTestBase::EnableFeature( |
| features::kDirectCompositionLetterboxVideoOptimization); |
| DCompPresenterTestBase::EnableFeature( |
| features::kDesktopPlaneRemovalForMFFullScreenLetterbox); |
| } else { |
| DCompPresenterTestBase::DisableFeature( |
| features::kDirectCompositionLetterboxVideoOptimization); |
| DCompPresenterTestBase::DisableFeature( |
| features::kDesktopPlaneRemovalForMFFullScreenLetterbox); |
| } |
| |
| if (GetTestParam().early_full_screen_video_optimization) { |
| DCompPresenterTestBase::EnableFeature( |
| features::kEarlyFullScreenVideoOptimization); |
| } else { |
| DCompPresenterTestBase::DisableFeature( |
| features::kEarlyFullScreenVideoOptimization); |
| } |
| } |
| |
| class MockDCOMPSurfaceProxy : public gl::DCOMPSurfaceProxy { |
| public: |
| MockDCOMPSurfaceProxy() = default; |
| MOCK_METHOD(gfx::Size&, GetSize, (), (const, override)); |
| MOCK_METHOD(HANDLE, GetSurfaceHandle, (), (override)); |
| MOCK_METHOD(void, |
| SetRect, |
| (const gfx::Rect& window_relative_rect), |
| (override)); |
| MOCK_METHOD(void, SetParentWindow, (HWND parent), (override)); |
| |
| private: |
| ~MockDCOMPSurfaceProxy() override = default; |
| }; |
| |
| void ScheduleFullScreenOverlay(DCLayerOverlayParams overlay) { |
| if (GetTestParam().use_letterbox_video_optimization && |
| base::FeatureList::IsEnabled( |
| features::kEarlyFullScreenVideoOptimization)) { |
| overlay.video_params.is_full_screen_video = true; |
| } else { |
| overlay.video_params.possible_video_fullscreen_letterboxing = true; |
| } |
| |
| DCompPresenterTestBase<LetterboxingTestParams>::ScheduleOverlay( |
| std::move(overlay)); |
| } |
| |
| DCLayerOverlayImage CreateOverlayImage( |
| const gfx::Size& resource_size, |
| const gfx::Size& monitor_size, |
| const gfx::Rect& clip_rect, |
| std::optional<gfx::Rect> dcomp_surface_set_rect_override = std::nullopt) { |
| switch (GetTestParam().presentation_mode) { |
| case SwapChainPresentationMode::kDecodeSwapChain: { |
| Microsoft::WRL::ComPtr<ID3D11Device> d3d11_device = |
| GetDirectCompositionD3D11Device(); |
| Microsoft::WRL::ComPtr<ID3D11Texture2D> texture = |
| CreateNV12Texture(d3d11_device, resource_size); |
| return DCLayerOverlayImage(resource_size, texture); |
| } |
| case SwapChainPresentationMode::kMFDCompSurface: { |
| auto dcomp_surface_proxy = |
| CreateMFDCompSurfaceProxy(resource_size, monitor_size, clip_rect, |
| dcomp_surface_set_rect_override); |
| return DCLayerOverlayImage(resource_size, dcomp_surface_proxy); |
| } |
| } |
| NOTREACHED() << "Unexpected presentation_mode value: " |
| << static_cast<int>(GetTestParam().presentation_mode); |
| } |
| |
| // `dcomp_surface_set_rect_override` can be optionally passed in to set an |
| // explicit Rect we expect `DCOMPSurfaceProxy->SetRect` to be called with, |
| // such as if we have a non-uniform transform and do not expect letterboxing |
| // optimizations to trigger. If this parameter is not passed in, the default |
| // behavior is to expect SetRect is called with the monitor size due to |
| // letterboxing optimizations, or just the clip rect if letterboxing |
| // optimizations are not used. |
| scoped_refptr<MockDCOMPSurfaceProxy> CreateMFDCompSurfaceProxy( |
| const gfx::Size& resource_size, |
| const gfx::Size& monitor_size, |
| const gfx::Rect& clip_rect, |
| std::optional<gfx::Rect> dcomp_surface_set_rect_override = std::nullopt) { |
| auto dcomp_surface_proxy = base::MakeRefCounted<MockDCOMPSurfaceProxy>(); |
| HANDLE handle = INVALID_HANDLE_VALUE; |
| EXPECT_TRUE( |
| gl::SwapChainPresenter::CreateSurfaceHandleHelperForTesting(&handle)); |
| EXPECT_CALL(*dcomp_surface_proxy, GetSurfaceHandle()) |
| .WillRepeatedly(::testing::Return(handle)); |
| EXPECT_CALL(*dcomp_surface_proxy, SetParentWindow(testing::_)) |
| .Times(testing::AnyNumber()); |
| EXPECT_CALL(*dcomp_surface_proxy, GetSize()) |
| .WillRepeatedly(::testing::ReturnRefOfCopy(resource_size)); |
| gfx::Rect expected_rect; |
| if (dcomp_surface_set_rect_override.has_value()) { |
| expected_rect = dcomp_surface_set_rect_override.value(); |
| } else { |
| expected_rect = GetTestParam().use_letterbox_video_optimization |
| ? gfx::Rect(monitor_size) |
| : clip_rect; |
| } |
| EXPECT_CALL(*dcomp_surface_proxy, SetRect(testing::Eq(expected_rect))) |
| .Times(testing::AnyNumber()); |
| return dcomp_surface_proxy; |
| } |
| |
| bool CheckVideoDisablesDesktopPlane(const gfx::Size& monitor_size) { |
| // To disable the desktop plane, the swap chain or dcomp surface must be |
| // unoccluded and covering the whole screen. Retrieve the front-most overlay |
| // in the visual tree, ensure it is a decode swap chain or dcomp surface, |
| // and make sure it is unoccluded. |
| gl::DCLayerTree::VisualTree::VisualSubtree* front_sub_tree = |
| presenter_->GetLayerTreeForTesting() |
| ->GetFrontMostVideoVisualSubtreeForTesting(); |
| EXPECT_NE(front_sub_tree, nullptr); |
| |
| gfx::Transform visual_transform = |
| front_sub_tree->GetQuadToRootTransformForTesting(); |
| // Rotated videos cannot cover the whole screen. |
| EXPECT_TRUE(visual_transform.Preserves2dAxisAlignment()); |
| gfx::Rect monitor_rect(monitor_size); |
| gfx::Rect onscreen_rect; |
| std::optional<gfx::Rect> clip_rect = |
| front_sub_tree->GetClipRectInRootForTesting(); |
| |
| if (GetTestParam().presentation_mode == |
| SwapChainPresentationMode::kDecodeSwapChain) { |
| Microsoft::WRL::ComPtr<IDXGIDecodeSwapChain> decode_swap_chain; |
| HRESULT hr = front_sub_tree->dcomp_visual_content()->QueryInterface( |
| IID_PPV_ARGS(&decode_swap_chain)); |
| if (FAILED(hr) || !decode_swap_chain) { |
| // This is reachable but current tests only test decode swap chain |
| // presentation. Adjust if supporting regular swap chain testing. |
| NOTREACHED(); |
| } |
| |
| // TODO(crbug.com/414842426): `SwapChainPresenter` doesn't tell |
| // `DCLayerTree` how large the video content is, so must use DestSize to |
| // find out instead of with a property on the overlay. |
| |
| uint32_t dest_width, dest_height; |
| EXPECT_HRESULT_SUCCEEDED( |
| decode_swap_chain->GetDestSize(&dest_width, &dest_height)); |
| |
| // This is the actual onscreen rect of the video. |
| onscreen_rect = |
| visual_transform.MapRect(gfx::Rect(dest_width, dest_height)); |
| |
| // If there is a `clip_rect` present, clip the actual onscreen rect to a |
| // clipped onscreen rect. |
| if (clip_rect.has_value()) { |
| onscreen_rect.Intersect(clip_rect.value()); |
| } |
| } else if (GetTestParam().presentation_mode == |
| SwapChainPresentationMode::kMFDCompSurface) { |
| // TODO(crbug.com/414842426): The clip rect is the only rect information |
| // we have about the dcomp surface. Add support for `SwapChainPresenter` |
| // to track size of the dcomp surface and track a target rect of where the |
| // content is being mapped inside its actual size. |
| |
| // We are not able to get the rect used to originally call |
| // `DCOMPSurfaceProxy::SetRect`, but we can use the visual_clip_rect. |
| // `SwapChainPresenter` will use the monitor size to call SetRect and will |
| // set its visual_clip_rect as the monitor size. As further validation, on |
| // creation of the `MockDCOMPSurfaceProxy` in `CreateOverlayImage`, we |
| // check that the monitor size is used to call SetRect. |
| if (!clip_rect.has_value()) { |
| // Current implementation of tests expect that we have a clip rect. |
| NOTREACHED(); |
| } |
| onscreen_rect = clip_rect.value(); |
| } |
| |
| // If the video layer's onscreen rect covers the monitor, |
| // that allows for DWM to disable the desktop plane. |
| if (!onscreen_rect.Contains(monitor_rect)) { |
| return false; |
| } |
| |
| return true; |
| } |
| |
| // The video is correctly letterboxed if the logical onscreen rect touches |
| // two sides of the monitor and is centered. |
| // TODO(crbug.com/416206298): Add full DWM optimization validations such as |
| // DMRRS enablement checks. |
| bool CheckVideoIsLetterboxedCorrectly(const gfx::Size& monitor_size) { |
| gl::DCLayerTree::VisualTree::VisualSubtree* front_sub_tree = |
| presenter_->GetLayerTreeForTesting() |
| ->GetFrontMostVideoVisualSubtreeForTesting(); |
| EXPECT_NE(front_sub_tree, nullptr); |
| |
| gfx::Transform visual_transform = |
| front_sub_tree->GetQuadToRootTransformForTesting(); |
| gfx::Rect monitor_rect(monitor_size); |
| gfx::Rect onscreen_rect; |
| |
| if (GetTestParam().presentation_mode == |
| SwapChainPresentationMode::kDecodeSwapChain) { |
| Microsoft::WRL::ComPtr<IDXGIDecodeSwapChain> decode_swap_chain; |
| HRESULT hr = front_sub_tree->dcomp_visual_content()->QueryInterface( |
| IID_PPV_ARGS(&decode_swap_chain)); |
| if (FAILED(hr) || !decode_swap_chain) { |
| // This is reachable but current tests only test decode swap chain |
| // presentation. Adjust if supporting regular swap chain testing. |
| NOTREACHED(); |
| } |
| |
| RECT target_rect; |
| EXPECT_HRESULT_SUCCEEDED(decode_swap_chain->GetTargetRect(&target_rect)); |
| // Note: We want the position of the actual video contents (i.e. |
| // `target_rect`) in window space. We're assuming here that, when |
| // full-screened, the swap chain (i.e. `dest_size`) is the size of the |
| // monitor and the overlay layer (i.e. `quad_rect` + `transform`) also |
| // fills the monitor. |
| // |
| // We make this assumption because running with |
| // `!EarlyFullScreenVideoOptimization`, `DCLayerTree` does not correctly |
| // place the `quad_rect` in the full screen case and relies on letting the |
| // content visual overflow its intended bounds. |
| onscreen_rect = gfx::Rect(target_rect); |
| onscreen_rect.Offset( |
| gfx::ToRoundedVector2d(visual_transform.To2dTranslation())); |
| } else if (GetTestParam().presentation_mode == |
| SwapChainPresentationMode::kMFDCompSurface) { |
| // TODO(crbug.com/414842426): The clip rect is the only rect information |
| // we have about the dcomp surface. Add support for `SwapChainPresenter` |
| // to track size of the dcomp surface and track a target rect of where the |
| // content is being mapped inside its actual size. |
| |
| // We are not able to get the rect used to originally call |
| // `DCOMPSurfaceProxy::SetRect`, but we can use the visual_clip_rect. |
| // `SwapChainPresenter` will use the monitor size to call SetRect and will |
| // set its visual_clip_rect as the monitor size. As further validation, on |
| // creation of the `MockDCOMPSurfaceProxy` in `CreateOverlayImage`, we |
| // check that the monitor size is used to call SetRect. |
| std::optional<gfx::Rect> clip_rect = |
| front_sub_tree->GetClipRectInRootForTesting(); |
| if (!clip_rect.has_value()) { |
| // Current implementation of tests expect that we have a clip rect. |
| NOTREACHED(); |
| } |
| onscreen_rect = clip_rect.value(); |
| } |
| |
| bool is_letterboxing = |
| onscreen_rect.x() == 0 && onscreen_rect.width() == monitor_rect.width(); |
| bool is_pillarboxing = onscreen_rect.y() == 0 && |
| onscreen_rect.height() == monitor_rect.height(); |
| |
| if (is_letterboxing && is_pillarboxing) { |
| return true; |
| } else if (is_letterboxing) { |
| float monitor_center_y = monitor_rect.height() / 2; |
| float onscreen_rect_center_y = |
| onscreen_rect.y() + (onscreen_rect.height() / 2); |
| if (std::abs(monitor_center_y - onscreen_rect_center_y) <= 1) { |
| return true; |
| } |
| } else if (is_pillarboxing) { |
| float monitor_center_x = monitor_rect.width() / 2; |
| float onscreen_rect_center_x = |
| onscreen_rect.x() + (onscreen_rect.width() / 2); |
| if (std::abs(monitor_center_x - onscreen_rect_center_x) <= 1) { |
| return true; |
| } |
| } |
| |
| // Neither letterboxing nor pillarboxing. |
| return false; |
| } |
| }; |
| |
| INSTANTIATE_TEST_SUITE_P( |
| , |
| DCompPresenterLetterboxingTest, |
| DCompPresenterLetterboxingTest::GetValues(testing::ConvertGenerator( |
| testing::Combine( |
| testing::Bool(), |
| testing::Values(SwapChainPresentationMode::kDecodeSwapChain, |
| SwapChainPresentationMode::kMFDCompSurface), |
| testing::Bool()), |
| [](std::tuple<bool, SwapChainPresentationMode, bool> t) { |
| return LetterboxingTestParams{ |
| .use_letterbox_video_optimization = std::get<0>(t), |
| .presentation_mode = std::get<1>(t), |
| .early_full_screen_video_optimization = std::get<2>(t), |
| }; |
| })), |
| &DCompPresenterLetterboxingTest::GetParamName); |
| |
| TEST_P(DCompPresenterLetterboxingTest, FullScreenLetterboxingResizeVideoLayer) { |
| // Define 1920x1200 monitor size. |
| const gfx::Size monitor_size(1920, 1200); |
| SetDirectCompositionScaledOverlaysSupportedForTesting(true); |
| SetDirectCompositionMonitorInfoForTesting(1, monitor_size); |
| EXPECT_TRUE(presenter_->Resize(monitor_size, 1.0, gfx::ColorSpace(), true)); |
| |
| // Schedule the overlay for root surface. |
| InitializeRootAndScheduleRootSurface(monitor_size, SkColors::kBlack); |
| |
| // Use 1080p content size for texture or dcomp surface. |
| const gfx::Size resource_size(1920, 1080); |
| const int letterboxing_height = |
| (monitor_size.height() - resource_size.height()) / 2; |
| const gfx::Rect quad_rect(0, 0, resource_size.width(), |
| resource_size.height()); |
| |
| // First test if swap chain and its visual info is adjusted to fit the |
| // monitor when letterboxing is generated for full screen presentation. |
| gfx::Transform quad_to_root_transform = |
| gfx::Transform::MakeTranslation(0, letterboxing_height); |
| gfx::Rect clip_rect = quad_to_root_transform.MapRect(quad_rect); |
| |
| { |
| DCLayerOverlayParams dc_layer_params = CreateParamsFromImage( |
| CreateOverlayImage(resource_size, monitor_size, clip_rect)); |
| |
| dc_layer_params.quad_rect = quad_rect; |
| dc_layer_params.transform = quad_to_root_transform; |
| dc_layer_params.clip_rect = clip_rect; |
| dc_layer_params.video_params.color_space = gfx::ColorSpace::CreateREC709(); |
| dc_layer_params.z_order = 1; |
| dc_layer_params.layer_id = gfx::OverlayLayerId::MakeForTesting(0); |
| ScheduleFullScreenOverlay(std::move(dc_layer_params)); |
| ASSERT_EQ(PresentAndGetSwapResult(), gfx::SwapResult::SWAP_ACK); |
| } |
| |
| if (GetTestParam().use_letterbox_video_optimization) { |
| EXPECT_TRUE(CheckVideoDisablesDesktopPlane(monitor_size)); |
| EXPECT_TRUE(CheckVideoIsLetterboxedCorrectly(monitor_size)); |
| } else { |
| EXPECT_FALSE(CheckVideoDisablesDesktopPlane(monitor_size)); |
| EXPECT_TRUE(CheckVideoIsLetterboxedCorrectly(monitor_size)); |
| } |
| |
| if (base::FeatureList::IsEnabled( |
| features::kEarlyFullScreenVideoOptimization)) { |
| // The rest of this test checks that video overlays are adjusted for full |
| // screen. EarlyFullScreenVideoOptimization handles adjustment of overlay |
| // position during overlay processing. |
| return; |
| } |
| |
| // Second test if swap chain visual info is adjusted to fit the monitor when |
| // some negative offset from typical letterboxing positioning. |
| quad_to_root_transform = |
| gfx::Transform::MakeTranslation(0, letterboxing_height - 2); |
| clip_rect = quad_to_root_transform.MapRect(quad_rect); |
| |
| { |
| DCLayerOverlayParams dc_layer_params = CreateParamsFromImage( |
| CreateOverlayImage(resource_size, monitor_size, clip_rect)); |
| |
| dc_layer_params.quad_rect = quad_rect; |
| dc_layer_params.transform = quad_to_root_transform; |
| dc_layer_params.clip_rect = clip_rect; |
| dc_layer_params.video_params.color_space = gfx::ColorSpace::CreateREC709(); |
| dc_layer_params.z_order = 1; |
| dc_layer_params.layer_id = gfx::OverlayLayerId::MakeForTesting(0); |
| ScheduleFullScreenOverlay(std::move(dc_layer_params)); |
| ASSERT_EQ(PresentAndGetSwapResult(), gfx::SwapResult::SWAP_ACK); |
| } |
| |
| if (GetTestParam().use_letterbox_video_optimization) { |
| EXPECT_TRUE(CheckVideoDisablesDesktopPlane(monitor_size)); |
| EXPECT_TRUE(CheckVideoIsLetterboxedCorrectly(monitor_size)); |
| } else { |
| EXPECT_FALSE(CheckVideoDisablesDesktopPlane(monitor_size)); |
| EXPECT_FALSE(CheckVideoIsLetterboxedCorrectly(monitor_size)); |
| } |
| |
| // Third test if swap chain visual info is adjusted to fit the monitor when |
| // some positive offset from typical letterboxing positioning. |
| quad_to_root_transform = |
| gfx::Transform::MakeTranslation(0, letterboxing_height + 2); |
| clip_rect = quad_to_root_transform.MapRect(quad_rect); |
| |
| { |
| DCLayerOverlayParams dc_layer_params = CreateParamsFromImage( |
| CreateOverlayImage(resource_size, monitor_size, clip_rect)); |
| |
| dc_layer_params.quad_rect = quad_rect; |
| dc_layer_params.transform = quad_to_root_transform; |
| dc_layer_params.clip_rect = clip_rect; |
| dc_layer_params.video_params.color_space = gfx::ColorSpace::CreateREC709(); |
| dc_layer_params.z_order = 1; |
| dc_layer_params.layer_id = gfx::OverlayLayerId::MakeForTesting(0); |
| ScheduleFullScreenOverlay(std::move(dc_layer_params)); |
| ASSERT_EQ(PresentAndGetSwapResult(), gfx::SwapResult::SWAP_ACK); |
| } |
| |
| if (GetTestParam().use_letterbox_video_optimization) { |
| EXPECT_TRUE(CheckVideoDisablesDesktopPlane(monitor_size)); |
| EXPECT_TRUE(CheckVideoIsLetterboxedCorrectly(monitor_size)); |
| } else { |
| EXPECT_FALSE(CheckVideoDisablesDesktopPlane(monitor_size)); |
| EXPECT_FALSE(CheckVideoIsLetterboxedCorrectly(monitor_size)); |
| } |
| } |
| |
| TEST_P(DCompPresenterLetterboxingTest, |
| FullScreenLetterboxingWithDesktopPlaneRemoval) { |
| // Define 1920x1200 monitor size. |
| const gfx::Size monitor_size(1920, 1200); |
| SetDirectCompositionScaledOverlaysSupportedForTesting(true); |
| SetDirectCompositionMonitorInfoForTesting(1, monitor_size); |
| EXPECT_TRUE(presenter_->Resize(monitor_size, 1.0, gfx::ColorSpace(), true)); |
| |
| // Schedule the overlay for root surface. |
| InitializeRootAndScheduleRootSurface(monitor_size, SkColors::kBlack); |
| |
| // Use 1080p content size for texture or dcomp surface. |
| const gfx::Size resource_size(1920, 1080); |
| const int letterboxing_height = |
| (monitor_size.height() - resource_size.height()) / 2; |
| const gfx::Rect quad_rect(0, 0, resource_size.width(), |
| resource_size.height()); |
| const gfx::Rect clip_rect(0, letterboxing_height, resource_size.width(), |
| resource_size.height()); |
| const gfx::Transform quad_to_root_transform( |
| gfx::AxisTransform2d(1, gfx::Vector2dF(0, letterboxing_height))); |
| |
| { |
| DCLayerOverlayParams dc_layer_params = CreateParamsFromImage( |
| CreateOverlayImage(resource_size, monitor_size, clip_rect)); |
| |
| dc_layer_params.quad_rect = quad_rect; |
| dc_layer_params.transform = quad_to_root_transform; |
| dc_layer_params.clip_rect = clip_rect; |
| dc_layer_params.video_params.color_space = gfx::ColorSpace::CreateREC709(); |
| dc_layer_params.z_order = 1; |
| dc_layer_params.layer_id = gfx::OverlayLayerId::MakeForTesting(0); |
| ScheduleFullScreenOverlay(std::move(dc_layer_params)); |
| ASSERT_EQ(PresentAndGetSwapResult(), gfx::SwapResult::SWAP_ACK); |
| } |
| |
| if (GetTestParam().use_letterbox_video_optimization) { |
| EXPECT_TRUE(CheckVideoDisablesDesktopPlane(monitor_size)); |
| EXPECT_TRUE(CheckVideoIsLetterboxedCorrectly(monitor_size)); |
| } else { |
| EXPECT_FALSE(CheckVideoDisablesDesktopPlane(monitor_size)); |
| EXPECT_TRUE(CheckVideoIsLetterboxedCorrectly(monitor_size)); |
| } |
| } |
| |
| TEST_P(DCompPresenterLetterboxingTest, FullScreenLetterboxingKeepVisualInfo) { |
| // Define 1920x1200 monitor size. |
| const gfx::Size monitor_size(1920, 1200); |
| SetDirectCompositionScaledOverlaysSupportedForTesting(true); |
| SetDirectCompositionMonitorInfoForTesting(1, monitor_size); |
| EXPECT_TRUE(presenter_->Resize(monitor_size, 1.0, gfx::ColorSpace(), true)); |
| |
| // Schedule the overlay for root surface. |
| InitializeRootAndScheduleRootSurface(monitor_size, SkColors::kBlack); |
| |
| // Make a 1080p texture or dcomp surface as display input. |
| const gfx::Size resource_size(1920, 1080); |
| |
| // First full screen presentation with letterboxing. |
| const int letterboxing_height = |
| (monitor_size.height() - resource_size.height()) / 2; |
| const gfx::Rect quad_rect = |
| gfx::Rect(0, 0, resource_size.width(), resource_size.height()); |
| const gfx::Rect clip_rect = gfx::Rect( |
| 0, letterboxing_height, resource_size.width(), resource_size.height()); |
| const gfx::Transform quad_to_root_transform( |
| gfx::AxisTransform2d(1, gfx::Vector2dF(0, letterboxing_height))); |
| |
| DCLayerOverlayImage overlay_image = |
| CreateOverlayImage(resource_size, monitor_size, clip_rect); |
| { |
| auto dc_layer_params = |
| CreateParamsFromImage(overlay_image.CloneForTesting()); |
| |
| dc_layer_params.quad_rect = quad_rect; |
| dc_layer_params.transform = quad_to_root_transform; |
| dc_layer_params.clip_rect = clip_rect; |
| dc_layer_params.video_params.color_space = gfx::ColorSpace::CreateREC709(); |
| dc_layer_params.z_order = 1; |
| dc_layer_params.layer_id = gfx::OverlayLayerId::MakeForTesting(0); |
| ScheduleFullScreenOverlay(std::move(dc_layer_params)); |
| ASSERT_EQ(PresentAndGetSwapResult(), gfx::SwapResult::SWAP_ACK); |
| } |
| |
| Microsoft::WRL::ComPtr<IDXGISwapChain1> swap_chain; |
| UINT last_present_count; |
| if (GetTestParam().presentation_mode == |
| SwapChainPresentationMode::kDecodeSwapChain) { |
| // Make sure it's a valid swap chain presentation |
| swap_chain = presenter_->GetLayerSwapChainForTesting( |
| gfx::OverlayLayerId::MakeForTesting(0)); |
| ASSERT_TRUE(swap_chain); |
| |
| // One present is normal, and a second present because it's the first frame |
| // and the other buffer needs to be drawn to. |
| last_present_count = 0; |
| EXPECT_HRESULT_SUCCEEDED( |
| swap_chain->GetLastPresentCount(&last_present_count)); |
| EXPECT_EQ(2u, last_present_count); |
| } |
| |
| if (GetTestParam().use_letterbox_video_optimization) { |
| EXPECT_TRUE(CheckVideoDisablesDesktopPlane(monitor_size)); |
| EXPECT_TRUE(CheckVideoIsLetterboxedCorrectly(monitor_size)); |
| } else { |
| EXPECT_FALSE(CheckVideoDisablesDesktopPlane(monitor_size)); |
| EXPECT_TRUE(CheckVideoIsLetterboxedCorrectly(monitor_size)); |
| } |
| |
| // Swap chain visual info is collected for the first presentation. |
| gfx::Transform visual_transform1; |
| gfx::Point visual_offset1; |
| gfx::Rect visual_clip_rect1; |
| presenter_->GetSwapChainVisualInfoForTesting( |
| gfx::OverlayLayerId::MakeForTesting(0), &visual_transform1, |
| &visual_offset1, &visual_clip_rect1); |
| |
| // Followed by second presentation with the same image. |
| { |
| auto dc_layer_params = |
| CreateParamsFromImage(overlay_image.CloneForTesting()); |
| |
| dc_layer_params.quad_rect = quad_rect; |
| dc_layer_params.transform = quad_to_root_transform; |
| dc_layer_params.clip_rect = clip_rect; |
| dc_layer_params.video_params.color_space = gfx::ColorSpace::CreateREC709(); |
| dc_layer_params.z_order = 1; |
| dc_layer_params.layer_id = gfx::OverlayLayerId::MakeForTesting(0); |
| ScheduleFullScreenOverlay(std::move(dc_layer_params)); |
| |
| ASSERT_EQ(PresentAndGetSwapResult(), gfx::SwapResult::SWAP_ACK); |
| } |
| |
| if (GetTestParam().presentation_mode == |
| SwapChainPresentationMode::kDecodeSwapChain) { |
| // It's the same image, so it should have the same swapchain. |
| Microsoft::WRL::ComPtr<IDXGISwapChain1> swap_chain2 = |
| presenter_->GetLayerSwapChainForTesting( |
| gfx::OverlayLayerId::MakeForTesting(0)); |
| EXPECT_EQ(swap_chain2.Get(), swap_chain.Get()); |
| |
| // No new presentation happened and no present count increase since it's |
| // with the same image. |
| EXPECT_HRESULT_SUCCEEDED( |
| swap_chain->GetLastPresentCount(&last_present_count)); |
| EXPECT_EQ(2u, last_present_count); |
| } |
| |
| if (GetTestParam().use_letterbox_video_optimization) { |
| EXPECT_TRUE(CheckVideoDisablesDesktopPlane(monitor_size)); |
| EXPECT_TRUE(CheckVideoIsLetterboxedCorrectly(monitor_size)); |
| } else { |
| EXPECT_FALSE(CheckVideoDisablesDesktopPlane(monitor_size)); |
| EXPECT_TRUE(CheckVideoIsLetterboxedCorrectly(monitor_size)); |
| } |
| |
| // Swap chain visual info should be kept same as the previous presentation. |
| gfx::Transform visual_transform2; |
| gfx::Point visual_offset2; |
| gfx::Rect visual_clip_rect2; |
| presenter_->GetSwapChainVisualInfoForTesting( |
| gfx::OverlayLayerId::MakeForTesting(0), &visual_transform2, |
| &visual_offset2, &visual_clip_rect2); |
| EXPECT_EQ(visual_transform1, visual_transform2); |
| EXPECT_EQ(visual_offset1, visual_offset2); |
| EXPECT_EQ(visual_clip_rect1, visual_clip_rect2); |
| |
| { |
| auto dc_layer_params = CreateParamsFromImage( |
| CreateOverlayImage(resource_size, monitor_size, clip_rect)); |
| |
| dc_layer_params.quad_rect = quad_rect; |
| dc_layer_params.transform = quad_to_root_transform; |
| dc_layer_params.clip_rect = clip_rect; |
| dc_layer_params.video_params.color_space = gfx::ColorSpace::CreateREC709(); |
| dc_layer_params.z_order = 1; |
| dc_layer_params.layer_id = gfx::OverlayLayerId::MakeForTesting(0); |
| ScheduleFullScreenOverlay(std::move(dc_layer_params)); |
| |
| ASSERT_EQ(PresentAndGetSwapResult(), gfx::SwapResult::SWAP_ACK); |
| } |
| if (GetTestParam().presentation_mode == |
| SwapChainPresentationMode::kDecodeSwapChain) { |
| Microsoft::WRL::ComPtr<IDXGISwapChain1> swap_chain3 = |
| presenter_->GetLayerSwapChainForTesting( |
| gfx::OverlayLayerId::MakeForTesting(0)); |
| EXPECT_HRESULT_SUCCEEDED( |
| swap_chain3->GetLastPresentCount(&last_present_count)); |
| // The present count should increase with the new image presentation. |
| EXPECT_EQ(3u, last_present_count); |
| } |
| |
| if (GetTestParam().use_letterbox_video_optimization) { |
| EXPECT_TRUE(CheckVideoDisablesDesktopPlane(monitor_size)); |
| EXPECT_TRUE(CheckVideoIsLetterboxedCorrectly(monitor_size)); |
| } else { |
| EXPECT_FALSE(CheckVideoDisablesDesktopPlane(monitor_size)); |
| EXPECT_TRUE(CheckVideoIsLetterboxedCorrectly(monitor_size)); |
| } |
| } |
| |
| // Pillarboxing is generally considered as a special letterboxing. |
| TEST_P(DCompPresenterLetterboxingTest, FullScreenPillarboxingResizeVideoLayer) { |
| // Define 1920x1200 monitor size. |
| const gfx::Size monitor_size(1920, 1200); |
| SetDirectCompositionScaledOverlaysSupportedForTesting(true); |
| SetDirectCompositionMonitorInfoForTesting(1, monitor_size); |
| EXPECT_TRUE(presenter_->Resize(monitor_size, 1.0, gfx::ColorSpace(), true)); |
| |
| // Schedule the overlay for root surface. |
| InitializeRootAndScheduleRootSurface(monitor_size, SkColors::kBlack); |
| |
| // Use 1800x1200 content size for texture or dcomp surface. |
| const gfx::Size resource_size(1800, 1200); |
| const int letterboxing_width = |
| (monitor_size.width() - resource_size.width()) / 2; |
| const gfx::Rect quad_rect(0, 0, resource_size.width(), |
| resource_size.height()); |
| |
| // First test if swap chain and its visual info is adjusted to fit the |
| // monitor when letterboxing is generated for full screen presentation. |
| int offset = 0; |
| gfx::Transform quad_to_root_transform = |
| gfx::Transform::MakeTranslation(letterboxing_width + offset, 0); |
| gfx::Rect clip_rect = quad_to_root_transform.MapRect(quad_rect); |
| { |
| DCLayerOverlayParams dc_layer_params = CreateParamsFromImage( |
| CreateOverlayImage(resource_size, monitor_size, clip_rect)); |
| |
| dc_layer_params.quad_rect = quad_rect; |
| dc_layer_params.transform = quad_to_root_transform; |
| dc_layer_params.clip_rect = clip_rect; |
| dc_layer_params.video_params.color_space = gfx::ColorSpace::CreateREC709(); |
| dc_layer_params.z_order = 1; |
| dc_layer_params.layer_id = gfx::OverlayLayerId::MakeForTesting(0); |
| ScheduleFullScreenOverlay(std::move(dc_layer_params)); |
| ASSERT_EQ(PresentAndGetSwapResult(), gfx::SwapResult::SWAP_ACK); |
| } |
| |
| if (GetTestParam().use_letterbox_video_optimization) { |
| EXPECT_TRUE(CheckVideoDisablesDesktopPlane(monitor_size)); |
| EXPECT_TRUE(CheckVideoIsLetterboxedCorrectly(monitor_size)); |
| } else { |
| EXPECT_FALSE(CheckVideoDisablesDesktopPlane(monitor_size)); |
| EXPECT_TRUE(CheckVideoIsLetterboxedCorrectly(monitor_size)); |
| } |
| |
| if (base::FeatureList::IsEnabled( |
| features::kEarlyFullScreenVideoOptimization)) { |
| // The rest of this test checks that video overlays are adjusted for full |
| // screen. EarlyFullScreenVideoOptimization handles adjustment of overlay |
| // position during overlay processing. |
| return; |
| } |
| |
| // Second test if swap chain visual info is adjusted to fit the monitor when |
| // some negative offset from typical letterboxing positioning. |
| offset = -2; |
| quad_to_root_transform = |
| gfx::Transform::MakeTranslation(letterboxing_width + offset, 0); |
| clip_rect = quad_to_root_transform.MapRect(quad_rect); |
| |
| { |
| DCLayerOverlayParams dc_layer_params = CreateParamsFromImage( |
| CreateOverlayImage(resource_size, monitor_size, clip_rect)); |
| |
| dc_layer_params.quad_rect = quad_rect; |
| dc_layer_params.transform = quad_to_root_transform; |
| dc_layer_params.clip_rect = clip_rect; |
| dc_layer_params.video_params.color_space = gfx::ColorSpace::CreateREC709(); |
| dc_layer_params.z_order = 1; |
| dc_layer_params.layer_id = gfx::OverlayLayerId::MakeForTesting(0); |
| ScheduleFullScreenOverlay(std::move(dc_layer_params)); |
| ASSERT_EQ(PresentAndGetSwapResult(), gfx::SwapResult::SWAP_ACK); |
| } |
| |
| if (GetTestParam().use_letterbox_video_optimization) { |
| EXPECT_TRUE(CheckVideoDisablesDesktopPlane(monitor_size)); |
| EXPECT_TRUE(CheckVideoIsLetterboxedCorrectly(monitor_size)); |
| } else { |
| EXPECT_FALSE(CheckVideoDisablesDesktopPlane(monitor_size)); |
| EXPECT_FALSE(CheckVideoIsLetterboxedCorrectly(monitor_size)); |
| } |
| |
| // Third test if swap chain visual info is adjusted to fit the monitor when |
| // some positive offset from typical letterboxing positioning. |
| offset = 2; |
| quad_to_root_transform = |
| gfx::Transform::MakeTranslation(letterboxing_width + offset, 0); |
| clip_rect = quad_to_root_transform.MapRect(quad_rect); |
| |
| { |
| DCLayerOverlayParams dc_layer_params = CreateParamsFromImage( |
| CreateOverlayImage(resource_size, monitor_size, clip_rect)); |
| |
| dc_layer_params.quad_rect = quad_rect; |
| dc_layer_params.transform = quad_to_root_transform; |
| dc_layer_params.clip_rect = clip_rect; |
| dc_layer_params.video_params.color_space = gfx::ColorSpace::CreateREC709(); |
| dc_layer_params.z_order = 1; |
| dc_layer_params.layer_id = gfx::OverlayLayerId::MakeForTesting(0); |
| ScheduleFullScreenOverlay(std::move(dc_layer_params)); |
| ASSERT_EQ(PresentAndGetSwapResult(), gfx::SwapResult::SWAP_ACK); |
| } |
| |
| if (GetTestParam().use_letterbox_video_optimization) { |
| EXPECT_TRUE(CheckVideoDisablesDesktopPlane(monitor_size)); |
| EXPECT_TRUE(CheckVideoIsLetterboxedCorrectly(monitor_size)); |
| } else { |
| EXPECT_FALSE(CheckVideoDisablesDesktopPlane(monitor_size)); |
| EXPECT_FALSE(CheckVideoIsLetterboxedCorrectly(monitor_size)); |
| } |
| } |
| |
| TEST_P(DCompPresenterLetterboxingTest, |
| FullScreenPillarboxingWithDesktopPlaneRemoval) { |
| // Define 1920x1200 monitor size. |
| const gfx::Size monitor_size(1920, 1200); |
| SetDirectCompositionScaledOverlaysSupportedForTesting(true); |
| SetDirectCompositionMonitorInfoForTesting(1, monitor_size); |
| EXPECT_TRUE(presenter_->Resize(monitor_size, 1.0, gfx::ColorSpace(), true)); |
| |
| // Schedule the overlay for root surface. |
| InitializeRootAndScheduleRootSurface(monitor_size, SkColors::kBlack); |
| |
| // Use 1800x1200 content size for texture or dcomp surface. |
| const gfx::Size resource_size(1800, 1200); |
| const int letterboxing_width = |
| (monitor_size.width() - resource_size.width()) / 2; |
| const gfx::Rect quad_rect(0, 0, resource_size.width(), |
| resource_size.height()); |
| const gfx::Rect clip_rect(letterboxing_width, 0, resource_size.width(), |
| resource_size.height()); |
| const gfx::Transform quad_to_root_transform( |
| gfx::AxisTransform2d(1, gfx::Vector2dF(letterboxing_width, 0))); |
| |
| { |
| DCLayerOverlayParams dc_layer_params = CreateParamsFromImage( |
| CreateOverlayImage(resource_size, monitor_size, clip_rect)); |
| |
| dc_layer_params.quad_rect = quad_rect; |
| dc_layer_params.transform = quad_to_root_transform; |
| dc_layer_params.clip_rect = clip_rect; |
| dc_layer_params.video_params.color_space = gfx::ColorSpace::CreateREC709(); |
| dc_layer_params.z_order = 1; |
| dc_layer_params.layer_id = gfx::OverlayLayerId::MakeForTesting(0); |
| ScheduleFullScreenOverlay(std::move(dc_layer_params)); |
| ASSERT_EQ(PresentAndGetSwapResult(), gfx::SwapResult::SWAP_ACK); |
| } |
| |
| if (GetTestParam().use_letterbox_video_optimization) { |
| EXPECT_TRUE(CheckVideoDisablesDesktopPlane(monitor_size)); |
| EXPECT_TRUE(CheckVideoIsLetterboxedCorrectly(monitor_size)); |
| } else { |
| EXPECT_FALSE(CheckVideoDisablesDesktopPlane(monitor_size)); |
| EXPECT_TRUE(CheckVideoIsLetterboxedCorrectly(monitor_size)); |
| } |
| } |
| |
| // MF presentation via `DCOMPSurfaceProxy->SetRect` only supports uniform |
| // scaling. MF will not correctly size letterboxed videos with non-uniform |
| // scaling, so they are not subject to DWM fullscreen optimizations. |
| TEST_P(DCompPresenterLetterboxingTest, |
| FullScreenLetterboxingNonUniformScaling) { |
| // Define 1920x1200 monitor size. |
| const gfx::Size monitor_size(1920, 1200); |
| SetDirectCompositionScaledOverlaysSupportedForTesting(true); |
| SetDirectCompositionMonitorInfoForTesting(1, monitor_size); |
| EXPECT_TRUE(presenter_->Resize(monitor_size, 1.0, gfx::ColorSpace(), true)); |
| |
| // Schedule the overlay for root surface. |
| InitializeRootAndScheduleRootSurface(monitor_size, SkColors::kBlack); |
| |
| // Use a 1000x1000 content size for texture or dcomp surface proxy. |
| scoped_refptr<MockDCOMPSurfaceProxy> dcomp_surface_proxy = |
| base::MakeRefCounted<MockDCOMPSurfaceProxy>(); |
| const gfx::Size resource_size(1000, 1000); |
| // Target letterboxed rect after non-uniform scaling is 1920x1080 and centered |
| // in monitor. |
| const int letterboxing_height = 60; |
| const gfx::Rect target_letterboxed_rect(0, letterboxing_height, 1920, 1080); |
| |
| // Test if dcomp surface and its visual info remains the clip size |
| // when letterboxing is generated for full screen presentation. |
| const gfx::Rect quad_rect = gfx::Rect(resource_size); |
| const gfx::Rect clip_rect = target_letterboxed_rect; |
| gfx::Transform quad_to_root_transform = gfx::TransformBetweenRects( |
| gfx::RectF(quad_rect), gfx::RectF(target_letterboxed_rect)); |
| |
| { |
| // Override expected value for `DCOMPSurfaceProxy->SetRect` because |
| // letterboxing optimizations will not be used due to non-uniform scaling. |
| std::optional<gfx::Rect> dcomp_surface_set_rect_override; |
| if (GetTestParam().presentation_mode == |
| SwapChainPresentationMode::kMFDCompSurface) { |
| dcomp_surface_set_rect_override = target_letterboxed_rect; |
| } |
| auto dc_layer_params = CreateParamsFromImage( |
| CreateOverlayImage(resource_size, monitor_size, clip_rect, |
| dcomp_surface_set_rect_override)); |
| dc_layer_params.quad_rect = quad_rect; |
| dc_layer_params.transform = quad_to_root_transform; |
| dc_layer_params.clip_rect = clip_rect; |
| dc_layer_params.video_params.color_space = gfx::ColorSpace::CreateREC709(); |
| dc_layer_params.z_order = 1; |
| dc_layer_params.layer_id = gfx::OverlayLayerId::MakeForTesting(0); |
| ScheduleFullScreenOverlay(std::move(dc_layer_params)); |
| |
| ASSERT_EQ(PresentAndGetSwapResult(), gfx::SwapResult::SWAP_ACK); |
| } |
| |
| if (GetTestParam().use_letterbox_video_optimization) { |
| if (GetTestParam().presentation_mode == |
| SwapChainPresentationMode::kDecodeSwapChain) { |
| EXPECT_TRUE(CheckVideoDisablesDesktopPlane(monitor_size)); |
| } else if (GetTestParam().presentation_mode == |
| SwapChainPresentationMode::kMFDCompSurface) { |
| // In dcomp surface case, letterboxing optimizations won't be used for |
| // non-uniform transform, so desktop plane won't be able to be disabled. |
| EXPECT_FALSE(CheckVideoDisablesDesktopPlane(monitor_size)); |
| } |
| EXPECT_TRUE(CheckVideoIsLetterboxedCorrectly(monitor_size)); |
| } else { |
| EXPECT_FALSE(CheckVideoDisablesDesktopPlane(monitor_size)); |
| EXPECT_TRUE(CheckVideoIsLetterboxedCorrectly(monitor_size)); |
| } |
| } |
| |
| class DCompPresenterFullscreenRoundingTest : public DCompPresenterTestBase<> {}; |
| |
| TEST_P(DCompPresenterFullscreenRoundingTest, |
| FullScreenRoundingWithHalfPixelTranslation) { |
| if (base::FeatureList::IsEnabled( |
| features::kEarlyFullScreenVideoOptimization)) { |
| // This test case is implemented in |
| // `OverlayProcessorWinFullScreenWithAdjustmentTest`. |
| GTEST_SKIP() << "EarlyFullScreenVideoOptimization handles adjustment of " |
| "overlay position during overlay processing."; |
| } |
| |
| // Define 1920x1080 monitor size. |
| const gfx::Size monitor_size(1920, 1080); |
| SetDirectCompositionScaledOverlaysSupportedForTesting(true); |
| SetDirectCompositionMonitorInfoForTesting(1, monitor_size); |
| EXPECT_TRUE(presenter_->Resize(monitor_size, 1.0, gfx::ColorSpace(), true)); |
| |
| // Schedule the overlay for root surface. |
| InitializeRootAndScheduleRootSurface(monitor_size, SkColors::kBlack); |
| |
| // Make a 1920*1080 texture as display input. |
| Microsoft::WRL::ComPtr<ID3D11Device> d3d11_device = |
| GetDirectCompositionD3D11Device(); |
| const gfx::Size texture_size(1920, 1080); |
| Microsoft::WRL::ComPtr<ID3D11Texture2D> texture = |
| CreateNV12Texture(d3d11_device, texture_size); |
| ASSERT_NE(texture, nullptr); |
| |
| // Simulate a half pixel translation in the DCLayerParams |
| const gfx::Rect quad_rect = gfx::Rect(0, 0, 1920, 1080); |
| const gfx::Rect clip_rect = quad_rect; |
| const gfx::Transform quad_to_root_transform( |
| gfx::AxisTransform2d(1, gfx::Vector2dF(0.5, 0.5))); |
| { |
| auto dc_layer_params = |
| CreateParamsFromImage(DCLayerOverlayImage(texture_size, texture)); |
| dc_layer_params.quad_rect = quad_rect; |
| dc_layer_params.transform = quad_to_root_transform; |
| dc_layer_params.clip_rect = clip_rect; |
| dc_layer_params.video_params.color_space = gfx::ColorSpace::CreateREC709(); |
| dc_layer_params.z_order = 1; |
| dc_layer_params.layer_id = gfx::OverlayLayerId::MakeForTesting(0); |
| ScheduleOverlay(std::move(dc_layer_params)); |
| |
| ASSERT_EQ(PresentAndGetSwapResult(), gfx::SwapResult::SWAP_ACK); |
| } |
| |
| // Swap chain size is set to onscreen content size. |
| DXGI_SWAP_CHAIN_DESC1 desc; |
| Microsoft::WRL::ComPtr<IDXGISwapChain1> swap_chain = |
| presenter_->GetLayerSwapChainForTesting( |
| gfx::OverlayLayerId::MakeForTesting(0)); |
| ASSERT_TRUE(swap_chain); |
| EXPECT_HRESULT_SUCCEEDED(swap_chain->GetDesc1(&desc)); |
| |
| EXPECT_EQ(1920u, desc.Width); |
| EXPECT_EQ(1080u, desc.Height); |
| |
| Microsoft::WRL::ComPtr<IDXGIDecodeSwapChain> decode_swap_chain; |
| EXPECT_HRESULT_SUCCEEDED( |
| swap_chain->QueryInterface(IID_PPV_ARGS(&decode_swap_chain))); |
| // The dest size has been set to monitor size. |
| uint32_t dest_width, dest_height; |
| EXPECT_HRESULT_SUCCEEDED( |
| decode_swap_chain->GetDestSize(&dest_width, &dest_height)); |
| |
| EXPECT_EQ(1920u, dest_width); |
| EXPECT_EQ(1080u, dest_height); |
| |
| // The target rect has been set to the onscreen content rect. |
| RECT target_rect; |
| EXPECT_HRESULT_SUCCEEDED(decode_swap_chain->GetTargetRect(&target_rect)); |
| |
| EXPECT_EQ(clip_rect, gfx::Rect(target_rect)); |
| |
| // Ensure translation was removed. |
| gfx::Transform visual_transform; |
| gfx::Point visual_offset; |
| gfx::Rect visual_clip_rect; |
| presenter_->GetSwapChainVisualInfoForTesting( |
| gfx::OverlayLayerId::MakeForTesting(0), &visual_transform, &visual_offset, |
| &visual_clip_rect); |
| DVLOG(1) << "visual_transform" << visual_transform.ToString(); |
| |
| EXPECT_TRUE(visual_transform.IsIdentity()); |
| } |
| |
| // This test attempts to emulate the behavior of |
| // https://codepen.io/OpherV/pen/vYxxbMQ The test site has a 2560x1440 video |
| // which is scaled to 200% width & 200% height, which should result in just the |
| // upper left portion of the frame being shown. When in full screen on a |
| // 1920x1080 monitor the video at 200% scaling should have a swap chain size of |
| // 3840 x 2160 but the clipping rect should match the monitor size of 1920x1080. |
| TEST_P(DCompPresenterFullscreenRoundingTest, FullScreenContentWithClipping) { |
| // Define 1920x1080 monitor size. |
| const gfx::Size monitor_size(1920, 1080); |
| SetDirectCompositionScaledOverlaysSupportedForTesting(true); |
| SetDirectCompositionMonitorInfoForTesting(1, monitor_size); |
| EXPECT_TRUE(presenter_->Resize(monitor_size, 1.0, gfx::ColorSpace(), true)); |
| |
| // Schedule the overlay for root surface. |
| InitializeRootAndScheduleRootSurface(monitor_size, SkColors::kBlack); |
| |
| // Make a 2560*1440 texture as display input. |
| Microsoft::WRL::ComPtr<ID3D11Device> d3d11_device = |
| GetDirectCompositionD3D11Device(); |
| const gfx::Size texture_size(2560, 1440); |
| Microsoft::WRL::ComPtr<ID3D11Texture2D> texture = |
| CreateNV12Texture(d3d11_device, texture_size); |
| ASSERT_NE(texture, nullptr); |
| |
| // Simulate a scaled up visual that will be clipped in half |
| const gfx::Rect quad_rect = gfx::Rect(0, 0, 2560, 1440); |
| const gfx::Rect clip_rect = gfx::Rect(0, 0, 1920, 1080); |
| const gfx::Transform quad_to_root_transform( |
| gfx::AxisTransform2d(1.5, gfx::Vector2dF(0, 0))); |
| { |
| auto dc_layer_params = |
| CreateParamsFromImage(DCLayerOverlayImage(texture_size, texture)); |
| dc_layer_params.quad_rect = quad_rect; |
| dc_layer_params.transform = quad_to_root_transform; |
| dc_layer_params.clip_rect = clip_rect; |
| dc_layer_params.video_params.color_space = gfx::ColorSpace::CreateREC709(); |
| dc_layer_params.z_order = 1; |
| dc_layer_params.layer_id = gfx::OverlayLayerId::MakeForTesting(0); |
| ScheduleOverlay(std::move(dc_layer_params)); |
| |
| ASSERT_EQ(PresentAndGetSwapResult(), gfx::SwapResult::SWAP_ACK); |
| } |
| |
| // Swap chain size is set to onscreen content size. |
| DXGI_SWAP_CHAIN_DESC1 desc; |
| Microsoft::WRL::ComPtr<IDXGISwapChain1> swap_chain = |
| presenter_->GetLayerSwapChainForTesting( |
| gfx::OverlayLayerId::MakeForTesting(0)); |
| ASSERT_TRUE(swap_chain); |
| EXPECT_HRESULT_SUCCEEDED(swap_chain->GetDesc1(&desc)); |
| EXPECT_EQ(3840u, desc.Width); |
| EXPECT_EQ(2160u, desc.Height); |
| |
| Microsoft::WRL::ComPtr<IDXGIDecodeSwapChain> decode_swap_chain; |
| EXPECT_HRESULT_SUCCEEDED( |
| swap_chain->QueryInterface(IID_PPV_ARGS(&decode_swap_chain))); |
| // The dest size has been set to monitor size. |
| uint32_t dest_width, dest_height; |
| EXPECT_HRESULT_SUCCEEDED( |
| decode_swap_chain->GetDestSize(&dest_width, &dest_height)); |
| EXPECT_EQ(3840u, dest_width); |
| EXPECT_EQ(2160u, dest_height); |
| |
| // The target rect has been set to the onscreen content rect. |
| RECT target_rect; |
| EXPECT_HRESULT_SUCCEEDED(decode_swap_chain->GetTargetRect(&target_rect)); |
| EXPECT_EQ(gfx::Rect(target_rect), gfx::Rect(3840, 2160)); |
| |
| // Ensure translation was removed. |
| gfx::Transform visual_transform; |
| gfx::Point visual_offset; |
| gfx::Rect visual_clip_rect; |
| presenter_->GetSwapChainVisualInfoForTesting( |
| gfx::OverlayLayerId::MakeForTesting(0), &visual_transform, &visual_offset, |
| &visual_clip_rect); |
| DVLOG(1) << "visual_transform" << visual_transform.ToString(); |
| EXPECT_EQ(visual_transform.To2dTranslation(), gfx::Vector2dF()); |
| EXPECT_EQ(clip_rect, visual_clip_rect); |
| } |
| |
| INSTANTIATE_TEST_SUITE_P(, |
| DCompPresenterFullscreenRoundingTest, |
| DCompPresenterFullscreenRoundingTest::GetValues(), |
| &DCompPresenterFullscreenRoundingTest::GetParamName); |
| |
| } // namespace gl |