| // 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( |
| const 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({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 |