network natively on HIP with Vulkan->HIP shared memory, extend K kernels to
gfx11 (RDNA3), and add dependency/packaging/test tooling around them.
Windows: FSR RR backend, native HIP K runner, gfx11 K kernels, tooling
FSR Ray Regeneration (experimental)
- Add fsr_rr_backend.{h,cpp}, fsr_rr_passes.h and the fsr_rr/ prepare and
compose compute shaders. They run AMD's FidelityFX denoiser
(amd_fidelityfx_denoiser_dx12.dll, SDK 2.3, not bundled) on a private
vkd3d-proton device.
- Add the amdxc64 shim (tools/windows/amdxc64_shim) and two vkd3d-proton
patches for libcxx includes. AMD's amdxc64.dll is never loaded.
- d4r_nvngx_windows: RrFeature now selects between the native DLSS RR
backend and the FSR backend (D4R_RR_BACKEND=fsr). After the denoiser it
runs an internal Super Sampling stage (feature 1) to upscale. If SR is
unavailable and output != render size, the frame fails; it is never
returned noisy.
- d4r.ini / player_config: new [RayReconstruction] FsrDenoiser,
FsrStabilityBias (auto or >= 0) and FsrDetail (0-1), with validation, and
matching D4R_FSR_* environment variables.
- Add the FSR RR fixture, output-check and ngx test scripts, and a shader
compile script.
Native HIP K runner (Windows)
- CMake: add d4r_engine_k_hip (d4r-k-hip) plus separate volk/engine copies,
because VK_USE_PLATFORM_WIN32_KHR changes volk's entry points.
- Share Vulkan memory with HIP via Win32 opaque handles on Windows
(VK_KHR_external_memory_win32) and via fds on Linux. Handle types are now
intptr_t, and the platform choice is in D4R_EXT_MEMORY_* macros.
- HipNetwork: add debugRepeatHashes and debugNodeMicros, exposed as
D4R_HIP_REPEAT_CHECK, D4R_HIP_REPEAT_ALL and D4R_HIP_NODE_PROFILE in
replay.cpp.
- replay.cpp: display-resolution-motion frames (FrameFlagDisplayMotion) no
longer require near2rnd == out2rnd. Both values must still be in (0, 1].
K kernels on gfx11
- Allow position-token and enc1 tiling on __GFX11__ as well as gfx12.
- Add an opt-in ds_bpermute cross-half exchange
(D4R_WINDOWS_GFX11_DS_EXCHANGE, enabled for K on Windows gfx11). M keeps
its LDS path.
- Fix a block-barrier race in the position layer. Without POS_VSHARE a fast
wave could overwrite its hb rows while a slower wave still read them for a
later head, giving rare wrong results on gfx1101 (enc0 outputs differing
between identical launches).
Packaging and install
- install.bat, d4r.ini, WINDOWS_README: FSR option, and
install_dependencies.py to install pinned NVIDIA dependencies with staged,
hash-verified replacement and rollback.
- Update the release package, local package refresh and test scripts.
- Add the InteropBatchInputCopies option (default false). Batching is faster
on some synthetic runs, but player temporal exactness is unresolved.
Tooling and tests
- K model: capture_k_weights, build_k_model, compare_k_outputs,
benchmark_k_hip, build_capture_runtime, test_k_model and the
zluda-k-output-capture patch. Captured weights and models are private
local assets and must not enter release archives.
- Synthetic probes: test_m_ngx, test_sr_dynamic,
prepare_rr_dynamic_fixture and the d3d12/rr_ngx probe updates.
- Add tests/windows/test_install_dependencies.py.
Docs
- Update the windows-fsr-rr, k-performance, local-package, rr-interop and
rr-model-cache docs. Add windows-parity-handoff.md and
windows-parity-continuation-prompt.md.
308 lines
15 KiB
C++
308 lines
15 KiB
C++
#pragma once
|
|
#include <windows.h>
|
|
#include <tlhelp32.h>
|
|
#include <dbghelp.h>
|
|
#include <cstdint>
|
|
#include <cstdio>
|
|
#include <cstdlib>
|
|
#include <cstring>
|
|
#include <filesystem>
|
|
#include <stdexcept>
|
|
#include <string>
|
|
#include <vector>
|
|
#include <algorithm>
|
|
|
|
namespace d4r::diag {
|
|
inline std::wstring wide(const std::string& s)
|
|
{
|
|
const int n = MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, s.c_str(), -1, nullptr, 0);
|
|
if (!n) throw std::runtime_error("Invalid UTF-8 path");
|
|
std::wstring out(n, L'\0');
|
|
MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, s.c_str(), -1, out.data(), n);
|
|
out.pop_back();
|
|
return out;
|
|
}
|
|
inline std::string utf8(const wchar_t* s)
|
|
{
|
|
const int n = WideCharToMultiByte(CP_UTF8, 0, s, -1, nullptr, 0, nullptr, nullptr);
|
|
std::string out(n, '\0');
|
|
WideCharToMultiByte(CP_UTF8, 0, s, -1, out.data(), n, nullptr, nullptr);
|
|
if (n) out.pop_back();
|
|
return out;
|
|
}
|
|
inline void loaded_modules()
|
|
{
|
|
HANDLE snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPMODULE, GetCurrentProcessId());
|
|
if (snapshot == INVALID_HANDLE_VALUE) return;
|
|
MODULEENTRY32W entry{};
|
|
entry.dwSize = sizeof(entry);
|
|
if (Module32FirstW(snapshot, &entry)) do {
|
|
DWORD unused = 0;
|
|
DWORD size = GetFileVersionInfoSizeW(entry.szExePath, &unused);
|
|
std::vector<unsigned char> bytes(size);
|
|
VS_FIXEDFILEINFO* version = nullptr;
|
|
UINT length = 0;
|
|
std::printf("DLL path=%s", utf8(entry.szExePath).c_str());
|
|
if (size && GetFileVersionInfoW(entry.szExePath, 0, size, bytes.data()) &&
|
|
VerQueryValueW(bytes.data(), L"\\", reinterpret_cast<void**>(&version), &length) &&
|
|
length >= sizeof(*version))
|
|
std::printf(" version=%u.%u.%u.%u", HIWORD(version->dwFileVersionMS),
|
|
LOWORD(version->dwFileVersionMS), HIWORD(version->dwFileVersionLS),
|
|
LOWORD(version->dwFileVersionLS));
|
|
std::printf("\n");
|
|
} while (Module32NextW(snapshot, &entry));
|
|
CloseHandle(snapshot);
|
|
}
|
|
inline LONG WINAPI unhandled(EXCEPTION_POINTERS* info)
|
|
{
|
|
// Loader hooks can fault while holding the loader lock. Do not enumerate
|
|
// modules or read their resources here; the dump contains the module list.
|
|
// Guard the final filter as well as the first-chance handler against a
|
|
// second exception inside DbgHelp.
|
|
static LONG dumping = 0;
|
|
if (InterlockedCompareExchange(&dumping, 1, 0) != 0)
|
|
return EXCEPTION_EXECUTE_HANDLER;
|
|
std::fprintf(stderr, "EXCEPTION code=0x%08lx address=%p\n",
|
|
info->ExceptionRecord->ExceptionCode, info->ExceptionRecord->ExceptionAddress);
|
|
if (info->ExceptionRecord->ExceptionCode == EXCEPTION_ACCESS_VIOLATION &&
|
|
info->ExceptionRecord->NumberParameters >= 2)
|
|
std::fprintf(stderr, "EXCEPTION_ACCESS operation=%llu target=0x%llx\n",
|
|
static_cast<unsigned long long>(info->ExceptionRecord->ExceptionInformation[0]),
|
|
static_cast<unsigned long long>(info->ExceptionRecord->ExceptionInformation[1]));
|
|
std::fprintf(stderr, "CONTEXT rip=0x%llx rsp=0x%llx rcx=0x%llx rdx=0x%llx r8=0x%llx r9=0x%llx\n",
|
|
static_cast<unsigned long long>(info->ContextRecord->Rip),
|
|
static_cast<unsigned long long>(info->ContextRecord->Rsp),
|
|
static_cast<unsigned long long>(info->ContextRecord->Rcx),
|
|
static_cast<unsigned long long>(info->ContextRecord->Rdx),
|
|
static_cast<unsigned long long>(info->ContextRecord->R8),
|
|
static_cast<unsigned long long>(info->ContextRecord->R9));
|
|
wchar_t directory[32768]{};
|
|
const DWORD n = GetEnvironmentVariableW(L"D4R_DIAG_DIR", directory, 32768);
|
|
if (n && n < 32768) {
|
|
const std::wstring path = std::wstring(directory) + L"\\crash-" +
|
|
std::to_wstring(GetCurrentProcessId()) + L".dmp";
|
|
HANDLE file = CreateFileW(path.c_str(), GENERIC_WRITE, 0, nullptr, CREATE_ALWAYS, 0, nullptr);
|
|
if (file != INVALID_HANDLE_VALUE) {
|
|
MINIDUMP_EXCEPTION_INFORMATION exception{GetCurrentThreadId(), info, FALSE};
|
|
const BOOL ok = MiniDumpWriteDump(GetCurrentProcess(), GetCurrentProcessId(), file,
|
|
MiniDumpNormal, &exception, nullptr, nullptr);
|
|
std::fprintf(stderr, "MINIDUMP success=%d error=%lu\n", ok, ok ? 0 : GetLastError());
|
|
CloseHandle(file);
|
|
}
|
|
}
|
|
return EXCEPTION_EXECUTE_HANDLER;
|
|
}
|
|
inline LONG WINAPI first_chance(EXCEPTION_POINTERS* info)
|
|
{
|
|
// NGX installs its own final exception filter. Capture a diagnostic dump
|
|
// before that filter can replace ours; do not swallow or retry the fault.
|
|
const auto code = info->ExceptionRecord->ExceptionCode;
|
|
if (code == EXCEPTION_ACCESS_VIOLATION || code == EXCEPTION_ILLEGAL_INSTRUCTION) {
|
|
static LONG captured = 0;
|
|
if (InterlockedCompareExchange(&captured, 1, 0) == 0) (void)unhandled(info);
|
|
}
|
|
return EXCEPTION_CONTINUE_SEARCH;
|
|
}
|
|
inline void start()
|
|
{
|
|
std::setvbuf(stdout, nullptr, _IONBF, 0);
|
|
std::setvbuf(stderr, nullptr, _IONBF, 0);
|
|
SetErrorMode(SEM_FAILCRITICALERRORS | SEM_NOGPFAULTERRORBOX);
|
|
SetUnhandledExceptionFilter(unhandled);
|
|
AddVectoredExceptionHandler(1, first_chance);
|
|
std::printf("d4r native Windows diagnostic x64 pid=%lu\n", GetCurrentProcessId());
|
|
}
|
|
struct Args {
|
|
std::string hip_root, cuda_dll, module, context = "primary";
|
|
std::string stream_lifetime = "persistent";
|
|
std::string interop_mode = "roundtrip";
|
|
std::string pixel_profile = "baseline";
|
|
std::string barrier_mode = "legacy";
|
|
bool early_indirect = false;
|
|
bool player_mode = false;
|
|
bool reset_each_frame = false;
|
|
std::string ngx_core, dlss_dll;
|
|
std::string nvapi_dll;
|
|
std::string ngx_abi = "driver";
|
|
unsigned long long ngx_app_id = 241534723ull;
|
|
std::string ngx_project_id = "24480451-f00d-face-1304-0308dabad187";
|
|
std::string ngx_project_version = "1.0";
|
|
int ngx_project_engine = 0;
|
|
std::string ngx_frontend = "d4r";
|
|
std::string ngx_mode = "init";
|
|
unsigned preset = 11;
|
|
unsigned ngx_create_flags = 0;
|
|
unsigned ngx_quality = 2;
|
|
unsigned output_width = 512, output_height = 288;
|
|
unsigned input_width = 0, input_height = 0;
|
|
std::string fixture_dir;
|
|
std::string benchmark_module;
|
|
std::string benchmark_timing = "events";
|
|
unsigned benchmark_batch = 1;
|
|
std::string output_dir;
|
|
std::string kernel_name = "enc1";
|
|
unsigned iterations = 32;
|
|
int device = -1;
|
|
Args(int argc, char** argv) {
|
|
for (int i = 1; i < argc; ++i) {
|
|
const std::string key = argv[i];
|
|
if (i + 1 == argc) throw std::runtime_error("Missing value for " + key);
|
|
const std::string value = argv[++i];
|
|
if (key == "--hip-root") hip_root = value;
|
|
else if (key == "--cuda-dll") cuda_dll = value;
|
|
else if (key == "--module") module = value;
|
|
else if (key == "--context") context = value;
|
|
else if (key == "--stream-lifetime") stream_lifetime = value;
|
|
else if (key == "--interop-mode") interop_mode = value;
|
|
else if (key == "--pixel-profile") {
|
|
if (value != "baseline" && value != "packed" && value != "unorm" && value != "depth-stencil") throw std::runtime_error("Invalid pixel-profile");
|
|
pixel_profile = value;
|
|
}
|
|
else if (key == "--barrier-mode") {
|
|
if (value != "legacy" && value != "enhanced" && value != "inherited-legacy" && value != "inherited-enhanced") throw std::runtime_error("Invalid barrier-mode");
|
|
barrier_mode = value;
|
|
}
|
|
else if (key == "--early-indirect") early_indirect = value == "1";
|
|
else if (key == "--player-mode") player_mode = value == "1";
|
|
else if (key == "--reset-every-frame") reset_each_frame = value == "1";
|
|
else if (key == "--ngx-core") ngx_core = value;
|
|
else if (key == "--dlss-dll") dlss_dll = value;
|
|
else if (key == "--nvapi-dll") nvapi_dll = value;
|
|
else if (key == "--ngx-abi") ngx_abi = value;
|
|
else if (key == "--ngx-app-id") {
|
|
size_t end = 0;
|
|
if (value.empty() || value[0] == '-') throw std::runtime_error("Invalid NGX application ID");
|
|
ngx_app_id = std::stoull(value, &end);
|
|
if (end != value.size()) throw std::runtime_error("Invalid NGX application ID");
|
|
}
|
|
else if (key == "--ngx-project-id") ngx_project_id = value;
|
|
else if (key == "--ngx-project-version") ngx_project_version = value;
|
|
else if (key == "--ngx-project-engine") {
|
|
size_t end = 0;
|
|
ngx_project_engine = std::stoi(value, &end);
|
|
if (end != value.size()) throw std::runtime_error("Invalid NGX project engine type");
|
|
}
|
|
else if (key == "--ngx-frontend") {
|
|
if (value != "d4r" && value != "optiscaler") throw std::runtime_error("ngx-frontend must be d4r or optiscaler");
|
|
ngx_frontend = value;
|
|
}
|
|
else if (key == "--ngx-mode") ngx_mode = value;
|
|
else if (key == "--ngx-create-flags") {
|
|
size_t end = 0;
|
|
ngx_create_flags = static_cast<unsigned>(std::stoul(value, &end));
|
|
if (end != value.size() || (ngx_create_flags != 0 && ngx_create_flags != 11))
|
|
throw std::runtime_error("ngx-create-flags must be 0 or 11 (HDR, low-resolution MV, inverted depth)");
|
|
}
|
|
else if (key == "--ngx-quality") {
|
|
size_t end = 0;
|
|
const auto parsed = std::stoul(value, &end);
|
|
if (end != value.size() || parsed > 5) throw std::runtime_error("ngx-quality must be 0..5");
|
|
ngx_quality = static_cast<unsigned>(parsed);
|
|
}
|
|
else if (key == "--ngx-output-resolution") {
|
|
const auto separator = value.find('x');
|
|
if (separator == std::string::npos) throw std::runtime_error("Output resolution must be WIDTHxHEIGHT");
|
|
size_t wend = 0, hend = 0;
|
|
output_width = static_cast<unsigned>(std::stoul(value.substr(0, separator), &wend));
|
|
output_height = static_cast<unsigned>(std::stoul(value.substr(separator + 1), &hend));
|
|
if (wend != separator || hend != value.size() - separator - 1 || output_width < 128 || output_width > 4096 ||
|
|
output_height < 128 || output_height > 2160 || output_width % 2 || output_height % 2)
|
|
throw std::runtime_error("Output resolution must be even and within 128x128..4096x2160");
|
|
}
|
|
else if (key == "--ngx-input-resolution") {
|
|
const auto separator = value.find('x');
|
|
if (separator == std::string::npos) throw std::runtime_error("Input resolution must be WIDTHxHEIGHT");
|
|
size_t wend = 0, hend = 0;
|
|
const auto width = std::stoul(value.substr(0, separator), &wend);
|
|
const auto height = std::stoul(value.substr(separator + 1), &hend);
|
|
if (wend != separator || hend != value.size() - separator - 1 || width < 64 || width > 4096 ||
|
|
height < 64 || height > 2160)
|
|
throw std::runtime_error("Input resolution must be within 64x64..4096x2160");
|
|
input_width = static_cast<unsigned>(width); input_height = static_cast<unsigned>(height);
|
|
}
|
|
else if (key == "--preset") {
|
|
size_t end = 0;
|
|
preset = static_cast<unsigned>(std::stoul(value, &end));
|
|
if (end != value.size() || (preset != 5 && preset != 11 && preset != 12 && preset != 13))
|
|
throw std::runtime_error("preset must be 5 (E), 11 (K), 12 (L) or 13 (M)");
|
|
}
|
|
else if (key == "--fixture-dir") fixture_dir = value;
|
|
else if (key == "--benchmark-module") benchmark_module = value;
|
|
else if (key == "--benchmark-timing") {
|
|
if (value != "events" && value != "dispatch")
|
|
throw std::runtime_error("benchmark-timing must be events or dispatch");
|
|
benchmark_timing = value;
|
|
}
|
|
else if (key == "--benchmark-batch") {
|
|
size_t end = 0;
|
|
const auto parsed = std::stoul(value, &end);
|
|
if (end != value.size() || parsed < 1 || parsed > 256)
|
|
throw std::runtime_error("benchmark-batch must be 1..256");
|
|
benchmark_batch = static_cast<unsigned>(parsed);
|
|
}
|
|
else if (key == "--output-dir") output_dir = value;
|
|
else if (key == "--kernel-name") kernel_name = value;
|
|
else if (key == "--iterations") {
|
|
size_t end = 0;
|
|
iterations = static_cast<unsigned>(std::stoul(value, &end));
|
|
if (end != value.size() || iterations < 1 || iterations > 10000)
|
|
throw std::runtime_error("iterations must be 1..10000");
|
|
} else if (key == "--device") {
|
|
size_t end = 0;
|
|
device = std::stoi(value, &end);
|
|
if (end != value.size() || device < 0) throw std::runtime_error("Invalid device ordinal");
|
|
} else throw std::runtime_error("Unknown option " + key);
|
|
}
|
|
if (hip_root.empty()) throw std::runtime_error("--hip-root is required");
|
|
if (input_width > output_width || input_height > output_height)
|
|
throw std::runtime_error("Input resolution cannot exceed output resolution");
|
|
}
|
|
};
|
|
class Library {
|
|
HMODULE handle_ = nullptr;
|
|
public:
|
|
explicit Library(const std::filesystem::path& path) {
|
|
if (!path.is_absolute()) throw std::runtime_error("DLL path must be absolute");
|
|
handle_ = LoadLibraryExW(path.c_str(), nullptr,
|
|
LOAD_LIBRARY_SEARCH_DLL_LOAD_DIR | LOAD_LIBRARY_SEARCH_DEFAULT_DIRS | LOAD_LIBRARY_SEARCH_USER_DIRS);
|
|
if (!handle_) throw std::runtime_error("LoadLibrary " + utf8(path.c_str()) +
|
|
" Win32=" + std::to_string(GetLastError()));
|
|
wchar_t actual[32768]{};
|
|
GetModuleFileNameW(handle_, actual, 32768);
|
|
std::printf("LOAD requested=%s actual=%s\n", utf8(path.c_str()).c_str(), utf8(actual).c_str());
|
|
}
|
|
Library(const Library&) = delete;
|
|
Library& operator=(const Library&) = delete;
|
|
~Library() { if (handle_) FreeLibrary(handle_); }
|
|
template<class T> T symbol(const char* name) const {
|
|
FARPROC raw = GetProcAddress(handle_, name);
|
|
if (!raw) throw std::runtime_error(std::string("Missing export ") + name);
|
|
static_assert(sizeof(T) == sizeof(raw));
|
|
T out;
|
|
std::memcpy(&out, &raw, sizeof(out));
|
|
return out;
|
|
}
|
|
};
|
|
class SearchDirectory {
|
|
DLL_DIRECTORY_COOKIE cookie_ = nullptr;
|
|
public:
|
|
explicit SearchDirectory(const std::filesystem::path& path) {
|
|
if (!path.is_absolute()) throw std::runtime_error("HIP root must be absolute");
|
|
cookie_ = AddDllDirectory(path.c_str());
|
|
if (!cookie_) throw std::runtime_error("AddDllDirectory failed");
|
|
}
|
|
~SearchDirectory() { if (cookie_) RemoveDllDirectory(cookie_); }
|
|
};
|
|
inline uint32_t pattern(uint32_t i, uint32_t seed) { return (i * 1664525u + seed) ^ 0xa5a55a5au; }
|
|
inline bool verify(const std::vector<uint32_t>& result, uint32_t count, uint32_t seed) {
|
|
for (size_t i = 0; i < result.size(); ++i) {
|
|
const uint32_t expected = i < count ? pattern(static_cast<uint32_t>(i), seed) : 0xdeadbeefu;
|
|
if (result[i] != expected) {
|
|
std::fprintf(stderr, "MISMATCH index=%zu expected=0x%08x actual=0x%08x\n", i, expected, result[i]);
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
}
|