blob: eda0d201742850da7cde0c9ab3808badbd27eb48 [file] [log] [blame]
// 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 "base/containers/flat_set.h"
#include "base/functional/callback_helpers.h"
#include "base/memory/raw_ptr.h"
#include "base/memory/ref_counted_memory.h"
#include "base/memory/weak_ptr.h"
#include "base/run_loop.h"
#include "base/strings/stringprintf.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/gtest/include/gtest/gtest.h"
#include "ui/base/test/skia_gold_pixel_diff.h"
#include "ui/base/win/hidden_window.h"
#include "ui/gfx/buffer_format_util.h"
#include "ui/gfx/frame_data.h"
#include "ui/gfx/geometry/rect_conversions.h"
#include "ui/gfx/geometry/transform.h"
#include "ui/gfx/geometry/vector2d_f.h"
#include "ui/gfx/test/sk_color_eq.h"
#include "ui/gl/dc_layer_overlay_params.h"
#include "ui/gl/direct_composition_child_surface_win.h"
#include "ui/gl/direct_composition_support.h"
#include "ui/gl/direct_composition_surface_win.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/test/gl_test_helper.h"
#include "ui/platform_window/platform_window_delegate.h"
#include "ui/platform_window/win/win_window.h"
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 OnMouseEnter() 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.
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;
}
// The precise colors may differ depending on the video processor, so allow a
// margin for error.
const int kMaxColorChannelDeviation = 10;
// 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 =
gl::GetDirectCompositionDevice();
Microsoft::WRL::ComPtr<IDCompositionSurface> surface;
hr = dcomp_device->CreateSurface(surface_size.width(), surface_size.height(),
DXGI_FORMAT_B8G8R8A8_UNORM,
DXGI_ALPHA_MODE_IGNORE, &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});
Microsoft::WRL::ComPtr<ID3D11Device> d3d11_device =
gl::QueryD3D11DeviceObjectFromANGLE();
Microsoft::WRL::ComPtr<ID3D11DeviceContext> immediate_context;
d3d11_device->GetImmediateContext(&immediate_context);
for (const auto& [draw_rect, color] : rectangles_back_to_front) {
CHECK(gfx::Rect(surface_size).Contains(draw_rect));
Microsoft::WRL::ComPtr<ID3D11Texture2D> update_texture;
RECT rect = draw_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(), color.vec());
hr = surface->EndDraw();
CHECK_EQ(S_OK, hr);
}
return DCLayerOverlayImage(surface_size, surface);
}
} // namespace
class DCompPresenterTest : public testing::Test {
public:
DCompPresenterTest() : parent_window_(ui::GetHiddenWindow()) {}
protected:
void SetUp() override {
// These tests are assumed to run on battery.
fake_power_monitor_source_.SetOnBatteryPower(true);
// Without this, the following check always fails.
display_ = gl::init::InitializeGLNoExtensionsOneOff(
/*init_bindings=*/true, /*gpu_preference=*/gl::GpuPreference::kDefault);
presenter_ = CreateDCompPresenter();
// All bots run on non-blocklisted hardware that supports DComp (>Win7)
ASSERT_TRUE(DirectCompositionSupported());
gl_surface_ = init::CreateOffscreenGLSurface(
gl::GLSurfaceEGL::GetGLDisplayEGL(), gfx::Size());
context_ = CreateGLContext(gl_surface_);
SetDirectCompositionScaledOverlaysSupportedForTesting(false);
SetDirectCompositionOverlayFormatUsedForTesting(DXGI_FORMAT_NV12);
}
void TearDown() override {
context_ = nullptr;
if (presenter_) {
DestroyPresenter(std::move(presenter_));
}
gl::init::ShutdownGL(display_, false);
}
scoped_refptr<DCompPresenter> CreateDCompPresenter() {
DirectCompositionSurfaceWin::Settings settings;
scoped_refptr<DCompPresenter> presenter =
base::MakeRefCounted<DCompPresenter>(
gl::GLSurfaceEGL::GetGLDisplayEGL(),
DCompPresenter::VSyncCallback(), settings);
EXPECT_TRUE(presenter->Initialize());
// 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->window(), parent_window_);
return presenter;
}
scoped_refptr<GLContext> CreateGLContext(scoped_refptr<GLSurface> surface) {
scoped_refptr<GLContext> context =
gl::init::CreateGLContext(nullptr, surface.get(), GLContextAttribs());
EXPECT_TRUE(context->MakeCurrent(surface.get()));
return context;
}
// Wait for |presenter_| to present asynchronously check the swap result.
void PresentAndCheckSwapResult(gfx::SwapResult expected_swap_result) {
base::RunLoop wait_for_present;
presenter_->Present(
base::BindOnce(
[](base::RepeatingClosure quit_closure,
gfx::SwapResult expected_swap_result,
gfx::SwapCompletionResult result) {
EXPECT_EQ(expected_swap_result, result.swap_result);
quit_closure.Run();
},
wait_for_present.QuitClosure(), expected_swap_result),
base::DoNothing(), gfx::FrameData());
wait_for_present.Run();
}
HWND parent_window_;
scoped_refptr<DCompPresenter> presenter_;
scoped_refptr<GLSurface> gl_surface_;
scoped_refptr<GLContext> context_;
base::test::ScopedPowerMonitorTestSource fake_power_monitor_source_;
raw_ptr<GLDisplay> display_ = nullptr;
};
// Ensure that the overlay image isn't presented again unless it changes.
TEST_F(DCompPresenterTest, NoPresentTwice) {
Microsoft::WRL::ComPtr<ID3D11Device> d3d11_device =
QueryD3D11DeviceObjectFromANGLE();
gfx::Size texture_size(50, 50);
Microsoft::WRL::ComPtr<ID3D11Texture2D> texture =
CreateNV12Texture(d3d11_device, texture_size);
ASSERT_NE(texture, nullptr);
{
auto params = std::make_unique<DCLayerOverlayParams>();
params->overlay_image.emplace(texture_size, texture);
params->content_rect = gfx::Rect(texture_size);
params->quad_rect = gfx::Rect(100, 100);
params->color_space = gfx::ColorSpace::CreateREC709();
presenter_->ScheduleDCLayer(std::move(params));
}
Microsoft::WRL::ComPtr<IDXGISwapChain1> swap_chain =
presenter_->GetLayerSwapChainForTesting(0);
ASSERT_FALSE(swap_chain);
PresentAndCheckSwapResult(gfx::SwapResult::SWAP_ACK);
swap_chain = presenter_->GetLayerSwapChainForTesting(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 = std::make_unique<DCLayerOverlayParams>();
params->overlay_image.emplace(texture_size, texture);
params->content_rect = gfx::Rect(texture_size);
params->quad_rect = gfx::Rect(100, 100);
params->color_space = gfx::ColorSpace::CreateREC709();
presenter_->ScheduleDCLayer(std::move(params));
}
PresentAndCheckSwapResult(gfx::SwapResult::SWAP_ACK);
Microsoft::WRL::ComPtr<IDXGISwapChain1> swap_chain2 =
presenter_->GetLayerSwapChainForTesting(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 = std::make_unique<DCLayerOverlayParams>();
params->overlay_image.emplace(texture_size, texture);
params->content_rect = gfx::Rect(texture_size);
params->quad_rect = gfx::Rect(100, 100);
params->color_space = gfx::ColorSpace::CreateREC709();
presenter_->ScheduleDCLayer(std::move(params));
}
PresentAndCheckSwapResult(gfx::SwapResult::SWAP_ACK);
Microsoft::WRL::ComPtr<IDXGISwapChain1> swap_chain3 =
presenter_->GetLayerSwapChainForTesting(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_F(DCompPresenterTest, SwapchainSizeWithScaledOverlays) {
Microsoft::WRL::ComPtr<ID3D11Device> d3d11_device =
QueryD3D11DeviceObjectFromANGLE();
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 = std::make_unique<DCLayerOverlayParams>();
params->overlay_image.emplace(texture_size, texture);
params->content_rect = gfx::Rect(texture_size);
params->quad_rect = quad_rect;
params->color_space = gfx::ColorSpace::CreateREC709();
presenter_->ScheduleDCLayer(std::move(params));
}
PresentAndCheckSwapResult(gfx::SwapResult::SWAP_ACK);
Microsoft::WRL::ComPtr<IDXGISwapChain1> swap_chain =
presenter_->GetLayerSwapChainForTesting(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.
PresentAndCheckSwapResult(gfx::SwapResult::SWAP_ACK);
// The input texture size is bigger than the window size.
quad_rect = gfx::Rect(32, 48);
{
auto params = std::make_unique<DCLayerOverlayParams>();
params->overlay_image.emplace(texture_size, texture);
params->content_rect = gfx::Rect(texture_size);
params->quad_rect = quad_rect;
params->color_space = gfx::ColorSpace::CreateREC709();
presenter_->ScheduleDCLayer(std::move(params));
}
PresentAndCheckSwapResult(gfx::SwapResult::SWAP_ACK);
Microsoft::WRL::ComPtr<IDXGISwapChain1> swap_chain2 =
presenter_->GetLayerSwapChainForTesting(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_F(DCompPresenterTest, SwapchainSizeWithoutScaledOverlays) {
Microsoft::WRL::ComPtr<ID3D11Device> d3d11_device =
QueryD3D11DeviceObjectFromANGLE();
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 = std::make_unique<DCLayerOverlayParams>();
params->overlay_image.emplace(texture_size, texture);
params->content_rect = gfx::Rect(texture_size);
params->quad_rect = quad_rect;
params->color_space = gfx::ColorSpace::CreateREC709();
presenter_->ScheduleDCLayer(std::move(params));
}
PresentAndCheckSwapResult(gfx::SwapResult::SWAP_ACK);
Microsoft::WRL::ComPtr<IDXGISwapChain1> swap_chain =
presenter_->GetLayerSwapChainForTesting(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 = std::make_unique<DCLayerOverlayParams>();
params->overlay_image.emplace(texture_size, texture);
params->content_rect = gfx::Rect(texture_size);
params->quad_rect = quad_rect;
params->color_space = gfx::ColorSpace::CreateREC709();
presenter_->ScheduleDCLayer(std::move(params));
}
PresentAndCheckSwapResult(gfx::SwapResult::SWAP_ACK);
Microsoft::WRL::ComPtr<IDXGISwapChain1> swap_chain2 =
presenter_->GetLayerSwapChainForTesting(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_F(DCompPresenterTest, ProtectedVideos) {
Microsoft::WRL::ComPtr<ID3D11Device> d3d11_device =
QueryD3D11DeviceObjectFromANGLE();
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 = std::make_unique<DCLayerOverlayParams>();
params->overlay_image.emplace(texture_size, texture);
params->quad_rect = gfx::Rect(window_size);
params->content_rect = gfx::Rect(texture_size);
params->color_space = gfx::ColorSpace::CreateREC709();
params->protected_video_type = gfx::ProtectedVideoType::kClear;
presenter_->ScheduleDCLayer(std::move(params));
PresentAndCheckSwapResult(gfx::SwapResult::SWAP_ACK);
Microsoft::WRL::ComPtr<IDXGISwapChain1> swap_chain =
presenter_->GetLayerSwapChainForTesting(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 = std::make_unique<DCLayerOverlayParams>();
params->overlay_image.emplace(texture_size, texture);
params->quad_rect = gfx::Rect(window_size);
params->content_rect = gfx::Rect(texture_size);
params->color_space = gfx::ColorSpace::CreateREC709();
params->protected_video_type = gfx::ProtectedVideoType::kSoftwareProtected;
presenter_->ScheduleDCLayer(std::move(params));
PresentAndCheckSwapResult(gfx::SwapResult::SWAP_ACK);
Microsoft::WRL::ComPtr<IDXGISwapChain1> swap_chain =
presenter_->GetLayerSwapChainForTesting(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
}
class DCompPresenterPixelTest : public DCompPresenterTest {
public:
DCompPresenterPixelTest()
: window_(&platform_delegate_, gfx::Rect(100, 100)) {
parent_window_ = window_.hwnd();
}
protected:
void SetUp() override {
static_cast<ui::PlatformWindow*>(&window_)->Show();
DCompPresenterTest::SetUp();
}
void TearDown() override {
// Test harness times out without DestroyWindow() here.
if (IsWindow(parent_window_))
DestroyWindow(parent_window_);
DCompPresenterTest::TearDown();
}
// DCompPresenter is surfaceless--it's root surface is achieved
// via an overlay the size of the window.
void InitializeRootAndScheduleRootSurface(const gfx::Size& window_size,
SkColor4f initial_color) {
// Schedule the root surface as a normal overlay
std::unique_ptr<DCLayerOverlayParams> params =
std::make_unique<DCLayerOverlayParams>();
params->z_order = 0;
params->quad_rect = gfx::Rect(window_size);
params->content_rect = params->quad_rect;
params->overlay_image = CreateDCompSurface(window_size, initial_color);
EXPECT_TRUE(presenter_->ScheduleDCLayer(std::move(params)));
}
void InitializeForPixelTest(const gfx::Size& window_size,
const gfx::Size& texture_size,
const gfx::Rect& content_rect,
const gfx::Rect& quad_rect) {
EXPECT_TRUE(presenter_->Resize(window_size, 1.0, gfx::ColorSpace(), true));
InitializeRootAndScheduleRootSurface(window_size, SkColors::kBlack);
Microsoft::WRL::ComPtr<ID3D11Device> d3d11_device =
QueryD3D11DeviceObjectFromANGLE();
Microsoft::WRL::ComPtr<ID3D11Texture2D> texture =
CreateNV12Texture(d3d11_device, texture_size);
ASSERT_NE(texture, nullptr);
auto params = std::make_unique<DCLayerOverlayParams>();
params->overlay_image.emplace(texture_size, texture);
params->content_rect = content_rect;
params->quad_rect = quad_rect;
params->color_space = gfx::ColorSpace::CreateREC709();
presenter_->ScheduleDCLayer(std::move(params));
PresentAndCheckSwapResult(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(presenter_->Resize(window_size, 1.0, gfx::ColorSpace(), true));
EXPECT_TRUE(presenter_->SetDrawRectangle(gfx::Rect(window_size)));
InitializeRootAndScheduleRootSurface(window_size, SkColors::kBlack);
auto dc_layer_params = std::make_unique<DCLayerOverlayParams>();
dc_layer_params->content_rect = gfx::Rect(2, 2);
dc_layer_params->overlay_image = CreateDCompSurface(
dc_layer_params->content_rect.size(), 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->color_space = gfx::ColorSpace::CreateSRGB();
dc_layer_params->z_order = 1;
dc_layer_params->nearest_neighbor_filter = true;
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 = 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());
}
presenter_->ScheduleDCLayer(std::move(dc_layer_params));
PresentAndCheckSwapResult(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,
std::unique_ptr<DCLayerOverlayParams> fit_in_hole_overlay) {
EXPECT_TRUE(gfx::Rect(window_size).Contains(root_surface_hole));
EXPECT_TRUE(presenter_->Resize(window_size, 1.0, gfx::ColorSpace(), true));
EXPECT_TRUE(presenter_->SetDrawRectangle(gfx::Rect(window_size)));
auto root_surface = std::make_unique<DCLayerOverlayParams>();
root_surface->quad_rect = gfx::Rect(window_size);
root_surface->content_rect = gfx::Rect(window_size);
root_surface->overlay_image =
CreateDCompSurface(window_size, kRootSurfaceInitialColor,
{{root_surface_hole, kRootSurfaceHiddenColor}});
root_surface->color_space = gfx::ColorSpace::CreateSRGB();
root_surface->z_order = 0;
presenter_->ScheduleDCLayer(std::move(root_surface));
presenter_->ScheduleDCLayer(std::move(fit_in_hole_overlay));
PresentAndCheckSwapResult(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;
}
}
}
}
TestPlatformDelegate platform_delegate_;
ui::WinWindow window_;
};
class DCompPresenterVideoPixelTest : public DCompPresenterPixelTest {
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 =
QueryD3D11DeviceObjectFromANGLE();
gfx::Size texture_size(50, 50);
Microsoft::WRL::ComPtr<ID3D11Texture2D> texture =
CreateNV12Texture(d3d11_device, texture_size);
ASSERT_NE(texture, nullptr);
{
auto params = std::make_unique<DCLayerOverlayParams>();
params->overlay_image.emplace(texture_size, texture);
params->content_rect = gfx::Rect(texture_size);
params->quad_rect = gfx::Rect(texture_size);
params->color_space = color_space;
presenter_->ScheduleDCLayer(std::move(params));
}
PresentAndCheckSwapResult(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 = std::make_unique<DCLayerOverlayParams>();
params->overlay_image.emplace(texture_size, texture);
params->content_rect = gfx::Rect(texture_size);
params->quad_rect = gfx::Rect(window_size);
params->color_space = color_space;
presenter_->ScheduleDCLayer(std::move(params));
}
PresentAndCheckSwapResult(gfx::SwapResult::SWAP_ACK);
Sleep(1000);
if (check_color) {
EXPECT_SKCOLOR_CLOSE(
expected_color,
GLTestHelper::ReadBackWindowPixel(window_.hwnd(), gfx::Point(75, 75)),
kMaxColorChannelDeviation);
}
}
};
TEST_F(DCompPresenterVideoPixelTest, BT601) {
TestVideo(gfx::ColorSpace::CreateREC601(), SkColorSetRGB(0xdb, 0x81, 0xe8),
true);
}
TEST_F(DCompPresenterVideoPixelTest, BT709) {
TestVideo(gfx::ColorSpace::CreateREC709(), SkColorSetRGB(0xe1, 0x90, 0xeb),
true);
}
TEST_F(DCompPresenterVideoPixelTest, SRGB) {
// SRGB doesn't make sense on an NV12 input, but don't crash.
TestVideo(gfx::ColorSpace::CreateSRGB(), SK_ColorTRANSPARENT, false);
}
TEST_F(DCompPresenterVideoPixelTest, SCRGBLinear) {
// SCRGB doesn't make sense on an NV12 input, but don't crash.
TestVideo(gfx::ColorSpace::CreateSRGBLinear(), SK_ColorTRANSPARENT, false);
}
TEST_F(DCompPresenterVideoPixelTest, InvalidColorSpace) {
// Invalid color space should be treated as BT.709
TestVideo(gfx::ColorSpace(), SkColorSetRGB(0xe1, 0x90, 0xeb), true);
}
TEST_F(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 =
QueryD3D11DeviceObjectFromANGLE();
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 = std::make_unique<DCLayerOverlayParams>();
params->overlay_image =
DCLayerOverlayImage(y_size, nv12_pixmap.data(), stride);
params->content_rect = gfx::Rect(y_size);
params->quad_rect = gfx::Rect(window_size);
params->color_space = gfx::ColorSpace::CreateREC709();
presenter_->ScheduleDCLayer(std::move(params));
PresentAndCheckSwapResult(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_F(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);
SkColor expected_color = SkColorSetRGB(0xe1, 0x90, 0xeb);
EXPECT_SKCOLOR_CLOSE(
expected_color,
GLTestHelper::ReadBackWindowPixel(window_.hwnd(), gfx::Point(75, 75)),
kMaxColorChannelDeviation);
}
TEST_F(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);
// 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_F(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));
EXPECT_TRUE(presenter_->SetDrawRectangle(gfx::Rect(window_size)));
InitializeRootAndScheduleRootSurface(window_size, SkColors::kBlack);
Microsoft::WRL::ComPtr<ID3D11Device> d3d11_device =
QueryD3D11DeviceObjectFromANGLE();
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 = std::make_unique<DCLayerOverlayParams>();
params->overlay_image.emplace(texture_size, texture);
params->quad_rect = gfx::Rect(window_size);
params->color_space = gfx::ColorSpace::CreateREC709();
presenter_->ScheduleDCLayer(std::move(params));
PresentAndCheckSwapResult(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_F(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);
Microsoft::WRL::ComPtr<IDXGISwapChain1> swap_chain =
presenter_->GetLayerSwapChainForTesting(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_F(DCompPresenterPixelTest, YUY2SwapChain) {
if (context_ && context_->GetVersionInfo() &&
context_->GetVersionInfo()->driver_vendor.find("AMD") !=
std::string::npos) {
GTEST_SKIP()
<< "CreateSwapChainForCompositionSurfaceHandle fails with YUY2 format "
"on Win10/AMD bot (Radeon RX550). See https://crbug.com/967860.";
}
// 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 YUY2 explicitly.
SetDirectCompositionOverlayFormatUsedForTesting(DXGI_FORMAT_YUY2);
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);
Microsoft::WRL::ComPtr<IDXGISwapChain1> swap_chain =
presenter_->GetLayerSwapChainForTesting(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_F(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);
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_F(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));
EXPECT_TRUE(presenter_->SetDrawRectangle(gfx::Rect(window_size)));
InitializeRootAndScheduleRootSurface(window_size, SkColors::kBlack);
Microsoft::WRL::ComPtr<ID3D11Device> d3d11_device =
QueryD3D11DeviceObjectFromANGLE();
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 = std::make_unique<DCLayerOverlayParams>();
params->overlay_image.emplace(texture_size, texture);
params->content_rect = gfx::Rect(texture_size);
params->quad_rect = gfx::Rect(window_size);
params->color_space = gfx::ColorSpace::CreateREC709();
presenter_->ScheduleDCLayer(std::move(params));
PresentAndCheckSwapResult(gfx::SwapResult::SWAP_ACK);
}
Microsoft::WRL::ComPtr<IDXGISwapChain1> swap_chain =
presenter_->GetLayerSwapChainForTesting(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 = std::make_unique<DCLayerOverlayParams>();
params->overlay_image.emplace(texture_size, texture);
params->content_rect = gfx::Rect(30, 30);
params->quad_rect = gfx::Rect(window_size);
params->color_space = gfx::ColorSpace::CreateREC709();
presenter_->ScheduleDCLayer(std::move(params));
PresentAndCheckSwapResult(gfx::SwapResult::SWAP_ACK);
}
swap_chain = presenter_->GetLayerSwapChainForTesting(0);
EXPECT_HRESULT_SUCCEEDED(swap_chain->GetDesc1(&desc));
// Onscreen window_size is (100, 100).
EXPECT_EQ(100u, desc.Width);
EXPECT_EQ(100u, desc.Height);
// (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 = std::make_unique<DCLayerOverlayParams>();
params->overlay_image.emplace(texture_size, texture);
params->content_rect = gfx::Rect(50, 50);
params->quad_rect = on_screen_rect;
params->clip_rect = on_screen_rect;
params->color_space = gfx::ColorSpace::CreateREC709();
presenter_->ScheduleDCLayer(std::move(params));
PresentAndCheckSwapResult(gfx::SwapResult::SWAP_ACK);
}
// Swap chain is set to monitor/onscreen size.
swap_chain = presenter_->GetLayerSwapChainForTesting(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(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 = std::make_unique<DCLayerOverlayParams>();
params->overlay_image.emplace(texture_size, texture);
params->content_rect = gfx::Rect(50, 50);
params->quad_rect = on_screen_rect;
params->color_space = gfx::ColorSpace::CreateREC709();
presenter_->ScheduleDCLayer(std::move(params));
PresentAndCheckSwapResult(gfx::SwapResult::SWAP_ACK);
}
// Swap chain is set to monitor size (100, 100).
swap_chain = presenter_->GetLayerSwapChainForTesting(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(0, &transform, &offset,
&clip_rect);
EXPECT_EQ(gfx::Rect(monitor_size), transform.MapRect(gfx::Rect(100, 100)));
}
TEST_F(DCompPresenterPixelTest, SwapChainImage) {
if (context_ && context_->GetVersionInfo() &&
context_->GetVersionInfo()->driver_vendor.find("AMD") !=
std::string::npos) {
GTEST_SKIP() << "Fails on AMD RX 5500 XT. https://crbug.com/1152565.";
}
Microsoft::WRL::ComPtr<ID3D11Device> d3d11_device =
QueryD3D11DeviceObjectFromANGLE();
ASSERT_TRUE(d3d11_device);
Microsoft::WRL::ComPtr<IDXGIDevice> dxgi_device;
d3d11_device.As(&dxgi_device);
ASSERT_TRUE(dxgi_device);
Microsoft::WRL::ComPtr<IDXGIAdapter> dxgi_adapter;
dxgi_device->GetAdapter(&dxgi_adapter);
ASSERT_TRUE(dxgi_adapter);
Microsoft::WRL::ComPtr<IDXGIFactory2> dxgi_factory;
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_chain;
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);
Microsoft::WRL::ComPtr<ID3D11RenderTargetView> rtv;
ASSERT_HRESULT_SUCCEEDED(d3d11_device->CreateRenderTargetView(
back_buffer_texture.Get(), nullptr, &rtv));
ASSERT_TRUE(rtv);
Microsoft::WRL::ComPtr<ID3D11DeviceContext> context;
d3d11_device->GetImmediateContext(&context);
ASSERT_TRUE(context);
gfx::Size window_size(100, 100);
EXPECT_TRUE(presenter_->Resize(window_size, 1.0, gfx::ColorSpace(), true));
EXPECT_TRUE(presenter_->SetDrawRectangle(gfx::Rect(window_size)));
InitializeRootAndScheduleRootSurface(window_size, SkColors::kBlack);
DXGI_PRESENT_PARAMETERS present_params = {};
present_params.DirtyRectsCount = 0;
present_params.pDirtyRects = nullptr;
// Clear to red and present.
{
float clear_color[] = {1.0, 0.0, 0.0, 1.0};
context->ClearRenderTargetView(rtv.Get(), clear_color);
ASSERT_HRESULT_SUCCEEDED(swap_chain->Present1(0, 0, &present_params));
auto dc_layer_params = std::make_unique<DCLayerOverlayParams>();
dc_layer_params->overlay_image =
DCLayerOverlayImage(swap_chain_size, swap_chain);
dc_layer_params->content_rect = gfx::Rect(swap_chain_size);
dc_layer_params->quad_rect = gfx::Rect(window_size);
dc_layer_params->color_space = gfx::ColorSpace::CreateSRGB();
dc_layer_params->z_order = 1;
presenter_->ScheduleDCLayer(std::move(dc_layer_params));
PresentAndCheckSwapResult(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.
{
float clear_color[] = {0.0, 1.0, 0.0, 1.0};
context->ClearRenderTargetView(rtv.Get(), clear_color);
ASSERT_HRESULT_SUCCEEDED(swap_chain->Present1(0, 0, &present_params));
auto dc_layer_params = std::make_unique<DCLayerOverlayParams>();
dc_layer_params->overlay_image =
DCLayerOverlayImage(swap_chain_size, swap_chain);
dc_layer_params->content_rect = gfx::Rect(swap_chain_size);
dc_layer_params->quad_rect = gfx::Rect(window_size);
dc_layer_params->color_space = gfx::ColorSpace::CreateSRGB();
presenter_->ScheduleDCLayer(std::move(dc_layer_params));
PresentAndCheckSwapResult(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.
{
ASSERT_HRESULT_SUCCEEDED(swap_chain->Present1(0, 0, &present_params));
auto dc_layer_params = std::make_unique<DCLayerOverlayParams>();
dc_layer_params->overlay_image =
DCLayerOverlayImage(swap_chain_size, swap_chain);
dc_layer_params->content_rect = gfx::Rect(swap_chain_size);
dc_layer_params->quad_rect = gfx::Rect(window_size);
dc_layer_params->color_space = gfx::ColorSpace::CreateSRGB();
presenter_->ScheduleDCLayer(std::move(dc_layer_params));
PresentAndCheckSwapResult(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};
context->ClearRenderTargetView(rtv.Get(), clear_color);
auto dc_layer_params = std::make_unique<DCLayerOverlayParams>();
dc_layer_params->overlay_image =
DCLayerOverlayImage(swap_chain_size, swap_chain);
dc_layer_params->content_rect = gfx::Rect(swap_chain_size);
dc_layer_params->quad_rect = gfx::Rect(window_size);
dc_layer_params->color_space = gfx::ColorSpace::CreateSRGB();
presenter_->ScheduleDCLayer(std::move(dc_layer_params));
PresentAndCheckSwapResult(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_F(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));
EXPECT_TRUE(presenter_->SetDrawRectangle(gfx::Rect(window_size)));
InitializeRootAndScheduleRootSurface(window_size, SkColors::kBlack);
auto dc_layer_params = std::make_unique<DCLayerOverlayParams>();
dc_layer_params->overlay_image =
CreateDCompSurface(quad_rect.size(), SkColors::kRed);
dc_layer_params->content_rect = gfx::Rect(quad_rect.size());
dc_layer_params->quad_rect = quad_rect;
dc_layer_params->transform = quad_to_root_transform;
dc_layer_params->color_space = gfx::ColorSpace::CreateSRGB();
dc_layer_params->z_order = 1;
presenter_->ScheduleDCLayer(std::move(dc_layer_params));
PresentAndCheckSwapResult(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_F(DCompPresenterPixelTest, NearestNeighborFilteringScaleViaBuffer) {
RunNearestNeighborTest(true);
}
// Test that scaling a (very) small texture up works with nearest neighbor
// filtering using the overlay's transform.
TEST_F(DCompPresenterPixelTest, NearestNeighborFilteringScaleViaTransform) {
RunNearestNeighborTest(false);
}
// Test that the |content_rect| of an overlay scales the buffer to fit the
// display rect, if needed.
TEST_F(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 = std::make_unique<DCLayerOverlayParams>();
overlay->content_rect = gfx::Rect(1, 1);
overlay->quad_rect = root_surface_hole;
overlay->overlay_image =
CreateDCompSurface(overlay->content_rect.size(), kOverlayExpectedColor);
overlay->color_space = gfx::ColorSpace::CreateSRGB();
overlay->z_order = 1;
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_F(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 = std::make_unique<DCLayerOverlayParams>();
overlay->content_rect = gfx::Rect(75, 100);
overlay->quad_rect = root_surface_hole;
overlay->overlay_image =
CreateDCompSurface(overlay->content_rect.size(), kOverlayExpectedColor);
overlay->color_space = gfx::ColorSpace::CreateSRGB();
overlay->z_order = 1;
CheckOverlayExactlyFillsHole(window_size, root_surface_hole,
std::move(overlay));
}
// Test that the |content_rect| of an overlay clips portions of the buffer.
TEST_F(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 = std::make_unique<DCLayerOverlayParams>();
overlay->content_rect = tex_coord;
overlay->quad_rect = root_surface_hole;
overlay->overlay_image =
CreateDCompSurface(window_size, kOverlayImageHiddenColor,
{{tex_coord, kOverlayExpectedColor}});
overlay->color_space = gfx::ColorSpace::CreateSRGB();
overlay->z_order = 1;
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_F(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 = std::make_unique<DCLayerOverlayParams>();
overlay->content_rect = tex_coord;
overlay->quad_rect = root_surface_hole;
overlay->overlay_image =
CreateDCompSurface(window_size, kOverlayImageHiddenColor,
{{tex_coord, kOverlayExpectedColor}});
overlay->color_space = gfx::ColorSpace::CreateSRGB();
overlay->z_order = 1;
// 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));
}
class DCompPresenterSkiaGoldTest : public DCompPresenterPixelTest {
protected:
void SetUp() override {
DCompPresenterPixelTest::SetUp();
// |pixel_diff_| only needs to be initialized once per suite, but depends on
// | DCompPresenterTest::SetUp()| so is initialized here.
if (!pixel_diff_.has_value()) {
pixel_diff_.emplace();
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_->Init(::testing::UnitTest::GetInstance()
->current_test_info()
->test_suite_name(),
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();
}
void ResizeWindow(const gfx::Size& window_size) {
EXPECT_TRUE(presenter_->Resize(window_size, 1.0, gfx::ColorSpace(), true));
EXPECT_TRUE(presenter_->SetDrawRectangle(gfx::Rect(window_size)));
window_size_ = window_size;
}
// |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);
PresentAndCheckSwapResult(gfx::SwapResult::SWAP_ACK);
std::string screenshot_name =
::testing::UnitTest::GetInstance()->current_test_info()->name();
if (!capture_name.empty()) {
screenshot_name = base::StringPrintf("%s/%s", screenshot_name.c_str(),
capture_name.c_str());
}
SkBitmap window_readback =
GLTestHelper::ReadBackWindow(window_.hwnd(), window_size_);
ASSERT_TRUE(pixel_diff_);
if (!pixel_diff_->CompareScreenshot(screenshot_name, window_readback)) {
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_; }
private:
absl::optional<ui::test::SkiaGoldPixelDiff> pixel_diff_;
// |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_;
};
TEST_F(DCompPresenterSkiaGoldTest, NonAxisPerservingTransform) {
InitializeTest(gfx::Size(100, 100));
InitializeRootAndScheduleRootSurface(current_window_size(), SkColors::kBlack);
auto overlay = std::make_unique<DCLayerOverlayParams>();
overlay->content_rect = gfx::Rect(50, 50);
overlay->quad_rect = gfx::Rect(50, 50);
overlay->overlay_image =
CreateDCompSurface(gfx::Size(50, 50), SkColors::kRed);
overlay->z_order = 1;
// Center and partially rotate the overlay
overlay->transform.Translate(50, 50);
overlay->transform.Rotate(15);
overlay->transform.Translate(-25, -25);
EXPECT_TRUE(presenter_->ScheduleDCLayer(std::move(overlay)));
PresentAndCheckScreenshot();
}
class DCompPresenterBufferCountTest : public DCompPresenterTest,
public testing::WithParamInterface<bool> {
public:
static const char* GetParamName(
const testing::TestParamInfo<ParamType>& info) {
return info.param ? "DCompTripleBufferVideoSwapChain" : "default";
}
protected:
void SetUp() override {
if (GetParam()) {
enabled_features_.InitWithFeatures(
{features::kDCompTripleBufferVideoSwapChain}, {});
} else {
enabled_features_.InitWithFeatures(
{}, {features::kDCompTripleBufferVideoSwapChain});
}
DCompPresenterTest::SetUp();
}
base::test::ScopedFeatureList enabled_features_;
};
TEST_P(DCompPresenterBufferCountTest, VideoSwapChainBufferCount) {
SetDirectCompositionScaledOverlaysSupportedForTesting(true);
constexpr gfx::Size window_size(100, 100);
EXPECT_TRUE(presenter_->Resize(window_size, 1.0, gfx::ColorSpace(), true));
EXPECT_TRUE(presenter_->SetDrawRectangle(gfx::Rect(window_size)));
constexpr gfx::Size texture_size(50, 50);
Microsoft::WRL::ComPtr<ID3D11Device> d3d11_device =
QueryD3D11DeviceObjectFromANGLE();
ASSERT_TRUE(d3d11_device);
Microsoft::WRL::ComPtr<ID3D11Texture2D> texture =
CreateNV12Texture(d3d11_device, texture_size);
ASSERT_NE(texture, nullptr);
auto params = std::make_unique<DCLayerOverlayParams>();
params->overlay_image.emplace(texture_size, texture);
params->content_rect = gfx::Rect(texture_size);
params->quad_rect = gfx::Rect(window_size);
params->color_space = gfx::ColorSpace::CreateREC709();
EXPECT_TRUE(presenter_->ScheduleDCLayer(std::move(params)));
PresentAndCheckSwapResult(gfx::SwapResult::SWAP_ACK);
auto swap_chain = presenter_->GetLayerSwapChainForTesting(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 (GetParam()) {
EXPECT_EQ(3u, desc.BufferCount);
} else {
EXPECT_EQ(2u, desc.BufferCount);
}
}
INSTANTIATE_TEST_SUITE_P(All,
DCompPresenterBufferCountTest,
testing::Bool(),
&DCompPresenterBufferCountTest::GetParamName);
} // namespace gl