// Synthetic native-Vulkan NGX integration test. No SDK or Vulkan import library. // usage: vulkan_dlss_harness.exe SHIM_DLL OUTPUT_RAW [FRAMES [discard|invalid|dynamic|recreate|exit]] // exit: the last frame is submitted and the process exits at once, without NGX shutdown. #define VK_NO_PROTOTYPES #include #include "d4r_vulkan_ngx.h" #include #include #include #include extern "C" { void d4r_ngx_set_uint(void*, const char*, unsigned int); void d4r_ngx_set_int(void*, const char*, int); void d4r_ngx_set_float(void*, const char*, float); void d4r_ngx_set_void(void*, const char*, void*); } using Result = unsigned int; struct Handle { unsigned int id; }; using Init = Result (*)(unsigned long long, const wchar_t*, VkInstance, VkPhysicalDevice, VkDevice, PFN_vkGetInstanceProcAddr, PFN_vkGetDeviceProcAddr, const void*, unsigned int); using Params = Result (*)(void**); using Destroy = Result (*)(void*); using Create = Result (*)(VkCommandBuffer, unsigned int, void*, Handle**); using Eval = Result (*)(VkCommandBuffer, const Handle*, void*, void*); using Release = Result (*)(Handle*); using Shutdown = Result (*)(); #define VK_FUNCTIONS(X) \ X(CreateInstance) X(EnumeratePhysicalDevices) X(GetPhysicalDeviceProperties) X(EnumerateDeviceExtensionProperties) \ X(GetPhysicalDeviceQueueFamilyProperties) X(GetPhysicalDeviceMemoryProperties) X(CreateDevice) \ X(GetDeviceProcAddr) X(GetDeviceQueue) X(CreateCommandPool) X(AllocateCommandBuffers) \ X(BeginCommandBuffer) X(EndCommandBuffer) X(ResetCommandPool) X(QueueSubmit) X(QueueWaitIdle) X(CreateFence) X(WaitForFences) X(ResetFences) X(DestroyFence) \ X(CreateImage) X(GetImageMemoryRequirements) X(AllocateMemory) X(BindImageMemory) X(CreateImageView) \ X(CreateBuffer) X(GetBufferMemoryRequirements) X(BindBufferMemory) X(MapMemory) X(UnmapMemory) \ X(CmdPipelineBarrier) X(CmdClearColorImage) X(CmdCopyImageToBuffer) X(DestroyImageView) X(DestroyImage) \ X(DestroyBuffer) X(FreeMemory) X(DestroyCommandPool) X(DestroyDevice) X(DestroyInstance) #define DECLARE(name) static PFN_vk##name vk##name; VK_FUNCTIONS(DECLARE) #undef DECLARE // -DD4R_HARNESS_SCALE=2 runs DOOM's Performance size (1280x720 -> 2560x1440) for timing. #ifndef D4R_HARNESS_SCALE #define D4R_HARNESS_SCALE 1 #endif constexpr uint32_t W = 640 * D4R_HARNESS_SCALE, H = 360 * D4R_HARNESS_SCALE, OW = 2 * W, OH = 2 * H; static VkDevice device; static VkPhysicalDeviceMemoryProperties memory; static void check(VkResult r, const char* name) { if (r != VK_SUCCESS) { std::fprintf(stderr, "%s: %d\n", name, r); std::exit(1); } } static void ngx(Result r, const char* name) { if (r != 1) { std::fprintf(stderr, "%s: 0x%08x\n", name, r); std::exit(1); } } static VkDeviceMemory allocate(VkMemoryRequirements requirements, VkMemoryPropertyFlags flags) { uint32_t type = UINT32_MAX; for (int cached = (flags & VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT) ? 1 : 0; cached >= 0 && type == UINT32_MAX; --cached) for (uint32_t i = 0; i < memory.memoryTypeCount; ++i) if ((requirements.memoryTypeBits & (1u << i)) && (memory.memoryTypes[i].propertyFlags & flags) == flags && (!cached || (memory.memoryTypes[i].propertyFlags & VK_MEMORY_PROPERTY_HOST_CACHED_BIT))) { type = i; break; } if (type == UINT32_MAX) { std::fprintf(stderr, "no memory type\n"); std::exit(1); } VkMemoryAllocateInfo info = {VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO}; info.allocationSize = requirements.size; info.memoryTypeIndex = type; VkDeviceMemory result; check(vkAllocateMemory(device, &info, nullptr, &result), "allocate"); return result; } static NgxVkResource make_image(uint32_t w, uint32_t h, VkFormat format, VkDeviceMemory& allocation) { VkImageCreateInfo info = {VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO}; info.imageType = VK_IMAGE_TYPE_2D; info.format = format; info.extent = {w,h,1}; info.mipLevels = info.arrayLayers = 1; info.samples = VK_SAMPLE_COUNT_1_BIT; info.tiling = VK_IMAGE_TILING_OPTIMAL; info.usage = w == OW && h == OH ? VK_IMAGE_USAGE_STORAGE_BIT | VK_IMAGE_USAGE_TRANSFER_SRC_BIT : VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT; NgxVkResource resource = {}; auto& image = resource.resource.image; check(vkCreateImage(device, &info, nullptr, &image.image), "create image"); VkMemoryRequirements requirements; vkGetImageMemoryRequirements(device, image.image, &requirements); allocation = allocate(requirements, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT); check(vkBindImageMemory(device, image.image, allocation, 0), "bind image"); image.range = {VK_IMAGE_ASPECT_COLOR_BIT,0,1,0,1}; image.format = format; image.width = w; image.height = h; VkImageViewCreateInfo view = {VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO}; view.image = image.image; view.viewType = VK_IMAGE_VIEW_TYPE_2D; view.format = format; view.subresourceRange = image.range; check(vkCreateImageView(device, &view, nullptr, &image.view), "image view"); resource.readWrite = true; return resource; } int main(int argc, char** argv) { if (argc < 3) return 2; if (const char* log = std::getenv("D4R_HARNESS_LOG")) { if (!std::freopen(log, "w", stdout)) return 2; if (!std::freopen(log, "a", stderr)) return 2; } std::setvbuf(stdout, nullptr, _IONBF, 0); std::puts("loading Vulkan and NGX shim"); const unsigned frames = argc > 3 ? std::strtoul(argv[3], nullptr, 10) : 3; const bool discard = argc > 4 && !std::strcmp(argv[4], "discard"); const bool invalid = argc > 4 && !std::strcmp(argv[4], "invalid"); const bool dynamic = argc > 4 && !std::strcmp(argv[4], "dynamic"); const bool recreate = argc > 4 && !std::strcmp(argv[4], "recreate"); const bool exitEarly = argc > 4 && !std::strcmp(argv[4], "exit"); HMODULE vulkan = LoadLibraryA("vulkan-1.dll"), shim = LoadLibraryA(argv[1]); if (!vulkan || !shim) { std::fprintf(stderr,"DLL load: %lu\n",GetLastError()); return 1; } auto ip = reinterpret_cast(reinterpret_cast(GetProcAddress(vulkan,"vkGetInstanceProcAddr"))); #define LOAD(name) vk##name = reinterpret_cast(ip(VK_NULL_HANDLE, "vk" #name)); LOAD(CreateInstance) #undef LOAD VkApplicationInfo app = {VK_STRUCTURE_TYPE_APPLICATION_INFO}; app.apiVersion = VK_API_VERSION_1_1; VkInstanceCreateInfo ci = {VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO}; ci.pApplicationInfo = &app; VkInstance instance; check(vkCreateInstance(&ci,nullptr,&instance),"instance"); #define LOAD(name) vk##name = reinterpret_cast(ip(instance, "vk" #name)); if (!vk##name) return 1; VK_FUNCTIONS(LOAD) #undef LOAD uint32_t count = 0; check(vkEnumeratePhysicalDevices(instance,&count,nullptr),"devices"); std::vector devices(count); check(vkEnumeratePhysicalDevices(instance,&count,devices.data()),"devices"); VkPhysicalDevice physical = VK_NULL_HANDLE; for (auto d : devices) { VkPhysicalDeviceProperties p; vkGetPhysicalDeviceProperties(d,&p); std::printf("Adapter: %s vendor=%04x\n",p.deviceName,p.vendorID); if (p.vendorID == 0x1002 || p.vendorID == 0x10de) { physical=d; std::printf("GPU: %s\n",p.deviceName); break; } } if (!physical) return 1; vkGetPhysicalDeviceMemoryProperties(physical,&memory); vkGetPhysicalDeviceQueueFamilyProperties(physical,&count,nullptr); std::vector families(count); vkGetPhysicalDeviceQueueFamilyProperties(physical,&count,families.data()); uint32_t family = UINT32_MAX; // D4R_HARNESS_COMPUTE_QUEUE=1 records on a compute-only family, as games with async compute DLSS do. const bool computeQueue = std::getenv("D4R_HARNESS_COMPUTE_QUEUE") != nullptr; for (uint32_t i=0;i extensions(count); check(vkEnumerateDeviceExtensionProperties(physical,nullptr,&count,extensions.data()),"extensions"); bool supported = false; for (const auto& e : extensions) supported |= std::strcmp(e.extensionName,externalExtension)==0; if (!supported) { std::fputs("external memory extension unavailable\n",stderr); return 1; } di.enabledExtensionCount=1; di.ppEnabledExtensionNames=&externalExtension; std::puts("enabling external-memory VRAM interop"); } check(vkCreateDevice(physical,&di,nullptr,&device),"device"); auto dp = vkGetDeviceProcAddr; #define LOAD(name) vk##name = reinterpret_cast(dp(device, "vk" #name)); // Instance entry points above remain valid; only load device entry points here. LOAD(GetDeviceQueue) LOAD(QueueSubmit) LOAD(QueueWaitIdle) #undef LOAD #define NGX(name, type) auto name = reinterpret_cast(reinterpret_cast(GetProcAddress(shim,"NVSDK_NGX_VULKAN_" #name))); if (!name) return 1; NGX(Init, ::Init) NGX(GetCapabilityParameters, Params) NGX(DestroyParameters, Destroy) NGX(CreateFeature, Create) NGX(EvaluateFeature, Eval) NGX(ReleaseFeature, Release) NGX(Shutdown, ::Shutdown) #undef NGX std::puts("initializing CUDA NGX through Vulkan frontend"); ngx(Init(100899111ULL,L"Z:\\tmp",instance,physical,device,ip,dp,nullptr,0x14),"init"); void* params = nullptr; ngx(GetCapabilityParameters(¶ms),"capability parameters"); d4r_ngx_set_uint(params,"Width",W); d4r_ngx_set_uint(params,"Height",H); d4r_ngx_set_uint(params,"OutWidth",OW); d4r_ngx_set_uint(params,"OutHeight",OH); d4r_ngx_set_int(params,"PerfQualityValue",2); d4r_ngx_set_int(params,"DLSS.Feature.Create.Flags",0x6b); Handle* handle = nullptr; ngx(CreateFeature(VK_NULL_HANDLE,1,params,&handle),"create"); VkDeviceMemory allocations[4]; NgxVkResource images[] = {make_image(W,H,VK_FORMAT_R16G16B16A16_SFLOAT,allocations[0]), make_image(W,H,VK_FORMAT_R32_SFLOAT,allocations[1]), make_image(W,H,VK_FORMAT_R16G16_SFLOAT,allocations[2]), make_image(OW,OH,VK_FORMAT_R16G16B16A16_SFLOAT,allocations[3])}; const char* names[] = {"Color","Depth","MotionVectors","Output"}; // D4R_HARNESS_DEPTH_ASPECT=1: label the R32F depth image with the depth aspect, as Indiana Jones // (through Streamline/OptiScaler) does; the harness keeps the real colour range for its own barriers. NgxVkResource described[4] = {images[0], images[1], images[2], images[3]}; if (std::getenv("D4R_HARNESS_DEPTH_ASPECT")) described[1].resource.image.range.aspectMask = VK_IMAGE_ASPECT_DEPTH_BIT; for (int i=0;i<4;++i) d4r_ngx_set_void(params,names[i],&described[i]); // D4R_HARNESS_IN_FLIGHT=N: up to N submitted frames at once (own pool, command buffer and fence each), // as a game does; only the final frame is read back. const unsigned inFlight = std::max(1u, std::min(8u, static_cast(std::getenv("D4R_HARNESS_IN_FLIGHT") ? std::atoi(std::getenv("D4R_HARNESS_IN_FLIGHT")) : 1))); VkCommandPool pools[8]; VkCommandBuffer commands[8]; VkFence fences[8]; for (unsigned i=0;i 1 && n+1 < frames) continue; // pipelined: only the last frame is checked check(vkQueueWaitIdle(queue),"wait"); void* data; check(vkMapMemory(device,readMemory,0,VK_WHOLE_SIZE,0,&data),"map"); const auto* values=static_cast(data); unsigned nonzero=0, nonfinite=0; for (unsigned j=0;j