diff --git a/BUILD.gn b/BUILD.gn index 42afea23f5..26db039be1 100644 --- a/BUILD.gn +++ b/BUILD.gn @@ -810,6 +810,14 @@ source_set("dawn_end2end_tests_sources") { libs += [ "IOSurface.framework" ] } + if (dawn_enable_d3d12) { + sources += [ "src/tests/end2end/D3D12ResourceWrappingTests.cpp" ] + libs += [ + "d3d11.lib", + "dxgi.lib", + ] + } + if (dawn_enable_opengl) { deps += [ "third_party:glfw" ] } diff --git a/src/dawn_native/d3d12/D3D12Backend.cpp b/src/dawn_native/d3d12/D3D12Backend.cpp index 47378d0c86..5d96593d81 100644 --- a/src/dawn_native/d3d12/D3D12Backend.cpp +++ b/src/dawn_native/d3d12/D3D12Backend.cpp @@ -23,6 +23,12 @@ namespace dawn_native { namespace d3d12 { + ComPtr GetD3D12Device(DawnDevice device) { + Device* backendDevice = reinterpret_cast(device); + + return backendDevice->GetD3D12Device(); + } + DawnSwapChainImplementation CreateNativeSwapChainImpl(DawnDevice device, HWND window) { Device* backendDevice = reinterpret_cast(device); @@ -39,4 +45,13 @@ namespace dawn_native { namespace d3d12 { return static_cast(impl->GetPreferredFormat()); } + DawnTexture WrapSharedHandle(DawnDevice device, + const DawnTextureDescriptor* descriptor, + HANDLE sharedHandle) { + Device* backendDevice = reinterpret_cast(device); + const TextureDescriptor* backendDescriptor = + reinterpret_cast(descriptor); + TextureBase* texture = backendDevice->WrapSharedHandle(backendDescriptor, sharedHandle); + return reinterpret_cast(texture); + } }} // namespace dawn_native::d3d12 diff --git a/src/dawn_native/d3d12/DeviceD3D12.cpp b/src/dawn_native/d3d12/DeviceD3D12.cpp index a91ce6cdfd..e925a771b7 100644 --- a/src/dawn_native/d3d12/DeviceD3D12.cpp +++ b/src/dawn_native/d3d12/DeviceD3D12.cpp @@ -380,4 +380,28 @@ namespace dawn_native { namespace d3d12 { return allocation; } + + TextureBase* Device::WrapSharedHandle(const TextureDescriptor* descriptor, + HANDLE sharedHandle) { + if (ConsumedError(ValidateTextureDescriptor(this, descriptor))) { + return nullptr; + } + + if (ConsumedError(ValidateTextureDescriptorCanBeWrapped(descriptor))) { + return nullptr; + } + + ComPtr d3d12Resource; + const HRESULT hr = + mD3d12Device->OpenSharedHandle(sharedHandle, IID_PPV_ARGS(&d3d12Resource)); + if (FAILED(hr)) { + return nullptr; + } + + if (ConsumedError(ValidateD3D12TextureCanBeWrapped(d3d12Resource.Get(), descriptor))) { + return nullptr; + } + + return new Texture(this, descriptor, d3d12Resource.Get()); + } }} // namespace dawn_native::d3d12 diff --git a/src/dawn_native/d3d12/DeviceD3D12.h b/src/dawn_native/d3d12/DeviceD3D12.h index 847fe35048..237c8b4b4e 100644 --- a/src/dawn_native/d3d12/DeviceD3D12.h +++ b/src/dawn_native/d3d12/DeviceD3D12.h @@ -90,6 +90,8 @@ namespace dawn_native { namespace d3d12 { void DeallocateMemory(ResourceMemoryAllocation& allocation); + TextureBase* WrapSharedHandle(const TextureDescriptor* descriptor, HANDLE sharedHandle); + private: ResultOrError CreateBindGroupImpl( const BindGroupDescriptor* descriptor) override; diff --git a/src/dawn_native/d3d12/TextureD3D12.cpp b/src/dawn_native/d3d12/TextureD3D12.cpp index 6f90ab343a..8903c1c935 100644 --- a/src/dawn_native/d3d12/TextureD3D12.cpp +++ b/src/dawn_native/d3d12/TextureD3D12.cpp @@ -100,7 +100,6 @@ namespace dawn_native { namespace d3d12 { UNREACHABLE(); } } - } // namespace DXGI_FORMAT D3D12TextureFormat(dawn::TextureFormat format) { @@ -221,6 +220,56 @@ namespace dawn_native { namespace d3d12 { } } + MaybeError ValidateTextureDescriptorCanBeWrapped(const TextureDescriptor* descriptor) { + if (descriptor->dimension != dawn::TextureDimension::e2D) { + return DAWN_VALIDATION_ERROR("Texture must be 2D"); + } + + if (descriptor->mipLevelCount != 1) { + return DAWN_VALIDATION_ERROR("Mip level count must be 1"); + } + + if (descriptor->arrayLayerCount != 1) { + return DAWN_VALIDATION_ERROR("Array layer count must be 1"); + } + + if (descriptor->sampleCount != 1) { + return DAWN_VALIDATION_ERROR("Sample count must be 1"); + } + + return {}; + } + + MaybeError ValidateD3D12TextureCanBeWrapped(ID3D12Resource* d3d12Resource, + const TextureDescriptor* dawnDescriptor) { + const D3D12_RESOURCE_DESC d3dDescriptor = d3d12Resource->GetDesc(); + if ((dawnDescriptor->size.width != d3dDescriptor.Width) || + (dawnDescriptor->size.height != d3dDescriptor.Height) || + (dawnDescriptor->size.depth != 1)) { + return DAWN_VALIDATION_ERROR("D3D12 texture size doesn't match descriptor"); + } + + const DXGI_FORMAT dxgiFormatFromDescriptor = D3D12TextureFormat(dawnDescriptor->format); + if (dxgiFormatFromDescriptor != d3dDescriptor.Format) { + return DAWN_VALIDATION_ERROR( + "D3D12 texture format must be compatible with descriptor format."); + } + + if (d3dDescriptor.MipLevels != 1) { + return DAWN_VALIDATION_ERROR("D3D12 texture number of miplevels must be 1."); + } + + if (d3dDescriptor.DepthOrArraySize != 1) { + return DAWN_VALIDATION_ERROR("D3D12 texture array size must be 1."); + } + + // Shared textures cannot be multi-sample so no need to check those. + ASSERT(d3dDescriptor.SampleDesc.Count == 1); + ASSERT(d3dDescriptor.SampleDesc.Quality == 0); + + return {}; + } + Texture::Texture(Device* device, const TextureDescriptor* descriptor) : TextureBase(device, descriptor, TextureState::OwnedInternal) { D3D12_RESOURCE_DESC resourceDescriptor; @@ -258,6 +307,8 @@ namespace dawn_native { namespace d3d12 { const TextureDescriptor* descriptor, ID3D12Resource* nativeTexture) : TextureBase(device, descriptor, TextureState::OwnedExternal), mResource(nativeTexture) { + SetIsSubresourceContentInitialized(0, descriptor->mipLevelCount, 0, + descriptor->arrayLayerCount); } Texture::~Texture() { diff --git a/src/dawn_native/d3d12/TextureD3D12.h b/src/dawn_native/d3d12/TextureD3D12.h index de8dcb20a5..af32dd3cf8 100644 --- a/src/dawn_native/d3d12/TextureD3D12.h +++ b/src/dawn_native/d3d12/TextureD3D12.h @@ -25,6 +25,9 @@ namespace dawn_native { namespace d3d12 { class Device; DXGI_FORMAT D3D12TextureFormat(dawn::TextureFormat format); + MaybeError ValidateD3D12TextureCanBeWrapped(ID3D12Resource* d3d12Resource, + const TextureDescriptor* descriptor); + MaybeError ValidateTextureDescriptorCanBeWrapped(const TextureDescriptor* descriptor); class Texture : public TextureBase { public: diff --git a/src/include/dawn_native/D3D12Backend.h b/src/include/dawn_native/D3D12Backend.h index 43debedfb8..5a87ab8860 100644 --- a/src/include/dawn_native/D3D12Backend.h +++ b/src/include/dawn_native/D3D12Backend.h @@ -19,12 +19,20 @@ #include #include +#include + +struct ID3D12Device; namespace dawn_native { namespace d3d12 { + DAWN_NATIVE_EXPORT Microsoft::WRL::ComPtr GetD3D12Device(DawnDevice device); DAWN_NATIVE_EXPORT DawnSwapChainImplementation CreateNativeSwapChainImpl(DawnDevice device, HWND window); DAWN_NATIVE_EXPORT DawnTextureFormat GetNativeSwapChainPreferredFormat(const DawnSwapChainImplementation* swapChain); + + DAWN_NATIVE_EXPORT DawnTexture WrapSharedHandle(DawnDevice device, + const DawnTextureDescriptor* descriptor, + HANDLE sharedHandle); }} // namespace dawn_native::d3d12 #endif // DAWNNATIVE_D3D12BACKEND_H_ diff --git a/src/tests/end2end/D3D12ResourceWrappingTests.cpp b/src/tests/end2end/D3D12ResourceWrappingTests.cpp new file mode 100644 index 0000000000..0123216e5f --- /dev/null +++ b/src/tests/end2end/D3D12ResourceWrappingTests.cpp @@ -0,0 +1,243 @@ +// Copyright 2019 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 "tests/DawnTest.h" + +#include +#include +#include +#include + +#include "dawn_native/D3D12Backend.h" +#include "utils/ComboRenderPipelineDescriptor.h" +#include "utils/DawnHelpers.h" + +using Microsoft::WRL::ComPtr; + +namespace { + + class D3D12ResourceTestBase : public DawnTest { + public: + void SetUp() override { + DawnTest::SetUp(); + if (UsesWire()) { + return; + } + + // Create the D3D11 device/contexts that will be used in subsequent tests + ComPtr d3d12Device = dawn_native::d3d12::GetD3D12Device(device.Get()); + + const LUID adapterLuid = d3d12Device->GetAdapterLuid(); + + ComPtr dxgiFactory; + HRESULT hr = ::CreateDXGIFactory2(0, IID_PPV_ARGS(&dxgiFactory)); + ASSERT_EQ(hr, S_OK); + + ComPtr dxgiAdapter; + hr = dxgiFactory->EnumAdapterByLuid(adapterLuid, IID_PPV_ARGS(&dxgiAdapter)); + ASSERT_EQ(hr, S_OK); + + ComPtr d3d11Device; + D3D_FEATURE_LEVEL d3dFeatureLevel; + ComPtr d3d11DeviceContext; + hr = ::D3D11CreateDevice(dxgiAdapter.Get(), D3D_DRIVER_TYPE_UNKNOWN, nullptr, 0, + nullptr, 0, D3D11_SDK_VERSION, &d3d11Device, &d3dFeatureLevel, + &d3d11DeviceContext); + ASSERT_EQ(hr, S_OK); + + mD3d11Device = std::move(d3d11Device); + mD3d11DeviceContext = std::move(d3d11DeviceContext); + } + + protected: + void WrapSharedHandle(const dawn::TextureDescriptor* dawnDescriptor, + const D3D11_TEXTURE2D_DESC* d3dDescriptor, + dawn::Texture* dawnTexture) const { + ComPtr 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; + hr = dxgiResource->CreateSharedHandle( + nullptr, DXGI_SHARED_RESOURCE_READ | DXGI_SHARED_RESOURCE_WRITE, nullptr, + &sharedHandle); + ASSERT_EQ(hr, S_OK); + + DawnTexture texture = dawn_native::d3d12::WrapSharedHandle( + device.Get(), reinterpret_cast(dawnDescriptor), + sharedHandle); + // Now that we've created all of our resources, we can close the handle + // since we no longer need it. + ::CloseHandle(sharedHandle); + + *dawnTexture = dawn::Texture::Acquire(texture); + } + + static constexpr size_t kTestWidth = 10; + static constexpr size_t kTestHeight = 10; + + ComPtr mD3d11Device; + ComPtr mD3d11DeviceContext; + }; + +} // anonymous namespace + +// A small fixture used to initialize default data for the D3D12Resource validation tests. +// These tests are skipped if the harness is using the wire. +class D3D12SharedHandleValidation : public D3D12ResourceTestBase { + public: + void SetUp() override { + D3D12ResourceTestBase::SetUp(); + + dawnDescriptor.dimension = dawn::TextureDimension::e2D; + dawnDescriptor.format = dawn::TextureFormat::BGRA8Unorm; + dawnDescriptor.size = {kTestWidth, kTestHeight, 1}; + dawnDescriptor.sampleCount = 1; + dawnDescriptor.arrayLayerCount = 1; + dawnDescriptor.mipLevelCount = 1; + dawnDescriptor.usage = dawn::TextureUsage::OutputAttachment; + + d3dDescriptor.Width = kTestWidth; + d3dDescriptor.Height = kTestHeight; + d3dDescriptor.MipLevels = 1; + d3dDescriptor.ArraySize = 1; + d3dDescriptor.Format = DXGI_FORMAT_B8G8R8A8_UNORM; + d3dDescriptor.SampleDesc.Count = 1; + d3dDescriptor.SampleDesc.Quality = 0; + d3dDescriptor.Usage = D3D11_USAGE_DEFAULT; + d3dDescriptor.BindFlags = D3D11_BIND_SHADER_RESOURCE | D3D11_BIND_RENDER_TARGET; + d3dDescriptor.CPUAccessFlags = 0; + d3dDescriptor.MiscFlags = + D3D11_RESOURCE_MISC_SHARED_NTHANDLE | D3D11_RESOURCE_MISC_SHARED_KEYEDMUTEX; + } + + protected: + D3D11_TEXTURE2D_DESC d3dDescriptor; + dawn::TextureDescriptor dawnDescriptor; +}; + +// Test a successful wrapping of an D3D12Resource in a texture +TEST_P(D3D12SharedHandleValidation, Success) { + DAWN_SKIP_TEST_IF(UsesWire()); + + dawn::Texture texture; + WrapSharedHandle(&dawnDescriptor, &d3dDescriptor, &texture); + + ASSERT_NE(texture.Get(), nullptr); +} + +// Test an error occurs if the texture descriptor is invalid +TEST_P(D3D12SharedHandleValidation, InvalidTextureDescriptor) { + DAWN_SKIP_TEST_IF(UsesWire()); + dawnDescriptor.nextInChain = this; + + dawn::Texture texture; + ASSERT_DEVICE_ERROR(WrapSharedHandle(&dawnDescriptor, &d3dDescriptor, &texture)); + + ASSERT_EQ(texture.Get(), nullptr); +} + +// Test an error occurs if the descriptor mip level count isn't 1 +TEST_P(D3D12SharedHandleValidation, InvalidMipLevelCount) { + DAWN_SKIP_TEST_IF(UsesWire()); + dawnDescriptor.mipLevelCount = 2; + + dawn::Texture texture; + ASSERT_DEVICE_ERROR(WrapSharedHandle(&dawnDescriptor, &d3dDescriptor, &texture)); + + ASSERT_EQ(texture.Get(), nullptr); +} + +// Test an error occurs if the descriptor array layer count isn't 1 +TEST_P(D3D12SharedHandleValidation, InvalidArrayLayerCount) { + DAWN_SKIP_TEST_IF(UsesWire()); + dawnDescriptor.arrayLayerCount = 2; + + dawn::Texture texture; + ASSERT_DEVICE_ERROR(WrapSharedHandle(&dawnDescriptor, &d3dDescriptor, &texture)); + + ASSERT_EQ(texture.Get(), nullptr); +} + +// Test an error occurs if the descriptor sample count isn't 1 +TEST_P(D3D12SharedHandleValidation, InvalidSampleCount) { + DAWN_SKIP_TEST_IF(UsesWire()); + dawnDescriptor.sampleCount = 4; + + dawn::Texture texture; + ASSERT_DEVICE_ERROR(WrapSharedHandle(&dawnDescriptor, &d3dDescriptor, &texture)); + + ASSERT_EQ(texture.Get(), nullptr); +} + +// Test an error occurs if the descriptor width doesn't match the texture's +TEST_P(D3D12SharedHandleValidation, InvalidWidth) { + DAWN_SKIP_TEST_IF(UsesWire()); + dawnDescriptor.size.width = kTestWidth + 1; + + dawn::Texture texture; + ASSERT_DEVICE_ERROR(WrapSharedHandle(&dawnDescriptor, &d3dDescriptor, &texture)); + + ASSERT_EQ(texture.Get(), nullptr); +} + +// Test an error occurs if the descriptor height doesn't match the texture's +TEST_P(D3D12SharedHandleValidation, InvalidHeight) { + DAWN_SKIP_TEST_IF(UsesWire()); + dawnDescriptor.size.height = kTestHeight + 1; + + dawn::Texture texture; + ASSERT_DEVICE_ERROR(WrapSharedHandle(&dawnDescriptor, &d3dDescriptor, &texture)); + + ASSERT_EQ(texture.Get(), nullptr); +} + +// Test an error occurs if the descriptor format isn't compatible with the D3D12 Resource +TEST_P(D3D12SharedHandleValidation, InvalidFormat) { + DAWN_SKIP_TEST_IF(UsesWire()); + dawnDescriptor.format = dawn::TextureFormat::R8Unorm; + + dawn::Texture texture; + ASSERT_DEVICE_ERROR(WrapSharedHandle(&dawnDescriptor, &d3dDescriptor, &texture)); + + ASSERT_EQ(texture.Get(), nullptr); +} + +// Test an error occurs if the number of D3D mip levels is greater than 1. +TEST_P(D3D12SharedHandleValidation, InvalidNumD3DMipLevels) { + DAWN_SKIP_TEST_IF(UsesWire()); + d3dDescriptor.MipLevels = 2; + + dawn::Texture texture; + ASSERT_DEVICE_ERROR(WrapSharedHandle(&dawnDescriptor, &d3dDescriptor, &texture)); + + ASSERT_EQ(texture.Get(), nullptr); +} + +// Test an error occurs if the number of array levels is greater than 1. +TEST_P(D3D12SharedHandleValidation, InvalidD3DArraySize) { + DAWN_SKIP_TEST_IF(UsesWire()); + d3dDescriptor.ArraySize = 2; + + dawn::Texture texture; + ASSERT_DEVICE_ERROR(WrapSharedHandle(&dawnDescriptor, &d3dDescriptor, &texture)); + + ASSERT_EQ(texture.Get(), nullptr); +} + +DAWN_INSTANTIATE_TEST(D3D12SharedHandleValidation, D3D12Backend);