blob: 872d709f43114cf0b28fa5cbd98036d0893033d5 [file] [log] [blame]
// 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 <d3d11_1.h>
#include <windows.h>
#include "base/atomic_ref_count.h"
#include "base/logging.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;
}
} // 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)) {
CHECK(d3d11_texture);
Microsoft::WRL::ComPtr<ID3D11Device> d3d11_device;
d3d11_texture->GetDevice(&d3d11_device);
CHECK(d3d11_device);
D3D11TextureState texture_state(std::move(d3d11_texture));
has_keyed_mutex_ = !!texture_state.dxgi_keyed_mutex;
d3d11_texture_state_map_.emplace(d3d11_device, std::move(texture_state));
}
DXGISharedHandleState::~DXGISharedHandleState() {
for (auto& [_, d3d11_state] : d3d11_texture_state_map_) {
if (d3d11_state.keyed_mutex_acquired_count > 0) {
CHECK(d3d11_state.dxgi_keyed_mutex);
const HRESULT hr =
d3d11_state.dxgi_keyed_mutex->ReleaseSync(kDXGIKeyedMutexAcquireKey);
if (FAILED(hr)) {
LOG(ERROR) << "Unable to release the keyed mutex " << std::hex << hr;
}
}
}
}
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) {
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::BeginAccessD3D11(
Microsoft::WRL::ComPtr<ID3D11Device> d3d11_device) {
if (!has_keyed_mutex_) {
return true;
}
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) {
d3d11_state.keyed_mutex_acquired_count++;
return true;
}
// Keyed mutex is acquired on another device which is not permitted.
if (keyed_mutex_acquired_) {
LOG(ERROR) << "Concurrent keyed mutex access not supported";
return false;
}
const HRESULT hr = d3d11_state.dxgi_keyed_mutex->AcquireSync(
kDXGIKeyedMutexAcquireKey, INFINITE);
if (FAILED(hr)) {
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::EndAccessD3D11(
Microsoft::WRL::ComPtr<ID3D11Device> d3d11_device) {
if (!has_keyed_mutex_) {
return;
}
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 (SUCCEEDED(hr)) {
keyed_mutex_acquired_ = false;
} else {
LOG(ERROR) << "Unable to release the keyed mutex " << std::hex << hr;
}
}
}
#if BUILDFLAG(USE_DAWN)
DXGISharedHandleState::DawnExternalImageState::DawnExternalImageState() =
default;
DXGISharedHandleState::DawnExternalImageState::~DawnExternalImageState() =
default;
DXGISharedHandleState::DawnExternalImageState::DawnExternalImageState(
DawnExternalImageState&&) = default;
DXGISharedHandleState::DawnExternalImageState&
DXGISharedHandleState::DawnExternalImageState::operator=(
DawnExternalImageState&&) = default;
bool DXGISharedHandleState::BeginAccessDawn(WGPUDevice device) {
auto& dawn_state = dawn_external_image_cache_.at(device);
CHECK_GE(dawn_state.access_count, 0);
// If a keyed mutex is present it's already acquired on |device| so just
// increment the access count.
if (dawn_state.access_count > 0) {
dawn_state.access_count++;
return true;
}
// Keyed mutex is acquired on another device which is not permitted.
if (keyed_mutex_acquired_) {
LOG(ERROR) << "Concurrent keyed mutex access not supported";
return false;
}
dawn_state.access_count++;
// The keyed mutex is actually acquired internally in Dawn, but we do extra
// tracking here for preventing concurrent access.
keyed_mutex_acquired_ = has_keyed_mutex_;
return true;
}
void DXGISharedHandleState::EndAccessDawn(WGPUDevice device) {
auto& dawn_state = dawn_external_image_cache_.at(device);
CHECK_GT(dawn_state.access_count, 0);
CHECK_EQ(has_keyed_mutex_, keyed_mutex_acquired_);
dawn_state.access_count--;
if (dawn_state.access_count == 0) {
if (!dawn_state.external_image) {
// Clear entry from the map if the backing has already reset the external
// image e.g. due to device destruction or loss.
dawn_external_image_cache_.erase(device);
}
// The keyed mutex is actually released internally in Dawn, but we do extra
// tracking here for preventing concurrent access.
keyed_mutex_acquired_ = false;
}
}
std::unique_ptr<ExternalImageDXGI>& DXGISharedHandleState::GetDawnExternalImage(
WGPUDevice device) {
return dawn_external_image_cache_[device].external_image;
}
#endif // BUILDFLAG(USE_DAWN)
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