diff --git a/CMakeLists.txt b/CMakeLists.txt index 721d595..762e278 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -21,7 +21,7 @@ add_custom_command(OUTPUT "${probe_object}" -o "${probe_object}" DEPENDS tools/windows/probe_kernel.hip VERBATIM) add_custom_target(d4r_probe_code_object ALL DEPENDS "${probe_object}") -foreach(probe hip_gfx1201_probe cuda_driver_probe d3d12_hip_interop_probe hip_stream_lifecycle_probe) +foreach(probe hip_gfx1201_probe cuda_driver_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") @@ -37,6 +37,39 @@ foreach(probe hip_gfx1201_probe cuda_driver_probe d3d12_hip_interop_probe hip_st endif() install(TARGETS d4r_${probe} RUNTIME DESTINATION bin) endforeach() +if(MSVC) + target_sources(d4r_ngx_cuda_init_probe PRIVATE tools/d4r_ngx_param_msvc.cpp tools/d4r_ngx_param_host.cpp) +else() + find_program(D4R_CLANG_CL NAMES clang-cl.exe PATHS "${D4R_HIP_ROOT}/bin" "${D4R_HIP_ROOT}/lib/llvm/bin" NO_DEFAULT_PATH REQUIRED) + set(ngx_parameter_object "${CMAKE_CURRENT_BINARY_DIR}/ngx_parameter_msvc.obj") + add_custom_command(OUTPUT "${ngx_parameter_object}" + COMMAND "${D4R_CLANG_CL}" /c /O2 /Zl /GS- /GR- /EHs-c- + "${CMAKE_CURRENT_SOURCE_DIR}/tools/d4r_ngx_param_msvc.cpp" "/Fo${ngx_parameter_object}" + DEPENDS tools/d4r_ngx_param_msvc.cpp VERBATIM) + set_source_files_properties("${ngx_parameter_object}" PROPERTIES GENERATED TRUE EXTERNAL_OBJECT TRUE) + target_sources(d4r_ngx_cuda_init_probe PRIVATE "${ngx_parameter_object}" tools/d4r_ngx_param_host.cpp) +endif() +add_library(d4r_nvapi_trace SHARED tools/windows/nvapi_trace.cpp) +set_target_properties(d4r_nvapi_trace PROPERTIES PREFIX "" OUTPUT_NAME "nvapi64" + ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/nvapi-trace" + RUNTIME_OUTPUT_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/nvapi-trace") +target_compile_definitions(d4r_nvapi_trace PRIVATE WIN32_LEAN_AND_MEAN NOMINMAX) +target_link_libraries(d4r_nvapi_trace PRIVATE version dbghelp) +if(MINGW) + target_link_options(d4r_nvapi_trace PRIVATE -static) +endif() +install(TARGETS d4r_nvapi_trace RUNTIME DESTINATION nvapi-trace) +add_library(d4r_nvapi_compat SHARED tools/windows/nvapi_trace.cpp) +set_target_properties(d4r_nvapi_compat PROPERTIES PREFIX "" OUTPUT_NAME "nvapi64" + ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/nvapi-compat" + RUNTIME_OUTPUT_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/nvapi-compat") +target_compile_definitions(d4r_nvapi_compat PRIVATE WIN32_LEAN_AND_MEAN NOMINMAX __HIP_PLATFORM_AMD__ D4R_NVAPI_COMPAT) +target_include_directories(d4r_nvapi_compat PRIVATE "${D4R_HIP_ROOT}/include") +target_link_libraries(d4r_nvapi_compat PRIVATE version dbghelp) +if(MINGW) + target_link_options(d4r_nvapi_compat PRIVATE -static) +endif() +install(TARGETS d4r_nvapi_compat RUNTIME DESTINATION nvapi-compat) target_link_libraries(d4r_d3d12_hip_interop_probe PRIVATE d3d12 dxgi dxguid) add_dependencies(d4r_hip_gfx1201_probe d4r_probe_code_object) install(FILES "${probe_object}" DESTINATION bin) diff --git a/README.md b/README.md index d3183f1..b85055c 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,8 @@ d4r runs NVIDIA's official DLSS Super Resolution library (`nvngx_dlss.dll`) in W It is an experiment. It has been tested on one GPU in a handful of games, it depends on unreleased patches to ZLUDA and vkd3d-proton. **Windows / RDNA4 development:** the `windows-rdna4` branch adds native Windows -gfx1201 HIP and ZLUDA diagnostics. Build commands, hardware results and remaining +gfx1201 HIP/ZLUDA diagnostics, validated D3D12/HIP interop and an NGX init probe. +Build commands, hardware results and remaining porting gates are in [docs/windows-rdna4-port.md](docs/windows-rdna4-port.md). This branch does not yet provide a Windows DLSS runtime. diff --git a/docs/windows-rdna4-port.md b/docs/windows-rdna4-port.md index 3a47c5d..24ce0b6 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 and M4 passed on the physical RX 9070 XT with the -isolated TheRock profile. Next: NGX initialization and validated gfx12 WMMA. +Current milestone: M1/M2/M4 passed; NGX init + DLSS CUDA capability discovery +passed with the user's DLLs. Next: validated gfx12 WMMA and native K kernels. Do not integrate NGX until the integer PTX workload is stable on the real GPU. | Gate | Status | @@ -15,7 +15,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 | Pending | +| M3: Windows NGX initialization and simple DLSS path | Init + SR capabilities PASS; Evaluate/transformer 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 | Pending | | M6: K layers, full transformer and image validation | Pending | @@ -148,6 +148,43 @@ the 64-cycle map lifetime test and the 32-cycle synchronized texture round trip. The runner automatically isolates resource/import/stream lifetimes on failure. Do not declare stable SDK 7.2 acceptable for the leak-free fast path. +## Windows NGX initialization + +`tools/windows/ngx_cuda_init_probe.cpp` retains the physical HIP/PCI device +check, initializes a ZLUDA primary context, loads the user-supplied DLLs, +discovers DLSS CUDA capability and tests the existing MSVC parameter accessor. +This does not create/evaluate a DLSS feature or assert transformer correctness. + +The supplied driver `_nvngx.dll` 32.0.16.1714 uses the **driver/snippet** +three-argument `NVSDK_NGX_CUDA_Init(appId, dataPath, sdkVersion)` ABI. The +four-argument SDK client interface used by the upstream shim passes a pointer +as the version and returns `0xBAD0000C` (OutOfDate) for this binary. The two +interfaces are distinguished in NVIDIA's public `nvsdk_ngx.h` by +`NGX_SNIPPET_BUILD`. The Windows backend must keep this distinction. The probe +has explicit `--ngx-abi driver` (default) / `sdk`; it does not guess and retry. + +Upstream Windows ZLUDA NVAPI lacks physical GPU enumeration, architecture and +logical GPU/LUID functions used by NGX. The compatibility target +`nvapi-compat/nvapi64.dll` implements those **public NVAPI** APIs through HIP; +unknown queries are logged and forwarded to the supplied ZLUDA NVAPI backend. +It validates version/size and includes `NV_LOGICAL_GPU_DATA_V1.reserved[8]` +(568-byte x64 ABI). Concurrent initialization is synchronized. The AD102/AD100 +identity is explicitly a CUDA/NGX network profile matching d4r's sm_89 kernels; +physical detection and native compilation remain gfx1201. It is currently a +probe backend, not a complete game-wide NVAPI replacement. A separate +`nvapi-trace` build forwards every response unchanged for diagnosis. + +Driver init's default feature search needs the DLSS DLL next to the executable. +The runner creates a unique private `%TEMP%/d4r-ngx-` directory containing +the probe and copies of the two supplied DLLs. Paths and original hashes/versions +are logged; the directory is removed after the subprocess ends. Neither DLL is +placed in dist/, git, or the diagnostics ZIP. No private NVAPI function is +invented and no NVIDIA binary is patched. + +References: [NGX API signatures](https://github.com/NVIDIA/DLSS/blob/main/include/nvsdk_ngx.h), +[public NVAPI ABI](https://github.com/NVIDIA/nvapi/blob/main/nvapi.h), +[public NVAPI interface IDs](https://github.com/NVIDIA/nvapi/blob/main/nvapi_interface.h). + ## Authoritative references * [AMD HIP SDK Windows support matrix](https://rocm.docs.amd.com/projects/install-on-windows/en/latest/reference/system-requirements.html) @@ -186,6 +223,16 @@ This builds into `build/windows-rdna4-therock` and installs into `dist/windows-rdna4-therock`. The stable profile stays available for reproducing the runtime bug. It is expected to return failure at the lifetime gate. +One command adds the local NGX initialization gate after the five hardware gates: + +```powershell +powershell -NoProfile -File scripts/windows/test-windows-rdna4.ps1 -RuntimeProfile therock -NgxCore "C:/Users/Administrator/d4r/_nvngx.dll" -DlssDll "C:/Users/Administrator/d4r/nvngx_dlss.dll" +``` + +Success requires all six tests and `PASS NGX_INIT ... sr_available=1`; +`transformer_executed=0` is explicit. On NGX failure, CUDA trace and NVAPI query +logs are collected automatically. NVIDIA DLLs remain local test inputs. + The build accepts `-HipRoot`, `-ZludaRoot`, `-ToolchainRoot`, `-BuildDirectory` and `-InstallDirectory`. MSVC users can invoke CMake from a Developer Shell without the portable MinGW compiler. Equivalent direct build: @@ -242,6 +289,12 @@ stable runtime mapped-view bug; the fix is the runtime ownership correction. Local proprietary inputs supplied by the user: `nvngx_dlss.dll` 310.9.1.0, `_nvngx.dll` 32.0.16.1714. Neither is packaged or tracked. +2026-09-29 NGX initialization: PASS on gfx1201 with those two DLLs and the native +NVAPI topology backend. SR Available=1, FeatureInitResult=0x1, NeedsUpdatedDriver=0, +MSVC parameter roundtrip Width=640, checked parameter/context/NGX cleanup. +Local logs: `test-results/ngx-init-private-runtime.zip`. This is only the first +part of M3; feature execution, K/M correctness and the Windows shim remain open. + Audit found an upstream validation blocker: `swin_model.py` imports `model_enc3`, which is absent from the repository. M reference validation needs that dependency restored or an independently checked replacement. diff --git a/scripts/windows/test-windows-rdna4.ps1 b/scripts/windows/test-windows-rdna4.ps1 index 8e04c17..45e5891 100644 --- a/scripts/windows/test-windows-rdna4.ps1 +++ b/scripts/windows/test-windows-rdna4.ps1 @@ -5,6 +5,8 @@ param( [string]$ZludaRoot, [string]$PackageRoot, [string]$OutputDirectory, + [string]$NgxCore = $env:D4R_NGX_CORE, + [string]$DlssDll = $env:D4R_DLSS_DLL, [int]$Iterations = 32, [int]$TimeoutSeconds = 180 ) @@ -39,7 +41,9 @@ $originalHip = $env:HIP_PATH $originalDiag = $env:D4R_DIAG_DIR $originalLog = $env:ZLUDA_LOG_DIR $originalCuda = $env:ZLUDA_CUDA_LIB +$originalNvapi = $env:D4R_NVAPI_BACKEND $exitStatus = 1 +$ngxRuntimeDirectory = $null $summary = [ordered]@{utc=[DateTime]::UtcNow.ToString('o'); profile=$RuntimeProfile; hipRoot=$HipRoot; zludaRoot=$ZludaRoot; tests=@()} function Quote-Argument([string]$Value) { # All generated paths are absolute file/directory paths, with no trailing backslash. @@ -86,10 +90,18 @@ try { $env:D4R_DIAG_DIR = $OutputDirectory $env:ZLUDA_LOG_DIR = Join-Path $OutputDirectory 'zluda-trace' $env:ZLUDA_CUDA_LIB = Join-Path $ZludaRoot 'nvcuda.dll' + $env:D4R_NVAPI_BACKEND = Join-Path $ZludaRoot 'nvapi64.dll' $comgr = Join-Path $HipRoot 'bin/amd_comgr_3.dll' if (!(Test-Path -LiteralPath $comgr)) { $comgr = Join-Path $HipRoot 'bin/amd_comgr.dll' } $files = @((Join-Path $HipRoot 'bin/amdhip64_7.dll'), $comgr, (Join-Path $ZludaRoot 'nvcuda.dll'), (Join-Path $PackageRoot 'bin/probe_gfx1201.hsaco')) + if ($NgxCore -or $DlssDll) { + if (!$NgxCore -or !$DlssDll) { throw 'Supply both -NgxCore and -DlssDll absolute paths.' } + if (![IO.Path]::IsPathRooted($NgxCore) -or ![IO.Path]::IsPathRooted($DlssDll)) { + throw 'NVIDIA DLL paths must be absolute.' + } + $files += @($NgxCore, $DlssDll) + } $summary.files = @($files | ForEach-Object { if (!(Test-Path -LiteralPath $_)) { throw "Required file missing: $_" } $file = Get-Item -LiteralPath $_ @@ -132,12 +144,46 @@ try { '--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 + # supplied binaries in a private temporary runtime, outside dist/ZIP. + $ngxRuntimeDirectory = Join-Path ([IO.Path]::GetTempPath()) ('d4r-ngx-' + [Guid]::NewGuid().ToString('N')) + New-Item -ItemType Directory -Path $ngxRuntimeDirectory | Out-Null + $ngxExe = Join-Path $ngxRuntimeDirectory 'd4r_ngx_cuda_init_probe.exe' + $localCore = Join-Path $ngxRuntimeDirectory '_nvngx.dll' + $localDlss = Join-Path $ngxRuntimeDirectory 'nvngx_dlss.dll' + Copy-Item -LiteralPath (Join-Path $bin 'd4r_ngx_cuda_init_probe.exe') -Destination $ngxExe + Copy-Item -LiteralPath $NgxCore -Destination $localCore + 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 + if (!$ngxOk) { + $exitStatus = 1 + if (Test-Path (Join-Path $ZludaRoot 'trace/nvcuda.dll')) { + Invoke-Probe 'ngx-init-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 + } + } + } } } } catch { $summary.error = $_.Exception.Message Write-Warning $summary.error } finally { + if ($ngxRuntimeDirectory -and (Test-Path -LiteralPath $ngxRuntimeDirectory)) { + $resolvedRuntime = [IO.Path]::GetFullPath($ngxRuntimeDirectory) + $tempRoot = [IO.Path]::GetFullPath([IO.Path]::GetTempPath()).TrimEnd('\') + '\' + if (!$resolvedRuntime.StartsWith($tempRoot, [StringComparison]::OrdinalIgnoreCase) -or + [IO.Path]::GetFileName($resolvedRuntime) -notmatch '^d4r-ngx-[0-9a-f]{32}$') { + throw 'Refusing to remove a path outside the private NGX temporary runtime.' + } + Remove-Item -LiteralPath $resolvedRuntime -Recurse -Force + } $summary.passed = $exitStatus -eq 0 $summary | ConvertTo-Json -Depth 8 | Set-Content -LiteralPath (Join-Path $OutputDirectory 'summary.json') -Encoding UTF8 $env:PATH = $originalPath @@ -145,6 +191,7 @@ try { $env:D4R_DIAG_DIR = $originalDiag $env:ZLUDA_LOG_DIR = $originalLog $env:ZLUDA_CUDA_LIB = $originalCuda + $env:D4R_NVAPI_BACKEND = $originalNvapi $archive = "$OutputDirectory.zip" Compress-Archive -LiteralPath $OutputDirectory -DestinationPath $archive -Force Write-Host "Diagnostic bundle: $archive" diff --git a/tools/windows/cuda_api.h b/tools/windows/cuda_api.h new file mode 100644 index 0000000..f5c5c67 --- /dev/null +++ b/tools/windows/cuda_api.h @@ -0,0 +1,44 @@ +#pragma once +#include "diagnostic.h" +// CUDA Driver API ABI subset. No CUDA SDK, CUDA runtime or NVIDIA binary is needed. +using CUdevice = int; +using CUdeviceptr = uint64_t; +using CUcontext = void*; +using CUmodule = void*; +using CUfunction = void*; +struct CudaApi { + 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); + D4R_CU(cuDriverGetVersion, int, int*); + D4R_CU(cuDeviceGetCount, int, int*); + D4R_CU(cuDeviceGet, int, CUdevice*, int); + D4R_CU(cuDeviceGetName, int, char*, int, CUdevice); + D4R_CU(cuDeviceGetAttribute, int, int*, int, CUdevice); + D4R_CU(cuDevicePrimaryCtxRetain, int, CUcontext*, CUdevice); + D4R_CU(cuDevicePrimaryCtxRelease_v2, int, CUdevice); + D4R_CU(cuCtxSetCurrent, int, CUcontext); + D4R_CU(cuCtxCreate_v2, int, CUcontext*, unsigned, CUdevice); + D4R_CU(cuCtxDestroy_v2, int, CUcontext); + D4R_CU(cuCtxSynchronize, int); + D4R_CU(cuMemAlloc_v2, int, CUdeviceptr*, size_t); + D4R_CU(cuMemFree_v2, int, CUdeviceptr); + D4R_CU(cuMemcpyHtoD_v2, int, CUdeviceptr, const void*, size_t); + D4R_CU(cuMemcpyDtoH_v2, int, void*, CUdeviceptr, size_t); + D4R_CU(cuModuleLoadData, int, CUmodule*, const void*); + D4R_CU(cuModuleUnload, int, CUmodule); + D4R_CU(cuModuleGetFunction, int, CUfunction*, CUmodule, const char*); + D4R_CU(cuLaunchKernel, int, CUfunction, unsigned, unsigned, unsigned, unsigned, unsigned, unsigned, + unsigned, void*, void**, void**); + 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)) {} + void check(int result, const char* call) { + const char* name = "unknown", *message = "unknown"; + if (result) { cuGetErrorName(result, &name); cuGetErrorString(result, &message); } + else name = "CUDA_SUCCESS"; + std::printf("CUDA %s -> %d (%s)\n", call, result, name ? name : "unknown"); + if (result) throw std::runtime_error(std::string(call) + ": " + (message ? message : "unknown")); + } +}; diff --git a/tools/windows/cuda_driver_probe.cpp b/tools/windows/cuda_driver_probe.cpp index 582bdba..9da1a01 100644 --- a/tools/windows/cuda_driver_probe.cpp +++ b/tools/windows/cuda_driver_probe.cpp @@ -1,48 +1,7 @@ #include "hip_api.h" #include -// CUDA Driver API ABI subset. No CUDA SDK, CUDA runtime or NVIDIA binary is needed. -using CUdevice = int; -using CUdeviceptr = uint64_t; -using CUcontext = void*; -using CUmodule = void*; -using CUfunction = void*; -struct CudaApi { - 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); - D4R_CU(cuDriverGetVersion, int, int*); - D4R_CU(cuDeviceGetCount, int, int*); - D4R_CU(cuDeviceGet, int, CUdevice*, int); - D4R_CU(cuDeviceGetName, int, char*, int, CUdevice); - D4R_CU(cuDeviceGetAttribute, int, int*, int, CUdevice); - D4R_CU(cuDevicePrimaryCtxRetain, int, CUcontext*, CUdevice); - D4R_CU(cuDevicePrimaryCtxRelease_v2, int, CUdevice); - D4R_CU(cuCtxSetCurrent, int, CUcontext); - D4R_CU(cuCtxCreate_v2, int, CUcontext*, unsigned, CUdevice); - D4R_CU(cuCtxDestroy_v2, int, CUcontext); - D4R_CU(cuCtxSynchronize, int); - D4R_CU(cuMemAlloc_v2, int, CUdeviceptr*, size_t); - D4R_CU(cuMemFree_v2, int, CUdeviceptr); - D4R_CU(cuMemcpyHtoD_v2, int, CUdeviceptr, const void*, size_t); - D4R_CU(cuMemcpyDtoH_v2, int, void*, CUdeviceptr, size_t); - D4R_CU(cuModuleLoadData, int, CUmodule*, const void*); - D4R_CU(cuModuleUnload, int, CUmodule); - D4R_CU(cuModuleGetFunction, int, CUfunction*, CUmodule, const char*); - D4R_CU(cuLaunchKernel, int, CUfunction, unsigned, unsigned, unsigned, unsigned, unsigned, unsigned, - unsigned, void*, void**, void**); - 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)) {} - void check(int result, const char* call) { - const char* name = "unknown", *message = "unknown"; - cuGetErrorName(result, &name); - cuGetErrorString(result, &message); - std::printf("CUDA %s -> %d (%s)\n", call, result, name ? name : "unknown"); - if (result) throw std::runtime_error(std::string(call) + ": " + (message ? message : "unknown")); - } -}; +#include "cuda_api.h" static constexpr char ptx[] = R"PTX( .version 7.0 diff --git a/tools/windows/diagnostic.h b/tools/windows/diagnostic.h index 78d160c..7dc06ee 100644 --- a/tools/windows/diagnostic.h +++ b/tools/windows/diagnostic.h @@ -86,6 +86,9 @@ struct Args { std::string hip_root, cuda_dll, module, context = "primary"; std::string stream_lifetime = "persistent"; std::string interop_mode = "roundtrip"; + std::string ngx_core, dlss_dll; + std::string nvapi_dll; + std::string ngx_abi = "driver"; unsigned iterations = 32; int device = -1; Args(int argc, char** argv) { @@ -99,6 +102,10 @@ struct Args { else if (key == "--context") context = value; else if (key == "--stream-lifetime") stream_lifetime = value; else if (key == "--interop-mode") interop_mode = value; + else if (key == "--ngx-core") ngx_core = value; + else if (key == "--dlss-dll") dlss_dll = value; + else if (key == "--nvapi-dll") nvapi_dll = value; + else if (key == "--ngx-abi") ngx_abi = value; else if (key == "--iterations") { size_t end = 0; iterations = static_cast(std::stoul(value, &end)); diff --git a/tools/windows/ngx_cuda_init_probe.cpp b/tools/windows/ngx_cuda_init_probe.cpp new file mode 100644 index 0000000..0928c75 --- /dev/null +++ b/tools/windows/ngx_cuda_init_probe.cpp @@ -0,0 +1,127 @@ +#include "hip_api.h" +#include "cuda_api.h" + +// Same NGX C ABI and freestanding MSVC parameter accessors as the upstream shim. +// NVIDIA SDK headers and proprietary binaries are not build dependencies. +struct NgxPathListInfo { const wchar_t* const* Path; unsigned Length; }; +struct NgxLoggingInfo { void* LoggingCallback; int MinimumLoggingLevel; bool DisableOtherLoggingSinks; }; +struct NgxFeatureCommonInfo { NgxPathListInfo PathListInfo; void* InternalData; NgxLoggingInfo LoggingInfo; }; +static_assert(sizeof(NgxFeatureCommonInfo) == 40); +extern "C" void d4r_ngx_set_uint(void*, const char*, unsigned); +extern "C" unsigned d4r_ngx_get_uint(void*, const char*, unsigned*); +extern "C" unsigned d4r_ngx_get_int(void*, const char*, int*); + +static void ngx_log(const char* message, int level, unsigned component) +{ + if (message) std::printf("NGX_LOG level=%d component=%u %s\n", level, component, message); +} +static void check(unsigned result, const char* call) +{ + std::printf("NGX %s -> 0x%08x\n", call, result); + if (result != 1) throw std::runtime_error(std::string(call) + " NGX result=" + std::to_string(result)); +} + +int main(int argc, char** argv) +{ + using namespace d4r::diag; + start(); + try { + Args args(argc, argv); + if (args.cuda_dll.empty() || args.ngx_core.empty() || args.dlss_dll.empty()) + throw std::runtime_error("--cuda-dll, --ngx-core and --dlss-dll absolute paths required"); + const std::filesystem::path core_path(wide(args.ngx_core)), dlss_path(wide(args.dlss_dll)); + if (dlss_path.filename() != L"nvngx_dlss.dll") throw std::runtime_error("DLSS file must be named nvngx_dlss.dll"); + HipApi hip(args.hip_root); + hipDeviceProp_t props{}; + hip.select_gfx1201(args.device, props); + SearchDirectory cuda_search(std::filesystem::path(wide(args.cuda_dll)).parent_path()); + CudaApi cuda(args.cuda_dll); + cuda.check(cuda.cuInit(0), "cuInit"); + int count = 0; + cuda.check(cuda.cuDeviceGetCount(&count), "cuDeviceGetCount"); + CUdevice device = -1; + for (int i = 0; i < count; ++i) { + CUdevice candidate = -1; + int bus = -1, pci_device = -1, domain = -1; + cuda.check(cuda.cuDeviceGet(&candidate, i), "cuDeviceGet"); + cuda.check(cuda.cuDeviceGetAttribute(&bus, 33, candidate), "cuDeviceGetAttribute(PCI_BUS_ID)"); + cuda.check(cuda.cuDeviceGetAttribute(&pci_device, 34, candidate), "cuDeviceGetAttribute(PCI_DEVICE_ID)"); + cuda.check(cuda.cuDeviceGetAttribute(&domain, 50, candidate), "cuDeviceGetAttribute(PCI_DOMAIN_ID)"); + if (bus == props.pciBusID && pci_device == props.pciDeviceID && domain == props.pciDomainID) device = candidate; + } + if (device < 0) throw std::runtime_error("NGX CUDA/HIP PCI identity mismatch"); + CUcontext context = nullptr; + cuda.check(cuda.cuDevicePrimaryCtxRetain(&context, device), "cuDevicePrimaryCtxRetain"); + struct ContextCleanup { + CudaApi& cuda; CUcontext& context; CUdevice device; + ~ContextCleanup() { if (context) { (void)cuda.cuCtxSetCurrent(nullptr); (void)cuda.cuDevicePrimaryCtxRelease_v2(device); } } + } cc{cuda, context, device}; + cuda.check(cuda.cuCtxSetCurrent(context), "cuCtxSetCurrent"); + auto nvapi_path = std::filesystem::path(wide(args.cuda_dll)).parent_path() / L"nvapi64.dll"; + if (!std::filesystem::exists(nvapi_path)) nvapi_path = nvapi_path.parent_path().parent_path() / L"nvapi64.dll"; + if (!args.nvapi_dll.empty()) nvapi_path = wide(args.nvapi_dll); + Library nvapi(nvapi_path); + SearchDirectory feature_search(dlss_path.parent_path()); + // Preload the supplied absolute file so a system copy cannot replace it. + Library dlss(dlss_path), core(core_path); + using Init = unsigned(*)(unsigned long long, const wchar_t*, const NgxFeatureCommonInfo*, unsigned); + using DriverInit = unsigned(*)(unsigned long long, const wchar_t*, unsigned); + using Shutdown = unsigned(*)(); + using Parameters = unsigned(*)(void**); + using Destroy = unsigned(*)(void*); + const auto init = core.symbol("NVSDK_NGX_CUDA_Init"); + const auto driver_init = core.symbol("NVSDK_NGX_CUDA_Init"); + const auto shutdown = core.symbol("NVSDK_NGX_CUDA_Shutdown"); + const auto capabilities = core.symbol("NVSDK_NGX_CUDA_GetCapabilityParameters"); + const auto allocate = core.symbol("NVSDK_NGX_CUDA_AllocateParameters"); + const auto destroy = core.symbol("NVSDK_NGX_CUDA_DestroyParameters"); + const std::wstring directory = dlss_path.parent_path().wstring(); + const wchar_t* paths[] = {directory.c_str()}; + const char* diagnostic_dir = std::getenv("D4R_DIAG_DIR"); + const auto data_path = diagnostic_dir ? std::filesystem::path(wide(diagnostic_dir)) : + std::filesystem::current_path() / L"test-results" / L"ngx-init"; + std::filesystem::create_directories(data_path); + NgxFeatureCommonInfo common{}; + common.PathListInfo = {paths, 1}; + common.LoggingInfo = {reinterpret_cast(&ngx_log), 2, false}; + loaded_modules(); + std::printf("STAGE NVSDK_NGX_CUDA_Init sdk=0x15 core=%s dlss=%s\n", args.ngx_core.c_str(), args.dlss_dll.c_str()); + if (args.ngx_abi == "driver") check(driver_init(241534723ull, data_path.c_str(), 0x15), "NVSDK_NGX_CUDA_Init(driver ABI)"); + else if (args.ngx_abi == "sdk") check(init(241534723ull, data_path.c_str(), &common, 0x15), "NVSDK_NGX_CUDA_Init(SDK ABI)"); + else throw std::runtime_error("--ngx-abi must be driver or sdk"); + bool initialized = true; + struct NgxCleanup { Shutdown shutdown; bool& active; ~NgxCleanup() { if (active) (void)shutdown(); } } nc{shutdown, initialized}; + void* caps = nullptr; + check(capabilities(&caps), "NVSDK_NGX_CUDA_GetCapabilityParameters"); + if (!caps) throw std::runtime_error("NGX returned null capability parameters"); + struct ParameterCleanup { Destroy destroy; void*& p; ~ParameterCleanup() { if (p) (void)destroy(p); } } pc{destroy, caps}; + int available = -1, init_result = -1, needs_driver = -1; + check(d4r_ngx_get_int(caps, "SuperSampling.Available", &available), "Get(SuperSampling.Available)"); + check(d4r_ngx_get_int(caps, "SuperSampling.FeatureInitResult", &init_result), "Get(SuperSampling.FeatureInitResult)"); + check(d4r_ngx_get_int(caps, "SuperSampling.NeedsUpdatedDriver", &needs_driver), "Get(SuperSampling.NeedsUpdatedDriver)"); + std::printf("CAPABILITIES available=%d feature_init=0x%08x needs_driver=%d\n", available, static_cast(init_result), needs_driver); + if (available != 1 || init_result != 1 || needs_driver != 0) + throw std::runtime_error("DLSS CUDA feature is unavailable; initialization alone is insufficient"); + void* parameters = nullptr; + check(allocate(¶meters), "NVSDK_NGX_CUDA_AllocateParameters"); + if (!parameters) throw std::runtime_error("Null NGX parameters"); + ParameterCleanup ac{destroy, parameters}; + d4r_ngx_set_uint(parameters, "Width", 640); + 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"); + check(destroy(parameters), "DestroyParameters(allocated)"); parameters = nullptr; + check(destroy(caps), "DestroyParameters(capabilities)"); caps = nullptr; + loaded_modules(); + check(shutdown(), "NVSDK_NGX_CUDA_Shutdown"); initialized = false; + cuda.check(cuda.cuCtxSynchronize(), "cuCtxSynchronize"); + cuda.check(cuda.cuCtxSetCurrent(nullptr), "cuCtxSetCurrent(NULL)"); + cuda.check(cuda.cuDevicePrimaryCtxRelease_v2(device), "cuDevicePrimaryCtxRelease_v2"); context = nullptr; + std::printf("PASS NGX_INIT architecture=gfx1201 sr_available=1 transformer_executed=0\n"); + return 0; + } catch (const std::exception& e) { + std::fprintf(stderr, "FAIL NGX_INIT %s\n", e.what()); + loaded_modules(); + return 4; + } +} diff --git a/tools/windows/nvapi_trace.cpp b/tools/windows/nvapi_trace.cpp new file mode 100644 index 0000000..836f511 --- /dev/null +++ b/tools/windows/nvapi_trace.cpp @@ -0,0 +1,113 @@ +#include "diagnostic.h" +#if defined(D4R_NVAPI_COMPAT) +#include "hip_api.h" +#include +#include +#include + +namespace { +// Public NVAPI ABI: NVIDIA/nvapi nvapi.h and nvapi_interface.h. +// AD100 describes the CUDA/NGX model profile, not the physical HIP target. +struct ArchInfo { unsigned version, architecture, implementation, revision; }; +struct LogicalGpuData { unsigned version; void* os_adapter_id; unsigned count; void* physical[64]; unsigned reserved[8]; }; +static_assert(sizeof(LogicalGpuData) == 568); +std::unique_ptr hip; +hipDeviceProp_t properties{}; +int device_ordinal = -1; +int physical_handle; +int logical_handle; +std::mutex init_mutex; +std::atomic runtime_ready{false}; +bool valid(void* handle) { return runtime_ready.load(std::memory_order_acquire) && handle == &physical_handle; } +int __cdecl initialize() +{ + try { + std::lock_guard lock(init_mutex); + if (!runtime_ready.load(std::memory_order_acquire)) { + const char* root = std::getenv("HIP_PATH"); + if (!root) return -5; + auto runtime = std::make_unique(root); + device_ordinal = runtime->select_gfx1201(-1, properties); + hip = std::move(runtime); + runtime_ready.store(true, std::memory_order_release); + } + std::printf("NVAPI_COMPAT initialize physical=gfx1201 CUDA_model_profile=AD100\n"); + return 0; + } catch (const std::exception& e) { std::fprintf(stderr, "NVAPI_COMPAT init failed: %s\n", e.what()); return -1; } +} +int __cdecl enumerate(void** handles, unsigned* count) +{ + if (!handles || !count) return -5; + if (!runtime_ready.load(std::memory_order_acquire)) return -4; + handles[0] = &physical_handle; *count = 1; + std::printf("NVAPI_COMPAT EnumPhysicalGPUs count=1 HIP_ordinal=%d\n", device_ordinal); + return 0; +} +int __cdecl architecture(void* handle, ArchInfo* info) +{ + if (!valid(handle) || !info) return -5; + if (info->version != (sizeof(ArchInfo) | 1u << 16) && info->version != (sizeof(ArchInfo) | 2u << 16)) return -9; + info->architecture = 0x190; info->implementation = 2; info->revision = 0x11; + std::printf("NVAPI_COMPAT GetArchInfo profile=AD102 physical=gfx1201\n"); + return 0; +} +int __cdecl adapter_id(void* handle, void* luid) +{ + if (!valid(handle) || !luid) return -5; + std::memcpy(luid, properties.luid, sizeof(LUID)); + std::printf("NVAPI_COMPAT GetAdapterIdFromPhysicalGpu matched_HIP_LUID=1\n"); + return 0; +} +int __cdecl logical_from_physical(void* handle, void** logical) +{ + if (!valid(handle) || !logical) return -5; + *logical = &logical_handle; + std::printf("NVAPI_COMPAT GetLogicalGPUFromPhysicalGPU count=1\n"); + return 0; +} +int __cdecl logical_info(void* logical, LogicalGpuData* info) +{ + std::printf("NVAPI_COMPAT GetLogicalGpuInfo handle_valid=%d info=%p version=0x%08x os_adapter_buffer=%p\n", + runtime_ready.load(std::memory_order_acquire) && logical == &logical_handle, static_cast(info), info ? info->version : 0, + info ? info->os_adapter_id : nullptr); + if (!runtime_ready.load(std::memory_order_acquire) || logical != &logical_handle || !info || !info->os_adapter_id) return -5; + if (info->version != (sizeof(LogicalGpuData) | 1u << 16)) return -9; + std::memcpy(info->os_adapter_id, properties.luid, sizeof(LUID)); + info->count = 1; + std::memset(info->physical, 0, sizeof(info->physical)); + std::memset(info->reserved, 0, sizeof(info->reserved)); + info->physical[0] = &physical_handle; + std::printf("NVAPI_COMPAT GetLogicalGpuInfo matched_HIP_LUID=1\n"); + return 0; +} +} +#endif + +// Trace build forwards responses unchanged. The compatibility build adds the +// public topology APIs above; other queries still go to upstream ZLUDA NVAPI. +extern "C" __declspec(dllexport) void* __cdecl nvapi_QueryInterface(unsigned id) +{ + try { +#if defined(D4R_NVAPI_COMPAT) + switch (id) { + case 0x0150e828: return reinterpret_cast(&initialize); + case 0xe5ac921f: return reinterpret_cast(&enumerate); + case 0xd8265d24: return reinterpret_cast(&architecture); + case 0x0ff07fde: return reinterpret_cast(&adapter_id); + case 0xadd604d1: return reinterpret_cast(&logical_from_physical); + case 0x842b066e: return reinterpret_cast(&logical_info); + } +#endif + const char* path = std::getenv("D4R_NVAPI_BACKEND"); + if (!path || !*path) throw std::runtime_error("D4R_NVAPI_BACKEND absolute path required"); + static d4r::diag::Library backend(d4r::diag::wide(path)); + using Query = void* (__cdecl*)(unsigned); + static Query query = backend.symbol("nvapi_QueryInterface"); + void* function = query(id); + std::printf("NVAPI_QUERY id=0x%08x supported=%d\n", id, function != nullptr); + return function; + } catch (const std::exception& e) { + std::fprintf(stderr, "NVAPI_TRACE_ERROR id=0x%08x %s\n", id, e.what()); + return nullptr; + } +}