From 17121ccbf6271f88dd0e9acdebbfc4e99b1df346 Mon Sep 17 00:00:00 2001 From: xdfnx-dev Date: Fri, 2 Oct 2026 23:31:27 +0300 Subject: [PATCH] Fix Windows numeric NGX feature discovery and quick-test launches --- README.md | 8 +- docs/windows-game.md | 10 +- docs/windows-quick-test.txt | 4 +- docs/windows-rdna4-port.md | 55 +++++- ...4-serialize-device-hook-installation.patch | 34 ++++ .../0005-forward-numeric-init-errors.patch | 22 +++ patches/optiscaler/README.md | 12 ++ scripts/windows/build-optiscaler-windows.ps1 | 21 ++- scripts/windows/game-summary.ps1 | 32 ++++ scripts/windows/package-windows-game.ps1 | 1 + .../windows/package-windows-quick-test.ps1 | 2 +- scripts/windows/quick-test.ps1 | 15 +- scripts/windows/test-game-summary.ps1 | 38 +++++ scripts/windows/test-ngx-feature-layout.ps1 | 160 ++++++++++++++++++ scripts/windows/test-windows-quick-test.ps1 | 31 +++- scripts/windows/test-windows-rdna4.ps1 | 2 +- scripts/windows/windows-game.ps1 | 35 ++-- tools/windows/d3d12_dlss_probe.cpp | 10 +- tools/windows/d4r_nvngx_windows.cpp | 9 +- tools/windows/diagnostic.h | 7 + tools/windows/hip_api.h | 4 +- tools/windows/ngx_cuda_init_probe.cpp | 5 +- 22 files changed, 473 insertions(+), 44 deletions(-) create mode 100644 patches/optiscaler/0004-serialize-device-hook-installation.patch create mode 100644 patches/optiscaler/0005-forward-numeric-init-errors.patch create mode 100644 scripts/windows/game-summary.ps1 create mode 100644 scripts/windows/test-game-summary.ps1 create mode 100644 scripts/windows/test-ngx-feature-layout.ps1 diff --git a/README.md b/README.md index 490d1cd..7dbbc3c 100644 --- a/README.md +++ b/README.md @@ -13,7 +13,7 @@ transformer networks on Windows 11 / RX 9070 XT (gfx1201), using patched ZLUDA/HIP and VRAM buffers/shared fences directly. Inputs and output belong to the current frame; this path does not use Wine, Proton or CPU image staging. The Windows backend, command-list hooks and diagnostics live in `tools/windows`. -All sixteen Windows hardware gates pass, and queued-frame/feature-recreation K +All twenty Windows diagnostic gates pass, and queued-frame/feature-recreation K outputs match synchronous controls exactly. A five-minute K/4K Silent Hill 2 run completes 16941 finite frames without backend errors. Performance remains under development; asynchronous interop is opt-in and requires one queue. @@ -37,6 +37,12 @@ or START-M.cmd, select your game and local NVIDIA DLLs, then send the single diagnostic ZIP after exiting. RESTORE-GAME.cmd restores replaced files. Read [the short English instructions](docs/windows-quick-test.txt). Corresponding sources are a separate release asset; the test requires no SDK installation. +The updated quick test launches without an attached debugger, stages the +locally supplied DLSS DLL beside the shim for numeric CUDA NGX initialization, +and reports a launcher exit with zero frames as inconclusive. Source fixes +also serialize OptiScaler device-hook installation and forward numeric init +errors. These fixes pass standalone direct/OptiScaler K and OptiScaler M on +RX 9070 XT; other GPUs and the reported Cyberpunk game still need testing. See [supported games](SUPPORTED_GAMES.md) for the tested games and DLSS models. diff --git a/docs/windows-game.md b/docs/windows-game.md index 46c5f39..6fce46f 100644 --- a/docs/windows-game.md +++ b/docs/windows-game.md @@ -22,7 +22,15 @@ executing kernels. It rejects another architecture's package and a local D3D12 proxy identifying itself as vkd3d/Wine, producing one diagnostic ZIP. RX 9060 XT needs gfx1200: the old gfx1201-only package fails before DLSS initialization on it. Use native D3D12. On an untested GPU, start with -synchronous K and output/exception diagnostics. +synchronous K and output diagnostics. The ordinary launcher does not attach +a debugger. `-CaptureExceptions` is an explicit developer option; games with +anti-debug protection can refuse it. + +Numeric CUDA driver NGX initialization searches for `nvngx_dlss.dll` beside +the calling d4r shim. The installer stages your local feature DLL in `d4r/` +and the original NVIDIA core in `d4r/vendor/`. `Libraries.NvngxDlssPath` is +the feature directory. Keeping the feature DLL only in `vendor/` causes +`BAD00004` (FeatureNotFound) in numeric-ID games such as Cyberpunk. Build the shim/diagnostics, corrected ZLUDA, K/M native objects and patched OptiScaler using the scripts in `scripts/windows`, then run: diff --git a/docs/windows-quick-test.txt b/docs/windows-quick-test.txt index 7f4b78b..ba838e0 100644 --- a/docs/windows-quick-test.txt +++ b/docs/windows-quick-test.txt @@ -43,7 +43,9 @@ IF SOMETHING FAILS: - Do not start a second test while the first game is running. No HIP SDK installation is required; the isolated runtime is included. -This uses synchronous interop, output checks and crash capture by default. +This uses synchronous interop and output checks. No debugger is attached by default. +Exit codes and loaded DLLs are logged. A debugger is only an explicit developer option. +Your local DLSS DLL is staged beside the d4r shim automatically. It is a correctness diagnostic, NOT an FPS benchmark or an optimized daily-use build. Colour, depth, motion and output remain in VRAM in the runtime fast path. diff --git a/docs/windows-rdna4-port.md b/docs/windows-rdna4-port.md index bec7f82..b32ada1 100644 --- a/docs/windows-rdna4-port.md +++ b/docs/windows-rdna4-port.md @@ -19,11 +19,64 @@ explicit limits; the validated package keeps conservative arithmetic. ## Milestone and gates +2026-10-02 issue #10 follow-up ([comment 5958769640](https://github.com/countervolts/d4r/issues/10#issuecomment-5958769640)): +four new logs confirm that RX 9060 XT / gfx1200 HIP discovery succeeds. The +Cyberpunk K/M runs never launch the transformer: numeric CUDA NGX capabilities +report `available=0, feature_init=BAD00004` (FeatureNotFound). The unmatched +adapter during enumeration is not the main Radeon: its runtime LUID matches. +GetFeatureRequirements now returns success with AdapterUnsupported for Intel / +WARP / another nonmatching adapter, while runtime initialization still requires +the exact HIP architecture and D3D12 LUID. + +The feature discovery failure is reproduced on RX 9070 XT without a game, +using Cyberpunk's unchanged numeric application ID `100152211`. Keeping the +feature DLL only beside the official core in `d4r/vendor/` fails; placing it +beside the calling shim in `d4r/` succeeds. Putting it beside the EXE alone is +insufficient when the call originates in the shim. The installer now stages +the local feature DLL beside the shim and sets `Libraries.NvngxDlssPath` to +the directory, as required by OptiScaler's discovery logic. Project identity, +DLL contents, numeric driver ABI and capability checks remain unchanged. + +The standalone reproduction also exposed concurrent/reentrant OptiScaler +device hook installation: Detours error `0x10dd` clears a live trampoline and +execution jumps to null. Source patch `0004` serializes installation/removal +and guards recursion; `0005` forwards numeric NGX init failures instead of +reporting success. The exact patched frontend source is +`86b21ea` (base `45a2001`), with pinned submodules verified directly before build. + +The quick launcher no longer attaches a debugger by default: Assassin's Creed +Black Flag Resynced refused the previous debugger launch (`DEADC0DE`). Empty +stdout/stderr now parse correctly. A clean early process exit with zero DLSS +frames is inconclusive, as in the SM2 log; an external Steam restart is not +tracked and must not be reported as successful hardware validation. + +Validation on the available RX 9070 XT: all eleven target builds pass; CTest +20/20; summary parser 9/9 including all four submitted logs. The layout +regression passes two expected FeatureNotFound cases (direct + OptiScaler), +then three finite frames each for direct K, OptiScaler K and OptiScaler M. +Every required native layer executes; GPU NaN/Inf checks pass, CPU frame copies +and frame age are zero. Direct/OptiScaler K RGB matches bit-for-bit for all +three frames (max absolute/relative error 0). Reports are under +`test-results/issue-10-5958769640/`, including `ctest.log`, +`summary-parser.json`, `layout-final/` and `build-all-targets.json`. +All five exported OptiScaler patches apply to the pinned upstream sources and +reproduce its exact committed files (`optiScaler-patches.json`). Reproduce the +standalone layout/error/coverage/RGB checks with: + +```powershell +.\scripts\windows\test-ngx-feature-layout.ps1 -DiagnosticRoot .\dist\windows-gpu-coverage-gfx1201 -HipRoot .\.tools\therock-10.2.0a20260929\_rocm_sdk_core -ZludaRoot .\dist\zluda-windows-gpu-coverage -NativeRoot .\dist\windows-gpu-coverage-game-gfx1201\d4r\native -OptiScalerDll .\dist\optiscaler-windows-d4r\OptiScaler.dll -NgxCore .\_nvngx.dll -DlssDll .\nvngx_dlss.dll -OutputDirectory .\test-results\ngx-feature-layout +``` + +These tests do not validate the reporter's gfx1200 kernels or game image. +The local Cyberpunk EXE is `D:\Games\Cyberpunk 2077\bin\x64\Cyberpunk2077.exe`; +it is located but is not launched or modified while the user is away. + 2026-10-02 quick-test packaging: one universal ZIP shares the identical OptiScaler/ZLUDA/HIP runtime and selects among all eleven previously compiled host/kernel targets using read-only HIP discovery. START-K.cmd / START-M.cmd use file pickers, remember validated game/DLL paths, back up replaced files, -run synchronous output/crash diagnostics, and produce one report ZIP. +run synchronous output diagnostics, and produce one report ZIP. The initial +release attached a debugger; the issue #10 follow-up above removes that default. RESTORE-GAME.cmd works without HIP discovery or NVIDIA DLL selection. No runtime arithmetic changes or additional hardware-support claims are made. The short English instructions are in `windows-quick-test.txt`; developer diff --git a/patches/optiscaler/0004-serialize-device-hook-installation.patch b/patches/optiscaler/0004-serialize-device-hook-installation.patch new file mode 100644 index 0000000..29e692d --- /dev/null +++ b/patches/optiscaler/0004-serialize-device-hook-installation.patch @@ -0,0 +1,34 @@ +diff --git a/OptiScaler/hooks/D3D12_Hooks.cpp b/OptiScaler/hooks/D3D12_Hooks.cpp +index 3b757be..05fc194 100644 +--- a/OptiScaler/hooks/D3D12_Hooks.cpp ++++ b/OptiScaler/hooks/D3D12_Hooks.cpp +@@ -2217,11 +2217,20 @@ static HRESULT hkD3D12GetInterface(REFCLSID rclsid, REFIID riid, void** ppvDebug + return result; + } + ++// Device discovery and NGX initialization can overlap. Installing d4r's ++// command hooks can also create a device and re-enter this function. Never ++// overwrite live Detours trampolines from a second installation attempt. ++static std::recursive_mutex deviceHookMutex; ++static bool deviceHookInstalling = false; + static void HookToDevice(ID3D12Device* InDevice) + { +- if (o_CreateSampler != nullptr || InDevice == nullptr) ++ std::lock_guard lock(deviceHookMutex); ++ if (o_CreateSampler != nullptr || InDevice == nullptr || deviceHookInstalling) + return; + ++ deviceHookInstalling = true; ++ struct InstallationGuard { ~InstallationGuard() { deviceHookInstalling = false; } } installationGuard; ++ + LOG_DEBUG("Dx12"); + + // Get the vtable pointer +@@ -2334,6 +2343,7 @@ static void HookToDevice(ID3D12Device* InDevice) + + static void UnhookDevice() + { ++ std::lock_guard lock(deviceHookMutex); + LOG_FUNC(); + + DetourTransactionBegin(); diff --git a/patches/optiscaler/0005-forward-numeric-init-errors.patch b/patches/optiscaler/0005-forward-numeric-init-errors.patch new file mode 100644 index 0000000..d00232d --- /dev/null +++ b/patches/optiscaler/0005-forward-numeric-init-errors.patch @@ -0,0 +1,22 @@ +diff --git a/OptiScaler/inputs/NVNGX_DLSS_Dx12.cpp b/OptiScaler/inputs/NVNGX_DLSS_Dx12.cpp +index 1f723f1..ef5ec77 100644 +--- a/OptiScaler/inputs/NVNGX_DLSS_Dx12.cpp ++++ b/OptiScaler/inputs/NVNGX_DLSS_Dx12.cpp +@@ -184,6 +184,8 @@ NVSDK_NGX_API NVSDK_NGX_Result NVSDK_NGX_D3D12_Init_Ext(unsigned long long InApp + + if (result == NVSDK_NGX_Result_Success) + NVNGXProxy::SetDx12Inited(true); ++ else if (State::Instance().dlssExternalBackend) ++ return result; + } + else + { +@@ -254,6 +256,8 @@ NVSDK_NGX_API NVSDK_NGX_Result NVSDK_NGX_D3D12_Init(unsigned long long InApplica + + if (result == NVSDK_NGX_Result_Success) + NVNGXProxy::SetDx12Inited(true); ++ else if (State::Instance().dlssExternalBackend) ++ return result; + } + } + diff --git a/patches/optiscaler/README.md b/patches/optiscaler/README.md index 5918b80..75911ae 100644 --- a/patches/optiscaler/README.md +++ b/patches/optiscaler/README.md @@ -1,6 +1,18 @@ # Explicit Windows d4r backend Base: OptiScaler `45a2001303ddff632e279f77aef85ceede5832cb` (2026-09-30). + +`0004` serializes D3D12 device hook installation/removal and guards recursive +device discovery while the d4r command backend installs. The previous frontend +can start two installations, fail a Detours transaction with `0x10dd`, clear +live original-function pointers and call null. This was reproduced in the +numeric-ID standalone harness; no game was required. + +`0005` propagates numeric Init/Init_Ext failures for the explicitly selected +external backend, matching the existing ProjectID behavior. FeatureNotFound +must fail initialization instead of being hidden until repeated CreateFeature +attempts. `Libraries.NvngxDlssPath` takes a directory, not the DLL filename; +the Windows installer and standalone runner now use that directory. The modifications to OptiScaler are GPL-3.0, matching its upstream license. No NVIDIA DLL is supplied or patched. diff --git a/scripts/windows/build-optiscaler-windows.ps1 b/scripts/windows/build-optiscaler-windows.ps1 index ac9a029..d5ad635 100644 --- a/scripts/windows/build-optiscaler-windows.ps1 +++ b/scripts/windows/build-optiscaler-windows.ps1 @@ -41,8 +41,23 @@ try { if ($LASTEXITCODE) { throw "Source patch failed: $($patch.Name)" } } } - & git -C $SourceRoot submodule update --init --depth 1 - if ($LASTEXITCODE) { throw 'Pinned OptiScaler submodules failed.' } + # Verify existing checkouts directly. A complete local checkout does not + # need Git's shell-based submodule helper (or a network connection). + $submodulePaths=@(& git -C $SourceRoot config --file .gitmodules --get-regexp '\.path$' | ForEach-Object { ($_ -split ' ',2)[1] }) + $submodules=@() + foreach ($path in $submodulePaths) { + $tree=(& git -C $SourceRoot ls-tree HEAD -- $path) -split '\s+',4 + if ($tree.Count -lt 4 -or $tree[0] -ne '160000') { throw "Missing pinned submodule: $path" } + $checkout=Join-Path $SourceRoot $path + $actual=if (Test-Path -LiteralPath (Join-Path $checkout '.git')) { (& git -C $checkout rev-parse HEAD).Trim() } else { '' } + if ($actual -ne $tree[2]) { + & git -C $SourceRoot submodule update --init --depth 1 -- $path + if ($LASTEXITCODE) { throw "Pinned OptiScaler submodule failed: $path" } + $actual=(& git -C $checkout rev-parse HEAD).Trim() + if ($actual -ne $tree[2]) { throw "Wrong submodule revision: $path" } + } + $submodules+=(' '+$actual+' '+$path) + } if (!$BuildToolsRoot -and (Test-Path -LiteralPath (Join-Path $repo '.tools/vs2022/MSBuild/Current/Bin/MSBuild.exe'))) { $BuildToolsRoot = Join-Path $repo '.tools/vs2022' } @@ -72,7 +87,7 @@ try { buildTools=$BuildToolsRoot; msbuildVersion=(& $msbuild -version -nologo | Select-Object -Last 1); patches=@($patches | ForEach-Object { @{name=$_.Name; sha256=(Get-FileHash -LiteralPath $_.FullName -Algorithm SHA256).Hash} }); dllSha256=(Get-FileHash -LiteralPath (Join-Path $InstallRoot 'OptiScaler.dll') -Algorithm SHA256).Hash; - submodules=@(& git -C $SourceRoot submodule status)} + submodules=$submodules} $metadata | ConvertTo-Json -Depth 5 | Set-Content -LiteralPath (Join-Path $InstallRoot 'build-info.json') -Encoding UTF8 Write-Host "Built OptiScaler with external Windows d4r backend: $InstallRoot" } finally { $env:PATH = $previousPath } diff --git a/scripts/windows/game-summary.ps1 b/scripts/windows/game-summary.ps1 new file mode 100644 index 0000000..bec34ba --- /dev/null +++ b/scripts/windows/game-summary.ps1 @@ -0,0 +1,32 @@ +# Parse completed diagnostic records. Empty streams are valid, including games +# that refuse a debugger before initializing the upscaler. +function Get-D4RGameSummary { + param([AllowEmptyString()][string]$Stdout='', [AllowEmptyString()][string]$Stderr='', + [AllowEmptyString()][string]$Debugger='', [int]$Preset=11, [double]$RunSeconds=0, + [bool]$ValidateOutput=$false, $GameCrash=$null) + $gameExit=[regex]::Match($Debugger, 'EXIT code=0x([0-9a-f]+)') + $exitCode=if ($gameExit.Success) { '0x'+$gameExit.Groups[1].Value } else { 'diagnostic_timeout' } + $frames=[regex]::Matches($Stdout, '(?m)^D4R_FRAME cpu_frame_copies=([0-9]+) frame_age=([0-9]+) interop_ngx_ms=([0-9.]+)\r?$') + $checks=[regex]::Matches($Stdout, '(?m)^D4R_OUTPUT_VALIDATION elements=([0-9]+) nan=([0-9]+) inf=([0-9]+) diagnostics_cpu_bytes=8\r?$') + $launches=[regex]::Matches($Stderr, '\[d4r-launch\] kernel="([^"]+)" backend=(native|translated)') + $kernels=@($launches | ForEach-Object { $_.Groups[1].Value+' '+$_.Groups[2].Value } | Group-Object | ForEach-Object { + $parts=$_.Name.Split(' '); @{kernel=$parts[0]; backend=$parts[1]; launches=$_.Count} + }) + $capabilities=@([regex]::Matches($Stdout, '(?m)^D4R_CAPABILITIES available=(-?[0-9]+) feature_init=(0x[0-9a-f]+) needs_driver=(-?[0-9]+)\r?$') | + ForEach-Object { $_.Groups[2].Value } | Select-Object -Unique) + $session=if ($frames.Count) { 'completed-dlss-frames' } + elseif ($capabilities -contains '0xbad00004') { 'ngx-feature-not-found' } + elseif ($capabilities.Count -and $capabilities -ne '0x00000001') { 'ngx-capability-failure' } + elseif ($exitCode -eq '0x0') { 'process-exited-without-dlss-frames' } + elseif ($exitCode -eq 'diagnostic_timeout') { 'diagnostic-timeout-without-dlss-frames' } + else { 'process-failed-before-dlss-frames' } + return [ordered]@{exitCode=$exitCode; session=$session; featureInitResults=$capabilities; + runSeconds=$RunSeconds; preset=$Preset; gameCrash=$GameCrash; + nativeLaunches=([regex]::Matches($Stderr, '\[d4r-launch\].*backend=native')).Count; + completedFrames=$frames.Count; outputValidationRequested=$ValidateOutput; + outputGpuChecks=$checks.Count; kernels=$kernels; + nonfiniteOutputs=@($checks | Where-Object { $_.Groups[2].Value -ne '0' -or $_.Groups[3].Value -ne '0' }).Count; + previousFrameOutputs=@($frames | Where-Object { $_.Groups[2].Value -ne '0' }).Count; + cpuImageCopyFrames=@($frames | Where-Object { $_.Groups[1].Value -ne '0' }).Count; + failures=([regex]::Matches($Stderr, 'D4R_[A-Z0-9_]*FAILURE\b')).Count} +} diff --git a/scripts/windows/package-windows-game.ps1 b/scripts/windows/package-windows-game.ps1 index c0667d8..2b9a9a9 100644 --- a/scripts/windows/package-windows-game.ps1 +++ b/scripts/windows/package-windows-game.ps1 @@ -66,6 +66,7 @@ if ($entries.Count -ne 16) { throw "Expected 11 K and 5 M native objects; found Set-Content -LiteralPath (Join-Path $PackageRoot 'd4r/native/d4r-kernels.txt') -Encoding ASCII $stagedFiles.Add('d4r/native/d4r-kernels.txt') Stage (Join-Path $repo 'scripts/windows/windows-game.ps1') 'windows-game.ps1' +Stage (Join-Path $repo 'scripts/windows/game-summary.ps1') 'game-summary.ps1' Stage (Join-Path $repo 'scripts/windows/gpu-target.ps1') 'gpu-target.ps1' Stage (Join-Path $repo 'scripts/windows/gpu-preflight.ps1') 'gpu-preflight.ps1' Stage (Join-Path $repo 'tools/windows/gpu-targets.json') 'gpu-targets.json' diff --git a/scripts/windows/package-windows-quick-test.ps1 b/scripts/windows/package-windows-quick-test.ps1 index 2f3dd9d..c3785c4 100644 --- a/scripts/windows/package-windows-quick-test.ps1 +++ b/scripts/windows/package-windows-quick-test.ps1 @@ -51,7 +51,7 @@ foreach ($gpu in Get-D4RGpuTargets) { if (!$baseline) { $baseline=$metadata } if ($metadata.dlssSha256 -ne $baseline.dlssSha256 -or $metadata.zludaBuild.sourceCommit -ne $baseline.zludaBuild.sourceCommit) { throw 'Input packages do not use the same validated dependencies.' } } -foreach ($name in @('windows-game.ps1','gpu-target.ps1','gpu-preflight.ps1')) { +foreach ($name in @('windows-game.ps1','game-summary.ps1','gpu-target.ps1','gpu-preflight.ps1')) { $entry=Stage (Join-Path $PSScriptRoot $name) "files/common/$name" $common+=@{path=$name; sha256=$entry.sha256; version=$entry.version} } diff --git a/scripts/windows/quick-test.ps1 b/scripts/windows/quick-test.ps1 index b538e0a..7b6d5b2 100644 --- a/scripts/windows/quick-test.ps1 +++ b/scripts/windows/quick-test.ps1 @@ -2,7 +2,8 @@ param( [ValidateSet('run','install','restore')][string]$Action='run', [ValidateSet(11,13)][int]$Preset=11, - [string]$GameExe, [string]$NgxCore, [string]$DlssDll + [string]$GameExe, [string]$NgxCore, [string]$DlssDll, + [switch]$CaptureExceptions ) $ErrorActionPreference='Stop' $root=[IO.Path]::GetFullPath((Join-Path $PSScriptRoot '..')) @@ -126,7 +127,7 @@ try { Write-Host 'In the game: select DLSS if available; otherwise select FSR/XeSS to let OptiScaler intercept it.' Write-Host 'The first launch compiles shaders and can take several minutes. Then play for 30 seconds and EXIT the game normally.' & (Join-Path $selectedPackage 'windows-game.ps1') -Action $Action -GameExe $GameExe -NgxCore $NgxCore -DlssDll $DlssDll ` - -Preset $Preset -ValidateOutput -CaptureExceptions -OutputDirectory $output + -Preset $Preset -ValidateOutput -CaptureExceptions:$CaptureExceptions -OutputDirectory $output if ($Action -eq 'install') { $status.status='installed-only' } else { $summary=Get-Content -LiteralPath (Join-Path $output 'summary.json') -Raw | ConvertFrom-Json @@ -143,7 +144,13 @@ try { @($required).Count -eq $(if ($Preset -eq 11) { 11 } else { 5 }) -and $status.missingNativeKernels.Count -eq 0 -and $status.translatedNativeKernels.Count -eq 0 $status.hardwareValidationCompleted=[bool]$passed - $status.status=if ($passed) { 'backend-checks-passed-image-still-needs-user-review' } else { 'backend-checks-failed-or-no-dlss-frames' } + $status.status=if ($passed) { 'backend-checks-passed-image-still-needs-user-review' } + elseif ($summary.completedFrames -eq 0 -and $summary.exitCode -eq '0x0' -and $summary.failures -eq 0) { 'inconclusive-process-exited-without-dlss-frames' } + else { 'backend-checks-failed-or-no-dlss-frames' } + $status.session=$summary.session + if ($status.status -eq 'inconclusive-process-exited-without-dlss-frames') { + Write-Host 'The launched process exited without a DLSS frame. A Steam/launcher restart is not tracked by this test. Keep Steam open and select the actual game executable. Send the ZIP if it restarts again.' -ForegroundColor Yellow + } Write-Host $(if ($passed) { 'Backend checks PASSED. Please also report whether the image looked correct.' } else { 'Test did not pass. Send the diagnostic ZIP; do not guess from the FPS.' }) -ForegroundColor $(if ($passed) { 'Green' } else { 'Yellow' }) } } catch { @@ -163,4 +170,4 @@ try { Write-Host 'Also tell us: GPU model, game name, K or M, and whether the image was correct.' Write-Host 'Close the game before running RESTORE-GAME.cmd.' } -if ($status.status -eq 'failed' -or $status.status -eq 'backend-checks-failed-or-no-dlss-frames') { exit 1 } +if ($status.status -eq 'failed' -or $status.status -eq 'backend-checks-failed-or-no-dlss-frames' -or $status.status.StartsWith('inconclusive-')) { exit 1 } diff --git a/scripts/windows/test-game-summary.ps1 b/scripts/windows/test-game-summary.ps1 new file mode 100644 index 0000000..b31c56a --- /dev/null +++ b/scripts/windows/test-game-summary.ps1 @@ -0,0 +1,38 @@ +[CmdletBinding()] +param([string]$LogRoot, [string]$OutputDirectory) +$ErrorActionPreference='Stop' +. (Join-Path $PSScriptRoot 'game-summary.ps1') +$cases=@( + @{name='empty-streams-debugger-refused'; stdout=''; stderr=''; debugger='EXIT code=0xdeadc0de'; session='process-failed-before-dlss-frames'}, + @{name='launcher-exit'; stdout=''; stderr=''; debugger='EXIT code=0x0'; session='process-exited-without-dlss-frames'}, + @{name='feature-not-found'; stdout='D4R_CAPABILITIES available=0 feature_init=0xbad00004 needs_driver=0'; stderr='D4R_WINDOWS_FAILURE unavailable'; debugger='EXIT code=0xc0000409'; session='ngx-feature-not-found'}, + @{name='complete-frame'; stdout="D4R_FRAME cpu_frame_copies=0 frame_age=0 interop_ngx_ms=12.4`nD4R_OUTPUT_VALIDATION elements=3 nan=0 inf=0 diagnostics_cpu_bytes=8"; + stderr='[d4r-launch] kernel="dltss_pwin_enc0_layer" backend=native'; debugger='EXIT code=0x0'; session='completed-dlss-frames'}, + @{name='truncated-frame'; stdout='D4R_FRAME cpu_frame_copies=0 frame_age=0 interop_ngx_ms='; stderr=''; debugger=''; session='diagnostic-timeout-without-dlss-frames'} +) +$results=@() +foreach ($case in $cases) { + $summary=Get-D4RGameSummary -Stdout $case.stdout -Stderr $case.stderr -Debugger $case.debugger + if ($summary.session -ne $case.session) { throw "Wrong session classification: $($case.name)" } + if ($case.name -eq 'complete-frame') { + if ($summary.completedFrames -ne 1 -or $summary.outputGpuChecks -ne 1 -or $summary.nativeLaunches -ne 1 -or + $summary.nonfiniteOutputs -or $summary.previousFrameOutputs -or $summary.cpuImageCopyFrames) { throw 'Complete frame counters are wrong.' } + } elseif ($summary.completedFrames -ne 0) { throw 'A failed or incomplete run was counted as a frame.' } + $results+=@{case=$case.name; passed=$true; summary=$summary} +} +if ($LogRoot) { + foreach ($entry in @{ 'acbf-k'='process-failed-before-dlss-frames'; 'cyberpunk-k'='ngx-feature-not-found'; + 'cyberpunk-m'='ngx-feature-not-found'; 'sm2-k'='process-exited-without-dlss-frames' }.GetEnumerator()) { + $dir=Get-ChildItem -LiteralPath (Join-Path $LogRoot $entry.Key) -Directory | Select-Object -First 1 + $summary=Get-D4RGameSummary -Stdout ([IO.File]::ReadAllText((Join-Path $dir.FullName 'd4r.stdout.log'))) ` + -Stderr ([IO.File]::ReadAllText((Join-Path $dir.FullName 'd4r.stderr.log'))) ` + -Debugger ([IO.File]::ReadAllText((Join-Path $dir.FullName 'debugger.log'))) + if ($summary.session -ne $entry.Value -or $summary.completedFrames -ne 0 -or $summary.nativeLaunches -ne 0) { throw "Unexpected classification for actual report $($entry.Key)" } + $results+=@{case='issue-10-'+$entry.Key; passed=$true; summary=$summary} + } +} +if ($OutputDirectory) { + New-Item -ItemType Directory -Force $OutputDirectory | Out-Null + $results | ConvertTo-Json -Depth 8 | Set-Content -LiteralPath (Join-Path $OutputDirectory 'summary-parser.json') -Encoding UTF8 +} +Write-Host "PASS GAME_SUMMARY cases=$($results.Count)" diff --git a/scripts/windows/test-ngx-feature-layout.ps1 b/scripts/windows/test-ngx-feature-layout.ps1 new file mode 100644 index 0000000..eb96d29 --- /dev/null +++ b/scripts/windows/test-ngx-feature-layout.ps1 @@ -0,0 +1,160 @@ +# Reproduce numeric NGX feature discovery without launching or modifying a game. +# The private NVIDIA DLL staging directory is deleted; reports contain logs only. +[CmdletBinding()] +param([Parameter(Mandatory=$true)][string]$DiagnosticRoot, + [Parameter(Mandatory=$true)][string]$HipRoot, + [Parameter(Mandatory=$true)][string]$ZludaRoot, + [Parameter(Mandatory=$true)][string]$NativeRoot, + [Parameter(Mandatory=$true)][string]$OptiScalerDll, + [Parameter(Mandatory=$true)][string]$NgxCore, + [Parameter(Mandatory=$true)][string]$DlssDll, + [Parameter(Mandatory=$true)][string]$OutputDirectory, + [UInt64]$ApplicationId=100152211) +$ErrorActionPreference='Stop' +. (Join-Path $PSScriptRoot 'gpu-target.ps1') +foreach ($name in @('DiagnosticRoot','HipRoot','ZludaRoot','NativeRoot','OptiScalerDll','NgxCore','DlssDll')) { + Set-Variable -Name $name -Value ((Get-Item -LiteralPath (Get-Variable -Name $name -ValueOnly)).FullName) +} +$arch=Get-D4RGpuTarget $DiagnosticRoot +$OutputDirectory=[IO.Path]::GetFullPath($OutputDirectory) +New-Item -ItemType Directory -Force $OutputDirectory | Out-Null +$runtime=Join-Path ([IO.Path]::GetTempPath()) ('d4r-feature-layout-'+[Guid]::NewGuid().ToString('N')) +$settings=@{HIP_PATH=$HipRoot; PATH="$HipRoot\bin;$ZludaRoot;$env:PATH"; + D4R_NVAPI_BACKEND=(Join-Path $ZludaRoot 'nvapi64.dll'); ZLUDA_CUDA_LIB=(Join-Path $ZludaRoot 'nvcuda.dll'); + D4R_ZLUDA_NATIVE_DIR=$NativeRoot; D4R_ZLUDA_VERBOSE='1'; + D4R_ZLUDA_WMMA='1'; D4R_ZLUDA_WMMA_F16_REFERENCE='1'; D4R_ZLUDA_WMMA_FP8='1'; D4R_ZLUDA_WMMA_FP8_NATIVE='0'; + D4R_VALIDATE_OUTPUT='1'; D4R_DIAG_DIR=$null; ZLUDA_LOG_DIR=$null; D4R_FORMAT_MODULE=$null; + D4R_ASYNC_INTEROP=$null; D4R_BATCH_INPUT_COPIES=$null; D4R_D3D12_COMMAND_BACKEND=$null} +$old=@{}; $results=@() +try { + foreach ($key in $settings.Keys) { $old[$key]=[Environment]::GetEnvironmentVariable($key,'Process'); [Environment]::SetEnvironmentVariable($key,$settings[$key],'Process') } + foreach ($dir in @('app','d4r','d4r/vendor')) { New-Item -ItemType Directory -Force (Join-Path $runtime $dir) | Out-Null } + $exe=Join-Path $runtime 'app/d4r_d3d12_dlss_probe.exe' + $shim=Join-Path $runtime 'd4r/_nvngx.dll' + $core=Join-Path $runtime 'd4r/vendor/_nvngx.dll' + $feature=Join-Path $runtime 'd4r/vendor/nvngx_dlss.dll' + Copy-Item -LiteralPath (Join-Path $DiagnosticRoot 'bin/d4r_d3d12_dlss_probe.exe') -Destination $exe + Copy-Item -LiteralPath (Join-Path $DiagnosticRoot 'bin/d4r_nvngx.dll') -Destination $shim + Copy-Item -LiteralPath (Join-Path $DiagnosticRoot "bin/pixel_convert_${arch}.hsaco") -Destination (Split-Path $shim) + Copy-Item -LiteralPath $NgxCore -Destination $core + Copy-Item -LiteralPath $DlssDll -Destination $feature + $opti=Join-Path $runtime 'app/OptiScaler.dll' + Copy-Item -LiteralPath $OptiScalerDll -Destination $opti + $cases=@(@{name='vendor-only-negative'; preset=11; frontend='d4r'; expectedExit=4}, + @{name='optiscaler-vendor-only-negative'; preset=11; frontend='optiscaler'; expectedExit=4}, + @{name='caller-dll-k'; preset=11; frontend='d4r'; expectedExit=0}, + @{name='optiscaler-caller-k'; preset=11; frontend='optiscaler'; expectedExit=0}, + @{name='optiscaler-caller-m'; preset=13; frontend='optiscaler'; expectedExit=0}) + foreach ($case in $cases) { + $out=Join-Path $OutputDirectory $case.name + New-Item -ItemType Directory -Force $out | Out-Null + if ($case.expectedExit -eq 0) { + $feature=Join-Path $runtime 'd4r/nvngx_dlss.dll' + if (!(Test-Path -LiteralPath $feature)) { Copy-Item -LiteralPath $DlssDll -Destination $feature } + } + $env:D4R_DIAG_DIR=$out; $env:ZLUDA_LOG_DIR=Join-Path $out 'zluda-trace' + $env:D4R_FORMAT_MODULE=Join-Path $runtime "d4r/pixel_convert_${arch}.hsaco" + $module=if ($case.frontend -eq 'optiscaler') { $opti } else { $shim } + if ($case.frontend -eq 'optiscaler') { + @" +[Upscalers] +Dx12Upscaler=dlss +[FrameGen] +Enabled=false +FGInput=nofg +FGOutput=nofg +[DLSS] +Enabled=true +AllowExternalBackend=true +RenderPresetOverride=true +RenderPresetForAll=$($case.preset) +UseGenericAppIdWithDlss=false +[Libraries] +NvngxPath=$(Split-Path $shim) +NvngxDlssPath=$(Split-Path $feature) +NvngxFeaturePath=$(Split-Path $feature) +[Hotfix] +RestoreComputeSignature=false +RestoreGraphicSignature=false +ExtendedStateRestore=false +[Log] +LogToFile=true +LogLevel=0 +[Menu] +DisableSplash=true +"@ | Set-Content -LiteralPath (Join-Path $runtime 'app/OptiScaler.ini') -Encoding UTF8 + } + $arguments=@('--hip-root',$HipRoot,'--cuda-dll',(Join-Path $ZludaRoot 'nvcuda.dll'), + '--module',$module,'--ngx-core',$core,'--dlss-dll',$feature, + '--nvapi-dll',(Join-Path $DiagnosticRoot 'nvapi-compat/nvapi64.dll'), + '--ngx-app-id',"$ApplicationId",'--ngx-abi','driver','--ngx-frontend',$case.frontend, + '--interop-mode','command-list','--iterations','3','--preset',"$($case.preset)") + $info=New-Object Diagnostics.ProcessStartInfo + # Capture harness faults outside OptiScaler's loader hooks. Games use + # the no-debug launcher by default; this developer reproducer is separate. + $arguments=@('--output-directory',$out,'--',$exe)+$arguments + $info.FileName=Join-Path $DiagnosticRoot 'bin/d4r_debug_launcher.exe'; $info.WorkingDirectory=Split-Path $exe + $info.Arguments=(@($arguments | ForEach-Object { if ($_.Contains('"')) { throw 'Unsupported quote in test path.' }; '"'+$_+'"' }) -join ' ') + $info.UseShellExecute=$false; $info.CreateNoWindow=$true; $info.RedirectStandardOutput=$true; $info.RedirectStandardError=$true + foreach ($key in $settings.Keys) { if (![Environment]::GetEnvironmentVariable($key,'Process')) { $info.EnvironmentVariables.Remove($key) } } + $process=New-Object Diagnostics.Process; $process.StartInfo=$info + try { + [void]$process.Start(); $stdout=$process.StandardOutput.ReadToEndAsync(); $stderr=$process.StandardError.ReadToEndAsync() + if (!$process.WaitForExit(240000)) { $process.Kill(); $process.WaitForExit(); throw "NGX layout test timed out: $($case.name)" } + $log=$stdout.GetAwaiter().GetResult(); $errorLog=$stderr.GetAwaiter().GetResult() + [IO.File]::WriteAllText((Join-Path $out 'stdout.log'),$log) + [IO.File]::WriteAllText((Join-Path $out 'stderr.log'),$errorLog) + if ($process.ExitCode -ne $case.expectedExit) { throw "Unexpected exit $($process.ExitCode): $($case.name), see $out" } + if ($case.expectedExit) { + if ($log -notmatch 'D4R_CAPABILITIES available=0 feature_init=0xbad00004' -or $log -match 'D4R_FRAME ') { throw 'Old vendor-only layout did not reproduce FeatureNotFound.' } + if ($case.frontend -eq 'optiscaler' -and $errorLog -notmatch 'FAIL D3D12_DLSS D3D12 Init NGX=') { throw 'OptiScaler swallowed the numeric backend initialization failure.' } + } else { + if ($log -notmatch 'D4R_CAPABILITIES available=1 feature_init=0x00000001 needs_driver=0' -or + ([regex]::Matches($log,'D3D12_OUTPUT preset=\d+ frame=\d+ finite=1')).Count -ne 3 -or + ([regex]::Matches($log,'D4R_OUTPUT_VALIDATION elements=\d+ nan=0 inf=0 diagnostics_cpu_bytes=8')).Count -ne 3 -or + $log -notmatch 'PASS D3D12_DLSS .*fast_path_cpu_copies=0 frame_age=0' -or $errorLog -match 'D4R_WINDOWS_FAILURE') { throw 'Corrected feature layout did not complete finite zero-copy frames.' } + $required=@(Get-Content -LiteralPath (Join-Path $NativeRoot 'd4r-kernels.txt') | Where-Object { + if ($case.preset -eq 11) { $_ -match '^dltss_pwin_' } else { $_ -match '^rrlite_' } + } | ForEach-Object { $_.Split(' ')[0] }) + if ($required.Count -ne $(if ($case.preset -eq 11) { 11 } else { 5 })) { throw 'Incomplete native manifest.' } + foreach ($kernel in $required) { + if ($errorLog -notmatch ('\[d4r-launch\] kernel="'+[regex]::Escape($kernel)+'" backend=native') -or + $errorLog -match ('\[d4r-launch\] kernel="'+[regex]::Escape($kernel)+'" backend=translated')) { throw "Missing native network execution: $kernel" } + } + if ($case.frontend -eq 'd4r' -and $log -notmatch 'D3D12_REQUIREMENTS warp_supported=0 enumeration_success=1') { throw 'WARP adapter enumeration regression failed.' } + } + $results+=@{case=$case.name; passed=$true; expectedExit=$case.expectedExit; application=$ApplicationId; preset=$case.preset; architecture=$arch} + Write-Host "PASS NGX_FEATURE_LAYOUT $($case.name)" + } finally { + if ($case.frontend -eq 'optiscaler') { + Get-ChildItem -LiteralPath (Split-Path $exe) -Filter '*.log' -File | ForEach-Object { + Copy-Item -LiteralPath $_.FullName -Destination $out -Force + } + } + $process.Dispose() + } + } + for ($frame=0; $frame -lt 3; $frame++) { + $direct=[IO.File]::ReadAllBytes((Join-Path $OutputDirectory "caller-dll-k/output-$frame.rgba16f")) + $frontend=[IO.File]::ReadAllBytes((Join-Path $OutputDirectory "optiscaler-caller-k/output-$frame.rgba16f")) + if ($direct.Length -ne $frontend.Length -or $direct.Length -ne 512*288*8) { throw 'RGB comparison dimensions differ.' } + for ($pixel=0; $pixel -lt $direct.Length; $pixel+=8) { + if (([BitConverter]::ToUInt64($direct,$pixel) -band [UInt64]0xffffffffffff) -ne + ([BitConverter]::ToUInt64($frontend,$pixel) -band [UInt64]0xffffffffffff)) { throw "K frontend RGB mismatch: frame=$frame offset=$pixel" } + } + } + $results+=@{case='k-direct-optiscaler-rgb'; passed=$true; frames=3; rgbByteExact=$true; maxAbsoluteError=0; maxRelativeError=0} + Write-Host 'PASS NGX_FEATURE_LAYOUT k_direct_optiscaler_rgb exact=1 max_abs=0 max_rel=0' +} finally { + foreach ($key in $old.Keys) { [Environment]::SetEnvironmentVariable($key,$old[$key],'Process') } + $results | ConvertTo-Json -Depth 6 | Set-Content -LiteralPath (Join-Path $OutputDirectory 'feature-layout.json') -Encoding UTF8 + $resolved=[IO.Path]::GetFullPath($runtime) + $temp=[IO.Path]::GetFullPath([IO.Path]::GetTempPath()).TrimEnd('\')+'\' + if (!$resolved.StartsWith($temp,[StringComparison]::OrdinalIgnoreCase) -or [IO.Path]::GetFileName($resolved) -notmatch '^d4r-feature-layout-[0-9a-f]{32}$') { throw 'Refusing cleanup outside private NGX staging.' } + if (Test-Path -LiteralPath $resolved) { + foreach ($attempt in 1..10) { + try { Remove-Item -LiteralPath $resolved -Recurse -Force; break } + catch { if ($attempt -eq 10) { throw }; Start-Sleep -Milliseconds 300 } + } + } +} diff --git a/scripts/windows/test-windows-quick-test.ps1 b/scripts/windows/test-windows-quick-test.ps1 index 5a28d78..b875f5f 100644 --- a/scripts/windows/test-windows-quick-test.ps1 +++ b/scripts/windows/test-windows-quick-test.ps1 @@ -29,11 +29,15 @@ $results=@() $exe=Fixture ('space GPU '+[char]0x03a9+' test') $game=Split-Path $exe $before=@{} -foreach ($name in @('dxgi.dll','OptiScaler.ini')) { +foreach ($name in @('dxgi.dll','OptiScaler.ini','nvngx_dlss.dll')) { Set-Content -LiteralPath (Join-Path $game $name) "original $name" -Encoding ASCII $before[$name]=(Get-FileHash -LiteralPath (Join-Path $game $name)).Hash } Invoke-Child 'install' @('-Action','install','-GameExe',$exe,'-NgxCore',$NgxCore,'-DlssDll',$DlssDll) +if ((Get-FileHash -LiteralPath (Join-Path $game 'd4r/nvngx_dlss.dll')).Hash -ne (Get-FileHash -LiteralPath $DlssDll).Hash) { + throw 'Numeric NGX driver feature DLL was not staged beside the shim.' +} +if ((Get-FileHash -LiteralPath (Join-Path $game 'nvngx_dlss.dll')).Hash -ne $before['nvngx_dlss.dll']) { throw 'Original application DLSS DLL was changed.' } $saved=Get-Content -LiteralPath (Join-Path $PackageRoot 'work/settings.json') -Raw | ConvertFrom-Json if ($saved.game -ne $exe -or $saved.architecture -ne 'gfx1201') { throw 'GPU detection or remembered selection failed on this RX9070XT fixture.' } # Restore uses remembered game selection and needs neither HIP nor NVIDIA DLLs. @@ -41,6 +45,29 @@ Invoke-Child 'restore' @('-Action','restore') foreach ($name in $before.Keys) { if ((Get-FileHash -LiteralPath (Join-Path $game $name)).Hash -ne $before[$name]) { throw 'Original game bytes not restored.' } } if (Test-Path -LiteralPath (Join-Path $game 'd4r')) { throw 'Runtime directory remains after restore.' } $results+=@{case='auto-gfx1201-install-remember-restore'; passed=$true; gpuKernelsExecuted=$false} +# A harmless child refuses attached debuggers and produces an empty stderr. +# The default launcher must allow it to exit and must report no DLSS session. +$exe=Join-Path (Join-Path $OutputDirectory 'launcher-fixture') 'Fixture.exe' +New-Item -ItemType Directory -Force (Split-Path $exe) | Out-Null +Add-Type -TypeDefinition @' +using System; +using System.Runtime.InteropServices; +public static class D4RLauncherFixture { + [DllImport("kernel32.dll")] private static extern bool IsDebuggerPresent(); + public static int Main(string[] args) { return IsDebuggerPresent() ? 91 : 0; } +} +'@ -OutputAssembly $exe -OutputType ConsoleApplication +Invoke-Child 'run-no-debug' @('-Action','run','-GameExe',$exe,'-NgxCore',$NgxCore,'-DlssDll',$DlssDll) 1 +$report=Get-ChildItem -LiteralPath (Join-Path $PackageRoot 'results') -Directory | Sort-Object Name -Descending | Select-Object -First 1 +$status=Get-Content -LiteralPath (Join-Path $report.FullName 'quick-test.json') -Raw | ConvertFrom-Json +$environment=Get-Content -LiteralPath (Join-Path $report.FullName 'environment.json') -Raw | ConvertFrom-Json +if ($environment.attachedDebugger -or $status.hardwareValidationCompleted -or + $status.status -ne 'inconclusive-process-exited-without-dlss-frames' -or $status.gameSummary.exitCode -ne '0x0' -or + (Get-Item -LiteralPath (Join-Path $report.FullName 'd4r.stderr.log')).Length -ne 0) { + throw 'Default launch attaches a debugger, mishandles empty stderr or falsely validates a launcher exit.' +} +Invoke-Child 'restore-launcher-fixture' @('-Action','restore','-GameExe',$exe) +$results+=@{case='run-no-debug-empty-stderr-no-false-pass'; passed=$true; gpuKernelsExecuted=$false} foreach ($case in @('wrong-core','wrong-dlss','vkd3d')) { $exe=Fixture $case; $game=Split-Path $exe $core=if ($case -eq 'wrong-core') { Join-Path $PackageRoot 'files/targets/gfx1201/d4r/_nvngx.dll' } else { $NgxCore } @@ -64,4 +91,4 @@ try { $results+=@{case='tampered-package'; passed=$true; rejectedBeforeGameChanges=$true; gpuKernelsExecuted=$false} } finally { [IO.File]::WriteAllBytes($readme,$original) } $results | ConvertTo-Json | Set-Content -LiteralPath (Join-Path $OutputDirectory 'summary.json') -Encoding UTF8 -Write-Host "PASS QUICK_TEST_FIXTURES positive=1 negative=4 real_game_untouched=1 Report: $OutputDirectory" +Write-Host "PASS QUICK_TEST_FIXTURES cases=$($results.Count) real_game_untouched=1 Report: $OutputDirectory" diff --git a/scripts/windows/test-windows-rdna4.ps1 b/scripts/windows/test-windows-rdna4.ps1 index 9bea7e2..ecd9450 100644 --- a/scripts/windows/test-windows-rdna4.ps1 +++ b/scripts/windows/test-windows-rdna4.ps1 @@ -314,7 +314,7 @@ RenderPresetForAll=$Preset UseGenericAppIdWithDlss=false [Libraries] NvngxPath=$bridgeDirectory -NvngxDlssPath=$localDlss +NvngxDlssPath=$ngxRuntimeDirectory NvngxFeaturePath=$ngxRuntimeDirectory [Hotfix] RestoreComputeSignature=false diff --git a/scripts/windows/windows-game.ps1 b/scripts/windows/windows-game.ps1 index 83dbd75..fb9c138 100644 --- a/scripts/windows/windows-game.ps1 +++ b/scripts/windows/windows-game.ps1 @@ -28,6 +28,7 @@ param( $ErrorActionPreference = 'Stop' . (Join-Path $PSScriptRoot 'gpu-target.ps1') . (Join-Path $PSScriptRoot 'gpu-preflight.ps1') +. (Join-Path $PSScriptRoot 'game-summary.ps1') if ($ProfileGpuBoundary -and !$AsyncInterop) { throw '-ProfileGpuBoundary requires -AsyncInterop.' } if ($ProfileKernels -and $ProfileKernelsDeferred) { throw 'Choose either serializing or deferred kernel profiling.' } if ($ProfileLegacyStream -and !$ProfileKernelsDeferred) { throw '-ProfileLegacyStream requires -ProfileKernelsDeferred.' } @@ -144,7 +145,10 @@ if ($LocalTextureKernels) { } # Private local test copies only; these are never added to the public package. InstallFile $NgxCore 'd4r/vendor/_nvngx.dll' -InstallFile $DlssDll 'd4r/vendor/nvngx_dlss.dll' +# The numeric driver Init ABI searches the calling module's directory; it does +# not accept FeatureCommonInfo. Keep the exact local feature DLL alongside the +# d4r shim. Putting it only beside the core in vendor/ produces FeatureNotFound. +InstallFile $DlssDll 'd4r/nvngx_dlss.dll' $bridge = GamePath 'd4r' $vendor = GamePath 'd4r/vendor' $ini = @" @@ -162,8 +166,8 @@ RenderPresetForAll=$Preset UseGenericAppIdWithDlss=false [Libraries] NvngxPath=$bridge -NvngxDlssPath=$vendor\nvngx_dlss.dll -NvngxFeaturePath=$vendor +NvngxDlssPath=$bridge +NvngxFeaturePath=$bridge [Hotfix] RestoreComputeSignature=false RestoreGraphicSignature=false @@ -187,7 +191,7 @@ $settings = @{ D4R_HIP_ROOT=(GamePath 'd4r/hip'); HIP_PATH=(GamePath 'd4r/hip'); D4R_NVCUDA_DLL=(GamePath 'd4r/zluda/nvcuda.dll'); ZLUDA_CUDA_LIB=(GamePath 'd4r/zluda/nvcuda.dll'); D4R_NVAPI_DLL=(GamePath 'd4r/nvapi/nvapi64.dll'); D4R_NVAPI_BACKEND=(GamePath 'd4r/zluda/nvapi64.dll'); - D4R_NGX_CORE=(GamePath 'd4r/vendor/_nvngx.dll'); D4R_DLSS_DLL=(GamePath 'd4r/vendor/nvngx_dlss.dll'); + D4R_NGX_CORE=(GamePath 'd4r/vendor/_nvngx.dll'); D4R_DLSS_DLL=(GamePath 'd4r/nvngx_dlss.dll'); D4R_FORMAT_MODULE=(GamePath "d4r/pixel_convert_${GpuArch}.hsaco"); D4R_ZLUDA_NATIVE_DIR=(GamePath 'd4r/native'); D4R_D3D12_COMMAND_BACKEND='1'; D4R_ZLUDA_VERBOSE='1'; D4R_ZLUDA_WMMA='1'; D4R_ZLUDA_WMMA_FP8='1'; D4R_ZLUDA_WMMA_FP8_NATIVE='0'; D4R_ZLUDA_WMMA_F16_REFERENCE='1'; @@ -288,9 +292,9 @@ try { [void]$outTask.GetAwaiter().GetResult(); [void]$errTask.GetAwaiter().GetResult() $outFile.Dispose(); $outFile = $null; $errFile.Dispose(); $errFile = $null foreach ($file in Get-ChildItem -LiteralPath $game -Filter 'OptiScaler*.log' -File) { Copy-Item -LiteralPath $file.FullName -Destination $OutputDirectory -Force } - $stderr = Get-Content -LiteralPath (Join-Path $OutputDirectory 'd4r.stderr.log') -Raw - $stdout = Get-Content -LiteralPath (Join-Path $OutputDirectory 'd4r.stdout.log') -Raw - $debugger = Get-Content -LiteralPath (Join-Path $OutputDirectory 'debugger.log') -Raw + $stderr = [IO.File]::ReadAllText((Join-Path $OutputDirectory 'd4r.stderr.log')) + $stdout = [IO.File]::ReadAllText((Join-Path $OutputDirectory 'd4r.stdout.log')) + $debugger = [IO.File]::ReadAllText((Join-Path $OutputDirectory 'debugger.log')) $gameExit = [regex]::Match($debugger, 'EXIT code=0x([0-9a-f]+)') $gameCrash = $null if ([IO.Path]::GetFileName($GameExe) -eq 'SHProto-Win64-Shipping.exe' -and $gameExit.Success -and $gameExit.Groups[1].Value -ne '0') { @@ -305,21 +309,8 @@ try { reason=[string]$context.FGenericCrashContext.RuntimeProperties.ErrorMessage} } } - # A bounded stop can interrupt the last printf; count only complete records. - $frames = [regex]::Matches($stdout, '(?m)^D4R_FRAME cpu_frame_copies=([0-9]+) frame_age=([0-9]+) interop_ngx_ms=([0-9.]+)\r?$') - $checks = [regex]::Matches($stdout, '(?m)^D4R_OUTPUT_VALIDATION elements=([0-9]+) nan=([0-9]+) inf=([0-9]+) diagnostics_cpu_bytes=8\r?$') - $nonfinite = @($checks | Where-Object { $_.Groups[2].Value -ne '0' -or $_.Groups[3].Value -ne '0' }).Count - $launches = [regex]::Matches($stderr, '\[d4r-launch\] kernel="([^"]+)" backend=(native|translated)') - $kernels = @($launches | ForEach-Object { $_.Groups[1].Value + ' ' + $_.Groups[2].Value } | Group-Object | ForEach-Object { - $parts=$_.Name.Split(' '); @{kernel=$parts[0]; backend=$parts[1]; launches=$_.Count} - }) - @{exitCode=$(if ($gameExit.Success) { '0x' + $gameExit.Groups[1].Value } else { 'diagnostic_timeout' }); runSeconds=$timer.Elapsed.TotalSeconds; preset=$Preset; gameCrash=$gameCrash; - nativeLaunches=([regex]::Matches($stderr, '\[d4r-launch\].*backend=native')).Count; - completedFrames=$frames.Count; outputValidationRequested=[bool]$ValidateOutput; - outputGpuChecks=$checks.Count; nonfiniteOutputs=$nonfinite; kernels=$kernels; - previousFrameOutputs=@($frames | Where-Object { $_.Groups[2].Value -ne '0' }).Count; - cpuImageCopyFrames=@($frames | Where-Object { $_.Groups[1].Value -ne '0' }).Count; - failures=([regex]::Matches($stderr, 'D4R_[A-Z0-9_]*FAILURE\b')).Count} | + Get-D4RGameSummary -Stdout $stdout -Stderr $stderr -Debugger $debugger -Preset $Preset ` + -RunSeconds $timer.Elapsed.TotalSeconds -ValidateOutput ([bool]$ValidateOutput) -GameCrash $gameCrash | ConvertTo-Json -Depth 6 | Set-Content -LiteralPath (Join-Path $OutputDirectory 'summary.json') -Encoding UTF8 Compress-Archive -LiteralPath $OutputDirectory -DestinationPath ($OutputDirectory + '.zip') -Force Write-Host "Game diagnostic bundle: $OutputDirectory.zip" diff --git a/tools/windows/d3d12_dlss_probe.cpp b/tools/windows/d3d12_dlss_probe.cpp index 6fcb5de..bdf3c1b 100644 --- a/tools/windows/d3d12_dlss_probe.cpp +++ b/tools/windows/d3d12_dlss_probe.cpp @@ -218,13 +218,21 @@ int main(int argc, char** argv) { using Requirements = unsigned(*)(IDXGIAdapter*,const d4r::ngx::FeatureDiscoveryInfo*,d4r::ngx::FeatureRequirement*); check(shim.symbol("NVSDK_NGX_D3D12_GetFeatureRequirements")(adapter.Get(),&discovery,&requirements),"D3D12 feature requirements"); if (requirements.FeatureSupported != 0) throw std::runtime_error("D3D12 DLSS requirements unsupported"); + if (args.ngx_frontend != "optiscaler") { + ComPtr warp; + dx(factory->EnumWarpAdapter(IID_PPV_ARGS(warp.GetAddressOf())), "Requirements WARP adapter"); + d4r::ngx::FeatureRequirement unsupported{}; + check(shim.symbol("NVSDK_NGX_D3D12_GetFeatureRequirements")(warp.Get(),&discovery,&unsupported),"WARP feature requirements"); + if (unsupported.FeatureSupported != 4) throw std::runtime_error("WARP must report AdapterUnsupported without an initialization failure"); + std::printf("D3D12_REQUIREMENTS warp_supported=0 enumeration_success=1\n"); + } if (args.ngx_abi == "project-legacy") { using ProjectInit = unsigned(*)(const char*,int,const char*,const wchar_t*,ID3D12Device*,const void*,unsigned); check(shim.symbol("NVSDK_NGX_D3D12_Init_with_ProjectID")("24480451-f00d-face-1304-0308dabad187",0,"1.0",directory.c_str(),device.Get(),nullptr,0x15),"D3D12 legacy project Init"); } else if (args.ngx_abi == "project") { using ProjectInit = unsigned(*)(const char*,int,const char*,const wchar_t*,ID3D12Device*,unsigned,const void*); check(shim.symbol("NVSDK_NGX_D3D12_Init_ProjectID")("24480451-f00d-face-1304-0308dabad187",0,"1.0",directory.c_str(),device.Get(),0x15,nullptr),"D3D12 project Init"); - } else check(shim.symbol("NVSDK_NGX_D3D12_Init_Ext")(241534723ull, directory.c_str(), device.Get(), 0x15, nullptr), "D3D12 Init"); + } else check(shim.symbol("NVSDK_NGX_D3D12_Init_Ext")(args.ngx_app_id, directory.c_str(), device.Get(), 0x15, nullptr), "D3D12 Init"); auto shutdown = shim.symbol("NVSDK_NGX_D3D12_Shutdown"); struct ShutdownCleanup { Shutdown fn; ~ShutdownCleanup() { (void)fn(); } } shutdownCleanup{shutdown}; void* parameters = nullptr; check(shim.symbol("NVSDK_NGX_D3D12_AllocateParameters")(¶meters), "AllocateParameters"); diff --git a/tools/windows/d4r_nvngx_windows.cpp b/tools/windows/d4r_nvngx_windows.cpp index 2f3696a..4d550fe 100644 --- a/tools/windows/d4r_nvngx_windows.cpp +++ b/tools/windows/d4r_nvngx_windows.cpp @@ -87,10 +87,13 @@ API unsigned NVSDK_NGX_D3D12_GetFeatureRequirements(IDXGIAdapter* adapter, const DXGI_ADAPTER_DESC desc{}; d4r::win::dx(adapter->GetDesc(&desc), "NGX requirements adapter"); // OptiScaler can override the DXGI vendor ID. The physical HIP // architecture and full adapter LUID are the authoritative identity. - d4r::diag::HipApi hip(d4r::win::env_path("D4R_HIP_ROOT")); hipDeviceProp_t props{}; hip.select_architecture(-1,props,&desc.AdapterLuid); - requirements->FeatureSupported = std::memcmp(&desc.AdapterLuid,props.luid,sizeof(LUID)) ? 4u : 0u; + d4r::diag::HipApi hip(d4r::win::env_path("D4R_HIP_ROOT")); hipDeviceProp_t props{}; + const int ordinal = hip.select_architecture(-1,props,&desc.AdapterLuid,true); + requirements->FeatureSupported = ordinal < 0 ? 4u : 0u; std::strcpy(requirements->MinOSVersion,"10.0.22000.0"); - std::printf("D4R_REQUIREMENTS feature=%u support=%u architecture=%s\n",info->FeatureID,requirements->FeatureSupported,d4r::diag::HipApi::target_arch()); + std::printf("D4R_REQUIREMENTS feature=%u support=%u architecture=%s adapter_luid=%08lx:%08lx hip_ordinal=%d\n", + info->FeatureID,requirements->FeatureSupported,d4r::diag::HipApi::target_arch(), + static_cast(desc.AdapterLuid.HighPart),static_cast(desc.AdapterLuid.LowPart),ordinal); return 1u; }); } diff --git a/tools/windows/diagnostic.h b/tools/windows/diagnostic.h index ad17731..5475855 100644 --- a/tools/windows/diagnostic.h +++ b/tools/windows/diagnostic.h @@ -122,6 +122,7 @@ struct Args { std::string ngx_core, dlss_dll; std::string nvapi_dll; std::string ngx_abi = "driver"; + unsigned long long ngx_app_id = 241534723ull; std::string ngx_frontend = "d4r"; std::string ngx_mode = "init"; unsigned preset = 11; @@ -160,6 +161,12 @@ 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-app-id") { + size_t end = 0; + if (value.empty() || value[0] == '-') throw std::runtime_error("Invalid NGX application ID"); + ngx_app_id = std::stoull(value, &end); + if (end != value.size()) throw std::runtime_error("Invalid NGX application ID"); + } else if (key == "--ngx-frontend") { if (value != "d4r" && value != "optiscaler") throw std::runtime_error("ngx-frontend must be d4r or optiscaler"); ngx_frontend = value; diff --git a/tools/windows/hip_api.h b/tools/windows/hip_api.h index c96e539..4e8fc81 100644 --- a/tools/windows/hip_api.h +++ b/tools/windows/hip_api.h @@ -42,7 +42,8 @@ struct HipApi { if (result != hipSuccess) throw std::runtime_error(std::string(call) + ": " + hipGetErrorString(result)); } static const char* target_arch() { return D4R_TARGET_ARCH; } - int select_architecture(int requested, hipDeviceProp_t& selected, const void* required_luid = nullptr) const { + int select_architecture(int requested, hipDeviceProp_t& selected, const void* required_luid = nullptr, + bool allow_no_match = false) const { // Architecture spoofing invalidates a gfx12 correctness test. for (const char* name : {"HSA_OVERRIDE_GFX_VERSION", "HSA_OVERRIDE_GFX_VERSION_0"}) if (std::getenv(name)) throw std::runtime_error(std::string("Unset architecture override ") + name); @@ -67,6 +68,7 @@ struct HipApi { selected = props; } } + if (found < 0 && allow_no_match) return -1; // Capability enumeration can include Intel/WARP adapters. if (found < 0) throw std::runtime_error(std::string("No selected ") + target_arch() + " device for this adapter; use the package matching the actual HIP architecture (see GPU log)"); require_gpu_target(selected.gcnArchName, target_arch()); diff --git a/tools/windows/ngx_cuda_init_probe.cpp b/tools/windows/ngx_cuda_init_probe.cpp index da3d8bb..55e0815 100644 --- a/tools/windows/ngx_cuda_init_probe.cpp +++ b/tools/windows/ngx_cuda_init_probe.cpp @@ -88,8 +88,9 @@ int main(int argc, char** argv) 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)"); + std::printf("NGX_APPLICATION id=%llu\n", args.ngx_app_id); + if (args.ngx_abi == "driver") check(driver_init(args.ngx_app_id, data_path.c_str(), 0x15), "NVSDK_NGX_CUDA_Init(driver ABI)"); + else if (args.ngx_abi == "sdk") check(init(args.ngx_app_id, data_path.c_str(), &common, 0x15), "NVSDK_NGX_CUDA_Init(SDK ABI)"); else if (args.ngx_abi == "project") check(core.symbol("NVSDK_NGX_CUDA_Init_ProjectID")( "24480451-f00d-face-1304-0308dabad187", 0, "1.0", data_path.c_str(), 0x15, &common), "NVSDK_NGX_CUDA_Init_ProjectID"); else throw std::runtime_error("--ngx-abi must be driver, sdk or project");