Build patched Windows ZLUDA and execute native NGX DLSS diagnostics
This commit is contained in:
1 parent
64472e5a61
commit
3c28a82e34
17 files changed
+1114
-13
No files matched your search
+6
-1
@@ -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()
|
||||
@@ -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:
|
||||
|
||||
@@ -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
|
||||
@@ -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<HashMap<usize, Vec<u8>>> {
|
||||
+ static RESOURCES: OnceLock<Mutex<HashMap<usize, Vec<u8>>>> = 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::<u8>(), std::mem::size_of::<HIP_RESOURCE_DESC>()).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::<u8>(), 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)
|
||||
+}
|
||||
@@ -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)"
|
||||
}
|
||||
@@ -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 }
|
||||
@@ -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') }
|
||||
}
|
||||
@@ -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 }
|
||||
@@ -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 }
|
||||
@@ -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"
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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##_fn>(#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); }
|
||||
|
||||
@@ -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##_fn>(#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)");
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -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<uint16_t> 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;
|
||||
}
|
||||
}
|
||||
@@ -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<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));
|
||||
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<unsigned>(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") {
|
||||
|
||||
@@ -0,0 +1,147 @@
|
||||
#pragma once
|
||||
#include "cuda_image_api.h"
|
||||
#include <cmath>
|
||||
#include <chrono>
|
||||
#include <fstream>
|
||||
|
||||
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<uint16_t>& 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<const char*>(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<unsigned char> 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<unsigned char>(value >> (8 * j));
|
||||
};
|
||||
bytes[0] = 'B'; bytes[1] = 'M'; u32(2, static_cast<uint32_t>(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<unsigned char>(std::clamp(value, 0.0f, 1.0f) * 255.0f + 0.5f);
|
||||
}
|
||||
std::ofstream bmp(directory / (stem + ".bmp"), std::ios::binary);
|
||||
bmp.write(reinterpret_cast<const char*>(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<Create>("NVSDK_NGX_CUDA_CreateFeature");
|
||||
const auto evaluate = core.symbol<Evaluate>("NVSDK_NGX_CUDA_EvaluateFeature");
|
||||
const auto release = core.symbol<Release>("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<void*>(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<uint16_t> input(size_t(width) * height * 4), zeroMotion(size_t(width) * height * 2, 0);
|
||||
std::vector<float> 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<uint16_t> 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<double, std::milli>(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);
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
|
||||
Reference in new issue
Block a user