d3d12: External image import using multiple wait fences and signal fence

To support concurrent readers on multiple command queues/devices, Dawn
must support importing via wait multiple fences and also export a signal
fence. The previous implementation of using a single fence for waiting
and signaling doesn't work for concurrent reads across queues.

This CL adds support for specifying multiple wait fences for
ExternalImageDXGI via a BeginAccess method that's meant to replace
ProduceTexture. There's also an EndAccess method that returns a signal
fence for the client.

For performance reasons, we use the same fence as the signal fence that
the Device uses internally, and record its value on the texture after
ExecuteCommandLists. Therefore, the CL also makes the Device's internal
fence a shared fence so that we can export it to the client.

This CL also expands the ConcurrentExternalImageReadAccess test so that
it tests fence synchronization across multiple devices. A number of test
helpers also now take an optional device parameter so that we can use
pixel value expectations with secondary devices.

Bug: dawn:576
Change-Id: I6bc86808ede9b5aacf87667106cbd16731a12516
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/99746
Commit-Queue: Sunny Sachanandani <sunnyps@chromium.org>
Reviewed-by: Austin Eng <enga@chromium.org>
Kokoro: Kokoro <noreply+kokoro@google.com>
This commit is contained in:
Sunny Sachanandani 2022-09-07 19:48:45 +00:00 committed by Dawn LUCI CQ
parent 29aa613dcf
commit 5881e735f9
19 changed files with 763 additions and 346 deletions

View File

