diff --git a/include/dawn/native/D3D12Backend.h b/include/dawn/native/D3D12Backend.h index 2cff0f2d5b..8384ff5c52 100644 --- a/include/dawn/native/D3D12Backend.h +++ b/include/dawn/native/D3D12Backend.h @@ -52,20 +52,43 @@ struct DAWN_NATIVE_EXPORT ExternalImageDescriptorDXGISharedHandle : ExternalImag ExternalImageDescriptorDXGISharedHandle(); // Note: SharedHandle must be a handle to a texture object. - HANDLE sharedHandle; + // TODO(dawn:576): Remove after changing Chromium code to set textureSharedHandle. + HANDLE sharedHandle = nullptr; + HANDLE textureSharedHandle = nullptr; + + // Optional shared handle to a D3D11/12 fence which can be used to synchronize using wait/signal + // values specified in the access descriptor below. If null, the texture will be assumed to have + // an associated DXGI keyed mutex which will be used with a fixed key of 0 for synchronization. + HANDLE fenceSharedHandle = nullptr; }; // Keyed mutex acquire/release uses a fixed key of 0 to match Chromium behavior. constexpr UINT64 kDXGIKeyedMutexAcquireReleaseKey = 0; -struct DAWN_NATIVE_EXPORT ExternalImageAccessDescriptorDXGIKeyedMutex +struct DAWN_NATIVE_EXPORT ExternalImageAccessDescriptorDXGISharedHandle : ExternalImageAccessDescriptor { + public: + // Value used for fence wait. A value of 0 is valid, but essentially a no-op since the fence + // lifetime starts with the 0 value signaled. A value of UINT64_MAX is ignored since it's also + // used by the D3D runtime to indicate that the device was removed. + uint64_t fenceWaitValue = 0; + + // Value to signal the fence with after the texture is destroyed. A value of 0 means the fence + // will not be signaled. + uint64_t fenceSignalValue = 0; + + // Whether the texture is for a WebGPU swap chain. + bool isSwapChainTexture = false; +}; + +// TODO(dawn:576): Remove after changing Chromium code to use the new struct name. +struct DAWN_NATIVE_EXPORT ExternalImageAccessDescriptorDXGIKeyedMutex + : ExternalImageAccessDescriptorDXGISharedHandle { public: // TODO(chromium:1241533): Remove deprecated keyed mutex params after removing associated // code from Chromium - we use a fixed key of 0 for acquire and release everywhere now. uint64_t acquireMutexKey; uint64_t releaseMutexKey; - bool isSwapChainTexture = false; }; class DAWN_NATIVE_EXPORT ExternalImageDXGI { @@ -78,13 +101,15 @@ class DAWN_NATIVE_EXPORT ExternalImageDXGI { const ExternalImageDescriptorDXGISharedHandle* descriptor); WGPUTexture ProduceTexture(WGPUDevice device, - const ExternalImageAccessDescriptorDXGIKeyedMutex* descriptor); + const ExternalImageAccessDescriptorDXGISharedHandle* descriptor); private: ExternalImageDXGI(Microsoft::WRL::ComPtr d3d12Resource, + Microsoft::WRL::ComPtr d3d12Fence, const WGPUTextureDescriptor* descriptor); Microsoft::WRL::ComPtr mD3D12Resource; + Microsoft::WRL::ComPtr mD3D12Fence; // Contents of WGPUTextureDescriptor are stored individually since the descriptor // could outlive this image. diff --git a/include/dawn/native/DawnNative.h b/include/dawn/native/DawnNative.h index ee01ff245a..5de6aea5f4 100644 --- a/include/dawn/native/DawnNative.h +++ b/include/dawn/native/DawnNative.h @@ -237,13 +237,13 @@ struct DAWN_NATIVE_EXPORT ExternalImageDescriptor { struct DAWN_NATIVE_EXPORT ExternalImageAccessDescriptor { public: - bool isInitialized; // Whether the texture is initialized on import - WGPUTextureUsageFlags usage; + bool isInitialized = false; // Whether the texture is initialized on import + WGPUTextureUsageFlags usage = WGPUTextureUsage_None; }; struct DAWN_NATIVE_EXPORT ExternalImageExportInfo { public: - bool isInitialized; // Whether the texture is initialized after export + bool isInitialized = false; // Whether the texture is initialized after export ExternalImageType GetType() const; protected: diff --git a/src/dawn/native/d3d12/CommandRecordingContext.cpp b/src/dawn/native/d3d12/CommandRecordingContext.cpp index d4fa04d525..6f050147df 100644 --- a/src/dawn/native/d3d12/CommandRecordingContext.cpp +++ b/src/dawn/native/d3d12/CommandRecordingContext.cpp @@ -71,7 +71,7 @@ MaybeError CommandRecordingContext::ExecuteCommandList(Device* device) { // common state right before command list submission. TransitionUsageNow itself ensures // no unnecessary transitions happen if the resources is already in the common state. for (Texture* texture : mSharedTextures) { - DAWN_TRY(texture->AcquireKeyedMutex()); + DAWN_TRY(texture->SynchronizeImportedTextureBeforeUse()); texture->TrackAllUsageAndTransitionNow(this, D3D12_RESOURCE_STATE_COMMON); } @@ -124,7 +124,7 @@ MaybeError CommandRecordingContext::ExecuteCommandList(Device* device) { device->GetCommandQueue()->ExecuteCommandLists(1, &d3d12CommandList); for (Texture* texture : mSharedTextures) { - texture->ReleaseKeyedMutex(); + texture->SynchronizeImportedTextureAfterUse(); } mIsOpen = false; diff --git a/src/dawn/native/d3d12/D3D12Backend.cpp b/src/dawn/native/d3d12/D3D12Backend.cpp index 9747b41c0e..3006193221 100644 --- a/src/dawn/native/d3d12/D3D12Backend.cpp +++ b/src/dawn/native/d3d12/D3D12Backend.cpp @@ -54,8 +54,10 @@ ExternalImageDescriptorDXGISharedHandle::ExternalImageDescriptorDXGISharedHandle : ExternalImageDescriptor(ExternalImageType::DXGISharedHandle) {} ExternalImageDXGI::ExternalImageDXGI(ComPtr d3d12Resource, + ComPtr d3d12Fence, const WGPUTextureDescriptor* descriptor) : mD3D12Resource(std::move(d3d12Resource)), + mD3D12Fence(std::move(d3d12Fence)), mUsage(descriptor->usage), mDimension(descriptor->dimension), mSize(descriptor->size), @@ -76,7 +78,7 @@ ExternalImageDXGI::~ExternalImageDXGI() = default; WGPUTexture ExternalImageDXGI::ProduceTexture( WGPUDevice device, - const ExternalImageAccessDescriptorDXGIKeyedMutex* descriptor) { + const ExternalImageAccessDescriptorDXGISharedHandle* descriptor) { Device* backendDevice = ToBackend(FromAPI(device)); // Ensure the texture usage is allowed @@ -100,16 +102,20 @@ WGPUTexture ExternalImageDXGI::ProduceTexture( internalDesc.sType = wgpu::SType::DawnTextureInternalUsageDescriptor; } - Ref d3d11on12Resource = - mD3D11on12ResourceCache->GetOrCreateD3D11on12Resource(device, mD3D12Resource.Get()); - if (d3d11on12Resource == nullptr) { - dawn::ErrorLog() << "Unable to create 11on12 resource for external image"; - return nullptr; + Ref d3d11on12Resource; + if (!mD3D12Fence) { + d3d11on12Resource = + mD3D11on12ResourceCache->GetOrCreateD3D11on12Resource(device, mD3D12Resource.Get()); + if (d3d11on12Resource == nullptr) { + dawn::ErrorLog() << "Unable to create 11on12 resource for external image"; + return nullptr; + } } Ref texture = backendDevice->CreateD3D12ExternalTexture( - &textureDescriptor, mD3D12Resource, std::move(d3d11on12Resource), - descriptor->isSwapChainTexture, descriptor->isInitialized); + &textureDescriptor, mD3D12Resource, mD3D12Fence, std::move(d3d11on12Resource), + descriptor->fenceWaitValue, descriptor->fenceSignalValue, descriptor->isSwapChainTexture, + descriptor->isInitialized); return ToAPI(texture.Detach()); } @@ -120,12 +126,25 @@ std::unique_ptr ExternalImageDXGI::Create( const ExternalImageDescriptorDXGISharedHandle* descriptor) { Device* backendDevice = ToBackend(FromAPI(device)); + // Use sharedHandle as a fallback until Chromium code is changed to set textureSharedHandle. + HANDLE textureSharedHandle = descriptor->textureSharedHandle; + if (!textureSharedHandle) { + textureSharedHandle = descriptor->sharedHandle; + } + Microsoft::WRL::ComPtr d3d12Resource; - if (FAILED(backendDevice->GetD3D12Device()->OpenSharedHandle(descriptor->sharedHandle, + if (FAILED(backendDevice->GetD3D12Device()->OpenSharedHandle(textureSharedHandle, IID_PPV_ARGS(&d3d12Resource)))) { return nullptr; } + Microsoft::WRL::ComPtr d3d12Fence; + if (descriptor->fenceSharedHandle && + FAILED(backendDevice->GetD3D12Device()->OpenSharedHandle(descriptor->fenceSharedHandle, + IID_PPV_ARGS(&d3d12Fence)))) { + return nullptr; + } + const TextureDescriptor* textureDescriptor = FromAPI(descriptor->cTextureDescriptor); if (backendDevice->ConsumedError(ValidateTextureDescriptor(backendDevice, textureDescriptor))) { @@ -154,8 +173,8 @@ std::unique_ptr ExternalImageDXGI::Create( } } - std::unique_ptr result( - new ExternalImageDXGI(std::move(d3d12Resource), descriptor->cTextureDescriptor)); + std::unique_ptr result(new ExternalImageDXGI( + std::move(d3d12Resource), std::move(d3d12Fence), descriptor->cTextureDescriptor)); return result; } diff --git a/src/dawn/native/d3d12/DeviceD3D12.cpp b/src/dawn/native/d3d12/DeviceD3D12.cpp index 90ddc3041e..c6398cb605 100644 --- a/src/dawn/native/d3d12/DeviceD3D12.cpp +++ b/src/dawn/native/d3d12/DeviceD3D12.cpp @@ -535,13 +535,17 @@ ResultOrError Device::AllocateMemory( Ref Device::CreateD3D12ExternalTexture( const TextureDescriptor* descriptor, ComPtr d3d12Texture, + ComPtr d3d12Fence, Ref d3d11on12Resource, + uint64_t fenceWaitValue, + uint64_t fenceSignalValue, bool isSwapChainTexture, bool isInitialized) { Ref dawnTexture; - if (ConsumedError(Texture::CreateExternalImage(this, descriptor, std::move(d3d12Texture), - std::move(d3d11on12Resource), isSwapChainTexture, - isInitialized), + if (ConsumedError(Texture::CreateExternalImage( + this, descriptor, std::move(d3d12Texture), std::move(d3d12Fence), + std::move(d3d11on12Resource), fenceWaitValue, fenceSignalValue, + isSwapChainTexture, isInitialized), &dawnTexture)) { return nullptr; } diff --git a/src/dawn/native/d3d12/DeviceD3D12.h b/src/dawn/native/d3d12/DeviceD3D12.h index 99b03e3964..b352eb97de 100644 --- a/src/dawn/native/d3d12/DeviceD3D12.h +++ b/src/dawn/native/d3d12/DeviceD3D12.h @@ -130,7 +130,10 @@ class Device final : public DeviceBase { Ref CreateD3D12ExternalTexture(const TextureDescriptor* descriptor, ComPtr d3d12Texture, + ComPtr d3d12Fence, Ref d3d11on12Resource, + uint64_t fenceWaitValue, + uint64_t fenceSignalValue, bool isSwapChainTexture, bool isInitialized); diff --git a/src/dawn/native/d3d12/TextureD3D12.cpp b/src/dawn/native/d3d12/TextureD3D12.cpp index f3aeee0e34..0cc6e3d753 100644 --- a/src/dawn/native/d3d12/TextureD3D12.cpp +++ b/src/dawn/native/d3d12/TextureD3D12.cpp @@ -511,13 +511,17 @@ ResultOrError> Texture::CreateExternalImage( Device* device, const TextureDescriptor* descriptor, ComPtr d3d12Texture, + ComPtr d3d12Fence, Ref d3d11on12Resource, + uint64_t fenceWaitValue, + uint64_t fenceSignalValue, bool isSwapChainTexture, bool isInitialized) { Ref dawnTexture = AcquireRef(new Texture(device, descriptor, TextureState::OwnedExternal)); DAWN_TRY(dawnTexture->InitializeAsExternalTexture( - descriptor, std::move(d3d12Texture), std::move(d3d11on12Resource), isSwapChainTexture)); + std::move(d3d12Texture), std::move(d3d12Fence), std::move(d3d11on12Resource), + fenceWaitValue, fenceSignalValue, isSwapChainTexture)); // Importing a multi-planar format must be initialized. This is required because // a shared multi-planar format cannot be initialized by Dawn. @@ -541,13 +545,12 @@ ResultOrError> Texture::Create(Device* device, return std::move(dawnTexture); } -MaybeError Texture::InitializeAsExternalTexture(const TextureDescriptor* descriptor, - ComPtr d3d12Texture, +MaybeError Texture::InitializeAsExternalTexture(ComPtr d3d12Texture, + ComPtr d3d12Fence, Ref d3d11on12Resource, + uint64_t fenceWaitValue, + uint64_t fenceSignalValue, bool isSwapChainTexture) { - mD3D11on12Resource = std::move(d3d11on12Resource); - mSwapChainTexture = isSwapChainTexture; - D3D12_RESOURCE_DESC desc = d3d12Texture->GetDesc(); mD3D12ResourceFlags = desc.Flags; @@ -558,6 +561,12 @@ MaybeError Texture::InitializeAsExternalTexture(const TextureDescriptor* descrip // memory management. mResourceAllocation = {info, 0, std::move(d3d12Texture), nullptr}; + mD3D12Fence = std::move(d3d12Fence); + mD3D11on12Resource = std::move(d3d11on12Resource); + mFenceWaitValue = fenceWaitValue; + mFenceSignalValue = fenceSignalValue; + mSwapChainTexture = isSwapChainTexture; + SetLabelHelper("Dawn_ExternalTexture"); return {}; @@ -657,9 +666,15 @@ void Texture::DestroyImpl() { // ID3D12SharingContract::Present. mSwapChainTexture = false; - // Now that the texture has been destroyed. It should release the refptr - // of the d3d11on12 resource. + // Signal the fence on destroy after all uses of the texture. + if (mD3D12Fence != nullptr && mFenceSignalValue != 0) { + device->GetCommandQueue()->Signal(mD3D12Fence.Get(), mFenceSignalValue); + } + + // Now that the texture has been destroyed. It should release the refptr of the d3d11on12 + // resource and the fence. mD3D11on12Resource = nullptr; + mD3D12Fence = nullptr; } DXGI_FORMAT Texture::GetD3D12Format() const { @@ -692,14 +707,27 @@ DXGI_FORMAT Texture::GetD3D12CopyableSubresourceFormat(Aspect aspect) const { } } -MaybeError Texture::AcquireKeyedMutex() { - ASSERT(mD3D11on12Resource != nullptr); - return mD3D11on12Resource->AcquireKeyedMutex(); +MaybeError Texture::SynchronizeImportedTextureBeforeUse() { + if (mD3D11on12Resource != nullptr) { + DAWN_TRY(mD3D11on12Resource->AcquireKeyedMutex()); + } + // Perform the wait only on the first call. We can use UINT64_MAX as a sentinel value since it's + // also used by the D3D runtime to indicate device removed according to: + // https://docs.microsoft.com/en-us/windows/win32/api/d3d12/nf-d3d12-id3d12fence-getcompletedvalue#return-value + if (mD3D12Fence != nullptr && mFenceWaitValue != UINT64_MAX) { + DAWN_TRY(CheckHRESULT( + ToBackend(GetDevice())->GetCommandQueue()->Wait(mD3D12Fence.Get(), mFenceWaitValue), + "D3D12 fence wait")); + mFenceWaitValue = UINT64_MAX; + } + return {}; } -void Texture::ReleaseKeyedMutex() { - ASSERT(mD3D11on12Resource != nullptr); - mD3D11on12Resource->ReleaseKeyedMutex(); +void Texture::SynchronizeImportedTextureAfterUse() { + if (mD3D11on12Resource != nullptr) { + mD3D11on12Resource->ReleaseKeyedMutex(); + } + // Defer signaling the fence until destroy after all uses of the fence. } void Texture::TrackUsageAndTransitionNow(CommandRecordingContext* commandContext, @@ -850,10 +878,10 @@ void Texture::TransitionSubresourceRange(std::vector* ba } void Texture::HandleTransitionSpecialCases(CommandRecordingContext* commandContext) { - // Textures with keyed mutexes can be written from other graphics queues. Hence, they - // must be acquired before command list submission to ensure work from the other queues - // has finished. See Device::ExecuteCommandContext. - if (mD3D11on12Resource != nullptr) { + // Externally allocated textures can be written from other graphics queues. Hence, they must be + // acquired before command list submission to ensure work from the other queues has finished. + // See CommandRecordingContext::ExecuteCommandList. + if (mResourceAllocation.GetInfo().mMethod == AllocationMethod::kExternal) { commandContext->AddToSharedTextureList(this); } } diff --git a/src/dawn/native/d3d12/TextureD3D12.h b/src/dawn/native/d3d12/TextureD3D12.h index 05b80db12b..c67aff2ef6 100644 --- a/src/dawn/native/d3d12/TextureD3D12.h +++ b/src/dawn/native/d3d12/TextureD3D12.h @@ -45,7 +45,10 @@ class Texture final : public TextureBase { Device* device, const TextureDescriptor* descriptor, ComPtr d3d12Texture, + ComPtr d3d12Fence, Ref d3d11on12Resource, + uint64_t fenceWaitValue, + uint64_t fenceSignalValue, bool isSwapChainTexture, bool isInitialized); static ResultOrError> Create(Device* device, @@ -70,8 +73,8 @@ class Texture final : public TextureBase { void EnsureSubresourceContentInitialized(CommandRecordingContext* commandContext, const SubresourceRange& range); - MaybeError AcquireKeyedMutex(); - void ReleaseKeyedMutex(); + MaybeError SynchronizeImportedTextureBeforeUse(); + void SynchronizeImportedTextureAfterUse(); void TrackUsageAndGetResourceBarrierForPass(CommandRecordingContext* commandContext, std::vector* barrier, @@ -97,9 +100,11 @@ class Texture final : public TextureBase { using TextureBase::TextureBase; MaybeError InitializeAsInternalTexture(); - MaybeError InitializeAsExternalTexture(const TextureDescriptor* descriptor, - ComPtr d3d12Texture, + MaybeError InitializeAsExternalTexture(ComPtr d3d12Texture, + ComPtr d3d12Fence, Ref d3d11on12Resource, + uint64_t fenceWaitValue, + uint64_t fenceSignalValue, bool isSwapChainTexture); MaybeError InitializeAsSwapChainTexture(ComPtr d3d12Texture); @@ -132,13 +137,17 @@ class Texture final : public TextureBase { ExecutionSerial pendingCommandSerial) const; void HandleTransitionSpecialCases(CommandRecordingContext* commandContext); - SubresourceStorage mSubresourceStateAndDecay; - - ResourceHeapAllocation mResourceAllocation; - bool mSwapChainTexture = false; D3D12_RESOURCE_FLAGS mD3D12ResourceFlags; + ResourceHeapAllocation mResourceAllocation; + // TODO(dawn:1460): Encapsulate imported image fields e.g. std::unique_ptr. + ComPtr mD3D12Fence; Ref mD3D11on12Resource; + uint64_t mFenceWaitValue = 0; + uint64_t mFenceSignalValue = 0; + bool mSwapChainTexture = false; + + SubresourceStorage mSubresourceStateAndDecay; }; class TextureView final : public TextureViewBase { diff --git a/src/dawn/tests/end2end/D3D12ResourceWrappingTests.cpp b/src/dawn/tests/end2end/D3D12ResourceWrappingTests.cpp index ce81ec2d3f..fc57036865 100644 --- a/src/dawn/tests/end2end/D3D12ResourceWrappingTests.cpp +++ b/src/dawn/tests/end2end/D3D12ResourceWrappingTests.cpp @@ -13,6 +13,7 @@ // limitations under the License. #include +#include #include #include #include @@ -30,9 +31,28 @@ using Microsoft::WRL::ComPtr; namespace { +enum class SyncMode { + kKeyedMutex, + kFence, +}; + +std::ostream& operator<<(std::ostream& o, const SyncMode& m) { + switch (m) { + case SyncMode::kKeyedMutex: + o << "KeyedMutex"; + break; + case SyncMode::kFence: + o << "Fence"; + break; + } + return o; +} + +DAWN_TEST_PARAM_STRUCT(D3D12ResourceTestParams, SyncMode); + using dawn::native::d3d12::kDXGIKeyedMutexAcquireReleaseKey; -class D3D12ResourceTestBase : public DawnTest { +class D3D12ResourceTestBase : public DawnTestWithParams { protected: std::vector GetRequiredFeatures() override { return {wgpu::FeatureName::DawnInternalUsages}; @@ -40,7 +60,7 @@ class D3D12ResourceTestBase : public DawnTest { public: void SetUp() override { - DawnTest::SetUp(); + DawnTestWithParams::SetUp(); if (UsesWire()) { return; } @@ -88,8 +108,12 @@ class D3D12ResourceTestBase : public DawnTest { baseD3dDescriptor.Usage = D3D11_USAGE_DEFAULT; baseD3dDescriptor.BindFlags = D3D11_BIND_SHADER_RESOURCE | D3D11_BIND_RENDER_TARGET; baseD3dDescriptor.CPUAccessFlags = 0; - baseD3dDescriptor.MiscFlags = - D3D11_RESOURCE_MISC_SHARED_NTHANDLE | D3D11_RESOURCE_MISC_SHARED_KEYEDMUTEX; + baseD3dDescriptor.MiscFlags = D3D11_RESOURCE_MISC_SHARED_NTHANDLE; + if (GetParam().mSyncMode == SyncMode::kKeyedMutex) { + baseD3dDescriptor.MiscFlags |= D3D11_RESOURCE_MISC_SHARED_KEYEDMUTEX; + } else { + baseD3dDescriptor.MiscFlags |= D3D11_RESOURCE_MISC_SHARED; + } } protected: @@ -98,7 +122,8 @@ class D3D12ResourceTestBase : public DawnTest { const D3D11_TEXTURE2D_DESC* baseD3dDescriptor, wgpu::Texture* dawnTexture, ID3D11Texture2D** d3d11TextureOut, - std::unique_ptr* externalImageOut = nullptr) const { + std::unique_ptr* externalImageOut = nullptr, + uint64_t fenceSignalValue = 1) const { ComPtr d3d11Texture; HRESULT hr = mD3d11Device->CreateTexture2D(baseD3dDescriptor, nullptr, &d3d11Texture); ASSERT_EQ(hr, S_OK); @@ -107,31 +132,54 @@ class D3D12ResourceTestBase : public DawnTest { hr = d3d11Texture.As(&dxgiResource); ASSERT_EQ(hr, S_OK); - HANDLE sharedHandle; + HANDLE textureSharedHandle; hr = dxgiResource->CreateSharedHandle( nullptr, DXGI_SHARED_RESOURCE_READ | DXGI_SHARED_RESOURCE_WRITE, nullptr, - &sharedHandle); + &textureSharedHandle); ASSERT_EQ(hr, S_OK); + HANDLE fenceSharedHandle = nullptr; + ComPtr d3d11Fence; + + if (GetParam().mSyncMode == SyncMode::kFence) { + ComPtr d3d11Device5; + hr = mD3d11Device.As(&d3d11Device5); + ASSERT_EQ(hr, S_OK); + + hr = d3d11Device5->CreateFence(0, D3D11_FENCE_FLAG_SHARED, IID_PPV_ARGS(&d3d11Fence)); + ASSERT_EQ(hr, S_OK); + + hr = d3d11Fence->CreateSharedHandle(nullptr, GENERIC_ALL, nullptr, &fenceSharedHandle); + ASSERT_EQ(hr, S_OK); + } + dawn::native::d3d12::ExternalImageDescriptorDXGISharedHandle externalImageDesc; externalImageDesc.cTextureDescriptor = reinterpret_cast(dawnDesc); - externalImageDesc.sharedHandle = sharedHandle; + externalImageDesc.textureSharedHandle = textureSharedHandle; + externalImageDesc.fenceSharedHandle = fenceSharedHandle; std::unique_ptr externalImage = dawn::native::d3d12::ExternalImageDXGI::Create(device.Get(), &externalImageDesc); // Now that we've created all of our resources, we can close the handle // since we no longer need it. - ::CloseHandle(sharedHandle); + ::CloseHandle(textureSharedHandle); + if (fenceSharedHandle != nullptr) { + ::CloseHandle(fenceSharedHandle); + } // Cannot access a non-existent external image (ex. validation error). if (externalImage == nullptr) { return; } - dawn::native::d3d12::ExternalImageAccessDescriptorDXGIKeyedMutex externalAccessDesc; + dawn::native::d3d12::ExternalImageAccessDescriptorDXGISharedHandle externalAccessDesc; externalAccessDesc.usage = static_cast(dawnDesc->usage); + if (d3d11Fence != nullptr) { + externalAccessDesc.fenceWaitValue = 0; + externalAccessDesc.fenceSignalValue = fenceSignalValue; + } *dawnTexture = wgpu::Texture::Acquire( externalImage->ProduceTexture(device.Get(), &externalAccessDesc)); @@ -340,70 +388,113 @@ class D3D12SharedHandleUsageTests : public D3D12ResourceTestBase { queue.Submit(1, &commands); } - void WrapAndClearD3D11Texture(const wgpu::TextureDescriptor* dawnDescriptor, - const D3D11_TEXTURE2D_DESC* d3dDescriptor, - wgpu::Texture* dawnTextureOut, + void WrapAndClearD3D11Texture(const wgpu::TextureDescriptor& dawnDescriptor, + const D3D11_TEXTURE2D_DESC& d3dDescriptor, const wgpu::Color& clearColor, + wgpu::Texture* dawnTextureOut, ID3D11Texture2D** d3d11TextureOut, - IDXGIKeyedMutex** dxgiKeyedMutexOut, - bool isInitialized = true) const { + bool isInitialized = true, + IDXGIKeyedMutex** dxgiKeyedMutexOut = nullptr, + ID3D11Fence** d3d11FenceOut = nullptr, + uint64_t* nextFenceWaitValue = nullptr) const { ComPtr d3d11Texture; - HRESULT hr = mD3d11Device->CreateTexture2D(d3dDescriptor, nullptr, &d3d11Texture); + HRESULT hr = mD3d11Device->CreateTexture2D(&d3dDescriptor, nullptr, &d3d11Texture); ASSERT_EQ(hr, S_OK); ComPtr dxgiResource; hr = d3d11Texture.As(&dxgiResource); ASSERT_EQ(hr, S_OK); - HANDLE sharedHandle; + HANDLE textureSharedHandle; hr = dxgiResource->CreateSharedHandle( nullptr, DXGI_SHARED_RESOURCE_READ | DXGI_SHARED_RESOURCE_WRITE, nullptr, - &sharedHandle); + &textureSharedHandle); ASSERT_EQ(hr, S_OK); ComPtr dxgiKeyedMutex; - hr = d3d11Texture.As(&dxgiKeyedMutex); - ASSERT_EQ(hr, S_OK); + + HANDLE fenceSharedHandle = nullptr; + ComPtr d3d11Fence; + + ComPtr d3d11DeviceContext4; + + if (GetParam().mSyncMode == SyncMode::kKeyedMutex) { + hr = d3d11Texture.As(&dxgiKeyedMutex); + ASSERT_EQ(hr, S_OK); + + hr = dxgiKeyedMutex->AcquireSync(kDXGIKeyedMutexAcquireReleaseKey, INFINITE); + ASSERT_EQ(hr, S_OK); + } else { + ComPtr d3d11Device5; + hr = mD3d11Device.As(&d3d11Device5); + ASSERT_EQ(hr, S_OK); + + hr = d3d11Device5->CreateFence(0, D3D11_FENCE_FLAG_SHARED, IID_PPV_ARGS(&d3d11Fence)); + ASSERT_EQ(hr, S_OK); + + hr = d3d11Fence->CreateSharedHandle(nullptr, GENERIC_ALL, nullptr, &fenceSharedHandle); + ASSERT_EQ(hr, S_OK); + } ComPtr d3d11RTV; hr = mD3d11Device->CreateRenderTargetView(d3d11Texture.Get(), nullptr, &d3d11RTV); ASSERT_EQ(hr, S_OK); - hr = dxgiKeyedMutex->AcquireSync(kDXGIKeyedMutexAcquireReleaseKey, INFINITE); - ASSERT_EQ(hr, S_OK); - const float colorRGBA[] = { static_cast(clearColor.r), static_cast(clearColor.g), static_cast(clearColor.b), static_cast(clearColor.a)}; mD3d11DeviceContext->ClearRenderTargetView(d3d11RTV.Get(), colorRGBA); - hr = dxgiKeyedMutex->ReleaseSync(kDXGIKeyedMutexAcquireReleaseKey); - ASSERT_EQ(hr, S_OK); - dawn::native::d3d12::ExternalImageDescriptorDXGISharedHandle externalImageDesc = {}; - externalImageDesc.sharedHandle = sharedHandle; + externalImageDesc.textureSharedHandle = textureSharedHandle; + externalImageDesc.fenceSharedHandle = fenceSharedHandle; externalImageDesc.cTextureDescriptor = - reinterpret_cast(dawnDescriptor); + reinterpret_cast(&dawnDescriptor); + + dawn::native::d3d12::ExternalImageAccessDescriptorDXGISharedHandle externalAccessDesc; + externalAccessDesc.isInitialized = isInitialized; + externalAccessDesc.usage = static_cast(dawnDescriptor.usage); + + if (dxgiKeyedMutex) { + hr = dxgiKeyedMutex->ReleaseSync(kDXGIKeyedMutexAcquireReleaseKey); + ASSERT_EQ(hr, S_OK); + } else { + hr = mD3d11DeviceContext.As(&d3d11DeviceContext4); + ASSERT_EQ(hr, S_OK); + + // The fence starts with 0 signaled, but that won't capture the render target view clear + // above, so signal explicitly with 1 and make the next Dawn access wait on 1. + d3d11DeviceContext4->Signal(d3d11Fence.Get(), 1); + + externalAccessDesc.fenceWaitValue = 1; + externalAccessDesc.fenceSignalValue = 2; + } std::unique_ptr externalImage = dawn::native::d3d12::ExternalImageDXGI::Create(device.Get(), &externalImageDesc); - dawn::native::d3d12::ExternalImageAccessDescriptorDXGIKeyedMutex externalAccessDesc; - externalAccessDesc.isInitialized = isInitialized; - externalAccessDesc.usage = static_cast(dawnDescriptor->usage); - *dawnTextureOut = wgpu::Texture::Acquire( externalImage->ProduceTexture(device.Get(), &externalAccessDesc)); *d3d11TextureOut = d3d11Texture.Detach(); - *dxgiKeyedMutexOut = dxgiKeyedMutex.Detach(); + + if (dxgiKeyedMutexOut != nullptr) { + *dxgiKeyedMutexOut = dxgiKeyedMutex.Detach(); + } + + if (d3d11FenceOut != nullptr) { + *d3d11FenceOut = d3d11Fence.Detach(); + } + + if (nextFenceWaitValue != nullptr) { + *nextFenceWaitValue = externalAccessDesc.fenceSignalValue; + } } void ExpectPixelRGBA8EQ(ID3D11Texture2D* d3d11Texture, IDXGIKeyedMutex* dxgiKeyedMutex, + ID3D11Fence* d3d11Fence, + uint64_t fenceWaitValue, const wgpu::Color& color) { - HRESULT hr = dxgiKeyedMutex->AcquireSync(kDXGIKeyedMutexAcquireReleaseKey, INFINITE); - ASSERT_EQ(hr, S_OK); - D3D11_TEXTURE2D_DESC texture2DDesc; d3d11Texture->GetDesc(&texture2DDesc); @@ -418,7 +509,8 @@ class D3D12SharedHandleUsageTests : public D3D12ResourceTestBase { D3D11_CPU_ACCESS_READ | D3D11_CPU_ACCESS_WRITE); // CPUAccessFlags ComPtr spD3DTextureStaging; - hr = mD3d11Device->CreateTexture2D(&texture2DStagingDesc, nullptr, &spD3DTextureStaging); + HRESULT hr = + mD3d11Device->CreateTexture2D(&texture2DStagingDesc, nullptr, &spD3DTextureStaging); ASSERT_EQ(hr, S_OK); D3D11_BOX d3dRc; @@ -429,6 +521,19 @@ class D3D12SharedHandleUsageTests : public D3D12ResourceTestBase { d3dRc.bottom = texture2DDesc.Height; d3dRc.right = texture2DDesc.Width; + if (dxgiKeyedMutex != nullptr) { + hr = dxgiKeyedMutex->AcquireSync(kDXGIKeyedMutexAcquireReleaseKey, INFINITE); + ASSERT_EQ(hr, S_OK); + } + if (d3d11Fence != nullptr) { + ComPtr d3d11DeviceContext4; + hr = mD3d11DeviceContext.As(&d3d11DeviceContext4); + ASSERT_EQ(hr, S_OK); + + hr = d3d11DeviceContext4->Wait(d3d11Fence, fenceWaitValue); + ASSERT_EQ(hr, S_OK); + } + mD3d11DeviceContext->CopySubresourceRegion(spD3DTextureStaging.Get(), // pDstResource 0, // DstSubresource 0, // DstX @@ -451,8 +556,10 @@ class D3D12SharedHandleUsageTests : public D3D12ResourceTestBase { mD3d11DeviceContext->Unmap(spD3DTextureStaging.Get(), 0); - hr = dxgiKeyedMutex->ReleaseSync(kDXGIKeyedMutexAcquireReleaseKey); - ASSERT_EQ(hr, S_OK); + if (dxgiKeyedMutex != nullptr) { + hr = dxgiKeyedMutex->ReleaseSync(kDXGIKeyedMutexAcquireReleaseKey); + ASSERT_EQ(hr, S_OK); + } } }; @@ -465,9 +572,8 @@ TEST_P(D3D12SharedHandleUsageTests, ClearInD3D11CopyAndReadbackInD3D12) { const wgpu::Color clearColor{1.0f, 1.0f, 0.0f, 1.0f}; wgpu::Texture dawnSrcTexture; ComPtr d3d11Texture; - ComPtr dxgiKeyedMutex; - WrapAndClearD3D11Texture(&baseDawnDescriptor, &baseD3dDescriptor, &dawnSrcTexture, clearColor, - &d3d11Texture, &dxgiKeyedMutex); + WrapAndClearD3D11Texture(baseDawnDescriptor, baseD3dDescriptor, clearColor, &dawnSrcTexture, + &d3d11Texture); ASSERT_NE(dawnSrcTexture.Get(), nullptr); // Create a texture on the device and copy the source texture to it. @@ -489,9 +595,8 @@ TEST_P(D3D12SharedHandleUsageTests, ClearInD3D11ReadbackInD3D12) { const wgpu::Color clearColor{1.0f, 1.0f, 0.0f, 1.0f}; wgpu::Texture dawnTexture; ComPtr d3d11Texture; - ComPtr dxgiKeyedMutex; - WrapAndClearD3D11Texture(&baseDawnDescriptor, &baseD3dDescriptor, &dawnTexture, clearColor, - &d3d11Texture, &dxgiKeyedMutex); + WrapAndClearD3D11Texture(baseDawnDescriptor, baseD3dDescriptor, clearColor, &dawnTexture, + &d3d11Texture); ASSERT_NE(dawnTexture.Get(), nullptr); // Readback the destination texture and ensure it contains the colors we used @@ -515,8 +620,11 @@ TEST_P(D3D12SharedHandleUsageTests, ClearInD3D12ReadbackInD3D11) { wgpu::Texture dawnTexture; ComPtr d3d11Texture; ComPtr dxgiKeyedMutex; - WrapAndClearD3D11Texture(&baseDawnDescriptor, &baseD3dDescriptor, &dawnTexture, d3d11ClearColor, - &d3d11Texture, &dxgiKeyedMutex); + ComPtr d3d11Fence; + uint64_t nextFenceWaitValue; + WrapAndClearD3D11Texture(baseDawnDescriptor, baseD3dDescriptor, d3d11ClearColor, &dawnTexture, + &d3d11Texture, /*isInitialized=*/true, &dxgiKeyedMutex, &d3d11Fence, + &nextFenceWaitValue); ASSERT_NE(dawnTexture.Get(), nullptr); const wgpu::Color d3d12ClearColor{0.0f, 0.0f, 1.0f, 1.0f}; @@ -527,7 +635,8 @@ TEST_P(D3D12SharedHandleUsageTests, ClearInD3D12ReadbackInD3D11) { // Now that Dawn (via D3D12) has finished writing to the texture, we should be // able to read it back by copying it to a staging texture and verifying the // color matches the D3D12 clear color. - ExpectPixelRGBA8EQ(d3d11Texture.Get(), dxgiKeyedMutex.Get(), d3d12ClearColor); + ExpectPixelRGBA8EQ(d3d11Texture.Get(), dxgiKeyedMutex.Get(), d3d11Fence.Get(), + nextFenceWaitValue, d3d12ClearColor); } // 1. Create and clear a D3D11 texture @@ -545,8 +654,11 @@ TEST_P(D3D12SharedHandleUsageTests, ClearTwiceInD3D12ReadbackInD3D11) { wgpu::Texture dawnTexture; ComPtr d3d11Texture; ComPtr dxgiKeyedMutex; - WrapAndClearD3D11Texture(&baseDawnDescriptor, &baseD3dDescriptor, &dawnTexture, d3d11ClearColor, - &d3d11Texture, &dxgiKeyedMutex); + ComPtr d3d11Fence; + uint64_t nextFenceWaitValue; + WrapAndClearD3D11Texture(baseDawnDescriptor, baseD3dDescriptor, d3d11ClearColor, &dawnTexture, + &d3d11Texture, /*isInitialized=*/true, &dxgiKeyedMutex, &d3d11Fence, + &nextFenceWaitValue); ASSERT_NE(dawnTexture.Get(), nullptr); const wgpu::Color d3d12ClearColor1{0.0f, 0.0f, 1.0f, 1.0f}; @@ -560,7 +672,8 @@ TEST_P(D3D12SharedHandleUsageTests, ClearTwiceInD3D12ReadbackInD3D11) { // Now that Dawn (via D3D12) has finished writing to the texture, we should be // able to read it back by copying it to a staging texture and verifying the // color matches the last D3D12 clear color. - ExpectPixelRGBA8EQ(d3d11Texture.Get(), dxgiKeyedMutex.Get(), d3d12ClearColor2); + ExpectPixelRGBA8EQ(d3d11Texture.Get(), dxgiKeyedMutex.Get(), d3d11Fence.Get(), + nextFenceWaitValue, d3d12ClearColor2); } // 1. Create and clear a D3D11 texture with clearColor @@ -572,9 +685,8 @@ TEST_P(D3D12SharedHandleUsageTests, UninitializedTextureIsCleared) { const wgpu::Color clearColor{1.0f, 0.0f, 0.0f, 1.0f}; wgpu::Texture dawnTexture; ComPtr d3d11Texture; - ComPtr dxgiKeyedMutex; - WrapAndClearD3D11Texture(&baseDawnDescriptor, &baseD3dDescriptor, &dawnTexture, clearColor, - &d3d11Texture, &dxgiKeyedMutex, false); + WrapAndClearD3D11Texture(baseDawnDescriptor, baseD3dDescriptor, clearColor, &dawnTexture, + &d3d11Texture, /*isInitialized=*/false); ASSERT_NE(dawnTexture.Get(), nullptr); // Readback the destination texture and ensure it contains the colors we used @@ -593,7 +705,7 @@ TEST_P(D3D12SharedHandleUsageTests, ReuseExternalImage) { ComPtr d3d11Texture; std::unique_ptr externalImage; WrapSharedHandle(&baseDawnDescriptor, &baseD3dDescriptor, &texture, &d3d11Texture, - &externalImage); + &externalImage, /*fenceSignalValue=*/1); { const wgpu::Color solidRed{1.0f, 0.0f, 0.0f, 1.0f}; ASSERT_NE(texture.Get(), nullptr); @@ -607,9 +719,11 @@ TEST_P(D3D12SharedHandleUsageTests, ReuseExternalImage) { texture.Destroy(); // Create another Dawn texture then clear it with another color. - dawn::native::d3d12::ExternalImageAccessDescriptorDXGIKeyedMutex externalAccessDesc; + dawn::native::d3d12::ExternalImageAccessDescriptorDXGISharedHandle externalAccessDesc; externalAccessDesc.isInitialized = true; externalAccessDesc.usage = static_cast(baseDawnDescriptor.usage); + externalAccessDesc.fenceWaitValue = 1; + externalAccessDesc.fenceSignalValue = 2; texture = wgpu::Texture::Acquire(externalImage->ProduceTexture(device.Get(), &externalAccessDesc)); @@ -625,49 +739,46 @@ TEST_P(D3D12SharedHandleUsageTests, ReuseExternalImage) { EXPECT_PIXEL_RGBA8_EQ(RGBA8(0, 0, 0xFF, 0xFF), texture.Get(), 0, 0); } + + texture.Destroy(); } -TEST_P(D3D12SharedHandleUsageTests, RecursiveExternalImageAccess) { +TEST_P(D3D12SharedHandleUsageTests, ConcurrentExternalImageReadAccess) { DAWN_TEST_UNSUPPORTED_IF(UsesWire()); - // Create the first Dawn texture then clear it to red. - wgpu::Texture texture1; + // Create Dawn texture with write access, then clear it to red. + wgpu::Texture texture; ComPtr d3d11Texture; std::unique_ptr externalImage; - WrapSharedHandle(&baseDawnDescriptor, &baseD3dDescriptor, &texture1, &d3d11Texture, - &externalImage); + WrapSharedHandle(&baseDawnDescriptor, &baseD3dDescriptor, &texture, &d3d11Texture, + &externalImage, /*fenceSignalValue=*/1); { const wgpu::Color solidRed{1.0f, 0.0f, 0.0f, 1.0f}; - ASSERT_NE(texture1.Get(), nullptr); - ClearImage(texture1.Get(), solidRed, device); + ASSERT_NE(texture.Get(), nullptr); + ClearImage(texture.Get(), solidRed, device); - EXPECT_PIXEL_RGBA8_EQ(RGBA8(0xFF, 0, 0, 0xFF), texture1.Get(), 0, 0); + EXPECT_PIXEL_RGBA8_EQ(RGBA8(0xFF, 0, 0, 0xFF), texture.Get(), 0, 0); } - // Create another Dawn texture then clear it with another color. - dawn::native::d3d12::ExternalImageAccessDescriptorDXGIKeyedMutex externalAccessDesc; - externalAccessDesc.isInitialized = true; - externalAccessDesc.usage = static_cast(baseDawnDescriptor.usage); + texture.Destroy(); + + // Create two Dawn textures for concurrent read. + dawn::native::d3d12::ExternalImageAccessDescriptorDXGISharedHandle externalAccessDesc; + externalAccessDesc.isInitialized = true; + externalAccessDesc.usage = WGPUTextureUsage_CopySrc; + externalAccessDesc.fenceWaitValue = 1; + externalAccessDesc.fenceSignalValue = 2; + + wgpu::Texture texture1 = + wgpu::Texture::Acquire(externalImage->ProduceTexture(device.Get(), &externalAccessDesc)); - // Acquire the ExternalImageDXGI again without destroying the original texture. wgpu::Texture texture2 = wgpu::Texture::Acquire(externalImage->ProduceTexture(device.Get(), &externalAccessDesc)); - // Check again that the new texture is still red + // Check again that the new textures are also red. + EXPECT_PIXEL_RGBA8_EQ(RGBA8(0xFF, 0, 0, 0xFF), texture1.Get(), 0, 0); EXPECT_PIXEL_RGBA8_EQ(RGBA8(0xFF, 0, 0, 0xFF), texture2.Get(), 0, 0); - // Clear the new texture to blue - { - const wgpu::Color solidBlue{0.0f, 0.0f, 1.0f, 1.0f}; - ASSERT_NE(texture2.Get(), nullptr); - ClearImage(texture2.Get(), solidBlue, device); - - EXPECT_PIXEL_RGBA8_EQ(RGBA8(0, 0, 0xFF, 0xFF), texture2.Get(), 0, 0); - } - - // Check that the original texture is also blue - EXPECT_PIXEL_RGBA8_EQ(RGBA8(0, 0, 0xFF, 0xFF), texture1.Get(), 0, 0); - texture1.Destroy(); texture2.Destroy(); } @@ -676,17 +787,18 @@ TEST_P(D3D12SharedHandleUsageTests, RecursiveExternalImageAccess) { TEST_P(D3D12SharedHandleUsageTests, ExternalImageUsage) { DAWN_TEST_UNSUPPORTED_IF(UsesWire()); - dawn::native::d3d12::ExternalImageAccessDescriptorDXGIKeyedMutex externalAccessDesc; - externalAccessDesc.isInitialized = true; - wgpu::Texture texture; ComPtr d3d11Texture; std::unique_ptr externalImage; WrapSharedHandle(&baseDawnDescriptor, &baseD3dDescriptor, &texture, &d3d11Texture, - &externalImage); + &externalImage, /*fenceSignalValue=*/1); ASSERT_NE(texture.Get(), nullptr); + dawn::native::d3d12::ExternalImageAccessDescriptorDXGISharedHandle externalAccessDesc; + externalAccessDesc.isInitialized = true; externalAccessDesc.usage = WGPUTextureUsage_StorageBinding; + externalAccessDesc.fenceWaitValue = 1; + externalAccessDesc.fenceSignalValue = 0; // No need to signal texture = wgpu::Texture::Acquire(externalImage->ProduceTexture(device.Get(), &externalAccessDesc)); ASSERT_EQ(texture.Get(), nullptr); @@ -707,7 +819,7 @@ TEST_P(D3D12SharedHandleUsageTests, ReuseExternalImageWithMultipleDevices) { // Create the Dawn texture then clear it to red using the first (default) device. WrapSharedHandle(&baseDawnDescriptor, &baseD3dDescriptor, &texture, &d3d11Texture, - &externalImage); + &externalImage, /*fenceSignalValue=*/1); const wgpu::Color solidRed{1.0f, 0.0f, 0.0f, 1.0f}; ASSERT_NE(texture.Get(), nullptr); ClearImage(texture.Get(), solidRed, device); @@ -718,8 +830,10 @@ TEST_P(D3D12SharedHandleUsageTests, ReuseExternalImageWithMultipleDevices) { texture.Destroy(); // Create the Dawn texture then clear it to blue using the second device. - dawn::native::d3d12::ExternalImageAccessDescriptorDXGIKeyedMutex externalAccessDesc; + dawn::native::d3d12::ExternalImageAccessDescriptorDXGISharedHandle externalAccessDesc; externalAccessDesc.usage = static_cast(baseDawnDescriptor.usage); + externalAccessDesc.fenceWaitValue = 1; + externalAccessDesc.fenceSignalValue = 2; wgpu::Device otherDevice = wgpu::Device::Acquire(GetAdapter().CreateDevice()); @@ -734,6 +848,8 @@ TEST_P(D3D12SharedHandleUsageTests, ReuseExternalImageWithMultipleDevices) { // Re-create the Dawn texture using the first (default) device. externalAccessDesc.isInitialized = true; + externalAccessDesc.fenceWaitValue = 2; + externalAccessDesc.fenceSignalValue = 3; texture = wgpu::Texture::Acquire(externalImage->ProduceTexture(device.Get(), &externalAccessDesc)); ASSERT_NE(texture.Get(), nullptr); @@ -743,5 +859,9 @@ TEST_P(D3D12SharedHandleUsageTests, ReuseExternalImageWithMultipleDevices) { EXPECT_PIXEL_RGBA8_EQ(RGBA8(0, 0, 0xFF, 0xFF), texture.Get(), 0, 0); } -DAWN_INSTANTIATE_TEST(D3D12SharedHandleValidation, D3D12Backend()); -DAWN_INSTANTIATE_TEST(D3D12SharedHandleUsageTests, D3D12Backend()); +DAWN_INSTANTIATE_TEST_P(D3D12SharedHandleValidation, + {D3D12Backend()}, + {SyncMode::kKeyedMutex, SyncMode::kFence}); +DAWN_INSTANTIATE_TEST_P(D3D12SharedHandleUsageTests, + {D3D12Backend()}, + {SyncMode::kKeyedMutex, SyncMode::kFence});