Initialize Windows NGX CUDA with HIP-backed NVAPI topology

This commit is contained in:
Codex committed 2026-09-29 19:32:58 +03:00
1 parent 239a644270
commit 64669c2c36
9 files changed
+431 -47

No files matched your search

+34 -1
View File
@@ -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)
+2 -1
View File
@@ -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.
+56 -3
View File
@@ -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-<GUID>` 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.
+47
View File
@@ -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"
+44
View File
@@ -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##_fn>(#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"));
}
};
+1 -42
View File
@@ -1,48 +1,7 @@
#include "hip_api.h"
#include <algorithm>
// 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##_fn>(#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
+7
View File
@@ -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<unsigned>(std::stoul(value, &end));
+127
View File
@@ -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<Init>("NVSDK_NGX_CUDA_Init");
const auto driver_init = core.symbol<DriverInit>("NVSDK_NGX_CUDA_Init");
const auto shutdown = core.symbol<Shutdown>("NVSDK_NGX_CUDA_Shutdown");
const auto capabilities = core.symbol<Parameters>("NVSDK_NGX_CUDA_GetCapabilityParameters");
const auto allocate = core.symbol<Parameters>("NVSDK_NGX_CUDA_AllocateParameters");
const auto destroy = core.symbol<Destroy>("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<void*>(&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<unsigned>(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(&parameters), "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;
}
}
+113
View File
@@ -0,0 +1,113 @@
#include "diagnostic.h"
#if defined(D4R_NVAPI_COMPAT)
#include "hip_api.h"
#include <memory>
#include <mutex>
#include <atomic>
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<d4r::diag::HipApi> hip;
hipDeviceProp_t properties{};
int device_ordinal = -1;
int physical_handle;
int logical_handle;
std::mutex init_mutex;
std::atomic<bool> 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<std::mutex> 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<d4r::diag::HipApi>(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<void*>(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<void*>(&initialize);
case 0xe5ac921f: return reinterpret_cast<void*>(&enumerate);
case 0xd8265d24: return reinterpret_cast<void*>(&architecture);
case 0x0ff07fde: return reinterpret_cast<void*>(&adapter_id);
case 0xadd604d1: return reinterpret_cast<void*>(&logical_from_physical);
case 0x842b066e: return reinterpret_cast<void*>(&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<Query>("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;
}
}