From 774efac52420dcef23ab9c53da2ff68a3733eb95 Mon Sep 17 00:00:00 2001 From: ayo <93222141+countervolts@users.noreply.github.com> Date: Sat, 3 Oct 2026 04:58:05 -0400 Subject: [PATCH] Windows: DLSS watermark off, check script, 310.7.0 pair, shorter docs - Set Disable.Watermark on the game path (the Linux shim already does); [DLSS] ShowWatermark = true keeps NVIDIA's stamp. - Always write d4r\d4r_nvngx.log in player mode. It was skipped when the launcher gave the game a valid stderr pipe, e.g. Steam. - Accept NVIDIA's 310.7.0 + _nvngx 32.0.15.9636 as "less tested" pins. Native kernels are still gated by PTX identity, so a different DLL falls back to translated kernels. - Add d4r-check.ps1 (ok/MISSING/note lines with a fix for each; -Report writes a bug-report ZIP) with 15 fixture tests. - package-windows-release.ps1: -Architectures subset, ships the check script, GPU registry and one GPU-query tool. - Add a shorter WINDOWS_README.txt; trim README's Windows section and merge in the RX 9060 XT / distributor-DLL notes. - Diagnostic probe accepts preset 12 (L). Tested on RX 7700 XT without a game: loader and shim start-up, K and M synthetic NGX frames with both DLL pairs (finite output, native kernels used). Not tested in a game. --- README.md | 49 +--- packaging/windows/WINDOWS_README.txt | 63 ++++ packaging/windows/d4r.ini | 3 + scripts/windows/d4r-check.ps1 | 271 ++++++++++++++++++ scripts/windows/package-windows-release.ps1 | 17 +- scripts/windows/test-d4r-check.ps1 | 122 ++++++++ .../windows/test-windows-release-package.ps1 | 18 +- tools/windows/d4r_nvngx_windows.cpp | 5 + tools/windows/diagnostic.h | 4 +- tools/windows/dll-pins.txt | 9 +- tools/windows/player_config.h | 22 +- tools/windows/player_config_probe.cpp | 6 + 12 files changed, 519 insertions(+), 70 deletions(-) create mode 100644 packaging/windows/WINDOWS_README.txt create mode 100644 scripts/windows/d4r-check.ps1 create mode 100644 scripts/windows/test-d4r-check.ps1 diff --git a/README.md b/README.md index ccda661..e240ef7 100644 --- a/README.md +++ b/README.md @@ -8,54 +8,11 @@ Preset L is NVIDIA's DLSS 4.5 model for Ultra Performance, especially at 4K. Set **Proof of concept:** d4r shows that DLSS can run on an AMD GPU, but it is not really that practical for everyday use yet. It has been tested on one GPU in a handful of games and depends on unreleased patches to ZLUDA and vkd3d-proton. -**Native Windows development:** an experimental D3D12 backend runs K and M -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 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. -M retains the FP16-equivalent baseline; native FP8 has not established a -full-network speedup. Windows preset L is not validated. See -[Windows build and validation](docs/windows-rdna4-port.md), -[OptiScaler installation](docs/windows-game.md) and -[Windows measurements](docs/windows-performance.md). NVIDIA DLLs are supplied -locally and excluded from packages. +**Windows (experimental):** d4r also runs natively on Windows 11, in D3D12 games, with no Wine or Proton. It supports the DLSS 4 (K) and DLSS 4.5 (M) models. Installing works like the Linux release: extract the ZIP into the game folder, copy in two NVIDIA files, and edit `d4r\d4r.ini`. See [the Windows guide](packaging/windows/WINDOWS_README.txt); `d4r\d4r-check.ps1` checks an install and writes a bug-report ZIP. -Experimental Windows builds also cover RDNA3 `gfx1100..gfx1103`, RDNA3.5 -`gfx1150..gfx1154` and RDNA4 `gfx1200/gfx1201`, with separate guarded packages. -Only RX 9070 XT has physical validation here; other targets are compile-tested. -Community RX 9060 XT / gfx1200 logs now show native K/M execution and finite -output in Cyberpunk and Dawnwalker. Visual issues are still reported, so this -is backend coverage rather than completed image-quality validation. -The installer checks the real HIP target before changing game files. See -[Windows GPU coverage and issue #10 diagnostics](docs/windows-gpu-support.md). +Only the Radeon RX 9070 XT (gfx1201) has been tested on real hardware here. Its backend checks pass for K and M in Silent Hill 2 and Cyberpunk 2077 at 4K; those runs are correctness checks, not FPS benchmarks, and Windows performance is still under development. Community logs from an RX 9060 XT (gfx1200) show native K/M execution and finite output in Cyberpunk and Dawnwalker, but texture/LOD and motion artifacts are still reported, so that is backend coverage and not completed image-quality validation. The other targets (RDNA3 `gfx1100..gfx1103`, RDNA3.5 `gfx1150..gfx1154`) are compile-tested only. Windows preset E and L are not available. NVIDIA DLLs are supplied by you and are never part of the repository; the quick launcher can use a distributor-supplied DLL pair, but that packaging support does not establish redistribution permission. -For a short Windows hardware test, the fork's -[quick-test prereleases](https://github.com/xdfnx-dev/d4r/releases) provide one -ZIP that detects the GPU automatically. Extract it and double-click START-K.cmd -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. Cyberpunk 2077 2.31 also reaches saved 4K gameplay with K and M: -13,145 K session frames and 5,268 M frames pass GPU output checks, every -required native transformer layer executes, and no backend failures, -previous-frame outputs or CPU image copies occur. Sessions include menus and -loading; this synchronous diagnostic is not an FPS benchmark. Other GPUs, -including the reporter's RX 9060 XT / gfx1200, required further testing at that point. -The subsequent RX 9060 XT retest passes all required native-layer/output -checks in Cyberpunk and Dawnwalker; texture/LOD and motion artifacts remain -open. Distributor-supplied DLL pairs can now be selected automatically by the -quick launcher. The public fork ZIP still uses locally supplied NVIDIA files; -packaging support does not establish redistribution permission. +More: [Windows GPU coverage](docs/windows-gpu-support.md), [measurements](docs/windows-performance.md), [build and validation notes](docs/windows-rdna4-port.md), and the [diagnostic launcher](docs/windows-game.md) and [quick test](docs/windows-quick-test.txt) used for bug reports. See [supported games](SUPPORTED_GAMES.md) for the tested games and DLSS models. diff --git a/packaging/windows/WINDOWS_README.txt b/packaging/windows/WINDOWS_README.txt new file mode 100644 index 0000000..d9fa8e5 --- /dev/null +++ b/packaging/windows/WINDOWS_README.txt @@ -0,0 +1,63 @@ +d4r for Windows: NVIDIA DLSS on AMD Radeon RDNA3 and RDNA4 +=========================================================== + +d4r runs NVIDIA's own DLSS (DLSS 4 by default) on a Radeon GPU in DirectX 12 games. OptiScaler +(included) catches the game's upscaler calls and hands them to d4r. + +This is an early release. Only the Radeon RX 9070 XT has been tested on real hardware; every other +GPU in this release is compile-tested. SUPPORTED_GAMES.md in the source repository lists the +tested games. Expect problems. + +What you need +------------- +- Windows 11, a Radeon RX 7000 or RX 9000 series GPU, a current AMD Adrenalin driver. +- A DirectX 12 game without anti-cheat (OptiScaler injects a DLL; it can get an account banned). +- Two files from NVIDIA, which this ZIP does not include: + nvngx_dlss.dll version 310.9.1 (tested) or 310.7.0. Many games ship one in their folder; + right-click > Properties > Details shows the version. + _nvngx.dll version 32.0.16.1714 (tested) or 32.0.15.9636. It is part of an NVIDIA display + driver package: extract the installer with 7-Zip and search for _nvngx.dll. + +Install +------- +1. Extract this ZIP into the folder that holds the game's main .exe. For Unreal Engine games that + is \\Binaries\Win64, next to -Win64-Shipping.exe. + If the folder already has a dxgi.dll or OptiScaler.ini (another OptiScaler install), or a + d3d12.dll from a Linux install, move those out first. +2. Copy nvngx_dlss.dll into the d4r folder and _nvngx.dll into d4r\ngx. +3. Optional: check the install. In the game folder, open PowerShell and run + powershell -ExecutionPolicy Bypass -File d4r\d4r-check.ps1 +4. Start the game from Steam, Epic or a shortcut as usual. In its graphics settings choose DLSS + (or FSR 3.1/XeSS if DLSS is hidden; OptiScaler then feeds DLSS). Keep frame generation off. + Press Insert in game for OptiScaler's menu. + +The first time DLSS starts, the game can freeze for several minutes while DLSS's GPU kernels are +compiled. They are cached in %LOCALAPPDATA%\d4r\cache, so later starts are quick. + +Settings +-------- +Edit d4r\d4r.ini (it is commented) and restart the game. Each game has its own copy. +- Model: K (DLSS 4, default) or M (DLSS 4.5, heavier). Select the quality mode in the game. +- AsyncInterop, ValidateOutput, Log: for testing and bug reports; the defaults are the tested setting. +OptiScaler's own settings (overlay, render ratios) are in OptiScaler.ini. + +If something goes wrong +----------------------- +- Run d4r\d4r-check.ps1: it prints ok / MISSING / note lines with the fix for each problem. +- d4r\d4r_nvngx.log is rewritten at every launch. A line "d4r: DLSS disabled: ..." says why. +- DLSS missing in the menu and no d4r_nvngx.log: the files are not next to the game's main .exe. +- For a bug report run d4r\d4r-check.ps1 -Report and send the ZIP it puts on your Desktop, + with your GPU, the game, the model and what the image looked like. Logs can contain folder names. +- Not supported: frame generation, ray reconstruction, DirectX 11, Vulkan, models E and L. + +Uninstall +--------- +Delete dxgi.dll, OptiScaler.ini, OptiScaler.log (if present), WINDOWS_README.txt and the d4r folder +from the game folder. The cache in %LOCALAPPDATA%\d4r can be deleted too. Nothing else was changed. + +Licenses +-------- +d4r is Apache License 2.0. This ZIP also contains ZLUDA (Apache 2.0 or MIT), OptiScaler (GPL 3.0, +patched; the patches are in the source repository), and AMD's HIP runtime. d4r\licenses has the +texts. d4r is not affiliated with NVIDIA, AMD or the OptiScaler project. NVIDIA's files are yours +to supply, under NVIDIA's terms. diff --git a/packaging/windows/d4r.ini b/packaging/windows/d4r.ini index 25a38ea..b7a4181 100644 --- a/packaging/windows/d4r.ini +++ b/packaging/windows/d4r.ini @@ -16,6 +16,9 @@ ; Select the quality mode (Quality, Performance, ...) in the game or the OptiScaler menu (Insert). ; Models E and L are not available on Windows. Model = K +; NVIDIA's library stamps "DLSS SDK - DO NOT DISTRIBUTE" over the image; d4r turns that off. +; true shows it. +ShowWatermark = false [Interop] ; Experimental: lets the game continue while DLSS runs on the GPU instead of waiting for it. diff --git a/scripts/windows/d4r-check.ps1 b/scripts/windows/d4r-check.ps1 new file mode 100644 index 0000000..4b804a0 --- /dev/null +++ b/scripts/windows/d4r-check.ps1 @@ -0,0 +1,271 @@ +# Checks a d4r install on Windows. It only reads files and prints what it finds. +# Run it from the game folder (the one holding the game's .exe and d4r\), or give that folder: +# powershell -ExecutionPolicy Bypass -File d4r\d4r-check.ps1 [GAME_FOLDER] +# Add -Report to also write one ZIP (logs, this output, GPU and driver) to send with a bug report. +[CmdletBinding()] +param( + [string]$GameFolder, + [switch]$Report, + [string]$InventoryFile, # tests: a gpu-inventory.json to use instead of running the GPU query + [string]$ReportDirectory # tests: where -Report writes its ZIP (default: your Desktop) +) +$ErrorActionPreference = 'Stop' +if (!$GameFolder) { + $GameFolder = if ((Split-Path $PSScriptRoot -Leaf) -eq 'd4r') { Split-Path $PSScriptRoot -Parent } else { (Get-Location).Path } +} +$game = [IO.Path]::GetFullPath($GameFolder).TrimEnd('\') +$d4r = Join-Path $game 'd4r' +$problems = 0 +$lines = [Collections.Generic.List[string]]::new() +function Say([string]$text) { $lines.Add($text); Write-Host $text } +function Ok([string]$text) { Say (' ok ' + $text) } +function Bad([string]$text) { Say (' MISSING ' + $text); $script:problems++ } +function Note([string]$text) { Say (' note ' + $text) } +function Hash([string]$path) { (Get-FileHash -LiteralPath $path -Algorithm SHA256).Hash } +function Version([string]$path) { + try { $v = (Get-Item -LiteralPath $path).VersionInfo; if ($v.FileVersion) { return ($v.FileVersion -replace ',\s*', '.') } } catch {} + return 'unknown' +} +function Read-TextFile([string]$path) { [IO.File]::ReadAllText($path) } + +Say "d4r install in $game" +if (!(Test-Path -LiteralPath $d4r -PathType Container)) { + Say 'no d4r folder here; run this from the folder with the game .exe (extract the d4r ZIP there first)' + exit 1 +} +if (@(Get-ChildItem -LiteralPath $game -Filter '*.exe' -File -ErrorAction SilentlyContinue).Count) { Ok 'game executable next to d4r\' } +else { Note 'no .exe next to d4r\ (the d4r files must sit in the folder of the game''s main .exe; for Unreal Engine games that is \\Binaries\Win64)' } + +# --- the install's own file list --------------------------------------------------------------- +$manifest = $null +$manifestPath = Join-Path $d4r 'package.json' +if (Test-Path -LiteralPath $manifestPath) { + try { $manifest = Get-Content -LiteralPath $manifestPath -Raw | ConvertFrom-Json } catch { $manifest = $null } +} +if (!$manifest) { Bad 'd4r\package.json is missing or damaged (extract the d4r ZIP again)' } + +# --- 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') +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)" } +} +if ($manifest) { + $edited = @('OptiScaler.ini', 'd4r/d4r.ini') # players are meant to edit these + $damaged = 0 + foreach ($entry in $manifest.files) { + $path = Join-Path $game ($entry.path -replace '/', '\') + if (!(Test-Path -LiteralPath $path -PathType Leaf)) { + if ($required -notcontains ($entry.path -replace '/', '\')) { Bad "$($entry.path) (extract the d4r ZIP again)" } + continue + } + if ((Hash $path) -ne $entry.sha256) { + if ($entry.path -eq 'dxgi.dll') { Note 'dxgi.dll is not the one that came with d4r (another OptiScaler or ReShade?). Move it away and extract the d4r ZIP again' } + elseif ($edited -contains $entry.path) { Note "$($entry.path) has been edited (fine if you did it)" } + else { Bad "$($entry.path) differs from the release (damaged or replaced; extract the d4r ZIP again)"; $damaged++ } + } + } + if (!$damaged) { Ok 'every other d4r file matches the release' } +} + +# --- OptiScaler.ini --------------------------------------------------------------------------- +$optiIni = Join-Path $game 'OptiScaler.ini' +if (Test-Path -LiteralPath $optiIni) { + $text = Read-TextFile $optiIni + if ($text -notmatch '(?im)^\s*AllowExternalBackend\s*=\s*true') { Bad 'OptiScaler.ini: [DLSS] AllowExternalBackend must be true (this is not the d4r OptiScaler.ini)' } + if ($text -notmatch '(?im)^\s*Dx12Upscaler\s*=\s*dlss') { Note 'OptiScaler.ini: [Upscalers] Dx12Upscaler is not dlss; the game will not use d4r' } + $m = [regex]::Match($text, '(?im)^\s*NvngxPath\s*=\s*(\S+)') + if (!$m.Success) { Bad 'OptiScaler.ini: [Libraries] NvngxPath is missing (it must name the d4r folder)' } + else { + $folder = $m.Groups[1].Value + $resolved = if ([IO.Path]::IsPathRooted($folder)) { $folder } else { Join-Path $game $folder } + if (Test-Path -LiteralPath (Join-Path $resolved '_nvngx.dll')) { Ok "OptiScaler.ini NvngxPath = $folder" } + else { Bad "OptiScaler.ini NvngxPath = $folder does not hold _nvngx.dll (use NvngxPath=d4r, or the full path of the d4r folder)" } + } +} + +# --- NVIDIA DLLs (never shipped; the exact versions come from dll-pins.txt) ----------------------- +$pins = @() +$pinsPath = Join-Path $d4r 'dll-pins.txt' +if (Test-Path -LiteralPath $pinsPath) { + foreach ($line in Get-Content -LiteralPath $pinsPath) { + $line = $line.Trim() + if (!$line -or $line.StartsWith('#')) { continue } + $f = $line -split '\s+' + if ($f.Count -ge 4 -and $f[1].Length -eq 64) { $pins += [pscustomobject]@{ kind = $f[0]; sha = $f[1].ToUpperInvariant(); version = $f[2]; status = $f[3] } } + } +} +function Check-Nvidia([string]$kind, [string]$relative, [string]$label, [string]$source) { + $path = Join-Path $game $relative + $wanted = @($pins | Where-Object { $_.kind -eq $kind } | ForEach-Object { $_.version }) -join ' or ' + if (!$wanted) { Note "$label cannot be checked (d4r\dll-pins.txt is missing or damaged)"; return } + if (!(Test-Path -LiteralPath $path -PathType Leaf)) { Bad "${relative}: copy NVIDIA's $label here (version $wanted; $source)"; return } + $hash = Hash $path + $pin = @($pins | Where-Object { $_.kind -eq $kind -and $_.sha -eq $hash }) | Select-Object -First 1 + if (!$pin) { Bad "$relative is version $(Version $path), which d4r does not accept. d4r on Windows needs exactly $wanted ($source)"; return } + if ($pin.status -eq 'validated') { Ok "$relative ($label $($pin.version), tested on Windows)" } + else { Ok "$relative ($label $($pin.version))"; Note "$label $($pin.version) is accepted but has had less Windows testing" } +} +Check-Nvidia 'dlss' 'd4r\nvngx_dlss.dll' 'nvngx_dlss.dll' 'many games ship one in their folder; see WINDOWS_README.txt' +Check-Nvidia 'ngx' 'd4r\ngx\_nvngx.dll' '_nvngx.dll' 'it is part of NVIDIA''s display driver package; see WINDOWS_README.txt' + +# --- GPU and the build for it -------------------------------------------------------------------- +$registryPath = Join-Path $d4r 'gpu-targets.json' +$registry = @() +if (Test-Path -LiteralPath $registryPath) { $registry = @((Get-Content -LiteralPath $registryPath -Raw | ConvertFrom-Json).targets) } +$inventory = $null +if ($InventoryFile) { $inventory = Get-Content -LiteralPath $InventoryFile -Raw | ConvertFrom-Json } +elseif (Test-Path -LiteralPath (Join-Path $d4r 'd4r_gpu_inventory.exe')) { + $work = Join-Path ([IO.Path]::GetTempPath()) ('d4r-check-' + [Guid]::NewGuid().ToString('N')) + New-Item -ItemType Directory -Path $work | Out-Null + try { + $info = New-Object Diagnostics.ProcessStartInfo + $info.FileName = Join-Path $d4r 'd4r_gpu_inventory.exe' + $info.Arguments = '--hip-root "' + (Join-Path $d4r 'hip') + '" --output-dir "' + $work + '"' + $info.UseShellExecute = $false; $info.CreateNoWindow = $true + $info.RedirectStandardOutput = $true; $info.RedirectStandardError = $true + $info.EnvironmentVariables['D4R_DIAG_DIR'] = $work + $process = New-Object Diagnostics.Process; $process.StartInfo = $info + [void]$process.Start() + $null = $process.StandardOutput.ReadToEndAsync(); $null = $process.StandardError.ReadToEndAsync() + if (!$process.WaitForExit(30000)) { $process.Kill(); $process.WaitForExit() } + if (Test-Path -LiteralPath (Join-Path $work 'gpu-inventory.json')) { $inventory = Get-Content -LiteralPath (Join-Path $work 'gpu-inventory.json') -Raw | ConvertFrom-Json } + } catch {} finally { + $process.Dispose() + Get-ChildItem -LiteralPath $work -File -ErrorAction SilentlyContinue | ForEach-Object { Remove-Item -LiteralPath $_.FullName } + Remove-Item -LiteralPath $work -ErrorAction SilentlyContinue + } +} +$arch = $null +if (!$inventory) { Note 'could not ask the AMD runtime about your GPU (d4r_gpu_inventory.exe is missing, or the AMD graphics driver is not installed or is too old)' } +else { + $supported = @($registry | ForEach-Object { $_.architecture }) + $devices = @($inventory.devices | Where-Object { $_.wavefrontSize -eq 32 -and ($supported -contains $_.architecture -or !$supported.Count) }) + if (!$devices.Count) { + $seen = @($inventory.devices | ForEach-Object { "$($_.name) ($($_.architecture))" }) -join ', ' + Bad ("no supported RDNA3/RDNA4 GPU found by the AMD runtime. It sees: " + $(if ($seen) { $seen } else { 'nothing' }) + '. Update the AMD Adrenalin driver; RDNA2 and older are not supported') + } else { + foreach ($device in $devices) { + $a = $device.architecture + $missing = @() + foreach ($rel in @("d4r\_nvngx_$a.dll", "d4r\pixel_convert_$a.hsaco", "d4r\nvapi\$a\nvapi64.dll", "d4r\native\$a\d4r-kernels.txt")) { + if (!(Test-Path -LiteralPath (Join-Path $game $rel))) { $missing += $rel } + } + $native = @(Get-ChildItem -LiteralPath (Join-Path $d4r "native\$a") -Filter '*.hsaco' -ErrorAction SilentlyContinue).Count + 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" + if (!$arch) { $arch = $a } + } + if ($a -ne 'gfx1201') { Note "only the RX 9070 XT (gfx1201) has been tested on real hardware; $a is compile-tested" } + } + } + if (@($inventory.devices | Where-Object { $_.wavefrontSize -eq 32 }).Count -gt 1) { Note 'several GPUs found; d4r uses the one the game renders on' } +} +if ($arch -and $registry.Count) { + $info = @($registry | Where-Object { $_.architecture -eq $arch }) | Select-Object -First 1 + $bad = 0 + $objects = @(Get-Item -LiteralPath (Join-Path $d4r "pixel_convert_$arch.hsaco") -ErrorAction SilentlyContinue) + + @(Get-ChildItem -LiteralPath (Join-Path $d4r "native\$arch") -Filter '*.hsaco' -ErrorAction SilentlyContinue) + foreach ($object in $objects) { + $stream = [IO.File]::OpenRead($object.FullName) + try { $header = New-Object byte[] 64; [void]$stream.Read($header, 0, 64) } finally { $stream.Dispose() } + if ($header[0] -ne 127 -or $header[1] -ne 69 -or $header[2] -ne 76 -or $header[3] -ne 70 -or $header[48] -ne $info.elfMachine) { + Bad "$($object.Name) is not built for $arch (do not rename or mix files from other GPUs; extract the d4r ZIP again)"; $bad++ + } + } + if ($objects.Count -and !$bad) { Ok "all $($objects.Count) GPU code objects are built for $arch" } +} + +# --- Linux / Wine leftovers ------------------------------------------------------------------------ +$proxyFound = $false +foreach ($name in @('d3d12.dll', 'd3d12core.dll')) { + $proxy = Join-Path $game $name + if (Test-Path -LiteralPath $proxy -PathType Leaf) { + $item = Get-Item -LiteralPath $proxy + if ($item.Length -le 64MB -and [Text.Encoding]::ASCII.GetString([IO.File]::ReadAllBytes($proxy)) -match '(?i)vkd3d|wined3d') { + Bad "$name in the game folder is vkd3d-proton/Wine (left over from a Linux install?). Move it out; d4r on Windows uses native D3D12" + $proxyFound = $true + } + } +} +if (!$proxyFound) { Ok 'no vkd3d/Wine d3d12.dll in the game folder' } + +# --- d4r.ini --------------------------------------------------------------------------------------- +$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; 'debug.validateoutput' = 1; 'debug.log' = 1; 'paths.cachedir' = 1 } + $boolean = @('dlss.showwatermark', 'interop.asyncinterop', 'debug.validateoutput') + foreach ($raw in (Read-TextFile $iniPath).TrimStart([char]0xFEFF) -split "`n") { + $line = $raw.Trim() + if (!$line -or $line.StartsWith(';') -or $line.StartsWith('#')) { continue } + if ($line.StartsWith('[')) { $section = $line.Trim('[', ']').Trim(); continue } + $eq = $line.IndexOf('='); if ($eq -lt 0) { continue } + $key = $line.Substring(0, $eq).Trim() + $value = ($line.Substring($eq + 1) -replace '\s[;#].*$', '').Trim() + $name = ("$section.$key").ToLowerInvariant() + $v = $value.ToLowerInvariant() + if (!$known.ContainsKey($name)) { Note "d4r.ini: [$section] $key is not a Windows setting; ignored"; $iniProblems++ } + elseif ($name -eq 'dlss.model') { + if (@('', 'auto', 'k', 'dlss4', '11') -contains $v) { $model = 'K' } + elseif (@('m', 'dlss4.5', '13') -contains $v) { $model = 'M' } + else { Note "d4r.ini: [DLSS] Model '$value' is not available on Windows (use K or M); K will be used"; $iniProblems++ } + } + elseif ($boolean -contains $name) { + if (@('', 'auto', '1', 'true', 'yes', 'on', '0', 'false', 'no', 'off') -notcontains $v) { Note "d4r.ini: [$section] $key must be true or false, not '$value'; the default will be used"; $iniProblems++ } + } + elseif ($name -eq 'debug.log' -and @('', 'auto', 'normal', 'verbose') -notcontains $v) { Note "d4r.ini: [Debug] Log must be normal or verbose, not '$value'; normal will be used"; $iniProblems++ } + } + if (!$iniProblems) { Ok "d4r.ini is valid (model $model)" } +} + +# --- what the last game start said ----------------------------------------------------------------- +$log = Join-Path $d4r 'd4r_nvngx.log' +if (Test-Path -LiteralPath $log) { + $messages = @(Get-Content -LiteralPath $log | Where-Object { $_ -match '^d4r: ' }) + $failed = @($messages | Where-Object { $_ -match 'DLSS disabled' }) + if ($failed.Count) { foreach ($m in $failed | Select-Object -First 3) { Bad ('last game start: ' + $m.Substring(5)) } } + elseif ($messages.Count) { Ok ('last game start: ' + $messages[0].Substring(5)) } + else { Note 'd4r\d4r_nvngx.log has no d4r summary line yet (the game may not have chosen DLSS)' } + foreach ($m in $messages | Where-Object { $_ -notmatch 'DLSS disabled|player mode' } | Select-Object -First 5) { Note ('last game start: ' + $m.Substring(5)) } +} else { Note 'no d4r\d4r_nvngx.log yet: start the game once and choose DLSS (or FSR/XeSS) in its graphics settings' } +if (Get-Process -ErrorAction SilentlyContinue | Where-Object { $_.Path -and $_.Path.StartsWith($game + '\', [StringComparison]::OrdinalIgnoreCase) -and $_.ProcessName -notmatch '^(powershell|pwsh)$' }) { + Note 'the game is running; its log is rewritten at every start' +} + +Say '' +if ($problems -eq 0) { Say 'Everything d4r needs is in place.' } else { Say "$problems thing(s) to fix above." } + +# --- bug-report ZIP ------------------------------------------------------------------------------ +if ($Report) { + $stamp = Get-Date -Format 'yyyyMMdd-HHmmss' + $destination = if ($ReportDirectory) { $ReportDirectory } else { [Environment]::GetFolderPath('Desktop') } + $staging = Join-Path ([IO.Path]::GetTempPath()) ("d4r-report-$stamp") + New-Item -ItemType Directory -Force -Path $staging | Out-Null + try { + $lines | Set-Content -LiteralPath (Join-Path $staging 'check.txt') -Encoding UTF8 + foreach ($file in @('d4r\d4r_nvngx.log', 'd4r\d4r.ini', 'OptiScaler.ini', 'OptiScaler.log')) { + $source = Join-Path $game $file + if (Test-Path -LiteralPath $source) { Copy-Item -LiteralPath $source -Destination (Join-Path $staging (Split-Path $file -Leaf)) } + } + Get-ChildItem -LiteralPath $game -Filter 'OptiScaler*.log' -File | ForEach-Object { Copy-Item -LiteralPath $_.FullName -Destination $staging -Force } + try { Get-CimInstance Win32_VideoController | Select-Object Name, DriverVersion, DriverDate | ConvertTo-Json | Set-Content -LiteralPath (Join-Path $staging 'driver.json') -Encoding UTF8 } catch {} + if ($inventory) { $inventory | ConvertTo-Json -Depth 6 | Set-Content -LiteralPath (Join-Path $staging 'gpu-inventory.json') -Encoding UTF8 } + Get-ChildItem -LiteralPath $game -Recurse -File -ErrorAction SilentlyContinue | + Where-Object { $_.FullName.StartsWith($d4r + '\') -or $_.Name -in @('dxgi.dll', 'OptiScaler.ini') } | + ForEach-Object { '{0} {1,12} {2}' -f (Hash $_.FullName), $_.Length, $_.FullName.Substring($game.Length + 1) } | + Set-Content -LiteralPath (Join-Path $staging 'files.sha256.txt') -Encoding UTF8 + $zip = Join-Path $destination "d4r-report-$stamp.zip" + Compress-Archive -Path (Join-Path $staging '*') -DestinationPath $zip -Force + Write-Host '' + Write-Host "Bug report written: $zip" -ForegroundColor Cyan + Write-Host 'Also tell us: GPU, game, K or M, and what the image looked like. The logs can contain local folder names; look before posting publicly.' + } finally { + Get-ChildItem -LiteralPath $staging -File -ErrorAction SilentlyContinue | ForEach-Object { Remove-Item -LiteralPath $_.FullName } + Remove-Item -LiteralPath $staging -ErrorAction SilentlyContinue + } +} +if ($problems) { exit 1 } diff --git a/scripts/windows/package-windows-release.ps1 b/scripts/windows/package-windows-release.ps1 index 9a2145d..7786877 100644 --- a/scripts/windows/package-windows-release.ps1 +++ b/scripts/windows/package-windows-release.ps1 @@ -7,6 +7,7 @@ param( [string]$GamePackagePrefix, [Parameter(Mandatory=$true)][string]$LoaderDll, [Parameter(Mandatory=$true)][string]$PackageRoot, + [string[]]$Architectures, # default: every target in gpu-targets.json; a subset makes a smaller release [string]$ArchivePath ) $ErrorActionPreference='Stop' @@ -28,8 +29,10 @@ function Stage([string]$Source,[string]$Relative) { $item=Get-Item -LiteralPath $destination $files.Add(@{path=$Relative.Replace('\','/'); sha256=(Get-FileHash -LiteralPath $destination -Algorithm SHA256).Hash; version=$item.VersionInfo.FileVersion}) } -$targets=[ordered]@{}; $baseline=$null; $commonStaged=$false -foreach ($gpu in Get-D4RGpuTargets) { +$targets=[ordered]@{}; $baseline=$null; $commonStaged=$false; $inventoryStaged=$false +$selected=@(Get-D4RGpuTargets | Where-Object { !$Architectures -or $Architectures -contains $_.architecture }) +if (!$selected.Count -or ($Architectures -and $selected.Count -ne @($Architectures | Select-Object -Unique).Count)) { throw "Unknown GPU target in -Architectures: $($Architectures -join ', ')" } +foreach ($gpu in $selected) { $arch=$gpu.architecture; $source="$GamePackagePrefix-$arch" $metadata=Get-Content -LiteralPath (Join-Path $source 'package.json') -Raw | ConvertFrom-Json if ($metadata.architecture -ne $arch -or $metadata.d4rWorkingTreeDirty) { throw "Uncommitted or wrong-target input: $source" } @@ -41,7 +44,9 @@ foreach ($gpu in Get-D4RGpuTargets) { if ((Get-FileHash -LiteralPath $path -Algorithm SHA256).Hash -ne $file.sha256) { throw "Input file changed: $source/$relative" } if ($relative.EndsWith('.hsaco')) { Assert-D4RCodeObjectTarget $path $arch; $objects++ } # Per-target files get target-specific names so one folder serves every GPU. - if ($relative -eq 'd4r/_nvngx.dll') { Stage $path "d4r/_nvngx_$arch.dll" } + # One GPU-query tool serves every target: it reports each device's real architecture. + if ($relative -eq 'd4r/d4r_gpu_inventory.exe') { if (!$inventoryStaged) { Stage $path 'd4r/d4r_gpu_inventory.exe'; $inventoryStaged=$true } } + elseif ($relative -eq 'd4r/_nvngx.dll') { Stage $path "d4r/_nvngx_$arch.dll" } elseif ($relative -eq 'd4r/nvapi/nvapi64.dll') { Stage $path "d4r/nvapi/$arch/nvapi64.dll" } elseif ($relative -like 'd4r/pixel_convert_*.hsaco') { Stage $path $relative } elseif ($relative.StartsWith('d4r/native/')) { Stage $path ("d4r/native/$arch/" + $relative.Substring('d4r/native/'.Length)) } @@ -51,7 +56,7 @@ foreach ($gpu in Get-D4RGpuTargets) { if (!$commonStaged) { Stage $path $public } elseif ((Get-FileHash -LiteralPath (Join-Path $PackageRoot $public) -Algorithm SHA256).Hash -ne $file.sha256) { throw "Shared runtime differs in $arch : $relative" } } - # Installer-only files (launcher, inventory, PowerShell, docs) are not part of a drag-in install. + # Installer-only files (launcher, PowerShell installer, docs) are not part of a drag-in install. } if ($objects -ne 17) { throw "Expected 17 code objects for $arch" } $commonStaged=$true @@ -63,6 +68,8 @@ Stage $loader 'd4r/_nvngx.dll' Stage (Join-Path $repo 'packaging/windows/OptiScaler.ini') 'OptiScaler.ini' Stage (Join-Path $repo 'packaging/windows/d4r.ini') 'd4r/d4r.ini' Stage (Join-Path $repo 'tools/windows/dll-pins.txt') 'd4r/dll-pins.txt' +Stage (Join-Path $repo 'tools/windows/gpu-targets.json') 'd4r/gpu-targets.json' +if (!$inventoryStaged) { throw 'No d4r/d4r_gpu_inventory.exe in the inputs; d4r-check.ps1 needs it to query the GPU.' } foreach ($optional in @(@{source='packaging/windows/WINDOWS_README.txt'; path='WINDOWS_README.txt'}, @{source='scripts/windows/d4r-check.ps1'; path='d4r/d4r-check.ps1'})) { $path=Join-Path $repo $optional.source @@ -95,4 +102,4 @@ if ($ArchivePath) { } } finally { if ($archive) { $archive.Dispose() }; $stream.Dispose() } } -Write-Host "Windows drag-in release: $PackageRoot ($($targets.Count) GPU targets, no NVIDIA DLLs)" +Write-Host "Windows drag-in release: $PackageRoot ($($targets.Count) GPU target(s): $($targets.Keys -join ', '); no NVIDIA DLLs)" diff --git a/scripts/windows/test-d4r-check.ps1 b/scripts/windows/test-d4r-check.ps1 new file mode 100644 index 0000000..34ac371 --- /dev/null +++ b/scripts/windows/test-d4r-check.ps1 @@ -0,0 +1,122 @@ +# d4r-check.ps1 against fixture installs. No GPU, no NVIDIA binaries: the packaging test's +# synthetic release is the install, a fake inventory file stands in for the GPU query, and +# fixture text files stand in for the NVIDIA DLLs (pinned in a fixture dll-pins.txt). +[CmdletBinding()] +param([string]$OutputDirectory) +$ErrorActionPreference='Stop' +$repo=[IO.Path]::GetFullPath((Join-Path $PSScriptRoot '../..')) +if (!$OutputDirectory) { $OutputDirectory=Join-Path $repo ('test-results/d4r-check/'+(Get-Date -Format 'yyyyMMdd-HHmmss-fff')) } +$OutputDirectory=[IO.Path]::GetFullPath($OutputDirectory) +New-Item -ItemType Directory -Force $OutputDirectory | Out-Null +& (Join-Path $PSScriptRoot 'test-windows-release-package.ps1') -OutputDirectory (Join-Path $OutputDirectory 'package') | Out-Null +$release=Join-Path $OutputDirectory 'package/release' +$check=Join-Path $repo 'scripts/windows/d4r-check.ps1' +$powershell="$env:SystemRoot/System32/WindowsPowerShell/v1.0/powershell.exe" +$results=@() +function Sha([string]$path) { (Get-FileHash -LiteralPath $path -Algorithm SHA256).Hash } +function Set-ManifestHash([string]$game,[string]$relative) { + $path=Join-Path $game 'd4r/package.json' + $manifest=Get-Content -LiteralPath $path -Raw | ConvertFrom-Json + foreach ($entry in $manifest.files) { if ($entry.path -eq $relative) { $entry.sha256=Sha (Join-Path $game ($relative -replace '/','\')) } } + $manifest | ConvertTo-Json -Depth 8 | Set-Content -LiteralPath $path -Encoding UTF8 +} +function New-Install([string]$name) { + $game=Join-Path $OutputDirectory "cases/$name" + New-Item -ItemType Directory -Force (Split-Path $game) | Out-Null + Copy-Item -LiteralPath $release -Destination $game -Recurse + Set-Content -LiteralPath (Join-Path $game 'Game.exe') 'fixture' -Encoding ASCII + Set-Content -LiteralPath (Join-Path $game 'd4r/nvngx_dlss.dll') 'fixture dlss' -Encoding ASCII + Set-Content -LiteralPath (Join-Path $game 'd4r/ngx/_nvngx.dll') 'fixture core' -Encoding ASCII + $unrelated=('B'*64) + @('# fixture pins', + "dlss $(Sha (Join-Path $game 'd4r/nvngx_dlss.dll')) 310.9.1 validated", + "dlss $unrelated 310.7.0 unvalidated", + "ngx $(Sha (Join-Path $game 'd4r/ngx/_nvngx.dll')) 32.0.16.1714 validated") | + Set-Content -LiteralPath (Join-Path $game 'd4r/dll-pins.txt') -Encoding ASCII + Set-ManifestHash $game 'd4r/dll-pins.txt' + return $game +} +function Write-Inventory([string]$name,[string]$arch,[string]$gpu='AMD Radeon RX 7700 XT') { + $path=Join-Path $OutputDirectory "inventory-$name.json" + @{devices=@(@{name=$gpu; architecture=$arch; wavefrontSize=32})} | ConvertTo-Json -Depth 4 | Set-Content -LiteralPath $path -Encoding UTF8 + return $path +} +function Run-Check([string]$game,[string]$inventory,[string[]]$More=@()) { + $ErrorActionPreference='Continue' # native stderr must not become a terminating error + $text=& $powershell -NoProfile -ExecutionPolicy Bypass -File (Join-Path $game 'd4r/d4r-check.ps1') -GameFolder $game -InventoryFile $inventory @More 2>&1 | Out-String + return @{exit=$LASTEXITCODE; text=$text} +} +function Expect([string]$case,$run,[int]$exit,[string[]]$patterns) { + if ($run.exit -ne $exit) { throw "$case : exit $($run.exit), expected $exit`n$($run.text)" } + foreach ($pattern in $patterns) { if ($run.text -notmatch $pattern) { throw "$case : output lacks /$pattern/`n$($run.text)" } } + $script:results+=@{case=$case; passed=$true; gpuKernelsExecuted=$false} +} + +# 1. Healthy install. +$game=New-Install 'healthy'; $inv=Write-Inventory 'a' 'gfx1101' +Expect 'healthy-install' (Run-Check $game $inv) 0 @('ok\s+d4r\\nvngx_dlss\.dll \(nvngx_dlss\.dll 310\.9\.1, tested on Windows\)','ok\s+d4r\\ngx\\_nvngx\.dll', + 'ok\s+GPU AMD Radeon RX 7700 XT \(gfx1101\): build present, 16 native kernels','ok\s+all 17 GPU code objects are built for gfx1101', + 'ok\s+no vkd3d/Wine d3d12\.dll','ok\s+d4r\.ini is valid \(model K\)','compile-tested','Everything d4r needs is in place') + +# 2. NVIDIA DLL problems, each with the fix. +$game=New-Install 'dll-missing'; Remove-Item -LiteralPath (Join-Path $game 'd4r/nvngx_dlss.dll') +Expect 'dll-missing' (Run-Check $game $inv) 1 @("MISSING\s+d4r\\nvngx_dlss\.dll: copy NVIDIA's nvngx_dlss\.dll here \(version 310\.9\.1 or 310\.7\.0","1 thing\(s\) to fix") +$game=New-Install 'dll-wrong'; Set-Content -LiteralPath (Join-Path $game 'd4r/nvngx_dlss.dll') 'some other version' -Encoding ASCII +Expect 'dll-wrong-version' (Run-Check $game $inv) 1 @('MISSING\s+d4r\\nvngx_dlss\.dll is version \S+, which d4r does not accept\. d4r on Windows needs exactly 310\.9\.1 or 310\.7\.0') +$game=New-Install 'dll-less-tested'; Set-Content -LiteralPath (Join-Path $game 'd4r/nvngx_dlss.dll') 'fixture dlss 310.7' -Encoding ASCII +@('dlss '+(Sha (Join-Path $game 'd4r/nvngx_dlss.dll'))+' 310.7.0 unvalidated','ngx '+(Sha (Join-Path $game 'd4r/ngx/_nvngx.dll'))+' 32.0.16.1714 validated') | Set-Content -LiteralPath (Join-Path $game 'd4r/dll-pins.txt') -Encoding ASCII +Set-ManifestHash $game 'd4r/dll-pins.txt' +Expect 'dll-less-tested-accepted' (Run-Check $game $inv) 0 @('ok\s+d4r\\nvngx_dlss\.dll \(nvngx_dlss\.dll 310\.7\.0\)','note\s+nvngx_dlss\.dll 310\.7\.0 is accepted but has had less Windows testing') + +# 3. Leftovers, edited files, a replaced dxgi.dll, and a broken OptiScaler.ini. +$game=New-Install 'vkd3d'; Set-Content -LiteralPath (Join-Path $game 'd3d12.dll') 'vkd3d-proton fixture' -Encoding ASCII +Expect 'vkd3d-proxy' (Run-Check $game $inv) 1 @('MISSING\s+d3d12\.dll in the game folder is vkd3d-proton/Wine') +$game=New-Install 'dxgi-replaced'; Set-Content -LiteralPath (Join-Path $game 'dxgi.dll') 'another dxgi' -Encoding ASCII +Expect 'foreign-dxgi' (Run-Check $game $inv) 0 @('note\s+dxgi\.dll is not the one that came with d4r') +$game=New-Install 'opti-path'; (Get-Content -LiteralPath (Join-Path $game 'OptiScaler.ini')) -replace '^NvngxPath=.*$','NvngxPath=nowhere' | Set-Content -LiteralPath (Join-Path $game 'OptiScaler.ini') -Encoding ASCII +Expect 'optiscaler-ini-path' (Run-Check $game $inv) 1 @('MISSING\s+OptiScaler\.ini NvngxPath = nowhere does not hold _nvngx\.dll') + +# 4. d4r.ini mistakes are notes with what will happen instead. +$game=New-Install 'ini'; Set-Content -LiteralPath (Join-Path $game 'd4r/d4r.ini') "[DLSS]`nModel = E`nShowWatermark = sometimes`n[Latency]`nFrameAge = 1`n[Debug]`nLog = loud`n" -Encoding ASCII +Expect 'ini-mistakes' (Run-Check $game $inv) 0 @("note\s+d4r\.ini: \[DLSS\] Model 'E' is not available on Windows",'must be true or false, not ''sometimes''','\[Latency\] FrameAge is not a Windows setting','Log must be normal or verbose') + +# 5. GPU cases. +$game=New-Install 'gpu-no-build'; Remove-Item -LiteralPath (Join-Path $game 'd4r/_nvngx_gfx1103.dll') +Expect 'gpu-without-build' (Run-Check $game (Write-Inventory 'b' 'gfx1103' 'AMD Radeon 780M')) 1 @('MISSING\s+GPU AMD Radeon 780M \(gfx1103\): this release has no complete build for it \(missing: d4r\\_nvngx_gfx1103\.dll\)') +$game=New-Install 'gpu-rdna2' +Expect 'gpu-unsupported' (Run-Check $game (Write-Inventory 'c' 'gfx1030' 'AMD Radeon RX 6800')) 1 @('MISSING\s+no supported RDNA3/RDNA4 GPU found by the AMD runtime\. It sees: AMD Radeon RX 6800 \(gfx1030\)') +$game=New-Install 'gpu-wrong-object' +$other=@(Get-ChildItem -LiteralPath (Join-Path $game 'd4r/native/gfx1100') -Filter '*.hsaco')[0].FullName +Copy-Item -LiteralPath $other -Destination (Join-Path $game 'd4r/native/gfx1101/kernel3.hsaco') -Force +Expect 'code-object-for-another-gpu' (Run-Check $game $inv) 1 @('MISSING\s+kernel3\.hsaco is not built for gfx1101','differs from the release') + +# 6. What the last game start said, and the bug-report ZIP. +$game=New-Install 'log-failure' +Set-Content -LiteralPath (Join-Path $game 'd4r/d4r_nvngx.log') "d4r: DLSS disabled: Wrong nvngx_dlss.dll: you have version 1.2.3.4" -Encoding ASCII +Expect 'log-reports-failure' (Run-Check $game $inv) 1 @('MISSING\s+last game start: DLSS disabled: Wrong nvngx_dlss\.dll') +$game=New-Install 'log-ok' +Set-Content -LiteralPath (Join-Path $game 'd4r/d4r_nvngx.log') "d4r: player mode, model=K validate=0 async=0 log=normal, architecture=gfx1101" -Encoding ASCII +Expect 'log-reports-ok' (Run-Check $game $inv) 0 @('ok\s+last game start: player mode, model=K') +$game=New-Install 'report'; $reportDir=Join-Path $OutputDirectory 'reports'; New-Item -ItemType Directory -Force $reportDir | Out-Null +Set-Content -LiteralPath (Join-Path $game 'd4r/d4r_nvngx.log') "d4r: DLSS disabled: test failure" -Encoding ASCII +$run=Run-Check $game $inv @('-Report','-ReportDirectory',$reportDir) +$zip=@(Get-ChildItem -LiteralPath $reportDir -Filter 'd4r-report-*.zip') +if ($zip.Count -ne 1) { throw "Expected one report ZIP.`n$($run.text)" } +Add-Type -AssemblyName System.IO.Compression.FileSystem +$archive=[IO.Compression.ZipFile]::OpenRead($zip[0].FullName) +try { $names=@($archive.Entries | ForEach-Object { $_.Name }) } finally { $archive.Dispose() } +foreach ($required in @('check.txt','d4r_nvngx.log','d4r.ini','OptiScaler.ini','files.sha256.txt','gpu-inventory.json')) { if ($names -notcontains $required) { throw "Report lacks $required (has: $($names -join ', '))" } } +if ($names -contains 'nvngx_dlss.dll' -or $names -contains '_nvngx.dll') { throw 'The report must never contain NVIDIA DLLs.' } +if ($run.text -notmatch 'Bug report written') { throw 'Report location was not printed.' } +$results+=@{case='bug-report-zip'; passed=$true; entries=$names} + +# 7. No d4r folder at all. +$empty=Join-Path $OutputDirectory 'cases/empty'; New-Item -ItemType Directory -Force $empty | Out-Null +$ErrorActionPreference='Continue' +$text=& $powershell -NoProfile -ExecutionPolicy Bypass -File $check -GameFolder $empty 2>&1 | Out-String +$exitEmpty=$LASTEXITCODE; $ErrorActionPreference='Stop' +if ($exitEmpty -ne 1 -or $text -notmatch 'no d4r folder here') { throw "A folder without d4r must be reported.`n$text" } +$results+=@{case='no-d4r-folder'; passed=$true} + +$results | ConvertTo-Json -Depth 4 | Set-Content -LiteralPath (Join-Path $OutputDirectory 'summary.json') -Encoding UTF8 +Write-Host "PASS D4R_CHECK cases=$($results.Count) Report: $OutputDirectory" diff --git a/scripts/windows/test-windows-release-package.ps1 b/scripts/windows/test-windows-release-package.ps1 index 1940789..70cc907 100644 --- a/scripts/windows/test-windows-release-package.ps1 +++ b/scripts/windows/test-windows-release-package.ps1 @@ -20,7 +20,7 @@ function New-Inputs([string]$Prefix,[scriptblock]$Mutate) { foreach ($target in $targets) { $arch=$target.architecture; $root="$Prefix-$arch" $text=@{'OptiScaler.dll'='optiscaler'; 'd4r/_nvngx.dll'="shim $arch"; 'd4r/nvapi/nvapi64.dll'="nvapi $arch"; - 'd4r/hip/bin/amdhip64_7.dll'='hip'; 'd4r/zluda/nvcuda.dll'='zluda'; 'licenses/d4r.txt'='license'; + 'd4r/hip/bin/amdhip64_7.dll'='hip'; 'd4r/zluda/nvcuda.dll'='zluda'; 'd4r/zluda/nvapi64.dll'='zluda nvapi'; 'licenses/d4r.txt'='license'; 'd4r/d4r_gpu_inventory.exe'='inventory'; 'd4r/d4r_debug_launcher.exe'='launcher'; 'windows-game.ps1'='installer'; 'd4r/native/d4r-kernels.txt'='manifest'} foreach ($entry in $text.GetEnumerator()) { @@ -53,7 +53,8 @@ New-Inputs $prefix $release=Join-Path $OutputDirectory 'release' Invoke-Package $prefix $release foreach ($path in @('dxgi.dll','OptiScaler.ini','d4r/_nvngx.dll','d4r/d4r.ini','d4r/dll-pins.txt','d4r/package.json', - 'd4r/hip/bin/amdhip64_7.dll','d4r/zluda/nvcuda.dll','d4r/licenses/d4r.txt','d4r/ngx/PUT _nvngx.dll HERE.txt')) { + 'd4r/hip/bin/amdhip64_7.dll','d4r/zluda/nvcuda.dll','d4r/licenses/d4r.txt','d4r/ngx/PUT _nvngx.dll HERE.txt', + 'd4r/gpu-targets.json','d4r/d4r_gpu_inventory.exe','d4r/d4r-check.ps1','WINDOWS_README.txt')) { if (!(Test-Path -LiteralPath (Join-Path $release $path))) { throw "Release lacks $path" } } if ((Get-Content -LiteralPath (Join-Path $release 'd4r/_nvngx.dll') -Raw).Trim() -ne 'loader fixture') { throw 'd4r/_nvngx.dll is not the loader.' } @@ -66,7 +67,8 @@ foreach ($target in $targets) { if ($native.Count -ne 16 -or !(Test-Path -LiteralPath (Join-Path $release "d4r/native/$arch/d4r-kernels.txt"))) { throw "Native folder for $arch is incomplete" } foreach ($object in $native) { Assert-D4RCodeObjectTarget $object.FullName $arch } } -foreach ($excluded in @('d4r/d4r_gpu_inventory.exe','d4r/d4r_debug_launcher.exe','windows-game.ps1','d4r/native/kernel1.hsaco','d4r/nvapi/nvapi64.dll')) { +if (@(Get-ChildItem -LiteralPath $release -Recurse -Filter 'd4r_gpu_inventory.exe').Count -ne 1) { throw 'The GPU query tool must ship exactly once.' } +foreach ($excluded in @('d4r/d4r_debug_launcher.exe','windows-game.ps1','d4r/native/kernel1.hsaco','d4r/nvapi/nvapi64.dll')) { if (Test-Path -LiteralPath (Join-Path $release $excluded)) { throw "Installer-only or untargeted file shipped: $excluded" } } $manifest=Get-Content -LiteralPath (Join-Path $release 'd4r/package.json') -Raw | ConvertFrom-Json @@ -77,6 +79,16 @@ $shipped=@(Get-ChildItem -LiteralPath $release -Recurse -File | ForEach-Object { if (Compare-Object $shipped @($manifest.files.path)) { throw 'Manifest and shipped files differ.' } $results+=@{case='drag-in-layout'; passed=$true; targets=$targets.Count; files=$shipped.Count} +# 1b. A subset release carries only the requested targets. +$subset=Join-Path $OutputDirectory 'release-subset' +& (Join-Path $PSScriptRoot 'package-windows-release.ps1') -GamePackagePrefix $prefix -LoaderDll $loader -PackageRoot $subset -Architectures gfx1101,gfx1201 | Out-Null +$kept=@(Get-ChildItem -LiteralPath (Join-Path $subset 'd4r') -Filter '_nvngx_gfx*.dll' | ForEach-Object { $_.Name }) +if (($kept | Sort-Object) -join ',' -ne '_nvngx_gfx1101.dll,_nvngx_gfx1201.dll') { throw "Subset shipped the wrong shims: $($kept -join ',')" } +$rejected=$false +try { & (Join-Path $PSScriptRoot 'package-windows-release.ps1') -GamePackagePrefix $prefix -LoaderDll $loader -PackageRoot (Join-Path $OutputDirectory 'release-unknown') -Architectures gfx9999 | Out-Null } catch { $rejected=$_.Exception.Message -match 'Unknown GPU target' } +if (!$rejected) { throw 'An unknown architecture was not rejected.' } +$results+=@{case='architecture-subset'; passed=$true; shipped=$kept} + # 2. A code object for another GPU in a target's input is rejected. $prefix=Join-Path $OutputDirectory 'inputs/mixed/game' New-Inputs $prefix { param($root,$arch) if ($arch -eq 'gfx1101') { Write-CodeObject (Join-Path $root 'd4r/native/kernel3.hsaco') 65 } } diff --git a/tools/windows/d4r_nvngx_windows.cpp b/tools/windows/d4r_nvngx_windows.cpp index 1ca5a43..e736188 100644 --- a/tools/windows/d4r_nvngx_windows.cpp +++ b/tools/windows/d4r_nvngx_windows.cpp @@ -148,6 +148,11 @@ API unsigned NVSDK_NGX_D3D12_EvaluateFeature(ID3D12GraphicsCommandList* list, co if (!snapshot->parameters) return failure; d4r::win::player::apply_preset(p); d4r::ngx::copy_create(p, snapshot->parameters); d4r::ngx::copy_frame(p, snapshot->parameters); + // NVIDIA's library stamps "DLSS SDK - DO NOT DISTRIBUTE" over its output unless the SDK's + // Disable.Watermark parameter is set (the Linux shim sets it too). The game's own value is + // not used. [DLSS] ShowWatermark = true (D4R_SHIM_WATERMARK=1) keeps the stamp. + static const bool watermark = [] { const char* v = std::getenv("D4R_SHIM_WATERMARK"); return v && v[0] == '1'; }(); + d4r_ngx_set_int(snapshot->parameters, "Disable.Watermark", watermark ? 0 : 1); const char* names[] = {"Color", "Depth", "MotionVectors", "Output", "ExposureTexture"}; const char* states[] = {"D4R.Color.State", "D4R.Depth.State", "D4R.Motion.State", "D4R.Output.State", "D4R.Exposure.State"}; for (unsigned i = 0; i < 5; ++i) { diff --git a/tools/windows/diagnostic.h b/tools/windows/diagnostic.h index 5475855..aa18517 100644 --- a/tools/windows/diagnostic.h +++ b/tools/windows/diagnostic.h @@ -202,8 +202,8 @@ struct Args { else if (key == "--preset") { size_t end = 0; preset = static_cast(std::stoul(value, &end)); - if (end != value.size() || (preset != 5 && preset != 11 && preset != 13)) - throw std::runtime_error("preset must be 5 (E), 11 (K) or 13 (M)"); + if (end != value.size() || (preset != 5 && preset != 11 && preset != 12 && preset != 13)) + throw std::runtime_error("preset must be 5 (E), 11 (K), 12 (L) or 13 (M)"); } else if (key == "--fixture-dir") fixture_dir = value; else if (key == "--benchmark-module") benchmark_module = value; diff --git a/tools/windows/dll-pins.txt b/tools/windows/dll-pins.txt index 5f70608..3b8dc18 100644 --- a/tools/windows/dll-pins.txt +++ b/tools/windows/dll-pins.txt @@ -2,8 +2,13 @@ # never shipped or committed; players copy their own. # dlss = nvngx_dlss.dll (d4r\nvngx_dlss.dll) # ngx = _nvngx.dll, NVIDIA's NGX core from a display driver (d4r\ngx\_nvngx.dll) -# status: validated = tested on Windows hardware; anything else is accepted -# but reported as unvalidated in d4r\d4r_nvngx.log. +# status: validated = tested on Windows hardware by the project; anything else is +# accepted but reported as having had less testing in d4r\d4r_nvngx.log and d4r-check.ps1. +# Native kernels only replace DLSS code whose PTX identity matches; anything else +# falls back to ZLUDA's translated kernels, so an accepted DLL is never run with a +# wrong native kernel. # kind sha256 version status dlss 3975567B8943C53ACCE397F2B72380092F84F162D00B0D2C7D08A1025C563983 310.9.1 validated +dlss 1CDFDC957CB7FC9805500CA6793F607EF2D4DBD7C967FEAC70ACA9CAF382D0C0 310.7.0 unvalidated ngx 66767018C36B3BAB46398DADE3ADF173DAA3730FDA75965689EA848C9BC4E79B 32.0.16.1714 validated +ngx 0765FB7FB2B8BE28EE1F0226679D7B2636A69CACCE5A23891984978F690D81A4 32.0.15.9636 unvalidated diff --git a/tools/windows/player_config.h b/tools/windows/player_config.h index c86e72b..21d74bf 100644 --- a/tools/windows/player_config.h +++ b/tools/windows/player_config.h @@ -22,7 +22,7 @@ struct IniEntry { std::string section, key, value; }; struct Pin { std::string kind, sha256, version, status; }; struct Settings { unsigned preset = 11; // K - bool asyncInterop = false, validateOutput = false, verbose = false; + bool asyncInterop = false, validateOutput = false, verbose = false, showWatermark = false; std::wstring cacheDir; }; struct State { @@ -102,7 +102,8 @@ inline Settings read_settings(const std::vector& ini, std::vector& SetEnvironmentVariableW(name, value ? value->c_str() : nullptr); _wputenv_s(name, value ? value->c_str() : L""); } -// Only when the game has no console/pipe: never steal a launcher's streams. -inline bool unattached(DWORD stream) { - const HANDLE current = GetStdHandle(stream); - return !current || current == INVALID_HANDLE_VALUE; -} +// Player mode only runs when no launcher set up the environment, so no launcher is +// reading these streams. A game started from Steam can have a valid but unread pipe +// as stderr; always writing to the log keeps errors visible. inline void redirect(FILE* crt, DWORD stream, const std::filesystem::path& path) { FILE* reopened = nullptr; if (_wfreopen_s(&reopened, path.c_str(), L"a", crt) || !reopened) return; @@ -253,6 +252,7 @@ inline void apply(const std::filesystem::path& dir, const Settings& settings) { {L"D4R_QUIET_API", settings.verbose ? unset : on}, {L"D4R_VALIDATE_OUTPUT", settings.validateOutput ? on : unset}, {L"D4R_ASYNC_INTEROP", settings.asyncInterop ? on : unset}, + {L"D4R_SHIM_WATERMARK", settings.showWatermark ? on : unset}, {L"D4R_PROFILE_STAGES", unset}, {L"D4R_ZLUDA_PROFILE", unset}, {L"D4R_ZLUDA_PROFILE_DEFERRED", unset}, {L"D4R_ZLUDA_PROFILE_ALLOW_LEGACY", unset}, {L"D4R_ZLUDA_PROFILE_EVERY", unset}, {L"D4R_PROFILE_COMMAND_HOOKS", unset}, {L"D4R_ZLUDA_PROFILE_API", unset}, {L"D4R_PROFILE_GPU_BOUNDARY", unset}, @@ -278,12 +278,10 @@ inline void ensure_environment() { if (!std::filesystem::exists(dir / L"d4r.ini")) return; // Developer layout: keep the original requirement. auto& current = state(); current.dir = dir; const auto log = dir / L"d4r_nvngx.log"; - if (unattached(STD_ERROR_HANDLE)) { - { std::ofstream truncate(log, std::ios::trunc); } // Rewritten at every launch, as on Linux. - redirect(stderr, STD_ERROR_HANDLE, log); - } + { std::ofstream truncate(log, std::ios::trunc); } // Rewritten at every launch, as on Linux. + redirect(stderr, STD_ERROR_HANDLE, log); const auto settings = read_settings(parse_ini(read_text(dir / L"d4r.ini")), current.notes); - if (settings.verbose && unattached(STD_OUTPUT_HANDLE)) redirect(stdout, STD_OUTPUT_HANDLE, log); + if (settings.verbose) redirect(stdout, STD_OUTPUT_HANDLE, log); const auto pins = parse_pins(read_text(dir / L"dll-pins.txt")); verify_dll(dir, pins, "dlss", L"nvngx_dlss.dll", "nvngx_dlss.dll", current.notes); verify_dll(dir, pins, "ngx", L"ngx\\_nvngx.dll", "_nvngx.dll", current.notes); diff --git a/tools/windows/player_config_probe.cpp b/tools/windows/player_config_probe.cpp index 99f067e..676d67a 100644 --- a/tools/windows/player_config_probe.cpp +++ b/tools/windows/player_config_probe.cpp @@ -46,6 +46,12 @@ int wmain(int argc, wchar_t** argv) { s = settings("[Kernels]\nNativeFp8 = on\n[Latency]\nFrameAge = 1\n", notes); expect(has_note(notes, "[Kernels] NativeFp8 is not a Windows setting") && has_note(notes, "[Latency] FrameAge"), "Linux-only settings are reported as ignored"); + s = settings("", notes); + expect(!s.showWatermark, "watermark is hidden by default"); + s = settings("[DLSS]\nShowWatermark = true\n", notes); + expect(s.showWatermark && notes.empty(), "[DLSS] ShowWatermark = true keeps NVIDIA's stamp"); + s = settings("[DLSS]\nShowWatermark = sometimes\n", notes); + expect(!s.showWatermark && has_note(notes, "ShowWatermark must be true or false"), "invalid ShowWatermark falls back to hidden with a note"); s = settings("[Paths]\nCacheDir = D:\\d4r-cache\n", notes); expect(s.cacheDir == L"D:\\d4r-cache" && notes.empty(), "CacheDir path");