// 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 "base/containers/flat_set.h"
#include "base/containers/span.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/string_number_conversions.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_matching_algorithm.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/size.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/dc_layer_tree.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/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;
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 =
gl::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.
std::unique_ptr<DCLayerOverlayParams> CreateParamsFromImage(
DCLayerOverlayImage image,
std::optional<gfx::RectF> content_rect_override = {}) {
auto params = std::make_unique<DCLayerOverlayParams>();
params->content_rect =
content_rect_override.value_or(gfx::RectF(image.size()));
params->overlay_image = std::move(image);
return params;
}
} // namespace
class DCompPresenterTestBase : public testing::Test {
public:
DCompPresenterTestBase() : parent_window_(ui::GetHiddenWindow()) {}
protected:
void SetUp() override {
enabled_features_.InitWithFeatures(enabled_features_list_,
disabled_features_list_);
display_ = gl::init::InitializeGLNoExtensionsOneOff(
/*init_bindings=*/true, /*gpu_preference=*/gl::GpuPreference::kDefault);
std::tie(gl_surface_, context_) =
GLTestHelper::CreateOffscreenGLSurfaceAndContext();
// These tests are assumed to run on battery.
fake_power_monitor_source_.SetOnBatteryPower(true);
// All bots run on non-blocklisted hardware that supports DComp (>Win7)
ASSERT_TRUE(DirectCompositionSupported());
presenter_ = CreateDCompPresenter();
SetDirectCompositionScaledOverlaysSupportedForTesting(false);
SetDirectCompositionOverlayFormatUsedForTesting(DXGI_FORMAT_NV12);
}
void TearDown() override {
if (presenter_) {
DestroyPresenter(std::move(presenter_));
}
context_.reset();
gl_surface_.reset();
gl::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;
}
// 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);
presenter_->ScheduleDCLayer(std::move(params));
}
// 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();
}
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_;
};
class DCompPresenterTest : public DCompPresenterTestBase,
public testing::WithParamInterface<bool> {
public:
void SetUp() override {
if (GetParam()) {
EnableFeature(features::kGpuVsync);
} else {
DisableFeature(features::kGpuVsync);
}
DCompPresenterTestBase::SetUp();
}
};
// 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->video_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 =
CreateParamsFromImage(DCLayerOverlayImage(texture_size, texture));
params->quad_rect = gfx::Rect(100, 100);
params->video_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 =
CreateParamsFromImage(DCLayerOverlayImage(texture_size, texture));
params->quad_rect = gfx::Rect(100, 100);
params->video_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_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->video_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 =
CreateParamsFromImage(DCLayerOverlayImage(texture_size, texture));
params->quad_rect = quad_rect;
params->video_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_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->video_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 =
CreateParamsFromImage(DCLayerOverlayImage(texture_size, texture));
params->quad_rect = quad_rect;
params->video_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_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->video_params.color_space = gfx::ColorSpace::CreateREC709();
params->video_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 =
CreateParamsFromImage(DCLayerOverlayImage(texture_size, texture));
params->quad_rect = gfx::Rect(window_size);
params->video_params.color_space = gfx::ColorSpace::CreateREC709();
params->video_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
}
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;
presenter_->ScheduleDCLayer(std::move(root_surface));
PresentAndCheckSwapResult(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++) {
auto params = std::make_unique<DCLayerOverlayParams>();
params->quad_rect = gfx::Rect(window_size);
params->background_color = SkColor4f::FromColor(SkColorSetRGB(i, 0, 0));
params->z_order = i + 1;
presenter_->ScheduleDCLayer(std::move(params));
}
PresentAndCheckSwapResult(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.
{
PresentAndCheckSwapResult(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++) {
auto params = std::make_unique<DCLayerOverlayParams>();
params->quad_rect = gfx::Rect(window_size);
params->background_color = colors[i];
params->z_order = i + 1;
presenter_->ScheduleDCLayer(std::move(params));
}
PresentAndCheckSwapResult(gfx::SwapResult::SWAP_ACK);
EXPECT_EQ(2u, layer_tree->GetNumSurfacesInPoolForTesting());
surfaces_frame1[0] = layer_tree->GetBackgroundColorSurfaceForTesting(0);
surfaces_frame1[1] = layer_tree->GetBackgroundColorSurfaceForTesting(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++) {
auto params = std::make_unique<DCLayerOverlayParams>();
params->quad_rect = gfx::Rect(window_size);
params->background_color = colors[i];
params->z_order = i + 1;
presenter_->ScheduleDCLayer(std::move(params));
}
PresentAndCheckSwapResult(gfx::SwapResult::SWAP_ACK);
EXPECT_EQ(2u, layer_tree->GetNumSurfacesInPoolForTesting());
surfaces_frame2[0] = layer_tree->GetBackgroundColorSurfaceForTesting(0);
surfaces_frame2[1] = layer_tree->GetBackgroundColorSurfaceForTesting(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({}));
// 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);
{
auto params = std::make_unique<DCLayerOverlayParams>();
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;
presenter_->ScheduleDCLayer(std::move(params));
}
PresentAndCheckSwapResult(gfx::SwapResult::SWAP_ACK);
DCLayerTree* dcLayerTree = presenter_->GetLayerTreeForTesting();
EXPECT_EQ(2u, dcLayerTree->GetDcompLayerCountForTesting());
Microsoft::WRL::ComPtr<IDCompositionVisual2> visual0 =
dcLayerTree->GetContentVisualForTesting(0);
Microsoft::WRL::ComPtr<IDCompositionVisual2> visual1 =
dcLayerTree->GetContentVisualForTesting(1);
// Frame 2:
// overlay 0: root dcomp surface
// overlay 1: swapchain z-order = -1 (underlay)
InitializeRootAndScheduleRootSurface(window_size, SkColors::kBlue);
{
auto params = std::make_unique<DCLayerOverlayParams>();
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;
presenter_->ScheduleDCLayer(std::move(params));
}
PresentAndCheckSwapResult(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(1));
EXPECT_EQ(visual1.Get(), dcLayerTree->GetContentVisualForTesting(0));
#if DCHECK_IS_ON()
EXPECT_TRUE(dcLayerTree->GetAttachedToRootFromPreviousFrameForTesting(0));
EXPECT_FALSE(dcLayerTree->GetAttachedToRootFromPreviousFrameForTesting(1));
#endif // DCHECK_IS_ON()
}
void ScheduleDCLayer(scoped_refptr<gl::Presenter> presenter,
Microsoft::WRL::ComPtr<IDXGISwapChain1> swap_chain,
const gfx::Size& swap_chain_size,
int z_order) {
auto params = std::make_unique<DCLayerOverlayParams>();
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;
presenter->ScheduleDCLayer(std::move(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) {
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.";
}
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
ScheduleDCLayer(presenter_, swap_chainA, swap_chain_size, 1);
ScheduleDCLayer(presenter_, swap_chainB, swap_chain_size, 2);
ScheduleDCLayer(presenter_, swap_chainC, swap_chain_size, 3);
ScheduleDCLayer(presenter_, swap_chainD, swap_chain_size, 4);
ScheduleDCLayer(presenter_, swap_chainE, swap_chain_size, 5);
ScheduleDCLayer(presenter_, swap_chainF, swap_chain_size, 6);
PresentAndCheckSwapResult(gfx::SwapResult::SWAP_ACK);
DCLayerTree* dc_layer_tree = presenter_->GetLayerTreeForTesting();
EXPECT_EQ(7u, dc_layer_tree->GetDcompLayerCountForTesting());
Microsoft::WRL::ComPtr<IDCompositionVisual2> visualRS =
dc_layer_tree->GetContentVisualForTesting(0);
EXPECT_NE(visualRS, nullptr);
Microsoft::WRL::ComPtr<IDCompositionVisual2> visualA =
dc_layer_tree->GetContentVisualForTesting(1);
EXPECT_NE(visualA, nullptr);
Microsoft::WRL::ComPtr<IDCompositionVisual2> visualB =
dc_layer_tree->GetContentVisualForTesting(2);
EXPECT_NE(visualB, nullptr);
Microsoft::WRL::ComPtr<IDCompositionVisual2> visualC =
dc_layer_tree->GetContentVisualForTesting(3);
EXPECT_NE(visualC, nullptr);
Microsoft::WRL::ComPtr<IDCompositionVisual2> visualD =
dc_layer_tree->GetContentVisualForTesting(4);
EXPECT_NE(visualD, nullptr);
Microsoft::WRL::ComPtr<IDCompositionVisual2> visualE =
dc_layer_tree->GetContentVisualForTesting(5);
EXPECT_NE(visualE, nullptr);
Microsoft::WRL::ComPtr<IDCompositionVisual2> visualF =
dc_layer_tree->GetContentVisualForTesting(6);
EXPECT_NE(visualF, nullptr);
// Frame 2: RootSurface, A L D C M
InitializeRootAndScheduleRootSurface(window_size, SkColors::kBlue);
ScheduleDCLayer(presenter_, swap_chainA, swap_chain_size, 1);
ScheduleDCLayer(presenter_, swap_chainL, swap_chain_size, 2);
ScheduleDCLayer(presenter_, swap_chainD, swap_chain_size, 3);
ScheduleDCLayer(presenter_, swap_chainC, swap_chain_size, 4);
ScheduleDCLayer(presenter_, swap_chainM, swap_chain_size, 5);
PresentAndCheckSwapResult(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(0) /*RS*/);
EXPECT_EQ(visualA.Get(), dc_layer_tree->GetContentVisualForTesting(1) /*A*/);
EXPECT_EQ(visualB.Get(), dc_layer_tree->GetContentVisualForTesting(2) /*L*/);
EXPECT_EQ(visualD.Get(), dc_layer_tree->GetContentVisualForTesting(3) /*D*/);
EXPECT_EQ(visualC.Get(), dc_layer_tree->GetContentVisualForTesting(4) /*C*/);
EXPECT_EQ(visualE.Get(), dc_layer_tree->GetContentVisualForTesting(5) /*M*/);
#if DCHECK_IS_ON()
EXPECT_TRUE(dc_layer_tree->GetAttachedToRootFromPreviousFrameForTesting(0));
EXPECT_TRUE(dc_layer_tree->GetAttachedToRootFromPreviousFrameForTesting(1));
EXPECT_TRUE(dc_layer_tree->GetAttachedToRootFromPreviousFrameForTesting(2));
EXPECT_TRUE(dc_layer_tree->GetAttachedToRootFromPreviousFrameForTesting(3));
EXPECT_FALSE(dc_layer_tree->GetAttachedToRootFromPreviousFrameForTesting(4));
EXPECT_TRUE(dc_layer_tree->GetAttachedToRootFromPreviousFrameForTesting(5));
#endif // DCHECK_IS_ON()
}
INSTANTIATE_TEST_SUITE_P(DCompPresenterTest,
DCompPresenterTest,
testing::Bool());
class DCompPresenterPixelTestBase : public DCompPresenterTestBase {
public:
DCompPresenterPixelTestBase()
: window_(&platform_delegate_, gfx::Rect(100, 100)) {
parent_window_ = window_.hwnd();
}
protected:
void SetUp() override {
static_cast<ui::PlatformWindow*>(&window_)->Show();
DCompPresenterTestBase::SetUp();
}
void TearDown() override {
// Test harness times out without DestroyWindow() here.
if (IsWindow(parent_window_)) {
DestroyWindow(parent_window_);
}
DCompPresenterTestBase::TearDown();
}
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 =
GetDirectCompositionD3D11Device();
Microsoft::WRL::ComPtr<ID3D11Texture2D> texture =
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->video_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));
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;
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());
}
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));
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;
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;
}
}
}
}
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 DCompPresenterPixelTest : public DCompPresenterPixelTestBase,
public testing::WithParamInterface<bool> {
protected:
void SetUp() override {
if (GetParam()) {
DCompPresenterTestBase::EnableFeature(features::kGpuVsync);
} else {
DCompPresenterTestBase::DisableFeature(features::kGpuVsync);
}
static_cast<ui::PlatformWindow*>(&window_)->Show();
DCompPresenterPixelTestBase::SetUp();
}
};
class DCompPresenterVideoPixelTest : public DCompPresenterPixelTestBase,
public testing::WithParamInterface<bool> {
protected:
void SetUp() override {
if (GetParam()) {
DCompPresenterTestBase::EnableFeature(features::kGpuVsync);
} else {
DCompPresenterTestBase::DisableFeature(features::kGpuVsync);
}
DCompPresenterPixelTestBase::SetUp();
}
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;
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 =
CreateParamsFromImage(DCLayerOverlayImage(texture_size, texture));
params->quad_rect = gfx::Rect(window_size);
params->video_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);
}
}
};
INSTANTIATE_TEST_SUITE_P(DCompPresenterVideoPixelTest,
DCompPresenterVideoPixelTest,
testing::Bool());
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);
}
INSTANTIATE_TEST_SUITE_P(DCompPresenterPixelTest,
DCompPresenterPixelTest,
testing::Bool());
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, nv12_pixmap.data(), stride));
params->quad_rect = gfx::Rect(window_size);
params->video_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_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);
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);
// 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();
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_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);
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_P(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_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);
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->video_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 =
CreateParamsFromImage(DCLayerOverlayImage(texture_size, texture),
/*content_rect_override=*/gfx::RectF(30, 30));
params->quad_rect = gfx::Rect(window_size);
params->video_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 =
CreateParamsFromImage(DCLayerOverlayImage(texture_size, texture));
params->quad_rect = on_screen_rect;
params->clip_rect = on_screen_rect;
params->video_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 =
CreateParamsFromImage(DCLayerOverlayImage(texture_size, texture));
params->quad_rect = on_screen_rect;
params->video_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_P(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.";
}
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;
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.
{
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);
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.
{
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);
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};
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);
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_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;
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_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;
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;
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;
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;
// 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) {
auto params = std::make_unique<DCLayerOverlayParams>();
params->quad_rect = gfx::Rect(window_size);
params->background_color = color;
params->z_order = 1;
presenter_->ScheduleDCLayer(std::move(params));
PresentAndCheckSwapResult(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(0);
}
EXPECT_NE(background_color_surface, nullptr);
EXPECT_EQ(background_color_surface,
layer_tree->GetBackgroundColorSurfaceForTesting(0))
<< "DComp content for solid color overlay expected to be reused across "
"frames";
}
}
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);
PresentAndCheckSwapResult(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<
std::unique_ptr<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;
presenter_->ScheduleDCLayer(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,
testing::Bool());
// 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->transform.Translate(25, 25);
presenter_->ScheduleDCLayer(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->transform.Translate(50, 50);
overlay->transform.Scale(1.2);
overlay->transform.Translate(-25, -25);
presenter_->ScheduleDCLayer(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;
// Center and partially rotate the overlay
overlay->transform.Translate(50, 50);
overlay->transform.Rotate(15);
overlay->transform.Translate(-25, -25);
presenter_->ScheduleDCLayer(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);
auto overlay = std::make_unique<DCLayerOverlayParams>();
overlay->quad_rect = gfx::Rect(120, 75);
overlay->background_color = SkColors::kGreen;
overlay->z_order = 1;
overlay->transform.Translate(50, 50);
overlay->transform.ApplyPerspectiveDepth(100);
overlay->transform.RotateAboutYAxis(45);
overlay->transform.RotateAboutXAxis(30);
overlay->transform.Translate(-25, -25);
presenter_->ScheduleDCLayer(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->transform.Translate(50, 50);
overlay->transform.Skew(15, 30);
overlay->transform.Translate(-25, -25);
presenter_->ScheduleDCLayer(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];
auto overlay = std::make_unique<DCLayerOverlayParams>();
overlay->quad_rect = bounds;
overlay->background_color = std::optional<SkColor4f>(color);
overlay->z_order = i + 1;
presenter_->ScheduleDCLayer(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;
auto overlay = std::make_unique<DCLayerOverlayParams>();
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 =
gl::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;
presenter_->ScheduleDCLayer(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->rounded_corner_bounds =
gfx::RRectF(gfx::RectF(overlay->quad_rect), 25.f);
presenter_->ScheduleDCLayer(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;
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;
presenter_->ScheduleDCLayer(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) {
auto overlay = std::make_unique<DCLayerOverlayParams>();
overlay->quad_rect = quad;
overlay->background_color = std::optional<SkColor4f>(SkColors::kWhite);
overlay->z_order = overlay_z_order;
overlay->rounded_corner_bounds = bounds;
presenter_->ScheduleDCLayer(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;
presenter_->ScheduleDCLayer(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;
presenter_->ScheduleDCLayer(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;
presenter_->ScheduleDCLayer(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;
presenter_->ScheduleDCLayer(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;
presenter_->ScheduleDCLayer(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;
presenter_->ScheduleDCLayer(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;
presenter_->ScheduleDCLayer(std::move(overlay));
PresentAndCheckScreenshot();
}
void RunSeamsWithComplexTransformTest(
DCompPresenter* presenter,
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);
const gfx::Size tile_size = gfx::Size(25, 25);
for (int y = 0; y < 4; y++) {
for (int x = 0; x < 4; x++) {
auto overlay = std::make_unique<DCLayerOverlayParams>();
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.get());
presenter->ScheduleDCLayer(std::move(overlay));
}
}
}
// 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));
RunSeamsWithComplexTransformTest(
presenter_.get(),
base::BindRepeating([](int x, int y, DCLayerOverlayParams& overlay) {
// All on the same layer.
overlay.aggregated_layer_id = 1;
}));
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));
RunSeamsWithComplexTransformTest(
presenter_.get(),
base::BindRepeating([](int x, int y, DCLayerOverlayParams& overlay) {
// Reuse layer IDs but have no two adjacent overlays have the same ID.
overlay.aggregated_layer_id = (x + y * 4) % 2 + 1;
}));
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,
const float diameter,
base::span<gfx::DelegatedInkPoint const> const 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,
testing::Bool());
// 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;
presenter_->ScheduleDCLayer(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);
presenter_->ScheduleDCLayer(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;
presenter_->ScheduleDCLayer(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");
}
class DCompPresenterBufferCountTest
: public DCompPresenterTestBase,
public testing::WithParamInterface<std::tuple<bool, bool>> {
public:
static std::string GetParamName(
const testing::TestParamInfo<ParamType>& info) {
return base::StringPrintf(
"%s_%s",
std::get<0>(info.param) ? "DCompTripleBufferVideoSwapChain"
: "DcompTripleBufferVideoSwapChain_default",
std::get<1>(info.param) ? "UseGpuVsync_On" : "UseGpuVsync_off");
}
protected:
void SetUp() override {
if (std::get<0>(GetParam())) {
DCompPresenterTestBase::EnableFeature(
features::kDCompTripleBufferVideoSwapChain);
} else {
DCompPresenterTestBase::DisableFeature(
features::kDCompTripleBufferVideoSwapChain);
}
if (std::get<1>(GetParam())) {
DCompPresenterTestBase::EnableFeature(features::kGpuVsync);
} else {
DCompPresenterTestBase::DisableFeature(features::kGpuVsync);
}
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->video_params.color_space = gfx::ColorSpace::CreateREC709();
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 (std::get<0>(GetParam())) {
EXPECT_EQ(3u, desc.BufferCount);
} else {
EXPECT_EQ(2u, desc.BufferCount);
}
}
INSTANTIATE_TEST_SUITE_P(All,
DCompPresenterBufferCountTest,
testing::Combine(testing::Bool(), testing::Bool()),
&DCompPresenterBufferCountTest::GetParamName);
struct LetterboxingTestParams {
LetterboxingTestParams(bool use_letterbox_video_optimization)
: use_letterbox_video_optimization(use_letterbox_video_optimization) {}
bool use_letterbox_video_optimization;
};
class DCompPresenterLetterboxingTest
: public DCompPresenterTestBase,
public testing::WithParamInterface<
std::tuple<LetterboxingTestParams, bool>> {
protected:
void SetUp() override {
SetupScopedFeatureList();
DCompPresenterTestBase::SetUp();
}
virtual void SetupScopedFeatureList() {
std::vector<base::test::FeatureRef> enabled_features;
std::vector<base::test::FeatureRef> disabled_features;
if (std::get<0>(GetParam()).use_letterbox_video_optimization) {
DCompPresenterTestBase::EnableFeature(
features::kDirectCompositionLetterboxVideoOptimization);
} else {
DCompPresenterTestBase::DisableFeature(
features::kDirectCompositionLetterboxVideoOptimization);
}
if (std::get<1>(GetParam())) {
DCompPresenterTestBase::EnableFeature(features::kGpuVsync);
} else {
DCompPresenterTestBase::DisableFeature(features::kGpuVsync);
}
}
};
INSTANTIATE_TEST_SUITE_P(
None,
DCompPresenterLetterboxingTest,
testing::Combine(::testing::Values(LetterboxingTestParams(false)),
testing::Bool()));
INSTANTIATE_TEST_SUITE_P(
LetterBoxOpt,
DCompPresenterLetterboxingTest,
testing::Combine(::testing::Values(LetterboxingTestParams(true)),
testing::Bool()));
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);
// Make a 1080p 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);
// First test if swap chain and its visual info is adjusted to fit the
// monitor when letterboxing is generated for full screen presentation.
const int letterboxing_height =
(monitor_size.height() - texture_size.height()) / 2;
const gfx::Rect quad_rect =
gfx::Rect(0, 0, texture_size.width(), texture_size.height());
gfx::Rect clip_rect = gfx::Rect(0, letterboxing_height, texture_size.width(),
texture_size.height());
gfx::Transform quad_to_root_transform(
gfx::AxisTransform2d(1, gfx::Vector2dF(0, letterboxing_height)));
{
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->video_params.possible_video_fullscreen_letterboxing = true;
presenter_->ScheduleDCLayer(std::move(dc_layer_params));
PresentAndCheckSwapResult(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(0);
ASSERT_TRUE(swap_chain);
EXPECT_HRESULT_SUCCEEDED(swap_chain->GetDesc1(&desc));
EXPECT_EQ(1920u, desc.Width);
EXPECT_EQ(1080u, 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.
gfx::Transform visual_transform;
gfx::Point visual_offset;
gfx::Rect visual_clip_rect;
presenter_->GetSwapChainVisualInfoForTesting(
0, &visual_transform, &visual_offset, &visual_clip_rect);
if (std::get<0>(GetParam()).use_letterbox_video_optimization) {
// In case DirectCompositionLetterboxVideoOptimization feature is enabled,
// DWM will do the swap chain positioning in case of overlay. And visual
// clip rect has been set to monitor rect.
EXPECT_EQ(gfx::Rect(monitor_size), visual_clip_rect);
} else {
// In case DirectCompositionLetterboxVideoOptimization feature is disabled,
// keep the origin clip rect from DCLayerOverlayParams.
EXPECT_EQ(quad_to_root_transform, visual_transform);
EXPECT_EQ(clip_rect, visual_clip_rect);
}
// Second test if swap chain visual info is adjusted to fit the monitor when
// some negative offset from typical letterboxing positioning.
texture = CreateNV12Texture(d3d11_device, texture_size);
clip_rect = gfx::Rect(0, letterboxing_height - 2, texture_size.width(),
texture_size.height());
quad_to_root_transform = gfx::Transform(
gfx::AxisTransform2d(1, gfx::Vector2dF(0, letterboxing_height - 2)));
{
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->video_params.possible_video_fullscreen_letterboxing = true;
presenter_->ScheduleDCLayer(std::move(dc_layer_params));
PresentAndCheckSwapResult(gfx::SwapResult::SWAP_ACK);
}
// Swap chain size is set to onscreen content size.
DXGI_SWAP_CHAIN_DESC1 desc2;
Microsoft::WRL::ComPtr<IDXGISwapChain1> swap_chain2 =
presenter_->GetLayerSwapChainForTesting(0);
ASSERT_TRUE(swap_chain2);
EXPECT_HRESULT_SUCCEEDED(swap_chain2->GetDesc1(&desc2));
if (std::get<0>(GetParam()).use_letterbox_video_optimization) {
// In case DirectCompositionLetterboxVideoOptimization feature is enabled,
// there would be four pixels more to cover extra blank bar since the
// adjustment is basically a padding without movedown.
EXPECT_EQ(1920u, desc2.Width);
EXPECT_EQ(1084u, desc2.Height);
} else {
EXPECT_EQ(1920u, desc2.Width);
EXPECT_EQ(1080u, desc2.Height);
}
// Make sure the new transform matrix is adjusted, so it transforms the swap
// chain to |new_on_screen_rect| which fits the monitor.
gfx::Transform visual_transform2;
gfx::Point visual_offset2;
gfx::Rect visual_clip_rect2;
presenter_->GetSwapChainVisualInfoForTesting(
0, &visual_transform2, &visual_offset2, &visual_clip_rect2);
if (std::get<0>(GetParam()).use_letterbox_video_optimization) {
// In case DirectCompositionLetterboxVideoOptimization feature is enabled,
// DWM will do the swap chain positioning in case of overlay. And visual
// clip rect has been set to monitor rect.
EXPECT_EQ(gfx::Rect(monitor_size), visual_clip_rect2);
} else {
// In case DirectCompositionLetterboxVideoOptimization feature is disabled,
// keep the origin clip rect from DCLayerOverlayParams.
EXPECT_EQ(quad_to_root_transform, visual_transform2);
EXPECT_EQ(clip_rect, visual_clip_rect2);
}
// Third test if swap chain visual info is adjusted to fit the monitor when
// some positive offset from typical letterboxing positioning.
texture = CreateNV12Texture(d3d11_device, texture_size);
clip_rect = gfx::Rect(0, letterboxing_height + 2, texture_size.width(),
texture_size.height());
quad_to_root_transform = gfx::Transform(
gfx::AxisTransform2d(1, gfx::Vector2dF(0, letterboxing_height + 2)));
{
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->video_params.possible_video_fullscreen_letterboxing = true;
presenter_->ScheduleDCLayer(std::move(dc_layer_params));
PresentAndCheckSwapResult(gfx::SwapResult::SWAP_ACK);
}
// Swap chain size is set to onscreen content size
DXGI_SWAP_CHAIN_DESC1 desc3;
Microsoft::WRL::ComPtr<IDXGISwapChain1> swap_chain3 =
presenter_->GetLayerSwapChainForTesting(0);
ASSERT_TRUE(swap_chain3);
EXPECT_HRESULT_SUCCEEDED(swap_chain3->GetDesc1(&desc3));
if (std::get<0>(GetParam()).use_letterbox_video_optimization) {
// In case DirectCompositionLetterboxVideoOptimization feature is enabled,
// there would be two pixels more to cover extra blank bar since the
// adjustment is basically a moveup.
EXPECT_EQ(1920u, desc3.Width);
EXPECT_EQ(1082u, desc3.Height);
} else {
EXPECT_EQ(1920u, desc3.Width);
EXPECT_EQ(1080u, desc3.Height);
}
// Make sure the new transform matrix is adjusted, so it transforms the swap
// chain to |new_on_screen_rect| which fits the monitor.
gfx::Transform visual_transform3;
gfx::Point visual_offset3;
gfx::Rect visual_clip_rect3;
presenter_->GetSwapChainVisualInfoForTesting(
0, &visual_transform3, &visual_offset3, &visual_clip_rect3);
if (std::get<0>(GetParam()).use_letterbox_video_optimization) {
// In case DirectCompositionLetterboxVideoOptimization feature is enabled,
// DWM will do the swap chain positioning in case of overlay. And visual
// clip rect has been set to monitor rect.
EXPECT_EQ(gfx::Rect(monitor_size), visual_clip_rect3);
} else {
// In case DirectCompositionLetterboxVideoOptimization feature is disabled,
// keep the origin clip rect from DCLayerOverlayParams.
EXPECT_EQ(quad_to_root_transform, visual_transform3);
EXPECT_EQ(clip_rect, visual_clip_rect3);
}
}
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);
// Make a 1080p 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);
// Test if swap chain and its visual info is adjusted to fit the monitor when
// letterboxing is generated for full screen presentation.
const int letterboxing_height =
(monitor_size.height() - texture_size.height()) / 2;
const gfx::Rect quad_rect =
gfx::Rect(0, 0, texture_size.width(), texture_size.height());
const gfx::Rect clip_rect = gfx::Rect(
0, letterboxing_height, texture_size.width(), texture_size.height());
const gfx::Transform quad_to_root_transform(
gfx::AxisTransform2d(1, gfx::Vector2dF(0, letterboxing_height)));
{
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->video_params.possible_video_fullscreen_letterboxing = true;
presenter_->ScheduleDCLayer(std::move(dc_layer_params));
PresentAndCheckSwapResult(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(0);
ASSERT_TRUE(swap_chain);
EXPECT_HRESULT_SUCCEEDED(swap_chain->GetDesc1(&desc));
EXPECT_EQ(1920u, desc.Width);
EXPECT_EQ(1080u, desc.Height);
if (std::get<0>(GetParam()).use_letterbox_video_optimization) {
// Check desktop plane removal part 1.
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(1200u, 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));
}
// Swap chain visual is clipped to the whole monitor size.
gfx::Transform visual_transform;
gfx::Point visual_offset;
gfx::Rect visual_clip_rect;
presenter_->GetSwapChainVisualInfoForTesting(
0, &visual_transform, &visual_offset, &visual_clip_rect);
if (std::get<0>(GetParam()).use_letterbox_video_optimization) {
// Check desktop plane removal part 2.
// In case DirectCompositionLetterboxVideoOptimization feature is enabled,
// DWM will do the swap chain positioning in case of overlay.
EXPECT_TRUE(visual_transform.IsIdentity());
// Visual clip rect has been set to monitor rect.
EXPECT_EQ(gfx::Rect(monitor_size), visual_clip_rect);
} else {
// In case DirectCompositionLetterboxVideoOptimization feature is disabled,
// keep the origin transform and clip rect from DCLayerOverlayParams.
EXPECT_EQ(quad_to_root_transform, visual_transform);
EXPECT_EQ(clip_rect, visual_clip_rect);
}
}
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 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);
// First full screen presentation with letterboxing.
const int letterboxing_height =
(monitor_size.height() - texture_size.height()) / 2;
const gfx::Rect quad_rect =
gfx::Rect(0, 0, texture_size.width(), texture_size.height());
const gfx::Rect clip_rect = gfx::Rect(
0, letterboxing_height, texture_size.width(), texture_size.height());
const gfx::Transform quad_to_root_transform(
gfx::AxisTransform2d(1, gfx::Vector2dF(0, letterboxing_height)));
{
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->video_params.possible_video_fullscreen_letterboxing = true;
presenter_->ScheduleDCLayer(std::move(dc_layer_params));
PresentAndCheckSwapResult(gfx::SwapResult::SWAP_ACK);
}
// Make sure it's a valid swap chain presentation
Microsoft::WRL::ComPtr<IDXGISwapChain1> swap_chain =
presenter_->GetLayerSwapChainForTesting(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.
UINT last_present_count = 0;
EXPECT_HRESULT_SUCCEEDED(
swap_chain->GetLastPresentCount(&last_present_count));
EXPECT_EQ(2u, last_present_count);
// 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(
0, &visual_transform1, &visual_offset1, &visual_clip_rect1);
// Followed by second presentation with the same image.
{
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->video_params.possible_video_fullscreen_letterboxing = true;
presenter_->ScheduleDCLayer(std::move(dc_layer_params));
PresentAndCheckSwapResult(gfx::SwapResult::SWAP_ACK);
}
// It's the same image, so it should have the same swapchain.
Microsoft::WRL::ComPtr<IDXGISwapChain1> swap_chain2 =
presenter_->GetLayerSwapChainForTesting(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);
// 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(
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);
// More checks followed by third presentation with a new image.
texture = CreateNV12Texture(d3d11_device, texture_size);
ASSERT_NE(texture, nullptr);
{
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->video_params.possible_video_fullscreen_letterboxing = true;
presenter_->ScheduleDCLayer(std::move(dc_layer_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 image presentation.
EXPECT_EQ(3u, last_present_count);
}
// 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);
// Make a 1800*1200 texture as display input.
Microsoft::WRL::ComPtr<ID3D11Device> d3d11_device =
GetDirectCompositionD3D11Device();
const gfx::Size texture_size(1800, 1200);
Microsoft::WRL::ComPtr<ID3D11Texture2D> texture =
CreateNV12Texture(d3d11_device, texture_size);
ASSERT_NE(texture, nullptr);
// First test if swap chain and its visual info is adjusted to fit the
// monitor when letterboxing is generated for full screen presentation.
const int letterboxing_width =
(monitor_size.width() - texture_size.width()) / 2;
const gfx::Rect quad_rect =
gfx::Rect(0, 0, texture_size.width(), texture_size.height());
gfx::Rect clip_rect = gfx::Rect(letterboxing_width, 0, texture_size.width(),
texture_size.height());
gfx::Transform quad_to_root_transform(
gfx::AxisTransform2d(1, gfx::Vector2dF(letterboxing_width, 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->video_params.possible_video_fullscreen_letterboxing = true;
presenter_->ScheduleDCLayer(std::move(dc_layer_params));
PresentAndCheckSwapResult(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(0);
ASSERT_TRUE(swap_chain);
EXPECT_HRESULT_SUCCEEDED(swap_chain->GetDesc1(&desc));
EXPECT_EQ(1800u, desc.Width);
EXPECT_EQ(1200u, 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.
gfx::Transform visual_transform;
gfx::Point visual_offset;
gfx::Rect visual_clip_rect;
presenter_->GetSwapChainVisualInfoForTesting(
0, &visual_transform, &visual_offset, &visual_clip_rect);
if (std::get<0>(GetParam()).use_letterbox_video_optimization) {
// In case DirectCompositionLetterboxVideoOptimization feature is enabled,
// DWM will do the swap chain positioning in case of overlay. And visual
// clip rect has been set to monitor rect.
EXPECT_EQ(gfx::Rect(monitor_size), visual_clip_rect);
} else {
// In case DirectCompositionLetterboxVideoOptimization feature is disabled,
// keep the origin clip rect from DCLayerOverlayParams.
EXPECT_EQ(quad_to_root_transform, visual_transform);
EXPECT_EQ(clip_rect, visual_clip_rect);
}
// Second test if swap chain visual info is adjusted to fit the monitor when
// some negative offset from typical letterboxing positioning.
texture = CreateNV12Texture(d3d11_device, texture_size);
clip_rect = gfx::Rect(letterboxing_width - 2, 0, texture_size.width(),
texture_size.height());
quad_to_root_transform = gfx::Transform(
gfx::AxisTransform2d(1, gfx::Vector2dF(letterboxing_width - 2, 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->video_params.possible_video_fullscreen_letterboxing = true;
presenter_->ScheduleDCLayer(std::move(dc_layer_params));
PresentAndCheckSwapResult(gfx::SwapResult::SWAP_ACK);
}
// Swap chain size is set to onscreen content size.
DXGI_SWAP_CHAIN_DESC1 desc2;
Microsoft::WRL::ComPtr<IDXGISwapChain1> swap_chain2 =
presenter_->GetLayerSwapChainForTesting(0);
ASSERT_TRUE(swap_chain2);
EXPECT_HRESULT_SUCCEEDED(swap_chain2->GetDesc1(&desc2));
if (std::get<0>(GetParam()).use_letterbox_video_optimization) {
// In case DirectCompositionLetterboxVideoOptimization feature is enabled,
// there would be four pixels more to cover extra blank bar since the
// adjustment is basically a padding without move-right.
EXPECT_EQ(1804u, desc2.Width);
EXPECT_EQ(1200u, desc2.Height);
} else {
EXPECT_EQ(1800u, desc2.Width);
EXPECT_EQ(1200u, desc2.Height);
}
// Make sure the new transform matrix is adjusted, so it transforms the swap
// chain to |new_on_screen_rect| which fits the monitor.
gfx::Transform visual_transform2;
gfx::Point visual_offset2;
gfx::Rect visual_clip_rect2;
presenter_->GetSwapChainVisualInfoForTesting(
0, &visual_transform2, &visual_offset2, &visual_clip_rect2);
if (std::get<0>(GetParam()).use_letterbox_video_optimization) {
// In case DirectCompositionLetterboxVideoOptimization feature is enabled,
// DWM will do the swap chain positioning in case of overlay. And visual
// clip rect has been set to monitor rect.
EXPECT_EQ(gfx::Rect(monitor_size), visual_clip_rect2);
} else {
// In case DirectCompositionLetterboxVideoOptimization feature is disabled,
// keep the origin clip rect from DCLayerOverlayParams.
EXPECT_EQ(quad_to_root_transform, visual_transform2);
EXPECT_EQ(clip_rect, visual_clip_rect2);
}
// Third test if swap chain visual info is adjusted to fit the monitor when
// some positive offset from typical letterboxing positioning.
texture = CreateNV12Texture(d3d11_device, texture_size);
clip_rect = gfx::Rect(letterboxing_width + 2, 0, texture_size.width(),
texture_size.height());
quad_to_root_transform = gfx::Transform(
gfx::AxisTransform2d(1, gfx::Vector2dF(letterboxing_width + 2, 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->video_params.possible_video_fullscreen_letterboxing = true;
presenter_->ScheduleDCLayer(std::move(dc_layer_params));
PresentAndCheckSwapResult(gfx::SwapResult::SWAP_ACK);
}
// Swap chain size is set to onscreen content size
DXGI_SWAP_CHAIN_DESC1 desc3;
Microsoft::WRL::ComPtr<IDXGISwapChain1> swap_chain3 =
presenter_->GetLayerSwapChainForTesting(0);
ASSERT_TRUE(swap_chain3);
EXPECT_HRESULT_SUCCEEDED(swap_chain3->GetDesc1(&desc3));
if (std::get<0>(GetParam()).use_letterbox_video_optimization) {
// In case DirectCompositionLetterboxVideoOptimization feature is enabled,
// there would be two pixels more to cover extra blank bar since the
// adjustment is basically a move-left.
EXPECT_EQ(1802u, desc3.Width);
EXPECT_EQ(1200u, desc3.Height);
} else {
EXPECT_EQ(1800u, desc3.Width);
EXPECT_EQ(1200u, desc3.Height);
}
// Make sure the new transform matrix is adjusted, so it transforms the swap
// chain to |new_on_screen_rect| which fits the monitor.
gfx::Transform visual_transform3;
gfx::Point visual_offset3;
gfx::Rect visual_clip_rect3;
presenter_->GetSwapChainVisualInfoForTesting(
0, &visual_transform3, &visual_offset3, &visual_clip_rect3);
if (std::get<0>(GetParam()).use_letterbox_video_optimization) {
// In case DirectCompositionLetterboxVideoOptimization feature is enabled,
// DWM will do the swap chain positioning in case of overlay. And visual
// clip rect has been set to monitor rect.
EXPECT_EQ(gfx::Rect(monitor_size), visual_clip_rect3);
} else {
// In case DirectCompositionLetterboxVideoOptimization feature is disabled,
// keep the origin clip rect from DCLayerOverlayParams.
EXPECT_EQ(quad_to_root_transform, visual_transform3);
EXPECT_EQ(clip_rect, visual_clip_rect3);
}
}
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);
// Make a 1800*1200 texture as display input.
Microsoft::WRL::ComPtr<ID3D11Device> d3d11_device =
GetDirectCompositionD3D11Device();
const gfx::Size texture_size(1800, 1200);
Microsoft::WRL::ComPtr<ID3D11Texture2D> texture =
CreateNV12Texture(d3d11_device, texture_size);
ASSERT_NE(texture, nullptr);
// Test if swap chain and its visual info is adjusted to fit the monitor when
// letterboxing is generated for full screen presentation.
const int letterboxing_width =
(monitor_size.width() - texture_size.width()) / 2;
const gfx::Rect quad_rect =
gfx::Rect(0, 0, texture_size.width(), texture_size.height());
const gfx::Rect clip_rect = gfx::Rect(
letterboxing_width, 0, texture_size.width(), texture_size.height());
const gfx::Transform quad_to_root_transform(
gfx::AxisTransform2d(1, gfx::Vector2dF(letterboxing_width, 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->video_params.possible_video_fullscreen_letterboxing = true;
presenter_->ScheduleDCLayer(std::move(dc_layer_params));
PresentAndCheckSwapResult(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(0);
ASSERT_TRUE(swap_chain);
EXPECT_HRESULT_SUCCEEDED(swap_chain->GetDesc1(&desc));
EXPECT_EQ(1800u, desc.Width);
EXPECT_EQ(1200u, desc.Height);
if (std::get<0>(GetParam()).use_letterbox_video_optimization) {
// Check desktop plane removal part 1.
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(1200u, 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));
}
// Swap chain visual is clipped to the whole monitor size.
gfx::Transform visual_transform;
gfx::Point visual_offset;
gfx::Rect visual_clip_rect;
presenter_->GetSwapChainVisualInfoForTesting(
0, &visual_transform, &visual_offset, &visual_clip_rect);
if (std::get<0>(GetParam()).use_letterbox_video_optimization) {
// Check desktop plane removal part 2.
// In case DirectCompositionLetterboxVideoOptimization feature is enabled,
// DWM will do the swap chain positioning in case of overlay.
EXPECT_TRUE(visual_transform.IsIdentity());
// Visual clip rect has been set to monitor rect.
EXPECT_EQ(gfx::Rect(monitor_size), visual_clip_rect);
} else {
// In case DirectCompositionLetterboxVideoOptimization feature is disabled,
// keep the origin transform and clip rect from DCLayerOverlayParams.
EXPECT_EQ(quad_to_root_transform, visual_transform);
EXPECT_EQ(clip_rect, visual_clip_rect);
}
}
class DCompPresenterFullscreenRoundingTest : public DCompPresenterTestBase {};
TEST_F(DCompPresenterFullscreenRoundingTest,
FullScreenRoundingWithHalfPixelTranslation) {
// 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;
presenter_->ScheduleDCLayer(std::move(dc_layer_params));
PresentAndCheckSwapResult(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(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(
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_F(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;
presenter_->ScheduleDCLayer(std::move(dc_layer_params));
PresentAndCheckSwapResult(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(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(
0, &visual_transform, &visual_offset, &visual_clip_rect);
DVLOG(1) << "visual_transform" << visual_transform.ToString();
EXPECT_TRUE(visual_transform.IsIdentity());
EXPECT_EQ(clip_rect, visual_clip_rect);
}
} // namespace gl