Add automatic native Windows GPU quick-test packaging
This commit is contained in:
1 parent
3d5f42865f
commit
21ed0a8c88
7 files changed
+502
No files matched your search
@@ -30,6 +30,14 @@ Only RX 9070 XT has physical validation here; all other targets are compile-test
|
||||
The installer checks the real HIP target before changing game files. See
|
||||
[Windows GPU coverage and issue #10 diagnostics](docs/windows-gpu-support.md).
|
||||
|
||||
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.
|
||||
|
||||
See [supported games](SUPPORTED_GAMES.md) for the tested games and DLSS models.
|
||||
|
||||
> **Not affiliated with NVIDIA or AMD.**
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
D4R WINDOWS QUICK TEST -- READ THIS FIRST
|
||||
|
||||
This is an EXPERIMENTAL Windows 11 D3D12 build for RDNA3 / RDNA3.5 / RDNA4.
|
||||
Only RX 9070 XT has been tested here. Other cards may fail.
|
||||
The package detects the real GPU automatically. Do not rename any DLLs.
|
||||
|
||||
1. EXTRACT THE WHOLE ZIP to a normal writable folder, e.g. Desktop\d4r-test.
|
||||
Do not run it inside the ZIP. Do not copy files into the game manually.
|
||||
|
||||
2. CLOSE YOUR GAME. Have these TWO ORIGINAL NVIDIA files ready:
|
||||
_nvngx.dll version 32.0.16.1714
|
||||
nvngx_dlss.dll version 310.9.1
|
||||
NVIDIA DLLs are not included. Other versions are rejected.
|
||||
The small _nvngx.dll inside this package is d4r, NOT the original NVIDIA file.
|
||||
|
||||
3. DOUBLE-CLICK START-K.cmd. Pick the actual game .exe when asked.
|
||||
Pick your two NVIDIA DLLs. These choices are remembered for the next run.
|
||||
Next time, press Enter for the same game, or C to choose another.
|
||||
If Steam/Epic is needed, keep it open. Choose a D3D12 game without anti-cheat.
|
||||
If a launcher opens, type C and choose the real game executable next time.
|
||||
|
||||
4. IN THE GAME, select DLSS if available, otherwise FSR/XeSS for OptiScaler.
|
||||
Leave frame generation OFF. First launch can compile for several minutes.
|
||||
Keep the console open. Play for 30 seconds, check the image, then EXIT THE GAME.
|
||||
START-M.cmd tests DLSS 4.5 preset M; START-K.cmd tests DLSS 4 preset K.
|
||||
|
||||
5. SEND THE SINGLE ZIP shown as "SEND THIS ONE FILE" in the console.
|
||||
It is also in the results folder. Tell us your GPU, game, K/M and image result:
|
||||
"correct image", "black screen", "crash" or "visual corruption".
|
||||
A passing backend log does not automatically prove the image is correct.
|
||||
Logs can contain local paths. Review them before posting them publicly.
|
||||
|
||||
TO UNINSTALL: close the game and double-click RESTORE-GAME.cmd.
|
||||
Original replaced files are backed up and restored; changed files are preserved.
|
||||
Restore the previous game BEFORE choosing a different game for the next test.
|
||||
Always launch tests through START-K/M.cmd, not the usual game shortcut.
|
||||
|
||||
IF SOMETHING FAILS:
|
||||
- Wrong NVIDIA DLL: select the exact versions above on the next run.
|
||||
- No supported GPU / HIP failure: send the ZIP; do not spoof the GPU architecture.
|
||||
- vkd3d/Wine detected: use the game's original native Windows D3D12 files.
|
||||
- Access denied: extract to a writable folder. Do not disable system security.
|
||||
- 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.
|
||||
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.
|
||||
|
||||
Runtime files are in files/. Cache/settings are created in work/.
|
||||
Licenses are in licenses/. Corresponding sources are a SEPARATE release download;
|
||||
testers do not need to download or build them. No NVIDIA proprietary DLLs are shipped.
|
||||
|
||||
Report: https://github.com/countervolts/d4r/issues/10
|
||||
Source: https://github.com/xdfnx-dev/d4r
|
||||
@@ -19,6 +19,24 @@ explicit limits; the validated package keeps conservative arithmetic.
|
||||
|
||||
## Milestone and gates
|
||||
|
||||
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.
|
||||
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
|
||||
sources and exact patched GPL OptiScaler/submodule sources are a separate asset.
|
||||
An isolated Windows PowerShell 5 fixture verifies automatic gfx1201 selection,
|
||||
spaces/Unicode paths, remembered selection, exact install/restore, and rejection
|
||||
of wrong NGX/DLSS DLLs, vkd3d and modified package files before game writes.
|
||||
No real game is launched by these packaging checks. Reproduce packaging with
|
||||
`package-windows-quick-test.ps1 -PackageRoot <new-dir> -ArchivePath <zip>` and
|
||||
`tools/windows/package_quick_test_sources.py --output <source-zip>` after commit.
|
||||
The quick-test build is a correctness diagnostic, not the async/private-texture
|
||||
performance configuration; K optimization remains open.
|
||||
|
||||
2026-10-02 coverage: all eleven listed RDNA3 / RDNA3.5 / RDNA4 targets compile
|
||||
with separate host/device objects and guarded packages. Only RX 9070 XT has
|
||||
physical validation here; no other-target GPU workloads are executed. The
|
||||
|
||||
@@ -0,0 +1,89 @@
|
||||
[CmdletBinding()]
|
||||
param(
|
||||
[string]$GamePackagePrefix, [Parameter(Mandatory=$true)][string]$PackageRoot,
|
||||
[string]$ArchivePath
|
||||
)
|
||||
$ErrorActionPreference='Stop'
|
||||
. (Join-Path $PSScriptRoot 'gpu-target.ps1')
|
||||
$repo=[IO.Path]::GetFullPath((Join-Path $PSScriptRoot '../..'))
|
||||
if (!$GamePackagePrefix) { $GamePackagePrefix=Join-Path $repo 'dist/windows-gpu-coverage-game' }
|
||||
$PackageRoot=[IO.Path]::GetFullPath($PackageRoot)
|
||||
if (Test-Path -LiteralPath $PackageRoot) { throw 'Use a new output directory to avoid mixing releases.' }
|
||||
$commit=(& git -C $repo rev-parse HEAD).Trim()
|
||||
if ($ArchivePath -and [bool](& git -C $repo status --porcelain)) { throw 'Commit source changes before creating a public ZIP.' }
|
||||
New-Item -ItemType Directory -Force $PackageRoot | Out-Null
|
||||
$files=[Collections.Generic.List[object]]::new()
|
||||
function Stage([string]$Source,[string]$Relative) {
|
||||
$destination=Join-Path $PackageRoot $Relative
|
||||
New-Item -ItemType Directory -Force (Split-Path $destination) | Out-Null
|
||||
Copy-Item -LiteralPath $Source -Destination $destination
|
||||
$item=Get-Item -LiteralPath $destination
|
||||
$entry=@{path=$Relative.Replace('\','/'); sha256=(Get-FileHash -LiteralPath $destination -Algorithm SHA256).Hash; version=$item.VersionInfo.FileVersion}
|
||||
$files.Add($entry)
|
||||
return $entry
|
||||
}
|
||||
$targets=[ordered]@{}; $common=@(); $baseline=$null
|
||||
foreach ($gpu in Get-D4RGpuTargets) {
|
||||
$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" }
|
||||
$targetFiles=@()
|
||||
foreach ($file in $metadata.files) {
|
||||
$relative=$file.path.Replace('\','/')
|
||||
if ((Get-FileHash -LiteralPath (Join-Path $source $file.path) -Algorithm SHA256).Hash -ne $file.sha256) { throw "Input file changed: $source/$relative" }
|
||||
if ($relative.EndsWith('.hsaco')) { Assert-D4RCodeObjectTarget (Join-Path $source $file.path) $arch }
|
||||
if ($relative -eq 'gpu-target.json' -or $relative -eq 'd4r/_nvngx.dll' -or $relative -eq 'd4r/d4r_gpu_inventory.exe' -or $relative -eq 'd4r/d4r_debug_launcher.exe' -or $relative -eq 'd4r/nvapi/nvapi64.dll' -or
|
||||
$relative.StartsWith('d4r/native/') -or $relative -like 'd4r/pixel_convert_*.hsaco') {
|
||||
$entry=Stage (Join-Path $source $file.path) "files/targets/$arch/$relative"
|
||||
$targetFiles+=@{path=$relative; sha256=$entry.sha256; version=$entry.version}
|
||||
} elseif ($relative -eq 'OptiScaler.dll' -or $relative.StartsWith('d4r/') -or $relative.StartsWith('licenses/')) {
|
||||
$publicPath=if ($relative.StartsWith('licenses/')) { $relative } else { "files/common/$relative" }
|
||||
if (!$baseline) {
|
||||
$entry=Stage (Join-Path $source $file.path) $publicPath
|
||||
if (!$relative.StartsWith('licenses/')) { $common+=@{path=$relative; sha256=$entry.sha256; version=$entry.version} }
|
||||
} elseif ((Get-FileHash -LiteralPath (Join-Path $PackageRoot $publicPath) -Algorithm SHA256).Hash -ne $file.sha256) {
|
||||
throw "Shared runtime differs in $arch : $relative"
|
||||
}
|
||||
}
|
||||
}
|
||||
if (@($targetFiles | Where-Object { $_.path.EndsWith('.hsaco') }).Count -ne 17) { throw "Expected 17 code objects for $arch" }
|
||||
$targets[$arch]=@{binarySourceCommit=$metadata.d4rCommit; hardwareValidation=$metadata.hardwareValidation; dlssSha256=$metadata.dlssSha256; files=$targetFiles}
|
||||
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')) {
|
||||
$entry=Stage (Join-Path $PSScriptRoot $name) "files/common/$name"
|
||||
$common+=@{path=$name; sha256=$entry.sha256; version=$entry.version}
|
||||
}
|
||||
$entry=Stage (Join-Path $repo 'tools/windows/gpu-targets.json') 'files/common/gpu-targets.json'
|
||||
$common+=@{path='gpu-targets.json'; sha256=$entry.sha256; version=$entry.version}
|
||||
[void](Stage (Join-Path $PSScriptRoot 'quick-test.ps1') 'files/quick-test.ps1')
|
||||
[void](Stage (Join-Path $repo 'docs/windows-quick-test.txt') 'READ-ME-FIRST.txt')
|
||||
foreach ($launch in @(@{name='START-K.cmd'; arguments='-Preset 11'},@{name='START-M.cmd'; arguments='-Preset 13'},@{name='RESTORE-GAME.cmd'; arguments='-Action restore'})) {
|
||||
$path=Join-Path $PackageRoot $launch.name
|
||||
@('@echo off','cd /d "%~dp0"',('"%SystemRoot%\System32\WindowsPowerShell\v1.0\powershell.exe" -NoProfile -STA -ExecutionPolicy Bypass -File "%~dp0files\quick-test.ps1" '+$launch.arguments),'pause') |
|
||||
Set-Content -LiteralPath $path -Encoding ASCII
|
||||
$files.Add(@{path=$launch.name; sha256=(Get-FileHash -LiteralPath $path -Algorithm SHA256).Hash; version=$null})
|
||||
}
|
||||
# Publicly supplied original identity, never the binary itself.
|
||||
$metadata=[ordered]@{schema=1; packageCommit=$commit; binarySourceCommit=$baseline.d4rCommit;
|
||||
ngxCoreSha256='66767018C36B3BAB46398DADE3ADF173DAA3730FDA75965689EA848C9BC4E79B';
|
||||
dependencies=@{optiScalerSourceCommit=$baseline.optiScaler.sourceCommit; zludaSourceCommit=$baseline.zludaBuild.sourceCommit;
|
||||
llvmSource=$baseline.zludaBuild.llvmSource; hipRuntime=$baseline.hipRuntime};
|
||||
commonFiles=$common; targets=$targets; files=$files}
|
||||
$metadata | ConvertTo-Json -Depth 12 | Set-Content -LiteralPath (Join-Path $PackageRoot 'files/package.json') -Encoding UTF8
|
||||
if ($ArchivePath) {
|
||||
$ArchivePath=[IO.Path]::GetFullPath($ArchivePath)
|
||||
if ($ArchivePath.StartsWith($PackageRoot+'\',[StringComparison]::OrdinalIgnoreCase)) { throw 'Place the ZIP outside the output directory.' }
|
||||
New-Item -ItemType Directory -Force (Split-Path $ArchivePath) | Out-Null
|
||||
Add-Type -AssemblyName System.IO.Compression,System.IO.Compression.FileSystem
|
||||
$stream=[IO.File]::Open($ArchivePath,[IO.FileMode]::Create,[IO.FileAccess]::ReadWrite,[IO.FileShare]::None)
|
||||
$archive=$null
|
||||
try {
|
||||
$archive=[IO.Compression.ZipArchive]::new($stream,[IO.Compression.ZipArchiveMode]::Create)
|
||||
foreach ($relative in @($files.path)+@('files/package.json')) {
|
||||
[void][IO.Compression.ZipFileExtensions]::CreateEntryFromFile($archive,(Join-Path $PackageRoot $relative),$relative,[IO.Compression.CompressionLevel]::Optimal)
|
||||
}
|
||||
} finally { if ($archive) { $archive.Dispose() }; $stream.Dispose() }
|
||||
}
|
||||
Write-Host "Quick-test package: $PackageRoot (11 targets, automatic GPU selection, no NVIDIA DLLs)"
|
||||
@@ -0,0 +1,166 @@
|
||||
[CmdletBinding()]
|
||||
param(
|
||||
[ValidateSet('run','install','restore')][string]$Action='run',
|
||||
[ValidateSet(11,13)][int]$Preset=11,
|
||||
[string]$GameExe, [string]$NgxCore, [string]$DlssDll
|
||||
)
|
||||
$ErrorActionPreference='Stop'
|
||||
$root=[IO.Path]::GetFullPath((Join-Path $PSScriptRoot '..'))
|
||||
$work=Join-Path $root 'work'
|
||||
$settingsPath=Join-Path $work 'settings.json'
|
||||
New-Item -ItemType Directory -Force $work | Out-Null
|
||||
$saved=$null
|
||||
if (Test-Path -LiteralPath $settingsPath) {
|
||||
try { $saved=Get-Content -LiteralPath $settingsPath -Raw | ConvertFrom-Json }
|
||||
catch { Write-Host 'Saved choices could not be read. Select the files again.' }
|
||||
}
|
||||
function Select-LocalFile([string]$Title, [string]$FileName, [string]$Previous) {
|
||||
if ($Previous -and (Test-Path -LiteralPath $Previous -PathType Leaf)) { return (Get-Item -LiteralPath $Previous).FullName }
|
||||
Add-Type -AssemblyName System.Windows.Forms
|
||||
$dialog=New-Object Windows.Forms.OpenFileDialog
|
||||
try {
|
||||
$dialog.Title=$Title; $dialog.FileName=$FileName
|
||||
$dialog.Filter=if ($FileName -eq '*.exe') { 'Game executable (*.exe)|*.exe' } else { "$FileName|$FileName" }
|
||||
if ($dialog.ShowDialog() -ne [Windows.Forms.DialogResult]::OK) { throw 'File selection cancelled. No test was launched.' }
|
||||
return $dialog.FileName
|
||||
} finally { $dialog.Dispose() }
|
||||
}
|
||||
function Copy-Payload([string]$Source, [string]$Destination, [string]$Relative) {
|
||||
$path=[IO.Path]::GetFullPath((Join-Path $Destination $Relative))
|
||||
if (!$path.StartsWith($Destination+'\',[StringComparison]::OrdinalIgnoreCase)) { throw 'Invalid package path.' }
|
||||
New-Item -ItemType Directory -Force (Split-Path $path) | Out-Null
|
||||
Copy-Item -LiteralPath (Join-Path $Source $Relative) -Destination $path -Force
|
||||
}
|
||||
if ($Action -eq 'restore') {
|
||||
$GameExe=Select-LocalFile 'Select the SAME game executable used for the test' '*.exe' $(if ($GameExe) { $GameExe } elseif ($saved) { $saved.game })
|
||||
& (Join-Path $PSScriptRoot 'common/windows-game.ps1') -Action restore -GameExe $GameExe
|
||||
Write-Host 'RESTORED. Launch the game normally again.' -ForegroundColor Green
|
||||
return
|
||||
}
|
||||
$output=Join-Path $root ('results/test-'+(Get-Date -Format 'yyyyMMdd-HHmmss-fff'))
|
||||
New-Item -ItemType Directory -Force $output | Out-Null
|
||||
$status=[ordered]@{action=$Action; preset=$Preset; status='starting'; hardwareValidationCompleted=$false}
|
||||
$selectedPackage=$null
|
||||
try {
|
||||
Write-Host 'd4r Windows quick test - keep this window open until you EXIT the game.'
|
||||
Write-Host 'Checking package and detecting the GPU...'
|
||||
$manifest=Get-Content -LiteralPath (Join-Path $PSScriptRoot 'package.json') -Raw | ConvertFrom-Json
|
||||
foreach ($file in $manifest.files) {
|
||||
$path=[IO.Path]::GetFullPath((Join-Path $root $file.path))
|
||||
if (!$path.StartsWith($root+'\',[StringComparison]::OrdinalIgnoreCase) -or
|
||||
(Get-FileHash -LiteralPath $path -Algorithm SHA256).Hash -ne $file.sha256) { throw "Package is incomplete or changed. Extract the ZIP again. File: $($file.path)" }
|
||||
}
|
||||
$status.packageCommit=$manifest.packageCommit
|
||||
$inventoryDir=Join-Path $output 'gpu'
|
||||
New-Item -ItemType Directory -Force $inventoryDir | Out-Null
|
||||
$info=New-Object Diagnostics.ProcessStartInfo
|
||||
$info.FileName=Join-Path $PSScriptRoot 'targets/gfx1201/d4r/d4r_gpu_inventory.exe'
|
||||
$info.Arguments='--hip-root "'+(Join-Path $PSScriptRoot 'common/d4r/hip')+'" --output-dir "'+$inventoryDir+'"'
|
||||
$info.UseShellExecute=$false; $info.CreateNoWindow=$true
|
||||
$info.EnvironmentVariables['D4R_DIAG_DIR']=$inventoryDir
|
||||
$info.RedirectStandardOutput=$true; $info.RedirectStandardError=$true
|
||||
$process=New-Object Diagnostics.Process; $process.StartInfo=$info
|
||||
try {
|
||||
if (!$process.Start()) { throw 'GPU detection could not start.' }
|
||||
$stdout=$process.StandardOutput.ReadToEndAsync(); $stderr=$process.StandardError.ReadToEndAsync()
|
||||
$timedOut=!$process.WaitForExit(30000)
|
||||
if ($timedOut) { $process.Kill(); $process.WaitForExit() }
|
||||
$stdout.GetAwaiter().GetResult() | Set-Content (Join-Path $inventoryDir 'stdout.log') -Encoding UTF8
|
||||
$stderr.GetAwaiter().GetResult() | Set-Content (Join-Path $inventoryDir 'stderr.log') -Encoding UTF8
|
||||
$status.inventoryExit='0x{0:X8}' -f [BitConverter]::ToUInt32([BitConverter]::GetBytes([int]$process.ExitCode),0)
|
||||
if ($timedOut -or $process.ExitCode) { throw 'HIP could not detect your GPU. The diagnostic ZIP contains the details.' }
|
||||
} finally { $process.Dispose() }
|
||||
$inventory=Get-Content -LiteralPath (Join-Path $inventoryDir 'gpu-inventory.json') -Raw | ConvertFrom-Json
|
||||
$devices=@($inventory.devices | Where-Object { $_.buildEligible -and $_.wavefrontSize -eq 32 -and $manifest.targets.PSObject.Properties.Name -contains $_.architecture } | Sort-Object vramBytes -Descending)
|
||||
if (!$devices.Count) { throw 'No supported RDNA3/RDNA4 HIP device was detected. See the GPU inventory in the diagnostic ZIP.' }
|
||||
$device=$devices[0]
|
||||
if (@($devices.architecture | Select-Object -Unique).Count -gt 1) {
|
||||
for ($i=0; $i -lt $devices.Count; $i++) { Write-Host "$($i+1): $($devices[$i].name) ($($devices[$i].architecture))" }
|
||||
$choice=Read-Host 'Select the GPU used by your game (press Enter for 1)'
|
||||
if ($choice) {
|
||||
$index=0
|
||||
if (![int]::TryParse($choice,[ref]$index) -or $index -lt 1 -or $index -gt $devices.Count) { throw 'Invalid GPU selection.' }
|
||||
$device=$devices[$index-1]
|
||||
}
|
||||
}
|
||||
$arch=$device.architecture; $target=$manifest.targets.$arch
|
||||
$status.gpu=$device; $status.binarySourceCommit=$target.binarySourceCommit
|
||||
Write-Host "Detected: $($device.name) / $arch" -ForegroundColor Cyan
|
||||
Write-Host 'Experimental build. This run is a correctness test, not an FPS benchmark.'
|
||||
$previousGame=if ($saved) { $saved.game } else { $null }
|
||||
if (!$GameExe -and $previousGame -and (Test-Path -LiteralPath $previousGame)) {
|
||||
Write-Host "Remembered game: $previousGame"
|
||||
$reuse=Read-Host 'Press Enter to use this game, or type C to choose another (restore the old game first)'
|
||||
if ($reuse -match '^(?i)c$') { $previousGame=$null }
|
||||
elseif ($reuse) { throw 'Invalid choice. Press Enter or type C on the next run.' }
|
||||
}
|
||||
$GameExe=Select-LocalFile 'Select the actual D3D12 game .exe (not Steam or a launcher)' '*.exe' $(if ($GameExe) { $GameExe } else { $previousGame })
|
||||
$NgxCore=Select-LocalFile 'Select your ORIGINAL NVIDIA _nvngx.dll (32.0.16.1714)' '_nvngx.dll' $(if ($NgxCore) { $NgxCore } elseif ($saved) { $saved.ngxCore })
|
||||
$DlssDll=Select-LocalFile 'Select your NVIDIA nvngx_dlss.dll (310.9.1)' 'nvngx_dlss.dll' $(if ($DlssDll) { $DlssDll } elseif ($saved) { $saved.dlssDll })
|
||||
$status.game=$GameExe
|
||||
$status.localDlls=@($NgxCore,$DlssDll | ForEach-Object { $item=Get-Item -LiteralPath $_; @{path=$item.FullName; version=$item.VersionInfo.FileVersion; sha256=(Get-FileHash -LiteralPath $_ -Algorithm SHA256).Hash} })
|
||||
if ((Get-FileHash -LiteralPath $NgxCore -Algorithm SHA256).Hash -ne $manifest.ngxCoreSha256) {
|
||||
throw 'Wrong _nvngx.dll. This test needs the original NVIDIA 32.0.16.1714 DLL, not the d4r shim. Select the correct file on the next run.'
|
||||
}
|
||||
if ((Get-FileHash -LiteralPath $DlssDll -Algorithm SHA256).Hash -ne $target.dlssSha256) {
|
||||
throw 'Wrong nvngx_dlss.dll. This test needs exactly NVIDIA DLSS 310.9.1. Select the correct file on the next run.'
|
||||
}
|
||||
if ($GameExe.StartsWith($root+'\',[StringComparison]::OrdinalIgnoreCase)) { throw 'Select the installed GAME executable, outside this extracted package.' }
|
||||
# Save only validated choices. Wrong DLLs are never remembered.
|
||||
@{game=$GameExe; ngxCore=$NgxCore; dlssDll=$DlssDll; architecture=$arch} | ConvertTo-Json | Set-Content -LiteralPath $settingsPath -Encoding UTF8
|
||||
$selectedPackage=Join-Path $work "package-$arch"
|
||||
New-Item -ItemType Directory -Force $selectedPackage | Out-Null
|
||||
$selectedFiles=@()
|
||||
foreach ($file in $manifest.commonFiles) {
|
||||
Copy-Payload (Join-Path $PSScriptRoot 'common') $selectedPackage $file.path
|
||||
$selectedFiles+=$file
|
||||
}
|
||||
foreach ($file in $target.files) {
|
||||
Copy-Payload (Join-Path $PSScriptRoot "targets/$arch") $selectedPackage $file.path
|
||||
$selectedFiles+=$file
|
||||
}
|
||||
@{architecture=$arch; d4rCommit=$target.binarySourceCommit; packageCommit=$manifest.packageCommit;
|
||||
dlssSha256=$target.dlssSha256; hardwareValidation=$target.hardwareValidation; files=$selectedFiles} |
|
||||
ConvertTo-Json -Depth 8 | Set-Content -LiteralPath (Join-Path $selectedPackage 'package.json') -Encoding UTF8
|
||||
Write-Host 'Original game files will be backed up. RESTORE-GAME.cmd reverses the installation.'
|
||||
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
|
||||
if ($Action -eq 'install') { $status.status='installed-only' }
|
||||
else {
|
||||
$summary=Get-Content -LiteralPath (Join-Path $output 'summary.json') -Raw | ConvertFrom-Json
|
||||
$status.gameSummary=$summary
|
||||
$required=Get-Content -LiteralPath (Join-Path $selectedPackage 'd4r/native/d4r-kernels.txt') | Where-Object {
|
||||
if ($Preset -eq 11) { $_ -match '^dltss_pwin_' } else { $_ -match '^rrlite_' }
|
||||
} | ForEach-Object { $_.Split(' ')[0] }
|
||||
$hits=@($summary.kernels | Where-Object { $_.backend -eq 'native' -and $_.launches -gt 0 } | ForEach-Object { $_.kernel })
|
||||
$status.missingNativeKernels=@($required | Where-Object { $hits -notcontains $_ })
|
||||
$status.translatedNativeKernels=@($summary.kernels | Where-Object { $_.backend -eq 'translated' -and $required -contains $_.kernel } | ForEach-Object { $_.kernel })
|
||||
$passed=$summary.exitCode -eq '0x0' -and $summary.completedFrames -gt 0 -and $summary.nativeLaunches -gt 0 -and
|
||||
$summary.outputGpuChecks -gt 0 -and $summary.failures -eq 0 -and $summary.nonfiniteOutputs -eq 0 -and
|
||||
$summary.previousFrameOutputs -eq 0 -and $summary.cpuImageCopyFrames -eq 0 -and
|
||||
@($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' }
|
||||
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 {
|
||||
$status.status='failed'; $status.error=$_.Exception.Message
|
||||
$_ | Out-String | Set-Content -LiteralPath (Join-Path $output 'error.txt') -Encoding UTF8
|
||||
Write-Host "TEST STOPPED: $($status.error)" -ForegroundColor Red
|
||||
} finally {
|
||||
if ($selectedPackage) {
|
||||
$preflights=Join-Path $selectedPackage 'results'
|
||||
$latest=Get-ChildItem -LiteralPath $preflights -Directory -ErrorAction SilentlyContinue | Sort-Object Name -Descending | Select-Object -First 1
|
||||
if ($latest) { Copy-Item -LiteralPath $latest.FullName -Destination (Join-Path $output 'preflight') -Recurse -Force }
|
||||
}
|
||||
try { Get-CimInstance Win32_VideoController | Select-Object Name,DriverVersion,DriverDate | ConvertTo-Json | Set-Content -LiteralPath (Join-Path $output 'driver.json') -Encoding UTF8 } catch {}
|
||||
$status | ConvertTo-Json -Depth 12 | Set-Content -LiteralPath (Join-Path $output 'quick-test.json') -Encoding UTF8
|
||||
Compress-Archive -LiteralPath $output -DestinationPath ($output+'.zip') -Force
|
||||
Write-Host "SEND THIS ONE FILE: $output.zip" -ForegroundColor Cyan
|
||||
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 }
|
||||
@@ -0,0 +1,67 @@
|
||||
# Isolated installer checks; never starts a real game or launches GPU kernels.
|
||||
[CmdletBinding()]
|
||||
param(
|
||||
[Parameter(Mandatory=$true)][string]$PackageRoot,
|
||||
[Parameter(Mandatory=$true)][string]$NgxCore,
|
||||
[Parameter(Mandatory=$true)][string]$DlssDll,
|
||||
[string]$OutputDirectory
|
||||
)
|
||||
$ErrorActionPreference='Stop'
|
||||
$repo=[IO.Path]::GetFullPath((Join-Path $PSScriptRoot '../..'))
|
||||
$PackageRoot=(Get-Item -LiteralPath $PackageRoot).FullName
|
||||
if (!$OutputDirectory) { $OutputDirectory=Join-Path $repo ('test-results/windows-quick-test/'+(Get-Date -Format 'yyyyMMdd-HHmmss-fff')) }
|
||||
$OutputDirectory=[IO.Path]::GetFullPath($OutputDirectory)
|
||||
New-Item -ItemType Directory -Force $OutputDirectory | Out-Null
|
||||
function Invoke-Child([string]$Name,[string[]]$Arguments,[int]$ExpectedExit=0) {
|
||||
$log=Join-Path $OutputDirectory "$Name.log"
|
||||
& "$env:SystemRoot/System32/WindowsPowerShell/v1.0/powershell.exe" -NoProfile -STA -ExecutionPolicy Bypass `
|
||||
-File (Join-Path $PackageRoot 'files/quick-test.ps1') @Arguments *> $log
|
||||
if ($LASTEXITCODE -ne $ExpectedExit) { throw "Unexpected exit $LASTEXITCODE for $Name; see $log" }
|
||||
}
|
||||
function Fixture([string]$Name) {
|
||||
$dir=Join-Path $OutputDirectory $Name
|
||||
New-Item -ItemType Directory -Force $dir | Out-Null
|
||||
$exe=Join-Path $dir 'Fixture.exe'
|
||||
Set-Content -LiteralPath $exe 'installer fixture; do not execute' -Encoding ASCII
|
||||
return $exe
|
||||
}
|
||||
$results=@()
|
||||
$exe=Fixture ('space GPU '+[char]0x03a9+' test')
|
||||
$game=Split-Path $exe
|
||||
$before=@{}
|
||||
foreach ($name in @('dxgi.dll','OptiScaler.ini')) {
|
||||
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)
|
||||
$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.
|
||||
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}
|
||||
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 }
|
||||
$dlss=if ($case -eq 'wrong-dlss') { $NgxCore } else { $DlssDll }
|
||||
if ($case -eq 'vkd3d') { Set-Content -LiteralPath (Join-Path $game 'd3d12.dll') 'vkd3d-proton fixture; do not load' -Encoding ASCII }
|
||||
Invoke-Child $case @('-Action','install','-GameExe',$exe,'-NgxCore',$core,'-DlssDll',$dlss) 1
|
||||
if ((Test-Path -LiteralPath (Join-Path $game '.d4r-backup')) -or (Test-Path -LiteralPath (Join-Path $game 'dxgi.dll'))) { throw "$case changed game files before rejection." }
|
||||
$message=Get-Content -LiteralPath (Join-Path $OutputDirectory "$case.log") -Raw
|
||||
$match=[regex]::Match($message,'SEND THIS ONE FILE: (.+\.zip)')
|
||||
if (!$match.Success -or !(Test-Path -LiteralPath $match.Groups[1].Value.Trim())) { throw "$case did not produce its single report ZIP." }
|
||||
$results+=@{case=$case; passed=$true; rejectedBeforeGameChanges=$true; gpuKernelsExecuted=$false}
|
||||
}
|
||||
$readme=Join-Path $PackageRoot 'READ-ME-FIRST.txt'
|
||||
$original=[IO.File]::ReadAllBytes($readme)
|
||||
try {
|
||||
Add-Content -LiteralPath $readme 'tampered fixture'
|
||||
$exe=Fixture 'tampered-package'
|
||||
Invoke-Child 'tampered-package' @('-Action','install','-GameExe',$exe,'-NgxCore',$NgxCore,'-DlssDll',$DlssDll) 1
|
||||
$game=Split-Path $exe
|
||||
if ((Test-Path -LiteralPath (Join-Path $game '.d4r-backup')) -or (Test-Path -LiteralPath (Join-Path $game 'dxgi.dll'))) { throw 'Tampered package changed game files.' }
|
||||
$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"
|
||||
@@ -0,0 +1,99 @@
|
||||
"""Separate corresponding-source asset for the lightweight Windows test ZIP.
|
||||
|
||||
Reads committed trees only, including the exact patched GPL OptiScaler tree and
|
||||
its pinned submodules. Never copies a working directory or NVIDIA binaries.
|
||||
"""
|
||||
import argparse
|
||||
import hashlib
|
||||
import io
|
||||
import json
|
||||
from pathlib import Path
|
||||
import subprocess
|
||||
import zipfile
|
||||
|
||||
|
||||
def git(root, *args):
|
||||
return subprocess.check_output(["git", "-C", str(root), *args])
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description=__doc__)
|
||||
parser.add_argument("--output", type=Path, required=True)
|
||||
parser.add_argument("--repo", type=Path, default=Path(__file__).resolve().parents[2])
|
||||
args = parser.parse_args()
|
||||
repo = args.repo.resolve()
|
||||
info = json.loads((repo / "dist/optiscaler-windows-d4r/build-info.json").read_text(encoding="utf-8-sig"))
|
||||
opti = repo / "external/OptiScaler"
|
||||
if info["sourceDirty"] or git(opti, "status", "--porcelain").strip():
|
||||
raise RuntimeError("OptiScaler must match its clean binary source tree")
|
||||
if git(opti, "rev-parse", "HEAD").decode().strip() != info["sourceCommit"]:
|
||||
raise RuntimeError("OptiScaler source does not match the binary")
|
||||
if git(repo, "status", "--porcelain").strip():
|
||||
raise RuntimeError("Commit the packaging sources before publishing")
|
||||
args.output.parent.mkdir(parents=True, exist_ok=True)
|
||||
if args.output.exists():
|
||||
raise RuntimeError("Use a new output archive")
|
||||
provenance = {"d4rCommit": git(repo, "rev-parse", "HEAD").decode().strip(),
|
||||
"optiScalerBinary": info["dllSha256"], "sourceTrees": [], "excludedNvidiaBinaries": []}
|
||||
|
||||
with zipfile.ZipFile(args.output, "w", compression=zipfile.ZIP_DEFLATED, compresslevel=6) as output:
|
||||
def tree(root, revision, prefix):
|
||||
provenance["sourceTrees"].append({"path": prefix, "commit": revision})
|
||||
raw = git(root, "archive", "--format=zip", revision)
|
||||
with zipfile.ZipFile(io.BytesIO(raw)) as source:
|
||||
for item in source.infolist():
|
||||
if item.is_dir():
|
||||
continue
|
||||
name = prefix + "/" + item.filename
|
||||
lowered = name.lower()
|
||||
nvidia_binary = ("/nvapi/" in lowered or "nvngx" in Path(lowered).name) and Path(lowered).suffix in {
|
||||
".dll", ".exe", ".lib", ".bin", ".cubin", ".fatbin", ".pdb"}
|
||||
if nvidia_binary:
|
||||
provenance["excludedNvidiaBinaries"].append(name)
|
||||
continue
|
||||
output.writestr(name, source.read(item))
|
||||
for entry in git(root, "ls-tree", "-rz", revision).split(b"\0"):
|
||||
if not entry:
|
||||
continue
|
||||
header, path = entry.split(b"\t", 1)
|
||||
mode, _, commit = header.split()
|
||||
if mode != b"160000":
|
||||
continue
|
||||
child = path.decode("utf-8")
|
||||
child_root = root / child
|
||||
child_commit = commit.decode()
|
||||
if git(child_root, "rev-parse", "HEAD").decode().strip() != child_commit:
|
||||
raise RuntimeError(f"Submodule does not match committed source: {child}")
|
||||
if git(child_root, "status", "--porcelain").strip():
|
||||
raise RuntimeError(f"Dirty source submodule: {child}")
|
||||
tree(child_root, child_commit, prefix + "/" + child)
|
||||
|
||||
tree(repo, provenance["d4rCommit"], "d4r")
|
||||
tree(opti, info["sourceCommit"], "OptiScaler")
|
||||
output.writestr("source-provenance.json", json.dumps(provenance, indent=2) + "\n")
|
||||
output.writestr("READ-ME-SOURCES.txt", """CORRESPONDING SOURCES -- NOT NEEDED TO TEST THE GAME
|
||||
|
||||
d4r/ contains the committed Windows port, build scripts, dependency patches
|
||||
and licenses. OptiScaler/ contains the exact modified GPL-3.0 source tree and
|
||||
its pinned source submodules corresponding to the distributed OptiScaler.dll.
|
||||
See source-provenance.json for exact commits and the binary SHA256.
|
||||
|
||||
Build instructions: d4r/docs/windows-rdna4-port.md and
|
||||
d4r/patches/optiscaler/README.md. The normal build script retrieves the pinned
|
||||
upstream checkout, submodules and applies the exported source patches.
|
||||
NVIDIA import-library binaries are intentionally excluded from this archive;
|
||||
the pinned public NVIDIA/nvapi SDK used by the builder supplies them locally.
|
||||
No proprietary NVIDIA runtime DLL or NVIDIA-derived private kernel is included.
|
||||
|
||||
The separately bundled ZLUDA/LLVM changes are in d4r/patches/zluda and
|
||||
d4r/patches/zluda-llvm, with upstream source commits and build instructions.
|
||||
""")
|
||||
with args.output.open("rb") as stream:
|
||||
sha256 = hashlib.file_digest(stream, "sha256").hexdigest()
|
||||
print(json.dumps({"archive": str(args.output), "bytes": args.output.stat().st_size,
|
||||
"sha256": sha256,
|
||||
"sourceTrees": len(provenance["sourceTrees"])}))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in new issue
Block a user