diff --git a/docs/windows-game.md b/docs/windows-game.md new file mode 100644 index 0000000..a51ea1f --- /dev/null +++ b/docs/windows-game.md @@ -0,0 +1,57 @@ +# Native Windows RDNA4 game development package + +Validated hardware: Windows 11 x64 / Radeon RX 9070 XT / gfx1201. Native K and +M transformer kernels pass standalone D3D12 + OptiScaler checks. Actual game +compatibility and performance are still being validated. This is a local +development package; no NVIDIA proprietary DLL is included. + +Build the shim/diagnostics, corrected ZLUDA, K/M native objects and patched +OptiScaler using the scripts in `scripts/windows`, then run: + +```powershell +.\scripts\windows\package-windows-game.ps1 +``` + +The package pins TheRock 10.2.0a20260929 because its Windows external-memory +mapping lifetime passes the repeated import/release test. Stable HIP 7.2 has +a reproduced mapped-view leak; see `docs/windows-rdna4-port.md` for the source +fix, exact dependency versions, validation and complete build commands. + +Run a D3D12 game without anti-cheat using your locally supplied NVIDIA DLLs: + +```powershell +.\dist\windows-rdna4-game\windows-game.ps1 -GameExe "D:\Games\SILENT HILL 2\SHProto\Binaries\Win64\SHProto-Win64-Shipping.exe" -NgxCore "C:\Users\Administrator\d4r\_nvngx.dll" -DlssDll "C:\Users\Administrator\d4r\nvngx_dlss.dll" -Preset 11 +``` + +Use `-Preset 13` for M. The validated manifest requires DLSS 310.9.1 with the +recorded SHA256; other feature DLLs require regenerating and validating the +native manifest. Select an upscaler supported by OptiScaler in the game. +The script installs `dxgi.dll`, an explicit DLSS configuration and a private +`d4r` runtime directory, and captures stdout/stderr, an external debugger log, +crash dump, loaded DLL paths, driver information and OptiScaler logs. A local +ZIP bundle is printed at exit. `-RunSeconds 120` bounds a diagnostic run; +the normal default allows you to play until you close the game. + +For the Silent Hill 2 startup black-screen investigation, add +`-DiagnosticResolution 1280x720`. This backs up its two graphics configuration +files, temporarily selects a window at that resolution, and restores the +original bytes after the test. It does not change progression saves. First-use +PTX compilation can be slow; `-CacheDirectory ` reuses +modules with matching GPU, codegen switches and ZLUDA binary fingerprint. + +Original files are backed up before replacement. Restore with: + +```powershell +.\dist\windows-rdna4-game\windows-game.ps1 -Action restore -GameExe "D:\Games\SILENT HILL 2\SHProto\Binaries\Win64\SHProto-Win64-Shipping.exe" +``` + +Restore refuses to overwrite files changed since installation. Backups and +diagnostic results are retained. The script does not change progression saves, +system DLLs, driver settings or security settings. All runtime work is native +Windows. Colour, depth, motion, exposure and output cross APIs through VRAM +and shared D3D12 fences; the backend waits for the current frame and never +uses a previous-frame fallback. + +OptiScaler source modifications are GPL-3.0 and exported under +`source-patches/optiscaler`. ZLUDA source modifications are exported under +`source-patches/zluda`. Dependency license texts and build hashes are included. diff --git a/docs/windows-rdna4-port.md b/docs/windows-rdna4-port.md index 55c7cd6..ade9be6 100644 --- a/docs/windows-rdna4-port.md +++ b/docs/windows-rdna4-port.md @@ -8,6 +8,53 @@ An actual game session remains unverified. ## Milestone and gates +First actual game runs (2026-10-01): Silent Hill 2 1.2, XeSS input -> OptiScaler +DLSS K. `silent-hill2-k-first.zip` found the old Project ID wrapper initializing +CUDA NGX with app 4919 instead of the caller's identity, and back-buffer +tracking references blocking `ResizeBuffers` (DXGI_ERROR_INVALID_CALL). +OptiScaler patch `0003` preserves identity; resource-state records now follow +the public D3D12 caller-owned resource lifetime instead of retaining barriers' +resources. A composition swap-chain regression resizes while its submitted, +unreset command list remains alive; it passes on Radeon and WARP, along with +all 16 CTest gates (`game-resize-ctest.log`). K/M legacy Project ID standalone +frames have bit-exact RGB. The invalid-core test propagates the error from +legacy Init rather than pretending initialization succeeded. + +`silent-hill2-k-project-resize.zip` successfully initializes CUDA DLSS and +presents thousands of startup frames with no d4r failures. It has **zero +completed DLSS frames** and is not a game acceptance pass. The next interactive +run reaches native K feature creation at 2259x1271 -> 3840x2160 after Enter; +the user reports a black screen during this phase. Capture and isolate this +before claiming successful in-game execution. + +`silent-hill2-k-720p.zip` localizes the flashing “upscaler failed to run” +message to the actual depth resource: DXGI `R32G8X24_TYPELESS` (19). The +backend previously rejected it before executing CUDA Evaluate. Implemented +its D3D12 depth copy plane as R32; it is not an interleaved 64-bit texel. +The [public planar depth/stencil specification](https://github.com/microsoft/DirectX-Specs/blob/master/d3d/PlanarDepthStencilDDISpec.md) +defines this plane mapping. `GetCopyableFootprints` is checked against R32 +and width * 4 bytes; copies/barriers affect subresource 0 only. All 4403 depth +pixels match an independent D3D12 typed SRV exactly in each of two legacy and +two enhanced runs. Full build / CTest 16/16 pass +(`d32-stencil-full-ctest.log`). The NGX runner supports +`-PixelProfile depth-stencil` for full-network validation. +`optiscaler-k-depth-stencil.zip` and `optiscaler-m-depth-stencil.zip` both +pass four full frames, with all 44/40 native transformer launches and bit-exact +RGB against the respective non-stencil depth baselines. + +The local game package is `dist/windows-rdna4-game`, produced by +`scripts/windows/package-windows-game.ps1`. Its native manifest combines all +11 K + 5 M objects; NVIDIA binaries are excluded. `windows-game.ps1` backs up +replaced files, verifies package hashes and the local DLSS identity, sets the +native runtime environment, and captures stdout/stderr, external debugger, +OptiScaler, exception and version/driver logs. Install/restore regression +restores original DLL/config sentinel bytes exactly. For Silent Hill 2, +`-DiagnosticResolution 1280x720` backs up and restores its two graphics config +files; this avoids modifying progression saves. Original settings after the +stopped failing run have an identical SHA256. `-CacheDirectory` can reuse an +already validated local JIT cache. Development package usage is documented in +`docs/windows-game.md`; in-game success is still a gate. + Startup coverage (2026-10-01): OptiScaler patch `0002` installs d4r at device discovery, before NGX initialization. `optiscaler-k-preinit-signature.zip` and `optiscaler-m-preinit-signature.zip` create a dispatch-only command signature @@ -149,8 +196,8 @@ copy counts remain zero. Add `-BarrierMode enhanced` to the packed-run command below to reproduce. The explicit harness can also supply layout metadata. Tracking now also publishes explicit ordinary texture state/layout metadata -in the actual queue submission order. Each split segment retains its own -resource-state snapshot and COM references. An Evaluate input with no barrier +in the actual queue submission order. Each split segment stores its own +resource-state snapshot. An Evaluate input with no barrier in its own list resolves the most recently submitted state at the callback, after preceding lists in the batch. The metadata belongs to the resource, with no global pointer cache or ownership cycle. Legacy buffer/simultaneous-access diff --git a/patches/optiscaler/0003-legacy-project-initialization.patch b/patches/optiscaler/0003-legacy-project-initialization.patch new file mode 100644 index 0000000..f279f3c --- /dev/null +++ b/patches/optiscaler/0003-legacy-project-initialization.patch @@ -0,0 +1,37 @@ +From 09488c47855aadbf85f68ab8c72c0f3ac5f915e9 Mon Sep 17 00:00:00 2001 +From: Codex +Date: Thu, 1 Oct 2026 17:01:32 +0300 +Subject: [PATCH] Preserve legacy ProjectID initialization in the external + backend + +--- + OptiScaler/inputs/NVNGX_DLSS_Dx12.cpp | 6 ++++++ + 1 file changed, 6 insertions(+) + +diff --git a/OptiScaler/inputs/NVNGX_DLSS_Dx12.cpp b/OptiScaler/inputs/NVNGX_DLSS_Dx12.cpp +index 89c9ba8..1f723f1 100644 +--- a/OptiScaler/inputs/NVNGX_DLSS_Dx12.cpp ++++ b/OptiScaler/inputs/NVNGX_DLSS_Dx12.cpp +@@ -313,6 +313,8 @@ NVSDK_NGX_API NVSDK_NGX_Result NVSDK_NGX_D3D12_Init_ProjectID(const char* InProj + + if (result == NVSDK_NGX_Result_Success) + NVNGXProxy::SetDx12Inited(true); ++ else if (State::Instance().dlssExternalBackend) ++ return result; + } + } + +@@ -343,6 +345,10 @@ NVSDK_NGX_API NVSDK_NGX_Result NVSDK_NGX_D3D12_Init_with_ProjectID( + { + LOG_FUNC(); + ++ if (State::Instance().dlssExternalBackend) ++ return NVSDK_NGX_D3D12_Init_ProjectID(InProjectId, InEngineType, InEngineVersion, ++ InApplicationDataPath, InDevice, InSDKVersion, InFeatureInfo); ++ + LOG_INFO("InProjectId: {0}", InProjectId); + LOG_INFO("InEngineType: {0}", (int) InEngineType); + LOG_INFO("InEngineVersion: {0}", InEngineVersion); +-- +2.55.0.windows.5 + diff --git a/patches/optiscaler/README.md b/patches/optiscaler/README.md index c0684e9..5918b80 100644 --- a/patches/optiscaler/README.md +++ b/patches/optiscaler/README.md @@ -21,6 +21,11 @@ and before OptiScaler's own method hooks. Command signatures created during game startup therefore retain their public argument descriptions for replay. The standalone runner's `-EarlyIndirectProbe` exercises this startup order. +`0003` preserves the Project ID and engine identity through OptiScaler's +older `Init_with_ProjectID` entry point used by its XeSS/FSR inputs. It also +propagates an explicit external backend initialization failure. Reproduce +with the D3D12 runner's `-NgxAbi project-legacy`. + Build with Microsoft Build Tools 2022, v143 and Windows SDK 10.0.26100: ```powershell diff --git a/scripts/windows/capture-game-window.ps1 b/scripts/windows/capture-game-window.ps1 new file mode 100644 index 0000000..e99dee5 --- /dev/null +++ b/scripts/windows/capture-game-window.ps1 @@ -0,0 +1,46 @@ +[CmdletBinding()] +param([Parameter(Mandatory=$true)][int]$GameProcessId, [Parameter(Mandatory=$true)][string]$OutputPath) +$ErrorActionPreference = 'Stop' +Add-Type -AssemblyName System.Drawing +if (!('D4RWindowCapture' -as [type])) { + Add-Type @' +using System; +using System.Runtime.InteropServices; +using System.Text; +public static class D4RWindowCapture { + public delegate bool Callback(IntPtr window, IntPtr data); + [StructLayout(LayoutKind.Sequential)] public struct Rect { public int Left, Top, Right, Bottom; } + [DllImport("user32.dll")] public static extern bool EnumWindows(Callback callback, IntPtr data); + [DllImport("user32.dll")] public static extern uint GetWindowThreadProcessId(IntPtr window, out uint process); + [DllImport("user32.dll")] public static extern bool GetWindowRect(IntPtr window, out Rect rect); + [DllImport("user32.dll",CharSet=CharSet.Unicode)] public static extern int GetWindowText(IntPtr window, StringBuilder text, int count); + [DllImport("user32.dll")] public static extern bool IsWindowVisible(IntPtr window); +} +'@ +} +$windows = [Collections.Generic.List[object]]::new() +$callback = [D4RWindowCapture+Callback]{ param($window,$data) + [uint32]$owner = 0 + [void][D4RWindowCapture]::GetWindowThreadProcessId($window,[ref]$owner) + if ($owner -eq $GameProcessId) { + $rect = [D4RWindowCapture+Rect]::new() + [void][D4RWindowCapture]::GetWindowRect($window,[ref]$rect) + $title = [Text.StringBuilder]::new(512) + [void][D4RWindowCapture]::GetWindowText($window,$title,512) + $windows.Add([pscustomobject]@{handle=$window; title=$title.ToString(); visible=[D4RWindowCapture]::IsWindowVisible($window); rect=$rect; + area=($rect.Right-$rect.Left)*($rect.Bottom-$rect.Top)}) + } + return $true +} +[void][D4RWindowCapture]::EnumWindows($callback,[IntPtr]::Zero) +$windows | Select-Object handle,title,visible,area | Format-Table +$chosen = $windows | Where-Object { $_.visible -and $_.area -gt 4096 } | Sort-Object area -Descending | Select-Object -First 1 +if (!$chosen) { throw "No visible game window for PID $GameProcessId" } +$rect = $chosen.rect +$bitmap = [Drawing.Bitmap]::new($rect.Right-$rect.Left,$rect.Bottom-$rect.Top) +$graphics = [Drawing.Graphics]::FromImage($bitmap) +try { + $graphics.CopyFromScreen($rect.Left,$rect.Top,0,0,$bitmap.Size) + $bitmap.Save([IO.Path]::GetFullPath($OutputPath),[Drawing.Imaging.ImageFormat]::Png) +} finally { $graphics.Dispose(); $bitmap.Dispose() } +Write-Host "Captured game window: $OutputPath" diff --git a/scripts/windows/package-windows-game.ps1 b/scripts/windows/package-windows-game.ps1 new file mode 100644 index 0000000..5250a5e --- /dev/null +++ b/scripts/windows/package-windows-game.ps1 @@ -0,0 +1,76 @@ +[CmdletBinding()] +param( + [string]$PackageRoot, [string]$DiagnosticRoot, [string]$OptiScalerRoot, + [string]$ZludaRoot, [string]$HipRoot, [string]$KernelKRoot, [string]$KernelMRoot +) +$ErrorActionPreference = 'Stop' +$repo = [IO.Path]::GetFullPath((Join-Path $PSScriptRoot '../..')) +$defaults = @{PackageRoot='dist/windows-rdna4-game'; DiagnosticRoot='dist/windows-rdna4-command-list'; + OptiScalerRoot='dist/optiscaler-windows-d4r'; ZludaRoot='dist/zluda-windows-f16-reference'; + HipRoot='.tools/therock-10.2.0a20260929/_rocm_sdk_core'; + KernelKRoot='build/native-k-gfx1201'; KernelMRoot='build/native-m-gfx1201'} +foreach ($name in $defaults.Keys) { + if (!(Get-Variable -Name $name -ValueOnly)) { Set-Variable -Name $name -Value (Join-Path $repo $defaults[$name]) } +} +$PackageRoot = [IO.Path]::GetFullPath($PackageRoot) +New-Item -ItemType Directory -Force $PackageRoot | Out-Null +$stagedFiles = [Collections.Generic.List[string]]::new() +function Stage([string]$source, [string]$relative) { + if (!(Test-Path -LiteralPath $source -PathType Leaf)) { throw "Required file missing: $source" } + $destination = Join-Path $PackageRoot $relative + New-Item -ItemType Directory -Force (Split-Path $destination) | Out-Null + Copy-Item -LiteralPath $source -Destination $destination -Force + $stagedFiles.Add($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_gfx1201.hsaco') 'd4r/pixel_convert_gfx1201.hsaco' +Stage (Join-Path $DiagnosticRoot 'nvapi-compat/nvapi64.dll') 'd4r/nvapi/nvapi64.dll' +Stage (Join-Path $DiagnosticRoot 'bin/d4r_debug_launcher.exe') 'd4r/d4r_debug_launcher.exe' +foreach ($file in @('nvcuda.dll','nvapi64.dll','libc++.dll','libunwind.dll')) { Stage (Join-Path $ZludaRoot $file) "d4r/zluda/$file" } +foreach ($file in @('amdhip64_7.dll','amd_comgr.dll','hiprtc0717.dll','hiprtc-builtins0717.dll','rocm_kpack.dll','AMD.ROCM.Comgr.MANIFEST','.hipVersion')) { + Stage (Join-Path $HipRoot "bin/$file") "d4r/hip/bin/$file" +} +$entries = @{} +foreach ($directory in @($KernelKRoot, $KernelMRoot)) { + foreach ($line in Get-Content -LiteralPath (Join-Path $directory 'd4r-kernels.txt')) { + if (!$line.Trim() -or $line.StartsWith('#')) { continue } + if ($line -notmatch '^([A-Za-z0-9_]+) ([0-9a-f]{16})$') { throw "Invalid native manifest entry: $line" } + if ($entries.ContainsKey($Matches[1])) { throw "Duplicate native kernel: $($Matches[1])" } + $entries[$Matches[1]] = $line + Stage (Join-Path $directory ($Matches[1] + '.hsaco')) ('d4r/native/' + $Matches[1] + '.hsaco') + } +} +if ($entries.Count -ne 16) { throw "Expected 11 K and 5 M native objects; found $($entries.Count)" } +@('# Audited DLSS 310.9.1: K + M gfx1201 native kernels', '# Generated by package-windows-game.ps1') + @($entries.Values | Sort-Object) | + Set-Content -LiteralPath (Join-Path $PackageRoot 'd4r/native/d4r-kernels.txt') -Encoding ASCII +$stagedFiles.Add('d4r/native/d4r-kernels.txt') +Stage (Join-Path $repo 'scripts/windows/windows-game.ps1') 'windows-game.ps1' +Stage (Join-Path $repo 'scripts/windows/capture-game-window.ps1') 'capture-game-window.ps1' +Stage (Join-Path $repo 'scripts/windows/send-game-key.ps1') 'send-game-key.ps1' +Stage (Join-Path $repo 'docs/windows-game.md') 'README.md' +Stage (Join-Path $repo 'LICENSE') 'licenses/d4r.txt' +Stage (Join-Path $OptiScalerRoot 'GPL-3.0.txt') 'licenses/OptiScaler.txt' +Stage (Join-Path $repo 'external/ZLUDA/LICENSE-MIT') 'licenses/ZLUDA-MIT.txt' +Stage (Join-Path $repo 'external/ZLUDA/LICENSE-APACHE') 'licenses/ZLUDA-APACHE.txt' +Stage (Join-Path $repo 'external/ZLUDA/ext/llvm-project/LICENSE.TXT') 'licenses/LLVM.txt' +Stage (Join-Path $repo 'external/MinHook/LICENSE.txt') 'licenses/MinHook.txt' +Stage (Join-Path $HipRoot 'share/doc/amd_comgr/LICENSE.txt') 'licenses/amd_comgr.txt' +Stage (Join-Path $repo 'external/clr/LICENSE.md') 'licenses/HIP-CLR.txt' +foreach ($patch in Get-ChildItem -LiteralPath (Join-Path $repo 'patches/optiscaler') -Filter '*.patch') { Stage $patch.FullName ('source-patches/optiscaler/' + $patch.Name) } +foreach ($patch in Get-ChildItem -LiteralPath (Join-Path $repo 'patches/zluda') -Filter '*.patch') { Stage $patch.FullName ('source-patches/zluda/' + $patch.Name) } +$metadata = [ordered]@{ + architecture='gfx1201'; nativeKernels=16; dlssSha256='3975567b8943c53acce397f2b72380092f84f162d00b0d2c7d08a1025c563983'; + d4rCommit=(& git -C $repo rev-parse HEAD).Trim(); + optiScaler=(Get-Content -LiteralPath (Join-Path $OptiScalerRoot 'build-info.json') -Raw | ConvertFrom-Json); + zludaCommit=(& git -C (Join-Path $repo 'external/ZLUDA') rev-parse HEAD).Trim(); + hipRuntime='TheRock 10.2.0a20260929 / HIP 7.17.26386'; + files=@($stagedFiles | ForEach-Object { Get-Item -LiteralPath (Join-Path $PackageRoot $_) } | ForEach-Object { + @{path=$_.FullName.Substring($PackageRoot.Length+1); sha256=(Get-FileHash -LiteralPath $_.FullName -Algorithm SHA256).Hash; version=$_.VersionInfo.FileVersion} + }) +} +$metadata | ConvertTo-Json -Depth 10 | Set-Content -LiteralPath (Join-Path $PackageRoot 'package.json') -Encoding UTF8 +if (Get-ChildItem -LiteralPath $PackageRoot -Recurse -File | Where-Object { $_.Name -eq 'nvngx_dlss.dll' -or ($_.Name -eq '_nvngx.dll' -and $_.DirectoryName -ne (Join-Path $PackageRoot 'd4r')) }) { + throw 'Proprietary NVIDIA binaries must not be in the package.' +} +Write-Host "Windows game development package: $PackageRoot (NVIDIA DLLs excluded)" diff --git a/scripts/windows/send-game-key.ps1 b/scripts/windows/send-game-key.ps1 new file mode 100644 index 0000000..c85ecb8 --- /dev/null +++ b/scripts/windows/send-game-key.ps1 @@ -0,0 +1,25 @@ +[CmdletBinding()] +param([Parameter(Mandatory=$true)][int]$GameProcessId, + [ValidateSet('Enter','Escape','Up','Down','Tab')][string]$Key='Enter') +$ErrorActionPreference='Stop' +$process=Get-Process -Id $GameProcessId -ErrorAction Stop +if ($process.ProcessName -ne 'SHProto-Win64-Shipping') { throw 'This helper is scoped to the authorized Silent Hill 2 diagnostic.' } +if (!('D4RGameKeys' -as [type])) { + Add-Type @' +using System; +using System.Runtime.InteropServices; +public static class D4RGameKeys { + [DllImport("user32.dll")] public static extern bool PostMessage(IntPtr window, uint message, IntPtr key, IntPtr flags); + [DllImport("user32.dll")] public static extern uint MapVirtualKey(uint code, uint kind); +} +'@ +} +$window=$process.MainWindowHandle +if ($window -eq [IntPtr]::Zero) { throw 'Game window missing on this desktop.' } +$codes=@{Enter=13; Escape=27; Up=38; Down=40; Tab=9} +$code=$codes[$Key]; $scan=[D4RGameKeys]::MapVirtualKey($code,0) +$flags=[long](1 -bor ($scan -shl 16)) +if ($Key -in @('Up','Down')) { $flags=$flags -bor 0x01000000 } +if (![D4RGameKeys]::PostMessage($window,0x100,[IntPtr]$code,[IntPtr]$flags)) { throw 'Game key-down post failed.' } +if (![D4RGameKeys]::PostMessage($window,0x101,[IntPtr]$code,[IntPtr]($flags -bor 0xc0000000L))) { throw 'Game key-up post failed.' } +Write-Host "Sent $Key only to diagnostic game PID $GameProcessId." diff --git a/scripts/windows/test-windows-rdna4.ps1 b/scripts/windows/test-windows-rdna4.ps1 index f9331cd..e8861a0 100644 --- a/scripts/windows/test-windows-rdna4.ps1 +++ b/scripts/windows/test-windows-rdna4.ps1 @@ -8,7 +8,7 @@ param( [string]$NgxCore = $env:D4R_NGX_CORE, [string]$DlssDll = $env:D4R_DLSS_DLL, [ValidateSet('init', 'evaluate', 'd3d12')][string]$NgxMode = 'init', - [ValidateSet('driver','sdk','project')][string]$NgxAbi = 'driver', + [ValidateSet('driver','sdk','project','project-legacy')][string]$NgxAbi = 'driver', [string]$OptiScalerDll, [ValidateSet(5, 11, 13)][int]$Preset = 11, [switch]$NgxOnly, @@ -17,7 +17,7 @@ param( [switch]$CommandListBackend, [switch]$CaptureExceptions, [switch]$EarlyIndirectProbe, - [ValidateSet('baseline','packed','unorm')][string]$PixelProfile = 'baseline', + [ValidateSet('baseline','packed','unorm','depth-stencil')][string]$PixelProfile = 'baseline', [ValidateSet('legacy','enhanced','inherited-legacy','inherited-enhanced')][string]$BarrierMode = 'legacy', [int]$Iterations = 32, [int]$TimeoutSeconds = 180 diff --git a/scripts/windows/windows-game.ps1 b/scripts/windows/windows-game.ps1 new file mode 100644 index 0000000..0b0db2f --- /dev/null +++ b/scripts/windows/windows-game.ps1 @@ -0,0 +1,193 @@ +[CmdletBinding()] +param( + [ValidateSet('install','restore','run')][string]$Action = 'run', + [Parameter(Mandatory=$true)][string]$GameExe, + [string]$NgxCore, [string]$DlssDll, + [ValidateSet(11,13)][int]$Preset = 11, + [int]$RunSeconds = 0, + [string]$DiagnosticResolution, + [string]$CacheDirectory = $env:ZLUDA_CACHE_DIR, + [string[]]$GameArguments = @('-dx12','-windowed','-ResX=1280','-ResY=720'), + [string]$OutputDirectory +) +$ErrorActionPreference = 'Stop' +$package = [IO.Path]::GetFullPath($PSScriptRoot) +$GameExe = (Get-Item -LiteralPath $GameExe -ErrorAction Stop).FullName +$game = Split-Path $GameExe +$backup = Join-Path $game '.d4r-backup' +$manifestPath = Join-Path $backup 'manifest.json' +function GamePath([string]$relative) { + $path = [IO.Path]::GetFullPath((Join-Path $game $relative)) + if (!$path.StartsWith($game + '\', [StringComparison]::OrdinalIgnoreCase)) { throw "Path escapes game directory: $relative" } + return $path +} +if (Get-Process -Name ([IO.Path]::GetFileNameWithoutExtension($GameExe)) -ErrorAction SilentlyContinue) { throw 'Close the game before installing, restoring or running this diagnostic.' } +if ($Action -eq 'restore') { + if (!(Test-Path -LiteralPath $manifestPath)) { throw 'No d4r installation manifest exists.' } + $manifest = Get-Content -LiteralPath $manifestPath -Raw | ConvertFrom-Json + foreach ($entry in $manifest.files) { + $path = GamePath $entry.path + if ((Test-Path -LiteralPath $path) -and (Get-FileHash -LiteralPath $path -Algorithm SHA256).Hash -ne $entry.installedHash) { throw "File changed since installation; preserved: $path" } + } + foreach ($entry in $manifest.files) { + $path = GamePath $entry.path + if ($entry.existed) { Copy-Item -LiteralPath (Join-Path $backup ('original/' + $entry.path)) -Destination $path -Force } + elseif (Test-Path -LiteralPath $path) { Remove-Item -LiteralPath $path -Force } + } + # Remove only empty directories; preserve user files and diagnostic logs. + if (Test-Path -LiteralPath (GamePath 'd4r')) { + Get-ChildItem -LiteralPath (GamePath 'd4r') -Directory -Recurse | Sort-Object { $_.FullName.Length } -Descending | ForEach-Object { + if (!(Get-ChildItem -LiteralPath $_.FullName -Force)) { Remove-Item -LiteralPath $_.FullName } + } + if (!(Get-ChildItem -LiteralPath (GamePath 'd4r') -Force)) { Remove-Item -LiteralPath (GamePath 'd4r') } + } + Move-Item -LiteralPath $manifestPath -Destination (Join-Path $backup ('restored-' + (Get-Date -Format 'yyyyMMdd-HHmmss-fff') + '.json')) + Write-Host "Original game files restored. Backups retained: $backup" + return +} +if (!$NgxCore -or !$DlssDll) { throw 'Pass both local NVIDIA DLL paths with -NgxCore and -DlssDll.' } +$NgxCore = (Get-Item -LiteralPath $NgxCore).FullName +$DlssDll = (Get-Item -LiteralPath $DlssDll).FullName +$metadata = Get-Content -LiteralPath (Join-Path $package 'package.json') -Raw | ConvertFrom-Json +foreach ($file in $metadata.files) { + if ((Get-FileHash -LiteralPath (Join-Path $package $file.path) -Algorithm SHA256).Hash -ne $file.sha256) { throw "Package file changed; rebuild package: $($file.path)" } +} +if ((Get-FileHash -LiteralPath $DlssDll -Algorithm SHA256).Hash -ne $metadata.dlssSha256) { throw 'DLSS DLL does not match the validated 310.9.1 native manifest. Rebuild and validate native manifests for this DLL first.' } +$manifest = if (Test-Path -LiteralPath $manifestPath) { Get-Content -LiteralPath $manifestPath -Raw | ConvertFrom-Json } else { [pscustomobject]@{game=$GameExe; files=@()} } +if ($manifest.game -ne $GameExe) { throw 'Backup manifest belongs to another executable.' } +if (!(Test-Path -LiteralPath $manifestPath) -and (Test-Path -LiteralPath (GamePath 'd4r'))) { throw 'An existing d4r directory has no installation manifest; preserved.' } +New-Item -ItemType Directory -Force $backup | Out-Null +function InstallFile([string]$source, [string]$relative) { + $path = GamePath $relative + $entry = @($manifest.files | Where-Object path -eq $relative) | Select-Object -First 1 + if ($entry) { + if ((Test-Path -LiteralPath $path) -and (Get-FileHash -LiteralPath $path -Algorithm SHA256).Hash -ne $entry.installedHash) { throw "Installed file changed; preserved: $path" } + } else { + $existed = Test-Path -LiteralPath $path + if ($existed) { + $original = Join-Path $backup ('original/' + $relative) + New-Item -ItemType Directory -Force (Split-Path $original) | Out-Null + Copy-Item -LiteralPath $path -Destination $original + } + $entry = [pscustomobject]@{path=$relative; existed=[bool]$existed; installedHash=''} + $manifest.files = @($manifest.files) + @($entry) + } + New-Item -ItemType Directory -Force (Split-Path $path) | Out-Null + Copy-Item -LiteralPath $source -Destination $path -Force + $entry.installedHash = (Get-FileHash -LiteralPath $path -Algorithm SHA256).Hash + $manifest | ConvertTo-Json -Depth 6 | Set-Content -LiteralPath $manifestPath -Encoding UTF8 +} +InstallFile (Join-Path $package 'OptiScaler.dll') 'dxgi.dll' +foreach ($file in Get-ChildItem -LiteralPath (Join-Path $package 'd4r') -Recurse -File) { InstallFile $file.FullName $file.FullName.Substring($package.Length+1) } +# Private local test copies only; these are never added to the public package. +InstallFile $NgxCore 'd4r/vendor/_nvngx.dll' +InstallFile $DlssDll 'd4r/vendor/nvngx_dlss.dll' +$bridge = GamePath 'd4r' +$vendor = GamePath 'd4r/vendor' +$ini = @" +[Upscalers] +Dx12Upscaler=dlss +[FrameGen] +Enabled=false +FGInput=nofg +FGOutput=nofg +[DLSS] +Enabled=true +AllowExternalBackend=true +RenderPresetOverride=true +RenderPresetForAll=$Preset +UseGenericAppIdWithDlss=false +[Libraries] +NvngxPath=$bridge +NvngxDlssPath=$vendor\nvngx_dlss.dll +NvngxFeaturePath=$vendor +[Hotfix] +RestoreComputeSignature=false +RestoreGraphicSignature=false +ExtendedStateRestore=false +[Log] +LogToFile=true +LogLevel=0 +[Menu] +DisableSplash=true +"@ +$tempIni = Join-Path $backup 'generated-OptiScaler.ini' +$ini | Set-Content -LiteralPath $tempIni -Encoding UTF8 +InstallFile $tempIni 'OptiScaler.ini' +if ($Action -eq 'install') { Write-Host "Installed development backend for preset $Preset. Use -Action restore to revert."; return } +if (!$OutputDirectory) { $OutputDirectory = Join-Path $package ('results/game-' + (Get-Date -Format 'yyyyMMdd-HHmmss-fff')) } +$OutputDirectory = [IO.Path]::GetFullPath($OutputDirectory) +New-Item -ItemType Directory -Force $OutputDirectory | Out-Null +if (!$CacheDirectory) { $CacheDirectory = Join-Path $package 'cache' } +$CacheDirectory = [IO.Path]::GetFullPath($CacheDirectory) +$settings = @{ + D4R_HIP_ROOT=(GamePath 'd4r/hip'); HIP_PATH=(GamePath 'd4r/hip'); + D4R_NVCUDA_DLL=(GamePath 'd4r/zluda/nvcuda.dll'); ZLUDA_CUDA_LIB=(GamePath 'd4r/zluda/nvcuda.dll'); + D4R_NVAPI_DLL=(GamePath 'd4r/nvapi/nvapi64.dll'); D4R_NVAPI_BACKEND=(GamePath 'd4r/zluda/nvapi64.dll'); + D4R_NGX_CORE=(GamePath 'd4r/vendor/_nvngx.dll'); D4R_DLSS_DLL=(GamePath 'd4r/vendor/nvngx_dlss.dll'); + D4R_FORMAT_MODULE=(GamePath 'd4r/pixel_convert_gfx1201.hsaco'); + D4R_ZLUDA_NATIVE_DIR=(GamePath 'd4r/native'); D4R_D3D12_COMMAND_BACKEND='1'; D4R_ZLUDA_VERBOSE='1'; + D4R_ZLUDA_WMMA='1'; D4R_ZLUDA_WMMA_FP8='1'; D4R_ZLUDA_WMMA_FP8_NATIVE='0'; D4R_ZLUDA_WMMA_F16_REFERENCE='1'; + D4R_DIAG_DIR=$OutputDirectory; ZLUDA_LOG_DIR=(Join-Path $OutputDirectory 'zluda-trace'); + ZLUDA_CACHE_DIR=$CacheDirectory; PATH=((GamePath 'd4r/hip/bin') + ';' + (GamePath 'd4r/zluda') + ';' + $env:PATH) +} +$old = @{}; $process = $null; $originalSettings = @() +try { + if ($DiagnosticResolution) { + if ([IO.Path]::GetFileName($GameExe) -ne 'SHProto-Win64-Shipping.exe' -or $DiagnosticResolution -notmatch '^([0-9]{3,4})x([0-9]{3,4})$') { throw 'DiagnosticResolution currently supports Silent Hill 2 only; use e.g. 1280x720.' } + $renderWidth=[int]$Matches[1]; $renderHeight=[int]$Matches[2] + if ($renderWidth -gt 4096 -or $renderHeight -gt 2160) { throw 'Diagnostic resolution is too large.' } + $userRoot=Join-Path $env:LOCALAPPDATA 'SilentHill2/Saved' + foreach ($relative in @('Config/Windows/GameUserSettings.ini','SaveGames/GFXSettings.SHProto-Win64-Shipping.xml')) { + $path=Join-Path $userRoot $relative + $saved=Join-Path $OutputDirectory ('original-settings/' + $relative) + New-Item -ItemType Directory -Force (Split-Path $saved) | Out-Null + Copy-Item -LiteralPath $path -Destination $saved + $originalSettings += @{path=$path; saved=$saved} + if ($relative.EndsWith('.ini')) { + $text=[IO.File]::ReadAllText($path) + $text=$text -replace 'Resolution=\(X=[0-9]+,Y=[0-9]+\)', "Resolution=(X=$renderWidth,Y=$renderHeight)" + $text=$text -replace 'ScreenMode=Borderless', 'ScreenMode=Windowed' + $text=$text -replace '(?m)^ResolutionSizeX=[0-9]+', "ResolutionSizeX=$renderWidth" + $text=$text -replace '(?m)^ResolutionSizeY=[0-9]+', "ResolutionSizeY=$renderHeight" + $text=$text -replace '(?m)^FullscreenMode=[0-9]+', 'FullscreenMode=2' + [IO.File]::WriteAllText($path,$text,[Text.UTF8Encoding]::new($false)) + } else { + $xml=[xml](Get-Content -LiteralPath $path -Raw -Encoding Unicode) + $xml.GSA_SDK.GAMESETTINGS.RESOLUTION.Width="$renderWidth" + $xml.GSA_SDK.GAMESETTINGS.RESOLUTION.Height="$renderHeight" + $xml.Save($path) + } + } + Write-Host "Temporary diagnostic resolution: $DiagnosticResolution; original settings restore at exit." + } + foreach ($key in $settings.Keys) { $old[$key] = [Environment]::GetEnvironmentVariable($key,'Process'); [Environment]::SetEnvironmentVariable($key,$settings[$key],'Process') } + @{preset=$Preset; game=$GameExe; package=$metadata; driver=@(Get-CimInstance Win32_VideoController | Select-Object Name,DriverVersion,DriverDate); arguments=$GameArguments} | + ConvertTo-Json -Depth 12 | Set-Content -LiteralPath (Join-Path $OutputDirectory 'environment.json') -Encoding UTF8 + function Quote([string]$value) { if ($value.Contains('"')) { throw 'A command argument contains an unsupported quote.' }; return '"' + $value + '"' } + $arguments = @('--output-directory', (Quote $OutputDirectory), '--', (Quote $GameExe)) + @($GameArguments | ForEach-Object { Quote $_ }) + $process = Start-Process -FilePath (GamePath 'd4r/d4r_debug_launcher.exe') -ArgumentList $arguments -WorkingDirectory $game -WindowStyle Hidden -PassThru ` + -RedirectStandardOutput (Join-Path $OutputDirectory 'd4r.stdout.log') -RedirectStandardError (Join-Path $OutputDirectory 'd4r.stderr.log') + Write-Host "Game diagnostic started (preset $Preset). Logs: $OutputDirectory" + $timer = [Diagnostics.Stopwatch]::StartNew() + while (!$process.WaitForExit(1000)) { + if ($RunSeconds -gt 0 -and $timer.Elapsed.TotalSeconds -ge $RunSeconds) { $process.Kill(); $process.WaitForExit(); break } + } + $process.WaitForExit() + foreach ($file in Get-ChildItem -LiteralPath $game -Filter 'OptiScaler*.log' -File) { Copy-Item -LiteralPath $file.FullName -Destination $OutputDirectory -Force } + $stderr = Get-Content -LiteralPath (Join-Path $OutputDirectory 'd4r.stderr.log') -Raw + $stdout = Get-Content -LiteralPath (Join-Path $OutputDirectory 'd4r.stdout.log') -Raw + $debugger = Get-Content -LiteralPath (Join-Path $OutputDirectory 'debugger.log') -Raw + $gameExit = [regex]::Match($debugger, 'EXIT code=0x([0-9a-f]+)') + @{exitCode=$(if ($gameExit.Success) { '0x' + $gameExit.Groups[1].Value } else { 'diagnostic_timeout' }); runSeconds=$timer.Elapsed.TotalSeconds; preset=$Preset; + nativeLaunches=([regex]::Matches($stderr, '\[d4r-launch\].*backend=native')).Count; + completedFrames=([regex]::Matches($stdout, 'D4R_FRAME ')).Count; + failures=([regex]::Matches($stderr, 'D4R_WINDOWS_FAILURE|D4R_COMMAND_FAILURE')).Count} | + ConvertTo-Json | Set-Content -LiteralPath (Join-Path $OutputDirectory 'summary.json') -Encoding UTF8 + Compress-Archive -LiteralPath $OutputDirectory -DestinationPath ($OutputDirectory + '.zip') -Force + Write-Host "Game diagnostic bundle: $OutputDirectory.zip" +} finally { + if ($process -and !$process.HasExited) { $process.Kill(); $process.WaitForExit() } + foreach ($key in $old.Keys) { [Environment]::SetEnvironmentVariable($key,$old[$key],'Process') } + foreach ($entry in $originalSettings) { Copy-Item -LiteralPath $entry.saved -Destination $entry.path -Force } +} diff --git a/tools/windows/d3d12_command_hooks.cpp b/tools/windows/d3d12_command_hooks.cpp index aff6b20..d6027e3 100644 --- a/tools/windows/d3d12_command_hooks.cpp +++ b/tools/windows/d3d12_command_hooks.cpp @@ -16,7 +16,9 @@ bool installed = false; struct Event { UINT metadata; std::vector data; }; using State = std::function; struct TrackedResource { - ComPtr owner; + // D3D12 command lists do not retain resource references. The caller keeps + // them alive through submission/completion. An extra tracking reference + // would keep swap-chain buffers alive and make ResizeBuffers fail. ResourceAccess state; }; using ResourceStates = std::unordered_map; @@ -26,9 +28,9 @@ void publish_states(const ResourceStates& states) { // Buffers/simultaneous-access textures have automatic legacy state // decay at ExecuteCommandLists completion. Only ordinary textures // retain these explicit transitions across submission boundaries. - const auto desc = resource_desc(entry.second.owner.Get()); + const auto desc = resource_desc(entry.first); if (desc.Dimension == D3D12_RESOURCE_DIMENSION_BUFFER || (desc.Flags & D3D12_RESOURCE_FLAG_ALLOW_SIMULTANEOUS_ACCESS)) continue; - dx(entry.second.owner->SetPrivateData(submitted_access_tag, sizeof(ResourceAccess), &entry.second.state), "Publish submitted texture access"); + dx(entry.first->SetPrivateData(submitted_access_tag, sizeof(ResourceAccess), &entry.second.state), "Publish submitted texture access"); } } struct Segment { @@ -206,7 +208,6 @@ void STDMETHODCALLTYPE hook_ResourceBarrier(ID3D12GraphicsCommandList* self, UIN if (barriers[i].Type == D3D12_RESOURCE_BARRIER_TYPE_TRANSITION && (barriers[i].Transition.Subresource == 0 || barriers[i].Transition.Subresource == D3D12_RESOURCE_BARRIER_ALL_SUBRESOURCES)) { auto& tracked = access.recording->resource_states[barriers[i].Transition.pResource]; - tracked.owner = retain(barriers[i].Transition.pResource); auto& state = tracked.state; state = ResourceAccess(barriers[i].Transition.StateAfter); state.pending_split = barriers[i].Flags == D3D12_RESOURCE_BARRIER_FLAG_BEGIN_ONLY; @@ -221,7 +222,7 @@ void STDMETHODCALLTYPE hook_Barrier(ID3D12GraphicsCommandList* self, UINT32 barr const bool containsZero = range.NumMipLevels == 0 ? (range.IndexOrFirstMipLevel == 0 || range.IndexOrFirstMipLevel == UINT32_MAX) : range.IndexOrFirstMipLevel == 0 && range.FirstArraySlice == 0 && range.FirstPlane == 0; if (containsZero) { - auto& tracked = access.recording->resource_states[barrier.pResource]; tracked.owner = retain(barrier.pResource); + auto& tracked = access.recording->resource_states[barrier.pResource]; auto& state = tracked.state; state.enhanced = true; state.inherited = false; state.layout = barrier.LayoutAfter; state.access = barrier.AccessAfter; state.sync = barrier.SyncAfter; state.pending_split = barrier.SyncAfter == D3D12_BARRIER_SYNC_SPLIT; diff --git a/tools/windows/d3d12_command_probe.cpp b/tools/windows/d3d12_command_probe.cpp index ba0a468..a2847f8 100644 --- a/tools/windows/d3d12_command_probe.cpp +++ b/tools/windows/d3d12_command_probe.cpp @@ -161,6 +161,23 @@ int main(int argc, char** argv) { using Live = unsigned long long(*)(); const bool no_hooks = std::getenv("D4R_COMMAND_PROBE_NO_HOOKS") != nullptr; if (!no_hooks && shim.symbol("d4r_D3D12_InstallCommandBackend")(device.Get()) != 1) throw std::runtime_error("Command backend installation failed"); + { + ComPtr newerFactory; dx(factory->QueryInterface(IID_PPV_ARGS(newerFactory.GetAddressOf())), "Resize regression factory"); + DXGI_SWAP_CHAIN_DESC1 desc{}; desc.Width = desc.Height = 64; + desc.Format = DXGI_FORMAT_R8G8B8A8_UNORM; desc.SampleDesc.Count = 1; + desc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT; desc.BufferCount = 2; + desc.SwapEffect = DXGI_SWAP_EFFECT_FLIP_DISCARD; desc.AlphaMode = DXGI_ALPHA_MODE_IGNORE; + ComPtr chain; + dx(newerFactory->CreateSwapChainForComposition(queue.Get(), &desc, nullptr, chain.GetAddressOf()), "Resize regression swap chain"); + ComPtr backBuffer; dx(chain->GetBuffer(0, IID_PPV_ARGS(backBuffer.GetAddressOf())), "Resize regression back buffer"); + NativeList recorded(device.Get()); + transition(recorded.list.Get(), backBuffer.Get(), D3D12_RESOURCE_STATE_PRESENT, D3D12_RESOURCE_STATE_RENDER_TARGET); + transition(recorded.list.Get(), backBuffer.Get(), D3D12_RESOURCE_STATE_RENDER_TARGET, D3D12_RESOURCE_STATE_PRESENT); + recorded.submit(device.Get(), queue.Get()); backBuffer.Reset(); + // The submitted list remains alive and unreset during resize. + dx(chain->ResizeBuffers(2, 96, 64, DXGI_FORMAT_UNKNOWN, 0), "ResizeBuffers with retained submitted list"); + std::printf("D3D12_RESIZE_REGRESSION submitted_list_alive=1 old_back_buffer_released=1\n"); + } auto record = shim.symbol("d4r_D3D12_RecordDiagnosticBoundary"); auto live = shim.symbol("d4r_D3D12_LiveRecordings"); const bool root_indirect = args.interop_mode == "indirect-root-reset"; diff --git a/tools/windows/d3d12_dlss_probe.cpp b/tools/windows/d3d12_dlss_probe.cpp index 725aaae..4962693 100644 --- a/tools/windows/d3d12_dlss_probe.cpp +++ b/tools/windows/d3d12_dlss_probe.cpp @@ -86,6 +86,7 @@ int main(int argc, char** argv) { i == 2 ? DXGI_FORMAT_R32G32B32A32_FLOAT : i == 4 ? DXGI_FORMAT_R16_FLOAT : DXGI_FORMAT_R32_FLOAT; if (args.pixel_profile == "unorm") desc.Format = i == 3 ? DXGI_FORMAT_B8G8R8A8_UNORM : i == 2 ? DXGI_FORMAT_R16G16_SNORM : i == 4 ? DXGI_FORMAT_R32G32B32A32_FLOAT : desc.Format; + if (args.pixel_profile == "depth-stencil" && i == 1) { desc.Format = DXGI_FORMAT_R32G8X24_TYPELESS; desc.Flags = D3D12_RESOURCE_FLAG_ALLOW_DEPTH_STENCIL; } if (i == 3) desc.Flags = D3D12_RESOURCE_FLAG_ALLOW_UNORDERED_ACCESS; D3D12_HEAP_PROPERTIES heap{}; heap.Type = D3D12_HEAP_TYPE_DEFAULT; heap.CreationNodeMask = heap.VisibleNodeMask = 1; @@ -214,7 +215,10 @@ int main(int argc, char** argv) { using Requirements = unsigned(*)(IDXGIAdapter*,const d4r::ngx::FeatureDiscoveryInfo*,d4r::ngx::FeatureRequirement*); check(shim.symbol("NVSDK_NGX_D3D12_GetFeatureRequirements")(adapter.Get(),&discovery,&requirements),"D3D12 feature requirements"); if (requirements.FeatureSupported != 0) throw std::runtime_error("D3D12 DLSS requirements unsupported"); - if (args.ngx_abi == "project") { + if (args.ngx_abi == "project-legacy") { + using ProjectInit = unsigned(*)(const char*,int,const char*,const wchar_t*,ID3D12Device*,const void*,unsigned); + check(shim.symbol("NVSDK_NGX_D3D12_Init_with_ProjectID")("24480451-f00d-face-1304-0308dabad187",0,"1.0",directory.c_str(),device.Get(),nullptr,0x15),"D3D12 legacy project Init"); + } else if (args.ngx_abi == "project") { using ProjectInit = unsigned(*)(const char*,int,const char*,const wchar_t*,ID3D12Device*,unsigned,const void*); check(shim.symbol("NVSDK_NGX_D3D12_Init_ProjectID")("24480451-f00d-face-1304-0308dabad187",0,"1.0",directory.c_str(),device.Get(),0x15,nullptr),"D3D12 project Init"); } else check(shim.symbol("NVSDK_NGX_D3D12_Init_Ext")(241534723ull, directory.c_str(), device.Get(), 0x15, nullptr), "D3D12 Init"); diff --git a/tools/windows/d3d12_external.h b/tools/windows/d3d12_external.h index bba5953..e24928a 100644 --- a/tools/windows/d3d12_external.h +++ b/tools/windows/d3d12_external.h @@ -115,6 +115,13 @@ public: SharedPlane(ExternalApi& api, ID3D12Device* device, const D3D12_RESOURCE_DESC& texture) : api_(api) { UINT rows = 0; UINT64 rowBytes = 0; device->GetCopyableFootprints(&texture, 0, 1, 0, &footprint, &rows, &rowBytes, &bytes); + if (texture.Format == DXGI_FORMAT_R32G8X24_TYPELESS || texture.Format == DXGI_FORMAT_D32_FLOAT_S8X24_UINT || texture.Format == DXGI_FORMAT_R32_FLOAT_X8X24_TYPELESS) { + // D3D12 exposes D32S8 as separate depth/stencil copy planes. Only + // subresource 0 (R32 depth) belongs to NGX; never copy interleaved + // 64-bit texels or touch the stencil plane. + if ((footprint.Footprint.Format != DXGI_FORMAT_R32_FLOAT && footprint.Footprint.Format != DXGI_FORMAT_R32_TYPELESS) || rowBytes != texture.Width * 4) + throw std::runtime_error("Unexpected D32S8 depth-plane copy footprint"); + } if (!bytes || bytes == UINT64_MAX || !footprint.Footprint.RowPitch) throw std::runtime_error("Invalid shared texture footprint"); buffer = make_buffer(device, bytes, D3D12_HEAP_TYPE_DEFAULT, D3D12_HEAP_FLAG_SHARED, D3D12_RESOURCE_STATE_COMMON); diff --git a/tools/windows/d3d12_pixel_probe.cpp b/tools/windows/d3d12_pixel_probe.cpp index bd57644..2fca777 100644 --- a/tools/windows/d3d12_pixel_probe.cpp +++ b/tools/windows/d3d12_pixel_probe.cpp @@ -25,6 +25,7 @@ const Case cases[] = { {DXGI_FORMAT_R32_FLOAT, DXGI_FORMAT_R32_FLOAT, 1, "depth-r32f"}, {DXGI_FORMAT_R16_TYPELESS, DXGI_FORMAT_R16_UNORM, 1, "depth-r16unorm"}, {DXGI_FORMAT_R24G8_TYPELESS, DXGI_FORMAT_R24_UNORM_X8_TYPELESS, 1, "depth24"}, + {DXGI_FORMAT_R32G8X24_TYPELESS, DXGI_FORMAT_R32_FLOAT_X8X24_TYPELESS, 1, "depth32-stencil8-plane0"}, {DXGI_FORMAT_R16_TYPELESS, DXGI_FORMAT_R16_FLOAT, 4, "exposure-r16f"}, {DXGI_FORMAT_R32_FLOAT, DXGI_FORMAT_R32_FLOAT, 4, "exposure-r32f"}, {DXGI_FORMAT_R16G16B16A16_FLOAT, DXGI_FORMAT_R16G16B16A16_FLOAT, 4, "exposure-rgba16f"}, @@ -46,6 +47,7 @@ ComPtr make_texture(ID3D12Device* device, DXGI_FORMAT fmt, bool D3D12_RESOURCE_DESC desc{}; desc.Dimension = D3D12_RESOURCE_DIMENSION_TEXTURE2D; desc.Width = width; desc.Height = height; desc.DepthOrArraySize = desc.MipLevels = desc.SampleDesc.Count = 1; desc.Format = fmt; desc.Flags = uav ? D3D12_RESOURCE_FLAG_ALLOW_UNORDERED_ACCESS : D3D12_RESOURCE_FLAG_NONE; + if (fmt == DXGI_FORMAT_R32G8X24_TYPELESS) desc.Flags = D3D12_RESOURCE_FLAG_ALLOW_DEPTH_STENCIL; D3D12_HEAP_PROPERTIES heap{}; heap.Type = D3D12_HEAP_TYPE_DEFAULT; heap.CreationNodeMask = heap.VisibleNodeMask = 1; ComPtr result; dx(device->CreateCommittedResource(&heap, D3D12_HEAP_FLAG_NONE, &desc, D3D12_RESOURCE_STATE_COMMON, @@ -182,6 +184,7 @@ int main(int argc, char** argv) { for (unsigned iteration = 0; iteration < args.iterations; ++iteration) for (const auto& item : cases) { PixelSpec spec = pixel_spec(item.plane, item.resource); if (!pixel_supported(item.plane, spec.storage)) throw std::runtime_error("Unsupported fixture plane"); auto input = make_texture(device.Get(), item.resource); SharedPlane shared(external, device.Get(), resource_desc(input.Get())); + std::printf("PIXEL_FOOTPRINT format=%s plane0_format=%u pitch=%u storage_bytes=%u\n", item.name, shared.footprint.Footprint.Format, shared.footprint.Footprint.RowPitch, spec.bytes); auto upload = make_buffer(device.Get(), shared.bytes, D3D12_HEAP_TYPE_UPLOAD, D3D12_HEAP_FLAG_NONE, D3D12_RESOURCE_STATE_GENERIC_READ); void* data = nullptr; D3D12_RANGE noRead{}; dx(upload->Map(0, &noRead, &data), "Pixel fixture upload"); std::memset(data, 0x5a, size_t(shared.bytes)); for (unsigned y = 0; y < height; ++y) fixture(static_cast(data) + y * shared.footprint.Footprint.RowPitch, y, spec); upload->Unmap(0, nullptr); diff --git a/tools/windows/d4r_nvngx_windows.cpp b/tools/windows/d4r_nvngx_windows.cpp index 7acfb12..765992d 100644 --- a/tools/windows/d4r_nvngx_windows.cpp +++ b/tools/windows/d4r_nvngx_windows.cpp @@ -13,6 +13,13 @@ std::unordered_map> features; std::unordered_map parameters; constexpr unsigned failure = 0xBAD00002, invalid = 0xBAD00005; unsigned call(const std::function& fn) noexcept { + static std::once_flag diagnosticOutput; + std::call_once(diagnosticOutput, [] { + if (std::getenv("D4R_DIAG_DIR")) { + std::setvbuf(stdout, nullptr, _IONBF, 0); + std::setvbuf(stderr, nullptr, _IONBF, 0); + } + }); try { return fn(); } catch (const std::exception& error) { std::fprintf(stderr, "D4R_WINDOWS_FAILURE %s\n", error.what()); return failure; diff --git a/tools/windows/diagnostic.h b/tools/windows/diagnostic.h index f1742a8..e905d14 100644 --- a/tools/windows/diagnostic.h +++ b/tools/windows/diagnostic.h @@ -142,7 +142,7 @@ struct Args { else if (key == "--stream-lifetime") stream_lifetime = value; else if (key == "--interop-mode") interop_mode = value; else if (key == "--pixel-profile") { - if (value != "baseline" && value != "packed" && value != "unorm") throw std::runtime_error("pixel-profile must be baseline, packed or unorm"); + if (value != "baseline" && value != "packed" && value != "unorm" && value != "depth-stencil") throw std::runtime_error("Invalid pixel-profile"); pixel_profile = value; } else if (key == "--barrier-mode") { diff --git a/tools/windows/pixel_conversion.h b/tools/windows/pixel_conversion.h index f18b75b..df2fc6f 100644 --- a/tools/windows/pixel_conversion.h +++ b/tools/windows/pixel_conversion.h @@ -26,6 +26,9 @@ inline PixelSpec pixel_spec(unsigned plane, DXGI_FORMAT format) { case DXGI_FORMAT_R16G16_UNORM: return {rg16unorm, 4, false}; case DXGI_FORMAT_R32_FLOAT: case DXGI_FORMAT_R32_TYPELESS: case DXGI_FORMAT_D32_FLOAT: return {r32f, 4, plane == depth || plane == exposure}; + case DXGI_FORMAT_R32G8X24_TYPELESS: case DXGI_FORMAT_D32_FLOAT_S8X24_UINT: case DXGI_FORMAT_R32_FLOAT_X8X24_TYPELESS: + if (plane != depth) throw std::runtime_error("Depth/stencil resources are supported only for the depth input"); + return {r32f, 4, true}; // D3D12 subresource 0 is the R32 depth plane. case DXGI_FORMAT_R16_FLOAT: return {r16f, 2, false}; case DXGI_FORMAT_D24_UNORM_S8_UINT: case DXGI_FORMAT_R24G8_TYPELESS: case DXGI_FORMAT_R24_UNORM_X8_TYPELESS: return {depth24, 4, false};