feat: add d4r inference engines and Windows parity tooling

Add native K/M/RR inference engines, HIP kernels, replay and validation
harnesses, and Windows DLSS setup and runtime support. Include GPU
profiling tools, build and packaging scripts, platform integration
patches, and implementation documentation.
This commit is contained in:
ayo committed 2026-10-16 08:54:47 -04:00
1 parent 25493b3198
commit 7c5d3c9157
430 files changed
+76191

No files matched your search

+446
View File
@@ -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, &region);
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, &region);
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;
}