// FSR Ray Regeneration as the native Windows Ray Reconstruction backend (experimental). See fsr_rr_backend.h. // // Data path of one evaluation (every hand-off is CPU-synchronized; no vkd3d-proton patch is needed): // game queue : guides -> shared packet (D3D12 copies, as the native DLSS backend), signal, CPU waits // vkd3d queue: one command list on a private vkd3d-proton device: // packet -> staging images, fsr_rr_prepare (Vulkan, recorded through vkd3d's interop), // AMD ffxDispatch, fsr_rr_compose (Vulkan), composed image -> packet output; CPU waits // game queue : packet output -> the game's output texture, signal, CPU waits // The conversion and composition follow the Linux adapter (reference 34e6b0fe, tools/d4r_fsr_rr_backend.inl). #include "fsr_rr_backend.h" #include "../d4r_fsr_rr.h" #include "../d4r_vkd3d_interop.h" #include "fsr_rr_passes.h" #include "d3d12_command_hooks.h" #include #include "rr_bridge_replay.h" #include #include #include #include #include #include #include #include namespace d4r::win::rr { namespace { using Clock = std::chrono::steady_clock; double since(Clock::time_point start) { return std::chrono::duration(Clock::now() - start).count(); } bool log_enabled() { static const bool on = std::getenv("D4R_FSR_RR_LOG") != nullptr; return on; } LONG WINAPI crash_report(EXCEPTION_POINTERS* info) { const auto code = info->ExceptionRecord->ExceptionCode; if (code != 0xC0000005 && code != 0xC000001D && code != 0xC0000094 && code != 0xC00000FD) return EXCEPTION_CONTINUE_SEARCH; char name[MAX_PATH] = "?"; HMODULE module = nullptr; const void* at = info->ExceptionRecord->ExceptionAddress; if (GetModuleHandleExA(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS | GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT, static_cast(at), &module)) GetModuleFileNameA(module, name, MAX_PATH); std::fprintf(stderr, "FSR_RR crash: code=%08lx at %p (%s +0x%llx) info0=%llu info1=%p\n", code, at, name, (unsigned long long)(static_cast(at) - reinterpret_cast(module)), (unsigned long long)info->ExceptionRecord->ExceptionInformation[0], (void*)info->ExceptionRecord->ExceptionInformation[1]); std::fflush(stderr); return EXCEPTION_CONTINUE_SEARCH; } // The denoiser finds amdxc64.dll with GetModuleHandle, so in a process whose game device already loaded AMD's real // amdxc64.dll (it always has: AMD's D3D12 driver does), the name resolves to AMD's driver component, which crashes // on a vkd3d device. The loaded copy of AMD's DLL gets its GetModuleHandleA/W imports redirected in memory so that // the name resolves to d4r's shim; every other lookup is forwarded. The DLL on disk is never modified. HMODULE g_shimModule = nullptr; HMODULE WINAPI module_handle_a(LPCSTR name) { if (name && g_shimModule && !_stricmp(name, "amdxc64.dll")) return g_shimModule; return GetModuleHandleA(name); } HMODULE WINAPI module_handle_w(LPCWSTR name) { if (name && g_shimModule && !_wcsicmp(name, L"amdxc64.dll")) return g_shimModule; return GetModuleHandleW(name); } bool redirect_amdxc(HMODULE module, HMODULE shim) { g_shimModule = shim; auto* base = reinterpret_cast(module); auto* dos = reinterpret_cast(base); auto* nt = reinterpret_cast(base + dos->e_lfanew); const auto& directory = nt->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT]; if (!directory.VirtualAddress) return false; unsigned patched = 0; for (auto* descriptor = reinterpret_cast(base + directory.VirtualAddress); descriptor->Name; ++descriptor) { if (_stricmp(reinterpret_cast(base + descriptor->Name), "KERNEL32.dll") != 0) continue; auto* lookup = reinterpret_cast(base + (descriptor->OriginalFirstThunk ? descriptor->OriginalFirstThunk : descriptor->FirstThunk)); auto* address = reinterpret_cast(base + descriptor->FirstThunk); for (; lookup->u1.AddressOfData; ++lookup, ++address) { if (lookup->u1.Ordinal & IMAGE_ORDINAL_FLAG) continue; const char* function = reinterpret_cast(base + lookup->u1.AddressOfData)->Name; void* replacement = !std::strcmp(function, "GetModuleHandleA") ? reinterpret_cast(&module_handle_a) : !std::strcmp(function, "GetModuleHandleW") ? reinterpret_cast(&module_handle_w) : nullptr; if (!replacement) continue; DWORD old = 0; if (!VirtualProtect(address, sizeof(void*), PAGE_READWRITE, &old)) return false; *address = replacement; ++patched; VirtualProtect(address, sizeof(void*), old, &old); } } return patched != 0; } void trace(const char* what) { if (log_enabled()) { std::fprintf(stderr, "FSR_RR step: %s\n", what); std::fflush(stderr); } } std::wstring widen(const char* text) { if (!text || !*text) return {}; const int n = MultiByteToWideChar(CP_UTF8, 0, text, -1, nullptr, 0); std::wstring out(size_t(n > 0 ? n - 1 : 0), L'\0'); if (n > 1) MultiByteToWideChar(CP_UTF8, 0, text, -1, out.data(), n); return out; } std::wstring env_wide(const char* name) { return widen(std::getenv(name)); } std::string narrow(const std::wstring& text) { std::string out(size_t(WideCharToMultiByte(CP_UTF8, 0, text.c_str(), -1, nullptr, 0, nullptr, nullptr)), '\0'); if (!out.empty()) { WideCharToMultiByte(CP_UTF8, 0, text.c_str(), -1, out.data(), int(out.size()), nullptr, nullptr); out.pop_back(); } return out; } // The vkd3d-proton core interface (include/vkd3d_core_interface.idl). The IDL declares no base interface, so the // vtable starts at CreateDevice: slot 0, not 3. const GUID CLSID_Core = {0xed53efad, 0xda21, 0x4d96, {0xa1, 0xbc, 0xe7, 0x34, 0xe0, 0x78, 0x87, 0x9c}}; const GUID IID_CoreInterface = {0x18da885c, 0xa7b5, 0x464e, {0xa1, 0x21, 0xcc, 0xb7, 0x5d, 0x4d, 0xfc, 0x31}}; struct CoreInterface { struct Vtbl { HRESULT(STDMETHODCALLTYPE* createDevice)(CoreInterface*, IUnknown*, D3D_FEATURE_LEVEL, REFIID, void**); }; const Vtbl* vtbl; }; // Row-major, row-vector 4x4 helpers (the NGX/FFX convention), in double. From the Linux adapter. bool invert(const float (&m)[16], double (&out)[16]) { double a[16]; for (int i = 0; i < 16; ++i) a[i] = m[i]; double inv[16]; inv[0] = a[5] * a[10] * a[15] - a[5] * a[11] * a[14] - a[9] * a[6] * a[15] + a[9] * a[7] * a[14] + a[13] * a[6] * a[11] - a[13] * a[7] * a[10]; inv[4] = -a[4] * a[10] * a[15] + a[4] * a[11] * a[14] + a[8] * a[6] * a[15] - a[8] * a[7] * a[14] - a[12] * a[6] * a[11] + a[12] * a[7] * a[10]; inv[8] = a[4] * a[9] * a[15] - a[4] * a[11] * a[13] - a[8] * a[5] * a[15] + a[8] * a[7] * a[13] + a[12] * a[5] * a[11] - a[12] * a[7] * a[9]; inv[12] = -a[4] * a[9] * a[14] + a[4] * a[10] * a[13] + a[8] * a[5] * a[14] - a[8] * a[6] * a[13] - a[12] * a[5] * a[10] + a[12] * a[6] * a[9]; inv[1] = -a[1] * a[10] * a[15] + a[1] * a[11] * a[14] + a[9] * a[2] * a[15] - a[9] * a[3] * a[14] - a[13] * a[2] * a[11] + a[13] * a[3] * a[10]; inv[5] = a[0] * a[10] * a[15] - a[0] * a[11] * a[14] - a[8] * a[2] * a[15] + a[8] * a[3] * a[14] + a[12] * a[2] * a[11] - a[12] * a[3] * a[10]; inv[9] = -a[0] * a[9] * a[15] + a[0] * a[11] * a[13] + a[8] * a[1] * a[15] - a[8] * a[3] * a[13] - a[12] * a[1] * a[11] + a[12] * a[3] * a[9]; inv[13] = a[0] * a[9] * a[14] - a[0] * a[10] * a[13] - a[8] * a[1] * a[14] + a[8] * a[2] * a[13] + a[12] * a[1] * a[10] - a[12] * a[2] * a[9]; inv[2] = a[1] * a[6] * a[15] - a[1] * a[7] * a[14] - a[5] * a[2] * a[15] + a[5] * a[3] * a[14] + a[13] * a[2] * a[7] - a[13] * a[3] * a[6]; inv[6] = -a[0] * a[6] * a[15] + a[0] * a[7] * a[14] + a[4] * a[2] * a[15] - a[4] * a[3] * a[14] - a[12] * a[2] * a[7] + a[12] * a[3] * a[6]; inv[10] = a[0] * a[5] * a[15] - a[0] * a[7] * a[13] - a[4] * a[1] * a[15] + a[4] * a[3] * a[13] + a[12] * a[1] * a[7] - a[12] * a[3] * a[5]; inv[14] = -a[0] * a[5] * a[14] + a[0] * a[6] * a[13] + a[4] * a[1] * a[14] - a[4] * a[2] * a[13] - a[12] * a[1] * a[6] + a[12] * a[2] * a[5]; inv[3] = -a[1] * a[6] * a[11] + a[1] * a[7] * a[10] + a[5] * a[2] * a[11] - a[5] * a[3] * a[10] - a[9] * a[2] * a[7] + a[9] * a[3] * a[6]; inv[7] = a[0] * a[6] * a[11] - a[0] * a[7] * a[10] - a[4] * a[2] * a[11] + a[4] * a[3] * a[10] + a[8] * a[2] * a[7] - a[8] * a[3] * a[6]; inv[11] = -a[0] * a[5] * a[11] + a[0] * a[7] * a[9] + a[4] * a[1] * a[11] - a[4] * a[3] * a[9] - a[8] * a[1] * a[7] + a[8] * a[3] * a[5]; inv[15] = a[0] * a[5] * a[10] - a[0] * a[6] * a[9] - a[4] * a[1] * a[10] + a[4] * a[2] * a[9] + a[8] * a[1] * a[6] - a[8] * a[2] * a[5]; const double det = a[0] * inv[0] + a[1] * inv[4] + a[2] * inv[8] + a[3] * inv[12]; if (!std::isfinite(det) || std::fabs(det) < 1e-30) return false; for (int i = 0; i < 16; ++i) { out[i] = inv[i] / det; if (!std::isfinite(out[i])) return false; } return true; } void multiply(const double (&a)[16], const double (&b)[16], double (&out)[16]) { for (int r = 0; r < 4; ++r) for (int c = 0; c < 4; ++c) { double sum = 0; for (int k = 0; k < 4; ++k) sum += a[r * 4 + k] * b[k * 4 + c]; out[r * 4 + c] = sum; } } bool unorm(VkFormat format) { return format == VK_FORMAT_R8G8B8A8_UNORM || format == VK_FORMAT_A2B10G10R10_UNORM_PACK32; } size_t resource_index(size_t plane) { return plane < 7 ? plane : plane + 2; } // Same sampled-input formats as the native backend: the texel values reach Vulkan unchanged. rr_bridge::Plane input_format(DXGI_FORMAT format, size_t plane) { if (plane == Snapshot::Motion) { if (format == DXGI_FORMAT_R16G16_FLOAT) return {nullptr, VK_FORMAT_R16G16_SFLOAT, 4}; if (format == DXGI_FORMAT_R32G32_FLOAT) return {nullptr, VK_FORMAT_R32G32_SFLOAT, 8}; } else if (plane == Snapshot::Depth || plane == Snapshot::HitDistance || plane == Snapshot::Disocclusion || plane == Snapshot::Subsurface) { if (plane == Snapshot::Depth && (format == DXGI_FORMAT_R32_TYPELESS || format == DXGI_FORMAT_D32_FLOAT || format == DXGI_FORMAT_R32G8X24_TYPELESS || format == DXGI_FORMAT_D32_FLOAT_S8X24_UINT || format == DXGI_FORMAT_R32_FLOAT_X8X24_TYPELESS)) return {nullptr, VK_FORMAT_R32_SFLOAT, 4}; if (format == DXGI_FORMAT_R32_FLOAT) return {nullptr, VK_FORMAT_R32_SFLOAT, 4}; if (format == DXGI_FORMAT_R16_FLOAT) return {nullptr, VK_FORMAT_R16_SFLOAT, 2}; if (plane != Snapshot::Depth && format == DXGI_FORMAT_R8_UNORM) return {nullptr, VK_FORMAT_R8_UNORM, 1}; } else { if (format == DXGI_FORMAT_R16G16B16A16_FLOAT) return {nullptr, VK_FORMAT_R16G16B16A16_SFLOAT, 8}; if (format == DXGI_FORMAT_R32G32B32A32_FLOAT) return {nullptr, VK_FORMAT_R32G32B32A32_SFLOAT, 16}; if (format == DXGI_FORMAT_R8G8B8A8_UNORM) return {nullptr, VK_FORMAT_R8G8B8A8_UNORM, 4}; if (format == DXGI_FORMAT_R10G10B10A2_UNORM) return {nullptr, VK_FORMAT_A2B10G10R10_UNORM_PACK32, 4}; if (plane == Snapshot::Roughness) { if (format == DXGI_FORMAT_R16_FLOAT) return {nullptr, VK_FORMAT_R16_SFLOAT, 2}; if (format == DXGI_FORMAT_R32_FLOAT) return {nullptr, VK_FORMAT_R32_SFLOAT, 4}; if (format == DXGI_FORMAT_R8_UNORM) return {nullptr, VK_FORMAT_R8_UNORM, 1}; } } throw std::runtime_error("Unsupported FSR RR sampled input format"); } DXGI_FORMAT copy_format(ID3D12Device* device, ID3D12Resource* texture, size_t plane) { const auto desc = resource_desc(texture); if (plane != Snapshot::Depth) return desc.Format; D3D12_PLACED_SUBRESOURCE_FOOTPRINT footprint{}; UINT rows = 0; UINT64 rowBytes = 0, bytes = 0; device->GetCopyableFootprints(&desc, 0, 1, 0, &footprint, &rows, &rowBytes, &bytes); const auto format = input_format(desc.Format, plane); if (!bytes || bytes == UINT64_MAX || rows != desc.Height || rowBytes != desc.Width * format.bytes || footprint.Footprint.Width != desc.Width || footprint.Footprint.Height != desc.Height) throw std::runtime_error("Unsupported FSR RR depth-plane footprint"); return footprint.Footprint.Format; } // The Vulkan entry points used on vkd3d-proton's VkDevice. Loaded from the loader by instance/device, never through // volk's globals, which belong to the native backend's own devices. struct VkApi { PFN_vkGetDeviceProcAddr deviceProc = nullptr; PFN_vkGetPhysicalDeviceMemoryProperties memoryProperties = nullptr; PFN_vkGetPhysicalDeviceFormatProperties formatProperties = nullptr; PFN_vkCreateBuffer createBuffer = nullptr; PFN_vkDestroyBuffer destroyBuffer = nullptr; PFN_vkGetBufferMemoryRequirements bufferRequirements = nullptr; PFN_vkAllocateMemory allocate = nullptr; PFN_vkFreeMemory free = nullptr; PFN_vkBindBufferMemory bindBuffer = nullptr; PFN_vkCreateImage createImage = nullptr; PFN_vkDestroyImage destroyImage = nullptr; PFN_vkGetImageMemoryRequirements imageRequirements = nullptr; PFN_vkBindImageMemory bindImage = nullptr; PFN_vkCmdCopyBufferToImage copyBufferToImage = nullptr; PFN_vkCmdCopyImageToBuffer copyImageToBuffer = nullptr; PFN_vkCmdBlitImage blitImage = nullptr; PFN_vkCmdPipelineBarrier barrier = nullptr; PFN_vkGetMemoryWin32HandlePropertiesKHR handleProperties = nullptr; PFN_vkDeviceWaitIdle waitIdle = nullptr; }; struct StagingImage { VkImage image = VK_NULL_HANDLE; VkDeviceMemory memory = VK_NULL_HANDLE; VkImageView view = VK_NULL_HANDLE; VkFormat format = VK_FORMAT_UNDEFINED; }; struct Intermediate { ComPtr resource; D3D12_RESOURCE_DESC desc{}; D3D12_RESOURCE_STATES state = D3D12_RESOURCE_STATE_UNORDERED_ACCESS; VkImageView view = VK_NULL_HANDLE; VkImageLayout layout = VK_IMAGE_LAYOUT_GENERAL; VkImage image = VK_NULL_HANDLE; }; enum Tex { LinearDepth, MotionT, NormalsT, DiffuseAlbedo, SpecularAlbedo, DiffuseT, SpecularT, DiffuseOut, SpecularOut, Residual, Composed, Widened, TexCount }; std::mutex hostMutex; } FsrConfig FsrConfig::from_environment() { FsrConfig config; config.vkd3dCore = env_wide("D4R_FSR_VKD3D_CORE"); config.denoiserDll = env_wide("D4R_FSR_RR_DLL"); config.amdxcShim = env_wide("D4R_FSR_AMDXC_SHIM"); config.cacheDir = env_wide("D4R_FSR_CACHE_DIR"); return config; } struct FsrBackend::Impl { Creation creation; rr_bridge::Layout layout; ComPtr d3d; // the game's device ComPtr queue; ComPtr shared; ComPtr fence; ComPtr producerAllocator, consumerAllocator; ComPtr producer, consumer; Handle event; uint64_t sequence = 0; bool pending = false, failed = false; // --- private vkd3d-proton side --- HMODULE core = nullptr; ComPtr vdev; ComPtr vqueue; ComPtr vallocator; ComPtr vlist; ComPtr vfence; Handle vevent; uint64_t vsequence = 0; ComPtr interop; VkInstance vkInstance = VK_NULL_HANDLE; VkPhysicalDevice vkPhysical = VK_NULL_HANDLE; VkDevice vk = VK_NULL_HANDLE; VkQueue vkQueue = VK_NULL_HANDLE; uint32_t vkFamily = 0; VkApi api; D4rFsrRrPasses passes; FsrRrRuntime runtime; ffxContext context = nullptr; uint32_t signals = 0; VkBuffer packetBuffer = VK_NULL_HANDLE; VkDeviceMemory packetMemory = VK_NULL_HANDLE; std::array staging{}; std::array textures{}; float previousView[16] = {}; bool havePrevious = false; uint32_t frameIndex = 0; FsrConfig config; HMODULE shimModule = nullptr; FsrBackend::SuperSampling superSampling; std::array srRenderLimits{}; // min width/height, max width/height from the SR library // D4R_FSR_RR_LOG: GPU timestamps of the vkd3d list (begin, after prepare, after denoise, end) ComPtr stamps; ComPtr stampReadback; UINT64 stampFrequency = 0; void stamp(UINT index) { if (stamps) vlist->EndQuery(stamps.Get(), D3D12_QUERY_TYPE_TIMESTAMP, index); } ComPtr denoised; // game-device copy of the denoised colour, the Super Sampling stage's input D3D12_RESOURCE_STATES denoisedState = D3D12_RESOURCE_STATE_COMMON; Impl(ID3D12Device* device, const Creation& c, const FsrConfig& cfg) : creation(c), layout(c.width, c.height, c.outWidth, c.outHeight), d3d(device), config(cfg) { // Reserve each guide's largest texel size once, as the native backend does, so format changes keep the packet. VkDeviceSize offset = 0; for (auto& plane : layout.planes) { plane.offset = (offset + 511) & ~VkDeviceSize(511); plane.pitch = (VkDeviceSize(layout.rw) * 16 + 255) & ~VkDeviceSize(255); offset = plane.offset + plane.pitch * layout.rh; } layout.outputOffset = (offset + 511) & ~VkDeviceSize(511); layout.outputPitch = (VkDeviceSize(layout.ow) * 16 + 255) & ~VkDeviceSize(255); layout.bytes = layout.outputOffset + layout.outputPitch * layout.oh; } ~Impl() { if (vk && api.waitIdle) (void)api.waitIdle(vk); if (vqueue && vfence) { const uint64_t value = ++vsequence; if (SUCCEEDED(vqueue->Signal(vfence.Get(), value)) && SUCCEEDED(vfence->SetEventOnCompletion(value, vevent.value))) WaitForSingleObject(vevent.value, 10000); } if (context) runtime.destroyContext(&context, nullptr); if (vk) { for (auto& s : staging) destroy_staging(s); for (auto& t : textures) if (t.view) passes.destroyView(vk, t.view, nullptr); passes.destroy(); if (packetBuffer) api.destroyBuffer(vk, packetBuffer, nullptr); if (packetMemory) api.free(vk, packetMemory, nullptr); } // vkd3d-proton's DLL stays loaded for the process: its device objects are released by their ComPtrs. } void destroy_staging(StagingImage& s) { if (s.view) passes.destroyView(vk, s.view, nullptr); if (s.image) api.destroyImage(vk, s.image, nullptr); if (s.memory) api.free(vk, s.memory, nullptr); s = StagingImage{}; } uint32_t memory_type(uint32_t bits, VkMemoryPropertyFlags flags) { VkPhysicalDeviceMemoryProperties properties{}; api.memoryProperties(vkPhysical, &properties); for (uint32_t i = 0; i < properties.memoryTypeCount; ++i) if ((bits & (1u << i)) && (properties.memoryTypes[i].propertyFlags & flags) == flags) return i; throw std::runtime_error("No compatible FSR RR Vulkan memory type"); } void check(VkResult r, const char* what) { if (r != VK_SUCCESS) throw std::runtime_error(std::string(what) + " VkResult=" + std::to_string(r)); } void initialize() { if (!d3d) throw std::runtime_error("Null FSR RR D3D12 device"); if ((creation.flags & 3u) != 3u) throw std::runtime_error("FSR RR requires HDR and render-resolution motion vectors"); if (creation.flags & 4u) throw std::runtime_error("FSR RR does not convert jittered motion vectors"); if (creation.quality < 0 || creation.quality > 5) throw std::runtime_error("Unsupported RR quality mode"); if (config.vkd3dCore.empty() || config.denoiserDll.empty() || config.amdxcShim.empty()) throw std::runtime_error("FSR RR needs D4R_FSR_VKD3D_CORE, D4R_FSR_RR_DLL and D4R_FSR_AMDXC_SHIM"); // --- the game-device packet, fence and copy lists (as the native backend) --- shared = make_buffer(d3d.Get(), layout.bytes, D3D12_HEAP_TYPE_DEFAULT, D3D12_HEAP_FLAG_SHARED, D3D12_RESOURCE_STATE_COMMON); dx(d3d->CreateFence(0, D3D12_FENCE_FLAG_NONE, IID_PPV_ARGS(&fence)), "FSR RR fence"); event.value = CreateEventW(nullptr, FALSE, FALSE, nullptr); vevent.value = CreateEventW(nullptr, FALSE, FALSE, nullptr); if (!event.value || !vevent.value) throw std::runtime_error("FSR RR event creation failed"); auto createList = [&](ID3D12Device* device, auto& allocator, auto& list) { dx(device->CreateCommandAllocator(D3D12_COMMAND_LIST_TYPE_DIRECT, IID_PPV_ARGS(&allocator)), "FSR RR allocator"); dx(device->CreateCommandList(0, D3D12_COMMAND_LIST_TYPE_DIRECT, allocator.Get(), nullptr, IID_PPV_ARGS(&list)), "FSR RR command list"); dx(list->Close(), "FSR RR initial close"); }; createList(d3d.Get(), producerAllocator, producer); createList(d3d.Get(), consumerAllocator, consumer); Handle memoryHandle; dx(d3d->CreateSharedHandle(shared.Get(), nullptr, GENERIC_ALL, nullptr, &memoryHandle.value), "FSR RR packet handle"); if (log_enabled()) AddVectoredExceptionHandler(1, crash_report); trace("before start_vkd3d"); start_vkd3d(); trace("before import_packet"); import_packet(memoryHandle.value); trace("before vkd3d list"); createList(vdev.Get(), vallocator, vlist); trace("before create_intermediates"); create_intermediates(); trace("before create_context"); create_context(); } void start_vkd3d() { // vkd3d-proton's own environment: the dxil-spirv RDNA3 WMMA/FP8 emulation, and a cache for its shaders. SetEnvironmentVariableA("DXIL_SPIRV_CONFIG", "wmma_rdna3_workaround"); if (!config.cacheDir.empty() && !std::getenv("VKD3D_SHADER_CACHE_PATH")) { CreateDirectoryW(config.cacheDir.c_str(), nullptr); SetEnvironmentVariableW(L"VKD3D_SHADER_CACHE_PATH", config.cacheDir.c_str()); } // The denoiser consults amdxc64.dll only when it is already loaded: ours, never AMD's. shimModule = LoadLibraryW(config.amdxcShim.c_str()); if (!shimModule) throw std::runtime_error("FSR RR amdxc64 shim could not be loaded"); core = LoadLibraryExW(config.vkd3dCore.c_str(), nullptr, LOAD_WITH_ALTERED_SEARCH_PATH); if (!core) throw std::runtime_error("FSR RR vkd3d-proton core could not be loaded"); using GetInterface = HRESULT(WINAPI*)(REFCLSID, REFIID, void**); auto getInterface = reinterpret_cast(reinterpret_cast(GetProcAddress(core, "D3D12GetInterface"))); CoreInterface* iface = nullptr; if (!getInterface || FAILED(getInterface(CLSID_Core, IID_CoreInterface, reinterpret_cast(&iface))) || !iface) throw std::runtime_error("FSR RR vkd3d-proton core interface unavailable"); ComPtr factory; dx(CreateDXGIFactory1(IID_PPV_ARGS(&factory)), "FSR RR DXGI factory"); ComPtr adapter; dx(factory->EnumAdapterByLuid(adapter_luid(d3d.Get()), IID_PPV_ARGS(&adapter)), "FSR RR adapter"); dx(iface->vtbl->createDevice(iface, adapter.Get(), D3D_FEATURE_LEVEL_12_0, IID_PPV_ARGS(&vdev)), "FSR RR vkd3d device"); trace("vkd3d device created"); D3D12_COMMAND_QUEUE_DESC queueDesc{}; queueDesc.Type = D3D12_COMMAND_LIST_TYPE_DIRECT; dx(vdev->CreateCommandQueue(&queueDesc, IID_PPV_ARGS(&vqueue)), "FSR RR vkd3d queue"); dx(vdev->CreateFence(0, D3D12_FENCE_FLAG_NONE, IID_PPV_ARGS(&vfence)), "FSR RR vkd3d fence"); if (log_enabled()) { D3D12_QUERY_HEAP_DESC heap{}; heap.Type = D3D12_QUERY_HEAP_TYPE_TIMESTAMP; heap.Count = 4; if (SUCCEEDED(vdev->CreateQueryHeap(&heap, IID_PPV_ARGS(&stamps))) && SUCCEEDED(vqueue->GetTimestampFrequency(&stampFrequency)) && stampFrequency) stampReadback = make_buffer(vdev.Get(), 4 * sizeof(UINT64), D3D12_HEAP_TYPE_READBACK, D3D12_HEAP_FLAG_NONE, D3D12_RESOURCE_STATE_COPY_DEST); else stamps.Reset(); } trace("vkd3d queue and fence created"); dx(vdev->QueryInterface(IID_PPV_ARGS(&interop)), "FSR RR vkd3d Vulkan interop"); dx(interop->GetVulkanHandles(&vkInstance, &vkPhysical, &vk), "FSR RR Vulkan handles"); dx(interop->GetVulkanQueueInfo(vqueue.Get(), &vkQueue, &vkFamily), "FSR RR Vulkan queue info"); trace("interop and handles ok"); HMODULE vulkan = GetModuleHandleA("vulkan-1.dll"); if (!vulkan) vulkan = LoadLibraryA("vulkan-1.dll"); auto instanceProc = vulkan ? reinterpret_cast(reinterpret_cast(GetProcAddress(vulkan, "vkGetInstanceProcAddr"))) : nullptr; if (!instanceProc) throw std::runtime_error("FSR RR Vulkan loader unavailable"); api.deviceProc = reinterpret_cast(instanceProc(vkInstance, "vkGetDeviceProcAddr")); api.memoryProperties = reinterpret_cast(instanceProc(vkInstance, "vkGetPhysicalDeviceMemoryProperties")); api.formatProperties = reinterpret_cast(instanceProc(vkInstance, "vkGetPhysicalDeviceFormatProperties")); auto physicalProperties = reinterpret_cast(instanceProc(vkInstance, "vkGetPhysicalDeviceProperties")); if (!api.deviceProc || !api.memoryProperties || !api.formatProperties || !physicalProperties) throw std::runtime_error("FSR RR Vulkan entry points missing"); #define D4R_VK(member, name) api.member = reinterpret_cast(api.deviceProc(vk, "vk" #name)); \ if (!api.member) throw std::runtime_error("FSR RR Vulkan entry point missing: vk" #name) D4R_VK(createBuffer, CreateBuffer); D4R_VK(destroyBuffer, DestroyBuffer); D4R_VK(bufferRequirements, GetBufferMemoryRequirements); D4R_VK(allocate, AllocateMemory); D4R_VK(free, FreeMemory); D4R_VK(bindBuffer, BindBufferMemory); D4R_VK(createImage, CreateImage); D4R_VK(destroyImage, DestroyImage); D4R_VK(imageRequirements, GetImageMemoryRequirements); D4R_VK(bindImage, BindImageMemory); D4R_VK(copyBufferToImage, CmdCopyBufferToImage); D4R_VK(copyImageToBuffer, CmdCopyImageToBuffer); D4R_VK(blitImage, CmdBlitImage); D4R_VK(barrier, CmdPipelineBarrier); D4R_VK(handleProperties, GetMemoryWin32HandlePropertiesKHR); D4R_VK(waitIdle, DeviceWaitIdle); #undef D4R_VK trace("vulkan entry points loaded"); VkPhysicalDeviceProperties limits{}; physicalProperties(vkPhysical, &limits); if (!passes.init(vk, api.deviceProc, limits.limits.maxPushConstantsSize)) throw std::runtime_error("FSR RR conversion passes could not be created"); trace("passes created"); if (!runtime.load(config.denoiserDll)) throw std::runtime_error(std::string("FSR RR denoiser DLL: ") + fsr_rr_stage_name(runtime.stage)); if (!redirect_amdxc(runtime.module, shimModule)) throw std::runtime_error("FSR RR could not redirect the denoiser's amdxc64 lookup"); trace("amdxc64 lookup redirected to the d4r shim"); trace("before message callback"); runtime.set_message_callback([](uint32_t type, const wchar_t* message) { std::fprintf(stderr, "FSR RR runtime %s: %ls\n", type == FFX_API_MESSAGE_TYPE_WARNING ? "warning" : "error", message); }, FFX_API_CONFIGURE_GLOBALDEBUG_LEVEL_WARNINGS); trace("denoiser dll loaded"); trace("before query_providers"); if (!runtime.query_providers(vdev.Get())) throw std::runtime_error(std::string("FSR RR provider: ") + fsr_rr_stage_name(runtime.stage)); trace("providers queried"); if (log_enabled()) std::fprintf(stderr, "FSR_RR provider \"%s\" on vkd3d-proton\n", runtime.providerName.c_str()); } void import_packet(HANDLE resource) { VkExternalMemoryBufferCreateInfo external{VK_STRUCTURE_TYPE_EXTERNAL_MEMORY_BUFFER_CREATE_INFO}; external.handleTypes = VK_EXTERNAL_MEMORY_HANDLE_TYPE_D3D12_RESOURCE_BIT; VkBufferCreateInfo bi{VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO, &external}; bi.size = layout.bytes; bi.usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT; check(api.createBuffer(vk, &bi, nullptr, &packetBuffer), "FSR RR packet buffer"); VkMemoryRequirements req{}; api.bufferRequirements(vk, packetBuffer, &req); VkMemoryWin32HandlePropertiesKHR handleProperties{VK_STRUCTURE_TYPE_MEMORY_WIN32_HANDLE_PROPERTIES_KHR}; check(api.handleProperties(vk, VK_EXTERNAL_MEMORY_HANDLE_TYPE_D3D12_RESOURCE_BIT, resource, &handleProperties), "FSR RR packet memory types"); const uint32_t type = memory_type(req.memoryTypeBits & handleProperties.memoryTypeBits, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT); VkImportMemoryWin32HandleInfoKHR imported{VK_STRUCTURE_TYPE_IMPORT_MEMORY_WIN32_HANDLE_INFO_KHR}; imported.handleType = VK_EXTERNAL_MEMORY_HANDLE_TYPE_D3D12_RESOURCE_BIT; imported.handle = resource; VkMemoryDedicatedAllocateInfo dedicated{VK_STRUCTURE_TYPE_MEMORY_DEDICATED_ALLOCATE_INFO, &imported}; dedicated.buffer = packetBuffer; VkMemoryAllocateInfo ai{VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO, &dedicated}; ai.allocationSize = req.size; ai.memoryTypeIndex = type; check(api.allocate(vk, &ai, nullptr, &packetMemory), "FSR RR packet import"); check(api.bindBuffer(vk, packetBuffer, packetMemory, 0), "FSR RR packet bind"); } void create_intermediate(int i) { auto& t = textures[i]; t.desc = {}; t.desc.Dimension = D3D12_RESOURCE_DIMENSION_TEXTURE2D; t.desc.Width = creation.width; t.desc.Height = creation.height; t.desc.DepthOrArraySize = 1; t.desc.MipLevels = 1; t.desc.Format = i == LinearDepth ? DXGI_FORMAT_R32_FLOAT : i == Widened ? DXGI_FORMAT_R32G32B32A32_FLOAT : DXGI_FORMAT_R16G16B16A16_FLOAT; t.desc.SampleDesc.Count = 1; t.desc.Flags = D3D12_RESOURCE_FLAG_ALLOW_UNORDERED_ACCESS; D3D12_HEAP_PROPERTIES heap{}; heap.Type = D3D12_HEAP_TYPE_DEFAULT; dx(vdev->CreateCommittedResource(&heap, D3D12_HEAP_FLAG_NONE, &t.desc, t.state, nullptr, IID_PPV_ARGS(&t.resource)), "FSR RR intermediate texture"); UINT64 handle = 0, offset = 0; VkFormat format = VK_FORMAT_UNDEFINED; dx(interop->GetVulkanResourceInfo1(t.resource.Get(), &handle, &offset, &format), "FSR RR intermediate Vulkan info"); if (!handle) throw std::runtime_error("FSR RR intermediate has no Vulkan image"); t.image = reinterpret_cast(handle); interop->GetVulkanImageLayout(t.resource.Get(), D3D12_RESOURCE_STATE_UNORDERED_ACCESS, &t.layout); VkImageViewCreateInfo info{VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO}; info.image = t.image; info.viewType = VK_IMAGE_VIEW_TYPE_2D; info.format = format; info.subresourceRange = {VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1}; check(passes.createView(vk, &info, nullptr, &t.view), "FSR RR intermediate view"); } void create_intermediates() { // The float output image is needed only for an FSR-only RGBA32F output. for (int i = 0; i < Widened; ++i) create_intermediate(i); } // AMD's tweakable settings (FfxApiConfigureDenoiserKey 1-6). Defaults are logged; D4R_FSR_RR_KEY_= // overrides key n (diagnostics) and D4R_FSR_RR_STABILITY_BIAS ([RayReconstruction] FsrStabilityBias) sets key 2. // AMD's default stability bias of 1 weights history heavily, which blurs fine detail on some content. void apply_denoiser_keys() { static const char* const names[7] = {"", "cross bilateral normal strength", "stability bias", "max radiance", "radiance clip std k", "gaussian kernel relaxation", "disocclusion threshold"}; std::string defaults; for (uint64_t key = 1; key <= 6; ++key) { float value = 0.0f; ffxQueryDescDenoiserGetDefaultKeyValue query = {}; query.header.type = FFX_API_QUERY_DESC_TYPE_DENOISER_GET_DEFAULT_KEYVALUE; query.key = key; query.count = 1; query.data = &value; char text[96]; if (runtime.query(&context, &query.header) == FFX_API_RETURN_OK) std::snprintf(text, sizeof(text), "%s%s %g", defaults.empty() ? "" : ", ", names[key], value); else std::snprintf(text, sizeof(text), "%s%s ?", defaults.empty() ? "" : ", ", names[key]); defaults += text; char variable[32]; std::snprintf(variable, sizeof(variable), "D4R_FSR_RR_KEY_%u", unsigned(key)); std::string override = std::getenv(variable) ? std::getenv(variable) : ""; if (override.empty() && key == 2 && std::getenv("D4R_FSR_RR_STABILITY_BIAS") && std::string(std::getenv("D4R_FSR_RR_STABILITY_BIAS")) != "auto") override = std::getenv("D4R_FSR_RR_STABILITY_BIAS"); if (override.empty()) continue; char* end = nullptr; const float wanted = std::strtof(override.c_str(), &end); if (end == override.c_str() || !std::isfinite(wanted)) { std::fprintf(stderr, "FSR_RR: %s override '%s' is not a number; ignored\n", names[key], override.c_str()); continue; } ffxConfigureDescDenoiserKeyValue configure = {}; configure.header.type = FFX_API_CONFIGURE_DESC_TYPE_DENOISER_KEYVALUE; configure.key = key; configure.count = 1; configure.data = &wanted; const ffxReturnCode_t configured = runtime.configure(&context, &configure.header); std::fprintf(stderr, "FSR_RR: %s set to %g (%s)\n", names[key], wanted, fsr_rr_return_name(configured)); } if (log_enabled()) std::fprintf(stderr, "FSR_RR denoiser defaults: %s\n", defaults.c_str()); } // [RayReconstruction] FsrDetail (D4R_FSR_RR_DETAIL, 0-1): the share of the colour that bypasses the denoiser through // the residual; D4R_FSR_RR_DEMOD_FLOOR (default 0.1): the smallest albedo a signal is divided by; D4R_FSR_RR_HALO_LIMIT // (default 1, 0 = off): caps each denoised lobe at that multiple of the brightest noisy input around the texel. static float env_float(const char* name, float fallback, float low, float high) { const char* text = std::getenv(name); if (!text || !*text) return fallback; char* end = nullptr; const float value = std::strtof(text, &end); if (end == text || !std::isfinite(value) || value < low || value > high) throw std::runtime_error(std::string(name) + " must be a number between " + std::to_string(low) + " and " + std::to_string(high)); return value; } void create_context() { const auto start = Clock::now(); // Diffuse is a direct signal (the game's diffuse hit distance is not carried); specular is indirect only // when the game passes a specular hit distance, which decides the context's signal set once. signals = FFX_DENOISER_SIGNAL_DIRECT_DIFFUSE | FFX_DENOISER_SIGNAL_DIRECT_SPECULAR; const ffxReturnCode_t created = fsr_rr_create_context(runtime, vdev.Get(), creation.width, creation.height, signals, 0, context); if (created != FFX_API_RETURN_OK) throw std::runtime_error(std::string("FSR RR ffxCreateContext failed: ") + fsr_rr_return_name(created)); apply_denoiser_keys(); if (log_enabled()) std::fprintf(stderr, "FSR_RR context created in %.0f ms (%ux%u, signals 0x%x)\n", since(start), creation.width, creation.height, signals); } void validate_frame(const Snapshot& frame) { if (failed) throw std::runtime_error("FSR RR backend is poisoned after failed submission"); if (frame.renderWidth < 64 || frame.renderHeight < 64 || frame.renderWidth > creation.width || frame.renderHeight > creation.height) throw std::runtime_error("RR render extent must be at least 64 and within creation geometry"); // The exposure texture belongs to the Super Sampling stage (it receives it); the denoiser does not use it. if (frame.resources[Snapshot::Exposure] && !superSampling) throw std::runtime_error("FSR RR exposure texture needs the Super Sampling stage"); // The old dynamic fixture shrank below NVIDIA's Quality minimum, triggering BAD00005 and a teardown fault. // Query the SR library's actual range and reject unsupported sizes before recording GPU work. if (superSampling && (frame.renderWidth < srRenderLimits[0] || frame.renderHeight < srRenderLimits[1] || frame.renderWidth > srRenderLimits[2] || frame.renderHeight > srRenderLimits[3])) throw std::runtime_error("FSR RR render size is outside the Super Sampling dynamic render range"); if (frame.exposureScale != 1 || frame.depthType || frame.normalsType || frame.roughnessType || frame.diffuseType || frame.specularType) throw std::runtime_error("FSR RR guide encoding or exposure scale requires conversion"); if (frame.resources[Snapshot::Disocclusion] || frame.resources[Snapshot::Subsurface]) throw std::runtime_error("FSR RR does not use the disocclusion or subsurface guides"); if (!superSampling && (frame.renderWidth != creation.outWidth || frame.renderHeight != creation.outHeight)) throw std::runtime_error("FSR RR upscaling needs a Super Sampling stage (none is installed) or output size == render size"); for (size_t i = 0; i < Snapshot::Count; ++i) { if (i == Snapshot::Exposure) continue; auto* resource = frame.resources[i].Get(); if (!resource && i >= Snapshot::HitDistance) continue; if (!resource) throw std::runtime_error("Missing required FSR RR texture"); ComPtr owner; dx(resource->GetDevice(IID_PPV_ARGS(&owner)), "FSR RR texture device"); if (owner.Get() != d3d.Get()) throw std::runtime_error("FSR RR texture belongs to another device"); const auto desc = resource_desc(resource); if (i != Snapshot::Output) (void)input_format(desc.Format, i); if (i == Snapshot::Depth) { (void)copy_format(d3d.Get(), resource, i); if ((desc.Flags & D3D12_RESOURCE_FLAG_ALLOW_DEPTH_STENCIL) && (frame.origins[i][0] || frame.origins[i][1] || frame.renderWidth != desc.Width || frame.renderHeight != desc.Height)) throw std::runtime_error("FSR RR depth-stencil requires a full depth-plane rectangle"); } const uint32_t width = i == Snapshot::Output ? layout.ow : frame.renderWidth; const uint32_t height = i == Snapshot::Output ? layout.oh : frame.renderHeight; if (desc.Dimension != D3D12_RESOURCE_DIMENSION_TEXTURE2D || (i == Snapshot::Output && !superSampling && desc.Format != DXGI_FORMAT_R16G16B16A16_FLOAT && desc.Format != DXGI_FORMAT_R32G32B32A32_FLOAT) || desc.SampleDesc.Count != 1 || desc.DepthOrArraySize != 1 || frame.origins[i][0] > desc.Width || width > desc.Width - frame.origins[i][0] || frame.origins[i][1] > desc.Height || height > desc.Height - frame.origins[i][1]) throw std::runtime_error("Unsupported FSR RR texture format or subrect (FSR-only output must be RGBA16F or RGBA32F)"); if (i == Snapshot::Output && !superSampling && desc.Format == DXGI_FORMAT_R32G32B32A32_FLOAT) { VkFormatProperties source{}, destination{}; api.formatProperties(vkPhysical, VK_FORMAT_R16G16B16A16_SFLOAT, &source); api.formatProperties(vkPhysical, VK_FORMAT_R32G32B32A32_SFLOAT, &destination); if (!(source.optimalTilingFeatures & VK_FORMAT_FEATURE_BLIT_SRC_BIT) || !(destination.optimalTilingFeatures & VK_FORMAT_FEATURE_BLIT_DST_BIT)) throw std::runtime_error("FSR RR output widening requires half-to-float blit support"); } if (i == Snapshot::Output && !(desc.Flags & D3D12_RESOURCE_FLAG_ALLOW_UNORDERED_ACCESS)) throw std::runtime_error("RR output lacks UAV capability"); } for (size_t i = 0; i < Snapshot::Count; ++i) if (i != Snapshot::Output && frame.resources[i] == frame.resources[Snapshot::Output]) throw std::runtime_error("RR input/output alias is unsupported"); } void ensure_staging(size_t plane) { auto& s = staging[plane]; const VkFormat wanted = layout.planes[plane].format; if (s.image && s.format == wanted) return; destroy_staging(s); VkImageCreateInfo ci{VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO}; ci.imageType = VK_IMAGE_TYPE_2D; ci.format = wanted; ci.extent = {creation.width, creation.height, 1}; ci.mipLevels = ci.arrayLayers = 1; ci.samples = VK_SAMPLE_COUNT_1_BIT; ci.tiling = VK_IMAGE_TILING_OPTIMAL; ci.usage = VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT; ci.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; check(api.createImage(vk, &ci, nullptr, &s.image), "FSR RR staging image"); VkMemoryRequirements req{}; api.imageRequirements(vk, s.image, &req); VkMemoryAllocateInfo ai{VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO}; ai.allocationSize = req.size; ai.memoryTypeIndex = memory_type(req.memoryTypeBits, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT); check(api.allocate(vk, &ai, nullptr, &s.memory), "FSR RR staging memory"); check(api.bindImage(vk, s.image, s.memory, 0), "FSR RR staging bind"); VkImageViewCreateInfo vi{VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO}; vi.image = s.image; vi.viewType = VK_IMAGE_VIEW_TYPE_2D; vi.format = wanted; vi.subresourceRange = {VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1}; check(passes.createView(vk, &vi, nullptr, &s.view), "FSR RR staging view"); s.format = wanted; } void wait_vkd3d() { const uint64_t value = ++vsequence; dx(vqueue->Signal(vfence.Get(), value), "FSR RR vkd3d signal"); dx(vfence->SetEventOnCompletion(value, vevent.value), "FSR RR vkd3d completion event"); if (WaitForSingleObject(vevent.value, 60000) != WAIT_OBJECT_0) throw std::runtime_error("FSR RR vkd3d completion timeout"); if (vfence->GetCompletedValue() == UINT64_MAX) throw std::runtime_error("FSR RR vkd3d device removed"); } void wait_game(ID3D12CommandQueue* q, uint64_t value) { dx(q->Signal(fence.Get(), value), "FSR RR game signal"); dx(fence->SetEventOnCompletion(value, event.value), "FSR RR game completion event"); if (WaitForSingleObject(event.value, 10000) != WAIT_OBJECT_0) throw std::runtime_error("FSR RR D3D12 completion timeout"); if (fence->GetCompletedValue() == UINT64_MAX) throw std::runtime_error("FSR RR D3D12 device removed"); } void validate(ID3D12CommandQueue* q, const Snapshot& frame, const std::array& access) { if (failed) throw std::runtime_error("FSR RR backend is poisoned after failed submission"); if (!q) throw std::runtime_error("Null RR submission queue"); D3D12_COMMAND_QUEUE_DESC queueDesc{}; #if defined(__MINGW32__) q->GetDesc(&queueDesc); #else queueDesc = q->GetDesc(); #endif if (queueDesc.Type != D3D12_COMMAND_LIST_TYPE_DIRECT) throw std::runtime_error("RR requires a direct queue"); ComPtr owner; dx(q->GetDevice(IID_PPV_ARGS(&owner)), "RR queue device"); if (owner.Get() != d3d.Get()) throw std::runtime_error("RR queue belongs to another device"); for (size_t i = 0; i < access.size(); ++i) if (frame.resources[i] && (access[i].pending_split || access[i].inherited)) throw std::runtime_error("Unresolved RR texture state"); validate_frame(frame); } void evaluate(ID3D12CommandQueue* q, const Snapshot& frame, const std::array& access) { validate(q, frame, access); failed = true; const auto begin = Clock::now(); layout.rw = frame.renderWidth; layout.rh = frame.renderHeight; for (size_t i = 0; i < layout.planes.size(); ++i) { const auto resource = resource_index(i); layout.planes[i].enabled = frame.resources[resource] != nullptr; if (!layout.planes[i].enabled) continue; const auto format = input_format(resource_desc(frame.resources[resource].Get()).Format, resource); layout.planes[i].format = format.format; layout.planes[i].bytes = format.bytes; } const bool floatOutput = !superSampling && resource_desc(frame.resources[Snapshot::Output].Get()).Format == DXGI_FORMAT_R32G32B32A32_FLOAT; if (floatOutput && !textures[Widened].resource) create_intermediate(Widened); layout.outputBytes = floatOutput ? 16 : 8; layout.outputFormat = floatOutput ? VK_FORMAT_R32G32B32A32_SFLOAT : VK_FORMAT_R16G16B16A16_SFLOAT; const auto outputFormat = floatOutput ? DXGI_FORMAT_R32G32B32A32_FLOAT : DXGI_FORMAT_R16G16B16A16_FLOAT; commands::InternalScope internal; // --- 1. the game's guides into the shared packet --- dx(producerAllocator->Reset(), "FSR RR producer allocator reset"); dx(producer->Reset(producerAllocator.Get(), nullptr), "FSR RR producer reset"); transition(producer.Get(), shared.Get(), D3D12_RESOURCE_STATE_COMMON, D3D12_RESOURCE_STATE_COPY_DEST); for (size_t i = 0; i < layout.planes.size(); ++i) { const auto resource = resource_index(i); auto* texture = frame.resources[resource].Get(); const auto& plane = layout.planes[i]; if (!plane.enabled) continue; texture_copy_barrier(producer.Get(), texture, access[resource], false, false); auto source = rr_bridge::D3D12Frames::texture(texture); auto dest = rr_bridge::D3D12Frames::placed(shared.Get(), plane.offset, plane.pitch, copy_format(d3d.Get(), texture, resource), layout.rw, layout.rh); const auto x = frame.origins[resource][0], y = frame.origins[resource][1]; D3D12_BOX box{x, y, 0, x + layout.rw, y + layout.rh, 1}; const bool depthStencil = (resource_desc(texture).Flags & D3D12_RESOURCE_FLAG_ALLOW_DEPTH_STENCIL) != 0; producer->CopyTextureRegion(&dest, 0, 0, 0, &source, depthStencil ? nullptr : &box); texture_copy_barrier(producer.Get(), texture, access[resource], false, true); } transition(producer.Get(), shared.Get(), D3D12_RESOURCE_STATE_COPY_DEST, D3D12_RESOURCE_STATE_COMMON); dx(producer->Close(), "FSR RR producer close"); ID3D12CommandList* inputs[] = {producer.Get()}; q->ExecuteCommandLists(1, inputs); pending = true; queue = q; wait_game(q, ++sequence); const double producedMs = since(begin); // --- 2. conversion, denoising and composition on the vkd3d device --- run_vkd3d(frame); const double vkd3dMs = since(begin) - producedMs; // --- 3. the packet's output into the game's output texture --- dx(consumerAllocator->Reset(), "FSR RR consumer allocator reset"); dx(consumer->Reset(consumerAllocator.Get(), nullptr), "FSR RR consumer reset"); transition(consumer.Get(), shared.Get(), D3D12_RESOURCE_STATE_COMMON, D3D12_RESOURCE_STATE_COPY_SOURCE); auto* output = frame.resources[Snapshot::Output].Get(); if (superSampling) { // the denoised colour at render size goes to a game-device texture; the Super Sampling stage upscales it if (!denoised) { D3D12_HEAP_PROPERTIES heap{}; heap.Type = D3D12_HEAP_TYPE_DEFAULT; D3D12_RESOURCE_DESC desc{}; desc.Dimension = D3D12_RESOURCE_DIMENSION_TEXTURE2D; desc.Width = creation.width; desc.Height = creation.height; desc.DepthOrArraySize = desc.MipLevels = 1; desc.Format = DXGI_FORMAT_R16G16B16A16_FLOAT; desc.SampleDesc.Count = 1; desc.Flags = D3D12_RESOURCE_FLAG_ALLOW_UNORDERED_ACCESS; dx(d3d->CreateCommittedResource(&heap, D3D12_HEAP_FLAG_NONE, &desc, denoisedState, nullptr, IID_PPV_ARGS(&denoised)), "FSR RR denoised texture"); } transition(consumer.Get(), denoised.Get(), denoisedState, D3D12_RESOURCE_STATE_COPY_DEST); auto source = rr_bridge::D3D12Frames::placed(shared.Get(), layout.outputOffset, layout.outputPitch, outputFormat, layout.rw, layout.rh); auto dest = rr_bridge::D3D12Frames::texture(denoised.Get()); consumer->CopyTextureRegion(&dest, 0, 0, 0, &source, nullptr); denoisedState = D3D12_RESOURCE_STATE_NON_PIXEL_SHADER_RESOURCE; transition(consumer.Get(), denoised.Get(), D3D12_RESOURCE_STATE_COPY_DEST, denoisedState); } else { texture_copy_barrier(consumer.Get(), output, access[Snapshot::Output], true, false); auto source = rr_bridge::D3D12Frames::placed(shared.Get(), layout.outputOffset, layout.outputPitch, outputFormat, layout.ow, layout.oh); auto dest = rr_bridge::D3D12Frames::texture(output); consumer->CopyTextureRegion(&dest, frame.origins[Snapshot::Output][0], frame.origins[Snapshot::Output][1], 0, &source, nullptr); texture_copy_barrier(consumer.Get(), output, access[Snapshot::Output], true, true); } transition(consumer.Get(), shared.Get(), D3D12_RESOURCE_STATE_COPY_SOURCE, D3D12_RESOURCE_STATE_COMMON); dx(consumer->Close(), "FSR RR consumer close"); ID3D12CommandList* outputs[] = {consumer.Get()}; q->ExecuteCommandLists(1, outputs); wait_game(q, ++sequence); if (superSampling) { // the stage records/submits its own work on `q`; the denoised texture is in NON_PIXEL_SHADER_RESOURCE const auto upscaleStart = Clock::now(); superSampling(q, denoised.Get(), frame, access); if (log_enabled()) std::fprintf(stderr, "FSR_RR super sampling submitted in %.2f ms\n", since(upscaleStart)); } pending = false; failed = false; queue.Reset(); if (log_enabled()) std::fprintf(stderr, "FSR_RR frame %u: %ux%u produce %.2f ms, vkd3d (convert+denoise+compose) %.2f ms, total %.2f ms\n", frameIndex, layout.rw, layout.rh, producedMs, vkd3dMs, since(begin)); ++frameIndex; } void run_vkd3d(const Snapshot& frame) { const auto& s = frame.settings; const uint32_t rw = layout.rw, rh = layout.rh; // which game planes exist decides the context's specular signal once const bool specularHit = layout.planes[7].enabled; const uint32_t wanted = FFX_DENOISER_SIGNAL_DIRECT_DIFFUSE | (specularHit ? FFX_DENOISER_SIGNAL_INDIRECT_SPECULAR : FFX_DENOISER_SIGNAL_DIRECT_SPECULAR); if (wanted != signals) { if (frameIndex == 0) { runtime.destroyContext(&context, nullptr); context = nullptr; signals = wanted; const ffxReturnCode_t created = fsr_rr_create_context(runtime, vdev.Get(), creation.width, creation.height, signals, 0, context); if (created != FFX_API_RETURN_OK) throw std::runtime_error(std::string("FSR RR ffxCreateContext failed: ") + fsr_rr_return_name(created)); apply_denoiser_keys(); } else throw std::runtime_error("FSR RR: the game's hit-distance guide changed after the denoiser was created (recreate the feature)"); } for (size_t plane : {0, 1, 2, 3, 4, 5, 6, 7, 10}) if (layout.planes[plane].enabled) ensure_staging(plane); double clipToView[16], viewToWorld[16]; if (!invert(s.viewToClip, clipToView) || !invert(s.worldToView, viewToWorld)) throw std::runtime_error("FSR RR camera matrix is not invertible"); const bool reset = s.reset || !havePrevious; double previousView[16], viewToPrevious[16]; for (int i = 0; i < 16; ++i) previousView[i] = reset ? s.worldToView[i] : this->previousView[i]; multiply(viewToWorld, previousView, viewToPrevious); D4rFsrRrPrepareConstants c = {}; for (int i = 0; i < 16; ++i) { c.clipToView[i] = float(clipToView[i]); c.viewToPreviousView[i] = float(viewToPrevious[i]); } c.renderSize[0] = float(rw); c.renderSize[1] = float(rh); c.renderSize[2] = 1.0f / rw; c.renderSize[3] = 1.0f / rh; c.motion[0] = s.motionScale[0] / rw; c.motion[1] = s.motionScale[1] / rh; c.motion[2] = 1.0f; c.motion[3] = 1.0f; uint32_t flags = 0; if (s.hardwareDepth) flags |= kFsrRrHardwareDepth; if (s.roughnessPacked) flags |= kFsrRrRoughnessPacked; if (specularHit) flags |= kFsrRrSpecularHit; if (unorm(layout.planes[3].format)) flags |= kFsrRrNormalsUnorm; if (layout.planes[10].enabled) flags |= kFsrRrBeforeParticles; flags |= kFsrRrInvalidNormalPassthrough; c.motionBaseFlags[2] = flags; const float viewZSign = s.viewToClip[11] < 0.0f ? -1.0f : 1.0f; c.depthParams[0] = viewZSign; c.depthParams[1] = s.depthInverted ? 0.0f : 1.0f; c.depthParams[2] = 1.0e6f; c.detail[0] = env_float("D4R_FSR_RR_DETAIL", 0.0f, 0.0f, 1.0f); c.detail[1] = std::max(env_float("D4R_FSR_RR_DEMOD_FLOOR", 0.1f, 0.0f, 1.0f), 1e-3f); dx(vallocator->Reset(), "FSR RR vkd3d allocator reset"); dx(vlist->Reset(vallocator.Get(), nullptr), "FSR RR vkd3d list reset"); stamp(0); auto setState = [&](int index, D3D12_RESOURCE_STATES state) { auto& t = textures[index]; if (t.resource) { transition(vlist.Get(), t.resource.Get(), t.state, state); t.state = state; } }; for (int i = 0; i < TexCount; ++i) setState(i, D3D12_RESOURCE_STATE_UNORDERED_ACCESS); VkCommandBuffer command = VK_NULL_HANDLE; dx(interop->BeginVkCommandBufferInterop(vlist.Get(), &command), "FSR RR Vulkan interop begin"); // the packet comes in from the game's device VkBufferMemoryBarrier acquire{VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER}; acquire.buffer = packetBuffer; acquire.size = VK_WHOLE_SIZE; acquire.srcQueueFamilyIndex = VK_QUEUE_FAMILY_EXTERNAL; acquire.dstQueueFamilyIndex = vkFamily; acquire.dstAccessMask = VK_ACCESS_TRANSFER_READ_BIT | VK_ACCESS_TRANSFER_WRITE_BIT; api.barrier(command, VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, 0, 0, nullptr, 1, &acquire, 0, nullptr); for (size_t plane : {0, 1, 2, 3, 4, 5, 6, 7, 10}) { if (!layout.planes[plane].enabled) continue; const auto& st = staging[plane]; VkImageMemoryBarrier toCopy{VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER}; toCopy.image = st.image; toCopy.oldLayout = VK_IMAGE_LAYOUT_UNDEFINED; toCopy.newLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL; toCopy.dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT; toCopy.srcQueueFamilyIndex = toCopy.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; toCopy.subresourceRange = {VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1}; api.barrier(command, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, 0, 0, nullptr, 0, nullptr, 1, &toCopy); VkBufferImageCopy region{}; region.bufferOffset = layout.planes[plane].offset; region.bufferRowLength = uint32_t(layout.planes[plane].pitch / layout.planes[plane].bytes); region.imageSubresource = {VK_IMAGE_ASPECT_COLOR_BIT, 0, 0, 1}; region.imageExtent = {rw, rh, 1}; api.copyBufferToImage(command, packetBuffer, st.image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, ®ion); VkImageMemoryBarrier toRead = toCopy; toRead.oldLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL; toRead.newLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL; toRead.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT; toRead.dstAccessMask = VK_ACCESS_SHADER_READ_BIT; api.barrier(command, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, 0, 0, nullptr, 0, nullptr, 1, &toRead); } auto sampled = [&](size_t plane) { return D4rFsrRrImage{staging[plane].view, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL}; }; const bool hasHit = layout.planes[7].enabled, hasParticles = layout.planes[10].enabled; D4rFsrRrImage images[D4rFsrRrPasses::kPrepareInputs + D4rFsrRrPasses::kPrepareOutputs] = { sampled(0), sampled(2), sampled(1), sampled(3), sampled(4), sampled(5), sampled(6), hasHit ? sampled(7) : sampled(3), sampled(3), hasParticles ? sampled(10) : sampled(0)}; static const int targets[D4rFsrRrPasses::kPrepareOutputs] = {LinearDepth, MotionT, NormalsT, DiffuseAlbedo, SpecularAlbedo, DiffuseT, SpecularT, Residual}; for (uint32_t i = 0; i < D4rFsrRrPasses::kPrepareOutputs; ++i) images[D4rFsrRrPasses::kPrepareInputs + i] = {textures[targets[i]].view, textures[targets[i]].layout}; passes.record_prepare(command, images, c, rw, rh); dx(interop->EndVkCommandBufferInterop(vlist.Get()), "FSR RR Vulkan interop end"); stamp(1); // --- AMD FSR Ray Regeneration (D3D12 on vkd3d-proton) --- const D3D12_RESOURCE_STATES read = D3D12_RESOURCE_STATE_NON_PIXEL_SHADER_RESOURCE | D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE; for (int index : {LinearDepth, MotionT, NormalsT, DiffuseAlbedo, SpecularAlbedo, DiffuseT, SpecularT}) setState(index, read); auto ffx = [&](int index, uint32_t state) { const auto& t = textures[index]; return fsr_rr_texture(t.resource.Get(), t.desc, fsr_rr_surface_format(t.desc.Format), state); }; const uint32_t ffxRead = FFX_API_RESOURCE_STATE_PIXEL_COMPUTE_READ; ffxDispatchDescDenoiser desc = {}; desc.header.type = FFX_API_DISPATCH_DESC_TYPE_DENOISER; desc.commandList = vlist.Get(); desc.linearDepth = ffx(LinearDepth, ffxRead); desc.motionVectors = ffx(MotionT, ffxRead); desc.normals = ffx(NormalsT, ffxRead); desc.specularAlbedo = ffx(SpecularAlbedo, ffxRead); desc.diffuseAlbedo = ffx(DiffuseAlbedo, ffxRead); desc.motionVectorScale = {1.0f, 1.0f, 1.0f}; desc.jitterOffsets = {s.jitter[0], s.jitter[1]}; const double position[3] = {viewToWorld[12], viewToWorld[13], viewToWorld[14]}; double previousPosition[3] = {position[0], position[1], position[2]}; if (!reset) { double previousInverse[16]; if (invert(this->previousView, previousInverse)) previousPosition[0] = previousInverse[12], previousPosition[1] = previousInverse[13], previousPosition[2] = previousInverse[14]; } desc.cameraPositionDelta = {float(previousPosition[0] - position[0]), float(previousPosition[1] - position[1]), float(previousPosition[2] - position[2])}; std::memcpy(&desc.view, s.worldToView, sizeof(desc.view)); std::memcpy(&desc.projection, s.viewToClip, sizeof(desc.projection)); desc.linearDepthBounds = {0.0f, 1.0e6f}; desc.renderSize = {rw, rh}; desc.frameIndex = frameIndex; desc.flags = uint32_t(FFX_DENOISER_DISPATCH_NON_GAMMA_ALBEDO) | (reset ? uint32_t(FFX_DENOISER_DISPATCH_RESET) : 0u); ffxDispatchDescDenoiserDirectDiffuse directDiffuse = {}; ffxDispatchDescDenoiserDirectSpecular directSpecular = {}; ffxDispatchDescDenoiserIndirectSpecular indirectSpecular = {}; directDiffuse.header.type = FFX_API_DISPATCH_DESC_TYPE_DENOISER_DIRECT_DIFFUSE; FfxApiDenoiserSignal* diffuseSignal = &directDiffuse.signal; FfxApiDenoiserSignal* specularSignal; ffxApiHeader* specularHeader; if (specularHit) { indirectSpecular.header.type = FFX_API_DISPATCH_DESC_TYPE_DENOISER_INDIRECT_SPECULAR; specularSignal = &indirectSpecular.signal; specularHeader = &indirectSpecular.header; } else { directSpecular.header.type = FFX_API_DISPATCH_DESC_TYPE_DENOISER_DIRECT_SPECULAR; specularSignal = &directSpecular.signal; specularHeader = &directSpecular.header; } diffuseSignal->input = ffx(DiffuseT, ffxRead); diffuseSignal->output = ffx(DiffuseOut, FFX_API_RESOURCE_STATE_UNORDERED_ACCESS); specularSignal->input = ffx(SpecularT, ffxRead); specularSignal->output = ffx(SpecularOut, FFX_API_RESOURCE_STATE_UNORDERED_ACCESS); desc.header.pNext = &directDiffuse.header; directDiffuse.header.pNext = specularHeader; const ffxReturnCode_t dispatched = runtime.dispatch(&context, &desc.header); if (dispatched != FFX_API_RETURN_OK) throw std::runtime_error(std::string("FSR RR ffxDispatch failed: ") + fsr_rr_return_name(dispatched)); D3D12_RESOURCE_BARRIER uav{}; uav.Type = D3D12_RESOURCE_BARRIER_TYPE_UAV; vlist->ResourceBarrier(1, &uav); stamp(2); // --- composition, then the composed image into the packet's output region --- for (int i = 0; i < TexCount; ++i) setState(i, D3D12_RESOURCE_STATE_UNORDERED_ACCESS); const D4rFsrRrImage compose[D4rFsrRrPasses::kComposeImages] = { {textures[DiffuseOut].view, textures[DiffuseOut].layout}, {textures[SpecularOut].view, textures[SpecularOut].layout}, {textures[DiffuseAlbedo].view, textures[DiffuseAlbedo].layout}, {textures[SpecularAlbedo].view, textures[SpecularAlbedo].layout}, {textures[Residual].view, textures[Residual].layout}, {textures[Composed].view, textures[Composed].layout}, {textures[DiffuseT].view, textures[DiffuseT].layout}, {textures[SpecularT].view, textures[SpecularT].layout}}; dx(interop->BeginVkCommandBufferInterop(vlist.Get(), &command), "FSR RR Vulkan interop begin (compose)"); passes.record_compose(command, compose, rw, rh, env_float("D4R_FSR_RR_HALO_LIMIT", 1.0f, 0.0f, 1000.0f)); int copiedImage = Composed; if (layout.outputBytes == 16) { // Preserve the denoiser/composition's half arithmetic; widen only the completed output on the GPU. VkImageBlit region{}; region.srcSubresource = region.dstSubresource = {VK_IMAGE_ASPECT_COLOR_BIT, 0, 0, 1}; region.srcOffsets[1] = region.dstOffsets[1] = {int32_t(rw), int32_t(rh), 1}; api.blitImage(command, textures[Composed].image, textures[Composed].layout, textures[Widened].image, textures[Widened].layout, 1, ®ion, VK_FILTER_NEAREST); passes.full_barrier(command); copiedImage = Widened; } VkBufferImageCopy back{}; back.bufferOffset = layout.outputOffset; back.bufferRowLength = uint32_t(layout.outputPitch / layout.outputBytes); back.imageSubresource = {VK_IMAGE_ASPECT_COLOR_BIT, 0, 0, 1}; back.imageExtent = {rw, rh, 1}; api.copyImageToBuffer(command, textures[copiedImage].image, textures[copiedImage].layout, packetBuffer, 1, &back); VkBufferMemoryBarrier release = acquire; release.srcQueueFamilyIndex = vkFamily; release.dstQueueFamilyIndex = VK_QUEUE_FAMILY_EXTERNAL; release.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT; release.dstAccessMask = 0; api.barrier(command, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT, 0, 0, nullptr, 1, &release, 0, nullptr); dx(interop->EndVkCommandBufferInterop(vlist.Get()), "FSR RR Vulkan interop end (compose)"); // Diagnostic (D4R_FSR_RR_DUMP_DIR, first D4R_FSR_RR_DUMP_FRAMES frames, default 3): raw copies of the converted // linear depth and motion textures, to check the DLSS -> FFX guide conversion numerically. stamp(3); if (stamps) vlist->ResolveQueryData(stamps.Get(), D3D12_QUERY_TYPE_TIMESTAMP, 0, 4, stampReadback.Get(), 0); const char* dumpDir = std::getenv("D4R_FSR_RR_DUMP_DIR"); const unsigned dumpFrames = std::getenv("D4R_FSR_RR_DUMP_FRAMES") ? unsigned(std::atoi(std::getenv("D4R_FSR_RR_DUMP_FRAMES"))) : 3; struct Dump { int index; const char* name; ComPtr buffer; D3D12_PLACED_SUBRESOURCE_FOOTPRINT footprint; UINT rows; UINT bytesPerTexel; }; std::vector dumps; if (dumpDir && frameIndex < dumpFrames) { for (auto [index, name, texel] : {std::tuple{LinearDepth, "linear-depth", 4}, {MotionT, "motion", 8}, {NormalsT, "normals", 8}, {Composed, "composed", 8}}) { Dump d{index, name, {}, {}, 0, texel}; UINT64 rowBytes = 0, total = 0; auto desc = textures[index].desc; vdev->GetCopyableFootprints(&desc, 0, 1, 0, &d.footprint, &d.rows, &rowBytes, &total); d.buffer = make_buffer(vdev.Get(), total, D3D12_HEAP_TYPE_READBACK, D3D12_HEAP_FLAG_NONE, D3D12_RESOURCE_STATE_COPY_DEST); transition(vlist.Get(), textures[index].resource.Get(), textures[index].state, D3D12_RESOURCE_STATE_COPY_SOURCE); D3D12_TEXTURE_COPY_LOCATION destination{}; destination.pResource = d.buffer.Get(); destination.Type = D3D12_TEXTURE_COPY_TYPE_PLACED_FOOTPRINT; destination.PlacedFootprint = d.footprint; D3D12_TEXTURE_COPY_LOCATION source{}; source.pResource = textures[index].resource.Get(); source.Type = D3D12_TEXTURE_COPY_TYPE_SUBRESOURCE_INDEX; vlist->CopyTextureRegion(&destination, 0, 0, 0, &source, nullptr); transition(vlist.Get(), textures[index].resource.Get(), D3D12_RESOURCE_STATE_COPY_SOURCE, textures[index].state); dumps.push_back(std::move(d)); } } dx(vlist->Close(), "FSR RR vkd3d list close"); ID3D12CommandList* lists[] = {vlist.Get()}; vqueue->ExecuteCommandLists(1, lists); wait_vkd3d(); if (stamps) { void* mapped = nullptr; D3D12_RANGE range{0, 4 * sizeof(UINT64)}; if (SUCCEEDED(stampReadback->Map(0, &range, &mapped))) { UINT64 t[4]; std::memcpy(t, mapped, sizeof(t)); stampReadback->Unmap(0, nullptr); auto ms = [&](int a, int b) { return double(t[b] - t[a]) * 1000.0 / double(stampFrequency); }; std::fprintf(stderr, "FSR_RR gpu frame %u: prepare %.2f ms, denoise %.2f ms, compose+copy %.2f ms, total %.2f ms\n", frameIndex, ms(0, 1), ms(1, 2), ms(2, 3), ms(0, 3)); } } for (auto& d : dumps) { void* mapped = nullptr; D3D12_RANGE range{0, size_t(d.footprint.Footprint.RowPitch) * d.rows}; dx(d.buffer->Map(0, &range, &mapped), "FSR RR dump map"); const std::string path = std::string(dumpDir) + "/frame" + std::to_string(frameIndex) + "-" + d.name + ".bin"; if (FILE* out = std::fopen(path.c_str(), "wb")) { for (UINT row = 0; row < rw && row < d.rows; ++row) {} // (rows are written below; width = render width) for (UINT row = 0; row < rh; ++row) std::fwrite(static_cast(mapped) + size_t(row) * d.footprint.Footprint.RowPitch, 1, size_t(rw) * d.bytesPerTexel, out); std::fclose(out); } d.buffer->Unmap(0, nullptr); } std::memcpy(this->previousView, s.worldToView, sizeof(this->previousView)); havePrevious = true; } }; FsrBackend::FsrBackend(ID3D12Device* device, const Creation& creation, const FsrConfig& config) { std::lock_guard lock(hostMutex); auto candidate = std::make_unique(device, creation, config); candidate->initialize(); impl = std::move(candidate); } FsrBackend::~FsrBackend() { std::lock_guard lock(hostMutex); if (impl && impl->pending) { std::fprintf(stderr, "D4R_FSR_RR_QUARANTINE incomplete GPU work; retaining backend and guide resources\n"); (void)impl.release(); } else impl.reset(); } void FsrBackend::evaluate(ID3D12CommandQueue* queue, const Snapshot& frame, const std::array& access) { std::lock_guard lock(hostMutex); impl->evaluate(queue, frame, access); } void FsrBackend::set_super_sampling(SuperSampling stage, const std::array& renderLimits) { std::lock_guard lock(hostMutex); impl->superSampling = std::move(stage); impl->srRenderLimits = renderLimits; } void FsrBackend::validate_frame(const Snapshot& frame) { std::lock_guard lock(hostMutex); impl->validate_frame(frame); } void FsrBackend::drain() { std::lock_guard lock(hostMutex); if (impl->pending || impl->failed) throw std::runtime_error("FSR RR backend cannot retire failed GPU work"); } }