blob: 8fa08843f58a92a8a7cc1dfb8cfda0a72aac5d77 [file] [log] [blame]
// Copyright 2017 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/direct_composition_child_surface_win.h"
#include <d3d11_1.h>
#include <dcomptypes.h>
#include "base/debug/alias.h"
#include "base/debug/dump_without_crashing.h"
#include "base/metrics/histogram_functions.h"
#include "base/metrics/histogram_macros.h"
#include "base/notreached.h"
#include "base/process/process.h"
#include "base/synchronization/waitable_event.h"
#include "base/trace_event/trace_event.h"
#include "base/trace_event/traced_value.h"
#include "ui/gfx/color_space_win.h"
#include "ui/gfx/native_widget_types.h"
#include "ui/gl/direct_composition_support.h"
#include "ui/gl/egl_util.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_features.h"
#include "ui/gl/gl_surface_egl.h"
#include "ui/gl/gl_switches.h"
#include "ui/gl/gl_utils.h"
#ifndef EGL_ANGLE_d3d_texture_client_buffer
#define EGL_ANGLE_d3d_texture_client_buffer 1
#define EGL_D3D_TEXTURE_ANGLE 0x33A3
#define EGL_TEXTURE_OFFSET_X_ANGLE 0x3490
#define EGL_TEXTURE_OFFSET_Y_ANGLE 0x3491
#endif /* EGL_ANGLE_d3d_texture_client_buffer */
namespace gl {
namespace {
// Only one DirectComposition surface can be rendered into at a time. Track
// here which IDCompositionSurface is being rendered into. If another context
// is made current, then this surface will be suspended.
IDCompositionSurface* g_current_surface = nullptr;
const char* kDirectCompositionChildSurfaceLabel =
"DirectCompositionChildSurface";
bool IsVerifyDrawOffsetEnabled() {
return base::FeatureList::IsEnabled(
features::kDirectCompositionVerifyDrawOffset);
}
} // namespace
DirectCompositionChildSurfaceWin::DirectCompositionChildSurfaceWin(
GLDisplayEGL* display,
bool use_angle_texture_offset)
: GLSurfaceEGL(display),
use_angle_texture_offset_(use_angle_texture_offset) {}
DirectCompositionChildSurfaceWin::~DirectCompositionChildSurfaceWin() {
Destroy();
}
bool DirectCompositionChildSurfaceWin::Initialize(GLSurfaceFormat format) {
d3d11_device_ = QueryD3D11DeviceObjectFromANGLE();
dcomp_device_ = GetDirectCompositionDevice();
if (!dcomp_device_)
return false;
EGLint pbuffer_attribs[] = {
EGL_WIDTH, 1, EGL_HEIGHT, 1, EGL_NONE,
};
default_surface_ = eglCreatePbufferSurface(display_->GetDisplay(),
GetConfig(), pbuffer_attribs);
if (!default_surface_) {
DLOG(ERROR) << "eglCreatePbufferSurface failed with error "
<< ui::GetLastEGLErrorString();
return false;
}
return true;
}
bool DirectCompositionChildSurfaceWin::ReleaseDrawTexture(bool will_discard) {
EGLSurface egl_surface = real_surface_;
real_surface_ = nullptr;
// We make current with the same surface (could be the parent), but its
// handle has changed to |default_surface_|.
gl::GLContext* context = gl::GLContext::GetCurrent();
DCHECK(context);
gl::GLSurface* surface = gl::GLSurface::GetCurrent();
DCHECK(surface);
bool result = context->MakeCurrent(surface);
// If MakeCurrent fails (probably lost device), we'll want to return failure,
// but we still want to reset the rest of the state for consistency.
DLOG_IF(ERROR, !result) << "Failed to make current in ReleaseDrawTexture";
if (egl_surface)
eglDestroySurface(display_->GetDisplay(), egl_surface);
if (dcomp_surface_.Get() == g_current_surface)
g_current_surface = nullptr;
HRESULT hr, device_removed_reason;
if (draw_texture_) {
CopyOffscreenTextureToDrawTexture();
draw_texture_.Reset();
if (dcomp_surface_) {
TRACE_EVENT0("gpu", "DirectCompositionChildSurfaceWin::EndDraw");
hr = dcomp_surface_->EndDraw();
if (FAILED(hr)) {
DLOG(ERROR) << "EndDraw failed with error " << std::hex << hr;
return false;
}
dcomp_surface_serial_++;
} else if (!will_discard) {
const bool use_swap_chain_tearing =
DirectCompositionSwapChainTearingEnabled();
const bool force_present_interval_0 = base::FeatureList::IsEnabled(
features::kDXGISwapChainPresentInterval0);
UINT interval = first_swap_ || !vsync_enabled_ ||
use_swap_chain_tearing || force_present_interval_0
? 0
: 1;
UINT flags = use_swap_chain_tearing ? DXGI_PRESENT_ALLOW_TEARING : 0;
TRACE_EVENT2("gpu", "DirectCompositionChildSurfaceWin::PresentSwapChain",
"has_alpha", has_alpha_, "dirty_rect",
swap_rect_.ToString());
DXGI_PRESENT_PARAMETERS params = {};
RECT dirty_rect = swap_rect_.ToRECT();
params.DirtyRectsCount = 1;
params.pDirtyRects = &dirty_rect;
hr = swap_chain_->Present1(interval, flags, &params);
// Ignore DXGI_STATUS_OCCLUDED since that's not an error but only
// indicates that the window is occluded and we can stop rendering.
if (FAILED(hr) && hr != DXGI_STATUS_OCCLUDED) {
DLOG(ERROR) << "Present1 failed with error " << std::hex << hr;
return false;
}
if (first_swap_) {
// Wait for the GPU to finish executing its commands before
// committing the DirectComposition tree, or else the swapchain
// may flicker black when it's first presented.
first_swap_ = false;
Microsoft::WRL::ComPtr<IDXGIDevice2> dxgi_device2;
d3d11_device_.As(&dxgi_device2);
DCHECK(dxgi_device2);
base::WaitableEvent event(
base::WaitableEvent::ResetPolicy::AUTOMATIC,
base::WaitableEvent::InitialState::NOT_SIGNALED);
hr = dxgi_device2->EnqueueSetEvent(event.handle());
if (SUCCEEDED(hr)) {
event.Wait();
} else {
device_removed_reason = d3d11_device_->GetDeviceRemovedReason();
base::debug::Alias(&hr);
base::debug::Alias(&device_removed_reason);
base::debug::DumpWithoutCrashing();
}
}
}
}
return result;
}
void DirectCompositionChildSurfaceWin::Destroy() {
if (default_surface_) {
if (!eglDestroySurface(display_->GetDisplay(), default_surface_)) {
DLOG(ERROR) << "eglDestroySurface failed with error "
<< ui::GetLastEGLErrorString();
}
default_surface_ = nullptr;
}
if (real_surface_) {
if (!eglDestroySurface(display_->GetDisplay(), real_surface_)) {
DLOG(ERROR) << "eglDestroySurface failed with error "
<< ui::GetLastEGLErrorString();
}
real_surface_ = nullptr;
}
if (dcomp_surface_ && (dcomp_surface_.Get() == g_current_surface)) {
CopyOffscreenTextureToDrawTexture();
HRESULT hr = dcomp_surface_->EndDraw();
if (FAILED(hr))
DLOG(ERROR) << "EndDraw failed with error " << std::hex << hr;
g_current_surface = nullptr;
}
draw_texture_.Reset();
offscreen_texture_.Reset();
dcomp_surface_.Reset();
}
gfx::Size DirectCompositionChildSurfaceWin::GetSize() {
return size_;
}
bool DirectCompositionChildSurfaceWin::IsOffscreen() {
return false;
}
void* DirectCompositionChildSurfaceWin::GetHandle() {
return real_surface_ ? real_surface_ : default_surface_;
}
gfx::SwapResult DirectCompositionChildSurfaceWin::SwapBuffers(
PresentationCallback callback,
gfx::FrameData data) {
NOTREACHED();
return gfx::SwapResult::SWAP_FAILED;
}
bool DirectCompositionChildSurfaceWin::EndDraw(gfx::Rect* swap_rect) {
TRACE_EVENT1("gpu", "DirectCompositionChildSurfaceWin::EndDraw", "size",
size_.ToString());
bool success = ReleaseDrawTexture(false /* will_discard */);
*swap_rect = swap_rect_;
// Reset swap_rect_ since SetDrawRectangle may not be called when the root
// damage rect is empty.
swap_rect_ = gfx::Rect();
return success;
}
gfx::SurfaceOrigin DirectCompositionChildSurfaceWin::GetOrigin() const {
return gfx::SurfaceOrigin::kTopLeft;
}
bool DirectCompositionChildSurfaceWin::SupportsPostSubBuffer() {
return true;
}
bool DirectCompositionChildSurfaceWin::OnMakeCurrent(GLContext* context) {
if (g_current_surface != dcomp_surface_.Get()) {
if (g_current_surface) {
HRESULT hr = g_current_surface->SuspendDraw();
if (FAILED(hr)) {
DLOG(ERROR) << "SuspendDraw failed with error " << std::hex << hr;
return false;
}
g_current_surface = nullptr;
}
// We're in the middle of |dcomp_surface_| draw only if |draw_texture_| is
// not null.
if (dcomp_surface_ && draw_texture_) {
HRESULT hr = dcomp_surface_->ResumeDraw();
if (FAILED(hr)) {
DLOG(ERROR) << "ResumeDraw failed with error " << std::hex << hr;
return false;
}
g_current_surface = dcomp_surface_.Get();
}
}
return true;
}
bool DirectCompositionChildSurfaceWin::SupportsDCLayers() const {
return true;
}
bool DirectCompositionChildSurfaceWin::SetDrawRectangle(
const gfx::Rect& rectangle) {
if (!gfx::Rect(size_).Contains(rectangle)) {
DLOG(ERROR) << "Draw rectangle must be contained within size of surface";
return false;
}
if (draw_texture_) {
DLOG(ERROR) << "SetDrawRectangle must be called only once per swap buffers";
return false;
}
DCHECK(!real_surface_);
DCHECK(!g_current_surface);
if (gfx::Rect(size_) != rectangle && !swap_chain_ && !dcomp_surface_) {
DLOG(ERROR) << "First draw to surface must draw to everything";
return false;
}
gl::GLContext* context = gl::GLContext::GetCurrent();
if (!context) {
DLOG(ERROR) << "gl::GLContext::GetCurrent() returned nullptr";
return false;
}
gl::GLSurface* surface = gl::GLSurface::GetCurrent();
if (!surface) {
DLOG(ERROR) << "gl::GLSurface::GetCurrent() returned nullptr";
return false;
}
DXGI_FORMAT dxgi_format = gfx::ColorSpaceWin::GetDXGIFormat(color_space_);
// IDCompositionDevice2::CreateSurface does not support rgb10. In cases where
// dc overlays are to be used for rgb10, use swap chains instead.
if (!dcomp_surface_ && enable_dc_layers_ &&
dxgi_format != DXGI_FORMAT::DXGI_FORMAT_R10G10B10A2_UNORM) {
TRACE_EVENT2("gpu", "DirectCompositionChildSurfaceWin::CreateSurface",
"width", size_.width(), "height", size_.height());
swap_chain_.Reset();
// Always treat as premultiplied, because an underlay could cause it to
// become transparent.
HRESULT hr = dcomp_device_->CreateSurface(
size_.width(), size_.height(), dxgi_format,
DXGI_ALPHA_MODE_PREMULTIPLIED, &dcomp_surface_);
base::UmaHistogramSparse("GPU.DirectComposition.DcompDeviceCreateSurface",
hr);
if (FAILED(hr)) {
DLOG(ERROR) << "CreateSurface failed with error " << std::hex << hr;
// Disable direct composition because CreateSurface might fail again next
// time.
SetDirectCompositionSwapChainFailed();
return false;
}
// Use swap chains for rgb10 because dcomp surfaces cannot be created.
} else if (!swap_chain_ &&
(!enable_dc_layers_ ||
dxgi_format == DXGI_FORMAT::DXGI_FORMAT_R10G10B10A2_UNORM)) {
TRACE_EVENT2("gpu", "DirectCompositionChildSurfaceWin::CreateSwapChain",
"width", size_.width(), "height", size_.height());
offscreen_texture_.Reset();
dcomp_surface_.Reset();
Microsoft::WRL::ComPtr<IDXGIDevice> dxgi_device;
d3d11_device_.As(&dxgi_device);
DCHECK(dxgi_device);
Microsoft::WRL::ComPtr<IDXGIAdapter> dxgi_adapter;
dxgi_device->GetAdapter(&dxgi_adapter);
DCHECK(dxgi_adapter);
Microsoft::WRL::ComPtr<IDXGIFactory2> dxgi_factory;
dxgi_adapter->GetParent(IID_PPV_ARGS(&dxgi_factory));
DCHECK(dxgi_factory);
DXGI_SWAP_CHAIN_DESC1 desc = {};
desc.Width = size_.width();
desc.Height = size_.height();
desc.Format = dxgi_format;
desc.Stereo = FALSE;
desc.SampleDesc.Count = 1;
desc.BufferCount = gl::DirectCompositionRootSurfaceBufferCount();
desc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
desc.Scaling = DXGI_SCALING_STRETCH;
desc.SwapEffect = DXGI_SWAP_EFFECT_FLIP_SEQUENTIAL;
desc.AlphaMode =
has_alpha_ ? DXGI_ALPHA_MODE_PREMULTIPLIED : DXGI_ALPHA_MODE_IGNORE;
desc.Flags = 0;
if (DirectCompositionSwapChainTearingEnabled()) {
desc.Flags |= DXGI_SWAP_CHAIN_FLAG_ALLOW_TEARING;
}
if (DXGIWaitableSwapChainEnabled()) {
desc.Flags |= DXGI_SWAP_CHAIN_FLAG_FRAME_LATENCY_WAITABLE_OBJECT;
}
HRESULT hr = dxgi_factory->CreateSwapChainForComposition(
d3d11_device_.Get(), &desc, nullptr, &swap_chain_);
first_swap_ = true;
base::UmaHistogramSparse(
"GPU.DirectComposition.CreateSwapChainForComposition", hr);
// If CreateSwapChainForComposition fails, we cannot draw to the
// browser window. Return false after disabling Direct Composition support
// and let the Renderer handle it. Either the GPU command buffer or the GPU
// process will be restarted.
if (FAILED(hr)) {
DLOG(ERROR) << "CreateSwapChainForComposition failed with error "
<< std::hex << hr;
// Disable direct composition because SwapChain creation might fail again
// next time.
SetDirectCompositionSwapChainFailed();
return false;
}
gl::LabelSwapChainAndBuffers(swap_chain_.Get(),
kDirectCompositionChildSurfaceLabel);
Microsoft::WRL::ComPtr<IDXGISwapChain3> swap_chain;
if (SUCCEEDED(swap_chain_.As(&swap_chain))) {
hr = swap_chain->SetColorSpace1(
gfx::ColorSpaceWin::GetDXGIColorSpace(color_space_));
DCHECK(SUCCEEDED(hr))
<< "SetColorSpace1 failed with error " << std::hex << hr;
if (DXGIWaitableSwapChainEnabled()) {
hr = swap_chain->SetMaximumFrameLatency(
GetDXGIWaitableSwapChainMaxQueuedFrames());
DCHECK(SUCCEEDED(hr)) << "SetMaximumFrameLatency failed with error "
<< logging::SystemErrorCodeToString(hr);
}
}
}
swap_rect_ = rectangle;
draw_offset_ = gfx::Vector2d();
const bool verify_draw_offset = dcomp_surface_ && IsVerifyDrawOffsetEnabled();
if (dcomp_surface_) {
TRACE_EVENT0("gpu", "DirectCompositionChildSurfaceWin::BeginDraw");
const RECT rect = rectangle.ToRECT();
dcomp_update_offset_ = {};
HRESULT hr = dcomp_surface_->BeginDraw(&rect, IID_PPV_ARGS(&draw_texture_),
&dcomp_update_offset_);
if (FAILED(hr)) {
DLOG(ERROR) << "BeginDraw failed with error " << std::hex << hr;
return false;
}
if (verify_draw_offset) {
draw_offset_ = {features::kVerifyDrawOffsetX.Get(),
features::kVerifyDrawOffsetY.Get()};
} else {
draw_offset_ = gfx::Point(dcomp_update_offset_) - rectangle.origin();
}
} else {
TRACE_EVENT0("gpu", "DirectCompositionChildSurfaceWin::GetBuffer");
swap_chain_->GetBuffer(0, IID_PPV_ARGS(&draw_texture_));
}
DCHECK(draw_texture_);
g_current_surface = dcomp_surface_.Get();
std::vector<EGLint> pbuffer_attribs;
pbuffer_attribs.push_back(EGL_WIDTH);
pbuffer_attribs.push_back(size_.width());
pbuffer_attribs.push_back(EGL_HEIGHT);
pbuffer_attribs.push_back(size_.height());
if (use_angle_texture_offset_) {
pbuffer_attribs.push_back(EGL_TEXTURE_OFFSET_X_ANGLE);
pbuffer_attribs.push_back(draw_offset_.x());
pbuffer_attribs.push_back(EGL_TEXTURE_OFFSET_Y_ANGLE);
pbuffer_attribs.push_back(draw_offset_.y());
}
pbuffer_attribs.push_back(EGL_NONE);
EGLClientBuffer buffer =
reinterpret_cast<EGLClientBuffer>(draw_texture_.Get());
if (verify_draw_offset) {
buffer = reinterpret_cast<EGLClientBuffer>(GetOffscreenTexture().Get());
}
real_surface_ = eglCreatePbufferFromClientBuffer(
display_->GetDisplay(), EGL_D3D_TEXTURE_ANGLE, buffer, GetConfig(),
pbuffer_attribs.data());
if (!real_surface_) {
DLOG(ERROR) << "eglCreatePbufferFromClientBuffer failed with error "
<< ui::GetLastEGLErrorString();
return false;
}
// We make current with the same surface (could be the parent), but its
// handle has changed to |real_surface_|.
if (!context->MakeCurrent(surface)) {
DLOG(ERROR) << "Failed to make current in SetDrawRectangle";
return false;
}
return true;
}
void DirectCompositionChildSurfaceWin::SetDCompSurfaceForTesting(
Microsoft::WRL::ComPtr<IDCompositionSurface> surface) {
offscreen_texture_.Reset();
dcomp_surface_ = std::move(surface);
}
gfx::Vector2d DirectCompositionChildSurfaceWin::GetDrawOffset() const {
return use_angle_texture_offset_ ? gfx::Vector2d() : draw_offset_;
}
void DirectCompositionChildSurfaceWin::SetVSyncEnabled(bool enabled) {
vsync_enabled_ = enabled;
}
bool DirectCompositionChildSurfaceWin::Resize(
const gfx::Size& size,
float scale_factor,
const gfx::ColorSpace& color_space,
bool has_alpha) {
if (size_ == size && has_alpha_ == has_alpha && color_space_ == color_space)
return true;
// This will release indirect references to swap chain (|real_surface_|) by
// binding |default_surface_| as the default framebuffer.
if (!ReleaseDrawTexture(true /* will_discard */))
return false;
bool resize_only = has_alpha_ == has_alpha && color_space_ == color_space;
size_ = size;
color_space_ = color_space;
has_alpha_ = has_alpha;
// ResizeBuffers can't change alpha blending mode.
if (swap_chain_ && resize_only) {
UINT buffer_count = gl::DirectCompositionRootSurfaceBufferCount();
DXGI_FORMAT format = gfx::ColorSpaceWin::GetDXGIFormat(color_space_);
UINT flags = 0;
if (DirectCompositionSwapChainTearingEnabled()) {
flags |= DXGI_SWAP_CHAIN_FLAG_ALLOW_TEARING;
}
if (DXGIWaitableSwapChainEnabled()) {
flags |= DXGI_SWAP_CHAIN_FLAG_FRAME_LATENCY_WAITABLE_OBJECT;
}
HRESULT hr = swap_chain_->ResizeBuffers(buffer_count, size.width(),
size.height(), format, flags);
UMA_HISTOGRAM_BOOLEAN("GPU.DirectComposition.SwapChainResizeResult",
SUCCEEDED(hr));
if (SUCCEEDED(hr)) {
// Resizing swap chain buffers causes the internal textures to be released
// and re-created as new textures. We need to label the new textures.
gl::LabelSwapChainBuffers(swap_chain_.Get(),
kDirectCompositionChildSurfaceLabel);
return true;
}
DLOG(ERROR) << "ResizeBuffers failed with error 0x" << std::hex << hr;
}
// Next SetDrawRectangle call will recreate the swap chain or surface.
swap_chain_.Reset();
offscreen_texture_.Reset();
dcomp_surface_.Reset();
return true;
}
bool DirectCompositionChildSurfaceWin::SetEnableDCLayers(bool enable) {
if (enable_dc_layers_ == enable)
return true;
enable_dc_layers_ = enable;
if (!ReleaseDrawTexture(true /* will_discard */))
return false;
// Next SetDrawRectangle call will recreate the swap chain or surface.
swap_chain_.Reset();
offscreen_texture_.Reset();
dcomp_surface_.Reset();
return true;
}
Microsoft::WRL::ComPtr<ID3D11Texture2D>
DirectCompositionChildSurfaceWin::GetOffscreenTexture() {
if (!dcomp_surface_) {
return offscreen_texture_ = nullptr;
}
if (offscreen_texture_) {
return offscreen_texture_;
}
D3D11_TEXTURE2D_DESC desc = {};
desc.Width = size_.width() + features::kVerifyDrawOffsetX.Get();
desc.Height = size_.height() + features::kVerifyDrawOffsetY.Get();
desc.MipLevels = 1;
desc.ArraySize = 1;
desc.Format = gfx::ColorSpaceWin::GetDXGIFormat(color_space_);
desc.SampleDesc.Count = 1;
desc.BindFlags = D3D11_BIND_RENDER_TARGET;
d3d11_device_->CreateTexture2D(&desc, nullptr, &offscreen_texture_);
return offscreen_texture_;
}
void DirectCompositionChildSurfaceWin::CopyOffscreenTextureToDrawTexture() {
if (!offscreen_texture_ || !draw_texture_ || !dcomp_surface_) {
return;
}
D3D11_BOX box = {};
box.left = swap_rect_.origin().x() + features::kVerifyDrawOffsetX.Get();
box.top = swap_rect_.origin().y() + features::kVerifyDrawOffsetY.Get();
box.right = box.left + swap_rect_.width();
box.bottom = box.top + swap_rect_.height();
box.front = 0;
box.back = 1;
Microsoft::WRL::ComPtr<ID3D11DeviceContext> context;
d3d11_device_->GetImmediateContext(&context);
context->CopySubresourceRegion(draw_texture_.Get(), 0, dcomp_update_offset_.x,
dcomp_update_offset_.y, 0,
offscreen_texture_.Get(), 0, &box);
}
} // namespace gl