@ -21,6 +21,7 @@
#include <wrl/client.h>
#include <memory>
#include <vector>
#include "dawn/dawn_wsi.h"
#include "dawn/native/DawnNative.h"
@ -54,30 +55,30 @@ struct DAWN_NATIVE_EXPORT ExternalImageDescriptorDXGISharedHandle : ExternalImag
ExternalImageDescriptorDXGISharedHandle();
// Note: SharedHandle must be a handle to a texture object.
// 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;
// Whether fence synchronization should be used instead of texture's keyed mutex.
bool useFenceSynchronization = false;
};
// Keyed mutex acquire/release uses a fixed key of 0 to match Chromium behavior.
constexpr UINT64 kDXGIKeyedMutexAcquireReleaseKey = 0;
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;
struct DAWN_NATIVE_EXPORT ExternalImageDXGIFenceDescriptor {
// Shared handle for the fence. This never passes ownership to the callee (when used as an input
// parameter) or to the caller (when used as a return value or output parameter).
HANDLE fenceHandle = nullptr;
// 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;
// The value that was previously signaled on this fence and should be waited on.
uint64_t fenceValue = 0;
};
struct DAWN_NATIVE_EXPORT ExternalImageDXGIBeginAccessDescriptor {
bool isInitialized = false; // Whether the texture is initialized on import
WGPUTextureUsageFlags usage = WGPUTextureUsage_None;
// A list of fences to wait on before accessing the texture.
std::vector<ExternalImageDXGIFenceDescriptor> waitFences;
// Whether the texture is for a WebGPU swap chain.
bool isSwapChainTexture = false;
@ -85,7 +86,7 @@ struct DAWN_NATIVE_EXPORT ExternalImageAccessDescriptorDXGISharedHandle
// TODO(dawn:576): Remove after changing Chromium code to use the new struct name.
struct DAWN_NATIVE_EXPORT ExternalImageAccessDescriptorDXGIKeyedMutex
: ExternalImageAccessDescriptorDXGISharedHandle {
: ExternalImageDXGIBeginAccessDescriptor {
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.
@ -105,11 +106,19 @@ class DAWN_NATIVE_EXPORT ExternalImageDXGI {
// guaranteed to fail e.g. after device destruction.
bool IsValid() const;
// TODO(sunnyps): |device| is ignored - remove after Chromium migrates to single parameter call.
// TODO(sunnyps): |device| is ignored - remove after Chromium migrates to BeginAccess().
WGPUTexture ProduceTexture(WGPUDevice device,
const ExternalImageAccessDescriptorDXGISharedHandle* descriptor);
const ExternalImageDXGIBeginAccessDescriptor* descriptor);
WGPUTexture ProduceTexture(const ExternalImageAccessDescriptorDXGISharedHandle* descriptor);
// Creates WGPUTexture wrapping the DXGI shared handle. The provided wait fences or the
// texture's keyed mutex will be synchronized before using the texture in any command lists.
// Empty fences (nullptr handle) are ignored for convenience (EndAccess can return such fences).
WGPUTexture BeginAccess(const ExternalImageDXGIBeginAccessDescriptor* descriptor);
// Returns the signalFence that the client must wait on for correct synchronization. Can return
// an empty fence (nullptr handle) if the texture wasn't accessed by Dawn.
// Note that merely calling Destroy() on the WGPUTexture does not ensure synchronization.
void EndAccess(WGPUTexture texture, ExternalImageDXGIFenceDescriptor* signalFence);
private:
explicit ExternalImageDXGI(std::unique_ptr<ExternalImageDXGIImpl> impl);

View File

@ -237,12 +237,6 @@ struct DAWN_NATIVE_EXPORT ExternalImageDescriptor {
ExternalImageType mType;
};
struct DAWN_NATIVE_EXPORT ExternalImageAccessDescriptor {
public:
bool isInitialized = false; // Whether the texture is initialized on import
WGPUTextureUsageFlags usage = WGPUTextureUsage_None;
};
struct DAWN_NATIVE_EXPORT ExternalImageExportInfo {
public:
bool isInitialized = false; // Whether the texture is initialized after export

View File

@ -417,6 +417,8 @@ source_set("sources") {
"d3d12/DeviceD3D12.h",
"d3d12/ExternalImageDXGIImpl.cpp",
"d3d12/ExternalImageDXGIImpl.h",
"d3d12/FenceD3D12.cpp",
"d3d12/FenceD3D12.h",
"d3d12/Forward.h",
"d3d12/GPUDescriptorHeapAllocationD3D12.cpp",
"d3d12/GPUDescriptorHeapAllocationD3D12.h",

View File

@ -284,6 +284,8 @@ if (DAWN_ENABLE_D3D12)
"d3d12/DeviceD3D12.h"
"d3d12/ExternalImageDXGIImpl.cpp"
"d3d12/ExternalImageDXGIImpl.h"
"d3d12/FenceD3D12.cpp"
"d3d12/FenceD3D12.h"
"d3d12/Forward.h"
"d3d12/GPUDescriptorHeapAllocationD3D12.cpp"
"d3d12/GPUDescriptorHeapAllocationD3D12.h"

View File

@ -123,8 +123,10 @@ MaybeError CommandRecordingContext::ExecuteCommandList(Device* device) {
ID3D12CommandList* d3d12CommandList = GetCommandList();
device->GetCommandQueue()->ExecuteCommandLists(1, &d3d12CommandList);
DAWN_TRY(device->NextSerial());
for (Texture* texture : mSharedTextures) {
texture->SynchronizeImportedTextureAfterUse();
DAWN_TRY(texture->SynchronizeImportedTextureAfterUse());
}
mIsOpen = false;

View File

@ -101,13 +101,15 @@ MaybeError D3D11on12ResourceCacheEntry::AcquireKeyedMutex() {
return {};
}
void D3D11on12ResourceCacheEntry::ReleaseKeyedMutex() {
MaybeError D3D11on12ResourceCacheEntry::ReleaseKeyedMutex() {
ASSERT(mDXGIKeyedMutex != nullptr);
ASSERT(mAcquireCount > 0);
mAcquireCount--;
if (mAcquireCount == 0) {
mDXGIKeyedMutex->ReleaseSync(kDXGIKeyedMutexAcquireReleaseKey);
DAWN_TRY(CheckHRESULT(mDXGIKeyedMutex->ReleaseSync(kDXGIKeyedMutexAcquireReleaseKey),
"D3D12 releasing keyed mutex"));
}
return {};
}
size_t D3D11on12ResourceCacheEntry::HashFunc::operator()(

View File

@ -39,7 +39,7 @@ class D3D11on12ResourceCacheEntry : public RefCounted {
~D3D11on12ResourceCacheEntry() override;
MaybeError AcquireKeyedMutex();
void ReleaseKeyedMutex();
MaybeError ReleaseKeyedMutex();
// Functors necessary for the
// unordered_set<D3D11on12ResourceCacheEntry&>-based cache.

View File

@ -67,17 +67,26 @@ bool ExternalImageDXGI::IsValid() const {
WGPUTexture ExternalImageDXGI::ProduceTexture(
WGPUDevice device,
const ExternalImageAccessDescriptorDXGISharedHandle* descriptor) {
return ProduceTexture(descriptor);
const ExternalImageDXGIBeginAccessDescriptor* descriptor) {
return BeginAccess(descriptor);
}
WGPUTexture ExternalImageDXGI::ProduceTexture(
const ExternalImageAccessDescriptorDXGISharedHandle* descriptor) {
WGPUTexture ExternalImageDXGI::BeginAccess(
const ExternalImageDXGIBeginAccessDescriptor* descriptor) {
if (!IsValid()) {
dawn::ErrorLog() << "Cannot produce texture from external image after device destruction";
dawn::ErrorLog() << "Cannot use external image after device destruction";
return nullptr;
}
return mImpl->ProduceTexture(descriptor);
return mImpl->BeginAccess(descriptor);
}
void ExternalImageDXGI::EndAccess(WGPUTexture texture,
ExternalImageDXGIFenceDescriptor* signalFence) {
if (!IsValid()) {
dawn::ErrorLog() << "Cannot use external image after device destruction";
return;
}
mImpl->EndAccess(texture, signalFence);
}
// static

View File

@ -102,12 +102,17 @@ MaybeError Device::Initialize(const DeviceDescriptor* descriptor) {
mCommandQueue.As(&mD3d12SharingContract);
DAWN_TRY(CheckHRESULT(mD3d12Device->CreateFence(uint64_t(GetLastSubmittedCommandSerial()),
D3D12_FENCE_FLAG_NONE, IID_PPV_ARGS(&mFence)),
D3D12_FENCE_FLAG_SHARED, IID_PPV_ARGS(&mFence)),
"D3D12 create fence"));
mFenceEvent = CreateEvent(nullptr, FALSE, FALSE, nullptr);
ASSERT(mFenceEvent != nullptr);
DAWN_TRY(CheckHRESULT(mD3d12Device->CreateSharedHandle(mFence.Get(), nullptr, GENERIC_ALL,
nullptr, &mFenceHandle),
"D3D12 create fence handle"));
ASSERT(mFenceHandle != nullptr);
// Initialize backend services
mCommandAllocatorManager = std::make_unique<CommandAllocatorManager>(this);
@ -200,6 +205,10 @@ ID3D12SharingContract* Device::GetSharingContract() const {
return mD3d12SharingContract.Get();
}
HANDLE Device::GetFenceHandle() const {
return mFenceHandle;
}
ComPtr<ID3D12CommandSignature> Device::GetDispatchIndirectSignature() const {
return mDispatchIndirectSignature;
}
@ -327,8 +336,8 @@ MaybeError Device::TickImpl() {
mUsedComObjectRefs.ClearUpTo(completedSerial);
if (mPendingCommands.IsOpen()) {
// CommandRecordingContext::ExecuteCommandList() calls NextSerial().
DAWN_TRY(ExecutePendingCommandContext());
DAWN_TRY(NextSerial());
}
DAWN_TRY(CheckDebugLayerAndGenerateErrors());
@ -543,25 +552,12 @@ std::unique_ptr<ExternalImageDXGIImpl> Device::CreateExternalImageDXGIImpl(
return nullptr;
}
// 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<ID3D12Resource> d3d12Resource;
if (FAILED(GetD3D12Device()->OpenSharedHandle(textureSharedHandle,
if (FAILED(GetD3D12Device()->OpenSharedHandle(descriptor->sharedHandle,
IID_PPV_ARGS(&d3d12Resource)))) {
return nullptr;
}
Microsoft::WRL::ComPtr<ID3D12Fence> d3d12Fence;
if (descriptor->fenceSharedHandle &&
FAILED(GetD3D12Device()->OpenSharedHandle(descriptor->fenceSharedHandle,
IID_PPV_ARGS(&d3d12Fence)))) {
return nullptr;
}
const TextureDescriptor* textureDescriptor = FromAPI(descriptor->cTextureDescriptor);
if (ConsumedError(ValidateTextureDescriptor(this, textureDescriptor))) {
@ -589,7 +585,7 @@ std::unique_ptr<ExternalImageDXGIImpl> Device::CreateExternalImageDXGIImpl(
}
auto impl = std::make_unique<ExternalImageDXGIImpl>(
this, std::move(d3d12Resource), std::move(d3d12Fence), descriptor->cTextureDescriptor);
this, std::move(d3d12Resource), textureDescriptor, descriptor->useFenceSynchronization);
mExternalImageList.Append(impl.get());
return impl;
}
@ -597,17 +593,14 @@ std::unique_ptr<ExternalImageDXGIImpl> Device::CreateExternalImageDXGIImpl(
Ref<TextureBase> Device::CreateD3D12ExternalTexture(
const TextureDescriptor* descriptor,
ComPtr<ID3D12Resource> d3d12Texture,
ComPtr<ID3D12Fence> d3d12Fence,
std::vector<Ref<Fence>> waitFences,
Ref<D3D11on12ResourceCacheEntry> d3d11on12Resource,
uint64_t fenceWaitValue,
uint64_t fenceSignalValue,
bool isSwapChainTexture,
bool isInitialized) {
Ref<Texture> dawnTexture;
if (ConsumedError(Texture::CreateExternalImage(
this, descriptor, std::move(d3d12Texture), std::move(d3d12Fence),
std::move(d3d11on12Resource), fenceWaitValue, fenceSignalValue,
isSwapChainTexture, isInitialized),
this, descriptor, std::move(d3d12Texture), std::move(waitFences),
std::move(d3d11on12Resource), isSwapChainTexture, isInitialized),
&dawnTexture)) {
return nullptr;
}

View File

@ -60,6 +60,7 @@ class Device final : public DeviceBase {
ID3D12Device* GetD3D12Device() const;
ComPtr<ID3D12CommandQueue> GetCommandQueue() const;
ID3D12SharingContract* GetSharingContract() const;
HANDLE GetFenceHandle() const;
ComPtr<ID3D12CommandSignature> GetDispatchIndirectSignature() const;
ComPtr<ID3D12CommandSignature> GetDrawIndirectSignature() const;
@ -88,6 +89,7 @@ class Device final : public DeviceBase {
void ReferenceUntilUnused(ComPtr<IUnknown> object);
// Execute pending CommandRecordingContext, and increment last submitted serial if needed.
MaybeError ExecutePendingCommandContext();
ResultOrError<std::unique_ptr<StagingBufferBase>> CreateStagingBuffer(size_t size) override;
@ -136,10 +138,8 @@ class Device final : public DeviceBase {
Ref<TextureBase> CreateD3D12ExternalTexture(const TextureDescriptor* descriptor,
ComPtr<ID3D12Resource> d3d12Texture,
ComPtr<ID3D12Fence> d3d12Fence,
std::vector<Ref<Fence>> waitFences,
Ref<D3D11on12ResourceCacheEntry> d3d11on12Resource,
uint64_t fenceWaitValue,
uint64_t fenceSignalValue,
bool isSwapChainTexture,
bool isInitialized);
@ -218,6 +218,7 @@ class Device final : public DeviceBase {
ComPtr<ID3D12Fence> mFence;
HANDLE mFenceEvent = nullptr;
HANDLE mFenceHandle = nullptr;
ResultOrError<ExecutionSerial> CheckAndUpdateCompletedSerials() override;
ComPtr<ID3D12Device> mD3d12Device; // Device is owned by adapter and will not be outlived.

View File

@ -14,9 +14,8 @@
#include "dawn/native/d3d12/ExternalImageDXGIImpl.h"
#include <d3d12.h>
#include <utility>
#include <vector>
#include "dawn/common/Log.h"
#include "dawn/native/D3D12Backend.h"
@ -28,26 +27,26 @@ namespace dawn::native::d3d12 {
ExternalImageDXGIImpl::ExternalImageDXGIImpl(Device* backendDevice,
Microsoft::WRL::ComPtr<ID3D12Resource> d3d12Resource,
Microsoft::WRL::ComPtr<ID3D12Fence> d3d12Fence,
const WGPUTextureDescriptor* descriptor)
const TextureDescriptor* textureDescriptor,
bool useFenceSynchronization)
: mBackendDevice(backendDevice),
mD3D12Resource(std::move(d3d12Resource)),
mD3D12Fence(std::move(d3d12Fence)),
mUseFenceSynchronization(useFenceSynchronization),
mD3D11on12ResourceCache(std::make_unique<D3D11on12ResourceCache>()),
mUsage(descriptor->usage),
mDimension(descriptor->dimension),
mSize(descriptor->size),
mFormat(descriptor->format),
mMipLevelCount(descriptor->mipLevelCount),
mSampleCount(descriptor->sampleCount) {
mUsage(textureDescriptor->usage),
mDimension(textureDescriptor->dimension),
mSize(textureDescriptor->size),
mFormat(textureDescriptor->format),
mMipLevelCount(textureDescriptor->mipLevelCount),
mSampleCount(textureDescriptor->sampleCount) {
ASSERT(mBackendDevice != nullptr);
ASSERT(mD3D12Resource != nullptr);
ASSERT(!descriptor->nextInChain ||
descriptor->nextInChain->sType == WGPUSType_DawnTextureInternalUsageDescriptor);
if (descriptor->nextInChain) {
mUsageInternal =
reinterpret_cast<const WGPUDawnTextureInternalUsageDescriptor*>(descriptor->nextInChain)
->internalUsage;
ASSERT(!textureDescriptor->nextInChain || textureDescriptor->nextInChain->sType ==
wgpu::SType::DawnTextureInternalUsageDescriptor);
if (textureDescriptor->nextInChain) {
mUsageInternal = reinterpret_cast<const wgpu::DawnTextureInternalUsageDescriptor*>(
textureDescriptor->nextInChain)
->internalUsage;
}
}
@ -62,45 +61,55 @@ bool ExternalImageDXGIImpl::IsValid() const {
void ExternalImageDXGIImpl::Destroy() {
if (IsInList()) {
RemoveFromList();
// Keep fence alive until any pending signal calls are done on the GPU.
mBackendDevice->ConsumedError(mBackendDevice->ExecutePendingCommandContext());
mBackendDevice->ConsumedError(mBackendDevice->NextSerial());
mBackendDevice->ReferenceUntilUnused(mD3D12Fence.Get());
mBackendDevice = nullptr;
mD3D12Resource.Reset();
mD3D12Fence.Reset();
mD3D11on12ResourceCache.reset();
mD3D12Resource = nullptr;
mD3D11on12ResourceCache = nullptr;
}
}
WGPUTexture ExternalImageDXGIImpl::ProduceTexture(
const ExternalImageAccessDescriptorDXGISharedHandle* descriptor) {
WGPUTexture ExternalImageDXGIImpl::BeginAccess(
const ExternalImageDXGIBeginAccessDescriptor* descriptor) {
ASSERT(mBackendDevice != nullptr);
ASSERT(descriptor != nullptr);
// Ensure the texture usage is allowed
if (!IsSubset(descriptor->usage, mUsage)) {
if (!IsSubset(descriptor->usage, static_cast<WGPUTextureUsageFlags>(mUsage))) {
dawn::ErrorLog() << "Texture usage is not valid for external image";
return nullptr;
}
TextureDescriptor textureDescriptor = {};
textureDescriptor.usage = static_cast<wgpu::TextureUsage>(descriptor->usage);
textureDescriptor.dimension = static_cast<wgpu::TextureDimension>(mDimension);
textureDescriptor.dimension = mDimension;
textureDescriptor.size = {mSize.width, mSize.height, mSize.depthOrArrayLayers};
textureDescriptor.format = static_cast<wgpu::TextureFormat>(mFormat);
textureDescriptor.format = mFormat;
textureDescriptor.mipLevelCount = mMipLevelCount;
textureDescriptor.sampleCount = mSampleCount;
DawnTextureInternalUsageDescriptor internalDesc = {};
if (mUsageInternal) {
if (mUsageInternal != wgpu::TextureUsage::None) {
textureDescriptor.nextInChain = &internalDesc;
internalDesc.internalUsage = static_cast<wgpu::TextureUsage>(mUsageInternal);
internalDesc.internalUsage = mUsageInternal;
internalDesc.sType = wgpu::SType::DawnTextureInternalUsageDescriptor;
}
std::vector<Ref<Fence>> waitFences;
Ref<D3D11on12ResourceCacheEntry> d3d11on12Resource;
if (!mD3D12Fence) {
if (mUseFenceSynchronization) {
for (const ExternalImageDXGIFenceDescriptor& fenceDescriptor : descriptor->waitFences) {
ASSERT(fenceDescriptor.fenceHandle != nullptr);
// TODO(sunnyps): Use a fence cache instead of re-importing fences on each BeginAccess.
Ref<Fence> fence;
if (mBackendDevice->ConsumedError(
Fence::CreateFromHandle(mBackendDevice->GetD3D12Device(),
fenceDescriptor.fenceHandle,
fenceDescriptor.fenceValue),
&fence)) {
dawn::ErrorLog() << "Unable to create D3D12 fence for external image";
return nullptr;
}
waitFences.push_back(std::move(fence));
}
} else {
d3d11on12Resource = mD3D11on12ResourceCache->GetOrCreateD3D11on12Resource(
mBackendDevice, mD3D12Resource.Get());
if (d3d11on12Resource == nullptr) {
@ -110,10 +119,27 @@ WGPUTexture ExternalImageDXGIImpl::ProduceTexture(
}
Ref<TextureBase> texture = mBackendDevice->CreateD3D12ExternalTexture(
&textureDescriptor, mD3D12Resource, mD3D12Fence, std::move(d3d11on12Resource),
descriptor->fenceWaitValue, descriptor->fenceSignalValue, descriptor->isSwapChainTexture,
descriptor->isInitialized);
&textureDescriptor, mD3D12Resource, std::move(waitFences), std::move(d3d11on12Resource),
descriptor->isSwapChainTexture, descriptor->isInitialized);
return ToAPI(texture.Detach());
}
void ExternalImageDXGIImpl::EndAccess(WGPUTexture texture,
ExternalImageDXGIFenceDescriptor* signalFence) {
ASSERT(signalFence != nullptr);
Texture* backendTexture = ToBackend(FromAPI(texture));
ASSERT(backendTexture != nullptr);
if (mUseFenceSynchronization) {
ExecutionSerial fenceValue;
if (mBackendDevice->ConsumedError(backendTexture->EndAccess(), &fenceValue)) {
dawn::ErrorLog() << "D3D12 fence end access failed";
return;
}
signalFence->fenceHandle = mBackendDevice->GetFenceHandle();
signalFence->fenceValue = static_cast<uint64_t>(fenceValue);
}
}
} // namespace dawn::native::d3d12

View File

@ -21,7 +21,12 @@
#include "dawn/common/LinkedList.h"
#include "dawn/dawn_wsi.h"
#include "dawn/native/D3D12Backend.h"
#include "dawn/native/Error.h"
#include "dawn/native/Forward.h"
#include "dawn/native/IntegerTypes.h"
#include "dawn/native/d3d12/FenceD3D12.h"
#include "dawn/webgpu_cpp.h"
struct ID3D12Resource;
struct ID3D12Fence;
@ -30,15 +35,15 @@ namespace dawn::native::d3d12 {
class D3D11on12ResourceCache;
class Device;
struct ExternalImageAccessDescriptorDXGISharedHandle;
struct ExternalImageDXGIBeginAccessDescriptor;
struct ExternalImageDescriptorDXGISharedHandle;
class ExternalImageDXGIImpl : public LinkNode<ExternalImageDXGIImpl> {
public:
ExternalImageDXGIImpl(Device* backendDevice,
Microsoft::WRL::ComPtr<ID3D12Resource> d3d12Resource,
Microsoft::WRL::ComPtr<ID3D12Fence> d3d12Fence,
const WGPUTextureDescriptor* descriptor);
const TextureDescriptor* textureDescriptor,
bool useFenceSynchronization);
~ExternalImageDXGIImpl();
ExternalImageDXGIImpl(const ExternalImageDXGIImpl&) = delete;
@ -48,21 +53,22 @@ class ExternalImageDXGIImpl : public LinkNode<ExternalImageDXGIImpl> {
bool IsValid() const;
WGPUTexture ProduceTexture(const ExternalImageAccessDescriptorDXGISharedHandle* descriptor);
WGPUTexture BeginAccess(const ExternalImageDXGIBeginAccessDescriptor* descriptor);
void EndAccess(WGPUTexture texture, ExternalImageDXGIFenceDescriptor* signalFence);
private:
Device* mBackendDevice;
Device* mBackendDevice = nullptr;
Microsoft::WRL::ComPtr<ID3D12Resource> mD3D12Resource;
Microsoft::WRL::ComPtr<ID3D12Fence> mD3D12Fence;
const bool mUseFenceSynchronization;
std::unique_ptr<D3D11on12ResourceCache> mD3D11on12ResourceCache;
// Contents of WGPUTextureDescriptor are stored individually since the descriptor
// could outlive this image.
WGPUTextureUsageFlags mUsage;
WGPUTextureUsageFlags mUsageInternal = WGPUTextureUsage_None;
WGPUTextureDimension mDimension;
WGPUExtent3D mSize;
WGPUTextureFormat mFormat;
wgpu::TextureUsage mUsage;
wgpu::TextureUsage mUsageInternal = wgpu::TextureUsage::None;
wgpu::TextureDimension mDimension;
Extent3D mSize;
wgpu::TextureFormat mFormat;
uint32_t mMipLevelCount;
uint32_t mSampleCount;
};

View File

@ -0,0 +1,61 @@
// Copyright 2022 The Dawn Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "dawn/native/d3d12/FenceD3D12.h"
#include <utility>
#include "dawn/common/Log.h"
#include "dawn/native/Error.h"
#include "dawn/native/d3d12/D3D12Error.h"
#include "dawn/native/d3d12/DeviceD3D12.h"
namespace dawn::native::d3d12 {
// static
ResultOrError<Ref<Fence>> Fence::CreateFromHandle(ID3D12Device* device,
HANDLE unownedHandle,
UINT64 fenceValue) {
ASSERT(unownedHandle != nullptr);
HANDLE ownedHandle = nullptr;
if (!::DuplicateHandle(::GetCurrentProcess(), unownedHandle, ::GetCurrentProcess(),
&ownedHandle, 0, FALSE, DUPLICATE_SAME_ACCESS)) {
return DAWN_DEVICE_LOST_ERROR("D3D12 fence dup handle");
}
ComPtr<ID3D12Fence> d3d12Fence;
DAWN_TRY_WITH_CLEANUP(
CheckHRESULT(device->OpenSharedHandle(ownedHandle, IID_PPV_ARGS(&d3d12Fence)),
"D3D12 fence open handle"),
{ ::CloseHandle(ownedHandle); });
return AcquireRef(new Fence(std::move(d3d12Fence), fenceValue, ownedHandle));
}
Fence::Fence(ComPtr<ID3D12Fence> d3d12Fence, UINT64 fenceValue, HANDLE sharedHandle)
: mD3D12Fence(std::move(d3d12Fence)), mFenceValue(fenceValue), mSharedHandle(sharedHandle) {}
Fence::~Fence() {
if (mSharedHandle != nullptr) {
::CloseHandle(mSharedHandle);
}
}
ID3D12Fence* Fence::GetD3D12Fence() const {
return mD3D12Fence.Get();
}
UINT64 Fence::GetFenceValue() const {
return mFenceValue;
}
} // namespace dawn::native::d3d12

View File

@ -0,0 +1,45 @@
// Copyright 2022 The Dawn Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#ifndef SRC_DAWN_NATIVE_D3D12_FENCED3D12_H_
#define SRC_DAWN_NATIVE_D3D12_FENCED3D12_H_
#include "dawn/common/RefCounted.h"
#include "dawn/native/DawnNative.h"
#include "dawn/native/Error.h"
#include "dawn/native/d3d12/d3d12_platform.h"
namespace dawn::native::d3d12 {
class Fence : public RefCounted {
public:
static ResultOrError<Ref<Fence>> CreateFromHandle(ID3D12Device* device,
HANDLE unownedHandle,
UINT64 fenceValue);
ID3D12Fence* GetD3D12Fence() const;
UINT64 GetFenceValue() const;
private:
Fence(ComPtr<ID3D12Fence> d3d12Fence, UINT64 fenceValue, HANDLE sharedHandle);
~Fence() override;
ComPtr<ID3D12Fence> mD3D12Fence;
const UINT64 mFenceValue;
const HANDLE mSharedHandle;
};
} // namespace dawn::native::d3d12
#endif // SRC_DAWN_NATIVE_D3D12_FENCED3D12_H_

View File

@ -22,11 +22,15 @@
#include "dawn/native/DynamicUploader.h"
#include "dawn/native/EnumMaskIterator.h"
#include "dawn/native/Error.h"
#include "dawn/native/IntegerTypes.h"
#include "dawn/native/ResourceMemoryAllocation.h"
#include "dawn/native/ToBackend.h"
#include "dawn/native/d3d12/BufferD3D12.h"
#include "dawn/native/d3d12/CommandRecordingContext.h"
#include "dawn/native/d3d12/D3D11on12Util.h"
#include "dawn/native/d3d12/D3D12Error.h"
#include "dawn/native/d3d12/DeviceD3D12.h"
#include "dawn/native/d3d12/Forward.h"
#include "dawn/native/d3d12/HeapD3D12.h"
#include "dawn/native/d3d12/ResourceAllocatorManagerD3D12.h"
#include "dawn/native/d3d12/StagingBufferD3D12.h"
@ -510,17 +514,16 @@ ResultOrError<Ref<Texture>> Texture::CreateExternalImage(
Device* device,
const TextureDescriptor* descriptor,
ComPtr<ID3D12Resource> d3d12Texture,
ComPtr<ID3D12Fence> d3d12Fence,
std::vector<Ref<Fence>> waitFences,
Ref<D3D11on12ResourceCacheEntry> d3d11on12Resource,
uint64_t fenceWaitValue,
uint64_t fenceSignalValue,
bool isSwapChainTexture,
bool isInitialized) {
Ref<Texture> dawnTexture =
AcquireRef(new Texture(device, descriptor, TextureState::OwnedExternal));
DAWN_TRY(dawnTexture->InitializeAsExternalTexture(
std::move(d3d12Texture), std::move(d3d12Fence), std::move(d3d11on12Resource),
fenceWaitValue, fenceSignalValue, isSwapChainTexture));
DAWN_TRY(
dawnTexture->InitializeAsExternalTexture(std::move(d3d12Texture), std::move(waitFences),
std::move(d3d11on12Resource), isSwapChainTexture));
// Importing a multi-planar format must be initialized. This is required because
// a shared multi-planar format cannot be initialized by Dawn.
@ -545,10 +548,8 @@ ResultOrError<Ref<Texture>> Texture::Create(Device* device,
}
MaybeError Texture::InitializeAsExternalTexture(ComPtr<ID3D12Resource> d3d12Texture,
ComPtr<ID3D12Fence> d3d12Fence,
std::vector<Ref<Fence>> waitFences,
Ref<D3D11on12ResourceCacheEntry> d3d11on12Resource,
uint64_t fenceWaitValue,
uint64_t fenceSignalValue,
bool isSwapChainTexture) {
D3D12_RESOURCE_DESC desc = d3d12Texture->GetDesc();
mD3D12ResourceFlags = desc.Flags;
@ -560,10 +561,8 @@ MaybeError Texture::InitializeAsExternalTexture(ComPtr<ID3D12Resource> d3d12Text
// memory management.
mResourceAllocation = {info, 0, std::move(d3d12Texture), nullptr};
mD3D12Fence = std::move(d3d12Fence);
mWaitFences = std::move(waitFences);
mD3D11on12Resource = std::move(d3d11on12Resource);
mFenceWaitValue = fenceWaitValue;
mFenceSignalValue = fenceSignalValue;
mSwapChainTexture = isSwapChainTexture;
SetLabelHelper("Dawn_ExternalTexture");
@ -679,23 +678,34 @@ void Texture::DestroyImpl() {
// ID3D12SharingContract::Present.
mSwapChainTexture = false;
// Signal the fence on destroy after all uses of the texture.
if (mD3D12Fence != nullptr && mFenceSignalValue != 0) {
// Enqueue a fence wait if we haven't already; otherwise the fence signal will be racy.
if (mFenceWaitValue != UINT64_MAX) {
device->ConsumedError(
CheckHRESULT(device->GetCommandQueue()->Wait(mD3D12Fence.Get(), mFenceWaitValue),
"D3D12 fence wait"));
}
device->ConsumedError(
CheckHRESULT(device->GetCommandQueue()->Signal(mD3D12Fence.Get(), mFenceSignalValue),
"D3D12 fence signal"));
}
// Now that the texture has been destroyed. It should release the refptr of the d3d11on12
// resource and the fence.
mD3D11on12Resource = nullptr;
mD3D12Fence = nullptr;
}
ResultOrError<ExecutionSerial> Texture::EndAccess() {
ASSERT(mD3D11on12Resource == nullptr);
// Synchronize if texture access wasn't synchronized already due to ExecuteCommandLists.
if (!mSignalFenceValue.has_value()) {
// Needed to ensure that command allocator doesn't get destroyed before pending commands
// are submitted due to calling NextSerial(). No-op if there are no pending commands.
DAWN_TRY(ToBackend(GetDevice())->ExecutePendingCommandContext());
// If there were pending commands that used this texture mSignalFenceValue will be set,
// but if it's still not set, generate a signal fence after waiting on wait fences.
if (!mSignalFenceValue.has_value()) {
DAWN_TRY(SynchronizeImportedTextureBeforeUse());
DAWN_TRY(ToBackend(GetDevice())->NextSerial());
DAWN_TRY(SynchronizeImportedTextureAfterUse());
ASSERT(mSignalFenceValue.has_value());
}
}
// Explicitly call reset() since std::move() on optional doesn't make it std::nullopt.
ExecutionSerial ret = mSignalFenceValue.value();
mSignalFenceValue.reset();
return ret;
}
DXGI_FORMAT Texture::GetD3D12Format() const {
@ -735,23 +745,25 @@ 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;
// Perform the wait only on the first call.
Device* device = ToBackend(GetDevice());
for (Ref<Fence>& fence : mWaitFences) {
DAWN_TRY(CheckHRESULT(device->GetCommandQueue()->Wait(fence->GetD3D12Fence(),
fence->GetFenceValue()),
"D3D12 fence wait"););
}
mWaitFences.clear();
return {};
}
void Texture::SynchronizeImportedTextureAfterUse() {
MaybeError Texture::SynchronizeImportedTextureAfterUse() {
if (mD3D11on12Resource != nullptr) {
mD3D11on12Resource->ReleaseKeyedMutex();
DAWN_TRY(mD3D11on12Resource->ReleaseKeyedMutex());
} else {
// CommandRecordingContext will call NextSerial() to increment the fence before this call.
mSignalFenceValue = ToBackend(GetDevice())->GetLastSubmittedCommandSerial();
}
// Defer signaling the fence until destroy after all uses of the fence.
return {};
}
void Texture::TrackUsageAndTransitionNow(CommandRecordingContext* commandContext,

View File

@ -15,13 +15,16 @@
#ifndef SRC_DAWN_NATIVE_D3D12_TEXTURED3D12_H_
#define SRC_DAWN_NATIVE_D3D12_TEXTURED3D12_H_
#include <optional>
#include <vector>
#include "dawn/native/Error.h"
#include "dawn/native/Texture.h"
#include "dawn/native/DawnNative.h"
#include "dawn/native/IntegerTypes.h"
#include "dawn/native/PassResourceUsage.h"
#include "dawn/native/d3d12/FenceD3D12.h"
#include "dawn/native/d3d12/IntegerTypes.h"
#include "dawn/native/d3d12/ResourceHeapAllocationD3D12.h"
#include "dawn/native/d3d12/d3d12_platform.h"
@ -45,16 +48,19 @@ class Texture final : public TextureBase {
Device* device,
const TextureDescriptor* descriptor,
ComPtr<ID3D12Resource> d3d12Texture,
ComPtr<ID3D12Fence> d3d12Fence,
std::vector<Ref<Fence>> waitFences,
Ref<D3D11on12ResourceCacheEntry> d3d11on12Resource,
uint64_t fenceWaitValue,
uint64_t fenceSignalValue,
bool isSwapChainTexture,
bool isInitialized);
static ResultOrError<Ref<Texture>> Create(Device* device,
const TextureDescriptor* descriptor,
ComPtr<ID3D12Resource> d3d12Texture);
// For external textures, returns the Device internal fence's value associated with the last
// ExecuteCommandLists that used this texture. If nullopt is returned, the texture wasn't used
// or keyed mutex is used instead of fences for synchronization.
ResultOrError<ExecutionSerial> EndAccess();
DXGI_FORMAT GetD3D12Format() const;
ID3D12Resource* GetD3D12Resource() const;
DXGI_FORMAT GetD3D12CopyableSubresourceFormat(Aspect aspect) const;
@ -75,7 +81,7 @@ class Texture final : public TextureBase {
const SubresourceRange& range);
MaybeError SynchronizeImportedTextureBeforeUse();
void SynchronizeImportedTextureAfterUse();
MaybeError SynchronizeImportedTextureAfterUse();
void TrackUsageAndGetResourceBarrierForPass(CommandRecordingContext* commandContext,
std::vector<D3D12_RESOURCE_BARRIER>* barrier,
@ -102,10 +108,8 @@ class Texture final : public TextureBase {
MaybeError InitializeAsInternalTexture();
MaybeError InitializeAsExternalTexture(ComPtr<ID3D12Resource> d3d12Texture,
ComPtr<ID3D12Fence> d3d12Fence,
std::vector<Ref<Fence>> waitFences,
Ref<D3D11on12ResourceCacheEntry> d3d11on12Resource,
uint64_t fenceWaitValue,
uint64_t fenceSignalValue,
bool isSwapChainTexture);
MaybeError InitializeAsSwapChainTexture(ComPtr<ID3D12Resource> d3d12Texture);
@ -142,10 +146,9 @@ class Texture final : public TextureBase {
ResourceHeapAllocation mResourceAllocation;
// TODO(dawn:1460): Encapsulate imported image fields e.g. std::unique_ptr<ExternalImportInfo>.
ComPtr<ID3D12Fence> mD3D12Fence;
std::vector<Ref<Fence>> mWaitFences;
std::optional<ExecutionSerial> mSignalFenceValue;
Ref<D3D11on12ResourceCacheEntry> mD3D11on12Resource;
uint64_t mFenceWaitValue = 0;
uint64_t mFenceSignalValue = 0;
bool mSwapChainTexture = false;
SubresourceStorage<StateAndDecay> mSubresourceStateAndDecay;

View File

@ -1006,7 +1006,7 @@ std::ostringstream& DawnTestBase::AddBufferExpectation(const char* file,
uint64_t size,
detail::Expectation* expectation) {
uint64_t alignedSize = Align(size, uint64_t(4));
auto readback = ReserveReadback(alignedSize);
auto readback = ReserveReadback(device, alignedSize);
// We need to enqueue the copy immediately because by the time we resolve the expectation,
// the buffer might have been modified.
@ -1031,6 +1031,7 @@ std::ostringstream& DawnTestBase::AddBufferExpectation(const char* file,
std::ostringstream& DawnTestBase::AddTextureExpectationImpl(const char* file,
int line,
wgpu::Device targetDevice,
detail::Expectation* expectation,
const wgpu::Texture& texture,
wgpu::Origin3D origin,
@ -1039,6 +1040,8 @@ std::ostringstream& DawnTestBase::AddTextureExpectationImpl(const char* file,
wgpu::TextureAspect aspect,
uint32_t dataSize,
uint32_t bytesPerRow) {
ASSERT(targetDevice != nullptr);
if (bytesPerRow == 0) {
bytesPerRow = Align(extent.width * dataSize, kTextureBytesPerRowAlignment);
} else {
@ -1050,7 +1053,7 @@ std::ostringstream& DawnTestBase::AddTextureExpectationImpl(const char* file,
uint32_t size = utils::RequiredBytesInCopy(bytesPerRow, rowsPerImage, extent.width,
extent.height, extent.depthOrArrayLayers, dataSize);
auto readback = ReserveReadback(Align(size, 4));
auto readback = ReserveReadback(targetDevice, Align(size, 4));
// We need to enqueue the copy immediately because by the time we resolve the expectation,
// the texture might have been modified.
@ -1059,11 +1062,11 @@ std::ostringstream& DawnTestBase::AddTextureExpectationImpl(const char* file,
wgpu::ImageCopyBuffer imageCopyBuffer =
utils::CreateImageCopyBuffer(readback.buffer, readback.offset, bytesPerRow, rowsPerImage);
wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
wgpu::CommandEncoder encoder = targetDevice.CreateCommandEncoder();
encoder.CopyTextureToBuffer(&imageCopyTexture, &imageCopyBuffer, &extent);
wgpu::CommandBuffer commands = encoder.Finish();
queue.Submit(1, &commands);
targetDevice.GetQueue().Submit(1, &commands);
DeferredExpectation deferred;
deferred.file = file;
@ -1357,9 +1360,12 @@ std::ostringstream& DawnTestBase::ExpectAttachmentDepthStencilTestData(
return EXPECT_TEXTURE_EQ(colorData.data(), colorTexture, {0, 0}, {width, height});
}
void DawnTestBase::WaitABit() {
if (device) {
device.Tick();
void DawnTestBase::WaitABit(wgpu::Device targetDevice) {
if (targetDevice == nullptr) {
targetDevice = this->device;
}
if (targetDevice != nullptr) {
targetDevice.Tick();
}
FlushWire();
@ -1385,18 +1391,21 @@ void DawnTestBase::WaitForAllOperations() {
}
}
DawnTestBase::ReadbackReservation DawnTestBase::ReserveReadback(uint64_t readbackSize) {
DawnTestBase::ReadbackReservation DawnTestBase::ReserveReadback(wgpu::Device targetDevice,
uint64_t readbackSize) {
ReadbackSlot slot;
slot.device = targetDevice;
slot.bufferSize = readbackSize;
// Create and initialize the slot buffer so that it won't unexpectedly affect the count of
// resource lazy clear in the tests.
const std::vector<uint8_t> initialBufferData(readbackSize, 0u);
slot.buffer =
utils::CreateBufferFromData(device, initialBufferData.data(), readbackSize,
utils::CreateBufferFromData(targetDevice, initialBufferData.data(), readbackSize,
wgpu::BufferUsage::MapRead | wgpu::BufferUsage::CopyDst);
ReadbackReservation reservation;
reservation.device = targetDevice;
reservation.buffer = slot.buffer;
reservation.slot = mReadbackSlots.size();
reservation.offset = 0;
@ -1410,6 +1419,8 @@ void DawnTestBase::MapSlotsSynchronously() {
// immediately.
mNumPendingMapOperations = mReadbackSlots.size();
std::vector<wgpu::Device> pendingDevices;
// Map all readback slots
for (size_t i = 0; i < mReadbackSlots.size(); ++i) {
MapReadUserdata* userdata = new MapReadUserdata{this, i};
@ -1417,11 +1428,15 @@ void DawnTestBase::MapSlotsSynchronously() {
const ReadbackSlot& slot = mReadbackSlots[i];
slot.buffer.MapAsync(wgpu::MapMode::Read, 0, wgpu::kWholeMapSize, SlotMapCallback,
userdata);
pendingDevices.push_back(slot.device);
}
// Busy wait until all map operations are done.
while (mNumPendingMapOperations != 0) {
WaitABit();
for (const wgpu::Device& device : pendingDevices) {
WaitABit(device);
}
}
}

View File

@ -302,11 +302,29 @@ class DawnTestBase {
uint64_t size,
detail::Expectation* expectation);
template <typename T, typename U = T>
std::ostringstream& AddTextureExpectation(const char* file,
int line,
const T* expectedData,
const wgpu::Texture& texture,
wgpu::Origin3D origin,
wgpu::Extent3D extent,
wgpu::TextureFormat format,
T tolerance = 0,
uint32_t level = 0,
wgpu::TextureAspect aspect = wgpu::TextureAspect::All,
uint32_t bytesPerRow = 0) {
// No device passed explicitly. Default it, and forward the rest of the args.
return AddTextureExpectation<T, U>(file, line, this->device, expectedData, texture, origin,
extent, format, tolerance, level, aspect, bytesPerRow);
}
// T - expected value Type
// U - actual value Type (defaults = T)
template <typename T, typename U = T>
std::ostringstream& AddTextureExpectation(const char* file,
int line,
wgpu::Device targetDevice,
const T* expectedData,
const wgpu::Texture& texture,
wgpu::Origin3D origin,
@ -320,7 +338,7 @@ class DawnTestBase {
uint32_t texelComponentCount = utils::GetWGSLRenderableColorTextureComponentCount(format);
return AddTextureExpectationImpl(
file, line,
file, line, std::move(targetDevice),
new detail::ExpectEq<T, U>(
expectedData,
texelComponentCount * extent.width * extent.height * extent.depthOrArrayLayers,
@ -338,8 +356,24 @@ class DawnTestBase {
uint32_t level = 0,
wgpu::TextureAspect aspect = wgpu::TextureAspect::All,
uint32_t bytesPerRow = 0) {
// No device passed explicitly. Default it, and forward the rest of the args.
return AddTextureExpectation<T, U>(file, line, this->device, expectedData, texture, origin,
extent, level, aspect, bytesPerRow);
}
template <typename T, typename U = T>
std::ostringstream& AddTextureExpectation(const char* file,
int line,
wgpu::Device targetDevice,
const T* expectedData,
const wgpu::Texture& texture,
wgpu::Origin3D origin,
wgpu::Extent3D extent,
uint32_t level = 0,
wgpu::TextureAspect aspect = wgpu::TextureAspect::All,
uint32_t bytesPerRow = 0) {
return AddTextureExpectationImpl(
file, line,
file, line, std::move(targetDevice),
new detail::ExpectEq<T, U>(expectedData,
extent.width * extent.height * extent.depthOrArrayLayers),
texture, origin, extent, level, aspect, sizeof(U), bytesPerRow);
@ -354,9 +388,24 @@ class DawnTestBase {
uint32_t level = 0,
wgpu::TextureAspect aspect = wgpu::TextureAspect::All,
uint32_t bytesPerRow = 0) {
return AddTextureExpectationImpl(file, line, new detail::ExpectEq<T, U>(expectedData),
texture, origin, {1, 1}, level, aspect, sizeof(U),
bytesPerRow);
// No device passed explicitly. Default it, and forward the rest of the args.
return AddTextureExpectation<T, U>(file, line, this->device, expectedData, texture, origin,
level, aspect, bytesPerRow);
}
template <typename T, typename U = T>
std::ostringstream& AddTextureExpectation(const char* file,
int line,
wgpu::Device targetDevice,
const T& expectedData,
const wgpu::Texture& texture,
wgpu::Origin3D origin,
uint32_t level = 0,
wgpu::TextureAspect aspect = wgpu::TextureAspect::All,
uint32_t bytesPerRow = 0) {
return AddTextureExpectationImpl(file, line, std::move(targetDevice),
new detail::ExpectEq<T, U>(expectedData), texture, origin,
{1, 1}, level, aspect, sizeof(U), bytesPerRow);
}
template <typename E,
@ -371,8 +420,27 @@ class DawnTestBase {
uint32_t level = 0,
wgpu::TextureAspect aspect = wgpu::TextureAspect::All,
uint32_t bytesPerRow = 0) {
return AddTextureExpectationImpl(file, line, expectation, texture, origin, extent, level,
aspect, expectation->DataSize(), bytesPerRow);
// No device passed explicitly. Default it, and forward the rest of the args.
return AddTextureExpectation(file, line, this->device, expectation, texture, origin, extent,
level, aspect, bytesPerRow);
}
template <typename E,
typename = typename std::enable_if<
std::is_base_of<detail::CustomTextureExpectation, E>::value>::type>
std::ostringstream& AddTextureExpectation(const char* file,
int line,
wgpu::Device targetDevice,
E* expectation,
const wgpu::Texture& texture,
wgpu::Origin3D origin,
wgpu::Extent3D extent,
uint32_t level = 0,
wgpu::TextureAspect aspect = wgpu::TextureAspect::All,
uint32_t bytesPerRow = 0) {
return AddTextureExpectationImpl(file, line, std::move(targetDevice), expectation, texture,
origin, extent, level, aspect, expectation->DataSize(),
bytesPerRow);
}
template <typename T>
@ -387,9 +455,27 @@ class DawnTestBase {
uint32_t level = 0,
wgpu::TextureAspect aspect = wgpu::TextureAspect::All,
uint32_t bytesPerRow = 0) {
// No device passed explicitly. Default it, and forward the rest of the args.
return AddTextureBetweenColorsExpectation(file, line, this->device, color0, color1, texture,
x, y, level, aspect, bytesPerRow);
}
template <typename T>
std::ostringstream& AddTextureBetweenColorsExpectation(
const char* file,
int line,
const wgpu::Device& targetDevice,
const T& color0,
const T& color1,
const wgpu::Texture& texture,
uint32_t x,
uint32_t y,
uint32_t level = 0,
wgpu::TextureAspect aspect = wgpu::TextureAspect::All,
uint32_t bytesPerRow = 0) {
return AddTextureExpectationImpl(
file, line, new detail::ExpectBetweenColors<T>(color0, color1), texture, {x, y}, {1, 1},
level, aspect, sizeof(T), bytesPerRow);
file, line, std::move(targetDevice), new detail::ExpectBetweenColors<T>(color0, color1),
texture, {x, y}, {1, 1}, level, aspect, sizeof(T), bytesPerRow);
}
std::ostringstream& ExpectSampledFloatData(wgpu::Texture texture,
@ -452,7 +538,7 @@ class DawnTestBase {
mipLevel, {}, &expectedStencil);
}
void WaitABit();
void WaitABit(wgpu::Device = nullptr);
void FlushWire();
void WaitForAllOperations();
@ -489,6 +575,7 @@ class DawnTestBase {
std::ostringstream& AddTextureExpectationImpl(const char* file,
int line,
wgpu::Device targetDevice,
detail::Expectation* expectation,
const wgpu::Texture& texture,
wgpu::Origin3D origin,
@ -508,6 +595,7 @@ class DawnTestBase {
// MapRead buffers used to get data for the expectations
struct ReadbackSlot {
wgpu::Device device;
wgpu::Buffer buffer;
uint64_t bufferSize;
const void* mappedData = nullptr;
@ -521,11 +609,12 @@ class DawnTestBase {
// Reserve space where the data for an expectation can be copied
struct ReadbackReservation {
wgpu::Device device;
wgpu::Buffer buffer;
size_t slot;
uint64_t offset;
};
ReadbackReservation ReserveReadback(uint64_t readbackSize);
ReadbackReservation ReserveReadback(wgpu::Device targetDevice, uint64_t readbackSize);
struct DeferredExpectation {
const char* file;

View File

@ -117,71 +117,57 @@ class D3D12ResourceTestBase : public DawnTestWithParams<D3D12ResourceTestParams>
}
protected:
std::unique_ptr<dawn::native::d3d12::ExternalImageDXGI> CreateExternalImage(
WGPUDevice targetDevice,
ID3D11Texture2D* d3d11Texture,
const wgpu::TextureDescriptor* dawnDesc) const {
ComPtr<IDXGIResource1> dxgiResource;
EXPECT_EQ(d3d11Texture->QueryInterface(IID_PPV_ARGS(&dxgiResource)), S_OK);
HANDLE textureSharedHandle = nullptr;
EXPECT_EQ(dxgiResource->CreateSharedHandle(
nullptr, DXGI_SHARED_RESOURCE_READ | DXGI_SHARED_RESOURCE_WRITE, nullptr,
&textureSharedHandle),
S_OK);
dawn::native::d3d12::ExternalImageDescriptorDXGISharedHandle externalImageDesc;
externalImageDesc.cTextureDescriptor =
reinterpret_cast<const WGPUTextureDescriptor*>(dawnDesc);
externalImageDesc.sharedHandle = textureSharedHandle;
externalImageDesc.useFenceSynchronization = GetParam().mSyncMode == SyncMode::kFence;
std::unique_ptr<dawn::native::d3d12::ExternalImageDXGI> externalImage =
dawn::native::d3d12::ExternalImageDXGI::Create(targetDevice, &externalImageDesc);
// Now that we've created all of our resources, we can close the handle
// since we no longer need it.
::CloseHandle(textureSharedHandle);
return externalImage;
}
void WrapSharedHandle(
const wgpu::TextureDescriptor* dawnDesc,
const D3D11_TEXTURE2D_DESC* baseD3dDescriptor,
wgpu::Texture* dawnTexture,
ID3D11Texture2D** d3d11TextureOut,
std::unique_ptr<dawn::native::d3d12::ExternalImageDXGI>* externalImageOut = nullptr,
uint64_t fenceSignalValue = 1) const {
std::unique_ptr<dawn::native::d3d12::ExternalImageDXGI>* externalImageOut) const {
ComPtr<ID3D11Texture2D> d3d11Texture;
HRESULT hr = mD3d11Device->CreateTexture2D(baseD3dDescriptor, nullptr, &d3d11Texture);
ASSERT_EQ(hr, S_OK);
ComPtr<IDXGIResource1> dxgiResource;
hr = d3d11Texture.As(&dxgiResource);
ASSERT_EQ(hr, S_OK);
HANDLE textureSharedHandle;
hr = dxgiResource->CreateSharedHandle(
nullptr, DXGI_SHARED_RESOURCE_READ | DXGI_SHARED_RESOURCE_WRITE, nullptr,
&textureSharedHandle);
ASSERT_EQ(hr, S_OK);
HANDLE fenceSharedHandle = nullptr;
ComPtr<ID3D11Fence> d3d11Fence;
if (GetParam().mSyncMode == SyncMode::kFence) {
ComPtr<ID3D11Device5> 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<const WGPUTextureDescriptor*>(dawnDesc);
externalImageDesc.textureSharedHandle = textureSharedHandle;
externalImageDesc.fenceSharedHandle = fenceSharedHandle;
std::unique_ptr<dawn::native::d3d12::ExternalImageDXGI> 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(textureSharedHandle);
if (fenceSharedHandle != nullptr) {
::CloseHandle(fenceSharedHandle);
}
CreateExternalImage(device.Get(), d3d11Texture.Get(), dawnDesc);
// Cannot access a non-existent external image (ex. validation error).
if (externalImage == nullptr) {
return;
}
dawn::native::d3d12::ExternalImageAccessDescriptorDXGISharedHandle externalAccessDesc;
dawn::native::d3d12::ExternalImageDXGIBeginAccessDescriptor externalAccessDesc;
externalAccessDesc.usage = static_cast<WGPUTextureUsageFlags>(dawnDesc->usage);
if (d3d11Fence != nullptr) {
externalAccessDesc.fenceWaitValue = 0;
externalAccessDesc.fenceSignalValue = fenceSignalValue;
}
*dawnTexture = wgpu::Texture::Acquire(externalImage->ProduceTexture(&externalAccessDesc));
*dawnTexture = wgpu::Texture::Acquire(externalImage->BeginAccess(&externalAccessDesc));
*d3d11TextureOut = d3d11Texture.Detach();
if (externalImageOut != nullptr) {
@ -211,9 +197,15 @@ TEST_P(D3D12SharedHandleValidation, Success) {
wgpu::Texture texture;
ComPtr<ID3D11Texture2D> d3d11Texture;
WrapSharedHandle(&baseDawnDescriptor, &baseD3dDescriptor, &texture, &d3d11Texture);
std::unique_ptr<dawn::native::d3d12::ExternalImageDXGI> externalImage;
WrapSharedHandle(&baseDawnDescriptor, &baseD3dDescriptor, &texture, &d3d11Texture,
&externalImage);
ASSERT_NE(texture.Get(), nullptr);
dawn::native::d3d12::ExternalImageDXGIFenceDescriptor signalFence;
externalImage->EndAccess(texture.Get(), &signalFence);
texture.Destroy();
}
// Test a successful wrapping of an D3D12Resource with DawnTextureInternalUsageDescriptor
@ -227,9 +219,15 @@ TEST_P(D3D12SharedHandleValidation, SuccessWithInternalUsageDescriptor) {
wgpu::Texture texture;
ComPtr<ID3D11Texture2D> d3d11Texture;
WrapSharedHandle(&baseDawnDescriptor, &baseD3dDescriptor, &texture, &d3d11Texture);
std::unique_ptr<dawn::native::d3d12::ExternalImageDXGI> externalImage;
WrapSharedHandle(&baseDawnDescriptor, &baseD3dDescriptor, &texture, &d3d11Texture,
&externalImage);
ASSERT_NE(texture.Get(), nullptr);
dawn::native::d3d12::ExternalImageDXGIFenceDescriptor signalFence;
externalImage->EndAccess(texture.Get(), &signalFence);
texture.Destroy();
}
// Test an error occurs if an invalid sType is the nextInChain
@ -242,8 +240,9 @@ TEST_P(D3D12SharedHandleValidation, InvalidTextureDescriptor) {
wgpu::Texture texture;
ComPtr<ID3D11Texture2D> d3d11Texture;
ASSERT_DEVICE_ERROR(
WrapSharedHandle(&baseDawnDescriptor, &baseD3dDescriptor, &texture, &d3d11Texture));
std::unique_ptr<dawn::native::d3d12::ExternalImageDXGI> externalImage;
ASSERT_DEVICE_ERROR(WrapSharedHandle(&baseDawnDescriptor, &baseD3dDescriptor, &texture,
&d3d11Texture, &externalImage));
ASSERT_EQ(texture.Get(), nullptr);
}
@ -255,8 +254,9 @@ TEST_P(D3D12SharedHandleValidation, InvalidMipLevelCount) {
wgpu::Texture texture;
ComPtr<ID3D11Texture2D> d3d11Texture;
ASSERT_DEVICE_ERROR(
WrapSharedHandle(&baseDawnDescriptor, &baseD3dDescriptor, &texture, &d3d11Texture));
std::unique_ptr<dawn::native::d3d12::ExternalImageDXGI> externalImage;
ASSERT_DEVICE_ERROR(WrapSharedHandle(&baseDawnDescriptor, &baseD3dDescriptor, &texture,
&d3d11Texture, &externalImage));
ASSERT_EQ(texture.Get(), nullptr);
}
@ -268,8 +268,9 @@ TEST_P(D3D12SharedHandleValidation, InvalidDepth) {
wgpu::Texture texture;
ComPtr<ID3D11Texture2D> d3d11Texture;
ASSERT_DEVICE_ERROR(
WrapSharedHandle(&baseDawnDescriptor, &baseD3dDescriptor, &texture, &d3d11Texture));
std::unique_ptr<dawn::native::d3d12::ExternalImageDXGI> externalImage;
ASSERT_DEVICE_ERROR(WrapSharedHandle(&baseDawnDescriptor, &baseD3dDescriptor, &texture,
&d3d11Texture, &externalImage));
ASSERT_EQ(texture.Get(), nullptr);
}
@ -281,8 +282,9 @@ TEST_P(D3D12SharedHandleValidation, InvalidSampleCount) {
wgpu::Texture texture;
ComPtr<ID3D11Texture2D> d3d11Texture;
ASSERT_DEVICE_ERROR(
WrapSharedHandle(&baseDawnDescriptor, &baseD3dDescriptor, &texture, &d3d11Texture));
std::unique_ptr<dawn::native::d3d12::ExternalImageDXGI> externalImage;
ASSERT_DEVICE_ERROR(WrapSharedHandle(&baseDawnDescriptor, &baseD3dDescriptor, &texture,
&d3d11Texture, &externalImage));
ASSERT_EQ(texture.Get(), nullptr);
}
@ -294,8 +296,9 @@ TEST_P(D3D12SharedHandleValidation, InvalidWidth) {
wgpu::Texture texture;
ComPtr<ID3D11Texture2D> d3d11Texture;
ASSERT_DEVICE_ERROR(
WrapSharedHandle(&baseDawnDescriptor, &baseD3dDescriptor, &texture, &d3d11Texture));
std::unique_ptr<dawn::native::d3d12::ExternalImageDXGI> externalImage;
ASSERT_DEVICE_ERROR(WrapSharedHandle(&baseDawnDescriptor, &baseD3dDescriptor, &texture,
&d3d11Texture, &externalImage));
ASSERT_EQ(texture.Get(), nullptr);
}
@ -307,8 +310,9 @@ TEST_P(D3D12SharedHandleValidation, InvalidHeight) {
wgpu::Texture texture;
ComPtr<ID3D11Texture2D> d3d11Texture;
ASSERT_DEVICE_ERROR(
WrapSharedHandle(&baseDawnDescriptor, &baseD3dDescriptor, &texture, &d3d11Texture));
std::unique_ptr<dawn::native::d3d12::ExternalImageDXGI> externalImage;
ASSERT_DEVICE_ERROR(WrapSharedHandle(&baseDawnDescriptor, &baseD3dDescriptor, &texture,
&d3d11Texture, &externalImage));
ASSERT_EQ(texture.Get(), nullptr);
}
@ -320,8 +324,9 @@ TEST_P(D3D12SharedHandleValidation, InvalidFormat) {
wgpu::Texture texture;
ComPtr<ID3D11Texture2D> d3d11Texture;
ASSERT_DEVICE_ERROR(
WrapSharedHandle(&baseDawnDescriptor, &baseD3dDescriptor, &texture, &d3d11Texture));
std::unique_ptr<dawn::native::d3d12::ExternalImageDXGI> externalImage;
ASSERT_DEVICE_ERROR(WrapSharedHandle(&baseDawnDescriptor, &baseD3dDescriptor, &texture,
&d3d11Texture, &externalImage));
ASSERT_EQ(texture.Get(), nullptr);
}
@ -333,8 +338,9 @@ TEST_P(D3D12SharedHandleValidation, InvalidNumD3DMipLevels) {
wgpu::Texture texture;
ComPtr<ID3D11Texture2D> d3d11Texture;
ASSERT_DEVICE_ERROR(
WrapSharedHandle(&baseDawnDescriptor, &baseD3dDescriptor, &texture, &d3d11Texture));
std::unique_ptr<dawn::native::d3d12::ExternalImageDXGI> externalImage;
ASSERT_DEVICE_ERROR(WrapSharedHandle(&baseDawnDescriptor, &baseD3dDescriptor, &texture,
&d3d11Texture, &externalImage));
ASSERT_EQ(texture.Get(), nullptr);
}
@ -346,8 +352,9 @@ TEST_P(D3D12SharedHandleValidation, InvalidD3DArraySize) {
wgpu::Texture texture;
ComPtr<ID3D11Texture2D> d3d11Texture;
ASSERT_DEVICE_ERROR(
WrapSharedHandle(&baseDawnDescriptor, &baseD3dDescriptor, &texture, &d3d11Texture));
std::unique_ptr<dawn::native::d3d12::ExternalImageDXGI> externalImage;
ASSERT_DEVICE_ERROR(WrapSharedHandle(&baseDawnDescriptor, &baseD3dDescriptor, &texture,
&d3d11Texture, &externalImage));
ASSERT_EQ(texture.Get(), nullptr);
}
@ -387,15 +394,14 @@ class D3D12SharedHandleUsageTests : public D3D12ResourceTestBase {
queue.Submit(1, &commands);
}
void WrapAndClearD3D11Texture(const wgpu::TextureDescriptor& dawnDescriptor,
const D3D11_TEXTURE2D_DESC& d3dDescriptor,
const wgpu::Color& clearColor,
wgpu::Texture* dawnTextureOut,
ID3D11Texture2D** d3d11TextureOut,
bool isInitialized = true,
IDXGIKeyedMutex** dxgiKeyedMutexOut = nullptr,
ID3D11Fence** d3d11FenceOut = nullptr,
uint64_t* nextFenceWaitValue = nullptr) const {
void WrapAndClearD3D11Texture(
const wgpu::TextureDescriptor& dawnDescriptor,
const D3D11_TEXTURE2D_DESC& d3dDescriptor,
const wgpu::Color& clearColor,
wgpu::Texture* dawnTextureOut,
ID3D11Texture2D** d3d11TextureOut,
std::unique_ptr<dawn::native::d3d12::ExternalImageDXGI>* externalImageOut,
bool isInitialized = true) const {
ComPtr<ID3D11Texture2D> d3d11Texture;
HRESULT hr = mD3d11Device->CreateTexture2D(&d3dDescriptor, nullptr, &d3d11Texture);
ASSERT_EQ(hr, S_OK);
@ -404,10 +410,10 @@ class D3D12SharedHandleUsageTests : public D3D12ResourceTestBase {
hr = d3d11Texture.As(&dxgiResource);
ASSERT_EQ(hr, S_OK);
HANDLE textureSharedHandle;
HANDLE sharedHandle;
hr = dxgiResource->CreateSharedHandle(
nullptr, DXGI_SHARED_RESOURCE_READ | DXGI_SHARED_RESOURCE_WRITE, nullptr,
&textureSharedHandle);
&sharedHandle);
ASSERT_EQ(hr, S_OK);
ComPtr<IDXGIKeyedMutex> dxgiKeyedMutex;
@ -444,56 +450,47 @@ class D3D12SharedHandleUsageTests : public D3D12ResourceTestBase {
static_cast<float>(clearColor.b), static_cast<float>(clearColor.a)};
mD3d11DeviceContext->ClearRenderTargetView(d3d11RTV.Get(), colorRGBA);
dawn::native::d3d12::ExternalImageDescriptorDXGISharedHandle externalImageDesc = {};
externalImageDesc.textureSharedHandle = textureSharedHandle;
externalImageDesc.fenceSharedHandle = fenceSharedHandle;
externalImageDesc.cTextureDescriptor =
reinterpret_cast<const WGPUTextureDescriptor*>(&dawnDescriptor);
dawn::native::d3d12::ExternalImageAccessDescriptorDXGISharedHandle externalAccessDesc;
externalAccessDesc.isInitialized = isInitialized;
externalAccessDesc.usage = static_cast<WGPUTextureUsageFlags>(dawnDescriptor.usage);
constexpr uint64_t kFenceSignalValue = 1;
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;
d3d11DeviceContext4->Signal(d3d11Fence.Get(), kFenceSignalValue);
}
dawn::native::d3d12::ExternalImageDescriptorDXGISharedHandle externalImageDesc = {};
externalImageDesc.sharedHandle = sharedHandle;
externalImageDesc.cTextureDescriptor =
reinterpret_cast<const WGPUTextureDescriptor*>(&dawnDescriptor);
externalImageDesc.useFenceSynchronization = GetParam().mSyncMode == SyncMode::kFence;
std::unique_ptr<dawn::native::d3d12::ExternalImageDXGI> externalImage =
dawn::native::d3d12::ExternalImageDXGI::Create(device.Get(), &externalImageDesc);
*dawnTextureOut =
wgpu::Texture::Acquire(externalImage->ProduceTexture(&externalAccessDesc));
dawn::native::d3d12::ExternalImageDXGIBeginAccessDescriptor externalAccessDesc;
externalAccessDesc.isInitialized = isInitialized;
externalAccessDesc.usage = static_cast<WGPUTextureUsageFlags>(dawnDescriptor.usage);
if (fenceSharedHandle != nullptr) {
externalAccessDesc.waitFences.push_back({fenceSharedHandle, kFenceSignalValue});
}
*dawnTextureOut = wgpu::Texture::Acquire(externalImage->BeginAccess(&externalAccessDesc));
*d3d11TextureOut = d3d11Texture.Detach();
*externalImageOut = std::move(externalImage);
if (dxgiKeyedMutexOut != nullptr) {
*dxgiKeyedMutexOut = dxgiKeyedMutex.Detach();
}
if (d3d11FenceOut != nullptr) {
*d3d11FenceOut = d3d11Fence.Detach();
}
if (nextFenceWaitValue != nullptr) {
*nextFenceWaitValue = externalAccessDesc.fenceSignalValue;
if (fenceSharedHandle != nullptr) {
::CloseHandle(fenceSharedHandle);
}
}
void ExpectPixelRGBA8EQ(ID3D11Texture2D* d3d11Texture,
IDXGIKeyedMutex* dxgiKeyedMutex,
ID3D11Fence* d3d11Fence,
uint64_t fenceWaitValue,
const wgpu::Color& color) {
void ExpectPixelRGBA8EQ(
ID3D11Texture2D* d3d11Texture,
const wgpu::Color& color,
const dawn::native::d3d12::ExternalImageDXGIFenceDescriptor* waitFence = nullptr) {
D3D11_TEXTURE2D_DESC texture2DDesc;
d3d11Texture->GetDesc(&texture2DDesc);
@ -520,16 +517,28 @@ class D3D12SharedHandleUsageTests : public D3D12ResourceTestBase {
d3dRc.bottom = texture2DDesc.Height;
d3dRc.right = texture2DDesc.Width;
ComPtr<IDXGIKeyedMutex> dxgiKeyedMutex;
d3d11Texture->QueryInterface(IID_PPV_ARGS(&dxgiKeyedMutex));
if (dxgiKeyedMutex != nullptr) {
hr = dxgiKeyedMutex->AcquireSync(kDXGIKeyedMutexAcquireReleaseKey, INFINITE);
ASSERT_EQ(hr, S_OK);
}
if (d3d11Fence != nullptr) {
if (waitFence != nullptr && waitFence->fenceHandle != nullptr) {
ComPtr<ID3D11Device5> d3d11Device5;
mD3d11Device.As(&d3d11Device5);
ASSERT_EQ(hr, S_OK);
ComPtr<ID3D11Fence> d3d11Fence;
hr = d3d11Device5->OpenSharedFence(waitFence->fenceHandle, IID_PPV_ARGS(&d3d11Fence));
ASSERT_EQ(hr, S_OK);
ComPtr<ID3D11DeviceContext4> d3d11DeviceContext4;
hr = mD3d11DeviceContext.As(&d3d11DeviceContext4);
ASSERT_EQ(hr, S_OK);
hr = d3d11DeviceContext4->Wait(d3d11Fence, fenceWaitValue);
hr = d3d11DeviceContext4->Wait(d3d11Fence.Get(), waitFence->fenceValue);
ASSERT_EQ(hr, S_OK);
}
@ -571,14 +580,19 @@ TEST_P(D3D12SharedHandleUsageTests, ClearInD3D11CopyAndReadbackInD3D12) {
const wgpu::Color clearColor{1.0f, 1.0f, 0.0f, 1.0f};
wgpu::Texture dawnSrcTexture;
ComPtr<ID3D11Texture2D> d3d11Texture;
std::unique_ptr<dawn::native::d3d12::ExternalImageDXGI> externalImage;
WrapAndClearD3D11Texture(baseDawnDescriptor, baseD3dDescriptor, clearColor, &dawnSrcTexture,
&d3d11Texture);
&d3d11Texture, &externalImage);
ASSERT_NE(dawnSrcTexture.Get(), nullptr);
// Create a texture on the device and copy the source texture to it.
wgpu::Texture dawnCopyDestTexture = device.CreateTexture(&baseDawnDescriptor);
SimpleCopyTextureToTexture(dawnSrcTexture, dawnCopyDestTexture);
dawn::native::d3d12::ExternalImageDXGIFenceDescriptor signalFence;
externalImage->EndAccess(dawnSrcTexture.Get(), &signalFence);
dawnSrcTexture.Destroy();
// Readback the destination texture and ensure it contains the colors we used
// to clear the source texture on the D3D device.
EXPECT_PIXEL_RGBA8_EQ(utils::RGBA8(clearColor.r * 255u, clearColor.g * 255u,
@ -594,8 +608,9 @@ TEST_P(D3D12SharedHandleUsageTests, ClearInD3D11ReadbackInD3D12) {
const wgpu::Color clearColor{1.0f, 1.0f, 0.0f, 1.0f};
wgpu::Texture dawnTexture;
ComPtr<ID3D11Texture2D> d3d11Texture;
std::unique_ptr<dawn::native::d3d12::ExternalImageDXGI> externalImage;
WrapAndClearD3D11Texture(baseDawnDescriptor, baseD3dDescriptor, clearColor, &dawnTexture,
&d3d11Texture);
&d3d11Texture, &externalImage);
ASSERT_NE(dawnTexture.Get(), nullptr);
// Readback the destination texture and ensure it contains the colors we used
@ -603,6 +618,10 @@ TEST_P(D3D12SharedHandleUsageTests, ClearInD3D11ReadbackInD3D12) {
EXPECT_PIXEL_RGBA8_EQ(utils::RGBA8(clearColor.r * 255, clearColor.g * 255, clearColor.b * 255,
clearColor.a * 255),
dawnTexture, 0, 0);
dawn::native::d3d12::ExternalImageDXGIFenceDescriptor signalFence;
externalImage->EndAccess(dawnTexture.Get(), &signalFence);
dawnTexture.Destroy();
}
// 1. Create and clear a D3D11 texture
@ -618,24 +637,23 @@ TEST_P(D3D12SharedHandleUsageTests, ClearInD3D12ReadbackInD3D11) {
const wgpu::Color d3d11ClearColor{1.0f, 1.0f, 0.0f, 1.0f};
wgpu::Texture dawnTexture;
ComPtr<ID3D11Texture2D> d3d11Texture;
std::unique_ptr<dawn::native::d3d12::ExternalImageDXGI> externalImage;
ComPtr<IDXGIKeyedMutex> dxgiKeyedMutex;
ComPtr<ID3D11Fence> d3d11Fence;
uint64_t nextFenceWaitValue;
WrapAndClearD3D11Texture(baseDawnDescriptor, baseD3dDescriptor, d3d11ClearColor, &dawnTexture,
&d3d11Texture, /*isInitialized=*/true, &dxgiKeyedMutex, &d3d11Fence,
&nextFenceWaitValue);
&d3d11Texture, &externalImage, /*isInitialized=*/true);
ASSERT_NE(dawnTexture.Get(), nullptr);
const wgpu::Color d3d12ClearColor{0.0f, 0.0f, 1.0f, 1.0f};
ClearImage(dawnTexture, d3d12ClearColor, device);
dawn::native::d3d12::ExternalImageDXGIFenceDescriptor signalFence;
externalImage->EndAccess(dawnTexture.Get(), &signalFence);
dawnTexture.Destroy();
// 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(), d3d11Fence.Get(),
nextFenceWaitValue, d3d12ClearColor);
ExpectPixelRGBA8EQ(d3d11Texture.Get(), d3d12ClearColor, &signalFence);
}
// 1. Create and clear a D3D11 texture
@ -652,12 +670,9 @@ TEST_P(D3D12SharedHandleUsageTests, ClearTwiceInD3D12ReadbackInD3D11) {
const wgpu::Color d3d11ClearColor{1.0f, 1.0f, 0.0f, 1.0f};
wgpu::Texture dawnTexture;
ComPtr<ID3D11Texture2D> d3d11Texture;
ComPtr<IDXGIKeyedMutex> dxgiKeyedMutex;
ComPtr<ID3D11Fence> d3d11Fence;
uint64_t nextFenceWaitValue;
std::unique_ptr<dawn::native::d3d12::ExternalImageDXGI> externalImage;
WrapAndClearD3D11Texture(baseDawnDescriptor, baseD3dDescriptor, d3d11ClearColor, &dawnTexture,
&d3d11Texture, /*isInitialized=*/true, &dxgiKeyedMutex, &d3d11Fence,
&nextFenceWaitValue);
&d3d11Texture, &externalImage, /*isInitialized=*/true);
ASSERT_NE(dawnTexture.Get(), nullptr);
const wgpu::Color d3d12ClearColor1{0.0f, 0.0f, 1.0f, 1.0f};
@ -666,13 +681,14 @@ TEST_P(D3D12SharedHandleUsageTests, ClearTwiceInD3D12ReadbackInD3D11) {
const wgpu::Color d3d12ClearColor2{0.0f, 1.0f, 1.0f, 1.0f};
ClearImage(dawnTexture, d3d12ClearColor2, device);
dawn::native::d3d12::ExternalImageDXGIFenceDescriptor signalFence;
externalImage->EndAccess(dawnTexture.Get(), &signalFence);
dawnTexture.Destroy();
// 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(), d3d11Fence.Get(),
nextFenceWaitValue, d3d12ClearColor2);
ExpectPixelRGBA8EQ(d3d11Texture.Get(), d3d12ClearColor2, &signalFence);
}
// 1. Create and clear a D3D11 texture with clearColor
@ -684,13 +700,18 @@ TEST_P(D3D12SharedHandleUsageTests, UninitializedTextureIsCleared) {
const wgpu::Color clearColor{1.0f, 0.0f, 0.0f, 1.0f};
wgpu::Texture dawnTexture;
ComPtr<ID3D11Texture2D> d3d11Texture;
std::unique_ptr<dawn::native::d3d12::ExternalImageDXGI> externalImage;
WrapAndClearD3D11Texture(baseDawnDescriptor, baseD3dDescriptor, clearColor, &dawnTexture,
&d3d11Texture, /*isInitialized=*/false);
&d3d11Texture, &externalImage, /*isInitialized=*/false);
ASSERT_NE(dawnTexture.Get(), nullptr);
// Readback the destination texture and ensure it contains the colors we used
// to clear the source texture on the D3D device.
EXPECT_PIXEL_RGBA8_EQ(utils::RGBA8(0, 0, 0, 0), dawnTexture, 0, 0);
dawn::native::d3d12::ExternalImageDXGIFenceDescriptor signalFence;
externalImage->EndAccess(dawnTexture.Get(), &signalFence);
dawnTexture.Destroy();
}
// 1. Create an external image from the DX11 texture.
@ -704,7 +725,7 @@ TEST_P(D3D12SharedHandleUsageTests, ReuseExternalImage) {
ComPtr<ID3D11Texture2D> d3d11Texture;
std::unique_ptr<dawn::native::d3d12::ExternalImageDXGI> externalImage;
WrapSharedHandle(&baseDawnDescriptor, &baseD3dDescriptor, &texture, &d3d11Texture,
&externalImage, /*fenceSignalValue=*/1);
&externalImage);
{
const wgpu::Color solidRed{1.0f, 0.0f, 0.0f, 1.0f};
ASSERT_NE(texture.Get(), nullptr);
@ -715,16 +736,17 @@ TEST_P(D3D12SharedHandleUsageTests, ReuseExternalImage) {
// Once finished with the first texture, destroy it so we may re-acquire the external image
// again.
dawn::native::d3d12::ExternalImageDXGIFenceDescriptor signalFence;
externalImage->EndAccess(texture.Get(), &signalFence);
texture.Destroy();
// Create another Dawn texture then clear it with another color.
dawn::native::d3d12::ExternalImageAccessDescriptorDXGISharedHandle externalAccessDesc;
dawn::native::d3d12::ExternalImageDXGIBeginAccessDescriptor externalAccessDesc;
externalAccessDesc.isInitialized = true;
externalAccessDesc.usage = static_cast<WGPUTextureUsageFlags>(baseDawnDescriptor.usage);
externalAccessDesc.fenceWaitValue = 1;
externalAccessDesc.fenceSignalValue = 2;
externalAccessDesc.waitFences.push_back(signalFence);
texture = wgpu::Texture::Acquire(externalImage->ProduceTexture(&externalAccessDesc));
texture = wgpu::Texture::Acquire(externalImage->BeginAccess(&externalAccessDesc));
// Check again that the new texture is still red
EXPECT_PIXEL_RGBA8_EQ(utils::RGBA8(0xFF, 0, 0, 0xFF), texture.Get(), 0, 0);
@ -738,47 +760,161 @@ TEST_P(D3D12SharedHandleUsageTests, ReuseExternalImage) {
EXPECT_PIXEL_RGBA8_EQ(utils::RGBA8(0, 0, 0xFF, 0xFF), texture.Get(), 0, 0);
}
externalImage->EndAccess(texture.Get(), &signalFence);
texture.Destroy();
}
TEST_P(D3D12SharedHandleUsageTests, ConcurrentExternalImageReadAccess) {
DAWN_TEST_UNSUPPORTED_IF(UsesWire());
DAWN_TEST_UNSUPPORTED_IF(GetParam().mSyncMode == SyncMode::kKeyedMutex);
wgpu::Device device2 = CreateDevice();
EXPECT_NE(device2, nullptr);
wgpu::Device device3 = CreateDevice();
EXPECT_NE(device3, nullptr);
wgpu::Device device4 = CreateDevice();
EXPECT_NE(device4, nullptr);
wgpu::Device device5 = CreateDevice();
EXPECT_NE(device5, nullptr);
// Create Dawn texture with write access, then clear it to red.
wgpu::Texture texture;
ComPtr<ID3D11Texture2D> d3d11Texture;
std::unique_ptr<dawn::native::d3d12::ExternalImageDXGI> externalImage;
WrapSharedHandle(&baseDawnDescriptor, &baseD3dDescriptor, &texture, &d3d11Texture,
&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);
&externalImage);
EXPECT_PIXEL_RGBA8_EQ(utils::RGBA8(0xFF, 0, 0, 0xFF), texture.Get(), 0, 0);
}
// Clear to red.
const wgpu::Color solidRed{1.0f, 0.0f, 0.0f, 1.0f};
ASSERT_NE(texture.Get(), nullptr);
ClearImage(texture.Get(), solidRed, device);
dawn::native::d3d12::ExternalImageDXGIFenceDescriptor signalFence;
externalImage->EndAccess(texture.Get(), &signalFence);
texture.Destroy();
// Create two Dawn textures for concurrent read.
dawn::native::d3d12::ExternalImageAccessDescriptorDXGISharedHandle externalAccessDesc;
std::unique_ptr<dawn::native::d3d12::ExternalImageDXGI> externalImage2 =
CreateExternalImage(device2.Get(), d3d11Texture.Get(), &baseDawnDescriptor);
EXPECT_NE(externalImage2, nullptr);
std::unique_ptr<dawn::native::d3d12::ExternalImageDXGI> externalImage3 =
CreateExternalImage(device3.Get(), d3d11Texture.Get(), &baseDawnDescriptor);
EXPECT_NE(externalImage3, nullptr);
std::unique_ptr<dawn::native::d3d12::ExternalImageDXGI> externalImage4 =
CreateExternalImage(device4.Get(), d3d11Texture.Get(), &baseDawnDescriptor);
EXPECT_NE(externalImage4, nullptr);
std::unique_ptr<dawn::native::d3d12::ExternalImageDXGI> externalImage5 =
CreateExternalImage(device5.Get(), d3d11Texture.Get(), &baseDawnDescriptor);
EXPECT_NE(externalImage5, nullptr);
// Create two Dawn textures for concurrent read on second device.
dawn::native::d3d12::ExternalImageDXGIBeginAccessDescriptor externalAccessDesc;
externalAccessDesc.isInitialized = true;
externalAccessDesc.usage = WGPUTextureUsage_CopySrc;
externalAccessDesc.fenceWaitValue = 1;
externalAccessDesc.fenceSignalValue = 2;
externalAccessDesc.waitFences = {signalFence};
wgpu::Texture texture1 =
wgpu::Texture::Acquire(externalImage->ProduceTexture(&externalAccessDesc));
// Concurrent read access on device 2 and 3.
dawn::native::d3d12::ExternalImageDXGIFenceDescriptor signalFence2;
dawn::native::d3d12::ExternalImageDXGIFenceDescriptor signalFence3;
{
wgpu::Texture texture2 =
wgpu::Texture::Acquire(externalImage2->BeginAccess(&externalAccessDesc));
EXPECT_NE(texture2, nullptr);
wgpu::Texture texture2 =
wgpu::Texture::Acquire(externalImage->ProduceTexture(&externalAccessDesc));
wgpu::Texture texture3 =
wgpu::Texture::Acquire(externalImage3->BeginAccess(&externalAccessDesc));
EXPECT_NE(texture3, nullptr);
// Check again that the new textures are also red.
EXPECT_PIXEL_RGBA8_EQ(utils::RGBA8(0xFF, 0, 0, 0xFF), texture1.Get(), 0, 0);
EXPECT_PIXEL_RGBA8_EQ(utils::RGBA8(0xFF, 0, 0, 0xFF), texture2.Get(), 0, 0);
// Check again that the new textures are also red.
const utils::RGBA8 solidRed(0xFF, 0, 0, 0xFF);
EXPECT_TEXTURE_EQ(device2, solidRed, texture2.Get(), {0, 0});
EXPECT_TEXTURE_EQ(device3, solidRed, texture3.Get(), {0, 0});
texture1.Destroy();
texture2.Destroy();
externalImage2->EndAccess(texture2.Get(), &signalFence2);
texture2.Destroy();
externalImage3->EndAccess(texture3.Get(), &signalFence3);
texture3.Destroy();
}
externalAccessDesc.usage = static_cast<WGPUTextureUsageFlags>(baseDawnDescriptor.usage);
externalAccessDesc.waitFences = {signalFence2, signalFence3};
// Exclusive read-write access on device 4.
dawn::native::d3d12::ExternalImageDXGIFenceDescriptor signalFence4;
{
wgpu::Texture texture4 =
wgpu::Texture::Acquire(externalImage4->BeginAccess(&externalAccessDesc));
EXPECT_NE(texture4, nullptr);
const utils::RGBA8 solidRed(0xFF, 0, 0, 0xFF);
EXPECT_TEXTURE_EQ(device4, solidRed, texture4.Get(), {0, 0});
// Clear to blue.
const wgpu::Color solidBlue{0.0f, 0.0f, 1.0f, 1.0f};
ASSERT_NE(texture4.Get(), nullptr);
ClearImage(texture4.Get(), solidBlue, device4);
externalImage4->EndAccess(texture4.Get(), &signalFence4);
texture4.Destroy();
}
externalAccessDesc.waitFences = {signalFence4};
// Import texture on device 5, but do nothing with it.
dawn::native::d3d12::ExternalImageDXGIFenceDescriptor signalFence5;
{
wgpu::Texture texture5 =
wgpu::Texture::Acquire(externalImage5->BeginAccess(&externalAccessDesc));
EXPECT_NE(texture5, nullptr);
externalImage5->EndAccess(texture5.Get(), &signalFence5);
texture5.Destroy();
}
externalAccessDesc.usage = WGPUTextureUsage_CopySrc;
externalAccessDesc.waitFences = {signalFence5};
// Concurrent read access on device 1 (twice), 2 and 3.
{
texture = wgpu::Texture::Acquire(externalImage->BeginAccess(&externalAccessDesc));
EXPECT_NE(texture, nullptr);
wgpu::Texture texture1 =
wgpu::Texture::Acquire(externalImage->BeginAccess(&externalAccessDesc));
EXPECT_NE(texture1, nullptr);
wgpu::Texture texture2 =
wgpu::Texture::Acquire(externalImage2->BeginAccess(&externalAccessDesc));
EXPECT_NE(texture2, nullptr);
wgpu::Texture texture3 =
wgpu::Texture::Acquire(externalImage3->BeginAccess(&externalAccessDesc));
EXPECT_NE(texture3, nullptr);
// Check again that the new textures are now blue.
const utils::RGBA8 solidBlue(0, 0, 0xFF, 0xFF);
EXPECT_TEXTURE_EQ(device, solidBlue, texture.Get(), {0, 0});
EXPECT_TEXTURE_EQ(device, solidBlue, texture1.Get(), {0, 0});
EXPECT_TEXTURE_EQ(device2, solidBlue, texture2.Get(), {0, 0});
EXPECT_TEXTURE_EQ(device3, solidBlue, texture3.Get(), {0, 0});
externalImage->EndAccess(texture.Get(), &signalFence);
texture.Destroy();
externalImage->EndAccess(texture1.Get(), &signalFence);
texture1.Destroy();
externalImage2->EndAccess(texture2.Get(), &signalFence2);
texture2.Destroy();
externalImage3->EndAccess(texture3.Get(), &signalFence3);
texture3.Destroy();
}
}
// Produce a new texture with a usage not specified in the external image.
@ -789,20 +925,25 @@ TEST_P(D3D12SharedHandleUsageTests, ExternalImageUsage) {
ComPtr<ID3D11Texture2D> d3d11Texture;
std::unique_ptr<dawn::native::d3d12::ExternalImageDXGI> externalImage;
WrapSharedHandle(&baseDawnDescriptor, &baseD3dDescriptor, &texture, &d3d11Texture,
&externalImage, /*fenceSignalValue=*/1);
&externalImage);
ASSERT_NE(texture.Get(), nullptr);
dawn::native::d3d12::ExternalImageAccessDescriptorDXGISharedHandle externalAccessDesc;
dawn::native::d3d12::ExternalImageDXGIFenceDescriptor signalFence;
externalImage->EndAccess(texture.Get(), &signalFence);
texture.Destroy();
dawn::native::d3d12::ExternalImageDXGIBeginAccessDescriptor externalAccessDesc;
externalAccessDesc.isInitialized = true;
externalAccessDesc.usage = WGPUTextureUsage_StorageBinding;
externalAccessDesc.fenceWaitValue = 1;
externalAccessDesc.fenceSignalValue = 0; // No need to signal
texture = wgpu::Texture::Acquire(externalImage->ProduceTexture(&externalAccessDesc));
externalAccessDesc.waitFences.push_back(signalFence);
texture = wgpu::Texture::Acquire(externalImage->BeginAccess(&externalAccessDesc));
ASSERT_EQ(texture.Get(), nullptr);
externalAccessDesc.usage = WGPUTextureUsage_TextureBinding;
texture = wgpu::Texture::Acquire(externalImage->ProduceTexture(&externalAccessDesc));
texture = wgpu::Texture::Acquire(externalImage->BeginAccess(&externalAccessDesc));
ASSERT_NE(texture.Get(), nullptr);
externalImage->EndAccess(texture.Get(), &signalFence);
texture.Destroy();
}
// Verify external image cannot be used after its creating device is destroyed.
@ -815,21 +956,23 @@ TEST_P(D3D12SharedHandleUsageTests, InvalidateExternalImageOnDestroyDevice) {
// Create the Dawn texture then clear it to red.
WrapSharedHandle(&baseDawnDescriptor, &baseD3dDescriptor, &texture, &d3d11Texture,
&externalImage, /*fenceSignalValue=*/1);
&externalImage);
const wgpu::Color solidRed{1.0f, 0.0f, 0.0f, 1.0f};
ASSERT_NE(texture.Get(), nullptr);
ClearImage(texture.Get(), solidRed, device);
dawn::native::d3d12::ExternalImageDXGIFenceDescriptor signalFence;
externalImage->EndAccess(texture.Get(), &signalFence);
texture.Destroy();
// Do not readback pixels since that requires device to be alive during DawnTest::TearDown().
DestroyDevice();
dawn::native::d3d12::ExternalImageAccessDescriptorDXGISharedHandle externalAccessDesc;
dawn::native::d3d12::ExternalImageDXGIBeginAccessDescriptor externalAccessDesc;
externalAccessDesc.isInitialized = true;
externalAccessDesc.usage = static_cast<WGPUTextureUsageFlags>(baseDawnDescriptor.usage);
externalAccessDesc.fenceWaitValue = 1;
externalAccessDesc.fenceSignalValue = 2;
EXPECT_EQ(wgpu::Texture::Acquire(externalImage->ProduceTexture(&externalAccessDesc)), nullptr);
EXPECT_EQ(wgpu::Texture::Acquire(externalImage->BeginAccess(&externalAccessDesc)), nullptr);
}
// Verify external image cannot be created after the target device is destroyed.
@ -843,7 +986,7 @@ TEST_P(D3D12SharedHandleUsageTests, DisallowExternalImageAfterDestroyDevice) {
DestroyDevice();
ASSERT_DEVICE_ERROR(WrapSharedHandle(&baseDawnDescriptor, &baseD3dDescriptor, &texture,
&d3d11Texture, &externalImage, /*fenceSignalValue=*/1));
&d3d11Texture, &externalImage));
EXPECT_EQ(externalImage, nullptr);
EXPECT_EQ(texture, nullptr);
@ -858,7 +1001,7 @@ TEST_P(D3D12SharedHandleUsageTests, CallWriteBufferBeforeDestroyingExternalImage
ComPtr<ID3D11Texture2D> d3d11Texture;
std::unique_ptr<dawn::native::d3d12::ExternalImageDXGI> externalImage;
WrapSharedHandle(&baseDawnDescriptor, &baseD3dDescriptor, &texture, &d3d11Texture,
&externalImage, /*fenceSignalValue=*/1);
&externalImage);
// In utils::CreateBufferFromData() we will call queue.WriteBuffer(), which will make a
// recording context pending.
@ -866,6 +1009,9 @@ TEST_P(D3D12SharedHandleUsageTests, CallWriteBufferBeforeDestroyingExternalImage
wgpu::Buffer buffer = utils::CreateBufferFromData(
device, wgpu::BufferUsage::CopySrc | wgpu::BufferUsage::CopyDst, {kExpected});
dawn::native::d3d12::ExternalImageDXGIFenceDescriptor signalFence;
externalImage->EndAccess(texture.Get(), &signalFence);
texture.Destroy();
externalImage = nullptr;
EXPECT_BUFFER_U32_EQ(kExpected, buffer, 0);