// 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 "gpu/command_buffer/service/shared_image/dcomp_surface_image_backing.h"
#include "base/metrics/histogram_functions.h"
#include "base/trace_event/trace_event.h"
#include "components/viz/common/resources/shared_image_format_utils.h"
#include "gpu/command_buffer/common/shared_image_usage.h"
#include "gpu/command_buffer/service/shared_context_state.h"
#include "gpu/command_buffer/service/shared_image/d3d_image_utils.h"
#include "gpu/command_buffer/service/shared_image/shared_image_format_service_utils.h"
#include "third_party/angle/include/EGL/egl.h"
#include "third_party/angle/include/EGL/eglext.h"
#include "third_party/angle/include/EGL/eglext_angle.h"
#include "third_party/skia/include/core/SkAlphaType.h"
#include "third_party/skia/include/core/SkSurface.h"
#include "third_party/skia/include/gpu/GrBackendSurface.h"
#include "third_party/skia/include/gpu/ganesh/SkSurfaceGanesh.h"
#include "third_party/skia/include/gpu/ganesh/gl/GrGLBackendSurface.h"
#include "third_party/skia/include/gpu/gl/GrGLTypes.h"
#include "ui/gfx/buffer_format_util.h"
#include "ui/gfx/color_space_win.h"
#include "ui/gl/debug_utils.h"
#include "ui/gl/direct_composition_support.h"
#include "ui/gl/egl_util.h"
#include "ui/gl/gl_bindings.h"
#include "ui/gl/gl_context.h"
#include "ui/gl/gl_surface.h"
#include "ui/gl/gl_surface_egl.h"
#if BUILDFLAG(SKIA_USE_DAWN)
#include "gpu/command_buffer/service/dawn_context_provider.h"
#include "gpu/command_buffer/service/shared_image/skia_graphite_dawn_image_representation.h"
#endif
namespace gpu {
namespace {
// Ensure that the full bounds of |surface| have been drawn to, so subsequent
// |BeginDraw| calls are not required to cover the entire surface.
bool InitializeDCompSurface(IDCompositionSurface* surface,
const gfx::Size& surface_size) {
HRESULT hr = S_OK;
RECT rect = gfx::Rect(surface_size).ToRECT();
Microsoft::WRL::ComPtr<ID3D11Texture2D> draw_texture;
POINT update_offset = {};
hr = surface->BeginDraw(&rect, IID_PPV_ARGS(&draw_texture), &update_offset);
if (FAILED(hr)) {
LOG(ERROR) << "BeginDraw failed: " << logging::SystemErrorCodeToString(hr);
return false;
}
#if DCHECK_IS_ON()
const SkColor4f initialize_color = SkColors::kBlue;
#else
const SkColor4f initialize_color = SkColors::kTransparent;
#endif
// DX11 protects the DComp surface atlas so this clear only affects pixels in
// the update rect.
if (!ClearD3D11TextureToColor(draw_texture, initialize_color)) {
return false;
}
hr = surface->EndDraw();
if (FAILED(hr)) {
LOG(ERROR) << "EndDraw failed: " << logging::SystemErrorCodeToString(hr);
return false;
}
return true;
}
} // namespace
// An EGL surface that can temporarily encapsulate a D3D texture (and ANGLE
// draw offset). This is used so Skia can draw into the draw texture returned
// by IDCompositionSurface::BeginDraw.
class DCompSurfaceImageBacking::D3DTextureGLSurfaceEGL
: public gl::GLSurfaceEGL {
public:
D3DTextureGLSurfaceEGL(gl::GLDisplayEGL* display, const gfx::Size& size)
: GLSurfaceEGL(display), size_(size) {}
D3DTextureGLSurfaceEGL(const D3DTextureGLSurfaceEGL&) = delete;
D3DTextureGLSurfaceEGL& operator=(const D3DTextureGLSurfaceEGL&) = delete;
// Implement GLSurface.
bool Initialize(gl::GLSurfaceFormat format) override {
if (display_->GetDisplay() == EGL_NO_DISPLAY) {
LOG(ERROR)
<< "Trying to create D3DTextureGLSurfaceEGL with invalid display.";
return false;
}
if (size_.IsEmpty()) {
LOG(ERROR) << "Trying to create D3DTextureGLSurfaceEGL with empty size.";
return false;
}
return true;
}
void Destroy() override {
if (surface_) {
if (!eglDestroySurface(display_->GetDisplay(), surface_)) {
LOG(ERROR) << "eglDestroySurface failed with error "
<< ui::GetLastEGLErrorString();
}
surface_ = nullptr;
}
}
bool IsOffscreen() override { return true; }
gfx::SwapResult SwapBuffers(PresentationCallback callback,
gfx::FrameData data) override {
NOTREACHED_IN_MIGRATION()
<< "Attempted to call SwapBuffers on a D3DTextureGLSurfaceEGL.";
return gfx::SwapResult::SWAP_FAILED;
}
gfx::Size GetSize() override { return size_; }
EGLSurface GetHandle() override { return surface_; }
// Bind a texture to a pbuffer and use the resulting surface as this EGL
// surface. The offset will be set as the EGL_TEXTURE_OFFSET_{X,Y}_ANGLE
// pbuffer attributes. Call |Destroy| to destroy and un-set the pbuffer
// surface.
bool BindTextureToSurface(ID3D11Texture2D* texture,
const gfx::Vector2d& draw_offset) {
DCHECK(!surface_);
DCHECK(texture);
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());
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>(texture);
surface_ = eglCreatePbufferFromClientBuffer(
display_->GetDisplay(), EGL_D3D_TEXTURE_ANGLE, buffer, GetConfig(),
pbuffer_attribs.data());
if (!surface_) {
LOG(ERROR) << "eglCreatePbufferFromClientBuffer failed with error "
<< ui::GetLastEGLErrorString();
return false;
}
return true;
}
protected:
~D3DTextureGLSurfaceEGL() override {
InvalidateWeakPtrs();
Destroy();
}
private:
gfx::Size size_;
EGLSurface surface_ = nullptr;
};
// static
std::unique_ptr<DCompSurfaceImageBacking> DCompSurfaceImageBacking::Create(
const Mailbox& mailbox,
viz::SharedImageFormat format,
DXGI_FORMAT internal_format,
const gfx::Size& size,
const gfx::ColorSpace& color_space,
GrSurfaceOrigin surface_origin,
SkAlphaType alpha_type,
gpu::SharedImageUsageSet usage,
std::string debug_label) {
// IDCompositionSurface only supports the following formats:
// https://learn.microsoft.com/en-us/windows/win32/api/dcomp/nf-dcomp-idcompositiondevice2-createsurface#remarks
DCHECK(internal_format == DXGI_FORMAT_B8G8R8A8_UNORM ||
internal_format == DXGI_FORMAT_R8G8B8A8_UNORM ||
internal_format == DXGI_FORMAT_R16G16B16A16_FLOAT)
<< "Incompatible DXGI_FORMAT = " << internal_format;
TRACE_EVENT2("gpu", "DCompSurfaceImageBacking::Create", "width", size.width(),
"height", size.height());
// Always treat as premultiplied, because an underlay could cause it to
// become transparent.
Microsoft::WRL::ComPtr<IDCompositionSurface> dcomp_surface;
HRESULT hr = gl::GetDirectCompositionDevice()->CreateSurface(
size.width(), size.height(), internal_format,
SkAlphaTypeIsOpaque(alpha_type) ? DXGI_ALPHA_MODE_IGNORE
: DXGI_ALPHA_MODE_PREMULTIPLIED,
&dcomp_surface);
base::UmaHistogramSparse("GPU.DirectComposition.DcompDeviceCreateSurface",
hr);
if (FAILED(hr)) {
DLOG(ERROR) << "CreateSurface failed: "
<< logging::SystemErrorCodeToString(hr);
// Disable direct composition because CreateSurface might fail again next
// time.
gl::SetDirectCompositionSwapChainFailed();
return nullptr;
}
return base::WrapUnique(new DCompSurfaceImageBacking(
mailbox, format, size, color_space, surface_origin, alpha_type, usage,
std::move(debug_label), std::move(dcomp_surface)));
}
DCompSurfaceImageBacking::DCompSurfaceImageBacking(
const Mailbox& mailbox,
viz::SharedImageFormat format,
const gfx::Size& size,
const gfx::ColorSpace& color_space,
GrSurfaceOrigin surface_origin,
SkAlphaType alpha_type,
gpu::SharedImageUsageSet usage,
std::string debug_label,
Microsoft::WRL::ComPtr<IDCompositionSurface> dcomp_surface)
: ClearTrackingSharedImageBacking(
mailbox,
format,
size,
color_space,
surface_origin,
alpha_type,
usage,
std::move(debug_label),
gfx::BufferSizeForBufferFormat(size, ToBufferFormat(format)),
/*is_thread_safe=*/false),
gl_surface_(scoped_refptr(
new D3DTextureGLSurfaceEGL(gl::GLSurfaceEGL::GetGLDisplayEGL(),
size))),
dcomp_surface_(std::move(dcomp_surface)) {
const bool has_scanout = usage.Has(SHARED_IMAGE_USAGE_SCANOUT);
const bool write_only = !usage.Has(SHARED_IMAGE_USAGE_DISPLAY_READ) &&
usage.Has(SHARED_IMAGE_USAGE_DISPLAY_WRITE);
DCHECK(has_scanout);
DCHECK(write_only);
DCHECK(dcomp_surface_);
bool success = gl_surface_->Initialize(gl::GLSurfaceFormat());
DCHECK(success);
}
DCompSurfaceImageBacking::~DCompSurfaceImageBacking() = default;
SharedImageBackingType DCompSurfaceImageBacking::GetType() const {
return SharedImageBackingType::kDCompSurface;
}
void DCompSurfaceImageBacking::Update(std::unique_ptr<gfx::GpuFence> in_fence) {
DCHECK(!in_fence);
}
std::unique_ptr<OverlayImageRepresentation>
DCompSurfaceImageBacking::ProduceOverlay(SharedImageManager* manager,
MemoryTypeTracker* tracker) {
return std::make_unique<DCompSurfaceOverlayImageRepresentation>(manager, this,
tracker);
}
std::unique_ptr<SkiaGaneshImageRepresentation>
DCompSurfaceImageBacking::ProduceSkiaGanesh(
SharedImageManager* manager,
MemoryTypeTracker* tracker,
scoped_refptr<SharedContextState> context_state) {
DCHECK_EQ(context_state->gr_context_type(), GrContextType::kGL);
return std::make_unique<DCompSurfaceSkiaGaneshImageRepresentation>(
std::move(context_state), manager, this, tracker);
}
std::unique_ptr<SkiaGraphiteImageRepresentation>
DCompSurfaceImageBacking::ProduceSkiaGraphite(
SharedImageManager* manager,
MemoryTypeTracker* tracker,
scoped_refptr<SharedContextState> context_state) {
#if BUILDFLAG(SKIA_USE_DAWN)
DCHECK_EQ(context_state->gr_context_type(), GrContextType::kGraphiteDawn);
auto device = context_state->dawn_context_provider()->GetDevice();
auto dawn_representation =
std::make_unique<DCompSurfaceDawnImageRepresentation>(
manager, this, tracker, device, wgpu::BackendType::D3D11);
return SkiaGraphiteDawnImageRepresentation::Create(
std::move(dawn_representation), context_state,
context_state->gpu_main_graphite_recorder(), manager, this, tracker);
#else
NOTREACHED();
#endif // BUILDFLAG(SKIA_USE_DAWN)
}
Microsoft::WRL::ComPtr<ID3D11Texture2D> DCompSurfaceImageBacking::BeginDraw(
const gfx::Rect& update_rect,
gfx::Point& update_offset_out) {
if (update_rect.IsEmpty()) {
DLOG(ERROR) << "Draw rectangle must be non-empty";
return nullptr;
}
if (!gfx::Rect(size()).Contains(update_rect)) {
DLOG(ERROR) << "Draw rectangle must be contained within size of surface";
return nullptr;
}
// SharedImage allows an incomplete first draw so long as we only read from
// the part that we've previously drawn to. However, IDCompositionSurface
// requires a full draw on the first |BeginDraw|. To make an incomplete first
// draw valid, we'll initialize all the pixels and expand the swap rect.
if (!IsCleared() && gfx::Rect(size()) != update_rect) {
if (!InitializeDCompSurface(dcomp_surface_.Get(), size())) {
LOG(ERROR) << "Could not initialize DComp surface";
return nullptr;
}
}
TRACE_EVENT0("gpu", "DCompSurfaceImageBacking::BeginDraw");
const RECT rect = update_rect.ToRECT();
Microsoft::WRL::ComPtr<ID3D11Texture2D> draw_texture;
POINT update_offset = {};
HRESULT hr = dcomp_surface_->BeginDraw(&rect, IID_PPV_ARGS(&draw_texture),
&update_offset);
if (FAILED(hr)) {
DCHECK_NE(hr, DCOMPOSITION_ERROR_SURFACE_BEING_RENDERED)
<< "Concurrent writes to multiple DCompSurfaceImageBacking "
"not allowed.";
LOG(ERROR) << "BeginDraw failed: " << logging::SystemErrorCodeToString(hr);
return nullptr;
}
update_offset_out = gfx::Point(update_offset.x, update_offset.y);
return draw_texture;
}
void DCompSurfaceImageBacking::EndDraw() {
TRACE_EVENT0("gpu", "DCompSurfaceImageBacking::EndDraw");
HRESULT hr = dcomp_surface_->EndDraw();
if (FAILED(hr)) {
DLOG(ERROR) << "EndDraw failed: " << logging::SystemErrorCodeToString(hr);
return;
}
dcomp_surface_serial_++;
}
sk_sp<SkSurface> DCompSurfaceImageBacking::BeginDrawGanesh(
SharedContextState* context_state,
int final_msaa_count,
const SkSurfaceProps& surface_props,
const gfx::Rect& update_rect) {
gfx::Point update_offset;
auto draw_texture = BeginDraw(update_rect, update_offset);
if (!draw_texture) {
return nullptr;
}
// Offset the BeginDraw offset by the update rect's origin so we don't have to
// change the coordinate space of our DDL.
gfx::Vector2d draw_offset = update_offset - update_rect.origin();
if (!gl_surface_->BindTextureToSurface(draw_texture.Get(), draw_offset)) {
DLOG(ERROR) << "Could not bind texture to GLSurface";
return nullptr;
}
// Bind the default framebuffer to a SkSurface
scoped_make_current_.emplace(context_state->context(), gl_surface_.get());
if (!scoped_make_current_->IsContextCurrent()) {
DLOG(ERROR) << "Failed to make current in BeginDraw";
return nullptr;
}
GrGLFramebufferInfo framebuffer_info = {0};
DCHECK_EQ(gl_surface_->GetBackingFramebufferObject(), 0u);
SkColorType color_type = viz::ToClosestSkColorType(
/*gpu_compositing=*/true, format());
switch (color_type) {
case kRGBA_8888_SkColorType:
framebuffer_info.fFormat = GL_RGBA8;
break;
case kRGB_888x_SkColorType:
framebuffer_info.fFormat = GL_RGB8;
break;
case kRGB_565_SkColorType:
framebuffer_info.fFormat = GL_RGB565;
break;
case kRGBA_1010102_SkColorType:
framebuffer_info.fFormat = GL_RGB10_A2_EXT;
break;
case kRGBA_F16_SkColorType:
framebuffer_info.fFormat = GL_RGBA16F;
break;
case kBGRA_8888_SkColorType:
framebuffer_info.fFormat = GL_BGRA8_EXT;
break;
default:
NOTREACHED_IN_MIGRATION() << "color_type: " << color_type;
}
auto render_target = GrBackendRenderTargets::MakeGL(
size().width(), size().height(), final_msaa_count, 0, framebuffer_info);
auto surface = SkSurfaces::WrapBackendRenderTarget(
context_state->gr_context(), render_target, surface_origin(), color_type,
color_space().ToSkColorSpace(), &surface_props);
DCHECK(surface);
return surface;
}
void DCompSurfaceImageBacking::EndDrawGanesh() {
DCHECK_EQ(gl::GLSurface::GetCurrent(), gl_surface_);
gl_surface_->Destroy();
scoped_make_current_.reset();
EndDraw();
}
wgpu::Texture DCompSurfaceImageBacking::BeginDrawDawn(
const wgpu::Device& device,
const wgpu::TextureUsage usage,
const wgpu::TextureUsage internal_usage,
const gfx::Rect& update_rect) {
auto draw_texture = BeginDraw(update_rect, dcomp_update_offset_);
if (!draw_texture) {
return nullptr;
}
if (!dcomp_surface_draw_texture_copy_) {
Microsoft::WRL::ComPtr<ID3D11Device> d3d11_device;
draw_texture->GetDevice(&d3d11_device);
D3D11_TEXTURE2D_DESC d3d11_texture_desc;
draw_texture->GetDesc(&d3d11_texture_desc);
// Textures from DComp are guarded and not textureable. Graphite cannot
// render to it. We need to create an intermediate texture with
// D3D11_BIND_SHADER_RESOURCE and without D3D11_RESOURCE_MISC_GUARDED.
// The EndDraw() will copy the content from the intermediate texture back.
d3d11_texture_desc.Width = size().width();
d3d11_texture_desc.Height = size().height();
d3d11_texture_desc.BindFlags |= D3D11_BIND_SHADER_RESOURCE;
d3d11_texture_desc.MiscFlags &= ~D3D11_RESOURCE_MISC_GUARDED;
HRESULT hr = d3d11_device->CreateTexture2D(
&d3d11_texture_desc, nullptr, &dcomp_surface_draw_texture_copy_);
if (FAILED(hr)) {
LOG(ERROR) << "CreateTexture2D failed: "
<< logging::SystemErrorCodeToString(hr);
return nullptr;
}
const char kDebugLabel[] = "SharedImage_DCompSurfaceDrawTextureCopy";
gl::SetDebugName(dcomp_surface_draw_texture_copy_.Get(), kDebugLabel);
}
dcomp_surface_draw_texture_ = std::move(draw_texture);
update_rect_ = update_rect;
// Import the texture into dawn
DCHECK(!shared_texture_memory_);
shared_texture_memory_ =
CreateDawnSharedTextureMemory(device, dcomp_surface_draw_texture_copy_);
if (!shared_texture_memory_) {
LOG(ERROR) << "Failed to create shared texture memory.";
return nullptr;
}
wgpu::SharedTextureMemoryD3DSwapchainBeginState swapchain_begin_state = {};
swapchain_begin_state.isSwapchain = true;
wgpu::SharedTextureMemoryBeginAccessDescriptor desc = {};
desc.initialized = true;
desc.nextInChain = &swapchain_begin_state;
wgpu::Texture texture =
CreateDawnSharedTexture(shared_texture_memory_, usage, internal_usage,
/*view_formats=*/{});
if (!texture || shared_texture_memory_.BeginAccess(texture, &desc) !=
wgpu::Status::Success) {
LOG(ERROR) << "Failed to begin access and produce WGPUTexture";
return nullptr;
}
return texture;
}
void DCompSurfaceImageBacking::EndDrawDawn(const wgpu::Device& device,
wgpu::Texture texture) {
// We don't need any synchronization here because dawn and dcomp are using the
// same d3d11 device.
wgpu::SharedTextureMemoryEndAccessState end_state = {};
shared_texture_memory_.EndAccess(texture.Get(), &end_state);
shared_texture_memory_ = nullptr;
texture.Destroy();
Microsoft::WRL::ComPtr<ID3D11Device> d3d11_device;
dcomp_surface_draw_texture_->GetDevice(&d3d11_device);
Microsoft::WRL::ComPtr<ID3D11DeviceContext> d3d11_context;
d3d11_device->GetImmediateContext(&d3d11_context);
D3D11_BOX src_box = {
static_cast<UINT>(update_rect_.x()),
static_cast<UINT>(update_rect_.y()),
0u, // front
static_cast<UINT>(update_rect_.right()),
static_cast<UINT>(update_rect_.bottom()),
1u, // back
};
d3d11_context->CopySubresourceRegion(
dcomp_surface_draw_texture_.Get(), /*DstSubresource=*/0,
dcomp_update_offset_.x(), dcomp_update_offset_.y(), /*DstZ=*/0,
dcomp_surface_draw_texture_copy_.Get(), /*SrcSubresource=*/0, &src_box);
dcomp_surface_draw_texture_.Reset();
EndDraw();
}
} // namespace gpu