From 3c28a82e34e07688122f513bf5e4cdc625abf445 Mon Sep 17 00:00:00 2001 From: Codex Date: Wed, 30 Sep 2026 16:35:30 +0300 Subject: [PATCH] Build patched Windows ZLUDA and execute native NGX DLSS diagnostics --- CMakeLists.txt | 7 +- docs/windows-rdna4-port.md | 99 ++++++++- patches/zluda/0008-native-windows-build.patch | 203 ++++++++++++++++++ .../0009-resource-descriptor-queries.patch | 124 +++++++++++ scripts/windows/build-zluda-helpers.ps1 | 70 ++++++ scripts/windows/build-zluda-llvm.ps1 | 40 ++++ scripts/windows/build-zluda-windows.ps1 | 76 +++++++ scripts/windows/check-zluda-patches.ps1 | 25 +++ scripts/windows/prepare-zluda-source.ps1 | 25 +++ scripts/windows/setup-rust-toolchain.ps1 | 48 +++++ scripts/windows/test-windows-rdna4.ps1 | 35 ++- tools/windows/cuda_api.h | 5 +- tools/windows/cuda_image_api.h | 135 ++++++++++++ tools/windows/cuda_image_probe.cpp | 51 +++++ tools/windows/diagnostic.h | 33 +++ tools/windows/ngx_cuda_evaluate.h | 147 +++++++++++++ tools/windows/ngx_cuda_init_probe.cpp | 4 + 17 files changed, 1114 insertions(+), 13 deletions(-) create mode 100644 patches/zluda/0008-native-windows-build.patch create mode 100644 patches/zluda/0009-resource-descriptor-queries.patch create mode 100644 scripts/windows/build-zluda-helpers.ps1 create mode 100644 scripts/windows/build-zluda-llvm.ps1 create mode 100644 scripts/windows/build-zluda-windows.ps1 create mode 100644 scripts/windows/check-zluda-patches.ps1 create mode 100644 scripts/windows/prepare-zluda-source.ps1 create mode 100644 scripts/windows/setup-rust-toolchain.ps1 create mode 100644 tools/windows/cuda_image_api.h create mode 100644 tools/windows/cuda_image_probe.cpp create mode 100644 tools/windows/ngx_cuda_evaluate.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 5901b4a..1685a4f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -59,7 +59,7 @@ foreach(family k m) endforeach() endforeach() add_custom_target(d4r_native_gfx1201_compile ALL DEPENDS ${native_objects}) -foreach(probe hip_gfx1201_probe gfx12_wmma_probe k_module_probe cuda_driver_probe d3d12_hip_interop_probe hip_stream_lifecycle_probe ngx_cuda_init_probe) +foreach(probe hip_gfx1201_probe gfx12_wmma_probe k_module_probe cuda_driver_probe cuda_image_probe d3d12_hip_interop_probe hip_stream_lifecycle_probe ngx_cuda_init_probe) add_executable(d4r_${probe} tools/windows/${probe}.cpp) target_compile_definitions(d4r_${probe} PRIVATE WIN32_LEAN_AND_MEAN NOMINMAX __HIP_PLATFORM_AMD__) target_include_directories(d4r_${probe} PRIVATE "${D4R_HIP_ROOT}/include") @@ -146,4 +146,9 @@ if(EXISTS "${D4R_ZLUDA_ROOT}/nvcuda.dll") --context ${context} --iterations 32) set_tests_properties(cuda_${context} PROPERTIES TIMEOUT 180 LABELS "hardware;zluda") endforeach() + if(EXISTS "${D4R_ZLUDA_ROOT}/build-info.json") + add_test(NAME cuda_images COMMAND d4r_cuda_image_probe + --hip-root "${D4R_HIP_ROOT}" --cuda-dll "${D4R_ZLUDA_ROOT}/nvcuda.dll" --iterations 32) + set_tests_properties(cuda_images PROPERTIES TIMEOUT 90 LABELS "hardware;zluda;descriptors") + endif() endif() diff --git a/docs/windows-rdna4-port.md b/docs/windows-rdna4-port.md index 31dcde6..24a76eb 100644 --- a/docs/windows-rdna4-port.md +++ b/docs/windows-rdna4-port.md @@ -6,8 +6,8 @@ This is a work log for an implementation in progress, not a claim of DLSS suppor ## Milestone and gates -Current milestone: M1/M2/M4/M5 passed; NGX init + DLSS CUDA capability discovery -passed with the user's DLLs. M6 is in progress: K `enc1` and `enc2` prep and +Current milestone: M1/M2/M4/M5 passed; native NGX Create/Evaluate on the simple +requested E path passes with the user's DLLs. M6 is in progress: K `enc1` and `enc2` prep and transformers run on gfx1201 and pass synthetic nonzero numpy reference checks. Do not integrate NGX until the integer PTX workload is stable on the real GPU. @@ -16,7 +16,7 @@ Do not integrate NGX until the integer PTX workload is stable on the real GPU. | M0: branch, audit, Windows build and diagnostics | Initial implementation complete | | M1: native HIP gfx1201 allocation, kernel, CPU verification | PASS, 32 iterations + guard verification | | M2: Windows ZLUDA integer PTX, primary and created contexts | PASS, 32 iterations each + guard verification | -| M3: Windows NGX initialization and simple DLSS path | Init + SR capabilities PASS; Evaluate/transformer pending | +| M3: Windows NGX initialization and simple DLSS path | Init, SR capabilities, Create/Evaluate PASS; four finite synthetic frames; K/M pending | | M4: D3D12 / HIP external memory and fence round trip | PASS with TheRock; stable 7.2 has mapped-view leak | | M5: independently validated gfx12 WMMA backend | PASS: raw + legacy adapter + upstream layout, max abs/relative error 0 | | M6: K layers, full transformer and image validation | Partial: `enc1`/`enc2` execute and match nonzero references; other layers/full network pending | @@ -68,8 +68,8 @@ and its `enc1`/`enc2` objects were checked on this RX 9070 XT against our fixtur unvalidated until K and its full DLSS integration are correct. * New ZLUDA patches `0006`/`0007` add k8 WMMA, prep slots, wave64 for MMA-free kernels, gfx12 helpers and FP8 selection/cache identity. These patches are - present in our branch but have not been built or validated in Windows ZLUDA. - Our current integer PTX test uses the unpatched `v7-preview.11` binary. + now built into native Windows ZLUDA. Integer PTX and resource descriptor + tests pass on hardware; complete K/M lowering remains to be validated. * Upstream restored `kernels/tools/model_enc3.py`, resolving the missing M reference dependency found in the initial audit. New check/replay utilities cover K/M and texture tails. Texture artifacts still require local DLL/PTX @@ -121,6 +121,95 @@ build the d4r-patched Windows ZLUDA and implement same-frame Windows NGX Evaluat with explicit D3D12 queue submission ownership. Full K precedes M and native FP8/performance work. No complete DLSS or OptiScaler game result is claimed. +## Native Windows ZLUDA build and Evaluate work (2026-09-30) + +The local fork now builds natively under Windows, including d4r patches +`0002`–`0007`. New `0008-native-windows-build.patch` preserves MSVC/Linux builds +and adds LLVM-MinGW support: prebuilt LLVM selection, GNU delay-load flags, +Windows LLVM system-library propagation, configurable HiGHS C++ library, +public OCKL assertion reporting, and optional TaskDialog lookup. It does not +modify NVIDIA binaries. The SDK's `long` shuffle overloads need an LLP64 fix +in a private header mirror; `build-zluda-helpers.ps1` applies it without touching +the installed HIP SDK. Helper bitcode is rebuilt in all four wave/FP variants. + +Pinned additional build dependencies: + +* Portable official Rust/Cargo `1.98.1`, `x86_64-pc-windows-gnu`, release + `2026-09-03`. `setup-rust-toolchain.ps1` verifies SHA256 of rustc, Cargo, + rust-std and rust-mingw archives and installs only under `.tools`. +* ZLUDA LLVM submodule `ff4dc1f7c9e1c64d4d69e40f4ed30c2280a96dfd`, + `llvm-config` reports `22.0.0git`. AMDGPU/LLVM/LLD are built with LLVM-MinGW + `20260922`; no Linux build host or runtime is used. +* HiGHS submodule `364c83a51e44ba6c27def9c8fc1a49b1daf5ad5c`. +* The LLVM IR helper producer is stable HIP 7.2/LLVM 21. The final target is + selected by the translator from HIP `gfx1201`; generic helper generation + strips its temporary target attributes, as the existing Linux builder does. +* `dist/zluda-windows-native` contains the built CUDA/NVAPI/trace DLLs, + `d4r_emit.exe`, local open-source libc++/libunwind DLLs and build hashes. + This directory contains no NVIDIA DLLs, extracted PTX or weights. + +Build sequence after preparing the pinned ZLUDA source/submodules: + +```powershell +powershell -NoProfile -File scripts/windows/setup-rust-toolchain.ps1 +powershell -NoProfile -File scripts/windows/prepare-zluda-source.ps1 +powershell -NoProfile -File scripts/windows/build-zluda-llvm.ps1 +powershell -NoProfile -File scripts/windows/build-zluda-helpers.ps1 +powershell -NoProfile -File scripts/windows/build-zluda-windows.ps1 +``` + +`prepare-zluda-source.ps1` refuses to reapply patches over a modified checkout. +For an already prepared checkout, rebuild only the affected components. +Configure/fetch/build logs remain under `build/zluda-*`. + +The NGX probe now accepts `--ngx-mode evaluate`, `--preset 5|11|13` and a frame +count. Its synthetic CUDA-array path is a **debug harness** with host uploads +and final readback, not the game's fast path. It initializes output to NaN, +checks all RGBA components for finite values, checks RGB variance and saves +unmodified `.rgba16f` plus BMP previews. A successful Evaluate alone is logged +as `network_validation=pending`. The diagnostic exception handler also saves +first-chance fault details/minidumps before NGX's own final filter takes over. + +The first stock-ZLUDA E test failed during CreateFeature (`0xc0000005`); +trace recorded missing `cuda_histogram_kernel`/`cuda_dldn_engine_histogram_kernel` +with unrecognized `tex.base.2d.v4.f32.s32`. This is a captured failure, not a +successful DLSS result. Bundle: `test-results/ngx-evaluate-e-stock.zip`. +The first custom build then exposed missing libc++ deployment and an eager +Common Controls v6 import; both are corrected. `0009-resource-descriptor-queries` +implements texture and 2D/3D array queries. Texture descriptors are tracked +from creation and removed on destruction: probing a surface as a texture +returns an API error instead of dereferencing HIP host metadata. This also +preserves CUDA flags/reserved bytes, which HIP's query does not fully initialize. + +Hardware evidence with the patched runtime: + +* Full runner `native-zluda-descriptors-milestone`: **12/12 PASS**, with 32 PTX + iterations per context, 32 CUDA image iterations and unchanged HIP/K/interop + checks. Package/default DLL selection prefers the locally built runtime. +* `cuda-images-kind.stdout.log`: 32 iterations of 1/2/4-channel arrays, + texture/surface descriptor and storage round trips, plus wrong-kind errors. +* `ngx-evaluate-e-kindfix`: requested preset E Create/Evaluate **PASS** for one synthetic + frame, finite RGBA, nonconstant output saved in the private result bundle. + This establishes the first native Windows DLSS CUDA execution path. K/M + transformer validation and D3D12 game integration are still pending. +* `ngx-evaluate-e-four-frames`: four consecutive finite synthetic frames, + reset only on frame 0, saved RGBA16F/BMP outputs. CTest after self-contained + CUDA dependency lookup: **9/9 PASS**. Fresh pinned-source application of all + eight patches passes `check-zluda-patches.ps1`. +* First-chance capture successfully saved two minidumps for the preceding + surface-as-texture HIP crash; `ngx-evaluate-e-arraydesc.zip` preserves it. + +Example focused diagnostic (after the full foundational checks), using the +user's local DLLs: + +```powershell +powershell -NoProfile -File scripts/windows/test-windows-rdna4.ps1 -RuntimeProfile therock -NgxOnly -NgxMode evaluate -Preset 5 -Iterations 4 -NgxCore C:\Users\Administrator\d4r\_nvngx.dll -DlssDll C:\Users\Administrator\d4r\nvngx_dlss.dll +``` + +`-NgxOnly` is for iterative NGX debugging; its summary explicitly says that +foundational checks were skipped. Private trace bundles can contain extracted +NVIDIA PTX/weights and must not be included in public release packages. + ## Architecture and platform boundaries Existing path: diff --git a/patches/zluda/0008-native-windows-build.patch b/patches/zluda/0008-native-windows-build.patch new file mode 100644 index 0000000..013c0a0 --- /dev/null +++ b/patches/zluda/0008-native-windows-build.patch @@ -0,0 +1,203 @@ +Windows LLVM-MinGW build, portable helper assertions and optional diagnostic UI. +Apply after upstream d4r patches 0002 through 0007, on ZLUDA ee2f25a. + +diff --git a/ext/highs-sys/build.rs b/ext/highs-sys/build.rs +--- a/ext/highs-sys/build.rs ++++ b/ext/highs-sys/build.rs +@@ -66,11 +66,12 @@ + let linux = target.contains("linux"); + let mingw = target.contains("pc-windows-gnu"); + if apple { + println!("cargo:rustc-link-lib=dylib=c++"); + } else if linux || mingw { +- println!("cargo:rustc-link-lib=dylib=stdc++"); ++ let cxx_library = env::var("CXXSTDLIB").unwrap_or_else(|_| "stdc++".to_owned()); ++ println!("cargo:rustc-link-lib=dylib={cxx_library}"); + } + println!("cargo:rerun-if-changed=../HiGHS/highs/interfaces/highs_c_api.h"); + + true + } +diff --git a/ext/hip_runtime-sys/build.rs b/ext/hip_runtime-sys/build.rs +--- a/ext/hip_runtime-sys/build.rs ++++ b/ext/hip_runtime-sys/build.rs +@@ -1,8 +1,12 @@ + fn main() { + if cfg!(windows) { +- println!("cargo:rustc-link-arg=/DELAYLOAD:amdhip64_7.dll"); ++ if std::env::var("CARGO_CFG_TARGET_ENV").as_deref() == Ok("gnu") { ++ println!("cargo:rustc-link-arg=-Wl,--delayload=amdhip64_7.dll"); ++ } else { ++ println!("cargo:rustc-link-arg=/DELAYLOAD:amdhip64_7.dll"); ++ } + } else { + println!("cargo:rustc-link-lib=dylib=amdhip64"); + println!("cargo:rustc-link-search=native=/opt/rocm/lib/"); + } + } +diff --git a/llvm_zluda/build.rs b/llvm_zluda/build.rs +--- a/llvm_zluda/build.rs ++++ b/llvm_zluda/build.rs +@@ -19,10 +19,18 @@ + + fn main() { + let bc_path1 = "src/device-libs/ockl.bc"; + println!("cargo:rerun-if-changed={}", bc_path1); + check_lfs_file(bc_path1); ++ ++ println!("cargo:rerun-if-env-changed=ZLUDA_LLVM_BUILD_DIR"); ++ if let Some(directory) = env::var_os("ZLUDA_LLVM_BUILD_DIR") { ++ let llvm_build_path = PathBuf::from(directory); ++ println!("cargo:build-path={}", llvm_build_path.display()); ++ link_prebuilt_llvm(&llvm_build_path); ++ return; ++ } + + let mut cmake = Config::new(r"../ext/llvm-project/llvm"); + try_use_sccache(&mut cmake); + try_use_ninja(&mut cmake); + cmake +@@ -57,16 +65,31 @@ + cmake.build(); + } + let cmake_profile = cmake.get_profile(); + let llvm_build_path = get_llvm_build_path(&llvm_dir, cmake_profile); + println!("cargo:build-path={}", llvm_build_path.display()); ++ link_prebuilt_llvm(&llvm_build_path); ++} ++ ++fn link_prebuilt_llvm(llvm_build_path: &PathBuf) { + let (cxxflags, ldflags, libdir, lib_names, system_libs) = + llvm_config(&llvm_build_path.join("bin/llvm-config")).unwrap(); + println!("cargo:rustc-link-arg={ldflags}"); + println!("cargo:rustc-link-search=native={libdir}"); + for lib in system_libs.split_ascii_whitespace() { +- println!("cargo:rustc-link-arg={lib}"); ++ if let Some(name) = lib.strip_prefix("-l") { ++ println!("cargo:rustc-link-lib={name}"); ++ } else { ++ println!("cargo:rustc-link-arg={lib}"); ++ } ++ } ++ // llvm-config omits system libraries on Windows. Propagate these through ++ // this rlib to its final cdylib consumer (link-arg alone cannot do that). ++ if env::var("CARGO_CFG_TARGET_OS").as_deref() == Ok("windows") { ++ for library in ["psapi", "shell32", "ole32", "uuid", "advapi32", "ws2_32", "ntdll"] { ++ println!("cargo:rustc-link-lib={library}"); ++ } + } + link_lld_components(); + link_llvm_components(lib_names); + compile_cxx_lib(cxxflags, &llvm_build_path); + } +diff --git a/zluda/build.rs b/zluda/build.rs +--- a/zluda/build.rs ++++ b/zluda/build.rs +@@ -1,11 +1,15 @@ + use vergen_gix::{Emitter, Gix}; + + fn main() { + if cfg!(windows) { +- println!("cargo:rustc-link-arg=delayimp.lib"); +- println!("cargo:rustc-link-arg=/DELAYLOAD:amdhip64_7.dll"); ++ if std::env::var("CARGO_CFG_TARGET_ENV").as_deref() == Ok("gnu") { ++ println!("cargo:rustc-link-arg=-Wl,--delayload=amdhip64_7.dll"); ++ } else { ++ println!("cargo:rustc-link-arg=delayimp.lib"); ++ println!("cargo:rustc-link-arg=/DELAYLOAD:amdhip64_7.dll"); ++ } + let dll_path = "bin/nvcudart_hybrid64.dll"; + println!("cargo:rerun-if-changed={}", dll_path); + check_lfs_file(dll_path); + } + let git = Gix::builder().sha(false).build(); +diff --git a/zluda_windows/src/lib.rs b/zluda_windows/src/lib.rs +--- a/zluda_windows/src/lib.rs ++++ b/zluda_windows/src/lib.rs +@@ -11,27 +11,27 @@ + use uuid::uuid; + use widestring::{u16str, U16Str, U16String}; + use windows::{ + core::{w, HRESULT, PCWSTR}, + Win32::{ +- Foundation::{HANDLE, HINSTANCE, HMODULE, HWND, LPARAM, S_OK, WPARAM}, ++ Foundation::{FreeLibrary, HANDLE, HINSTANCE, HMODULE, HWND, LPARAM, S_OK, WPARAM}, + System::{ + JobObjects::{ + AssignProcessToJobObject, JobObjectExtendedLimitInformation, + SetInformationJobObject, JOBOBJECT_EXTENDED_LIMIT_INFORMATION, + JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE, + }, + LibraryLoader::{ +- GetModuleFileNameA, GetModuleFileNameW, GetModuleHandleExW, LoadLibraryExW, ++ GetModuleFileNameA, GetModuleFileNameW, GetModuleHandleExW, GetProcAddress, LoadLibraryExW, + GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS, + GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT, LOAD_LIBRARY_SEARCH_DEFAULT_DIRS, +- LOAD_LIBRARY_SEARCH_DLL_LOAD_DIR, ++ LOAD_LIBRARY_SEARCH_DLL_LOAD_DIR, LOAD_LIBRARY_SEARCH_SYSTEM32, + }, + }, + UI::{ + Controls::{ +- TaskDialogIndirect, TASKDIALOGCONFIG, TASKDIALOGCONFIG_0, TASKDIALOGCONFIG_1, ++ TASKDIALOGCONFIG, TASKDIALOGCONFIG_0, TASKDIALOGCONFIG_1, + TASKDIALOG_NOTIFICATIONS, TDCBF_CLOSE_BUTTON, TDF_ENABLE_HYPERLINKS, + TDN_HYPERLINK_CLICKED, TD_ERROR_ICON, + }, + WindowsAndMessaging::IDCLOSE, + }, +@@ -487,11 +487,23 @@ + pszFooter: PCWSTR::null(), + pfCallback: Some(task_dialog_callback), + lpCallbackData: 0, + cxWidth: 0, + }; +- TaskDialogIndirect(&config, None, None, None).ok(); ++ // A CUDA DLL may be loaded by an executable with no Common Controls ++ // v6 activation manifest. Do not make its diagnostic UI an eager DLL ++ // import: comctl32 v5 has no TaskDialogIndirect export. ++ if let Ok(controls) = LoadLibraryExW(w!("comctl32.dll"), None, LOAD_LIBRARY_SEARCH_SYSTEM32) { ++ if let Some(raw) = GetProcAddress(controls, windows::core::s!("TaskDialogIndirect")) { ++ let show: unsafe extern "system" fn(*const TASKDIALOGCONFIG, *mut i32, *mut i32, *mut i32) -> HRESULT = ++ mem::transmute(raw); ++ let _ = show(&config, ptr::null_mut(), ptr::null_mut(), ptr::null_mut()); ++ } else { ++ eprintln!("ZLUDA failed to load {}; set HIP_PATH to the selected HIP SDK", redirect_names[0]); ++ } ++ let _ = FreeLibrary(controls); ++ } + } + result + } + + unsafe extern "system" fn task_dialog_callback( +diff --git a/ptx/lib/zluda_ptx_impl.cpp b/ptx/lib/zluda_ptx_impl.cpp +--- a/ptx/lib/zluda_ptx_impl.cpp ++++ b/ptx/lib/zluda_ptx_impl.cpp +@@ -405,11 +405,26 @@ + uint32_t line, + uint64_t function, + uint64_t char_size) + { + (void)char_size; ++#if defined(_WIN32) || defined(_WIN64) ++ // HIP on Windows only supplies the wide _wassert entry point. Keep the ++ // CUDA assertion's ASCII diagnostics and trap through public OCKL APIs. ++ auto output = __ockl_fprintf_stderr_begin(); ++ const char* parts[] = {"CUDA assertion: ", (const char*)message, " at ", (const char*)file, ++ " in ", (const char*)function, "\n"}; ++ for (const char* part : parts) { ++ const char* end = part; ++ while (*end) ++end; ++ output = __ockl_fprintf_append_string_n(output, part, end - part, 0); ++ } ++ __ockl_fprintf_append_args(output, 1, line, 0, 0, 0, 0, 0, 0, 1); ++ __builtin_trap(); ++#else + [[clang::noinline]] __assert_fail((const char *)message, (const char *)file, line, (const char *)function); ++#endif + } + + // * Smallest denormal is 1.4 × 10^-45 + // * Smallest normal is ~1.175494351 × 10^(-38) + // * Now, 1.175494351×10^-38 / 1.4 × 10^-45 = 8396388 + 31/140 diff --git a/patches/zluda/0009-resource-descriptor-queries.patch b/patches/zluda/0009-resource-descriptor-queries.patch new file mode 100644 index 0000000..fce5e19 --- /dev/null +++ b/patches/zluda/0009-resource-descriptor-queries.patch @@ -0,0 +1,124 @@ +Implement the CUDA texture resource descriptor query required by NGX Evaluate. +Regression: tools/windows/cuda_image_probe.cpp in the d4r Windows port. + +diff --git a/zluda/src/lib.rs b/zluda/src/lib.rs +--- a/zluda/src/lib.rs ++++ b/zluda/src/lib.rs +@@ -67,12 +67,14 @@ + cuda_macros::cuda_function_declarations!( + unimplemented, + implemented + <= [ + cuArray3DCreate_v2, ++ cuArray3DGetDescriptor_v2, + cuArrayCreate_v2, + cuArrayDestroy, ++ cuArrayGetDescriptor_v2, + cuCtxCreate_v2, + cuCtxCreate_v4, + cuCtxDestroy_v2, + cuCtxGetApiVersion, + cuCtxGetCurrent, +@@ -203,10 +205,11 @@ + cuSurfObjectCreate, + cuSurfObjectDestroy, + cuSurfObjectGetResourceDesc, + cuTexObjectCreate, + cuTexObjectDestroy, ++ cuTexObjectGetResourceDesc, + cuTexRefSetAddress_v2, + cuTexRefSetAddressMode, + cuTexRefSetArray, + cuTexRefSetFilterMode, + cuTexRefSetFlags, +diff --git a/zluda/src/impl/tex.rs b/zluda/src/impl/tex.rs +--- a/zluda/src/impl/tex.rs ++++ b/zluda/src/impl/tex.rs +@@ -1,19 +1,58 @@ + use crate::r#impl::hipfix; + use hip_runtime_sys::*; ++use std::collections::HashMap; ++use std::sync::{Mutex, OnceLock}; ++ ++fn object_resources() -> &'static Mutex>> { ++ static RESOURCES: OnceLock>>> = OnceLock::new(); ++ RESOURCES.get_or_init(|| Mutex::new(HashMap::new())) ++} + + pub(crate) unsafe fn object_create( + p_tex_object: *mut hipTextureObject_t, + p_res_desc: *const HIP_RESOURCE_DESC, + p_tex_desc: *const HIP_TEXTURE_DESC, + p_res_view_desc: *const HIP_RESOURCE_VIEW_DESC, + ) -> hipError_t { +- hipTexObjectCreate(p_tex_object, p_res_desc, p_tex_desc, p_res_view_desc) ++ let output = p_tex_object.as_mut().ok_or(hipErrorCode_t::InvalidValue)?; ++ let descriptor = p_res_desc.as_ref().ok_or(hipErrorCode_t::InvalidValue)?; ++ hipTexObjectCreate(output, descriptor, p_tex_desc, p_res_view_desc)?; ++ let bytes = std::slice::from_raw_parts(p_res_desc.cast::(), std::mem::size_of::()).to_vec(); ++ match object_resources().lock() { ++ Ok(mut resources) => { ++ resources.insert(*output as usize, bytes); ++ Ok(()) ++ } ++ Err(_) => { ++ let _ = hipDestroyTextureObject(*output); ++ *output = std::ptr::null_mut(); ++ Err(hipErrorCode_t::Unknown) ++ } ++ } + } + + pub(crate) unsafe fn object_destroy(tex_object: hipTextureObject_t) -> hipError_t { +- hipDestroyTextureObject(tex_object) ++ let mut resources = object_resources().lock().map_err(|_| hipErrorCode_t::Unknown)?; ++ if !resources.contains_key(&(tex_object as usize)) { return Err(hipErrorCode_t::InvalidValue); } ++ hipDestroyTextureObject(tex_object)?; ++ resources.remove(&(tex_object as usize)); ++ Ok(()) ++} ++ ++pub(crate) unsafe fn object_get_resource_desc( ++ descriptor: &mut HIP_RESOURCE_DESC, ++ tex_object: hipTextureObject_t, ++) -> hipError_t { ++ // NGX probes surface handles as textures before trying the surface API. ++ // Passing that foreign handle to HIP dereferences invalid host metadata. ++ // Return a CUDA error for unknown handles and preserve the complete CUDA ++ // descriptor (HIP's query leaves the flags/reserved bytes unspecified). ++ let resources = object_resources().lock().map_err(|_| hipErrorCode_t::Unknown)?; ++ let bytes = resources.get(&(tex_object as usize)).ok_or(hipErrorCode_t::InvalidValue)?; ++ std::ptr::copy_nonoverlapping(bytes.as_ptr(), (descriptor as *mut HIP_RESOURCE_DESC).cast::(), bytes.len()); ++ Ok(()) + } + + pub(crate) unsafe fn ref_set_array( + texref: *mut textureReference, + array: hipArray_t, +diff --git a/zluda/src/impl/array.rs b/zluda/src/impl/array.rs +--- a/zluda/src/impl/array.rs ++++ b/zluda/src/impl/array.rs +@@ -70,5 +70,23 @@ + handle: *mut hipArray_t, + desc: *const HIP_ARRAY3D_DESCRIPTOR, + ) -> hipError_t { + hipArray3DCreate(handle, desc) + } ++ ++pub(crate) unsafe fn get_descriptor_v2( ++ output: &mut HIP_ARRAY_DESCRIPTOR, ++ array: hipArray_t, ++) -> hipError_t { ++ hipArrayGetDescriptor(output, array)?; ++ if let Some(format) = original_format(array)? { ++ output.Format = hipArray_Format(format); ++ } ++ Ok(()) ++} ++ ++pub(crate) unsafe fn _3d_get_descriptor_v2( ++ output: &mut HIP_ARRAY3D_DESCRIPTOR, ++ array: hipArray_t, ++) -> hipError_t { ++ hipArray3DGetDescriptor(output, array) ++} diff --git a/scripts/windows/build-zluda-helpers.ps1 b/scripts/windows/build-zluda-helpers.ps1 new file mode 100644 index 0000000..16e8bea --- /dev/null +++ b/scripts/windows/build-zluda-helpers.ps1 @@ -0,0 +1,70 @@ +[CmdletBinding()] +param([string]$SourceRoot, [string]$HipRoot = 'C:\Program Files\AMD\ROCm\7.2', [string]$WorkRoot) +$ErrorActionPreference = 'Stop' +$repo = [IO.Path]::GetFullPath((Join-Path $PSScriptRoot '../..')) +if (!$SourceRoot) { $SourceRoot = Join-Path $repo 'external/ZLUDA' } +if (!$WorkRoot) { $WorkRoot = Join-Path $repo 'build/zluda-helpers-windows' } +$toolchain = Join-Path $repo '.tools/llvm-mingw-20260922-ucrt-x86_64' +$compiler = Join-Path $HipRoot 'bin/clang++.exe' +$dis = Join-Path $HipRoot 'bin/llvm-dis.exe' +$assembler = Join-Path $HipRoot 'bin/llvm-as.exe' +$ocml = Join-Path $HipRoot 'amdgcn/bitcode/ocml.bc' +foreach ($file in @($compiler,$dis,$assembler,$ocml)) { + if (!(Test-Path $file)) { throw "HIP helper dependency missing: $file" } +} +New-Item -ItemType Directory -Force $WorkRoot | Out-Null +$configInclude = Join-Path $WorkRoot 'libcxx-config' +New-Item -ItemType Directory -Force $configInclude | Out-Null +Copy-Item -LiteralPath (Join-Path $toolchain 'include/c++/v1/__config_site') -Destination $configInclude -Force +Copy-Item -LiteralPath (Join-Path $SourceRoot 'ext/llvm-project/libcxx/vendor/llvm/default_assertion_handler.in') ` + -Destination (Join-Path $configInclude '__assertion_handler') -Force +# HIP 7.2's long shuffle overloads assume every non-MSVC compiler has LP64. +# Windows GNU also has 32-bit long. Correct that public header in a private +# mirror; the installed SDK remains untouched and the GPU target is unchanged. +$hipInclude = Join-Path $WorkRoot 'hip-include' +New-Item -ItemType Directory -Force $hipInclude | Out-Null +Copy-Item -LiteralPath (Join-Path $HipRoot 'include/hip') -Destination $hipInclude -Recurse -Force +$warpHeader = Join-Path $hipInclude 'hip/amd_detail/amd_warp_functions.h' +$warpSource = [IO.File]::ReadAllText($warpHeader) +$warpSource = $warpSource.Replace('#ifndef _MSC_VER', '#if __SIZEOF_LONG__ == 8') +[IO.File]::WriteAllText($warpHeader,$warpSource,[Text.UTF8Encoding]::new($false)) +$variants = @( + @{Suffix=''; Flags=@()}, + @{Suffix='_constrained'; Flags=@('-ffp-model=strict','-ffp-exception-behavior=ignore')}, + @{Suffix='_w64'; Flags=@('-mwavefrontsize64','-DD4R_WAVE64')}, + @{Suffix='_constrained_w64'; Flags=@('-ffp-model=strict','-ffp-exception-behavior=ignore','-mwavefrontsize64','-DD4R_WAVE64')} +) +foreach ($variant in $variants) { + $name = 'zluda_ptx_impl' + $variant.Suffix + $raw = Join-Path $WorkRoot "$name.raw.bc" + $ir = Join-Path $WorkRoot "$name.ll" + $output = Join-Path $SourceRoot "ptx/lib/$name.bc" + $arguments = @('-x','hip','-std=c++20','--target=x86_64-w64-windows-gnu','-stdlib=libc++', + '--offload-arch=gfx1030','--offload-device-only','-emit-llvm','-c','-nogpulib','-O3', + '-mno-wavefrontsize64','-DHIP_ENABLE_WARP_SYNC_BUILTINS', + '-Xclang','-fdenormal-fp-math=dynamic', + '-isystem',(Join-Path $SourceRoot 'ext/llvm-project/libcxx/include'), + '-isystem',$configInclude, + '-isystem',(Join-Path $toolchain 'include'), + '-I',$hipInclude, + '-Xclang','-mlink-bitcode-file','-Xclang',$ocml) + $arguments += $variant.Flags + $arguments += @('-o',$raw,(Join-Path $SourceRoot 'ptx/lib/zluda_ptx_impl.cpp')) + $ErrorActionPreference = 'Continue' + & $compiler @arguments 2>&1 | Tee-Object (Join-Path $WorkRoot "$name.compile.log") + $ErrorActionPreference = 'Stop' + if ($LASTEXITCODE) { throw "PTX helper compile failed: $name ($LASTEXITCODE)" } + & $dis $raw -o $ir + if ($LASTEXITCODE) { throw "Disassemble failed: $raw" } + $clean = [Collections.Generic.List[string]]::new() + foreach ($line in [IO.File]::ReadLines($ir)) { + if ($line -match '@llvm.used|wchar_size|llvm.module.flags|__hip_cuid') { continue } + $line = $line -replace 'optnone','' -replace 'define hidden','define linkonce_odr' + $line = $line -replace '"target-cpu"="gfx1030"','' -replace '"target-features"="[^"]+"','' + $clean.Add($line) + } + [IO.File]::WriteAllLines($ir,$clean,[Text.UTF8Encoding]::new($false)) + & $assembler $ir -o $output + if ($LASTEXITCODE) { throw "Assemble failed: $ir" } + Write-Host "Built $output ($((Get-Item $output).Length) bytes)" +} diff --git a/scripts/windows/build-zluda-llvm.ps1 b/scripts/windows/build-zluda-llvm.ps1 new file mode 100644 index 0000000..345a46a --- /dev/null +++ b/scripts/windows/build-zluda-llvm.ps1 @@ -0,0 +1,40 @@ +[CmdletBinding()] +param([string]$SourceRoot, [string]$BuildRoot, [int]$Jobs = 4) +$ErrorActionPreference = 'Stop' +$repo = [IO.Path]::GetFullPath((Join-Path $PSScriptRoot '../..')) +if (!$SourceRoot) { $SourceRoot = Join-Path $repo 'external/ZLUDA' } +if (!$BuildRoot) { $BuildRoot = Join-Path $repo 'build/zluda-llvm-windows-gnu' } +$toolchain = Join-Path $repo '.tools/llvm-mingw-20260922-ucrt-x86_64' +$cmake = Join-Path $repo '.tools/python/cmake/data/bin/cmake.exe' +$ninja = Join-Path $repo '.tools/python/bin/ninja.exe' +if (!(Test-Path $ninja)) { $ninja = Join-Path $repo '.tools/python/ninja/data/bin/ninja.exe' } +if (!(Test-Path (Join-Path $SourceRoot 'ext/llvm-project/llvm/CMakeLists.txt'))) { + throw 'Run git submodule update --init --depth 1 ext/llvm-project in the ZLUDA checkout.' +} +New-Item -ItemType Directory -Force $BuildRoot | Out-Null +$oldPath = $env:PATH +try { + $env:PATH = "$(Join-Path $toolchain 'bin');$oldPath" + $arguments = @('-S', (Join-Path $SourceRoot 'ext/llvm-project/llvm'), '-B', $BuildRoot, + '-G', 'Ninja', "-DCMAKE_MAKE_PROGRAM=$ninja", '-DCMAKE_BUILD_TYPE=Release', + "-DCMAKE_C_COMPILER=$(Join-Path $toolchain 'bin/x86_64-w64-mingw32-clang.exe')", + "-DCMAKE_CXX_COMPILER=$(Join-Path $toolchain 'bin/x86_64-w64-mingw32-clang++.exe')", + '-DLLVM_TARGETS_TO_BUILD=AMDGPU', '-DLLVM_ENABLE_PROJECTS=llvm;lld', + '-DLLVM_ENABLE_WARNINGS=OFF', '-DLLVM_ENABLE_TERMINFO=OFF', '-DLLVM_ENABLE_LIBXML2=OFF', + '-DLLVM_ENABLE_LIBEDIT=OFF', '-DLLVM_ENABLE_LIBPFM=OFF', '-DLLVM_ENABLE_ZLIB=OFF', + '-DLLVM_ENABLE_ZSTD=OFF', '-DLLVM_INCLUDE_BENCHMARKS=OFF', '-DLLVM_INCLUDE_EXAMPLES=OFF', + '-DLLVM_INCLUDE_TESTS=OFF', '-DLLVM_BUILD_TESTS=OFF', '-DLLVM_BUILD_TOOLS=OFF', + '-DLLVM_OPTIMIZED_TABLEGEN=ON', '-DLLVM_PARALLEL_LINK_JOBS=1') + $ErrorActionPreference = 'Continue' + & $cmake @arguments 2>&1 | Tee-Object (Join-Path $BuildRoot 'configure.log') + $ErrorActionPreference = 'Stop' + if ($LASTEXITCODE) { throw "LLVM configure failed ($LASTEXITCODE)" } + $ErrorActionPreference = 'Continue' + & $cmake --build $BuildRoot --parallel $Jobs --target llvm-config llvm-libraries lldCommon lldELF ` + 2>&1 | Tee-Object (Join-Path $BuildRoot 'build.log') + $ErrorActionPreference = 'Stop' + if ($LASTEXITCODE) { throw "LLVM build failed ($LASTEXITCODE)" } + & (Join-Path $BuildRoot 'bin/llvm-config.exe') --version + if ($LASTEXITCODE) { throw 'llvm-config verification failed' } + Write-Host "Native Windows LLVM: $BuildRoot" +} finally { $env:PATH = $oldPath } diff --git a/scripts/windows/build-zluda-windows.ps1 b/scripts/windows/build-zluda-windows.ps1 new file mode 100644 index 0000000..15f2725 --- /dev/null +++ b/scripts/windows/build-zluda-windows.ps1 @@ -0,0 +1,76 @@ +[CmdletBinding()] +param([string]$SourceRoot, [string]$LlvmBuildRoot, [string]$InstallRoot, [switch]$FetchOnly, [int]$Jobs = 4) +$ErrorActionPreference = 'Stop' +$repo = [IO.Path]::GetFullPath((Join-Path $PSScriptRoot '../..')) +if (!$SourceRoot) { $SourceRoot = Join-Path $repo 'external/ZLUDA' } +if (!$LlvmBuildRoot) { $LlvmBuildRoot = Join-Path $repo 'build/zluda-llvm-windows-gnu' } +if (!$InstallRoot) { $InstallRoot = Join-Path $repo 'dist/zluda-windows-native' } +$rust = Join-Path $repo '.tools/rust-1.98.1-gnu' +$toolchain = Join-Path $repo '.tools/llvm-mingw-20260922-ucrt-x86_64' +$target = Join-Path $repo 'build/zluda-rust-windows-gnu' +New-Item -ItemType Directory -Force $target,$InstallRoot | Out-Null +$settings = @{ + PATH="$(Join-Path $rust 'bin');$(Join-Path $toolchain 'bin');$(Join-Path $repo '.tools/python/cmake/data/bin');$(Join-Path $repo '.tools/python/bin');C:\Program Files\Git\usr\bin;$env:PATH" + CARGO_HOME=(Join-Path $repo '.tools/cargo-home') + CARGO_TARGET_DIR=$target + CARGO_BUILD_JOBS="$Jobs" + RUSTC=(Join-Path $rust 'bin/rustc.exe') + CC=(Join-Path $toolchain 'bin/x86_64-w64-mingw32-clang.exe') + CXX=(Join-Path $toolchain 'bin/x86_64-w64-mingw32-clang++.exe') + AR=(Join-Path $toolchain 'bin/llvm-ar.exe') + CXXSTDLIB='c++' + LIBRARY_PATH=(Join-Path $rust 'lib/rustlib/x86_64-pc-windows-gnu/lib/self-contained') + CARGO_TARGET_X86_64_PC_WINDOWS_GNU_LINKER=(Join-Path $toolchain 'bin/x86_64-w64-mingw32-clang.exe') + CARGO_TARGET_X86_64_PC_WINDOWS_GNU_RUSTFLAGS='-C link-arg=-static' + ZLUDA_LLVM_BUILD_DIR=$LlvmBuildRoot +} +$previous = @{} +foreach ($name in $settings.Keys) { + $previous[$name] = [Environment]::GetEnvironmentVariable($name,'Process') + [Environment]::SetEnvironmentVariable($name,$settings[$name],'Process') +} +Push-Location $SourceRoot +try { + $cargo = Join-Path $rust 'bin/cargo.exe' + if (!(Test-Path $cargo)) { throw 'Run setup-rust-toolchain.ps1 first.' } + if ($FetchOnly) { + $ErrorActionPreference = 'Continue' + & $cargo fetch --locked --target x86_64-pc-windows-gnu 2>&1 | Tee-Object (Join-Path $target 'fetch.log') + $ErrorActionPreference = 'Stop' + if ($LASTEXITCODE) { throw "Cargo fetch failed ($LASTEXITCODE)" } + return + } + if (!(Test-Path (Join-Path $LlvmBuildRoot 'bin/llvm-config.exe'))) { throw 'Run build-zluda-llvm.ps1 first.' } + foreach ($variant in @('','_constrained','_w64','_constrained_w64')) { + if (!(Test-Path "ptx/lib/zluda_ptx_impl$variant.bc")) { throw 'Run build-zluda-helpers.ps1 first.' } + } + $ErrorActionPreference = 'Continue' + & $cargo build --locked --release --target x86_64-pc-windows-gnu -p zluda -p nvapi -p zluda_trace ` + 2>&1 | Tee-Object (Join-Path $target 'build.log') + $ErrorActionPreference = 'Stop' + if ($LASTEXITCODE) { throw "ZLUDA build failed ($LASTEXITCODE)" } + $ErrorActionPreference = 'Continue' + & $cargo build --locked --release --target x86_64-pc-windows-gnu -p ptx --example d4r_emit ` + 2>&1 | Tee-Object (Join-Path $target 'emit-build.log') + $ErrorActionPreference = 'Stop' + if ($LASTEXITCODE) { throw "d4r_emit build failed ($LASTEXITCODE)" } + $release = Join-Path $target 'x86_64-pc-windows-gnu/release' + Copy-Item -LiteralPath (Join-Path $release 'nvcuda.dll') -Destination $InstallRoot -Force + Copy-Item -LiteralPath (Join-Path $release 'nvapi64.dll') -Destination $InstallRoot -Force + # The C++ dependencies select libc++ dynamically even when Rust requests + # static GNU support libraries. Keep these portable runtime DLLs local. + foreach ($runtime in @('libc++.dll', 'libunwind.dll')) { + Copy-Item -LiteralPath (Join-Path $toolchain "bin/$runtime") -Destination $InstallRoot -Force + } + New-Item -ItemType Directory -Force (Join-Path $InstallRoot 'trace') | Out-Null + Copy-Item -LiteralPath (Join-Path $release 'zluda_trace.dll') -Destination (Join-Path $InstallRoot 'trace/nvcuda.dll') -Force + Copy-Item -LiteralPath (Join-Path $release 'examples/d4r_emit.exe') -Destination $InstallRoot -Force + @{base='ee2f25a180099fa42f36b2346732e1f2470a03ad'; target='x86_64-pc-windows-gnu'; + rust='1.98.1'; llvm=(& (Join-Path $LlvmBuildRoot 'bin/llvm-config.exe') --version); + files=@(Get-ChildItem -LiteralPath $InstallRoot -Filter '*.dll' | Get-FileHash -Algorithm SHA256)} | + ConvertTo-Json -Depth 5 | Set-Content -LiteralPath (Join-Path $InstallRoot 'build-info.json') -Encoding UTF8 + Write-Host "Native Windows d4r ZLUDA runtime: $InstallRoot" +} finally { + Pop-Location + foreach ($name in $previous.Keys) { [Environment]::SetEnvironmentVariable($name,$previous[$name],'Process') } +} diff --git a/scripts/windows/check-zluda-patches.ps1 b/scripts/windows/check-zluda-patches.ps1 new file mode 100644 index 0000000..04dbb0a --- /dev/null +++ b/scripts/windows/check-zluda-patches.ps1 @@ -0,0 +1,25 @@ +[CmdletBinding()] +param([string]$SourceRoot) +$ErrorActionPreference = 'Stop' +$repo = [IO.Path]::GetFullPath((Join-Path $PSScriptRoot '../..')) +if (!$SourceRoot) { $SourceRoot = Join-Path $repo 'external/ZLUDA' } +$base = 'ee2f25a180099fa42f36b2346732e1f2470a03ad' +$relative = 'build/zluda-patch-check-' + [Guid]::NewGuid().ToString('N') +$snapshot = Join-Path $repo $relative +New-Item -ItemType Directory -Force $snapshot | Out-Null +$archive = "$snapshot.tar" +& git -C $SourceRoot archive --format=tar "--output=$archive" $base +if ($LASTEXITCODE) { throw 'Could not archive the pinned ZLUDA base.' } +& tar.exe -xf $archive -C $snapshot +if ($LASTEXITCODE) { throw 'Could not extract the ZLUDA patch-check snapshot.' } +Push-Location $repo +try { + foreach ($patch in (Get-ChildItem -LiteralPath (Join-Path $repo 'patches/zluda') -Filter '*.patch' | Sort-Object Name)) { + & git apply "--directory=$relative" --check $patch.FullName + if ($LASTEXITCODE) { throw "Fresh-source patch check failed: $($patch.Name)" } + & git apply "--directory=$relative" --whitespace=nowarn $patch.FullName + if ($LASTEXITCODE) { throw "Fresh-source patch apply failed: $($patch.Name)" } + Write-Host "PASS patch $($patch.Name)" + } + Write-Host "Pinned-source patch snapshot: $snapshot" +} finally { Pop-Location } diff --git a/scripts/windows/prepare-zluda-source.ps1 b/scripts/windows/prepare-zluda-source.ps1 new file mode 100644 index 0000000..668581a --- /dev/null +++ b/scripts/windows/prepare-zluda-source.ps1 @@ -0,0 +1,25 @@ +[CmdletBinding()] +param([string]$SourceRoot) +$ErrorActionPreference = 'Stop' +$repo = [IO.Path]::GetFullPath((Join-Path $PSScriptRoot '../..')) +if (!$SourceRoot) { $SourceRoot = Join-Path $repo 'external/ZLUDA' } +$SourceRoot = [IO.Path]::GetFullPath($SourceRoot) +$base = 'ee2f25a180099fa42f36b2346732e1f2470a03ad' +if (!(Test-Path (Join-Path $SourceRoot 'Cargo.toml'))) { throw 'Clone upstream ZLUDA into external/ZLUDA first.' } +Push-Location $SourceRoot +try { + $revision = (& git rev-parse HEAD).Trim() + if ($revision -ne $base) { throw "Expected ZLUDA $base, found $revision; review patches against the new revision." } + $dirty = @(& git status --porcelain --untracked-files=no --ignore-submodules=all) + if ($dirty.Count) { throw 'ZLUDA source has tracked modifications. Keep this prepared checkout; do not reapply patches.' } + & git switch -c windows-rdna4 + if ($LASTEXITCODE) { throw 'Could not create the local ZLUDA branch.' } + foreach ($patch in (Get-ChildItem -LiteralPath (Join-Path $repo 'patches/zluda') -Filter '*.patch' | Sort-Object Name)) { + & git apply --check $patch.FullName + if ($LASTEXITCODE) { throw "Patch check failed: $($patch.Name)" } + & git apply --whitespace=nowarn $patch.FullName + if ($LASTEXITCODE) { throw "Patch failed: $($patch.Name)" } + Write-Host "Applied $($patch.Name)" + } + Write-Host "Prepared Windows ZLUDA source at $SourceRoot (base $base)" +} finally { Pop-Location } diff --git a/scripts/windows/setup-rust-toolchain.ps1 b/scripts/windows/setup-rust-toolchain.ps1 new file mode 100644 index 0000000..6b572fe --- /dev/null +++ b/scripts/windows/setup-rust-toolchain.ps1 @@ -0,0 +1,48 @@ +[CmdletBinding()] +param([string]$ToolRoot, [string]$DownloadRoot) +$ErrorActionPreference = 'Stop' +$repo = [IO.Path]::GetFullPath((Join-Path $PSScriptRoot '../..')) +if (!$ToolRoot) { $ToolRoot = Join-Path $repo '.tools/rust-1.98.1-gnu' } +if (!$DownloadRoot) { $DownloadRoot = Join-Path $repo '.tools/downloads' } +$ToolRoot = [IO.Path]::GetFullPath($ToolRoot) +$DownloadRoot = [IO.Path]::GetFullPath($DownloadRoot) +New-Item -ItemType Directory -Force $ToolRoot,$DownloadRoot | Out-Null +# Pinned official Rust 2026-09-03 manifest. The toolchain is local to this +# workspace; rustup, PATH changes and machine-wide installation are unnecessary. +$components = @( + @{Name='rustc'; Hash='f0e8e33973771acc4d2f87891b19d4f3f2d3827e2e7848c091d7c070bd63479c'}, + @{Name='cargo'; Hash='4bd77f16bd2a26db6eacf9320414d3a792d9998cb5e9ac122280ba56126f2a44'}, + @{Name='rust-std'; Hash='5bb599a541fcb9c0edc00e512570f60d2262623f1e2a19a44cce3a7a97208788'}, + @{Name='rust-mingw'; Hash='75d898804789c12ca969365f0a86d85c3bca1fdb070f0be615ca513a986b2674'} +) +foreach ($component in $components) { + $package = "$($component.Name)-1.98.1-x86_64-pc-windows-gnu" + $archive = Join-Path $DownloadRoot "$package.tar.xz" + if (!(Test-Path $archive) -or (Get-FileHash -LiteralPath $archive -Algorithm SHA256).Hash -ne $component.Hash) { + Write-Host "Downloading official Rust component $($component.Name)" + & curl.exe --fail --location --silent --show-error --connect-timeout 15 --max-time 300 --retry 2 ` + "https://static.rust-lang.org/dist/2026-09-03/$package.tar.xz" --output $archive + if ($LASTEXITCODE) { throw "Download failed for $package (exit=$LASTEXITCODE)" } + } + if ((Get-FileHash -LiteralPath $archive -Algorithm SHA256).Hash -ne $component.Hash) { + throw "Rust component SHA256 mismatch: $archive" + } + $extract = Join-Path $DownloadRoot $package + if (!(Test-Path (Join-Path $extract 'components'))) { + & tar.exe -xf $archive -C $DownloadRoot + if ($LASTEXITCODE) { throw "Extract failed: $archive" } + } + foreach ($entry in (Get-Content -LiteralPath (Join-Path $extract 'components'))) { + $payload = Join-Path $extract $entry + foreach ($directory in @('bin','lib','share','etc')) { + $source = Join-Path $payload $directory + if (Test-Path $source) { Copy-Item -LiteralPath $source -Destination $ToolRoot -Recurse -Force } + } + } + Write-Host "Verified and installed $package" +} +& (Join-Path $ToolRoot 'bin/rustc.exe') --version +if ($LASTEXITCODE) { throw 'Rust compiler failed to start' } +& (Join-Path $ToolRoot 'bin/cargo.exe') --version +if ($LASTEXITCODE) { throw 'Cargo failed to start' } +Write-Host "Local Rust toolchain: $ToolRoot" diff --git a/scripts/windows/test-windows-rdna4.ps1 b/scripts/windows/test-windows-rdna4.ps1 index 49108f3..cb4b5ff 100644 --- a/scripts/windows/test-windows-rdna4.ps1 +++ b/scripts/windows/test-windows-rdna4.ps1 @@ -7,6 +7,9 @@ param( [string]$OutputDirectory, [string]$NgxCore = $env:D4R_NGX_CORE, [string]$DlssDll = $env:D4R_DLSS_DLL, + [ValidateSet('init', 'evaluate')][string]$NgxMode = 'init', + [ValidateSet(5, 11, 13)][int]$Preset = 11, + [switch]$NgxOnly, [int]$Iterations = 32, [int]$TimeoutSeconds = 180 ) @@ -28,8 +31,10 @@ $PackageRoot = [IO.Path]::GetFullPath($PackageRoot) if (!$HipRoot) { $HipRoot = 'C:\Program Files\AMD\ROCm\7.2' } $HipRoot = [IO.Path]::GetFullPath($HipRoot) if (!$ZludaRoot) { + $builtZluda = Join-Path $repoRoot 'dist/zluda-windows-native' $localZluda = [IO.Path]::GetFullPath((Join-Path $PackageRoot '../../.tools/zluda/zluda')) - if (Test-Path (Join-Path $localZluda 'nvcuda.dll')) { $ZludaRoot = $localZluda } + if (Test-Path (Join-Path $builtZluda 'build-info.json')) { $ZludaRoot = $builtZluda } + elseif (Test-Path (Join-Path $localZluda 'nvcuda.dll')) { $ZludaRoot = $localZluda } else { throw 'Pass -ZludaRoot with the directory containing the Windows ZLUDA nvcuda.dll.' } } $ZludaRoot = [IO.Path]::GetFullPath($ZludaRoot) @@ -84,6 +89,8 @@ function Invoke-Probe([string]$Name, [string]$Exe, [string[]]$Arguments) { return $pass } try { + if ($NgxOnly -and (!$NgxCore -or !$DlssDll)) { throw '-NgxOnly requires both locally supplied NVIDIA DLL paths.' } + $summary.foundationDiagnosticsPerformed = !$NgxOnly if ($Iterations -lt 1 -or $Iterations -gt 10000) { throw 'Iterations must be 1..10000' } if ($TimeoutSeconds -lt 1) { throw 'TimeoutSeconds must be positive' } $env:HIP_PATH = $HipRoot @@ -122,9 +129,12 @@ try { $summary.os = Get-CimInstance Win32_OperatingSystem | Select-Object Caption,Version,BuildNumber } catch { $summary.inventoryError = $_.Exception.Message } $bin = Join-Path $PackageRoot 'bin' - $hipOk = Invoke-Probe 'hip' (Join-Path $bin 'd4r_hip_gfx1201_probe.exe') @( + $hipOk = $true + if (!$NgxOnly) { $hipOk = Invoke-Probe 'hip' (Join-Path $bin 'd4r_hip_gfx1201_probe.exe') @( '--hip-root', $HipRoot, '--module', (Join-Path $bin 'probe_gfx1201.hsaco'), '--iterations', "$Iterations") + } if ($hipOk) { + if (!$NgxOnly) { $wmmaOk = Invoke-Probe 'gfx12-wmma' (Join-Path $bin 'd4r_gfx12_wmma_probe.exe') @( '--hip-root', $HipRoot, '--module', (Join-Path $bin 'wmma_gfx1201.hsaco'), '--iterations', "$Iterations") if (!$wmmaOk) { throw 'gfx12 WMMA layout validation failed; refusing to mark K/M readiness.' } @@ -141,7 +151,9 @@ try { (Join-Path $PackageRoot 'k_layer_validate.py'), '--kernel-name', $layer, '--fixture-dir', $kFixture) if (!$kReferenceOk) { throw "K $layer output differs from pwin_model.py; see the k-$layer-reference logs." } } + } $cudaOk = $true + if (!$NgxOnly) { foreach ($context in @('primary', 'created')) { $ok = Invoke-Probe "cuda-$context" (Join-Path $bin 'd4r_cuda_driver_probe.exe') @( '--hip-root', $HipRoot, '--cuda-dll', (Join-Path $ZludaRoot 'nvcuda.dll'), @@ -154,7 +166,14 @@ try { '--context', $context, '--iterations', '1') | Out-Null } } + if ($cudaOk -and ($NgxMode -eq 'evaluate' -or (Test-Path (Join-Path $ZludaRoot 'build-info.json')))) { + $cudaOk = Invoke-Probe 'cuda-images' (Join-Path $bin 'd4r_cuda_image_probe.exe') @( + '--hip-root', $HipRoot, '--cuda-dll', (Join-Path $ZludaRoot 'nvcuda.dll'), '--iterations', "$Iterations") + } + } if ($cudaOk) { + $mapOk = $interopOk = $true + if (!$NgxOnly) { $mapOk = Invoke-Probe 'interop-map-lifetime' (Join-Path $bin 'd4r_d3d12_hip_interop_probe.exe') @( '--hip-root', $HipRoot, '--interop-mode', 'map', '--iterations', '64') $interopOk = Invoke-Probe 'interop-roundtrip' (Join-Path $bin 'd4r_d3d12_hip_interop_probe.exe') @( @@ -167,6 +186,7 @@ try { Invoke-Probe 'hip-stream-lifetime' (Join-Path $bin 'd4r_hip_stream_lifecycle_probe.exe') @( '--hip-root', $HipRoot, '--module', (Join-Path $bin 'probe_gfx1201.hsaco'), '--iterations', '32') | Out-Null } + } if ($mapOk -and $interopOk) { $exitStatus = 0 } if ($exitStatus -eq 0 -and $NgxCore) { # The driver Init ABI searches the executable directory. Keep the @@ -181,15 +201,18 @@ try { Copy-Item -LiteralPath $DlssDll -Destination $localDlss $ngxArguments = @('--hip-root', $HipRoot, '--cuda-dll', (Join-Path $ZludaRoot 'nvcuda.dll'), '--ngx-core', $localCore, '--dlss-dll', $localDlss, - '--nvapi-dll', (Join-Path $PackageRoot 'nvapi-compat/nvapi64.dll')) - $ngxOk = Invoke-Probe 'ngx-init' $ngxExe $ngxArguments + '--nvapi-dll', (Join-Path $PackageRoot 'nvapi-compat/nvapi64.dll'), + '--ngx-mode', $NgxMode, '--preset', "$Preset", '--iterations', "$Iterations") + $ngxName = if ($NgxMode -eq 'evaluate') { "ngx-evaluate-preset-$Preset" } else { 'ngx-init' } + $ngxOk = Invoke-Probe $ngxName $ngxExe $ngxArguments if (!$ngxOk) { $exitStatus = 1 if (Test-Path (Join-Path $ZludaRoot 'trace/nvcuda.dll')) { - Invoke-Probe 'ngx-init-trace' $ngxExe @( + Invoke-Probe "$ngxName-trace" $ngxExe @( '--hip-root', $HipRoot, '--cuda-dll', (Join-Path $ZludaRoot 'trace/nvcuda.dll'), '--ngx-core', $localCore, '--dlss-dll', $localDlss, - '--nvapi-dll', (Join-Path $PackageRoot 'nvapi-compat/nvapi64.dll')) | Out-Null + '--nvapi-dll', (Join-Path $PackageRoot 'nvapi-compat/nvapi64.dll'), + '--ngx-mode', $NgxMode, '--preset', "$Preset", '--iterations', '1') | Out-Null } } } diff --git a/tools/windows/cuda_api.h b/tools/windows/cuda_api.h index f5c5c67..a2a59a3 100644 --- a/tools/windows/cuda_api.h +++ b/tools/windows/cuda_api.h @@ -7,6 +7,7 @@ using CUcontext = void*; using CUmodule = void*; using CUfunction = void*; struct CudaApi { + d4r::diag::SearchDirectory search; d4r::diag::Library library; #define D4R_CU(name, ret, ...) using name##_fn = ret(WINAPI*)(__VA_ARGS__); name##_fn name = library.symbol(#name) D4R_CU(cuInit, int, unsigned); @@ -33,7 +34,9 @@ struct CudaApi { D4R_CU(cuGetErrorName, int, int, const char**); D4R_CU(cuGetErrorString, int, int, const char**); #undef D4R_CU - explicit CudaApi(const std::string& path) : library(d4r::diag::wide(path)) {} + explicit CudaApi(const std::string& path) : + search(std::filesystem::path(d4r::diag::wide(path)).parent_path()), + library(d4r::diag::wide(path)) {} void check(int result, const char* call) { const char* name = "unknown", *message = "unknown"; if (result) { cuGetErrorName(result, &name); cuGetErrorString(result, &message); } diff --git a/tools/windows/cuda_image_api.h b/tools/windows/cuda_image_api.h new file mode 100644 index 0000000..441779f --- /dev/null +++ b/tools/windows/cuda_image_api.h @@ -0,0 +1,135 @@ +#pragma once +#include "cuda_api.h" + +namespace d4r::cuda { +// Public CUDA Driver API layouts, kept independent of NVIDIA SDK headers. +using Array = void*; +using Texture = uint64_t; +struct ArrayDescriptor { size_t width, height; uint32_t format, channels; }; +struct Array3DDescriptor { + size_t width, height, depth; + uint32_t format, channels, flags; +}; +struct Copy2D { + size_t srcX, srcY; + uint32_t srcType, srcAlignment; + const void* srcHost; + CUdeviceptr srcDevice; + Array srcArray; + size_t srcPitch, dstX, dstY; + uint32_t dstType, dstAlignment; + void* dstHost; + CUdeviceptr dstDevice; + Array dstArray; + size_t dstPitch, widthBytes, height; +}; +struct ResourceDescriptor { + uint32_t type, alignment; + union { Array array; int reserved[32]; } resource; + uint32_t flags, reserved; +}; +struct TextureDescriptor { + uint32_t addressMode[3], filterMode, flags, maxAnisotropy, mipmapFilterMode; + float mipmapBias, minMipmapClamp, maxMipmapClamp, borderColor[4]; + int reserved[12]; +}; +static_assert(sizeof(Array3DDescriptor) == 40); +static_assert(sizeof(ArrayDescriptor) == 24); +static_assert(sizeof(Copy2D) == 128); +static_assert(sizeof(ResourceDescriptor) == 144); +static_assert(sizeof(TextureDescriptor) == 104); + +struct ImageApi { + CudaApi& cuda; +#define D4R_IMAGE(name, ...) using name##_fn = int(WINAPI*)(__VA_ARGS__); name##_fn name = cuda.library.symbol(#name) + D4R_IMAGE(cuArray3DCreate_v2, Array*, const Array3DDescriptor*); + D4R_IMAGE(cuArrayGetDescriptor_v2, ArrayDescriptor*, Array); + D4R_IMAGE(cuArray3DGetDescriptor_v2, Array3DDescriptor*, Array); + D4R_IMAGE(cuArrayDestroy, Array); + D4R_IMAGE(cuMemcpy2D_v2, const Copy2D*); + D4R_IMAGE(cuTexObjectCreate, Texture*, const ResourceDescriptor*, const TextureDescriptor*, const void*); + D4R_IMAGE(cuTexObjectDestroy, Texture); + D4R_IMAGE(cuTexObjectGetResourceDesc, ResourceDescriptor*, Texture); + D4R_IMAGE(cuSurfObjectCreate, Texture*, const ResourceDescriptor*); + D4R_IMAGE(cuSurfObjectDestroy, Texture); + D4R_IMAGE(cuSurfObjectGetResourceDesc, ResourceDescriptor*, Texture); +#undef D4R_IMAGE + explicit ImageApi(CudaApi& api) : cuda(api) {} +}; + +class Image { + ImageApi& api_; + void release() noexcept { + if (object) { + const int result = surface_ ? api_.cuSurfObjectDestroy(object) : api_.cuTexObjectDestroy(object); + if (result) std::fprintf(stderr, "IMAGE_DESTROY object result=%d\n", result); + object = 0; + } + if (array_) { + const int result = api_.cuArrayDestroy(array_); + if (result) std::fprintf(stderr, "IMAGE_DESTROY array result=%d\n", result); + array_ = nullptr; + } + } + Array array_ = nullptr; + bool surface_; + size_t rowBytes_, height_; +public: + Texture object = 0; + Image(ImageApi& api, unsigned width, unsigned height, uint32_t format, + unsigned channels, bool surface, unsigned filter = 0) + : api_(api), surface_(surface), rowBytes_(size_t(width) * channels * (format == 16 ? 2 : 4)), height_(height) { + try { + const Array3DDescriptor descriptor{width, height, 0, format, channels, surface ? 2u : 0u}; + api_.cuda.check(api_.cuArray3DCreate_v2(&array_, &descriptor), "cuArray3DCreate_v2"); + ResourceDescriptor resource{}; + resource.resource.array = array_; + if (surface) api_.cuda.check(api_.cuSurfObjectCreate(&object, &resource), "cuSurfObjectCreate"); + else { + TextureDescriptor texture{}; + texture.addressMode[0] = texture.addressMode[1] = 1; // clamp + texture.filterMode = filter; + texture.flags = 2; // normalized coordinates + api_.cuda.check(api_.cuTexObjectCreate(&object, &resource, &texture, nullptr), "cuTexObjectCreate"); + } + ResourceDescriptor roundtrip{}; + api_.cuda.check(surface ? api_.cuSurfObjectGetResourceDesc(&roundtrip, object) : + api_.cuTexObjectGetResourceDesc(&roundtrip, object), "CUDA object resource descriptor roundtrip"); + if (roundtrip.type != 0 || roundtrip.resource.array != array_ || roundtrip.flags != 0) + throw std::runtime_error("CUDA object descriptor does not match its array"); + ArrayDescriptor twoD{}; + Array3DDescriptor threeD{}; + api_.cuda.check(api_.cuArrayGetDescriptor_v2(&twoD, array_), "cuArrayGetDescriptor_v2"); + api_.cuda.check(api_.cuArray3DGetDescriptor_v2(&threeD, array_), "cuArray3DGetDescriptor_v2"); + if (twoD.width != width || twoD.height != height || twoD.format != format || twoD.channels != channels || + threeD.width != width || threeD.height != height || threeD.depth != 0 || threeD.format != format || + threeD.channels != channels || threeD.flags != descriptor.flags) + throw std::runtime_error("CUDA array descriptor geometry or format mismatch"); + } catch (...) { release(); throw; } + } + Image(const Image&) = delete; + Image& operator=(const Image&) = delete; + ~Image() { release(); } + void verify_wrong_object_kind() { + ResourceDescriptor wrong{}; + const int result = surface_ ? api_.cuTexObjectGetResourceDesc(&wrong, object) : + api_.cuSurfObjectGetResourceDesc(&wrong, object); + if (!result) throw std::runtime_error("Wrong CUDA object kind was accepted as a valid resource"); + std::printf("CUDA wrong-kind descriptor safely rejected result=%d\n", result); + } + void upload(const void* source) { + Copy2D copy{}; + copy.srcType = 1; copy.srcHost = source; copy.srcPitch = rowBytes_; + copy.dstType = 3; copy.dstArray = array_; + copy.widthBytes = rowBytes_; copy.height = height_; + api_.cuda.check(api_.cuMemcpy2D_v2(©), "cuMemcpy2D_v2(host to diagnostic array)"); + } + void download(void* destination) { + Copy2D copy{}; + copy.srcType = 3; copy.srcArray = array_; + copy.dstType = 1; copy.dstHost = destination; copy.dstPitch = rowBytes_; + copy.widthBytes = rowBytes_; copy.height = height_; + api_.cuda.check(api_.cuMemcpy2D_v2(©), "cuMemcpy2D_v2(diagnostic array to host)"); + } +}; +} diff --git a/tools/windows/cuda_image_probe.cpp b/tools/windows/cuda_image_probe.cpp new file mode 100644 index 0000000..25a972c --- /dev/null +++ b/tools/windows/cuda_image_probe.cpp @@ -0,0 +1,51 @@ +#include "hip_api.h" +#include "cuda_image_api.h" + +int main(int argc, char** argv) { + using namespace d4r::diag; + start(); + try { + Args args(argc, argv); + HipApi hip(args.hip_root); + hipDeviceProp_t props{}; + hip.select_gfx1201(args.device, props); + SearchDirectory search(std::filesystem::path(wide(args.cuda_dll)).parent_path()); + CudaApi cuda(args.cuda_dll); + cuda.check(cuda.cuInit(0), "cuInit"); + CUdevice device = -1; + int count = 0; + cuda.check(cuda.cuDeviceGetCount(&count), "cuDeviceGetCount"); + for (int ordinal = 0; ordinal < count; ++ordinal) { + CUdevice candidate = -1; + int bus = -1, pciDevice = -1, domain = -1; + cuda.check(cuda.cuDeviceGet(&candidate, ordinal), "cuDeviceGet"); + cuda.check(cuda.cuDeviceGetAttribute(&bus, 33, candidate), "cuDeviceGetAttribute(PCI_BUS_ID)"); + cuda.check(cuda.cuDeviceGetAttribute(&pciDevice, 34, candidate), "cuDeviceGetAttribute(PCI_DEVICE_ID)"); + cuda.check(cuda.cuDeviceGetAttribute(&domain, 50, candidate), "cuDeviceGetAttribute(PCI_DOMAIN_ID)"); + if (bus == props.pciBusID && pciDevice == props.pciDeviceID && domain == props.pciDomainID) device = candidate; + } + if (device < 0) throw std::runtime_error("CUDA image probe HIP/CUDA PCI identity mismatch"); + CUcontext context = nullptr; + cuda.check(cuda.cuCtxCreate_v2(&context, 0, device), "cuCtxCreate_v2"); + struct Cleanup { CudaApi& api; CUcontext context; ~Cleanup() { (void)api.cuCtxDestroy_v2(context); } } cleanup{cuda, context}; + d4r::cuda::ImageApi images(cuda); + for (unsigned iteration = 0; iteration < args.iterations; ++iteration) { + for (unsigned channels : {1u, 2u, 4u}) for (bool surface : {false, true}) { + d4r::cuda::Image image(images, 17, 9, 16, channels, surface); + image.verify_wrong_object_kind(); + std::vector input(17 * 9 * channels), output(input.size()); + for (size_t i = 0; i < input.size(); ++i) input[i] = uint16_t((i + iteration) % 8 * 0x100); + image.upload(input.data()); + image.download(output.data()); + if (input != output) throw std::runtime_error("CUDA array storage roundtrip mismatch"); + } + } + cuda.check(cuda.cuCtxSynchronize(), "cuCtxSynchronize"); + std::printf("PASS CUDA_IMAGES architecture=gfx1201 iterations=%u descriptor_and_storage=1\n", args.iterations); + return 0; + } catch (const std::exception& e) { + std::fprintf(stderr, "FAIL CUDA_IMAGES %s\n", e.what()); + loaded_modules(); + return 4; + } +} diff --git a/tools/windows/diagnostic.h b/tools/windows/diagnostic.h index 9d2c8fb..3188b1c 100644 --- a/tools/windows/diagnostic.h +++ b/tools/windows/diagnostic.h @@ -57,6 +57,18 @@ inline LONG WINAPI unhandled(EXCEPTION_POINTERS* info) { 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(info->ExceptionRecord->ExceptionInformation[0]), + static_cast(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(info->ContextRecord->Rip), + static_cast(info->ContextRecord->Rsp), + static_cast(info->ContextRecord->Rcx), + static_cast(info->ContextRecord->Rdx), + static_cast(info->ContextRecord->R8), + static_cast(info->ContextRecord->R9)); loaded_modules(); wchar_t directory[32768]{}; const DWORD n = GetEnvironmentVariableW(L"D4R_DIAG_DIR", directory, 32768); @@ -74,12 +86,24 @@ inline LONG WINAPI unhandled(EXCEPTION_POINTERS* info) } 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 { @@ -89,6 +113,8 @@ struct Args { std::string ngx_core, dlss_dll; std::string nvapi_dll; std::string ngx_abi = "driver"; + std::string ngx_mode = "init"; + unsigned preset = 11; std::string fixture_dir; std::string kernel_name = "enc1"; unsigned iterations = 32; @@ -108,6 +134,13 @@ struct Args { 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-mode") ngx_mode = value; + else if (key == "--preset") { + size_t end = 0; + preset = static_cast(std::stoul(value, &end)); + if (end != value.size() || (preset != 5 && preset != 11 && preset != 13)) + throw std::runtime_error("preset must be 5 (E), 11 (K) or 13 (M)"); + } else if (key == "--fixture-dir") fixture_dir = value; else if (key == "--kernel-name") kernel_name = value; else if (key == "--iterations") { diff --git a/tools/windows/ngx_cuda_evaluate.h b/tools/windows/ngx_cuda_evaluate.h new file mode 100644 index 0000000..717c3ec --- /dev/null +++ b/tools/windows/ngx_cuda_evaluate.h @@ -0,0 +1,147 @@ +#pragma once +#include "cuda_image_api.h" +#include +#include +#include + +extern "C" void d4r_ngx_set_uint(void*, const char*, unsigned); +extern "C" void d4r_ngx_set_int(void*, const char*, int); +extern "C" void d4r_ngx_set_float(void*, const char*, float); +extern "C" void d4r_ngx_set_ull(void*, const char*, unsigned long long); +extern "C" void d4r_ngx_set_void(void*, const char*, void*); + +namespace d4r::diag { +inline float half_value(uint16_t bits) { + const unsigned exponent = (bits >> 10) & 31, mantissa = bits & 1023; + const float sign = bits & 0x8000 ? -1.0f : 1.0f; + if (exponent == 31) return mantissa ? NAN : sign * INFINITY; + if (!exponent) return sign * std::ldexp(float(mantissa), -24); + return sign * std::ldexp(float(1024 + mantissa), int(exponent) - 25); +} +inline void save_output(const std::filesystem::path& directory, const std::vector& output, + unsigned width, unsigned height, unsigned frame) { + const auto stem = "output-" + std::to_string(frame); + std::ofstream raw(directory / (stem + ".rgba16f"), std::ios::binary); + raw.write(reinterpret_cast(output.data()), std::streamsize(output.size() * 2)); + // BMP is only a diagnostic preview; the raw file preserves unmodified HDR values. + const unsigned stride = (width * 3 + 3) & ~3u; + std::vector bytes(54 + size_t(stride) * height, 0); + auto u32 = [&](size_t offset, uint32_t value) { + for (int j = 0; j < 4; ++j) bytes[offset + j] = static_cast(value >> (8 * j)); + }; + bytes[0] = 'B'; bytes[1] = 'M'; u32(2, static_cast(bytes.size())); u32(10, 54); + u32(14, 40); u32(18, width); u32(22, height); bytes[26] = 1; bytes[28] = 24; + for (unsigned y = 0; y < height; ++y) for (unsigned x = 0; x < width; ++x) + for (unsigned c = 0; c < 3; ++c) { + const float value = half_value(output[(size_t(y) * width + x) * 4 + c]); + bytes[54 + size_t(height - y - 1) * stride + x * 3 + (2 - c)] = + static_cast(std::clamp(value, 0.0f, 1.0f) * 255.0f + 0.5f); + } + std::ofstream bmp(directory / (stem + ".bmp"), std::ios::binary); + bmp.write(reinterpret_cast(bytes.data()), std::streamsize(bytes.size())); + if (!raw || !bmp) throw std::runtime_error("Saving DLSS output failed"); +} + +inline void run_synthetic_dlss(CudaApi& cuda, Library& core, void* parameters, + unsigned preset, unsigned frames, const std::filesystem::path& directory) { + // CPU upload/readback is confined to this synthetic diagnostic. The game + // backend uses external D3D12 memory and GPU copies instead. + constexpr unsigned width = 256, height = 144, outWidth = 512, outHeight = 288; + using Create = unsigned(*)(unsigned, void*, void**); + using Evaluate = unsigned(*)(void*, void*, void*); + using Release = unsigned(*)(void*); + const auto create = core.symbol("NVSDK_NGX_CUDA_CreateFeature"); + const auto evaluate = core.symbol("NVSDK_NGX_CUDA_EvaluateFeature"); + const auto release = core.symbol("NVSDK_NGX_CUDA_ReleaseFeature"); + auto ngx_check = [](unsigned result, const char* operation) { + std::printf("NGX %s -> 0x%08x\n", operation, result); + if (result != 1) throw std::runtime_error(std::string(operation) + " result=" + std::to_string(result)); + }; + d4r_ngx_set_uint(parameters, "Width", width); + d4r_ngx_set_uint(parameters, "Height", height); + d4r_ngx_set_uint(parameters, "OutWidth", outWidth); + d4r_ngx_set_uint(parameters, "OutHeight", outHeight); + d4r_ngx_set_int(parameters, "PerfQualityValue", 2); + d4r_ngx_set_int(parameters, "DLSS.Feature.Create.Flags", 0); + d4r_ngx_set_int(parameters, "DLSS.Enable.Output.Subrects", 0); + d4r_ngx_set_uint(parameters, "CreationNodeMask", 1); + d4r_ngx_set_uint(parameters, "VisibilityNodeMask", 1); + for (const char* name : {"DLSS.Hint.Render.Preset.DLAA", "DLSS.Hint.Render.Preset.Quality", + "DLSS.Hint.Render.Preset.Balanced", "DLSS.Hint.Render.Preset.Performance", + "DLSS.Hint.Render.Preset.UltraPerformance", "DLSS.Hint.Render.Preset.UltraQuality"}) + d4r_ngx_set_uint(parameters, name, preset); + CUdeviceptr scratch = 0; + cuda.check(cuda.cuMemAlloc_v2(&scratch, 64ull * 1024 * 1024), "cuMemAlloc_v2(NGX scratch)"); + struct ScratchCleanup { CudaApi& api; CUdeviceptr pointer; ~ScratchCleanup() { (void)api.cuMemFree_v2(pointer); } } sc{cuda, scratch}; + d4r_ngx_set_void(parameters, "Scratch", reinterpret_cast(uintptr_t(scratch))); + d4r_ngx_set_ull(parameters, "Scratch.SizeInBytes", 64ull * 1024 * 1024); + void* handle = nullptr; + std::printf("STAGE NGX_CREATE preset=%u dimensions=%ux%u->%ux%u\n", preset, width, height, outWidth, outHeight); + ngx_check(create(1, parameters, &handle), "NVSDK_NGX_CUDA_CreateFeature"); + if (!handle) throw std::runtime_error("NGX CreateFeature returned a null handle"); + struct FeatureCleanup { Release release; void*& handle; ~FeatureCleanup() { if (handle) (void)release(handle); } } fc{release, handle}; + cuda::ImageApi images(cuda); + cuda::Image color(images, width, height, 16, 4, false, 1); + cuda::Image depth(images, width, height, 32, 1, false); + cuda::Image motion(images, width, height, 16, 2, false); + cuda::Image exposure(images, 1, 1, 32, 1, false); + cuda::Image output(images, outWidth, outHeight, 16, 4, true); + std::vector input(size_t(width) * height * 4), zeroMotion(size_t(width) * height * 2, 0); + std::vector inputDepth(size_t(width) * height, 0.5f); + for (unsigned y = 0; y < height; ++y) for (unsigned x = 0; x < width; ++x) { + const size_t index = (size_t(y) * width + x) * 4; + input[index] = ((x / 16 + y / 16) & 1) ? 0x3a00 : 0x3400; // .75 / .25 + input[index + 1] = (x & 32) ? 0x3800 : 0x3400; + input[index + 2] = (y & 32) ? 0x3a00 : 0x3800; + input[index + 3] = 0x3c00; + } + const float one = 1.0f; + color.upload(input.data()); depth.upload(inputDepth.data()); motion.upload(zeroMotion.data()); exposure.upload(&one); + d4r_ngx_set_void(parameters, "Color", &color.object); + d4r_ngx_set_void(parameters, "Depth", &depth.object); + d4r_ngx_set_void(parameters, "MotionVectors", &motion.object); + d4r_ngx_set_void(parameters, "ExposureTexture", &exposure.object); + d4r_ngx_set_void(parameters, "Output", &output.object); + for (const char* name : {"TransparencyMask", "DLSS.Input.Bias.Current.Color.Mask"}) + d4r_ngx_set_void(parameters, name, nullptr); + for (const char* name : {"Jitter.Offset.X", "Jitter.Offset.Y", "Sharpness"}) + d4r_ngx_set_float(parameters, name, 0.0f); + for (const char* name : {"MV.Scale.X", "MV.Scale.Y", "DLSS.Pre.Exposure", "DLSS.Exposure.Scale"}) + d4r_ngx_set_float(parameters, name, 1.0f); + d4r_ngx_set_float(parameters, "FrameTimeDeltaInMsec", 16.666667f); + d4r_ngx_set_int(parameters, "Disable.Watermark", 1); + d4r_ngx_set_uint(parameters, "DLSS.Render.Subrect.Dimensions.Width", width); + d4r_ngx_set_uint(parameters, "DLSS.Render.Subrect.Dimensions.Height", height); + for (const char* name : {"DLSS.Input.Color.Subrect.Base.X", "DLSS.Input.Color.Subrect.Base.Y", + "DLSS.Input.Depth.Subrect.Base.X", "DLSS.Input.Depth.Subrect.Base.Y", "DLSS.Input.MV.Subrect.Base.X", + "DLSS.Input.MV.Subrect.Base.Y", "DLSS.Output.Subrect.Base.X", "DLSS.Output.Subrect.Base.Y"}) + d4r_ngx_set_uint(parameters, name, 0); + std::vector pixels(size_t(outWidth) * outHeight * 4, 0x7e00); + for (unsigned frame = 0; frame < frames; ++frame) { + std::fill(pixels.begin(), pixels.end(), 0x7e00); // unwritten output is an explicit failure + output.upload(pixels.data()); + d4r_ngx_set_int(parameters, "Reset", frame == 0 ? 1 : 0); + std::printf("STAGE NGX_EVALUATE preset=%u frame=%u\n", preset, frame); + const auto begin = std::chrono::steady_clock::now(); + ngx_check(evaluate(handle, parameters, nullptr), "NVSDK_NGX_CUDA_EvaluateFeature"); + cuda.check(cuda.cuCtxSynchronize(), "cuCtxSynchronize(NGX Evaluate)"); + const double milliseconds = std::chrono::duration(std::chrono::steady_clock::now() - begin).count(); + output.download(pixels.data()); + size_t nonfinite = 0; double sum = 0, square = 0; + for (size_t i = 0; i < pixels.size(); ++i) { + const float value = half_value(pixels[i]); + if (!std::isfinite(value)) ++nonfinite; + if ((i & 3) < 3) { sum += value; square += double(value) * value; } + } + const double count = outWidth * outHeight * 3; + const double variance = square / count - (sum / count) * (sum / count); + std::printf("NGX_OUTPUT preset=%u frame=%u nonfinite=%zu mean=%.9g variance=%.9g evaluate_ms=%.3f\n", + preset, frame, nonfinite, sum / count, variance, milliseconds); + if (nonfinite || !std::isfinite(variance) || variance < 1e-6) + throw std::runtime_error("DLSS output has NaN/Inf, unwritten pixels or no synthetic pattern"); + save_output(directory, pixels, outWidth, outHeight, frame); + } + ngx_check(release(handle), "NVSDK_NGX_CUDA_ReleaseFeature"); handle = nullptr; + std::printf("PASS NGX_EVALUATE preset=%u frames=%u finite=1 network_validation=pending\n", preset, frames); +} +} diff --git a/tools/windows/ngx_cuda_init_probe.cpp b/tools/windows/ngx_cuda_init_probe.cpp index 0928c75..758523a 100644 --- a/tools/windows/ngx_cuda_init_probe.cpp +++ b/tools/windows/ngx_cuda_init_probe.cpp @@ -1,5 +1,6 @@ #include "hip_api.h" #include "cuda_api.h" +#include "ngx_cuda_evaluate.h" // Same NGX C ABI and freestanding MSVC parameter accessors as the upstream shim. // NVIDIA SDK headers and proprietary binaries are not build dependencies. @@ -110,6 +111,9 @@ int main(int argc, char** argv) unsigned roundtrip = 0; check(d4r_ngx_get_uint(parameters, "Width", &roundtrip), "MSVC parameter ABI roundtrip"); if (roundtrip != 640) throw std::runtime_error("NGX parameter ABI mismatch"); + if (args.ngx_mode == "evaluate") + run_synthetic_dlss(cuda, core, parameters, args.preset, args.iterations, data_path); + else if (args.ngx_mode != "init") throw std::runtime_error("--ngx-mode must be init or evaluate"); check(destroy(parameters), "DestroyParameters(allocated)"); parameters = nullptr; check(destroy(caps), "DestroyParameters(capabilities)"); caps = nullptr; loaded_modules();