blob: 4982b956bfbd12e799aaec13974747da4e87d158 [file] [log] [blame]
// Copyright 2017 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "gpu/ipc/service/direct_composition_surface_win.h"
#include "base/bind_helpers.h"
#include "base/memory/ref_counted_memory.h"
#include "base/memory/weak_ptr.h"
#include "base/run_loop.h"
#include "base/synchronization/waitable_event.h"
#include "base/threading/thread_task_runner_handle.h"
#include "base/win/scoped_gdi_object.h"
#include "base/win/scoped_hdc.h"
#include "base/win/scoped_select_object.h"
#include "gpu/command_buffer/common/swap_buffers_complete_params.h"
#include "gpu/command_buffer/service/feature_info.h"
#include "gpu/command_buffer/service/gpu_preferences.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "ui/base/win/hidden_window.h"
#include "ui/gfx/buffer_format_util.h"
#include "ui/gfx/gdi_util.h"
#include "ui/gfx/transform.h"
#include "ui/gl/dc_renderer_layer_params.h"
#include "ui/gl/gl_angle_util_win.h"
#include "ui/gl/gl_context.h"
#include "ui/gl/gl_image_dxgi.h"
#include "ui/gl/gl_image_ref_counted_memory.h"
#include "ui/gl/init/gl_factory.h"
#include "ui/platform_window/platform_window_delegate.h"
#include "ui/platform_window/win/win_window.h"
namespace gpu {
namespace {
bool CheckIfDCSupported() {
if (!gl::QueryDirectCompositionDevice(
gl::QueryD3D11DeviceObjectFromANGLE())) {
LOG(WARNING)
<< "GL implementation not using DirectComposition, skipping test.";
return false;
}
return true;
}
class TestImageTransportSurfaceDelegate
: public ImageTransportSurfaceDelegate,
public base::SupportsWeakPtr<TestImageTransportSurfaceDelegate> {
public:
TestImageTransportSurfaceDelegate()
: feature_info_(new gpu::gles2::FeatureInfo()) {}
~TestImageTransportSurfaceDelegate() override {}
// ImageTransportSurfaceDelegate implementation.
void DidCreateAcceleratedSurfaceChildWindow(
SurfaceHandle parent_window,
SurfaceHandle child_window) override {
if (parent_window)
::SetParent(child_window, parent_window);
}
void DidSwapBuffersComplete(SwapBuffersCompleteParams params) override {}
const gles2::FeatureInfo* GetFeatureInfo() const override {
return feature_info_.get();
}
const GpuPreferences& GetGpuPreferences() const override {
return gpu_preferences_;
}
void SetSnapshotRequestedCallback(const base::Closure& callback) override {}
void UpdateVSyncParameters(base::TimeTicks timebase,
base::TimeDelta interval) override {}
void BufferPresented(const gfx::PresentationFeedback& feedback) override {}
void AddFilter(IPC::MessageFilter* message_filter) override {}
int32_t GetRouteID() const override { return 0; }
private:
scoped_refptr<gpu::gles2::FeatureInfo> feature_info_;
GpuPreferences gpu_preferences_;
};
class TestPlatformDelegate : public ui::PlatformWindowDelegate {
public:
// ui::PlatformWindowDelegate implementation.
void OnBoundsChanged(const gfx::Rect& new_bounds) 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 new_state) override {}
void OnLostCapture() override {}
void OnAcceleratedWidgetAvailable(gfx::AcceleratedWidget widget,
float device_pixel_ratio) override {}
void OnAcceleratedWidgetDestroying() override {}
void OnAcceleratedWidgetDestroyed() override {}
void OnActivationChanged(bool active) 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,
Bind(&base::WaitableEvent::Signal, Unretained(&done)));
done.Wait();
}
void DestroySurface(scoped_refptr<DirectCompositionSurfaceWin> surface) {
scoped_refptr<base::TaskRunner> task_runner =
surface->GetWindowTaskRunnerForTesting();
DCHECK(surface->HasOneRef());
surface = nullptr;
// 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,
bool shared) {
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;
if (shared) {
desc.MiscFlags = D3D11_RESOURCE_MISC_SHARED_KEYEDMUTEX |
D3D11_RESOURCE_MISC_SHARED_NTHANDLE;
}
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.GetAddressOf());
CHECK(SUCCEEDED(hr));
return texture;
}
TEST(DirectCompositionSurfaceTest, TestMakeCurrent) {
if (!CheckIfDCSupported())
return;
TestImageTransportSurfaceDelegate delegate;
scoped_refptr<DirectCompositionSurfaceWin> surface1(
new DirectCompositionSurfaceWin(nullptr, delegate.AsWeakPtr(),
ui::GetHiddenWindow()));
EXPECT_TRUE(surface1->Initialize());
scoped_refptr<gl::GLContext> context1 = gl::init::CreateGLContext(
nullptr, surface1.get(), gl::GLContextAttribs());
EXPECT_TRUE(context1->MakeCurrent(surface1.get()));
surface1->SetEnableDCLayers(true);
EXPECT_TRUE(surface1->Resize(gfx::Size(100, 100), 1.0,
gl::GLSurface::ColorSpace::UNSPECIFIED, true));
// First SetDrawRectangle must be full size of surface.
EXPECT_FALSE(surface1->SetDrawRectangle(gfx::Rect(0, 0, 50, 50)));
EXPECT_TRUE(surface1->SetDrawRectangle(gfx::Rect(0, 0, 100, 100)));
// SetDrawRectangle can't be called again until swap.
EXPECT_FALSE(surface1->SetDrawRectangle(gfx::Rect(0, 0, 100, 100)));
EXPECT_EQ(gfx::SwapResult::SWAP_ACK,
surface1->SwapBuffers(base::DoNothing()));
EXPECT_TRUE(context1->IsCurrent(surface1.get()));
// SetDrawRectangle must be contained within surface.
EXPECT_FALSE(surface1->SetDrawRectangle(gfx::Rect(0, 0, 101, 101)));
EXPECT_TRUE(surface1->SetDrawRectangle(gfx::Rect(0, 0, 100, 100)));
EXPECT_TRUE(context1->IsCurrent(surface1.get()));
EXPECT_TRUE(surface1->Resize(gfx::Size(50, 50), 1.0,
gl::GLSurface::ColorSpace::UNSPECIFIED, true));
EXPECT_TRUE(context1->IsCurrent(surface1.get()));
EXPECT_TRUE(surface1->SetDrawRectangle(gfx::Rect(0, 0, 50, 50)));
EXPECT_TRUE(context1->IsCurrent(surface1.get()));
scoped_refptr<DirectCompositionSurfaceWin> surface2(
new DirectCompositionSurfaceWin(nullptr, delegate.AsWeakPtr(),
ui::GetHiddenWindow()));
EXPECT_TRUE(surface2->Initialize());
scoped_refptr<gl::GLContext> context2 = gl::init::CreateGLContext(
nullptr, surface2.get(), gl::GLContextAttribs());
EXPECT_TRUE(context2->MakeCurrent(surface2.get()));
surface2->SetEnableDCLayers(true);
EXPECT_TRUE(surface2->Resize(gfx::Size(100, 100), 1.0,
gl::GLSurface::ColorSpace::UNSPECIFIED, true));
// The previous IDCompositionSurface should be suspended when another
// surface is being drawn to.
EXPECT_TRUE(surface2->SetDrawRectangle(gfx::Rect(0, 0, 100, 100)));
EXPECT_TRUE(context2->IsCurrent(surface2.get()));
// It should be possible to switch back to the previous surface and
// unsuspend it.
EXPECT_TRUE(context1->MakeCurrent(surface1.get()));
context2 = nullptr;
context1 = nullptr;
DestroySurface(std::move(surface1));
DestroySurface(std::move(surface2));
}
// Tests that switching using EnableDCLayers works.
TEST(DirectCompositionSurfaceTest, DXGIDCLayerSwitch) {
if (!CheckIfDCSupported())
return;
TestImageTransportSurfaceDelegate delegate;
scoped_refptr<DirectCompositionSurfaceWin> surface(
new DirectCompositionSurfaceWin(nullptr, delegate.AsWeakPtr(),
ui::GetHiddenWindow()));
EXPECT_TRUE(surface->Initialize());
scoped_refptr<gl::GLContext> context =
gl::init::CreateGLContext(nullptr, surface.get(), gl::GLContextAttribs());
EXPECT_TRUE(context->MakeCurrent(surface.get()));
EXPECT_TRUE(surface->Resize(gfx::Size(100, 100), 1.0,
gl::GLSurface::ColorSpace::UNSPECIFIED, true));
EXPECT_FALSE(surface->swap_chain());
// First SetDrawRectangle must be full size of surface for DXGI
// swapchain.
EXPECT_FALSE(surface->SetDrawRectangle(gfx::Rect(0, 0, 50, 50)));
EXPECT_TRUE(surface->SetDrawRectangle(gfx::Rect(0, 0, 100, 100)));
EXPECT_TRUE(surface->swap_chain());
// SetDrawRectangle can't be called again until swap.
EXPECT_FALSE(surface->SetDrawRectangle(gfx::Rect(0, 0, 100, 100)));
EXPECT_TRUE(context->MakeCurrent(surface.get()));
EXPECT_EQ(gfx::SwapResult::SWAP_ACK, surface->SwapBuffers(base::DoNothing()));
EXPECT_TRUE(context->IsCurrent(surface.get()));
surface->SetEnableDCLayers(true);
// Surface switched to use IDCompositionSurface, so must draw to
// entire surface.
EXPECT_FALSE(surface->SetDrawRectangle(gfx::Rect(0, 0, 50, 50)));
EXPECT_TRUE(surface->SetDrawRectangle(gfx::Rect(0, 0, 100, 100)));
EXPECT_TRUE(context->IsCurrent(surface.get()));
EXPECT_FALSE(surface->swap_chain());
surface->SetEnableDCLayers(false);
EXPECT_EQ(gfx::SwapResult::SWAP_ACK, surface->SwapBuffers(base::DoNothing()));
// Surface switched to use IDXGISwapChain, so must draw to entire
// surface.
EXPECT_FALSE(surface->SetDrawRectangle(gfx::Rect(0, 0, 50, 50)));
EXPECT_TRUE(surface->SetDrawRectangle(gfx::Rect(0, 0, 100, 100)));
EXPECT_TRUE(surface->swap_chain());
context = nullptr;
DestroySurface(std::move(surface));
}
// Ensure that the swapchain's alpha is correct.
TEST(DirectCompositionSurfaceTest, SwitchAlpha) {
if (!CheckIfDCSupported())
return;
TestImageTransportSurfaceDelegate delegate;
scoped_refptr<DirectCompositionSurfaceWin> surface(
new DirectCompositionSurfaceWin(nullptr, delegate.AsWeakPtr(),
ui::GetHiddenWindow()));
EXPECT_TRUE(surface->Initialize());
scoped_refptr<gl::GLContext> context =
gl::init::CreateGLContext(nullptr, surface.get(), gl::GLContextAttribs());
EXPECT_TRUE(context->MakeCurrent(surface.get()));
EXPECT_TRUE(surface->Resize(gfx::Size(100, 100), 1.0,
gl::GLSurface::ColorSpace::UNSPECIFIED, true));
EXPECT_FALSE(surface->swap_chain());
EXPECT_TRUE(surface->SetDrawRectangle(gfx::Rect(0, 0, 100, 100)));
Microsoft::WRL::ComPtr<IDXGISwapChain1> swap_chain = surface->swap_chain();
ASSERT_TRUE(swap_chain);
DXGI_SWAP_CHAIN_DESC1 desc;
swap_chain->GetDesc1(&desc);
EXPECT_EQ(DXGI_ALPHA_MODE_PREMULTIPLIED, desc.AlphaMode);
// Resize to the same parameters should have no effect.
EXPECT_TRUE(surface->Resize(gfx::Size(100, 100), 1.0,
gl::GLSurface::ColorSpace::UNSPECIFIED, true));
EXPECT_TRUE(surface->swap_chain());
EXPECT_TRUE(surface->Resize(gfx::Size(100, 100), 1.0,
gl::GLSurface::ColorSpace::UNSPECIFIED, false));
EXPECT_FALSE(surface->swap_chain());
EXPECT_TRUE(surface->SetDrawRectangle(gfx::Rect(0, 0, 100, 100)));
swap_chain = surface->swap_chain();
ASSERT_TRUE(swap_chain);
swap_chain->GetDesc1(&desc);
EXPECT_EQ(DXGI_ALPHA_MODE_IGNORE, desc.AlphaMode);
context = nullptr;
DestroySurface(std::move(surface));
}
// Ensure that the GLImage isn't presented again unless it changes.
TEST(DirectCompositionSurfaceTest, NoPresentTwice) {
if (!CheckIfDCSupported())
return;
TestImageTransportSurfaceDelegate delegate;
scoped_refptr<DirectCompositionSurfaceWin> surface(
new DirectCompositionSurfaceWin(nullptr, delegate.AsWeakPtr(),
ui::GetHiddenWindow()));
EXPECT_TRUE(surface->Initialize());
scoped_refptr<gl::GLContext> context =
gl::init::CreateGLContext(nullptr, surface.get(), gl::GLContextAttribs());
EXPECT_TRUE(context->MakeCurrent(surface.get()));
surface->SetEnableDCLayers(true);
Microsoft::WRL::ComPtr<ID3D11Device> d3d11_device =
gl::QueryD3D11DeviceObjectFromANGLE();
gfx::Size texture_size(50, 50);
Microsoft::WRL::ComPtr<ID3D11Texture2D> texture =
CreateNV12Texture(d3d11_device, texture_size, false);
scoped_refptr<gl::GLImageDXGI> image_dxgi(
new gl::GLImageDXGI(texture_size, nullptr));
image_dxgi->SetTexture(texture, 0);
image_dxgi->SetColorSpace(gfx::ColorSpace::CreateREC709());
gfx::Size window_size(100, 100);
ui::DCRendererLayerParams params(
false, gfx::Rect(), 1, gfx::Transform(),
std::vector<scoped_refptr<gl::GLImage>>{image_dxgi},
gfx::RectF(gfx::Rect(texture_size)), gfx::Rect(window_size), 0, 0, 1.0, 0,
false);
surface->ScheduleDCLayer(params);
Microsoft::WRL::ComPtr<IDXGISwapChain1> swap_chain =
surface->GetLayerSwapChainForTesting(1);
ASSERT_FALSE(swap_chain);
EXPECT_EQ(gfx::SwapResult::SWAP_ACK, surface->SwapBuffers(base::DoNothing()));
swap_chain = surface->GetLayerSwapChainForTesting(1);
ASSERT_TRUE(swap_chain);
UINT last_present_count = 0;
EXPECT_TRUE(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);
surface->ScheduleDCLayer(params);
EXPECT_EQ(gfx::SwapResult::SWAP_ACK, surface->SwapBuffers(base::DoNothing()));
Microsoft::WRL::ComPtr<IDXGISwapChain1> swap_chain2 =
surface->GetLayerSwapChainForTesting(1);
EXPECT_EQ(swap_chain2.Get(), swap_chain.Get());
// It's the same image, so it should have the same swapchain.
EXPECT_TRUE(SUCCEEDED(swap_chain->GetLastPresentCount(&last_present_count)));
EXPECT_EQ(2u, last_present_count);
// The size of the swapchain changed, so it should be recreated.
ui::DCRendererLayerParams params2(
false, gfx::Rect(), 1, gfx::Transform(),
std::vector<scoped_refptr<gl::GLImage>>{image_dxgi},
gfx::RectF(gfx::Rect(texture_size)), gfx::Rect(0, 0, 25, 25), 0, 0, 1.0,
0, false);
surface->ScheduleDCLayer(params2);
EXPECT_EQ(gfx::SwapResult::SWAP_ACK, surface->SwapBuffers(base::DoNothing()));
Microsoft::WRL::ComPtr<IDXGISwapChain1> swap_chain3 =
surface->GetLayerSwapChainForTesting(1);
EXPECT_NE(swap_chain2.Get(), swap_chain3.Get());
context = nullptr;
DestroySurface(std::move(surface));
}
SkColor ReadBackWindowPixel(HWND window, const gfx::Point& point) {
base::win::ScopedCreateDC mem_hdc(::CreateCompatibleDC(nullptr));
void* bits = nullptr;
BITMAPV4HEADER hdr;
gfx::CreateBitmapV4Header(point.x() + 1, point.y() + 1, &hdr);
DCHECK(mem_hdc.IsValid());
base::win::ScopedBitmap bitmap(
::CreateDIBSection(mem_hdc.Get(), reinterpret_cast<BITMAPINFO*>(&hdr),
DIB_RGB_COLORS, &bits, nullptr, 0));
DCHECK(bitmap.is_valid());
base::win::ScopedSelectObject select_object(mem_hdc.Get(), bitmap.get());
// Grab a copy of the window. Use PrintWindow because it works even when the
// window's partially occluded. The PW_RENDERFULLCONTENT flag is undocumented,
// but works starting in Windows 8.1. It allows for capturing the contents of
// the window that are drawn using DirectComposition.
UINT flags = PW_CLIENTONLY | PW_RENDERFULLCONTENT;
BOOL result = PrintWindow(window, mem_hdc.Get(), flags);
if (!result)
PLOG(ERROR) << "Failed to print window";
GdiFlush();
uint32_t pixel_value =
static_cast<uint32_t*>(bits)[hdr.bV4Width * point.y() + point.x()];
return static_cast<SkColor>(pixel_value);
}
class DirectCompositionPixelTest : public testing::Test {
public:
DirectCompositionPixelTest()
: window_(&platform_delegate_, gfx::Rect(0, 0, 100, 100)) {}
~DirectCompositionPixelTest() override {
context_ = nullptr;
if (surface_)
DestroySurface(std::move(surface_));
}
protected:
void InitializeSurface() {
static_cast<ui::PlatformWindow*>(&window_)->Show();
surface_ = new DirectCompositionSurfaceWin(nullptr, delegate_.AsWeakPtr(),
window_.hwnd());
EXPECT_TRUE(surface_->Initialize());
context_ = gl::init::CreateGLContext(nullptr, surface_.get(),
gl::GLContextAttribs());
EXPECT_TRUE(context_->MakeCurrent(surface_.get()));
}
void PixelTestSwapChain(bool layers_enabled) {
if (!CheckIfDCSupported())
return;
InitializeSurface();
surface_->SetEnableDCLayers(layers_enabled);
gfx::Size window_size(100, 100);
EXPECT_TRUE(surface_->Resize(window_size, 1.0,
gl::GLSurface::ColorSpace::UNSPECIFIED, true));
EXPECT_TRUE(surface_->SetDrawRectangle(gfx::Rect(window_size)));
glClearColor(1.0, 0.0, 0.0, 1.0);
glClear(GL_COLOR_BUFFER_BIT);
EXPECT_EQ(gfx::SwapResult::SWAP_ACK,
surface_->SwapBuffers(base::DoNothing()));
// Ensure DWM swap completed.
Sleep(1000);
SkColor expected_color = SK_ColorRED;
SkColor actual_color =
ReadBackWindowPixel(window_.hwnd(), gfx::Point(75, 75));
EXPECT_EQ(expected_color, actual_color)
<< std::hex << "Expected " << expected_color << " Actual "
<< actual_color;
EXPECT_TRUE(context_->IsCurrent(surface_.get()));
}
TestPlatformDelegate platform_delegate_;
TestImageTransportSurfaceDelegate delegate_;
ui::WinWindow window_;
scoped_refptr<DirectCompositionSurfaceWin> surface_;
scoped_refptr<gl::GLContext> context_;
};
TEST_F(DirectCompositionPixelTest, DCLayersEnabled) {
PixelTestSwapChain(true);
}
TEST_F(DirectCompositionPixelTest, DCLayersDisabled) {
PixelTestSwapChain(false);
}
bool AreColorsSimilar(int a, int b) {
// The precise colors may differ depending on the video processor, so allow
// a margin for error.
const int kMargin = 10;
return abs(SkColorGetA(a) - SkColorGetA(b)) < kMargin &&
abs(SkColorGetR(a) - SkColorGetR(b)) < kMargin &&
abs(SkColorGetG(a) - SkColorGetG(b)) < kMargin &&
abs(SkColorGetB(a) - SkColorGetB(b)) < kMargin;
}
class DirectCompositionVideoPixelTest : public DirectCompositionPixelTest {
protected:
void TestVideo(const gfx::ColorSpace& color_space,
SkColor expected_color,
bool check_color) {
if (!CheckIfDCSupported())
return;
InitializeSurface();
surface_->SetEnableDCLayers(true);
gfx::Size window_size(100, 100);
EXPECT_TRUE(surface_->Resize(window_size, 1.0,
gl::GLSurface::ColorSpace::UNSPECIFIED, true));
Microsoft::WRL::ComPtr<ID3D11Device> d3d11_device =
gl::QueryD3D11DeviceObjectFromANGLE();
gfx::Size texture_size(50, 50);
Microsoft::WRL::ComPtr<ID3D11Texture2D> texture =
CreateNV12Texture(d3d11_device, texture_size, false);
scoped_refptr<gl::GLImageDXGI> image_dxgi(
new gl::GLImageDXGI(texture_size, nullptr));
image_dxgi->SetTexture(texture, 0);
image_dxgi->SetColorSpace(color_space);
ui::DCRendererLayerParams params(
false, gfx::Rect(), 1, gfx::Transform(),
std::vector<scoped_refptr<gl::GLImage>>{image_dxgi},
gfx::RectF(gfx::Rect(texture_size)), gfx::Rect(texture_size), 0, 0, 1.0,
0, false);
surface_->ScheduleDCLayer(params);
EXPECT_EQ(gfx::SwapResult::SWAP_ACK,
surface_->SwapBuffers(base::DoNothing()));
// Scaling up the swapchain with the same image should cause it to be
// transformed again, but not presented again.
ui::DCRendererLayerParams params2(
false, gfx::Rect(), 1, gfx::Transform(),
std::vector<scoped_refptr<gl::GLImage>>{image_dxgi},
gfx::RectF(gfx::Rect(texture_size)), gfx::Rect(window_size), 0, 0, 1.0,
0, false);
surface_->ScheduleDCLayer(params2);
EXPECT_EQ(gfx::SwapResult::SWAP_ACK,
surface_->SwapBuffers(base::DoNothing()));
Sleep(1000);
if (check_color) {
SkColor actual_color =
ReadBackWindowPixel(window_.hwnd(), gfx::Point(75, 75));
EXPECT_TRUE(AreColorsSimilar(expected_color, actual_color))
<< std::hex << "Expected " << expected_color << " Actual "
<< actual_color;
}
}
};
TEST_F(DirectCompositionVideoPixelTest, BT601) {
TestVideo(gfx::ColorSpace::CreateREC601(), SkColorSetRGB(0xdb, 0x81, 0xe8),
true);
}
TEST_F(DirectCompositionVideoPixelTest, BT709) {
TestVideo(gfx::ColorSpace::CreateREC709(), SkColorSetRGB(0xe1, 0x90, 0xeb),
true);
}
TEST_F(DirectCompositionVideoPixelTest, SRGB) {
// SRGB doesn't make sense on an NV12 input, but don't crash.
TestVideo(gfx::ColorSpace::CreateSRGB(), SK_ColorTRANSPARENT, false);
}
TEST_F(DirectCompositionVideoPixelTest, SCRGBLinear) {
// SCRGB doesn't make sense on an NV12 input, but don't crash.
TestVideo(gfx::ColorSpace::CreateSCRGBLinear(), SK_ColorTRANSPARENT, false);
}
TEST_F(DirectCompositionVideoPixelTest, InvalidColorSpace) {
// Invalid color space should be treated as BT.709
TestVideo(gfx::ColorSpace(), SkColorSetRGB(0xe1, 0x90, 0xeb), true);
}
TEST_F(DirectCompositionPixelTest, SoftwareVideoSwapchain) {
if (!CheckIfDCSupported())
return;
InitializeSurface();
surface_->SetEnableDCLayers(true);
gfx::Size window_size(100, 100);
EXPECT_TRUE(surface_->Resize(window_size, 1.0,
gl::GLSurface::ColorSpace::UNSPECIFIED, true));
Microsoft::WRL::ComPtr<ID3D11Device> d3d11_device =
gl::QueryD3D11DeviceObjectFromANGLE();
gfx::Size y_size(50, 50);
gfx::Size uv_size(25, 25);
size_t y_stride =
gfx::RowSizeForBufferFormat(y_size.width(), gfx::BufferFormat::R_8, 0);
size_t uv_stride =
gfx::RowSizeForBufferFormat(uv_size.width(), gfx::BufferFormat::RG_88, 0);
std::vector<uint8_t> y_data(y_stride * y_size.height(), 0xff);
std::vector<uint8_t> uv_data(uv_stride * uv_size.height(), 0xff);
scoped_refptr<gl::GLImageRefCountedMemory> y_image(
new gl::GLImageRefCountedMemory(y_size, GL_BGRA_EXT));
y_image->Initialize(new base::RefCountedBytes(y_data),
gfx::BufferFormat::R_8);
scoped_refptr<gl::GLImageRefCountedMemory> uv_image(
new gl::GLImageRefCountedMemory(uv_size, GL_BGRA_EXT));
uv_image->Initialize(new base::RefCountedBytes(uv_data),
gfx::BufferFormat::RG_88);
y_image->SetColorSpace(gfx::ColorSpace::CreateREC709());
ui::DCRendererLayerParams params(
false, gfx::Rect(), 1, gfx::Transform(),
std::vector<scoped_refptr<gl::GLImage>>{y_image, uv_image},
gfx::RectF(gfx::Rect(y_size)), gfx::Rect(window_size), 0, 0, 1.0, 0,
false);
surface_->ScheduleDCLayer(params);
EXPECT_EQ(gfx::SwapResult::SWAP_ACK,
surface_->SwapBuffers(base::DoNothing()));
Sleep(1000);
SkColor expected_color = SkColorSetRGB(0xff, 0xb7, 0xff);
SkColor actual_color =
ReadBackWindowPixel(window_.hwnd(), gfx::Point(75, 75));
EXPECT_TRUE(AreColorsSimilar(expected_color, actual_color))
<< std::hex << "Expected " << expected_color << " Actual "
<< actual_color;
}
TEST_F(DirectCompositionPixelTest, VideoHandleSwapchain) {
if (!CheckIfDCSupported())
return;
InitializeSurface();
surface_->SetEnableDCLayers(true);
gfx::Size window_size(100, 100);
EXPECT_TRUE(surface_->Resize(window_size, 1.0,
gl::GLSurface::ColorSpace::UNSPECIFIED, true));
Microsoft::WRL::ComPtr<ID3D11Device> d3d11_device =
gl::QueryD3D11DeviceObjectFromANGLE();
gfx::Size texture_size(50, 50);
Microsoft::WRL::ComPtr<ID3D11Texture2D> texture =
CreateNV12Texture(d3d11_device, texture_size, true);
Microsoft::WRL::ComPtr<IDXGIResource1> resource;
texture.CopyTo(resource.GetAddressOf());
HANDLE handle = 0;
resource->CreateSharedHandle(nullptr, DXGI_SHARED_RESOURCE_READ, nullptr,
&handle);
// The format doesn't matter, since we aren't binding.
scoped_refptr<gl::GLImageDXGIHandle> image_dxgi(
new gl::GLImageDXGIHandle(texture_size, 0, gfx::BufferFormat::RGBA_8888));
ASSERT_TRUE(image_dxgi->Initialize(base::win::ScopedHandle(handle)));
ui::DCRendererLayerParams params(
false, gfx::Rect(), 1, gfx::Transform(),
std::vector<scoped_refptr<gl::GLImage>>{image_dxgi},
gfx::RectF(gfx::Rect(texture_size)), gfx::Rect(window_size), 0, 0, 1.0, 0,
false);
surface_->ScheduleDCLayer(params);
EXPECT_EQ(gfx::SwapResult::SWAP_ACK,
surface_->SwapBuffers(base::DoNothing()));
Sleep(1000);
SkColor expected_color = SkColorSetRGB(0xe1, 0x90, 0xeb);
SkColor actual_color =
ReadBackWindowPixel(window_.hwnd(), gfx::Point(75, 75));
EXPECT_TRUE(AreColorsSimilar(expected_color, actual_color))
<< std::hex << "Expected " << expected_color << " Actual "
<< actual_color;
}
TEST_F(DirectCompositionPixelTest, SkipVideoLayerEmptyBoundsRect) {
if (!CheckIfDCSupported())
return;
InitializeSurface();
surface_->SetEnableDCLayers(true);
gfx::Size window_size(100, 100);
EXPECT_TRUE(surface_->Resize(window_size, 1.0,
gl::GLSurface::ColorSpace::UNSPECIFIED, true));
EXPECT_TRUE(surface_->SetDrawRectangle(gfx::Rect(window_size)));
glClearColor(0.0, 0.0, 0.0, 1.0);
glClear(GL_COLOR_BUFFER_BIT);
Microsoft::WRL::ComPtr<ID3D11Device> d3d11_device =
gl::QueryD3D11DeviceObjectFromANGLE();
gfx::Size texture_size(50, 50);
Microsoft::WRL::ComPtr<ID3D11Texture2D> texture =
CreateNV12Texture(d3d11_device, texture_size, true);
Microsoft::WRL::ComPtr<IDXGIResource1> resource;
texture.CopyTo(resource.GetAddressOf());
HANDLE handle = 0;
resource->CreateSharedHandle(nullptr, DXGI_SHARED_RESOURCE_READ, nullptr,
&handle);
// The format doesn't matter, since we aren't binding.
scoped_refptr<gl::GLImageDXGIHandle> image_dxgi(
new gl::GLImageDXGIHandle(texture_size, 0, gfx::BufferFormat::RGBA_8888));
ASSERT_TRUE(image_dxgi->Initialize(base::win::ScopedHandle(handle)));
// Layer with empty bounds rect.
gfx::Rect bounds_rect;
ui::DCRendererLayerParams params(
false, gfx::Rect(), 1, gfx::Transform(),
std::vector<scoped_refptr<gl::GLImage>>{image_dxgi},
gfx::RectF(gfx::Rect(texture_size)), bounds_rect, 0, 0, 1.0, 0, false);
surface_->ScheduleDCLayer(params);
EXPECT_EQ(gfx::SwapResult::SWAP_ACK,
surface_->SwapBuffers(base::DoNothing()));
Sleep(1000);
// No color is written since the visual committed to DirectComposition has no
// content.
SkColor expected_color = SK_ColorBLACK;
SkColor actual_color =
ReadBackWindowPixel(window_.hwnd(), gfx::Point(75, 75));
EXPECT_TRUE(AreColorsSimilar(expected_color, actual_color))
<< std::hex << "Expected " << expected_color << " Actual "
<< actual_color;
}
TEST_F(DirectCompositionPixelTest, SkipVideoLayerEmptyContentsRect) {
if (!CheckIfDCSupported())
return;
InitializeSurface();
// Swap chain size is overridden to content rect size only if scaled overlays
// are supported.
DirectCompositionSurfaceWin::EnableScaledOverlaysForTesting();
surface_->SetEnableDCLayers(true);
gfx::Size window_size(100, 100);
EXPECT_TRUE(surface_->Resize(window_size, 1.0,
gl::GLSurface::ColorSpace::UNSPECIFIED, true));
EXPECT_TRUE(surface_->SetDrawRectangle(gfx::Rect(window_size)));
glClearColor(0.0, 0.0, 0.0, 1.0);
glClear(GL_COLOR_BUFFER_BIT);
Microsoft::WRL::ComPtr<ID3D11Device> d3d11_device =
gl::QueryD3D11DeviceObjectFromANGLE();
gfx::Size texture_size(50, 50);
Microsoft::WRL::ComPtr<ID3D11Texture2D> texture =
CreateNV12Texture(d3d11_device, texture_size, true);
Microsoft::WRL::ComPtr<IDXGIResource1> resource;
texture.CopyTo(resource.GetAddressOf());
HANDLE handle = 0;
resource->CreateSharedHandle(nullptr, DXGI_SHARED_RESOURCE_READ, nullptr,
&handle);
// The format doesn't matter, since we aren't binding.
scoped_refptr<gl::GLImageDXGIHandle> image_dxgi(
new gl::GLImageDXGIHandle(texture_size, 0, gfx::BufferFormat::RGBA_8888));
ASSERT_TRUE(image_dxgi->Initialize(base::win::ScopedHandle(handle)));
// Layer with empty content rect.
gfx::RectF contents_rect;
ui::DCRendererLayerParams params(
false, gfx::Rect(), 1, gfx::Transform(),
std::vector<scoped_refptr<gl::GLImage>>{image_dxgi}, contents_rect,
gfx::Rect(window_size), 0, 0, 1.0, 0, false);
surface_->ScheduleDCLayer(params);
EXPECT_EQ(gfx::SwapResult::SWAP_ACK,
surface_->SwapBuffers(base::DoNothing()));
Sleep(1000);
// No color is written since the visual committed to DirectComposition has no
// content.
SkColor expected_color = SK_ColorBLACK;
SkColor actual_color =
ReadBackWindowPixel(window_.hwnd(), gfx::Point(75, 75));
EXPECT_TRUE(AreColorsSimilar(expected_color, actual_color))
<< std::hex << "Expected " << expected_color << " Actual "
<< actual_color;
}
} // namespace
} // namespace gpu