Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7da2f9ac39 |
No files matched your search
@@ -46,7 +46,7 @@ if (!$manifest) { Bad 'd4r\package.json is missing or damaged (extract the d4r Z
|
||||
|
||||
# --- files --------------------------------------------------------------------------------------
|
||||
$required = @('dxgi.dll', 'OptiScaler.ini', 'd4r\_nvngx.dll', 'd4r\d4r.ini', 'd4r\dll-pins.txt',
|
||||
'd4r\hip\bin\amdhip64_7.dll', 'd4r\zluda\nvcuda.dll', 'd4r\zluda\nvapi64.dll', 'd4r\nvapi\backend\nvapi64.dll')
|
||||
'd4r\hip\bin\amdhip64_7.dll', 'd4r\zluda\nvcuda.dll', 'd4r\nvapi\backend\nvapi64.dll')
|
||||
foreach ($file in $required) {
|
||||
if (Test-Path -LiteralPath (Join-Path $game $file) -PathType Leaf) { Ok $file }
|
||||
else { Bad "$file (extract the d4r ZIP again over the game folder)" }
|
||||
@@ -156,6 +156,10 @@ else {
|
||||
if ($missing.Count) { Bad "GPU $($device.name) ($a): this release has no complete build for it (missing: $($missing -join ', '))" }
|
||||
else {
|
||||
Ok "GPU $($device.name) ($a): build present, $native native kernels"
|
||||
# Each nvapi64.dll names the GPU target it was built for; a copy for another GPU is refused at startup.
|
||||
$nvapiText = [Text.Encoding]::GetEncoding(28591).GetString([IO.File]::ReadAllBytes((Join-Path $d4r "nvapi\$a\nvapi64.dll")))
|
||||
$nvapiNames = @([regex]::Matches($nvapiText, 'D4R_NVAPI_TARGET=(gfx[0-9a-f]+)\0') | ForEach-Object { $_.Groups[1].Value } | Select-Object -Unique)
|
||||
if ($nvapiNames.Count -ne 1 -or $nvapiNames[0] -ne $a) { Bad "d4r\nvapi\$a\nvapi64.dll is built for '$($nvapiNames -join ', ')' instead of $a (files from different GPUs are mixed; extract the d4r ZIP again)" }
|
||||
if (!$arch) { $arch = $a }
|
||||
}
|
||||
if ($a -eq 'gfx1101') { Note 'RX 7700 XT has passed native Windows synthetic checks and limited game-frame tests; broad game validation remains incomplete' }
|
||||
@@ -164,6 +168,9 @@ else {
|
||||
}
|
||||
if (@($inventory.devices | Where-Object { $_.wavefrontSize -eq 32 }).Count -gt 1) { Note 'several GPUs found; d4r uses the one the game renders on' }
|
||||
}
|
||||
if (Test-Path -LiteralPath (Join-Path $d4r 'zluda\nvapi64.dll')) {
|
||||
Note 'd4r\zluda\nvapi64.dll is left over from an older d4r release (one GPU''s build shared by all). d4r ignores it, but delete it'
|
||||
}
|
||||
if ($arch -and $registry.Count) {
|
||||
$info = @($registry | Where-Object { $_.architecture -eq $arch }) | Select-Object -First 1
|
||||
$bad = 0
|
||||
@@ -198,7 +205,7 @@ $iniPath = Join-Path $d4r 'd4r.ini'
|
||||
$model = 'K'
|
||||
if (Test-Path -LiteralPath $iniPath) {
|
||||
$section = ''; $iniProblems = 0
|
||||
$known = @{ 'dlss.model' = 1; 'dlss.showwatermark' = 1; 'interop.asyncinterop' = 1; 'interop.batchinputcopies' = 1; 'debug.validateoutput' = 1; 'debug.log' = 1; 'paths.cachedir' = 1; 'paths.nativedir' = 1; 'kernels.nativefp8' = 1; 'rayreconstruction.backend' = 1; 'rayreconstruction.modeldir' = 1 }
|
||||
$known = @{ 'dlss.model' = 1; 'dlss.showwatermark' = 1; 'interop.asyncinterop' = 1; 'interop.batchinputcopies' = 1; 'debug.validateoutput' = 1; 'debug.log' = 1; 'paths.cachedir' = 1; 'paths.nativedir' = 1; 'kernels.nativefp8' = 1; 'rayreconstruction.backend' = 1; 'rayreconstruction.modeldir' = 1; 'rayreconstruction.fsrdenoiser' = 1; 'rayreconstruction.fsrstabilitybias' = 1; 'rayreconstruction.fsrdetail' = 1 }
|
||||
$boolean = @('dlss.showwatermark', 'interop.asyncinterop', 'interop.batchinputcopies', 'debug.validateoutput', 'kernels.nativefp8')
|
||||
foreach ($raw in (Read-TextFile $iniPath).TrimStart([char]0xFEFF) -split "`n") {
|
||||
$line = $raw.Trim()
|
||||
|
||||
@@ -36,3 +36,12 @@ function Assert-D4RCodeObjectTarget([string]$Path, [string]$Target) {
|
||||
$expected=$info.elfMachine
|
||||
if ($header[48] -ne $expected) { throw "Code object does not target ${Target}: $Path (ELF machine=$($header[48]))" }
|
||||
}
|
||||
# The compatibility NVAPI names the GPU target it was built for (nvapi_trace.cpp); a copy for another GPU, or one
|
||||
# built before the marker existed, must never be packaged: it would refuse the GPU it is installed for.
|
||||
function Assert-D4RNvapiTarget([string]$Path, [string]$Target) {
|
||||
[void](Get-D4RGpuTargetInfo $Target)
|
||||
$text=[Text.Encoding]::GetEncoding(28591).GetString([IO.File]::ReadAllBytes($Path))
|
||||
$found=@([regex]::Matches($text,'D4R_NVAPI_TARGET=(gfx[0-9a-f]+)\0') | ForEach-Object { $_.Groups[1].Value } | Select-Object -Unique)
|
||||
if ($found.Count -ne 1) { throw "NVAPI does not name exactly one GPU target (rebuild d4r_nvapi_compat): $Path" }
|
||||
if ($found[0] -ne $Target) { throw "NVAPI is built for $($found[0]), not ${Target}: $Path" }
|
||||
}
|
||||
@@ -44,6 +44,7 @@ function Stage([string]$source, [string]$relative) {
|
||||
Stage (Join-Path $OptiScalerRoot 'OptiScaler.dll') 'OptiScaler.dll'
|
||||
Stage (Join-Path $DiagnosticRoot 'bin/d4r_nvngx.dll') 'd4r/_nvngx.dll'
|
||||
Stage (Join-Path $DiagnosticRoot "bin/pixel_convert_${GpuArch}.hsaco") "d4r/pixel_convert_${GpuArch}.hsaco"
|
||||
Assert-D4RNvapiTarget (Join-Path $DiagnosticRoot 'nvapi-compat/nvapi64.dll') $GpuArch
|
||||
Stage (Join-Path $DiagnosticRoot 'nvapi-compat/nvapi64.dll') 'd4r/nvapi/nvapi64.dll'
|
||||
Stage (Join-Path $DiagnosticRoot 'nvapi-compat/nvapi64.dll') 'd4r/zluda/nvapi64.dll'
|
||||
Stage (Join-Path $DiagnosticRoot 'bin/d4r_debug_launcher.exe') 'd4r/d4r_debug_launcher.exe'
|
||||
|
||||
@@ -33,7 +33,7 @@ foreach ($gpu in Get-D4RGpuTargets) {
|
||||
$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
|
||||
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 -eq 'd4r/zluda/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}
|
||||
|
||||
@@ -64,7 +64,10 @@ foreach ($gpu in $selected) {
|
||||
# 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 -eq 'd4r/nvapi/nvapi64.dll') { Assert-D4RNvapiTarget $path $arch; Stage $path "d4r/nvapi/$arch/nvapi64.dll" }
|
||||
# The copy beside nvcuda.dll is one GPU's build: it is never shared. Each target's own build ships in
|
||||
# d4r/nvapi/<arch>/ (above); the old shared copy made every GPU load the first target's NVAPI.
|
||||
elseif ($relative -eq 'd4r/zluda/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)) }
|
||||
elseif ($relative -eq 'd4r/nvapi/backend/nvapi64.dll' -or $relative -eq 'OptiScaler.dll' -or $relative.StartsWith('licenses/') -or
|
||||
@@ -76,6 +79,7 @@ foreach ($gpu in $selected) {
|
||||
# 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" }
|
||||
if (!(Test-Path -LiteralPath (Join-Path $PackageRoot "d4r/nvapi/$arch/nvapi64.dll") -PathType Leaf)) { throw "No d4r/nvapi/nvapi64.dll in the $arch input; the release needs one NVAPI build per target." }
|
||||
$commonStaged=$true
|
||||
$targets[$arch]=@{binarySourceCommit=$metadata.d4rCommit; hardwareValidation=$metadata.hardwareValidation;
|
||||
nativeFp8M=[bool]$metadata.nativeFp8M; tokenLanesM=[bool]$metadata.tokenLanesM}
|
||||
|
||||
@@ -91,6 +91,13 @@ $other=@(Get-ChildItem -LiteralPath (Join-Path $game 'd4r/native/gfx1100') -Filt
|
||||
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')
|
||||
|
||||
$game=New-Install 'gpu-wrong-nvapi'
|
||||
Copy-Item -LiteralPath (Join-Path $game 'd4r/nvapi/gfx1100/nvapi64.dll') -Destination (Join-Path $game 'd4r/nvapi/gfx1151/nvapi64.dll') -Force
|
||||
Expect 'nvapi-for-another-gpu' (Run-Check $game (Write-Inventory 'd' 'gfx1151' 'Radeon 8060S')) 1 @('MISSING\s+d4r\\nvapi\\gfx1151\\nvapi64\.dll is built for ''gfx1100'' instead of gfx1151')
|
||||
$game=New-Install 'stale-shared-nvapi'
|
||||
Copy-Item -LiteralPath (Join-Path $game 'd4r/nvapi/gfx1101/nvapi64.dll') -Destination (New-Item -ItemType Directory -Force (Join-Path $game 'd4r/zluda')).FullName -Force
|
||||
Expect 'stale-shared-nvapi-noted' (Run-Check $game (Write-Inventory 'e' 'gfx1151' 'Radeon 8060S')) 0 @('note\s+d4r\\zluda\\nvapi64\.dll is left over from an older d4r release','ok\s+GPU Radeon 8060S \(gfx1151\): build present')
|
||||
|
||||
# 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
|
||||
|
||||
@@ -46,10 +46,21 @@ function Build([string]$Name,[string[]]$Arguments) {
|
||||
$results=@()
|
||||
|
||||
# 1. d4r.ini validation and DLL pin decisions (the shim's own header).
|
||||
# The compatibility NVAPI is compiled once per registered target, exactly as the release builds it, so the
|
||||
# probe can check the target each binary names.
|
||||
$builtNvapi=Join-Path $OutputDirectory 'built-nvapi'
|
||||
$noTarget=@($common | Where-Object { $_ -notlike '-DD4R_TARGET_ARCH=*' })
|
||||
foreach ($target in $targets) {
|
||||
$arch=$target.architecture
|
||||
$dll=Join-Path $builtNvapi "$arch/nvapi64.dll"
|
||||
New-Item -ItemType Directory -Force (Split-Path $dll) | Out-Null
|
||||
Build "nvapi-$arch" ($noTarget+@("-DD4R_TARGET_ARCH=`"$arch`"",'-DD4R_NVAPI_COMPAT','-shared',(Join-Path $repo 'tools/windows/nvapi_trace.cpp'),'-o',$dll,'-lversion','-ldbghelp'))
|
||||
}
|
||||
$probe=Join-Path $OutputDirectory 'player_config_probe.exe'
|
||||
Build 'player-config' ($common+@('-municode',(Join-Path $repo 'tools/windows/player_config_probe.cpp'),'-o',$probe,'-lversion','-ldbghelp','-lbcrypt'))
|
||||
if (Invoke-Native (Join-Path $OutputDirectory 'player-config.log') $probe @((Join-Path $OutputDirectory 'pins-fixture'))) { throw "player_config_probe failed; see $(Join-Path $OutputDirectory 'player-config.log')" }
|
||||
if (Invoke-Native (Join-Path $OutputDirectory 'player-config.log') $probe @((Join-Path $OutputDirectory 'pins-fixture'),$builtNvapi)) { throw "player_config_probe failed; see $(Join-Path $OutputDirectory 'player-config.log')" }
|
||||
$results+=@{case='d4r-ini-and-dll-pins'; passed=$true}
|
||||
$results+=@{case='architecture-selection-all-targets'; passed=$true; targets=$targets.Count; nvapiBuilds=$targets.Count}
|
||||
|
||||
# 2. Loader: GPU-free marker, LUID-based target selection, forwarding, and a
|
||||
# clear failure when the folder has no build for the detected GPU.
|
||||
|
||||
@@ -16,17 +16,24 @@ function Write-CodeObject([string]$Path,[int]$Machine) {
|
||||
New-Item -ItemType Directory -Force (Split-Path $Path) | Out-Null
|
||||
[IO.File]::WriteAllBytes($Path,$header)
|
||||
}
|
||||
# Stand-in compatibility NVAPI: names its build target the way the real DLL does (nvapi_trace.cpp).
|
||||
function Write-Nvapi([string]$Path,[string]$Target) {
|
||||
New-Item -ItemType Directory -Force (Split-Path $Path) | Out-Null
|
||||
[IO.File]::WriteAllBytes($Path,[Text.Encoding]::ASCII.GetBytes("MZ nvapi fixture D4R_NVAPI_TARGET=$Target")+[byte]0)
|
||||
}
|
||||
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/nvapi/backend/nvapi64.dll'='raw backend nvapi'; 'd4r/hip/bin/amdhip64_7.dll'='hip'; 'd4r/zluda/nvcuda.dll'='zluda'; 'd4r/zluda/nvapi64.dll'='compat nvapi'; 'licenses/d4r.txt'='license';
|
||||
$text=@{'OptiScaler.dll'='optiscaler'; 'd4r/_nvngx.dll'="shim $arch"; 'd4r/nvapi/backend/nvapi64.dll'='raw backend nvapi'; 'd4r/hip/bin/amdhip64_7.dll'='hip'; 'd4r/zluda/nvcuda.dll'='zluda'; '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()) {
|
||||
$path=Join-Path $root $entry.Key; New-Item -ItemType Directory -Force (Split-Path $path) | Out-Null
|
||||
Set-Content -LiteralPath $path $entry.Value -Encoding ASCII
|
||||
}
|
||||
# As the game package stages it: the target's own build, in both places.
|
||||
Write-Nvapi (Join-Path $root 'd4r/nvapi/nvapi64.dll') $arch
|
||||
Write-Nvapi (Join-Path $root 'd4r/zluda/nvapi64.dll') $arch
|
||||
$objects=@("d4r/pixel_convert_$arch.hsaco")+@(1..16 | ForEach-Object { "d4r/native/kernel$_.hsaco" })
|
||||
foreach ($object in $objects) { Write-CodeObject (Join-Path $root $object) $target.elfMachine }
|
||||
if ($Mutate) { & $Mutate $root $arch }
|
||||
@@ -62,16 +69,23 @@ if ((Get-Content -LiteralPath (Join-Path $release 'd4r/_nvngx.dll') -Raw).Trim()
|
||||
foreach ($target in $targets) {
|
||||
$arch=$target.architecture
|
||||
if ((Get-Content -LiteralPath (Join-Path $release "d4r/_nvngx_$arch.dll") -Raw).Trim() -ne "shim $arch") { throw "Wrong shim for $arch" }
|
||||
if ((Get-Content -LiteralPath (Join-Path $release "d4r/nvapi/$arch/nvapi64.dll") -Raw).Trim() -ne "nvapi $arch") { throw "Wrong NVAPI for $arch" }
|
||||
Assert-D4RNvapiTarget (Join-Path $release "d4r/nvapi/$arch/nvapi64.dll") $arch
|
||||
foreach ($other in $targets) {
|
||||
if ($other.architecture -ne $arch) {
|
||||
$mismatch=$false
|
||||
try { Assert-D4RNvapiTarget (Join-Path $release "d4r/nvapi/$arch/nvapi64.dll") $other.architecture } catch { $mismatch=$_.Exception.Message -match "built for $arch" }
|
||||
if (!$mismatch) { throw "NVAPI for $arch passed as $($other.architecture)" }
|
||||
}
|
||||
}
|
||||
Assert-D4RCodeObjectTarget (Join-Path $release "d4r/pixel_convert_$arch.hsaco") $arch
|
||||
$native=@(Get-ChildItem -LiteralPath (Join-Path $release "d4r/native/$arch") -Filter '*.hsaco')
|
||||
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 }
|
||||
}
|
||||
if ((Get-Content -LiteralPath (Join-Path $release 'd4r/nvapi/backend/nvapi64.dll') -Raw).Trim() -ne 'raw backend nvapi') { throw 'The raw ZLUDA NVAPI backend was not staged separately.' }
|
||||
if ((Get-Content -LiteralPath (Join-Path $release 'd4r/zluda/nvapi64.dll') -Raw).Trim() -ne 'compat nvapi') { throw 'The compatibility NVAPI is not colocated with nvcuda.dll.' }
|
||||
if (Test-Path -LiteralPath (Join-Path $release 'd4r/zluda/nvapi64.dll')) { throw 'A shared NVAPI beside nvcuda.dll would be loaded for every GPU; it must not ship.' }
|
||||
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')) {
|
||||
foreach ($excluded in @('d4r/d4r_debug_launcher.exe','windows-game.ps1','d4r/native/kernel1.hsaco','d4r/nvapi/nvapi64.dll','d4r/zluda/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
|
||||
@@ -141,6 +155,29 @@ try { Invoke-Package $prefix (Join-Path $OutputDirectory 'release-mixed') } catc
|
||||
if (!$rejected) { throw 'A gfx1100 code object in the gfx1101 input was not rejected.' }
|
||||
$results+=@{case='mixed-target-rejected'; passed=$true}
|
||||
|
||||
# 2b. NVAPI selection inputs: an NVAPI built for another GPU, one without a target marker (built before the
|
||||
# marker existed) and a missing one are rejected; the d4r 0.1.5 input shape (the gfx1101 copy shared beside
|
||||
# nvcuda.dll in every input) packages without that copy, and every target still gets its own build.
|
||||
$cases=[ordered]@{
|
||||
'wrong-target'=@{mutate={ param($root,$arch) if ($arch -eq 'gfx1151') { Write-Nvapi (Join-Path $root 'd4r/nvapi/nvapi64.dll') 'gfx1101' } }; expect='built for gfx1101, not gfx1151'}
|
||||
'no-marker'=@{mutate={ param($root,$arch) if ($arch -eq 'gfx1102') { Set-Content -LiteralPath (Join-Path $root 'd4r/nvapi/nvapi64.dll') 'legacy nvapi' -Encoding ASCII } }; expect='does not name exactly one GPU target'}
|
||||
'missing'=@{mutate={ param($root,$arch) if ($arch -eq 'gfx1200') { Remove-Item -LiteralPath (Join-Path $root 'd4r/nvapi/nvapi64.dll') } }; expect='No d4r/nvapi/nvapi64.dll in the gfx1200 input'}
|
||||
}
|
||||
foreach ($case in $cases.GetEnumerator()) {
|
||||
$prefix=Join-Path $OutputDirectory "inputs/nvapi-$($case.Key)/game"
|
||||
New-Inputs $prefix $case.Value.mutate
|
||||
$rejected=$false
|
||||
try { Invoke-Package $prefix (Join-Path $OutputDirectory "release-nvapi-$($case.Key)") } catch { $rejected=$_.Exception.Message -like "*$($case.Value.expect)*" }
|
||||
if (!$rejected) { throw "NVAPI case '$($case.Key)' was not rejected with '$($case.Value.expect)'." }
|
||||
}
|
||||
$prefix=Join-Path $OutputDirectory 'inputs/shared-nvapi/game'
|
||||
New-Inputs $prefix { param($root,$arch) Write-Nvapi (Join-Path $root 'd4r/zluda/nvapi64.dll') 'gfx1101' }
|
||||
$sharedRelease=Join-Path $OutputDirectory 'release-shared-nvapi'
|
||||
Invoke-Package $prefix $sharedRelease
|
||||
if (Test-Path -LiteralPath (Join-Path $sharedRelease 'd4r/zluda/nvapi64.dll')) { throw 'The shared gfx1101 NVAPI shipped.' }
|
||||
foreach ($target in $targets) { Assert-D4RNvapiTarget (Join-Path $sharedRelease "d4r/nvapi/$($target.architecture)/nvapi64.dll") $target.architecture }
|
||||
$results+=@{case='nvapi-per-target'; passed=$true; targets=$targets.Count}
|
||||
|
||||
# 3. Shared runtime files must be identical across targets.
|
||||
$prefix=Join-Path $OutputDirectory 'inputs/runtime/game'
|
||||
New-Inputs $prefix { param($root,$arch) if ($arch -eq 'gfx1200') { Set-Content -LiteralPath (Join-Path $root 'd4r/zluda/nvcuda.dll') 'other zluda' -Encoding ASCII } }
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
// Keeping every file in one folder preserves NGX's search for
|
||||
// nvngx_dlss.dll beside the calling module.
|
||||
#include "d3d12_external.h"
|
||||
#include "gpu_environment.h"
|
||||
#include "ngx_public_abi.h"
|
||||
#include <dxgi.h>
|
||||
#include <fstream>
|
||||
@@ -54,22 +55,25 @@ HMODULE select(const LUID* luid) {
|
||||
d4r::diag::HipApi hip((dir / L"hip").string());
|
||||
if (hip.hipInit(0) != hipSuccess) throw std::runtime_error("the AMD HIP runtime found no usable GPU; update the AMD graphics driver");
|
||||
int count = 0; (void)hip.hipGetDeviceCount(&count);
|
||||
std::string found, detected;
|
||||
std::vector<d4r::diag::GpuCandidate> devices;
|
||||
for (int i = 0; i < count; ++i) {
|
||||
hipDeviceProp_t props{};
|
||||
if (hip.hipGetDeviceProperties(&props, i) != hipSuccess) continue;
|
||||
const auto arch = d4r::diag::gpu_architecture(props.gcnArchName);
|
||||
detected += (detected.empty() ? "" : ", ") + arch + " (" + props.name + ")";
|
||||
if (luid && std::memcmp(props.luid, luid, sizeof(*luid))) continue;
|
||||
if (!luid && !std::filesystem::exists(dir / (L"_nvngx_" + d4r::diag::wide(arch) + L".dll"))) continue;
|
||||
if (!found.empty() && found != arch) throw std::runtime_error("several GPUs with different architectures; start the game once DLSS selects one");
|
||||
found = arch;
|
||||
d4r::diag::GpuCandidate device{props.name, d4r::diag::gpu_architecture(props.gcnArchName), {}};
|
||||
std::memcpy(device.luid.data(), props.luid, device.luid.size());
|
||||
devices.push_back(device);
|
||||
}
|
||||
if (found.empty()) throw std::runtime_error("no supported AMD GPU for this game's adapter. Detected: " +
|
||||
(detected.empty() ? std::string("none") : detected) + ". This release supports: " + supported_list(dir) + ".");
|
||||
d4r::diag::AdapterLuid wanted{};
|
||||
if (luid) std::memcpy(wanted.data(), luid, wanted.size());
|
||||
const auto choice = d4r::diag::choose_loader_architecture(devices, luid ? &wanted : nullptr,
|
||||
[&](const std::string& arch) { return std::filesystem::exists(dir / (L"_nvngx_" + d4r::diag::wide(arch) + L".dll")); },
|
||||
supported_list(dir));
|
||||
if (!choice.error.empty()) throw std::runtime_error(choice.error);
|
||||
const auto& found = choice.architecture;
|
||||
const auto path = dir / (L"_nvngx_" + d4r::diag::wide(found) + L".dll");
|
||||
if (!std::filesystem::exists(path))
|
||||
throw std::runtime_error("this release has no build for your GPU (" + found + "). It supports: " + supported_list(dir) + ".");
|
||||
// The shim, NVAPI and the NGX core start from the environment: state the adapter's real architecture (and,
|
||||
// once the game's adapter is known, its LUID) before the first of them loads, replacing any stale value.
|
||||
d4r::diag::publish_selected_gpu(found, luid ? &wanted : nullptr);
|
||||
shim = LoadLibraryExW(path.c_str(), nullptr, LOAD_LIBRARY_SEARCH_DLL_LOAD_DIR | LOAD_LIBRARY_SEARCH_DEFAULT_DIRS);
|
||||
if (!shim) {
|
||||
selectionFailed = true; // A broken install will not fix itself during this session.
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
#pragma once
|
||||
// Publishes the GPU the game renders on to the rest of the process. The NVAPI
|
||||
// compatibility DLL, the NGX core's CUDA initialization and any later shim
|
||||
// start from the environment, so the loader and the shim state the adapter's
|
||||
// real architecture and LUID before they load them.
|
||||
#include "gpu_selection.h"
|
||||
#include <windows.h>
|
||||
#include <cstdlib>
|
||||
|
||||
namespace d4r::diag {
|
||||
// Updates the OS block (other DLLs, ZLUDA) and the CRT copy that getenv reads.
|
||||
inline void set_process_variable(const char* name, const std::string& value) {
|
||||
SetEnvironmentVariableA(name, value.c_str());
|
||||
_putenv_s(name, value.c_str());
|
||||
}
|
||||
inline void publish_selected_gpu(const std::string& architecture, const AdapterLuid* luid = nullptr) {
|
||||
set_process_variable("D4R_GPU_ARCH", architecture);
|
||||
if (luid) set_process_variable("D4R_GPU_LUID", format_luid(*luid));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,121 @@
|
||||
#pragma once
|
||||
// GPU-architecture selection rules shared by the loader, the NGX shim and the
|
||||
// NVAPI compatibility DLL. Pure logic: no HIP, no Windows headers, so the same
|
||||
// code is unit-tested without a GPU (see player_config_probe.cpp).
|
||||
//
|
||||
// One physical adapter decides everything: its HIP architecture picks the
|
||||
// _nvngx_<arch>.dll shim, the nvapi\<arch>\nvapi64.dll and the native kernels,
|
||||
// and its LUID picks the device when several GPUs share an architecture.
|
||||
#include "gpu_target.h"
|
||||
#include <array>
|
||||
#include <cstdio>
|
||||
#include <functional>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace d4r::diag {
|
||||
using AdapterLuid = std::array<unsigned char, 8>; // Memory layout of LUID: LowPart, then HighPart.
|
||||
|
||||
struct GpuCandidate { std::string name, architecture; AdapterLuid luid{}; };
|
||||
|
||||
inline std::string supported_gpu_list() {
|
||||
std::string list;
|
||||
for (const auto& target : gpu_targets) list += (list.empty() ? "" : ", ") + std::string(target.architecture);
|
||||
return list;
|
||||
}
|
||||
inline std::string describe_gpus(const std::vector<GpuCandidate>& devices) {
|
||||
std::string text;
|
||||
for (const auto& device : devices) text += (text.empty() ? "" : ", ") + device.architecture + " (" + device.name + ")";
|
||||
return text.empty() ? "none" : text;
|
||||
}
|
||||
|
||||
// "HHHHHHHH:LLLLLLLL", the form the logs already use for adapter LUIDs.
|
||||
inline std::string format_luid(const AdapterLuid& luid) {
|
||||
unsigned low = 0, high = 0;
|
||||
for (int i = 0; i < 4; ++i) { low |= unsigned(luid[i]) << (8 * i); high |= unsigned(luid[4 + i]) << (8 * i); }
|
||||
char text[32]; std::snprintf(text, sizeof(text), "%08x:%08x", high, low);
|
||||
return text;
|
||||
}
|
||||
inline bool parse_luid(const char* text, AdapterLuid& luid) {
|
||||
const std::string value = text ? text : "";
|
||||
if (value.size() != 17 || value[8] != ':' || value.find_first_not_of("0123456789abcdefABCDEF:") != std::string::npos ||
|
||||
value.find(':', 9) != std::string::npos) return false;
|
||||
const unsigned high = std::stoul(value.substr(0, 8), nullptr, 16), low = std::stoul(value.substr(9), nullptr, 16);
|
||||
for (int i = 0; i < 4; ++i) { luid[i] = static_cast<unsigned char>(low >> (8 * i)); luid[4 + i] = static_cast<unsigned char>(high >> (8 * i)); }
|
||||
return true;
|
||||
}
|
||||
|
||||
// D4R_GPU_ARCH, when valid, names the architecture this process runs for; the
|
||||
// build's own target applies when it is absent. A set but unsupported value is
|
||||
// reported and ignored: it must not silently pick a different GPU target.
|
||||
struct TargetResolution { std::string architecture, source, warning; };
|
||||
inline TargetResolution resolve_target_arch(const char* configured, const char* built_in) {
|
||||
TargetResolution result{built_in, "build", {}};
|
||||
if (!configured || !*configured) return result;
|
||||
const std::string arch = gpu_architecture(configured); // "gfx1101:xnack-" -> "gfx1101"
|
||||
if (supported_gpu_target(arch)) { result.architecture = arch; result.source = "D4R_GPU_ARCH"; return result; }
|
||||
result.warning = std::string("D4R_GPU_ARCH='") + configured + "' is not a supported target (supported: " +
|
||||
supported_gpu_list() + "); using the build's target " + built_in;
|
||||
return result;
|
||||
}
|
||||
|
||||
inline int find_gpu_device(const std::vector<GpuCandidate>& devices, const std::string& target, int requested = -1,
|
||||
const AdapterLuid* luid = nullptr) {
|
||||
for (size_t i = 0; i < devices.size(); ++i)
|
||||
if (devices[i].architecture == target && (requested < 0 || requested == int(i)) && (!luid || devices[i].luid == *luid))
|
||||
return int(i);
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Why no device matched, in terms a player can act on: unsupported hardware,
|
||||
// a file built for another GPU, or an adapter HIP cannot see.
|
||||
inline std::string no_device_message(const std::vector<GpuCandidate>& devices, const std::string& target,
|
||||
const AdapterLuid* luid = nullptr) {
|
||||
const std::string prefix = "No selected " + target + " device for this adapter. ";
|
||||
if (devices.empty()) return prefix + "The AMD HIP runtime reports no GPU; update the AMD graphics driver.";
|
||||
const GpuCandidate* actual = nullptr;
|
||||
if (luid) {
|
||||
for (const auto& device : devices) if (device.luid == *luid) actual = &device;
|
||||
if (!actual) return prefix + "No HIP device has the game's adapter LUID " + format_luid(*luid) + ". HIP sees: " + describe_gpus(devices) + ".";
|
||||
} else {
|
||||
for (const auto& device : devices) if (supported_gpu_target(device.architecture)) { actual = &device; break; }
|
||||
}
|
||||
if (!actual) return prefix + "Detected: " + describe_gpus(devices) + ", none supported by d4r (supported: " + supported_gpu_list() + ").";
|
||||
if (!supported_gpu_target(actual->architecture))
|
||||
return prefix + "The GPU is " + actual->architecture + " (" + actual->name + "), which d4r does not support (supported: " + supported_gpu_list() + ").";
|
||||
return prefix + "The GPU is " + actual->architecture + " (" + actual->name + ") but this file is built for " + target +
|
||||
". A file for another GPU was loaded (nvapi\\" + actual->architecture + "\\nvapi64.dll, _nvngx_" + actual->architecture +
|
||||
".dll, or a stale D4R_GPU_ARCH); extract the d4r ZIP again.";
|
||||
}
|
||||
|
||||
// An NVAPI (or any per-target file) that names another target than the selected GPU's is a mixed install.
|
||||
inline std::string nvapi_target_error(const std::string& built, const std::string& wanted, const std::string& path) {
|
||||
if (built == wanted) return {};
|
||||
return "nvapi64.dll (" + path + ") is built for " + (built.empty() ? std::string("an unknown target") : built) +
|
||||
" but the selected GPU is " + wanted + "; use d4r\\nvapi\\" + wanted + "\\nvapi64.dll and extract the d4r ZIP again if files are mixed up";
|
||||
}
|
||||
|
||||
// Loader: the HIP architecture of the game's adapter (matched by LUID), or,
|
||||
// before any adapter is known, the only architecture that has a build.
|
||||
struct LoaderChoice { std::string architecture, error; };
|
||||
inline LoaderChoice choose_loader_architecture(const std::vector<GpuCandidate>& devices, const AdapterLuid* luid,
|
||||
const std::function<bool(const std::string&)>& has_build, const std::string& available) {
|
||||
LoaderChoice choice;
|
||||
for (const auto& device : devices) {
|
||||
if (luid && device.luid != *luid) continue;
|
||||
if (!luid && !has_build(device.architecture)) continue;
|
||||
if (!choice.architecture.empty() && choice.architecture != device.architecture)
|
||||
return {{}, "several GPUs with different architectures; start the game once DLSS selects one"};
|
||||
choice.architecture = device.architecture;
|
||||
}
|
||||
if (choice.architecture.empty())
|
||||
choice.error = "no supported AMD GPU for this game's adapter. Detected: " + describe_gpus(devices) +
|
||||
". This release supports: " + available + ".";
|
||||
else if (!supported_gpu_target(choice.architecture))
|
||||
choice.error = "this GPU (" + choice.architecture + ") is not supported by d4r. This release supports: " + available + ".";
|
||||
else if (!has_build(choice.architecture))
|
||||
choice.error = "this release has no build for your GPU (" + choice.architecture + "). It supports: " + available + ".";
|
||||
if (!choice.error.empty()) choice.architecture.clear();
|
||||
return choice;
|
||||
}
|
||||
}
|
||||
+30
-16
@@ -1,6 +1,6 @@
|
||||
#pragma once
|
||||
#include "diagnostic.h"
|
||||
#include "gpu_target.h"
|
||||
#include "gpu_selection.h"
|
||||
#include <hip/hip_runtime_api.h>
|
||||
#include <cstdlib>
|
||||
#include <string>
|
||||
@@ -43,14 +43,16 @@ struct HipApi {
|
||||
if (verbose || result != hipSuccess) std::printf("HIP %s -> %d (%s)\n", call, static_cast<int>(result), hipGetErrorName(result));
|
||||
if (result != hipSuccess) throw std::runtime_error(std::string(call) + ": " + hipGetErrorString(result));
|
||||
}
|
||||
// The package's backend sets D4R_GPU_ARCH to the GPU type of the game's adapter before it loads this DLL, so one
|
||||
// build serves every supported type (a compile-time-only target locked RDNA3 / gfx1201 cards out: "No selected
|
||||
// gfx1200 device"). Unknown or missing values fall back to the build's own target.
|
||||
// The architecture this process runs for: D4R_GPU_ARCH when it names a supported target, otherwise the build's
|
||||
// own D4R_TARGET_ARCH. The loader and the shim publish the adapter's real architecture there before the
|
||||
// NVAPI/NGX components start (publish_selected_gpu in gpu_environment.h), so every component agrees. A value that
|
||||
// is set but unsupported is reported and ignored; select_architecture still refuses any device that is not of
|
||||
// the architecture in use.
|
||||
static const char* target_arch() {
|
||||
static const std::string arch = [] {
|
||||
const char* e = std::getenv("D4R_GPU_ARCH");
|
||||
const std::string v = e ? gpu_architecture(e) : std::string(); // "gfx1101:xnack-" -> "gfx1101"
|
||||
return supported_gpu_target(v) ? v : std::string(D4R_TARGET_ARCH);
|
||||
const auto resolved = resolve_target_arch(std::getenv("D4R_GPU_ARCH"), D4R_TARGET_ARCH);
|
||||
if (!resolved.warning.empty()) std::fprintf(stderr, "d4r: %s\n", resolved.warning.c_str());
|
||||
return resolved.architecture;
|
||||
}();
|
||||
return arch.c_str();
|
||||
}
|
||||
@@ -59,6 +61,14 @@ struct HipApi {
|
||||
const char* e = std::getenv("D4R_HIP_DEVICE");
|
||||
return e && *e ? std::atoi(e) : -1;
|
||||
}
|
||||
// The adapter the loader or shim selected (D4R_GPU_LUID), for components that start before any D3D12 device is
|
||||
// at hand. Absent means "any device of the target architecture"; a malformed value is an error, never ignored.
|
||||
static bool configured_luid(AdapterLuid& luid) {
|
||||
const char* text = std::getenv("D4R_GPU_LUID");
|
||||
if (!text || !*text) return false;
|
||||
if (!parse_luid(text, luid)) throw std::runtime_error(std::string("D4R_GPU_LUID='") + text + "' is not HHHHHHHH:LLLLLLLL");
|
||||
return true;
|
||||
}
|
||||
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.
|
||||
@@ -71,24 +81,28 @@ struct HipApi {
|
||||
std::printf("HIP runtime=%d driver_api=%d headers=%d.%d.%d sizeof(properties)=%zu\n",
|
||||
version, driver, HIP_VERSION_MAJOR, HIP_VERSION_MINOR, HIP_VERSION_PATCH, sizeof(hipDeviceProp_t));
|
||||
check(hipGetDeviceCount(&count), "hipGetDeviceCount");
|
||||
int found = -1;
|
||||
std::vector<GpuCandidate> devices;
|
||||
requested = requested_device(requested);
|
||||
AdapterLuid wanted{};
|
||||
const AdapterLuid* luid = nullptr;
|
||||
if (required_luid) { std::memcpy(wanted.data(), required_luid, wanted.size()); luid = &wanted; }
|
||||
else if (configured_luid(wanted)) luid = &wanted;
|
||||
std::vector<hipDeviceProp_t> properties;
|
||||
for (int i = 0; i < count; ++i) {
|
||||
hipDeviceProp_t props{};
|
||||
check(hipGetDeviceProperties(&props, i), "hipGetDevicePropertiesR0600");
|
||||
std::printf("GPU ordinal=%d name=%s gcnArchName=%s wave=%d vram=%zu pci=%04x:%02x:%02x\n",
|
||||
i, props.name, props.gcnArchName, props.warpSize, props.totalGlobalMem,
|
||||
props.pciDomainID, props.pciBusID, props.pciDeviceID);
|
||||
const std::string arch = props.gcnArchName;
|
||||
if (gpu_architecture(arch) == target_arch() && (requested < 0 || requested == i) && found < 0 &&
|
||||
(!required_luid || std::memcmp(props.luid, required_luid, sizeof(props.luid)) == 0)) {
|
||||
found = i;
|
||||
selected = props;
|
||||
}
|
||||
GpuCandidate candidate{props.name, gpu_architecture(props.gcnArchName), {}};
|
||||
std::memcpy(candidate.luid.data(), props.luid, candidate.luid.size());
|
||||
devices.push_back(candidate);
|
||||
properties.push_back(props);
|
||||
}
|
||||
const int found = find_gpu_device(devices, target_arch(), requested, luid);
|
||||
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)");
|
||||
if (found < 0) throw std::runtime_error(no_device_message(devices, target_arch(), luid));
|
||||
selected = properties[found];
|
||||
require_gpu_target(selected.gcnArchName, target_arch());
|
||||
check(hipSetDevice(found), "hipSetDevice");
|
||||
std::printf("SELECTED HIP ordinal=%d architecture=%s validation=%s\n", found, target_arch(),
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#pragma once
|
||||
#include "d3d12_external.h"
|
||||
#include "gpu_environment.h"
|
||||
#include "d3d12_boundary_timing.h"
|
||||
#include "pixel_conversion.h"
|
||||
#include "cuda_image_api.h"
|
||||
@@ -23,6 +24,19 @@ inline std::string env_path(const char* name) {
|
||||
if (!std::filesystem::path(diag::wide(value)).is_absolute()) throw std::runtime_error(std::string(name) + " must be absolute");
|
||||
return value;
|
||||
}
|
||||
// The NVAPI compatibility DLL names the GPU target it was built for. A file for another GPU (a mixed install, or
|
||||
// a shared copy from an older release) is refused here with the fix spelled out; older builds without the export
|
||||
// are accepted and still validate the device themselves.
|
||||
inline void require_nvapi_target(const diag::Library& nvapi, const std::string& path) {
|
||||
using Target = const char*(__cdecl*)();
|
||||
Target target = nullptr;
|
||||
try { target = nvapi.symbol<Target>("d4r_NvapiTargetArchitecture"); }
|
||||
catch (const std::exception&) { std::printf("D4R_NVAPI target=unknown (no d4r_NvapiTargetArchitecture export) path=%s\n", path.c_str()); return; }
|
||||
const std::string built = target ? target() : "";
|
||||
const std::string wanted = diag::HipApi::target_arch();
|
||||
std::printf("D4R_NVAPI target=%s wanted=%s path=%s\n", built.c_str(), wanted.c_str(), path.c_str());
|
||||
if (const auto error = diag::nvapi_target_error(built, wanted, path); !error.empty()) throw std::runtime_error(error);
|
||||
}
|
||||
inline void ngx_check(unsigned result, const char* operation) {
|
||||
if (result != 1 || !std::getenv("D4R_QUIET_API")) std::printf("D4R_NGX %s result=0x%08x\n", operation, result);
|
||||
if (result != 1) throw std::runtime_error(std::string(operation) + " NGX=" + std::to_string(result));
|
||||
@@ -105,6 +119,9 @@ struct Runtime {
|
||||
hip.select_architecture(-1, props, &luid);
|
||||
hip.verbose = cuda.verbose = !std::getenv("D4R_QUIET_API");
|
||||
if (std::memcmp(&luid, props.luid, sizeof(luid))) throw std::runtime_error("D3D12/HIP LUID mismatch");
|
||||
// NVAPI and the NGX core start from the environment: tell them which adapter and architecture this is.
|
||||
diag::AdapterLuid selectedLuid{}; std::memcpy(selectedLuid.data(), props.luid, selectedLuid.size());
|
||||
diag::publish_selected_gpu(diag::HipApi::target_arch(), &selectedLuid);
|
||||
cuda.check(cuda.cuInit(0), "cuInit(Windows runtime)");
|
||||
int count = 0; cuda.check(cuda.cuDeviceGetCount(&count), "cuDeviceGetCount");
|
||||
for (int i = 0; i < count; ++i) {
|
||||
@@ -124,6 +141,7 @@ struct Runtime {
|
||||
// NGX's loader can probe CUDA during DLL initialization: establish
|
||||
// the correct primary context before loading either NVIDIA DLL.
|
||||
nvapi = std::make_unique<diag::Library>(diag::wide(env_path("D4R_NVAPI_DLL")));
|
||||
require_nvapi_target(*nvapi, env_path("D4R_NVAPI_DLL"));
|
||||
dlss = std::make_unique<diag::Library>(diag::wide(env_path("D4R_DLSS_DLL")));
|
||||
core = std::make_unique<diag::Library>(diag::wide(env_path("D4R_NGX_CORE")));
|
||||
allocate = core->symbol<Allocate>("NVSDK_NGX_CUDA_AllocateParameters");
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
#include "diagnostic.h"
|
||||
#include "gpu_target.h"
|
||||
#if defined(D4R_NVAPI_COMPAT)
|
||||
#include "hip_api.h"
|
||||
#include <memory>
|
||||
@@ -83,6 +84,14 @@ int __cdecl logical_info(void* logical, LogicalGpuData* info)
|
||||
}
|
||||
#endif
|
||||
|
||||
// The GPU target this DLL was built for. The marker string is also searched for by the packaging scripts, so a
|
||||
// release cannot ship (and a shim cannot load) an NVAPI built for another GPU.
|
||||
static const char target_marker[] = "D4R_NVAPI_TARGET=" D4R_TARGET_ARCH;
|
||||
extern "C" __declspec(dllexport) const char* __cdecl d4r_NvapiTargetArchitecture()
|
||||
{
|
||||
return target_marker + sizeof("D4R_NVAPI_TARGET=") - 1;
|
||||
}
|
||||
|
||||
// Trace build forwards responses unchanged. The compatibility build adds the
|
||||
// public topology APIs above; other queries still go to upstream ZLUDA NVAPI.
|
||||
extern "C" __declspec(dllexport) void* __cdecl nvapi_QueryInterface(unsigned id)
|
||||
|
||||
@@ -272,11 +272,39 @@ inline void verify_native_fp8_bundle(const std::filesystem::path& dir, const Set
|
||||
contents.find("tokenLanes=true\n") == std::string::npos)
|
||||
throw std::runtime_error("[Kernels] NativeFp8=true needs matching gfx12 token-lane M kernels; rebuild the package with -TokenLanes and restart.");
|
||||
}
|
||||
inline std::filesystem::path compat_nvapi_path(const std::filesystem::path& dir) {
|
||||
const auto colocated = dir / L"zluda" / L"nvapi64.dll";
|
||||
if (std::filesystem::is_regular_file(colocated)) return colocated;
|
||||
const auto arch = dir / L"nvapi" / diag::wide(diag::HipApi::target_arch()) / L"nvapi64.dll";
|
||||
return std::filesystem::is_regular_file(arch) ? arch : dir / L"nvapi" / L"nvapi64.dll";
|
||||
// The compatibility NVAPI is built for one GPU target, so it is never shared between targets. A release carries
|
||||
// one per target in nvapi\<arch>\; that copy is the only acceptable one for <arch>. A single-target layout (quick
|
||||
// test, game package) has just nvapi\nvapi64.dll or the copy beside nvcuda.dll, and the shim then checks the
|
||||
// target the file names. In a multi-target folder a missing copy is an error: falling back to a shared DLL is
|
||||
// how a gfx1101 NVAPI once served a gfx1151 GPU ("No selected gfx1101 device").
|
||||
struct NvapiChoice { std::filesystem::path path; std::string error; };
|
||||
inline NvapiChoice select_compat_nvapi(const std::filesystem::path& dir, const std::string& arch) {
|
||||
const auto file = [&](const std::string& target) { return dir / L"nvapi" / diag::wide(target) / L"nvapi64.dll"; };
|
||||
if (std::filesystem::is_regular_file(file(arch))) return {file(arch), {}};
|
||||
std::string others;
|
||||
for (const auto& target : diag::gpu_targets)
|
||||
if (std::filesystem::is_regular_file(file(target.architecture)))
|
||||
others += (others.empty() ? "" : ", ") + std::string(target.architecture);
|
||||
if (!others.empty())
|
||||
return {{}, "this release has no NVAPI build for " + arch + " (d4r\\nvapi\\" + arch + "\\nvapi64.dll is missing; it has " +
|
||||
others + "); extract the d4r ZIP again"};
|
||||
for (const auto& single : {dir / L"nvapi" / L"nvapi64.dll", dir / L"zluda" / L"nvapi64.dll"})
|
||||
if (std::filesystem::is_regular_file(single)) return {single, {}};
|
||||
return {{}, "nvapi64.dll is missing (expected d4r\\nvapi\\" + arch + "\\nvapi64.dll); extract the d4r ZIP again"};
|
||||
}
|
||||
inline std::filesystem::path compat_nvapi_path(const std::filesystem::path& dir, const std::string& arch = diag::HipApi::target_arch()) {
|
||||
const auto choice = select_compat_nvapi(dir, arch);
|
||||
if (!choice.error.empty()) throw std::runtime_error(choice.error);
|
||||
return choice.path;
|
||||
}
|
||||
// A shared nvapi64.dll beside nvcuda.dll (d4r 0.1.5 shipped the gfx1101 build there for every GPU) is on PATH and
|
||||
// could be loaded by name ahead of the right file; it is ignored here, so say so.
|
||||
inline void note_stale_shared_nvapi(const std::filesystem::path& dir, std::vector<std::string>& notes) {
|
||||
bool perTarget = false;
|
||||
for (const auto& target : diag::gpu_targets)
|
||||
perTarget = perTarget || std::filesystem::is_regular_file(dir / L"nvapi" / diag::wide(target.architecture) / L"nvapi64.dll");
|
||||
if (perTarget && std::filesystem::is_regular_file(dir / L"zluda" / L"nvapi64.dll"))
|
||||
notes.push_back("d4r\\zluda\\nvapi64.dll is left over from an older d4r release (one GPU's build shared by all); it is ignored. Delete it");
|
||||
}
|
||||
inline std::filesystem::path raw_nvapi_backend_path(const std::filesystem::path& dir) {
|
||||
return dir / L"nvapi" / L"backend" / L"nvapi64.dll";
|
||||
@@ -325,7 +353,10 @@ inline void apply(const std::filesystem::path& dir, const Settings& settings) {
|
||||
const auto p = [&](const std::wstring& relative) { return std::optional<std::wstring>((dir / relative).wstring()); };
|
||||
const std::optional<std::wstring> on = std::wstring(L"1"), off = std::wstring(L"0"), unset;
|
||||
const wchar_t* path = _wgetenv(L"PATH");
|
||||
const auto compatNvapi = compat_nvapi_path(dir);
|
||||
const std::string arch = diag::HipApi::target_arch();
|
||||
// Startup has already verified the file (verify_nvapi); a missing one leaves D4R_NVAPI_DLL unset so a stale
|
||||
// value from an earlier run cannot point at a DLL for another GPU.
|
||||
const auto nvapi = select_compat_nvapi(dir, arch);
|
||||
// Same codegen and paths as windows-game.ps1; only diagnostics differ.
|
||||
const std::pair<const wchar_t*, std::optional<std::wstring>> values[] = {
|
||||
{L"D4R_RR_BACKEND", settings.rrBackend == "off" ? unset : std::optional<std::wstring>(diag::wide(settings.rrBackend))},
|
||||
@@ -339,7 +370,8 @@ inline void apply(const std::filesystem::path& dir, const Settings& settings) {
|
||||
{L"D4R_FSR_CACHE_DIR", settings.rrBackend == "fsr" ? std::optional<std::wstring>((cache / L"vkd3d").wstring()) : unset},
|
||||
{L"D4R_HIP_ROOT", p(L"hip")}, {L"HIP_PATH", p(L"hip")},
|
||||
{L"D4R_NVCUDA_DLL", p(L"zluda\\nvcuda.dll")}, {L"ZLUDA_CUDA_LIB", p(L"zluda\\nvcuda.dll")},
|
||||
{L"D4R_NVAPI_DLL", std::optional<std::wstring>(compatNvapi.wstring())},
|
||||
{L"D4R_GPU_ARCH", std::optional<std::wstring>(diag::wide(arch))},
|
||||
{L"D4R_NVAPI_DLL", nvapi.error.empty() ? std::optional<std::wstring>(nvapi.path.wstring()) : unset},
|
||||
{L"D4R_NVAPI_BACKEND", std::optional<std::wstring>(raw_nvapi_backend_path(dir).wstring())},
|
||||
{L"D4R_NGX_CORE", p(L"ngx\\_nvngx.dll")}, {L"D4R_DLSS_DLL", p(L"dlss\\nvngx_dlss.dll")},
|
||||
{L"D4R_NGX_DATA_DIR", std::optional<std::wstring>(ngxData.wstring())},
|
||||
@@ -392,6 +424,8 @@ inline void ensure_environment() {
|
||||
reject_translation_layer();
|
||||
if (settings.nativeFp8) verify_native_fp8_bundle(dir, &settings);
|
||||
verify_code_objects(dir, &settings);
|
||||
compat_nvapi_path(dir); // Throws with the fix spelled out when this GPU has no NVAPI build.
|
||||
note_stale_shared_nvapi(dir, current.notes);
|
||||
apply(dir, settings);
|
||||
current.active = true; current.preset = settings.preset;
|
||||
const char* model = settings.preset == 5 ? "E" : settings.preset == 12 ? "L" : settings.preset == 13 ? "M" : "K";
|
||||
|
||||
@@ -21,10 +21,194 @@ std::string error_of(const std::function<void()>& fn) {
|
||||
try { fn(); } catch (const std::exception& error) { return error.what(); }
|
||||
return {};
|
||||
}
|
||||
|
||||
using d4r::diag::AdapterLuid;
|
||||
using d4r::diag::GpuCandidate;
|
||||
AdapterLuid luid_of(unsigned low, unsigned high = 0) {
|
||||
AdapterLuid luid{};
|
||||
for (int i = 0; i < 4; ++i) { luid[i] = static_cast<unsigned char>(low >> (8 * i)); luid[4 + i] = static_cast<unsigned char>(high >> (8 * i)); }
|
||||
return luid;
|
||||
}
|
||||
void write_file(const std::filesystem::path& path, const std::string& text) {
|
||||
std::filesystem::create_directories(path.parent_path());
|
||||
std::ofstream(path, std::ios::binary) << text;
|
||||
}
|
||||
bool contains(const std::string& text, const std::string& part) { return text.find(part) != std::string::npos; }
|
||||
|
||||
// Architecture selection rules that decide which NVAPI, shim and kernels a GPU gets. Pure logic, so every
|
||||
// registered target is covered here without the hardware; `builtNvapi` (optional) holds real nvapi64.dll
|
||||
// builds, one per target, to check the compiled-in target marker the same way the shim and packager do.
|
||||
void architecture_selection(const std::filesystem::path& root, const std::filesystem::path& builtNvapi) {
|
||||
namespace diag = d4r::diag;
|
||||
std::filesystem::remove_all(root);
|
||||
std::vector<std::string> archs;
|
||||
for (const auto& target : diag::gpu_targets) archs.push_back(target.architecture);
|
||||
expect(archs.size() == 11, "the registry lists the 11 Windows GPU targets");
|
||||
|
||||
// D4R_GPU_ARCH: valid, suffixed, missing, empty and unsupported values.
|
||||
for (const auto& arch : archs) {
|
||||
auto r = diag::resolve_target_arch(arch.c_str(), "gfx1101");
|
||||
expect(r.architecture == arch && r.source == "D4R_GPU_ARCH" && r.warning.empty(), "D4R_GPU_ARCH=" + arch + " selects " + arch);
|
||||
r = diag::resolve_target_arch((arch + ":xnack-").c_str(), "gfx1201");
|
||||
expect(r.architecture == arch, "feature-suffixed D4R_GPU_ARCH is normalized for " + arch);
|
||||
r = diag::resolve_target_arch(nullptr, arch.c_str());
|
||||
expect(r.architecture == arch && r.source == "build" && r.warning.empty(), "missing D4R_GPU_ARCH uses the build target " + arch);
|
||||
r = diag::resolve_target_arch("", arch.c_str());
|
||||
expect(r.architecture == arch && r.warning.empty(), "empty D4R_GPU_ARCH uses the build target " + arch);
|
||||
}
|
||||
for (const char* bad : {"gfx1030", "gfx9999", "GFX1101", "1101", "gfx1101 ", "rdna3"}) {
|
||||
const auto r = diag::resolve_target_arch(bad, "gfx1151");
|
||||
expect(r.architecture == "gfx1151" && r.source == "build" && contains(r.warning, std::string("'") + bad + "' is not a supported target"),
|
||||
std::string("unsupported D4R_GPU_ARCH='") + bad + "' is reported and the build target is kept");
|
||||
}
|
||||
|
||||
// LUID text used to hand the game's adapter to NVAPI.
|
||||
for (const auto luid : {luid_of(0), luid_of(0x1a2b3c4d, 0), luid_of(0xdeadbeef, 0x7), luid_of(0xffffffff, 0xffffffff)}) {
|
||||
AdapterLuid parsed{};
|
||||
expect(diag::parse_luid(diag::format_luid(luid).c_str(), parsed) && parsed == luid, "LUID round trip " + diag::format_luid(luid));
|
||||
}
|
||||
AdapterLuid scratch{};
|
||||
for (const char* bad : {"", "1234", "0000000000000000", "0000000:0000000a", "0000000g:00000001", "00000000:0000000100", "0x000000:00000001", "00000000:+0000001"})
|
||||
expect(!diag::parse_luid(bad, scratch), std::string("malformed LUID '") + bad + "' is rejected");
|
||||
|
||||
// Release layout: nvapi\<arch>\nvapi64.dll for all targets plus the 0.1.5-style shared copy beside nvcuda.
|
||||
const auto release = root / L"release";
|
||||
for (const auto& arch : archs) write_file(release / L"nvapi" / diag::wide(arch) / L"nvapi64.dll", "nvapi " + arch);
|
||||
write_file(release / L"zluda" / L"nvapi64.dll", "nvapi gfx1101 (shared)");
|
||||
write_file(release / L"nvapi" / L"backend" / L"nvapi64.dll", "raw backend");
|
||||
for (const auto& arch : archs) {
|
||||
const auto choice = player::select_compat_nvapi(release, arch);
|
||||
expect(choice.error.empty() && choice.path == release / L"nvapi" / diag::wide(arch) / L"nvapi64.dll" && player::read_text(choice.path) == "nvapi " + arch,
|
||||
"NVAPI for " + arch + " is its own nvapi\\" + arch + " build, not the shared gfx1101 copy");
|
||||
}
|
||||
std::vector<std::string> notes;
|
||||
player::note_stale_shared_nvapi(release, notes);
|
||||
expect(has_note(notes, "left over from an older d4r release"), "a leftover shared NVAPI is called out");
|
||||
|
||||
// A missing per-target build is an error that names the fix; no other file may stand in for it.
|
||||
for (const auto& arch : archs) {
|
||||
const auto folder = root / (L"missing-" + diag::wide(arch));
|
||||
std::filesystem::remove_all(folder);
|
||||
std::filesystem::copy(release, folder, std::filesystem::copy_options::recursive);
|
||||
std::filesystem::remove(folder / L"nvapi" / diag::wide(arch) / L"nvapi64.dll");
|
||||
const auto choice = player::select_compat_nvapi(folder, arch);
|
||||
expect(choice.path.empty() && contains(choice.error, "no NVAPI build for " + arch) && contains(choice.error, "nvapi\\" + arch + "\\nvapi64.dll"),
|
||||
"missing nvapi\\" + arch + " fails with a clear message instead of using another DLL");
|
||||
expect(!error_of([&] { player::compat_nvapi_path(folder, arch); }).empty(), "compat_nvapi_path(" + arch + ") throws when its build is missing");
|
||||
std::filesystem::remove_all(folder);
|
||||
}
|
||||
|
||||
// Single-target layouts (quick test, game package, development folders).
|
||||
const auto single = root / L"single";
|
||||
expect(contains(player::select_compat_nvapi(single, "gfx1151").error, "nvapi64.dll is missing"), "an empty folder has no NVAPI");
|
||||
write_file(single / L"zluda" / L"nvapi64.dll", "single zluda");
|
||||
expect(player::select_compat_nvapi(single, "gfx1151").path == single / L"zluda" / L"nvapi64.dll", "a single-target package may keep NVAPI beside nvcuda");
|
||||
write_file(single / L"nvapi" / L"nvapi64.dll", "single nvapi");
|
||||
expect(player::select_compat_nvapi(single, "gfx1151").path == single / L"nvapi" / L"nvapi64.dll", "nvapi\\nvapi64.dll outranks the copy beside nvcuda");
|
||||
notes.clear(); player::note_stale_shared_nvapi(single, notes);
|
||||
expect(notes.empty(), "single-target layouts are not flagged as stale");
|
||||
|
||||
// The environment the shim hands to ZLUDA, NVAPI and NGX.
|
||||
{ // target_arch() is fixed per process; the per-target matrix above covers the other architectures.
|
||||
const auto folder = root / L"apply";
|
||||
for (const auto& other : archs) write_file(folder / L"nvapi" / diag::wide(other) / L"nvapi64.dll", "nvapi " + other);
|
||||
auto s = settings("", notes); s.cacheDir = (folder / L"cache").wstring();
|
||||
player::apply(folder, s);
|
||||
const auto current = std::string(diag::HipApi::target_arch());
|
||||
expect(_wgetenv(L"D4R_GPU_ARCH") && std::wstring(_wgetenv(L"D4R_GPU_ARCH")) == diag::wide(current), "player environment publishes D4R_GPU_ARCH=" + current);
|
||||
expect(std::filesystem::path(_wgetenv(L"D4R_NVAPI_DLL")) == folder / L"nvapi" / diag::wide(current) / L"nvapi64.dll",
|
||||
"player environment points D4R_NVAPI_DLL at the build for " + current);
|
||||
std::filesystem::remove(folder / L"nvapi" / diag::wide(current) / L"nvapi64.dll");
|
||||
player::apply(folder, s);
|
||||
expect(!std::getenv("D4R_NVAPI_DLL"), "a missing build clears D4R_NVAPI_DLL instead of keeping a stale path");
|
||||
}
|
||||
|
||||
// Several GPUs: iGPU + two dGPUs of one architecture + every registered target on one machine.
|
||||
const std::vector<GpuCandidate> machine{{"AMD Radeon 780M", "gfx1103", luid_of(10)}, {"AMD Radeon RX 7700 XT", "gfx1101", luid_of(11)},
|
||||
{"AMD Radeon RX 7700 XT #2", "gfx1101", luid_of(12)}, {"Radeon 8060S", "gfx1151", luid_of(13)}};
|
||||
expect(diag::find_gpu_device(machine, "gfx1101") == 1, "without a LUID the first device of the architecture is used");
|
||||
const auto second = luid_of(12), igpu = luid_of(10), unknown = luid_of(99);
|
||||
expect(diag::find_gpu_device(machine, "gfx1101", -1, &second) == 2, "the LUID picks the second of two same-architecture GPUs");
|
||||
expect(diag::find_gpu_device(machine, "gfx1103", -1, &igpu) == 0, "the iGPU is found by LUID");
|
||||
expect(diag::find_gpu_device(machine, "gfx1101", -1, &igpu) == -1, "a LUID of another architecture does not match");
|
||||
expect(diag::find_gpu_device(machine, "gfx1101", 2) == 2 && diag::find_gpu_device(machine, "gfx1101", 3) == -1, "an explicit ordinal must also be of the target architecture");
|
||||
expect(diag::find_gpu_device(machine, "gfx1151", -1, &unknown) == -1, "an unknown adapter LUID matches nothing");
|
||||
std::vector<GpuCandidate> everything;
|
||||
for (size_t i = 0; i < archs.size(); ++i) everything.push_back({"GPU " + archs[i], archs[i], luid_of(100 + unsigned(i))});
|
||||
for (size_t i = 0; i < archs.size(); ++i) {
|
||||
const auto luid = luid_of(100 + unsigned(i));
|
||||
bool exact = diag::find_gpu_device(everything, archs[i], -1, &luid) == int(i) && diag::find_gpu_device(everything, archs[i]) == int(i);
|
||||
for (size_t j = 0; j < archs.size(); ++j) exact = exact && (j == i || diag::find_gpu_device(everything, archs[j], -1, &luid) == -1);
|
||||
expect(exact, "among all 11 architectures " + archs[i] + " resolves only to its own adapter");
|
||||
}
|
||||
|
||||
// Diagnostics for genuinely unsupported or mismatched GPUs (no crash, an actionable message).
|
||||
auto message = diag::no_device_message(machine, "gfx1101", &unknown);
|
||||
expect(contains(message, "No HIP device has the game's adapter LUID") && contains(message, "gfx1151 (Radeon 8060S)"), "unknown adapter: " + message);
|
||||
const auto strix = luid_of(13);
|
||||
message = diag::no_device_message(machine, "gfx1101", &strix);
|
||||
expect(contains(message, "The GPU is gfx1151") && contains(message, "built for gfx1101") && contains(message, "extract the d4r ZIP again"),
|
||||
"a gfx1101 file on a gfx1151 adapter is named as a mixed install: " + message);
|
||||
const std::vector<GpuCandidate> rdna2{{"AMD Radeon RX 6800", "gfx1030", luid_of(5)}};
|
||||
const auto six = luid_of(5);
|
||||
message = diag::no_device_message(rdna2, "gfx1101", &six);
|
||||
expect(contains(message, "gfx1030") && contains(message, "does not support") && contains(message, "gfx1154"), "unsupported RDNA2 GPU: " + message);
|
||||
message = diag::no_device_message(rdna2, "gfx1201");
|
||||
expect(contains(message, "none supported by d4r"), "no supported device at all: " + message);
|
||||
expect(contains(diag::no_device_message({}, "gfx1100"), "reports no GPU"), "an empty HIP device list asks for a driver update");
|
||||
|
||||
// Loader: pick the shim for the game's adapter, never a different GPU's.
|
||||
const auto has_all = [](const std::string& arch) { return diag::supported_gpu_target(arch); }; // A full release: one shim per target.
|
||||
for (size_t i = 0; i < archs.size(); ++i) {
|
||||
const auto luid = luid_of(100 + unsigned(i));
|
||||
const auto choice = diag::choose_loader_architecture(everything, &luid, has_all, "all");
|
||||
expect(choice.architecture == archs[i] && choice.error.empty(), "loader picks _nvngx_" + archs[i] + ".dll for that adapter");
|
||||
}
|
||||
auto choice = diag::choose_loader_architecture(machine, &igpu, has_all, "all");
|
||||
expect(choice.architecture == "gfx1103", "loader follows the iGPU when the game renders on it");
|
||||
choice = diag::choose_loader_architecture(machine, &second, has_all, "all");
|
||||
expect(choice.architecture == "gfx1101", "loader follows the game's dGPU among several GPUs");
|
||||
choice = diag::choose_loader_architecture(machine, nullptr, has_all, "all");
|
||||
expect(choice.architecture.empty() && contains(choice.error, "several GPUs with different architectures"), "before an adapter is known, several architectures wait for Init");
|
||||
choice = diag::choose_loader_architecture(machine, nullptr, [](const std::string& a) { return a == "gfx1151"; }, "gfx1151");
|
||||
expect(choice.architecture == "gfx1151", "before an adapter is known, the only architecture with a build is used");
|
||||
choice = diag::choose_loader_architecture(machine, &strix, [](const std::string& a) { return a == "gfx1101"; }, "gfx1101");
|
||||
expect(choice.architecture.empty() && contains(choice.error, "no build for your GPU (gfx1151)") && contains(choice.error, "supports: gfx1101"), "loader: " + choice.error);
|
||||
choice = diag::choose_loader_architecture(rdna2, &six, has_all, "all");
|
||||
expect(choice.architecture.empty() && contains(choice.error, "gfx1030") && contains(choice.error, "not supported by d4r"), "loader: " + choice.error);
|
||||
choice = diag::choose_loader_architecture(rdna2, nullptr, has_all, "all");
|
||||
expect(choice.architecture.empty() && contains(choice.error, "no supported AMD GPU") && contains(choice.error, "gfx1030"), "loader: " + choice.error);
|
||||
expect(diag::nvapi_target_error("gfx1151", "gfx1151", "p").empty() && contains(diag::nvapi_target_error("gfx1101", "gfx1151", "p"), "built for gfx1101 but the selected GPU is gfx1151"),
|
||||
"an NVAPI built for another target is refused");
|
||||
|
||||
// Real nvapi64.dll builds, one per target: the file in nvapi\<arch> names <arch> and only <arch>.
|
||||
if (builtNvapi.empty()) { std::printf("skip real NVAPI builds (no directory given)\n"); return; }
|
||||
for (const auto& arch : archs) {
|
||||
const auto layout = root / L"built";
|
||||
const auto source = builtNvapi / diag::wide(arch) / L"nvapi64.dll";
|
||||
std::filesystem::create_directories(layout / L"nvapi" / diag::wide(arch));
|
||||
std::filesystem::copy_file(source, layout / L"nvapi" / diag::wide(arch) / L"nvapi64.dll", std::filesystem::copy_options::overwrite_existing);
|
||||
}
|
||||
for (const auto& arch : archs) {
|
||||
const auto choice = player::select_compat_nvapi(root / L"built", arch);
|
||||
expect(choice.error.empty(), "built layout has nvapi\\" + arch);
|
||||
if (!choice.error.empty()) continue;
|
||||
const HMODULE module = LoadLibraryExW(choice.path.c_str(), nullptr, LOAD_WITH_ALTERED_SEARCH_PATH);
|
||||
auto target = module ? reinterpret_cast<const char*(__cdecl*)()>(reinterpret_cast<void*>(GetProcAddress(module, "d4r_NvapiTargetArchitecture"))) : nullptr;
|
||||
const std::string built = target ? target() : "";
|
||||
expect(built == arch, "nvapi\\" + arch + "\\nvapi64.dll names " + arch + " (reports '" + built + "')");
|
||||
const auto bytes = player::read_text(choice.path);
|
||||
std::string others;
|
||||
for (const auto& other : archs) if (other != arch && contains(bytes, "D4R_NVAPI_TARGET=" + other)) others += other + " ";
|
||||
expect(contains(bytes, "D4R_NVAPI_TARGET=" + arch + '\0') && others.empty(), "nvapi\\" + arch + " carries only its own marker for the packager (" + others + ")");
|
||||
expect(diag::nvapi_target_error(built, arch, "p").empty(), "the shim accepts nvapi\\" + arch + " for " + arch);
|
||||
if (module) FreeLibrary(module);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int wmain(int argc, wchar_t** argv) {
|
||||
if (argc != 2) { std::printf("usage: player_config_probe FIXTURE_DIR\n"); return 2; }
|
||||
if (argc != 2 && argc != 3) { std::printf("usage: player_config_probe FIXTURE_DIR [BUILT_NVAPI_DIR]\n"); return 2; }
|
||||
const std::filesystem::path dir(argv[1]);
|
||||
std::vector<std::string> notes;
|
||||
|
||||
@@ -122,9 +306,13 @@ int wmain(int argc, wchar_t** argv) {
|
||||
std::filesystem::create_directories(dir / L"zluda");
|
||||
const auto colocatedNvapi = dir / L"zluda" / L"nvapi64.dll";
|
||||
{ std::ofstream(colocatedNvapi, std::ios::binary) << "colocated compatibility NVAPI fixture"; }
|
||||
expect(player::compat_nvapi_path(dir) == colocatedNvapi, "player mode reuses NVAPI colocated with nvcuda");
|
||||
expect(player::compat_nvapi_path(dir) == archNvapi, "a shared NVAPI beside nvcuda never outranks the architecture-specific one");
|
||||
std::filesystem::remove(colocatedNvapi);
|
||||
std::filesystem::remove(archNvapi);
|
||||
std::filesystem::remove(dir / L"nvapi" / L"nvapi64.dll");
|
||||
architecture_selection(dir / L"architecture-fixture", argc > 2 ? std::filesystem::path(argv[2]) : std::filesystem::path());
|
||||
std::filesystem::create_directories(dir / L"nvapi");
|
||||
{ std::ofstream(dir / L"nvapi" / L"nvapi64.dll", std::ios::binary) << "generic nvapi fixture"; }
|
||||
expect(player::raw_nvapi_backend_path(dir) == dir / L"nvapi" / L"backend" / L"nvapi64.dll",
|
||||
"raw ZLUDA NVAPI backend has a separate explicit path");
|
||||
const auto archNative = dir / L"native" / d4r::diag::wide(d4r::diag::HipApi::target_arch());
|
||||
|
||||
Reference in new issue
Block a user