d4r: official NVIDIA DLSS on AMD GPUs under Linux and Proton
This commit is contained in:
commit
118173ecd7
128 files changed
+31333
No files matched your search
@@ -0,0 +1,446 @@
|
||||
// Probe for zero-copy D3D12 <-> CUDA/HIP sharing under Proton.
|
||||
//
|
||||
// D3D12 shared NT handles are a dead end under Wine (no unix fd behind them;
|
||||
// vkd3d-proton exports only textures), so this goes through vkd3d-proton's
|
||||
// Vulkan interop instead:
|
||||
// - a VkBuffer in device-local memory, allocated on vkd3d-proton's own
|
||||
// VkDevice with VkExportMemoryAllocateInfo (winevulkan makes it a host
|
||||
// OPAQUE_FD export),
|
||||
// - imported into HIP by the d4r nvcuda bridge (d4rImportVulkanMemory),
|
||||
// - filled from / copied into a D3D12 texture by raw Vulkan copy commands
|
||||
// recorded inside a D3D12 command list (ID3D12DXVKInteropDevice1::
|
||||
// BeginVkCommandBufferInterop), which is where the NGX shim records its
|
||||
// staging copies today.
|
||||
//
|
||||
// usage: d3d12_hip_interop_probe.exe NVCUDA_BRIDGE_DLL REPORT_FILE [WIDTH HEIGHT]
|
||||
// (Proton does not connect the console, so results go to REPORT_FILE.)
|
||||
#define WIDL_EXPLICIT_AGGREGATE_RETURNS
|
||||
#include <windows.h>
|
||||
#include <d3d12.h>
|
||||
#include <vulkan/vulkan_core.h>
|
||||
|
||||
#include <chrono>
|
||||
#include <cstdint>
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
#include <vector>
|
||||
|
||||
using CUresult = int;
|
||||
using CUdeviceptr = unsigned long long;
|
||||
|
||||
// vkd3d-proton's interop interface (include/vkd3d_device_vkd3d_ext.idl).
|
||||
MIDL_INTERFACE("39da4e09-bd1c-4198-9fae-86bbe3be41fd")
|
||||
ID3D12DXVKInteropDevice : public IUnknown
|
||||
{
|
||||
virtual HRESULT STDMETHODCALLTYPE GetDXGIAdapter(REFIID iid, void** object) = 0;
|
||||
virtual HRESULT STDMETHODCALLTYPE GetInstanceExtensions(UINT* count, const char** extensions) = 0;
|
||||
virtual HRESULT STDMETHODCALLTYPE GetDeviceExtensions(UINT* count, const char** extensions) = 0;
|
||||
virtual HRESULT STDMETHODCALLTYPE GetDeviceFeatures(const VkPhysicalDeviceFeatures2** features) = 0;
|
||||
virtual HRESULT STDMETHODCALLTYPE GetVulkanHandles(VkInstance* instance, VkPhysicalDevice* physical, VkDevice* device) = 0;
|
||||
virtual HRESULT STDMETHODCALLTYPE GetVulkanQueueInfo(ID3D12CommandQueue* queue, VkQueue* vk_queue, UINT32* family) = 0;
|
||||
virtual void STDMETHODCALLTYPE GetVulkanImageLayout(ID3D12Resource* resource, D3D12_RESOURCE_STATES state,
|
||||
VkImageLayout* layout) = 0;
|
||||
virtual HRESULT STDMETHODCALLTYPE GetVulkanResourceInfo(ID3D12Resource* resource, UINT64* handle, UINT64* offset) = 0;
|
||||
virtual HRESULT STDMETHODCALLTYPE LockCommandQueue(ID3D12CommandQueue* queue) = 0;
|
||||
virtual HRESULT STDMETHODCALLTYPE UnlockCommandQueue(ID3D12CommandQueue* queue) = 0;
|
||||
};
|
||||
|
||||
MIDL_INTERFACE("902d8115-59eb-4406-9518-fe00f991ee65")
|
||||
ID3D12DXVKInteropDevice1 : public ID3D12DXVKInteropDevice
|
||||
{
|
||||
virtual HRESULT STDMETHODCALLTYPE GetVulkanResourceInfo1(ID3D12Resource* resource, UINT64* handle, UINT64* offset,
|
||||
VkFormat* format) = 0;
|
||||
virtual HRESULT STDMETHODCALLTYPE CreateInteropCommandQueue(const D3D12_COMMAND_QUEUE_DESC* desc, UINT32 family,
|
||||
ID3D12CommandQueue** queue) = 0;
|
||||
virtual HRESULT STDMETHODCALLTYPE CreateInteropCommandAllocator(D3D12_COMMAND_LIST_TYPE type, UINT32 family,
|
||||
ID3D12CommandAllocator** allocator) = 0;
|
||||
virtual HRESULT STDMETHODCALLTYPE BeginVkCommandBufferInterop(ID3D12CommandList* list, VkCommandBuffer* buffer) = 0;
|
||||
virtual HRESULT STDMETHODCALLTYPE EndVkCommandBufferInterop(ID3D12CommandList* list) = 0;
|
||||
};
|
||||
__CRT_UUID_DECL(ID3D12DXVKInteropDevice1, 0x902d8115, 0x59eb, 0x4406, 0x95, 0x18, 0xfe, 0x00, 0xf9, 0x91, 0xee, 0x65)
|
||||
|
||||
static ID3D12Device* g_device;
|
||||
static ID3D12CommandQueue* g_queue;
|
||||
static ID3D12CommandAllocator* g_allocator;
|
||||
static ID3D12GraphicsCommandList* g_list;
|
||||
static ID3D12Fence* g_fence;
|
||||
static HANDLE g_event;
|
||||
static UINT64 g_fenceValue;
|
||||
|
||||
static bool check(HRESULT hr, const char* what)
|
||||
{
|
||||
if (FAILED(hr))
|
||||
std::printf("%s failed: 0x%08lx\n", what, hr);
|
||||
return SUCCEEDED(hr);
|
||||
}
|
||||
|
||||
static bool flush()
|
||||
{
|
||||
if (!check(g_list->Close(), "Close"))
|
||||
return false;
|
||||
ID3D12CommandList* lists[] = {g_list};
|
||||
g_queue->ExecuteCommandLists(1, lists);
|
||||
g_queue->Signal(g_fence, ++g_fenceValue);
|
||||
g_fence->SetEventOnCompletion(g_fenceValue, g_event);
|
||||
WaitForSingleObject(g_event, INFINITE);
|
||||
return check(g_allocator->Reset(), "allocator Reset") && check(g_list->Reset(g_allocator, nullptr), "list Reset");
|
||||
}
|
||||
|
||||
static double elapsed_ms(std::chrono::steady_clock::time_point start)
|
||||
{
|
||||
return std::chrono::duration<double, std::milli>(std::chrono::steady_clock::now() - start).count();
|
||||
}
|
||||
|
||||
static ID3D12Resource* create_buffer(D3D12_HEAP_TYPE type, UINT64 size, D3D12_RESOURCE_STATES state)
|
||||
{
|
||||
D3D12_HEAP_PROPERTIES heap = {};
|
||||
heap.Type = type;
|
||||
D3D12_RESOURCE_DESC desc = {};
|
||||
desc.Dimension = D3D12_RESOURCE_DIMENSION_BUFFER;
|
||||
desc.Width = size;
|
||||
desc.Height = 1;
|
||||
desc.DepthOrArraySize = 1;
|
||||
desc.MipLevels = 1;
|
||||
desc.SampleDesc.Count = 1;
|
||||
desc.Layout = D3D12_TEXTURE_LAYOUT_ROW_MAJOR;
|
||||
ID3D12Resource* resource = nullptr;
|
||||
if (!check(g_device->CreateCommittedResource(&heap, D3D12_HEAP_FLAG_NONE, &desc, state, nullptr,
|
||||
__uuidof(ID3D12Resource), reinterpret_cast<void**>(&resource)),
|
||||
"CreateCommittedResource(buffer)"))
|
||||
return nullptr;
|
||||
return resource;
|
||||
}
|
||||
|
||||
static void transition(ID3D12Resource* resource, D3D12_RESOURCE_STATES before, D3D12_RESOURCE_STATES after)
|
||||
{
|
||||
D3D12_RESOURCE_BARRIER barrier = {};
|
||||
barrier.Type = D3D12_RESOURCE_BARRIER_TYPE_TRANSITION;
|
||||
barrier.Transition.pResource = resource;
|
||||
barrier.Transition.Subresource = D3D12_RESOURCE_BARRIER_ALL_SUBRESOURCES;
|
||||
barrier.Transition.StateBefore = before;
|
||||
barrier.Transition.StateAfter = after;
|
||||
g_list->ResourceBarrier(1, &barrier);
|
||||
}
|
||||
|
||||
static uint64_t pattern(uint64_t i)
|
||||
{
|
||||
return (i * 0x9E3779B97F4A7C15ull) ^ (i >> 7);
|
||||
}
|
||||
|
||||
int main(int argc, char** argv)
|
||||
{
|
||||
if (argc < 3)
|
||||
{
|
||||
std::printf("usage: %s NVCUDA_BRIDGE_DLL REPORT_FILE [WIDTH HEIGHT]\n", argv[0]);
|
||||
return 2;
|
||||
}
|
||||
if (std::freopen(argv[2], "w", stdout) == nullptr)
|
||||
return 2;
|
||||
std::setvbuf(stdout, nullptr, _IONBF, 0);
|
||||
const UINT width = argc > 4 ? static_cast<UINT>(std::strtoul(argv[3], nullptr, 0)) : 2560;
|
||||
const UINT height = argc > 4 ? static_cast<UINT>(std::strtoul(argv[4], nullptr, 0)) : 1440;
|
||||
const UINT64 texel = 8; // R16G16B16A16_FLOAT, the DLSS output format
|
||||
const UINT64 pitch = width * texel;
|
||||
const UINT64 bytes = pitch * height;
|
||||
const UINT64 words = bytes / 8;
|
||||
if (pitch % D3D12_TEXTURE_DATA_PITCH_ALIGNMENT != 0)
|
||||
{
|
||||
std::printf("width must make a 256-byte aligned row pitch\n");
|
||||
return 2;
|
||||
}
|
||||
|
||||
if (!check(D3D12CreateDevice(nullptr, D3D_FEATURE_LEVEL_12_0, __uuidof(ID3D12Device),
|
||||
reinterpret_cast<void**>(&g_device)),
|
||||
"D3D12CreateDevice"))
|
||||
return 1;
|
||||
D3D12_COMMAND_QUEUE_DESC queueDesc = {};
|
||||
queueDesc.Type = D3D12_COMMAND_LIST_TYPE_DIRECT;
|
||||
if (!check(g_device->CreateCommandQueue(&queueDesc, __uuidof(ID3D12CommandQueue), reinterpret_cast<void**>(&g_queue)),
|
||||
"CreateCommandQueue") ||
|
||||
!check(g_device->CreateCommandAllocator(D3D12_COMMAND_LIST_TYPE_DIRECT, __uuidof(ID3D12CommandAllocator),
|
||||
reinterpret_cast<void**>(&g_allocator)),
|
||||
"CreateCommandAllocator") ||
|
||||
!check(g_device->CreateCommandList(0, D3D12_COMMAND_LIST_TYPE_DIRECT, g_allocator, nullptr,
|
||||
__uuidof(ID3D12GraphicsCommandList), reinterpret_cast<void**>(&g_list)),
|
||||
"CreateCommandList") ||
|
||||
!check(g_device->CreateFence(0, D3D12_FENCE_FLAG_NONE, __uuidof(ID3D12Fence), reinterpret_cast<void**>(&g_fence)),
|
||||
"CreateFence"))
|
||||
return 1;
|
||||
g_event = CreateEventA(nullptr, FALSE, FALSE, nullptr);
|
||||
|
||||
ID3D12DXVKInteropDevice1* interop = nullptr;
|
||||
if (!check(g_device->QueryInterface(__uuidof(ID3D12DXVKInteropDevice1), reinterpret_cast<void**>(&interop)),
|
||||
"QueryInterface(ID3D12DXVKInteropDevice1)"))
|
||||
return 1;
|
||||
VkInstance vkInstance = VK_NULL_HANDLE;
|
||||
VkPhysicalDevice vkPhysical = VK_NULL_HANDLE;
|
||||
VkDevice vkDevice = VK_NULL_HANDLE;
|
||||
if (!check(interop->GetVulkanHandles(&vkInstance, &vkPhysical, &vkDevice), "GetVulkanHandles"))
|
||||
return 1;
|
||||
UINT extensionCount = 0;
|
||||
interop->GetDeviceExtensions(&extensionCount, nullptr);
|
||||
std::vector<const char*> extensions(extensionCount);
|
||||
interop->GetDeviceExtensions(&extensionCount, extensions.data());
|
||||
bool win32Memory = false;
|
||||
for (const char* name : extensions)
|
||||
win32Memory |= std::strcmp(name, "VK_KHR_external_memory_win32") == 0;
|
||||
std::printf("vkd3d-proton VkDevice %p, %u extensions, VK_KHR_external_memory_win32 %s\n",
|
||||
static_cast<void*>(vkDevice), extensionCount, win32Memory ? "enabled" : "MISSING");
|
||||
|
||||
HMODULE vulkan = LoadLibraryA("vulkan-1.dll");
|
||||
auto getInstanceProc = vulkan != nullptr ? reinterpret_cast<PFN_vkGetInstanceProcAddr>(
|
||||
reinterpret_cast<void*>(GetProcAddress(vulkan, "vkGetInstanceProcAddr")))
|
||||
: nullptr;
|
||||
if (getInstanceProc == nullptr)
|
||||
{
|
||||
std::printf("vulkan-1.dll unavailable\n");
|
||||
return 1;
|
||||
}
|
||||
auto getDeviceProc = reinterpret_cast<PFN_vkGetDeviceProcAddr>(getInstanceProc(vkInstance, "vkGetDeviceProcAddr"));
|
||||
auto getMemoryProperties = reinterpret_cast<PFN_vkGetPhysicalDeviceMemoryProperties>(
|
||||
getInstanceProc(vkInstance, "vkGetPhysicalDeviceMemoryProperties"));
|
||||
#define DEVICE_PROC(name) auto name = reinterpret_cast<PFN_##name>(getDeviceProc(vkDevice, #name))
|
||||
DEVICE_PROC(vkCreateBuffer);
|
||||
DEVICE_PROC(vkGetBufferMemoryRequirements);
|
||||
DEVICE_PROC(vkAllocateMemory);
|
||||
DEVICE_PROC(vkBindBufferMemory);
|
||||
DEVICE_PROC(vkCmdPipelineBarrier);
|
||||
DEVICE_PROC(vkCmdCopyImageToBuffer);
|
||||
DEVICE_PROC(vkCmdCopyBufferToImage);
|
||||
#undef DEVICE_PROC
|
||||
|
||||
// Exportable device-local buffer on vkd3d-proton's device.
|
||||
VkExternalMemoryBufferCreateInfo externalBuffer = {VK_STRUCTURE_TYPE_EXTERNAL_MEMORY_BUFFER_CREATE_INFO};
|
||||
externalBuffer.handleTypes = VK_EXTERNAL_MEMORY_HANDLE_TYPE_OPAQUE_WIN32_BIT;
|
||||
VkBufferCreateInfo bufferInfo = {VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO, &externalBuffer};
|
||||
bufferInfo.size = bytes;
|
||||
bufferInfo.usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT;
|
||||
bufferInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
|
||||
VkBuffer vkBuffer = VK_NULL_HANDLE;
|
||||
VkResult vr = vkCreateBuffer(vkDevice, &bufferInfo, nullptr, &vkBuffer);
|
||||
VkMemoryRequirements requirements = {};
|
||||
vkGetBufferMemoryRequirements(vkDevice, vkBuffer, &requirements);
|
||||
VkPhysicalDeviceMemoryProperties memoryProperties = {};
|
||||
getMemoryProperties(vkPhysical, &memoryProperties);
|
||||
int memoryType = -1;
|
||||
for (int pass = 0; pass < 2 && memoryType < 0; ++pass)
|
||||
for (uint32_t i = 0; i < memoryProperties.memoryTypeCount && memoryType < 0; ++i)
|
||||
{
|
||||
const VkMemoryPropertyFlags flags = memoryProperties.memoryTypes[i].propertyFlags;
|
||||
if ((requirements.memoryTypeBits & (1u << i)) && (flags & VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT) &&
|
||||
(pass == 1 || !(flags & VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT)))
|
||||
memoryType = static_cast<int>(i);
|
||||
}
|
||||
VkMemoryDedicatedAllocateInfo dedicated = {VK_STRUCTURE_TYPE_MEMORY_DEDICATED_ALLOCATE_INFO};
|
||||
dedicated.buffer = vkBuffer;
|
||||
VkExportMemoryAllocateInfo exportInfo = {VK_STRUCTURE_TYPE_EXPORT_MEMORY_ALLOCATE_INFO, &dedicated};
|
||||
exportInfo.handleTypes = VK_EXTERNAL_MEMORY_HANDLE_TYPE_OPAQUE_WIN32_BIT;
|
||||
VkMemoryAllocateInfo allocateInfo = {VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO, &exportInfo};
|
||||
allocateInfo.allocationSize = requirements.size;
|
||||
allocateInfo.memoryTypeIndex = static_cast<uint32_t>(memoryType);
|
||||
VkDeviceMemory vkMemory = VK_NULL_HANDLE;
|
||||
if (vr == VK_SUCCESS && memoryType >= 0)
|
||||
vr = vkAllocateMemory(vkDevice, &allocateInfo, nullptr, &vkMemory);
|
||||
if (vr == VK_SUCCESS)
|
||||
vr = vkBindBufferMemory(vkDevice, vkBuffer, vkMemory, 0);
|
||||
std::printf("exportable VkBuffer: %llu bytes (allocation %llu, memory type %d): VkResult %d\n",
|
||||
static_cast<unsigned long long>(bytes), static_cast<unsigned long long>(requirements.size), memoryType,
|
||||
vr);
|
||||
if (vr != VK_SUCCESS || memoryType < 0)
|
||||
return 1;
|
||||
|
||||
HMODULE bridge = LoadLibraryA(argv[1]);
|
||||
if (bridge == nullptr)
|
||||
{
|
||||
std::printf("LoadLibrary(%s) failed: %lu\n", argv[1], GetLastError());
|
||||
return 1;
|
||||
}
|
||||
using ImportFn = CUresult(WINAPI*)(void*, uint64_t, uint64_t, CUdeviceptr*, void**);
|
||||
using DtoHFn = CUresult(WINAPI*)(void*, CUdeviceptr, size_t);
|
||||
using MemsetFn = CUresult(WINAPI*)(CUdeviceptr, unsigned int, size_t);
|
||||
using SyncFn = CUresult(WINAPI*)();
|
||||
using AllocFn = CUresult(WINAPI*)(CUdeviceptr*, size_t);
|
||||
using DtoDFn = CUresult(WINAPI*)(CUdeviceptr, CUdeviceptr, size_t);
|
||||
auto import = reinterpret_cast<ImportFn>(reinterpret_cast<void*>(GetProcAddress(bridge, "d4rImportVulkanMemory")));
|
||||
auto dtoh = reinterpret_cast<DtoHFn>(reinterpret_cast<void*>(GetProcAddress(bridge, "cuMemcpyDtoH")));
|
||||
auto memset32 = reinterpret_cast<MemsetFn>(reinterpret_cast<void*>(GetProcAddress(bridge, "cuMemsetD32")));
|
||||
auto synchronize = reinterpret_cast<SyncFn>(reinterpret_cast<void*>(GetProcAddress(bridge, "cuCtxSynchronize")));
|
||||
auto alloc = reinterpret_cast<AllocFn>(reinterpret_cast<void*>(GetProcAddress(bridge, "cuMemAlloc")));
|
||||
auto dtod = reinterpret_cast<DtoDFn>(reinterpret_cast<void*>(GetProcAddress(bridge, "cuMemcpyDtoD")));
|
||||
if (import == nullptr || dtoh == nullptr || memset32 == nullptr || synchronize == nullptr || alloc == nullptr ||
|
||||
dtod == nullptr)
|
||||
{
|
||||
std::printf("bridge exports missing\n");
|
||||
return 1;
|
||||
}
|
||||
CUdeviceptr device = 0;
|
||||
void* external = nullptr;
|
||||
CUresult result = import(vkDevice, reinterpret_cast<uint64_t>(vkMemory), requirements.size, &device, &external);
|
||||
std::printf("d4rImportVulkanMemory -> %d, device 0x%llx\n", result, device);
|
||||
if (result != 0)
|
||||
return 1;
|
||||
|
||||
// A game-like R16G16B16A16_FLOAT texture, filled through an upload buffer.
|
||||
D3D12_HEAP_PROPERTIES defaultHeap = {};
|
||||
defaultHeap.Type = D3D12_HEAP_TYPE_DEFAULT;
|
||||
D3D12_RESOURCE_DESC textureDesc = {};
|
||||
textureDesc.Dimension = D3D12_RESOURCE_DIMENSION_TEXTURE2D;
|
||||
textureDesc.Width = width;
|
||||
textureDesc.Height = height;
|
||||
textureDesc.DepthOrArraySize = 1;
|
||||
textureDesc.MipLevels = 1;
|
||||
textureDesc.Format = DXGI_FORMAT_R16G16B16A16_FLOAT;
|
||||
textureDesc.SampleDesc.Count = 1;
|
||||
textureDesc.Flags = D3D12_RESOURCE_FLAG_ALLOW_UNORDERED_ACCESS;
|
||||
ID3D12Resource* texture = nullptr;
|
||||
ID3D12Resource* upload = create_buffer(D3D12_HEAP_TYPE_UPLOAD, bytes, D3D12_RESOURCE_STATE_GENERIC_READ);
|
||||
ID3D12Resource* readback = create_buffer(D3D12_HEAP_TYPE_READBACK, bytes, D3D12_RESOURCE_STATE_COPY_DEST);
|
||||
if (!check(g_device->CreateCommittedResource(&defaultHeap, D3D12_HEAP_FLAG_NONE, &textureDesc,
|
||||
D3D12_RESOURCE_STATE_COPY_DEST, nullptr, __uuidof(ID3D12Resource),
|
||||
reinterpret_cast<void**>(&texture)),
|
||||
"CreateCommittedResource(texture)") ||
|
||||
upload == nullptr || readback == nullptr)
|
||||
return 1;
|
||||
UINT64 vkImageHandle = 0, unusedOffset = 0;
|
||||
interop->GetVulkanResourceInfo(texture, &vkImageHandle, &unusedOffset);
|
||||
const VkImage vkImage = reinterpret_cast<VkImage>(vkImageHandle);
|
||||
|
||||
uint64_t* mapped = nullptr;
|
||||
D3D12_RANGE none = {0, 0};
|
||||
if (!check(upload->Map(0, &none, reinterpret_cast<void**>(&mapped)), "Map upload"))
|
||||
return 1;
|
||||
for (UINT64 i = 0; i < words; ++i)
|
||||
mapped[i] = pattern(i) & 0x3BFF3BFF3BFF3BFFull; // finite halves: copies must not canonicalize NaNs
|
||||
D3D12_PLACED_SUBRESOURCE_FOOTPRINT footprint = {};
|
||||
footprint.Footprint.Format = textureDesc.Format;
|
||||
footprint.Footprint.Width = width;
|
||||
footprint.Footprint.Height = height;
|
||||
footprint.Footprint.Depth = 1;
|
||||
footprint.Footprint.RowPitch = static_cast<UINT>(pitch);
|
||||
D3D12_TEXTURE_COPY_LOCATION textureLocation = {};
|
||||
textureLocation.pResource = texture;
|
||||
textureLocation.Type = D3D12_TEXTURE_COPY_TYPE_SUBRESOURCE_INDEX;
|
||||
D3D12_TEXTURE_COPY_LOCATION uploadLocation = {};
|
||||
uploadLocation.pResource = upload;
|
||||
uploadLocation.Type = D3D12_TEXTURE_COPY_TYPE_PLACED_FOOTPRINT;
|
||||
uploadLocation.PlacedFootprint = footprint;
|
||||
D3D12_TEXTURE_COPY_LOCATION readbackLocation = uploadLocation;
|
||||
readbackLocation.pResource = readback;
|
||||
g_list->CopyTextureRegion(&textureLocation, 0, 0, 0, &uploadLocation, nullptr);
|
||||
transition(texture, D3D12_RESOURCE_STATE_COPY_DEST, D3D12_RESOURCE_STATE_COPY_SOURCE);
|
||||
if (!flush())
|
||||
return 1;
|
||||
|
||||
VkBufferImageCopy region = {};
|
||||
region.imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
|
||||
region.imageSubresource.layerCount = 1;
|
||||
region.imageExtent = {width, height, 1};
|
||||
VkMemoryBarrier toTransfer = {VK_STRUCTURE_TYPE_MEMORY_BARRIER};
|
||||
toTransfer.srcAccessMask = VK_ACCESS_MEMORY_WRITE_BIT;
|
||||
toTransfer.dstAccessMask = VK_ACCESS_TRANSFER_READ_BIT | VK_ACCESS_TRANSFER_WRITE_BIT;
|
||||
VkMemoryBarrier fromTransfer = {VK_STRUCTURE_TYPE_MEMORY_BARRIER};
|
||||
fromTransfer.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
|
||||
fromTransfer.dstAccessMask = VK_ACCESS_MEMORY_READ_BIT | VK_ACCESS_MEMORY_WRITE_BIT;
|
||||
|
||||
// Texture -> exportable buffer, recorded inside the D3D12 command list.
|
||||
auto recordTextureToBuffer = [&]() {
|
||||
VkCommandBuffer cmd = VK_NULL_HANDLE;
|
||||
if (!check(interop->BeginVkCommandBufferInterop(g_list, &cmd), "BeginVkCommandBufferInterop"))
|
||||
return false;
|
||||
VkImageLayout layout = VK_IMAGE_LAYOUT_UNDEFINED;
|
||||
interop->GetVulkanImageLayout(texture, D3D12_RESOURCE_STATE_COPY_SOURCE, &layout);
|
||||
vkCmdPipelineBarrier(cmd, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, 0, 1, &toTransfer,
|
||||
0, nullptr, 0, nullptr);
|
||||
vkCmdCopyImageToBuffer(cmd, vkImage, layout, vkBuffer, 1, ®ion);
|
||||
vkCmdPipelineBarrier(cmd, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, 0, 1,
|
||||
&fromTransfer, 0, nullptr, 0, nullptr);
|
||||
return check(interop->EndVkCommandBufferInterop(g_list), "EndVkCommandBufferInterop");
|
||||
};
|
||||
if (!recordTextureToBuffer() || !flush())
|
||||
return 1;
|
||||
std::vector<uint64_t> host(words);
|
||||
result = dtoh(host.data(), device, bytes);
|
||||
size_t mismatches = 0;
|
||||
for (UINT64 i = 0; i < words; ++i)
|
||||
mismatches += host[i] != (pattern(i) & 0x3BFF3BFF3BFF3BFFull);
|
||||
std::printf("D3D12 texture -> Vulkan buffer -> CUDA: result %d, %zu of %llu texels mismatched\n", result,
|
||||
mismatches, static_cast<unsigned long long>(words));
|
||||
|
||||
// CUDA write -> buffer -> D3D12 texture (the DLSS output direction).
|
||||
result = memset32(device, 0x3C003800u, bytes / 4);
|
||||
if (result == 0)
|
||||
result = synchronize();
|
||||
transition(texture, D3D12_RESOURCE_STATE_COPY_SOURCE, D3D12_RESOURCE_STATE_COPY_DEST);
|
||||
auto recordBufferToTexture = [&]() {
|
||||
VkCommandBuffer cmd = VK_NULL_HANDLE;
|
||||
if (!check(interop->BeginVkCommandBufferInterop(g_list, &cmd), "BeginVkCommandBufferInterop"))
|
||||
return false;
|
||||
VkImageLayout layout = VK_IMAGE_LAYOUT_UNDEFINED;
|
||||
interop->GetVulkanImageLayout(texture, D3D12_RESOURCE_STATE_COPY_DEST, &layout);
|
||||
vkCmdPipelineBarrier(cmd, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, 0, 1, &toTransfer,
|
||||
0, nullptr, 0, nullptr);
|
||||
vkCmdCopyBufferToImage(cmd, vkBuffer, vkImage, layout, 1, ®ion);
|
||||
vkCmdPipelineBarrier(cmd, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, 0, 1,
|
||||
&fromTransfer, 0, nullptr, 0, nullptr);
|
||||
return check(interop->EndVkCommandBufferInterop(g_list), "EndVkCommandBufferInterop");
|
||||
};
|
||||
if (!recordBufferToTexture())
|
||||
return 1;
|
||||
transition(texture, D3D12_RESOURCE_STATE_COPY_DEST, D3D12_RESOURCE_STATE_COPY_SOURCE);
|
||||
g_list->CopyTextureRegion(&readbackLocation, 0, 0, 0, &textureLocation, nullptr);
|
||||
if (!flush())
|
||||
return 1;
|
||||
uint64_t* back = nullptr;
|
||||
D3D12_RANGE all = {0, static_cast<SIZE_T>(bytes)};
|
||||
readback->Map(0, &all, reinterpret_cast<void**>(&back));
|
||||
size_t reverse = 0;
|
||||
for (UINT64 i = 0; i < words; ++i)
|
||||
reverse += back[i] != 0x3C0038003C003800ull;
|
||||
readback->Unmap(0, nullptr);
|
||||
std::printf("CUDA -> Vulkan buffer -> D3D12 texture: result %d, %zu of %llu texels mismatched\n", result, reverse,
|
||||
static_cast<unsigned long long>(words));
|
||||
|
||||
// Timings: interop copies vs today's texture -> readback copy, and a CUDA
|
||||
// device copy out of the imported buffer.
|
||||
double toBuffer = 1e9, toTexture = 1e9, toReadback = 1e9, cudaCopy = 1e9;
|
||||
for (int i = 0; i < 10; ++i)
|
||||
{
|
||||
auto start = std::chrono::steady_clock::now();
|
||||
recordTextureToBuffer();
|
||||
flush();
|
||||
toBuffer = std::min(toBuffer, elapsed_ms(start));
|
||||
|
||||
transition(texture, D3D12_RESOURCE_STATE_COPY_SOURCE, D3D12_RESOURCE_STATE_COPY_DEST);
|
||||
flush();
|
||||
start = std::chrono::steady_clock::now();
|
||||
recordBufferToTexture();
|
||||
flush();
|
||||
toTexture = std::min(toTexture, elapsed_ms(start));
|
||||
transition(texture, D3D12_RESOURCE_STATE_COPY_DEST, D3D12_RESOURCE_STATE_COPY_SOURCE);
|
||||
flush();
|
||||
|
||||
start = std::chrono::steady_clock::now();
|
||||
g_list->CopyTextureRegion(&readbackLocation, 0, 0, 0, &textureLocation, nullptr);
|
||||
flush();
|
||||
toReadback = std::min(toReadback, elapsed_ms(start));
|
||||
}
|
||||
CUdeviceptr local = 0;
|
||||
if (alloc(&local, bytes) == 0)
|
||||
for (int i = 0; i < 10; ++i)
|
||||
{
|
||||
synchronize();
|
||||
const auto start = std::chrono::steady_clock::now();
|
||||
dtod(local, device, bytes);
|
||||
synchronize();
|
||||
cudaCopy = std::min(cudaCopy, elapsed_ms(start));
|
||||
}
|
||||
std::printf("%ux%u RGBA16F (%.1f MB), best of 10 incl. submit+wait:\n", width, height, bytes / 1048576.0);
|
||||
std::printf(" interop texture -> VRAM buffer: %.3f ms\n", toBuffer);
|
||||
std::printf(" interop VRAM buffer -> texture: %.3f ms\n", toTexture);
|
||||
std::printf(" D3D12 texture -> readback (today): %.3f ms\n", toReadback);
|
||||
std::printf(" CUDA device copy from imported: %.3f ms\n", cudaCopy);
|
||||
const bool ok = mismatches == 0 && reverse == 0;
|
||||
std::printf("zero-copy interop probe: %s\n", ok ? "PASS" : "FAIL");
|
||||
return ok ? 0 : 1;
|
||||
}
|
||||
Reference in new issue
Block a user