Fix Windows NVAPI loading the gfx1101 build on every other AMD GPU

0.1.5 shipped one d4r/zluda/nvapi64.dll (the gfx1101 build) for all
targets, and compat_nvapi_path() preferred it over d4r/nvapi/<arch>/.
Nothing on Windows ever set D4R_GPU_ARCH, so on any other GPU NVAPI
failed with "No selected gfx1101 device", NGX CUDA init failed, and
DLSS stayed unavailable (black frames, no OptiScaler upscaler section).

- Select the NVAPI per architecture; never fall back to a shared DLL
  in a multi-target folder, and report a missing build by name.

- The loader and runtime publish D4R_GPU_ARCH and D4R_GPU_LUID for the
  game's adapter, so NVAPI picks the right device with several GPUs.

- nvapi64.dll exports the target it was built for; the runtime refuses
  a mismatch, and the packager and d4r-check read the same marker.

- Release packaging no longer ships d4r/zluda/nvapi64.dll and rejects
  NVAPI builds for the wrong target, without a marker, or missing.

- Move target resolution, device matching and loader choice into
  gpu_selection.h; regression tests cover all 11 targets, bad or
  missing D4R_GPU_ARCH, mixed installs, multi-GPU and missing files.

- d4r-check: accept the FSR keys in the shipped d4r.ini.
This commit is contained in:
ayo committed 2026-10-18 12:19:37 -04:00
1 parent df9f2cd012
commit 7da2f9ac39
16 files changed
+530 -46

No files matched your search

+9 -2
View File
@@ -46,7 +46,7 @@ if (!$manifest) { Bad 'd4r\package.json is missing or damaged (extract the d4r Z
# --- files --------------------------------------------------------------------------------------
$required = @('dxgi.dll', 'OptiScaler.ini', 'd4r\_nvngx.dll', 'd4r\d4r.ini', 'd4r\dll-pins.txt',
'd4r\hip\bin\amdhip64_7.dll', 'd4r\zluda\nvcuda.dll', 'd4r\zluda\nvapi64.dll', 'd4r\nvapi\backend\nvapi64.dll')
'd4r\hip\bin\amdhip64_7.dll', 'd4r\zluda\nvcuda.dll', 'd4r\nvapi\backend\nvapi64.dll')
foreach ($file in $required) {
if (Test-Path -LiteralPath (Join-Path $game $file) -PathType Leaf) { Ok $file }
else { Bad "$file (extract the d4r ZIP again over the game folder)" }
@@ -156,6 +156,10 @@ else {
if ($missing.Count) { Bad "GPU $($device.name) ($a): this release has no complete build for it (missing: $($missing -join ', '))" }
else {
Ok "GPU $($device.name) ($a): build present, $native native kernels"
# Each nvapi64.dll names the GPU target it was built for; a copy for another GPU is refused at startup.
$nvapiText = [Text.Encoding]::GetEncoding(28591).GetString([IO.File]::ReadAllBytes((Join-Path $d4r "nvapi\$a\nvapi64.dll")))
$nvapiNames = @([regex]::Matches($nvapiText, 'D4R_NVAPI_TARGET=(gfx[0-9a-f]+)\0') | ForEach-Object { $_.Groups[1].Value } | Select-Object -Unique)
if ($nvapiNames.Count -ne 1 -or $nvapiNames[0] -ne $a) { Bad "d4r\nvapi\$a\nvapi64.dll is built for '$($nvapiNames -join ', ')' instead of $a (files from different GPUs are mixed; extract the d4r ZIP again)" }
if (!$arch) { $arch = $a }
}
if ($a -eq 'gfx1101') { Note 'RX 7700 XT has passed native Windows synthetic checks and limited game-frame tests; broad game validation remains incomplete' }
@@ -164,6 +168,9 @@ else {
}
if (@($inventory.devices | Where-Object { $_.wavefrontSize -eq 32 }).Count -gt 1) { Note 'several GPUs found; d4r uses the one the game renders on' }
}
if (Test-Path -LiteralPath (Join-Path $d4r 'zluda\nvapi64.dll')) {
Note 'd4r\zluda\nvapi64.dll is left over from an older d4r release (one GPU''s build shared by all). d4r ignores it, but delete it'
}
if ($arch -and $registry.Count) {
$info = @($registry | Where-Object { $_.architecture -eq $arch }) | Select-Object -First 1
$bad = 0
@@ -198,7 +205,7 @@ $iniPath = Join-Path $d4r 'd4r.ini'
$model = 'K'
if (Test-Path -LiteralPath $iniPath) {
$section = ''; $iniProblems = 0
$known = @{ 'dlss.model' = 1; 'dlss.showwatermark' = 1; 'interop.asyncinterop' = 1; 'interop.batchinputcopies' = 1; 'debug.validateoutput' = 1; 'debug.log' = 1; 'paths.cachedir' = 1; 'paths.nativedir' = 1; 'kernels.nativefp8' = 1; 'rayreconstruction.backend' = 1; 'rayreconstruction.modeldir' = 1 }
$known = @{ 'dlss.model' = 1; 'dlss.showwatermark' = 1; 'interop.asyncinterop' = 1; 'interop.batchinputcopies' = 1; 'debug.validateoutput' = 1; 'debug.log' = 1; 'paths.cachedir' = 1; 'paths.nativedir' = 1; 'kernels.nativefp8' = 1; 'rayreconstruction.backend' = 1; 'rayreconstruction.modeldir' = 1; 'rayreconstruction.fsrdenoiser' = 1; 'rayreconstruction.fsrstabilitybias' = 1; 'rayreconstruction.fsrdetail' = 1 }
$boolean = @('dlss.showwatermark', 'interop.asyncinterop', 'interop.batchinputcopies', 'debug.validateoutput', 'kernels.nativefp8')
foreach ($raw in (Read-TextFile $iniPath).TrimStart([char]0xFEFF) -split "`n") {
$line = $raw.Trim()