// Copyright 2021 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifdef UNSAFE_BUFFERS_BUILD
// TODO(crbug.com/40285824): Remove this and convert code to safer constructs.
#pragma allow_unsafe_buffers
#endif
#include "gpu/ipc/common/dxgi_helpers.h"
#include "base/check.h"
#include "base/debug/crash_logging.h"
#include "base/debug/dump_without_crashing.h"
#include "base/logging.h"
#include "base/metrics/histogram_functions.h"
#include "base/time/time.h"
#include "third_party/libyuv/include/libyuv/planar_functions.h"
namespace {
constexpr char kStagingTextureLabel[] = "DxgiGmb_Map_StagingTexture";
Microsoft::WRL::ComPtr<ID3D11Texture2D> CreateStagingTexture(
ID3D11Device* d3d11_device,
D3D11_TEXTURE2D_DESC input_desc) {
D3D11_TEXTURE2D_DESC staging_desc = {};
staging_desc.Width = input_desc.Width;
staging_desc.Height = input_desc.Height;
staging_desc.Format = input_desc.Format;
staging_desc.MipLevels = 1;
staging_desc.ArraySize = 1;
staging_desc.SampleDesc.Count = 1;
staging_desc.Usage = D3D11_USAGE_STAGING;
staging_desc.CPUAccessFlags = D3D11_CPU_ACCESS_READ;
Microsoft::WRL::ComPtr<ID3D11Texture2D> staging_texture;
HRESULT hr =
d3d11_device->CreateTexture2D(&staging_desc, nullptr, &staging_texture);
if (FAILED(hr)) {
DLOG(ERROR) << "Failed to create staging texture. hr=" << std::hex << hr;
return nullptr;
}
// Add debug label to the long lived texture.
staging_texture->SetPrivateData(WKPDID_D3DDebugObjectName,
strlen(kStagingTextureLabel),
kStagingTextureLabel);
return staging_texture;
}
} // namespace
namespace gpu {
D3D11ScopedTextureUnmap::D3D11ScopedTextureUnmap(
Microsoft::WRL::ComPtr<ID3D11DeviceContext> context,
Microsoft::WRL::ComPtr<ID3D11Texture2D> texture)
: context_(std::move(context)), texture_(std::move(texture)) {}
D3D11ScopedTextureUnmap::~D3D11ScopedTextureUnmap() {
context_->Unmap(texture_.Get(), 0);
}
DXGIScopedReleaseKeyedMutex::DXGIScopedReleaseKeyedMutex(
Microsoft::WRL::ComPtr<IDXGIKeyedMutex> keyed_mutex,
UINT64 key)
: keyed_mutex_(std::move(keyed_mutex)), key_(key) {
DCHECK(keyed_mutex_);
}
DXGIScopedReleaseKeyedMutex::~DXGIScopedReleaseKeyedMutex() {
HRESULT hr = keyed_mutex_->ReleaseSync(key_);
DCHECK(SUCCEEDED(hr));
}
bool CopyDXGIBufferToShMem(
HANDLE dxgi_handle,
base::span<uint8_t> shared_memory,
ID3D11Device* d3d11_device,
Microsoft::WRL::ComPtr<ID3D11Texture2D>* staging_texture) {
DCHECK(d3d11_device);
uint8_t* dest_buffer = shared_memory.data();
size_t dst_buffer_size = shared_memory.size_bytes();
Microsoft::WRL::ComPtr<ID3D11Device1> device1;
HRESULT hr = d3d11_device->QueryInterface(IID_PPV_ARGS(&device1));
if (FAILED(hr)) {
DLOG(ERROR) << "Failed to open D3D11_1 device. hr=" << std::hex << hr;
return false;
}
Microsoft::WRL::ComPtr<ID3D11Texture2D> texture;
// Open texture on device using shared handle
hr = device1->OpenSharedResource1(dxgi_handle, IID_PPV_ARGS(&texture));
if (FAILED(hr)) {
DLOG(ERROR) << "Failed to open shared texture. hr=" << std::hex << hr;
return false;
}
return CopyD3D11TexToMem(texture.Get(), dest_buffer, dst_buffer_size,
d3d11_device, staging_texture);
}
bool CopyD3D11TexToMem(
ID3D11Texture2D* src_texture,
uint8_t* dst_buffer,
size_t buffer_size,
ID3D11Device* d3d11_device,
Microsoft::WRL::ComPtr<ID3D11Texture2D>* staging_texture) {
DCHECK(d3d11_device);
DCHECK(staging_texture);
DCHECK(dst_buffer);
DCHECK(src_texture);
D3D11_TEXTURE2D_DESC texture_desc = {};
src_texture->GetDesc(&texture_desc);
if (texture_desc.Format != DXGI_FORMAT_NV12) {
DLOG(ERROR) << "Can't copy non-NV12 texture. format="
<< static_cast<int>(texture_desc.Format);
return false;
}
size_t copy_size = texture_desc.Height * texture_desc.Width * 3 / 2;
if (buffer_size < copy_size) {
DLOG(ERROR) << "Invalid buffer size for copy.";
return false;
}
// The texture isn't accessible for CPU reads, thus a staging texture is used.
bool create_staging_texture = !*staging_texture;
if (*staging_texture) {
D3D11_TEXTURE2D_DESC staging_texture_desc;
(*staging_texture)->GetDesc(&staging_texture_desc);
create_staging_texture =
(staging_texture_desc.Width != texture_desc.Width ||
staging_texture_desc.Height != texture_desc.Height ||
staging_texture_desc.Format != texture_desc.Format);
}
if (create_staging_texture) {
*staging_texture = CreateStagingTexture(d3d11_device, texture_desc);
if (!*staging_texture)
return false;
}
Microsoft::WRL::ComPtr<ID3D11DeviceContext> device_context;
d3d11_device->GetImmediateContext(&device_context);
HRESULT hr = S_OK;
if (texture_desc.MiscFlags & D3D11_RESOURCE_MISC_SHARED_KEYEDMUTEX) {
Microsoft::WRL::ComPtr<IDXGIKeyedMutex> keyed_mutex;
hr = src_texture->QueryInterface(IID_PPV_ARGS(&keyed_mutex));
if (FAILED(hr)) {
DLOG(ERROR) << "Failed to get keyed mutex. Error msg: "
<< logging::SystemErrorCodeToString(hr);
return false;
}
// Key equal to 0 is also used by the producer. Therefore, this keyed
// mutex acts purely as a regular mutex.
// 300ms is long enough to get the mutex in 99.999% of cases. Yet we
// don't want to stall the callee indefinitely if the mutex is held by
// e.g. GpuMain thread while it's blocked on driver waiting for shader
// compilation.
// It's better to drop a frame in this case.
hr = keyed_mutex->AcquireSync(0, 300);
// Can't check FAILED(hr), because AcquireSync may return e.g. WAIT_TIMEOUT
// value.
if (hr != S_OK) {
DLOG(ERROR) << "Failed to acquire keyed mutex. Error msg: "
<< logging::SystemErrorCodeToString(hr);
return false;
}
DXGIScopedReleaseKeyedMutex release_keyed_mutex(keyed_mutex, 0);
device_context->CopySubresourceRegion(staging_texture->Get(), 0, 0, 0, 0,
src_texture, 0, nullptr);
} else {
device_context->CopySubresourceRegion(staging_texture->Get(), 0, 0, 0, 0,
src_texture, 0, nullptr);
}
D3D11_MAPPED_SUBRESOURCE mapped_resource = {};
hr = device_context->Map(staging_texture->Get(), 0, D3D11_MAP_READ, 0,
&mapped_resource);
if (FAILED(hr)) {
DLOG(ERROR) << "Failed to map texture for read. Error msg: "
<< logging::SystemErrorCodeToString(hr);
return false;
}
D3D11ScopedTextureUnmap scoped_unmap(device_context, *staging_texture);
const uint8_t* source_buffer = static_cast<uint8_t*>(mapped_resource.pData);
const uint32_t source_stride = mapped_resource.RowPitch;
const uint32_t dest_stride = texture_desc.Width;
return libyuv::NV12Copy(source_buffer, source_stride,
source_buffer + texture_desc.Height * source_stride,
source_stride, dst_buffer, dest_stride,
dst_buffer + texture_desc.Height * dest_stride,
dest_stride, texture_desc.Width,
texture_desc.Height) == 0;
}
GPU_EXPORT bool CopyShMemToDXGIBuffer(base::span<uint8_t> shared_memory,
HANDLE dxgi_handle,
ID3D11Device* d3d11_device) {
CHECK(d3d11_device);
uint8_t* src_buffer = shared_memory.data();
size_t src_buffer_size = shared_memory.size_bytes();
Microsoft::WRL::ComPtr<ID3D11Device1> device1;
HRESULT hr = d3d11_device->QueryInterface(IID_PPV_ARGS(&device1));
if (FAILED(hr)) {
DLOG(ERROR) << "Failed to open D3D11_1 device. hr=" << std::hex << hr;
return false;
}
Microsoft::WRL::ComPtr<ID3D11Texture2D> texture;
// Open texture on device using shared handle
hr = device1->OpenSharedResource1(dxgi_handle, IID_PPV_ARGS(&texture));
if (FAILED(hr)) {
DLOG(ERROR) << "Failed to open shared texture. hr=" << std::hex << hr;
return false;
}
return CopyMemToD3D11Tex(src_buffer, src_buffer_size, texture.Get(),
d3d11_device);
}
GPU_EXPORT bool CopyMemToD3D11Tex(uint8_t* src_buffer,
size_t buffer_size,
ID3D11Texture2D* output_texture,
ID3D11Device* d3d11_device) {
CHECK(d3d11_device);
CHECK(src_buffer);
CHECK(output_texture);
D3D11_TEXTURE2D_DESC texture_desc = {};
output_texture->GetDesc(&texture_desc);
if (texture_desc.Format != DXGI_FORMAT_NV12) {
DLOG(ERROR) << "Can't copy non-NV12 texture. format="
<< static_cast<int>(texture_desc.Format);
return false;
}
size_t copy_size = texture_desc.Height * texture_desc.Width * 3 / 2;
if (buffer_size < copy_size) {
DLOG(ERROR) << "Invalid buffer size for copy.";
return false;
}
Microsoft::WRL::ComPtr<ID3D11DeviceContext> device_context;
d3d11_device->GetImmediateContext(&device_context);
HRESULT hr = S_OK;
if (texture_desc.MiscFlags & D3D11_RESOURCE_MISC_SHARED_KEYEDMUTEX) {
Microsoft::WRL::ComPtr<IDXGIKeyedMutex> keyed_mutex;
hr = output_texture->QueryInterface(IID_PPV_ARGS(&keyed_mutex));
if (FAILED(hr)) {
DLOG(ERROR) << "Failed to get keyed mutex. Error msg: "
<< logging::SystemErrorCodeToString(hr);
return false;
}
// Key equal to 0 is also used by the producer. Therefore, this keyed
// mutex acts purely as a regular mutex.
hr = keyed_mutex->AcquireSync(0, INFINITE);
// Can't check FAILED(hr), because AcquireSync may return e.g. WAIT_TIMEOUT
// value.
if (hr != S_OK) {
DLOG(ERROR) << "Failed to acquire keyed mutex. Error msg: "
<< logging::SystemErrorCodeToString(hr);
return false;
}
DXGIScopedReleaseKeyedMutex release_keyed_mutex(keyed_mutex, 0);
device_context->UpdateSubresource(output_texture, 0, nullptr, src_buffer,
texture_desc.Width, copy_size);
} else {
device_context->UpdateSubresource(output_texture, 0, nullptr, src_buffer,
texture_desc.Width, copy_size);
}
return true;
}
} // namespace gpu