// 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.
#include "gpu/command_buffer/service/dxgi_shared_handle_manager.h"
#include <windows.h>
#include <d3d11_1.h>
#include "base/atomic_ref_count.h"
#include "base/logging.h"
#include "base/trace_event/trace_event.h"
#include "gpu/command_buffer/common/constants.h"
#include "ui/gl/gl_angle_util_win.h"
namespace gpu {
namespace {
Microsoft::WRL::ComPtr<ID3D11Texture2D> OpenSharedHandleTexture(
Microsoft::WRL::ComPtr<ID3D11Device> d3d11_device,
HANDLE shared_handle) {
Microsoft::WRL::ComPtr<ID3D11Device1> d3d11_device1;
HRESULT hr = d3d11_device.As(&d3d11_device1);
if (FAILED(hr)) {
LOG(ERROR) << "Failed to query for ID3D11Device1. Error: "
<< logging::SystemErrorCodeToString(hr);
return nullptr;
}
Microsoft::WRL::ComPtr<ID3D11Texture2D> d3d11_texture;
hr = d3d11_device1->OpenSharedResource1(shared_handle,
IID_PPV_ARGS(&d3d11_texture));
if (FAILED(hr)) {
LOG(ERROR) << "Unable to open shared resource from DXGI handle. Error: "
<< logging::SystemErrorCodeToString(hr);
return nullptr;
}
return d3d11_texture;
}
bool HasKeyedMutex(Microsoft::WRL::ComPtr<ID3D11Texture2D> d3d11_texture) {
CHECK(d3d11_texture);
D3D11_TEXTURE2D_DESC desc;
d3d11_texture->GetDesc(&desc);
return desc.MiscFlags & D3D11_RESOURCE_MISC_SHARED_KEYEDMUTEX;
}
} // namespace
DXGISharedHandleState::D3D11TextureState::D3D11TextureState(
Microsoft::WRL::ComPtr<ID3D11Texture2D> texture)
: d3d11_texture(std::move(texture)) {
d3d11_texture.As(&dxgi_keyed_mutex);
}
DXGISharedHandleState::D3D11TextureState::~D3D11TextureState() {
CHECK_EQ(keyed_mutex_acquired_count, 0);
}
DXGISharedHandleState::D3D11TextureState::D3D11TextureState(
D3D11TextureState&&) = default;
DXGISharedHandleState::D3D11TextureState&
DXGISharedHandleState::D3D11TextureState::operator=(D3D11TextureState&&) =
default;
DXGISharedHandleState::DXGISharedHandleState(
base::PassKey<DXGISharedHandleManager>,
scoped_refptr<DXGISharedHandleManager> manager,
gfx::DXGIHandleToken token,
base::win::ScopedHandle shared_handle,
Microsoft::WRL::ComPtr<ID3D11Texture2D> d3d11_texture)
: base::subtle::RefCountedThreadSafeBase(
base::subtle::GetRefCountPreference<DXGISharedHandleState>()),
manager_(std::move(manager)),
token_(std::move(token)),
shared_handle_(std::move(shared_handle)),
has_keyed_mutex_(HasKeyedMutex(d3d11_texture)) {
CHECK(d3d11_texture);
Microsoft::WRL::ComPtr<ID3D11Device> d3d11_device;
d3d11_texture->GetDevice(&d3d11_device);
CHECK(d3d11_device);
D3D11TextureState texture_state(std::move(d3d11_texture));
d3d11_texture_state_map_.emplace(d3d11_device, std::move(texture_state));
}
DXGISharedHandleState::~DXGISharedHandleState() {
CHECK(!keyed_mutex_acquired_);
}
void DXGISharedHandleState::AddRef() const {
base::subtle::RefCountedThreadSafeBase::AddRefWithCheck();
}
void DXGISharedHandleState::Release() const {
// Hold the lock to prevent a race between erasing the state from the map and
// adding another reference to it. If GetOrCreateStateByToken runs before we
// erase the state from the map, we would return a scoped_refptr that will
// have a dangling pointer to the state after the call to delete causing a
// use-after-free when the scoped_refptr is later dereferenced.
base::AutoLock auto_lock(manager_->lock_);
if (base::subtle::RefCountedThreadSafeBase::Release()) {
// Prune the code paths which the static analyzer may take to simulate
// object destruction. Use-after-free errors aren't possible given the
// lifetime guarantees of the refcounting system.
ANALYZER_SKIP_THIS_PATH();
manager_->shared_handle_state_map_.erase(token_);
delete this;
}
}
Microsoft::WRL::ComPtr<ID3D11Texture2D>
DXGISharedHandleState::GetOrCreateD3D11Texture(
Microsoft::WRL::ComPtr<ID3D11Device> d3d11_device) {
base::AutoLock auto_lock(lock_);
auto it = d3d11_texture_state_map_.find(d3d11_device);
if (it == d3d11_texture_state_map_.end()) {
auto d3d11_texture =
OpenSharedHandleTexture(d3d11_device, shared_handle_.Get());
if (!d3d11_texture) {
LOG(ERROR) << "Failed to open DXGI shared handle";
return nullptr;
}
// TODO(sunnyps): Consider adding a method to cleanup entries in the map.
d3d11_texture_state_map_.emplace(std::move(d3d11_device),
D3D11TextureState(d3d11_texture));
return d3d11_texture;
}
return it->second.d3d11_texture;
}
bool DXGISharedHandleState::AcquireKeyedMutex(
Microsoft::WRL::ComPtr<ID3D11Device> d3d11_device) {
if (!has_keyed_mutex_) {
return true;
}
TRACE_EVENT0("gpu", "DXGISharedHandleState::AcquireKeyedMutex");
base::AutoLock auto_lock(lock_);
auto& d3d11_state = d3d11_texture_state_map_.at(d3d11_device);
CHECK(d3d11_state.dxgi_keyed_mutex);
CHECK_GE(d3d11_state.keyed_mutex_acquired_count, 0);
// Keyed mutex is acquired on |d3d11_device|. Simply increment acquired count.
if (d3d11_state.keyed_mutex_acquired_count > 0) {
CHECK(keyed_mutex_acquired_);
d3d11_state.keyed_mutex_acquired_count++;
return true;
}
// Keyed mutex is acquired on another device which is not allowed.
CHECK(!keyed_mutex_acquired_) << "Concurrent keyed mutex access not allowed";
const HRESULT hr = d3d11_state.dxgi_keyed_mutex->AcquireSync(
kDXGIKeyedMutexAcquireKey, INFINITE);
// Can't check for FAILED(hr) because AcquireSync may return e.g.
// WAIT_ABANDONED.
if (hr != S_OK) {
LOG(ERROR) << "Unable to acquire the keyed mutex " << std::hex << hr;
return false;
}
d3d11_state.keyed_mutex_acquired_count++;
keyed_mutex_acquired_ = true;
return true;
}
void DXGISharedHandleState::ReleaseKeyedMutex(
Microsoft::WRL::ComPtr<ID3D11Device> d3d11_device) {
if (!has_keyed_mutex_) {
return;
}
base::AutoLock auto_lock(lock_);
CHECK(keyed_mutex_acquired_);
auto& d3d11_state = d3d11_texture_state_map_.at(d3d11_device);
CHECK(d3d11_state.dxgi_keyed_mutex);
CHECK_GT(d3d11_state.keyed_mutex_acquired_count, 0);
d3d11_state.keyed_mutex_acquired_count--;
if (d3d11_state.keyed_mutex_acquired_count == 0) {
const HRESULT hr =
d3d11_state.dxgi_keyed_mutex->ReleaseSync(kDXGIKeyedMutexAcquireKey);
if (FAILED(hr)) {
LOG(ERROR) << "Unable to release the keyed mutex " << std::hex << hr;
}
keyed_mutex_acquired_ = false;
}
}
wgpu::SharedTextureMemory DXGISharedHandleState::GetSharedTextureMemory(
const wgpu::Device& device) {
base::AutoLock auto_lock(lock_);
auto iter = dawn_shared_texture_memory_cache_.find(device.Get());
if (iter == dawn_shared_texture_memory_cache_.end()) {
return nullptr;
}
return iter->second;
}
void DXGISharedHandleState::MaybeCacheSharedTextureMemory(
const wgpu::Device& device,
wgpu::SharedTextureMemory memory) {
base::AutoLock auto_lock(lock_);
CHECK(memory);
auto [iter, _] =
dawn_shared_texture_memory_cache_.try_emplace(device.Get(), memory);
CHECK_EQ(memory.Get(), iter->second.Get());
}
void DXGISharedHandleState::EraseDawnSharedTextureMemory(
const wgpu::Device& device) {
base::AutoLock auto_lock(lock_);
CHECK(dawn_shared_texture_memory_cache_.at(device.Get()).IsDeviceLost());
dawn_shared_texture_memory_cache_.erase(device.Get());
}
DXGISharedHandleManager::DXGISharedHandleManager() = default;
DXGISharedHandleManager::~DXGISharedHandleManager() {
#if DCHECK_IS_ON()
base::AutoLock auto_lock(lock_);
DCHECK(shared_handle_state_map_.empty());
#endif
}
scoped_refptr<DXGISharedHandleState>
DXGISharedHandleManager::GetOrCreateSharedHandleState(
gfx::DXGIHandleToken token,
base::win::ScopedHandle shared_handle,
Microsoft::WRL::ComPtr<ID3D11Device> d3d11_device) {
DCHECK(shared_handle.IsValid());
base::AutoLock auto_lock(lock_);
auto it = shared_handle_state_map_.find(token);
if (it != shared_handle_state_map_.end()) {
DXGISharedHandleState* state = it->second;
DCHECK(state);
// If there's already a shared handle associated with the token, it should
// refer to the same D3D11 texture (or kernel object).
if (!CompareObjectHandles(shared_handle.Get(), state->GetSharedHandle())) {
LOG(ERROR) << "Existing shared handle for token doesn't match";
return nullptr;
}
return base::WrapRefCounted(state);
}
auto d3d11_texture =
OpenSharedHandleTexture(d3d11_device, shared_handle.Get());
if (!d3d11_texture) {
LOG(ERROR) << "Failed to open DXGI shared handle";
return nullptr;
}
auto state = base::MakeRefCounted<DXGISharedHandleState>(
base::PassKey<DXGISharedHandleManager>(), base::WrapRefCounted(this),
token, std::move(shared_handle), std::move(d3d11_texture));
shared_handle_state_map_.insert(
std::make_pair(std::move(token), state.get()));
return state;
}
scoped_refptr<DXGISharedHandleState>
DXGISharedHandleManager::CreateAnonymousSharedHandleState(
base::win::ScopedHandle shared_handle,
Microsoft::WRL::ComPtr<ID3D11Texture2D> d3d11_texture) {
DCHECK(shared_handle.IsValid());
DCHECK(d3d11_texture);
base::AutoLock auto_lock(lock_);
auto token = gfx::DXGIHandleToken();
auto state = base::MakeRefCounted<DXGISharedHandleState>(
base::PassKey<DXGISharedHandleManager>(), base::WrapRefCounted(this),
token, std::move(shared_handle), std::move(d3d11_texture));
std::pair<SharedHandleMap::iterator, bool> inserted =
shared_handle_state_map_.insert(
std::make_pair(std::move(token), state.get()));
DCHECK(inserted.second);
return state;
}
size_t DXGISharedHandleManager::GetSharedHandleMapSizeForTesting() const {
base::AutoLock auto_lock(lock_);
return shared_handle_state_map_.size();
}
} // namespace gpu