447 lines
23 KiB
C++
447 lines
23 KiB
C++
// 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;
|
|
}
|