| // Copyright 2024 The Dawn & Tint Authors | |
| // | |
| // Redistribution and use in source and binary forms, with or without | |
| // modification, are permitted provided that the following conditions are met: | |
| // | |
| // 1. Redistributions of source code must retain the above copyright notice, this | |
| // list of conditions and the following disclaimer. | |
| // | |
| // 2. Redistributions in binary form must reproduce the above copyright notice, | |
| // this list of conditions and the following disclaimer in the documentation | |
| // and/or other materials provided with the distribution. | |
| // | |
| // 3. Neither the name of the copyright holder nor the names of its | |
| // contributors may be used to endorse or promote products derived from | |
| // this software without specific prior written permission. | |
| // | |
| // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" | |
| // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE | |
| // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE | |
| // DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE | |
| // FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL | |
| // DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR | |
| // SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER | |
| // CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, | |
| // OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | |
| // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |
| #include <d3d12.h> | |
| #include <vector> | |
| #include "dawn/native/D3D12Backend.h" | |
| #include "dawn/native/d3d12/DeviceD3D12.h" | |
| #include "dawn/tests/DawnTest.h" | |
| #include "dawn/tests/white_box/SharedBufferMemoryTests.h" | |
| #include "dawn/utils/ComboRenderPipelineDescriptor.h" | |
| #include "dawn/utils/WGPUHelpers.h" | |
| namespace dawn { | |
| namespace { | |
| constexpr uint32_t kBufferSize = 4; | |
| struct FenceInfo { | |
| ComPtr<ID3D12Fence> fence; | |
| uint64_t signaledValue; | |
| }; | |
| void WriteD3D12UploadBuffer(ID3D12Resource* resource, uint32_t data) { | |
| void* mappedBufferBegin; | |
| D3D12_RANGE range; | |
| range.Begin = 0; | |
| range.End = kBufferSize; | |
| resource->Map(0, &range, &mappedBufferBegin); | |
| memcpy(mappedBufferBegin, &data, kBufferSize); | |
| resource->Unmap(0, &range); | |
| } | |
| void CopyD3D12Resource(ID3D12Device* device, ID3D12Resource* source, ID3D12Resource* destination) { | |
| ComPtr<ID3D12CommandAllocator> commandAllocator; | |
| device->CreateCommandAllocator(D3D12_COMMAND_LIST_TYPE_DIRECT, IID_PPV_ARGS(&commandAllocator)); | |
| ComPtr<ID3D12CommandQueue> commandQueue; | |
| D3D12_COMMAND_QUEUE_DESC queueDesc = {}; | |
| queueDesc.Flags = D3D12_COMMAND_QUEUE_FLAG_NONE; | |
| queueDesc.Type = D3D12_COMMAND_LIST_TYPE_DIRECT; | |
| device->CreateCommandQueue(&queueDesc, IID_PPV_ARGS(&commandQueue)); | |
| ComPtr<ID3D12GraphicsCommandList> commandList; | |
| device->CreateCommandList(0, D3D12_COMMAND_LIST_TYPE_DIRECT, commandAllocator.Get(), nullptr, | |
| IID_PPV_ARGS(&commandList)); | |
| ID3D12CommandList* commandLists[] = {commandList.Get()}; | |
| commandList->CopyResource(destination, source); | |
| commandList->Close(); | |
| commandQueue->ExecuteCommandLists(_countof(commandLists), commandLists); | |
| ComPtr<ID3D12Fence> fence; | |
| device->CreateFence(0, D3D12_FENCE_FLAG_SHARED, IID_PPV_ARGS(&fence)); | |
| UINT64 signaledValue = 1; | |
| commandQueue->Signal(fence.Get(), signaledValue); | |
| HANDLE fenceEvent = 0; | |
| if (fence->GetCompletedValue() < signaledValue) { | |
| fence->SetEventOnCompletion(signaledValue, fenceEvent); | |
| WaitForSingleObject(fenceEvent, INFINITE); | |
| } | |
| } | |
| class Backend : public SharedBufferMemoryTestBackend { | |
| public: | |
| static Backend* GetInstance() { | |
| static Backend b; | |
| return &b; | |
| } | |
| std::vector<wgpu::FeatureName> RequiredFeatures(const wgpu::Adapter& adapter) const override { | |
| return {wgpu::FeatureName::SharedBufferMemoryD3D12Resource, | |
| wgpu::FeatureName::SharedFenceDXGISharedHandle}; | |
| } | |
| wgpu::SharedBufferMemory CreateSharedBufferMemory(const wgpu::Device& device, | |
| wgpu::BufferUsage usages, | |
| uint32_t bufferSize, | |
| uint32_t initializationData = 0) override { | |
| ComPtr<ID3D12Device> d3d12Device = CreateD3D12Device(device); | |
| D3D12_HEAP_TYPE d3d12HeapType; | |
| if (usages & wgpu::BufferUsage::MapWrite) { | |
| d3d12HeapType = D3D12_HEAP_TYPE_UPLOAD; | |
| } else if (usages & wgpu::BufferUsage::MapRead) { | |
| d3d12HeapType = D3D12_HEAP_TYPE_READBACK; | |
| } else { | |
| d3d12HeapType = D3D12_HEAP_TYPE_DEFAULT; | |
| } | |
| // To use a buffer with CreateConstantBufferView, it must be aligned to a constant. | |
| if (usages & wgpu::BufferUsage::Uniform) { | |
| bufferSize = Align(bufferSize, D3D12_CONSTANT_BUFFER_DATA_PLACEMENT_ALIGNMENT); | |
| } | |
| ComPtr<ID3D12Resource> d3d12Resource = | |
| CreateD3D12Buffer(d3d12Device.Get(), d3d12HeapType, bufferSize); | |
| if (initializationData) { | |
| switch (d3d12HeapType) { | |
| case D3D12_HEAP_TYPE_UPLOAD: | |
| WriteD3D12UploadBuffer(d3d12Resource.Get(), initializationData); | |
| break; | |
| case D3D12_HEAP_TYPE_READBACK: | |
| case D3D12_HEAP_TYPE_DEFAULT: { | |
| ComPtr<ID3D12Resource> uploadBuffer = | |
| CreateD3D12Buffer(d3d12Device.Get(), D3D12_HEAP_TYPE_UPLOAD, bufferSize); | |
| WriteD3D12UploadBuffer(uploadBuffer.Get(), initializationData); | |
| CopyD3D12Resource(d3d12Device.Get(), uploadBuffer.Get(), d3d12Resource.Get()); | |
| } break; | |
| default: | |
| DAWN_UNREACHABLE(); | |
| } | |
| } | |
| wgpu::SharedBufferMemoryDescriptor desc; | |
| native::d3d12::SharedBufferMemoryD3D12ResourceDescriptor sharedD3d12ResourceDesc; | |
| sharedD3d12ResourceDesc.resource = d3d12Resource.Get(); | |
| desc.nextInChain = &sharedD3d12ResourceDesc; | |
| return device.ImportSharedBufferMemory(&desc); | |
| } | |
| ComPtr<ID3D12Device> CreateD3D12Device(const wgpu::Device& device, | |
| bool createWarpDevice = false) { | |
| ComPtr<IDXGIAdapter> dxgiAdapter = nullptr; | |
| ComPtr<IDXGIFactory4> dxgiFactory; | |
| CreateDXGIFactory2(0, IID_PPV_ARGS(&dxgiFactory)); | |
| if (createWarpDevice) { | |
| dxgiFactory->EnumWarpAdapter(IID_PPV_ARGS(&dxgiAdapter)); | |
| } else { | |
| dxgiAdapter = native::d3d::GetDXGIAdapter(device.GetAdapter().Get()); | |
| DXGI_ADAPTER_DESC adapterDesc; | |
| dxgiAdapter->GetDesc(&adapterDesc); | |
| dxgiFactory->EnumAdapterByLuid(adapterDesc.AdapterLuid, IID_PPV_ARGS(&dxgiAdapter)); | |
| } | |
| ComPtr<ID3D12Device> d3d12Device; | |
| D3D12CreateDevice(dxgiAdapter.Get(), D3D_FEATURE_LEVEL_11_0, __uuidof(ID3D12Device), | |
| &d3d12Device); | |
| return d3d12Device; | |
| } | |
| ComPtr<ID3D12Resource> CreateD3D12Buffer(ID3D12Device* device, | |
| D3D12_HEAP_TYPE heapType, | |
| uint32_t bufferSize = kBufferSize) { | |
| D3D12_RESOURCE_STATES initialResourceState; | |
| D3D12_RESOURCE_FLAGS resourceFlags = D3D12_RESOURCE_FLAG_NONE; | |
| switch (heapType) { | |
| case D3D12_HEAP_TYPE_UPLOAD: | |
| initialResourceState = D3D12_RESOURCE_STATE_GENERIC_READ; | |
| break; | |
| case D3D12_HEAP_TYPE_READBACK: | |
| initialResourceState = D3D12_RESOURCE_STATE_COPY_DEST; | |
| break; | |
| default: | |
| initialResourceState = D3D12_RESOURCE_STATE_COMMON; | |
| resourceFlags = D3D12_RESOURCE_FLAG_ALLOW_UNORDERED_ACCESS; | |
| } | |
| D3D12_HEAP_PROPERTIES heapProperties = {heapType, D3D12_CPU_PAGE_PROPERTY_UNKNOWN, | |
| D3D12_MEMORY_POOL_UNKNOWN, 0, 0}; | |
| D3D12_RESOURCE_DESC descriptor; | |
| descriptor.Dimension = D3D12_RESOURCE_DIMENSION_BUFFER; | |
| descriptor.Alignment = 0; | |
| descriptor.Width = bufferSize; | |
| descriptor.Height = 1; | |
| descriptor.DepthOrArraySize = 1; | |
| descriptor.MipLevels = 1; | |
| descriptor.Format = DXGI_FORMAT_UNKNOWN; | |
| descriptor.SampleDesc.Count = 1; | |
| descriptor.SampleDesc.Quality = 0; | |
| descriptor.Layout = D3D12_TEXTURE_LAYOUT_ROW_MAJOR; | |
| descriptor.Flags = resourceFlags; | |
| ComPtr<ID3D12Resource> resource; | |
| device->CreateCommittedResource(&heapProperties, D3D12_HEAP_FLAG_NONE, &descriptor, | |
| initialResourceState, {}, IID_PPV_ARGS(&resource)); | |
| return resource; | |
| } | |
| private: | |
| Backend() {} | |
| }; | |
| // Ensure that importing a nullptr ID3D12Resource results in error. | |
| TEST_P(SharedBufferMemoryTests, nullResourceFailure) { | |
| native::d3d12::SharedBufferMemoryD3D12ResourceDescriptor sharedD3d12ResourceDesc; | |
| sharedD3d12ResourceDesc.resource = nullptr; | |
| wgpu::SharedBufferMemoryDescriptor desc; | |
| desc.nextInChain = &sharedD3d12ResourceDesc; | |
| ASSERT_DEVICE_ERROR(device.ImportSharedBufferMemory(&desc)); | |
| } | |
| // Validate that importing an ID3D12Resource across devices results in failure. This is tested by | |
| // creating a resource with a WARP device and attempting to use it on a non-WARP device. | |
| TEST_P(SharedBufferMemoryTests, CrossDeviceResourceImportFailure) { | |
| DAWN_TEST_UNSUPPORTED_IF(IsWARP()); | |
| ComPtr<ID3D12Device> warpDevice = | |
| static_cast<Backend*>(GetParam().mBackend)->CreateD3D12Device(device, true); | |
| ComPtr<ID3D12Resource> d3d12Resource = | |
| static_cast<Backend*>(GetParam().mBackend) | |
| ->CreateD3D12Buffer(warpDevice.Get(), D3D12_HEAP_TYPE_UPLOAD, D3D12_RESOURCE_FLAG_NONE); | |
| wgpu::SharedBufferMemoryDescriptor desc; | |
| native::d3d12::SharedBufferMemoryD3D12ResourceDescriptor sharedD3d12ResourceDesc; | |
| sharedD3d12ResourceDesc.resource = d3d12Resource.Get(); | |
| desc.nextInChain = &sharedD3d12ResourceDesc; | |
| ASSERT_DEVICE_ERROR(device.ImportSharedBufferMemory(&desc)); | |
| } | |
| DAWN_INSTANTIATE_PREFIXED_TEST_P(D3D12, | |
| SharedBufferMemoryTests, | |
| {D3D12Backend()}, | |
| {Backend::GetInstance()}); | |
| } // anonymous namespace | |
| } // namespace dawn |