diff --git a/CMakeLists.txt b/CMakeLists.txt index 32b2dea..e5bfcb8 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -146,7 +146,7 @@ if(MINGW) endif() install(TARGETS d4r_nvapi_compat RUNTIME DESTINATION nvapi-compat) target_link_libraries(d4r_d3d12_hip_interop_probe PRIVATE d3d12 dxgi dxguid) -target_link_libraries(d4r_d3d12_dlss_probe PRIVATE d3d12 dxgi dxguid) +target_link_libraries(d4r_d3d12_dlss_probe PRIVATE d3d12 dxgi dxguid d3dcompiler) add_executable(d4r_debug_launcher tools/windows/debug_launcher.cpp) target_include_directories(d4r_debug_launcher PRIVATE tools/windows) target_link_libraries(d4r_debug_launcher PRIVATE version dbghelp) diff --git a/docs/windows-rdna4-port.md b/docs/windows-rdna4-port.md index d12e23c..55c7cd6 100644 --- a/docs/windows-rdna4-port.md +++ b/docs/windows-rdna4-port.md @@ -8,6 +8,17 @@ An actual game session remains unverified. ## Milestone and gates +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 +before NGX Init, execute it in the evaluation list, and run four full frames. +Both use all native transformer layers (44 K / 40 M launches), with bit-exact +RGB against their control frames. K uses inherited legacy states; M uses +inherited enhanced layouts. The full build and all 16 CTest gates pass. Reproduce +with `-EarlyIndirectProbe` in the standalone OptiScaler runner. This avoids +the previously documented unknown-signature rejection for observed startup +objects; signatures created before the frontend is loaded remain untracked. + Current milestone: M1/M2/M4/M5 passed; native NGX Create/Evaluate on the simple requested E path passes with the user's DLLs. All 11 K layers now pass synthetic and real-weight NumPy/PTX replay checks; native K Evaluate produces four finite diff --git a/patches/optiscaler/0002-early-d3d12-command-backend.patch b/patches/optiscaler/0002-early-d3d12-command-backend.patch new file mode 100644 index 0000000..3fcd5ea --- /dev/null +++ b/patches/optiscaler/0002-early-d3d12-command-backend.patch @@ -0,0 +1,45 @@ +From 7ed1f1d66b430ba35a197fbd9880cd6a76af8f06 Mon Sep 17 00:00:00 2001 +From: Codex +Date: Thu, 1 Oct 2026 16:45:01 +0300 +Subject: [PATCH] Install the external d4r command backend before NGX + initialization + +--- + OptiScaler/hooks/D3D12_Hooks.cpp | 14 ++++++++++++++ + 1 file changed, 14 insertions(+) + +diff --git a/OptiScaler/hooks/D3D12_Hooks.cpp b/OptiScaler/hooks/D3D12_Hooks.cpp +index 29c0bec..3b757be 100644 +--- a/OptiScaler/hooks/D3D12_Hooks.cpp ++++ b/OptiScaler/hooks/D3D12_Hooks.cpp +@@ -14,6 +14,7 @@ + #include + #include + #include ++#include + + #include + +@@ -2230,6 +2231,19 @@ static void HookToDevice(ID3D12Device* InDevice) + if (Util::CheckForRealObject(__FUNCTION__, InDevice, (IUnknown**) &realDevice)) + pVTable = *(PVOID**) realDevice; + ++ if (State::Instance().dlssExternalBackend) ++ { ++ // Capture command signatures/resources created before the game's NGX ++ // Init. Install before adding OptiScaler's own method trampolines. ++ using InstallCommandBackend = unsigned (*)(ID3D12Device*); ++ auto install = reinterpret_cast(KernelBaseProxy::GetProcAddress_()( ++ NVNGXProxy::NVNGXModule(), "d4r_D3D12_InstallCommandBackend")); ++ auto result = install ? install(realDevice ? realDevice : InDevice) : 0; ++ LOG_INFO("External d4r command backend installed at device discovery: result={:X}", result); ++ if (result != 1) ++ LOG_ERROR("External d4r early command tracking failed; DLSS initialization must be checked"); ++ } ++ + // hudless + o_D3D12DeviceRelease = (PFN_Release) pVTable[2]; + o_CreateSampler = (PFN_CreateSampler) pVTable[22]; +-- +2.55.0.windows.5 + diff --git a/patches/optiscaler/README.md b/patches/optiscaler/README.md index a78aa54..c0684e9 100644 --- a/patches/optiscaler/README.md +++ b/patches/optiscaler/README.md @@ -16,6 +16,11 @@ selected external DLSS backend reports initialization/load errors instead of silently switching to FSR. The patch fixes the upstream loader's filesystem exception by making IsSubpath a lexical check, and retains a PDB for diagnosis. +`0002` installs the command backend at D3D12 device discovery, before NGX Init +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. + Build with Microsoft Build Tools 2022, v143 and Windows SDK 10.0.26100: ```powershell diff --git a/scripts/windows/build-optiscaler-windows.ps1 b/scripts/windows/build-optiscaler-windows.ps1 index 20bc250..ac9a029 100644 --- a/scripts/windows/build-optiscaler-windows.ps1 +++ b/scripts/windows/build-optiscaler-windows.ps1 @@ -12,7 +12,8 @@ if (!$InstallRoot) { $InstallRoot = Join-Path $repo 'dist/optiscaler-windows-d4r $SourceRoot = [IO.Path]::GetFullPath($SourceRoot) $InstallRoot = [IO.Path]::GetFullPath($InstallRoot) $base = '45a2001303ddff632e279f77aef85ceede5832cb' -$patch = Join-Path $repo 'patches/optiscaler/0001-windows-external-d4r-backend.patch' +$patches = @(Get-ChildItem -LiteralPath (Join-Path $repo 'patches/optiscaler') -Filter '*.patch' | Sort-Object Name) +if (!$patches.Count) { throw 'OptiScaler source patches are missing.' } $previousPath = $env:PATH try { # Git for Windows' submodule helper needs its POSIX utility directory. @@ -28,15 +29,17 @@ try { } & git -C $SourceRoot merge-base --is-ancestor $base HEAD if ($LASTEXITCODE) { throw "OptiScaler must be based on audited commit $base" } - $ErrorActionPreference = 'Continue' - & git -C $SourceRoot apply --reverse --check $patch 2>$null - $alreadyApplied = $LASTEXITCODE -eq 0 - $ErrorActionPreference = 'Stop' - if (!$alreadyApplied) { - & git -C $SourceRoot apply --check $patch - if ($LASTEXITCODE) { throw 'External backend patch conflicts with the existing source; no files changed.' } - & git -C $SourceRoot apply $patch - if ($LASTEXITCODE) { throw 'External backend patch failed.' } + foreach ($patch in $patches) { + $ErrorActionPreference = 'Continue' + & git -C $SourceRoot apply --reverse --check $patch.FullName 2>$null + $alreadyApplied = $LASTEXITCODE -eq 0 + $ErrorActionPreference = 'Stop' + if (!$alreadyApplied) { + & git -C $SourceRoot apply --check $patch.FullName + if ($LASTEXITCODE) { throw "Source patch conflicts: $($patch.Name); existing changes retained." } + & git -C $SourceRoot apply $patch.FullName + if ($LASTEXITCODE) { throw "Source patch failed: $($patch.Name)" } + } } & git -C $SourceRoot submodule update --init --depth 1 if ($LASTEXITCODE) { throw 'Pinned OptiScaler submodules failed.' } @@ -63,11 +66,11 @@ try { Copy-Item -LiteralPath (Join-Path $SourceRoot "x64/Release/$file") -Destination $InstallRoot -Force } Copy-Item -LiteralPath (Join-Path $SourceRoot 'LICENSE') -Destination (Join-Path $InstallRoot 'GPL-3.0.txt') -Force - Copy-Item -LiteralPath $patch -Destination $InstallRoot -Force + foreach ($patch in $patches) { Copy-Item -LiteralPath $patch.FullName -Destination $InstallRoot -Force } $metadata = @{base=$base; sourceCommit=(& git -C $SourceRoot rev-parse HEAD).Trim(); sourceDirty=[bool](& git -C $SourceRoot status --porcelain); backendAbi=1; buildTools=$BuildToolsRoot; msbuildVersion=(& $msbuild -version -nologo | Select-Object -Last 1); - patchSha256=(Get-FileHash -LiteralPath $patch -Algorithm SHA256).Hash; + patches=@($patches | ForEach-Object { @{name=$_.Name; sha256=(Get-FileHash -LiteralPath $_.FullName -Algorithm SHA256).Hash} }); dllSha256=(Get-FileHash -LiteralPath (Join-Path $InstallRoot 'OptiScaler.dll') -Algorithm SHA256).Hash; submodules=@(& git -C $SourceRoot submodule status)} $metadata | ConvertTo-Json -Depth 5 | Set-Content -LiteralPath (Join-Path $InstallRoot 'build-info.json') -Encoding UTF8 diff --git a/scripts/windows/test-windows-rdna4.ps1 b/scripts/windows/test-windows-rdna4.ps1 index b9ae9a1..f9331cd 100644 --- a/scripts/windows/test-windows-rdna4.ps1 +++ b/scripts/windows/test-windows-rdna4.ps1 @@ -16,6 +16,7 @@ param( [switch]$RequireNativeNetwork, [switch]$CommandListBackend, [switch]$CaptureExceptions, + [switch]$EarlyIndirectProbe, [ValidateSet('baseline','packed','unorm')][string]$PixelProfile = 'baseline', [ValidateSet('legacy','enhanced','inherited-legacy','inherited-enhanced')][string]$BarrierMode = 'legacy', [int]$Iterations = 32, @@ -311,6 +312,10 @@ DisableSplash=true $ngxArguments += @('--ngx-frontend', 'optiscaler') } $ngxArguments += @('--module', $localShim, '--pixel-profile', $PixelProfile, '--barrier-mode', $BarrierMode) + if ($EarlyIndirectProbe) { + if (!$CommandListBackend) { throw '-EarlyIndirectProbe requires -CommandListBackend' } + $ngxArguments += @('--early-indirect', '1') + } if ($CommandListBackend) { $ngxArguments += @('--interop-mode', 'command-list') } } $ngxName = if ($NgxMode -eq 'evaluate') { "ngx-evaluate-preset-$Preset" } elseif ($NgxMode -eq 'd3d12') { "d3d12-evaluate-preset-$Preset" } else { 'ngx-init' } diff --git a/tools/windows/d3d12_command_hooks.cpp b/tools/windows/d3d12_command_hooks.cpp index 920f492..aff6b20 100644 --- a/tools/windows/d3d12_command_hooks.cpp +++ b/tools/windows/d3d12_command_hooks.cpp @@ -144,6 +144,7 @@ HRESULT STDMETHODCALLTYPE hook_signature(ID3D12Device* device, const D3D12_COMMA std::memcpy(value.data() + sizeof(UINT), desc->pArgumentDescs, value.size() - sizeof(UINT)); const auto tagged = signature->SetPrivateData(signature_description_tag, UINT(value.size()), value.data()); if (FAILED(tagged)) std::fprintf(stderr, "D4R_SIGNATURE_TRACK_FAILURE result=0x%08x\n", unsigned(tagged)); + else std::fprintf(stderr, "D4R_COMMAND_SIGNATURE arguments=%u tracked=1\n", desc->NumArgumentDescs); } } catch (const std::exception& failure) { // Preserve the successful public API call. An untracked signature is diff --git a/tools/windows/d3d12_dlss_probe.cpp b/tools/windows/d3d12_dlss_probe.cpp index 6d1414c..725aaae 100644 --- a/tools/windows/d3d12_dlss_probe.cpp +++ b/tools/windows/d3d12_dlss_probe.cpp @@ -5,6 +5,7 @@ #include #include #include +#include int main(int argc, char** argv) { using namespace d4r::diag; @@ -168,6 +169,40 @@ int main(int argc, char** argv) { return 0; } Library shim(wide(args.module)); + ComPtr earlySignature; + ComPtr earlyRoot; + ComPtr earlyPso; + ComPtr earlyArguments; + if (args.early_indirect) { + if (!commandBackend) throw std::runtime_error("Early indirect diagnostic requires command-list backend"); + // Discover a device through the frontend before NGX Init. This is + // the startup order used by games which create signatures early. + ComPtr discovered; + dx(D3D12CreateDevice(adapter.Get(), D3D_FEATURE_LEVEL_12_0, IID_PPV_ARGS(discovered.GetAddressOf())), "Early frontend device discovery"); + D3D12_ROOT_SIGNATURE_DESC rootDesc{}; + ComPtr rootCode, errors, code; + dx(D3D12SerializeRootSignature(&rootDesc, D3D_ROOT_SIGNATURE_VERSION_1, rootCode.GetAddressOf(), errors.GetAddressOf()), "Early empty root signature"); + dx(device->CreateRootSignature(0, rootCode->GetBufferPointer(), rootCode->GetBufferSize(), IID_PPV_ARGS(earlyRoot.GetAddressOf())), "Early root signature"); + constexpr char shader[] = "[numthreads(1,1,1)] void main() {}"; + dx(D3DCompile(shader, sizeof(shader) - 1, "early-indirect", nullptr, nullptr, "main", "cs_5_0", 0, 0, code.GetAddressOf(), errors.ReleaseAndGetAddressOf()), "Early noop shader"); + D3D12_COMPUTE_PIPELINE_STATE_DESC pipeline{}; + pipeline.pRootSignature = earlyRoot.Get(); pipeline.CS = {code->GetBufferPointer(), code->GetBufferSize()}; + dx(device->CreateComputePipelineState(&pipeline, IID_PPV_ARGS(earlyPso.GetAddressOf())), "Early noop PSO"); + D3D12_INDIRECT_ARGUMENT_DESC argument{}; argument.Type = D3D12_INDIRECT_ARGUMENT_TYPE_DISPATCH; + D3D12_COMMAND_SIGNATURE_DESC signature{}; signature.ByteStride = 12; signature.NumArgumentDescs = 1; signature.pArgumentDescs = &argument; + dx(device->CreateCommandSignature(&signature, nullptr, IID_PPV_ARGS(earlySignature.GetAddressOf())), "Pre-NGX command signature"); + earlyArguments = make_buffer(device.Get(), 12, D3D12_HEAP_TYPE_DEFAULT, D3D12_HEAP_FLAG_NONE, D3D12_RESOURCE_STATE_COPY_DEST); + auto upload = make_buffer(device.Get(), 12, D3D12_HEAP_TYPE_UPLOAD, D3D12_HEAP_FLAG_NONE, D3D12_RESOURCE_STATE_GENERIC_READ); + void* mapped = nullptr; D3D12_RANGE noRead{}; dx(upload->Map(0, &noRead, &mapped), "Early indirect upload"); + const uint32_t dimensions[] = {0, 1, 1}; std::memcpy(mapped, dimensions, sizeof(dimensions)); upload->Unmap(0, nullptr); + ComPtr copyAllocator; ComPtr copy; + dx(device->CreateCommandAllocator(D3D12_COMMAND_LIST_TYPE_DIRECT, IID_PPV_ARGS(copyAllocator.GetAddressOf())), "Early copy allocator"); + dx(device->CreateCommandList(0, D3D12_COMMAND_LIST_TYPE_DIRECT, copyAllocator.Get(), nullptr, IID_PPV_ARGS(copy.GetAddressOf())), "Early copy list"); + copy->CopyResource(earlyArguments.Get(), upload.Get()); + transition(copy.Get(), earlyArguments.Get(), D3D12_RESOURCE_STATE_COPY_DEST, D3D12_RESOURCE_STATE_INDIRECT_ARGUMENT); + dx(copy->Close(), "Early copy close"); ID3D12CommandList* batch[] = {copy.Get()}; queue->ExecuteCommandLists(1, batch); drain(); + std::printf("D3D12_EARLY_INDIRECT signature_created_before_ngx_init=1\n"); + } using Init = unsigned(*)(unsigned long long, const wchar_t*, ID3D12Device*, unsigned, const void*); using Allocate = unsigned(*)(void**); using Destroy = unsigned(*)(void*); using Create = unsigned(*)(ID3D12GraphicsCommandList*, unsigned, void*, void**); @@ -243,6 +278,10 @@ int main(int argc, char** argv) { d4r_ngx_set_int(parameters, "Reset", frame == 0 ? 1 : 0); if (commandBackend) { dx(allocator->Reset(), "Reset(game-style allocator)"); dx(list->Reset(allocator.Get(), nullptr), "Reset(game-style list)"); + if (earlySignature) { + list->SetComputeRootSignature(earlyRoot.Get()); list->SetPipelineState(earlyPso.Get()); + list->ExecuteIndirect(earlySignature.Get(), 1, earlyArguments.Get(), 0, nullptr, 0); + } if (enhanced && !inherited) for (unsigned i = 0; i < 5; ++i) enhancedBarrier(list.Get(), textures[i].image.Get(), i, false); using RecordedEvaluate = unsigned(*)(ID3D12GraphicsCommandList*, void*, void*, void*); check(shim.symbol("NVSDK_NGX_D3D12_EvaluateFeature")(list.Get(), handle, parameters, nullptr), "D3D12 recorded same-frame Evaluate"); diff --git a/tools/windows/diagnostic.h b/tools/windows/diagnostic.h index a02a9f1..f1742a8 100644 --- a/tools/windows/diagnostic.h +++ b/tools/windows/diagnostic.h @@ -118,6 +118,7 @@ struct Args { std::string interop_mode = "roundtrip"; std::string pixel_profile = "baseline"; std::string barrier_mode = "legacy"; + bool early_indirect = false; std::string ngx_core, dlss_dll; std::string nvapi_dll; std::string ngx_abi = "driver"; @@ -148,6 +149,7 @@ struct Args { if (value != "legacy" && value != "enhanced" && value != "inherited-legacy" && value != "inherited-enhanced") throw std::runtime_error("Invalid barrier-mode"); barrier_mode = value; } + else if (key == "--early-indirect") early_indirect = value == "1"; else if (key == "--ngx-core") ngx_core = value; else if (key == "--dlss-dll") dlss_dll = value; else if (key == "--nvapi-dll") nvapi_dll = value;