Run native Windows DLSS K and M through an explicit OptiScaler source backend
This commit is contained in:
1 parent
27d6a1230a
commit
e14b4cb54c
9 files changed
+633
-8
No files matched your search
@@ -147,6 +147,10 @@ endif()
|
|||||||
install(TARGETS d4r_nvapi_compat RUNTIME DESTINATION nvapi-compat)
|
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_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)
|
||||||
|
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)
|
||||||
|
install(TARGETS d4r_debug_launcher RUNTIME DESTINATION bin)
|
||||||
target_link_libraries(d4r_d3d12_command_probe PRIVATE d3d12 dxgi dxguid)
|
target_link_libraries(d4r_d3d12_command_probe PRIVATE d3d12 dxgi dxguid)
|
||||||
target_link_libraries(d4r_d3d12_pixel_probe PRIVATE d3d12 dxgi dxguid)
|
target_link_libraries(d4r_d3d12_pixel_probe PRIVATE d3d12 dxgi dxguid)
|
||||||
add_dependencies(d4r_d3d12_pixel_probe d4r_pixel_code_object)
|
add_dependencies(d4r_d3d12_pixel_probe d4r_pixel_code_object)
|
||||||
|
|||||||
@@ -14,8 +14,10 @@ submission, and returns output in the same frame without CPU image copies.
|
|||||||
RGBA16/32F, R11G11B10, RGBA/BGRA8, depth, exposure and motion formats have
|
RGBA16/32F, R11G11B10, RGBA/BGRA8, depth, exposure and motion formats have
|
||||||
independent D3D12 typed SRV/UAV conversion tests. Four-frame K/M runs with packed
|
independent D3D12 typed SRV/UAV conversion tests. Four-frame K/M runs with packed
|
||||||
output match the FP16 baseline exactly after resource-format quantisation.
|
output match the FP16 baseline exactly after resource-format quantisation.
|
||||||
The build incorporates upstream RDNA4 commit `dbef4b2`. OptiScaler/game
|
The build incorporates upstream RDNA4 commit `dbef4b2`. Patched OptiScaler now
|
||||||
integration and performance profiling remain in progress; no game support is
|
passes standalone K/M native-network tests with exact RGB agreement against
|
||||||
|
the direct-d4r baseline; see [the source patch](patches/optiscaler/README.md).
|
||||||
|
Game integration and performance profiling remain in progress; no game support is
|
||||||
claimed yet. See [build instructions and hardware results](docs/windows-rdna4-port.md).
|
claimed yet. See [build instructions and hardware results](docs/windows-rdna4-port.md).
|
||||||
|
|
||||||
See [supported games](SUPPORTED_GAMES.md) for the tested games and DLSS models.
|
See [supported games](SUPPORTED_GAMES.md) for the tested games and DLSS models.
|
||||||
|
|||||||
@@ -3,7 +3,8 @@
|
|||||||
Branch: `windows-rdna4`. Target: Windows 11 x64, RX 9070 XT, **gfx1201**.
|
Branch: `windows-rdna4`. Target: Windows 11 x64, RX 9070 XT, **gfx1201**.
|
||||||
Priority: correct K, correct M, native Windows, same-frame output, then speed.
|
Priority: correct K, correct M, native Windows, same-frame output, then speed.
|
||||||
This is a work log for an implementation in progress. Standalone DLSS K/M
|
This is a work log for an implementation in progress. Standalone DLSS K/M
|
||||||
works on the real Windows GPU; OptiScaler/game support remains unverified.
|
works on the real Windows GPU, including the patched OptiScaler frontend.
|
||||||
|
An actual game session remains unverified.
|
||||||
|
|
||||||
## Milestone and gates
|
## Milestone and gates
|
||||||
|
|
||||||
@@ -36,7 +37,7 @@ Do not integrate NGX until the integer PTX workload is stable on the real GPU.
|
|||||||
| M6: K layers, full transformer and image validation | PASS: 11 real-weight layers, 44 native launches, four D3D12 frames bit-exact against native CUDA, 81.85–92.91 dB versus translated K |
|
| M6: K layers, full transformer and image validation | PASS: 11 real-weight layers, 44 native launches, four D3D12 frames bit-exact against native CUDA, 81.85–92.91 dB versus translated K |
|
||||||
| M7: M FP16-equivalent baseline and full transformer | PASS: all blocks of 40 native launches exactly match NumPy; targeted independent PTX regression matches; four D3D12 frames bit-exact against corrected translated M |
|
| M7: M FP16-equivalent baseline and full transformer | PASS: all blocks of 40 native launches exactly match NumPy; targeted independent PTX regression matches; four D3D12 frames bit-exact against corrected translated M |
|
||||||
| M8: standalone Windows D3D12 NGX harness | PASS for ordinary recorded EvaluateFeature and explicit boundaries: K/M, four frames each, no shim CPU image copies; both bit-exact against explicit boundary baselines |
|
| M8: standalone Windows D3D12 NGX harness | PASS for ordinary recorded EvaluateFeature and explicit boundaries: K/M, four frames each, no shim CPU image copies; both bit-exact against explicit boundary baselines |
|
||||||
| M9: OptiScaler integration, profiling, installation | Pending |
|
| M9: OptiScaler integration, profiling, installation | Patched frontend K/M PASS; game, profiling and final package pending |
|
||||||
|
|
||||||
## Public NGX identity and OptiScaler gate (2026-10-01)
|
## Public NGX identity and OptiScaler gate (2026-10-01)
|
||||||
|
|
||||||
@@ -70,13 +71,43 @@ A local OptiScaler source fork starts from
|
|||||||
`45a2001303ddff632e279f77aef85ceede5832cb`. Its explicit
|
`45a2001303ddff632e279f77aef85ceede5832cb`. Its explicit
|
||||||
`[DLSS] AllowExternalBackend=true` option requires an absolute NvngxPath with
|
`[DLSS] AllowExternalBackend=true` option requires an absolute NvngxPath with
|
||||||
the `d4r_WindowsBackendVersion` ABI-1 marker; it does not spoof HIP architecture.
|
the `d4r_WindowsBackendVersion` ABI-1 marker; it does not spoof HIP architecture.
|
||||||
The marker is GPU-free and safe during discovery. The pending patch also lets
|
The marker is GPU-free and safe during discovery. The patch lets the external
|
||||||
the external backend load its own NVAPI provider through the original loader.
|
backend load its own NVAPI provider through the original loader and preserves
|
||||||
This source integration has not yet passed the frontend harness.
|
the exact locally supplied CUDA NGX core path from frontend redirection.
|
||||||
|
Physical adapter capability is queried outside DLL startup instead of using
|
||||||
|
NVIDIA architecture as a proxy. GetFeatureRequirements spoofing is bypassed.
|
||||||
|
Explicit external DLSS selection returns errors instead of silently using FSR.
|
||||||
Microsoft Build Tools 17.14 / MSVC 14.44.35207 and SDK 10.0.26100 were installed
|
Microsoft Build Tools 17.14 / MSVC 14.44.35207 and SDK 10.0.26100 were installed
|
||||||
from the signature-verified official bootstrapper without restarting Windows.
|
from the signature-verified official bootstrapper without restarting Windows.
|
||||||
No game files have been modified.
|
No game files have been modified.
|
||||||
|
|
||||||
|
The OptiScaler fork commit is `33bbac2` on `windows-rdna4-d4r`. Its reproducible
|
||||||
|
source patch and build instructions are in [patches/optiscaler](../patches/optiscaler/README.md).
|
||||||
|
`build-optiscaler-windows.ps1` produces a DLL, PDB, GPL license and dependency
|
||||||
|
metadata from pinned source/submodules; it disables upstream's unrelated
|
||||||
|
packaging post-build commands. The public D3D12 probe now supplies and submits
|
||||||
|
a real open creation command list, as required by OptiScaler's API.
|
||||||
|
|
||||||
|
`optiscaler-k-final.zip` and `optiscaler-m-final.zip` pass four complete
|
||||||
|
OptiScaler -> Windows d4r -> CUDA NGX -> ZLUDA/HIP evaluations on RX 9070 XT.
|
||||||
|
They contain 44 K and 40 M native transformer launches, respectively. Every
|
||||||
|
RGB value exactly matches the independently quantised direct-d4r baseline;
|
||||||
|
max absolute/relative error is 0. No FSR evaluation, NaN/Inf, shim CPU image copy
|
||||||
|
or previous-frame output is observed. The invalid-core regression intentionally
|
||||||
|
passes the shim as the CUDA core: it fails with an explicit frontend
|
||||||
|
`refusing FSR fallback` message and zero FSR evaluations, as required.
|
||||||
|
|
||||||
|
The loader crash was localized with a PDB to Util::IsSubpath calling
|
||||||
|
filesystem::relative/weakly_canonical from LdrLoadDll interception. The patched
|
||||||
|
check is lexical, case-insensitive and handles empty relative paths without
|
||||||
|
filesystem I/O. The separate `d4r_debug_launcher.exe`, enabled by
|
||||||
|
`-CaptureExceptions`, captures minidumps, first/second-chance exception codes,
|
||||||
|
module load/unload addresses and raw stack slots outside the hooked process.
|
||||||
|
It propagates the child's exit code and terminates its child on runner timeout
|
||||||
|
via a process lifetime job. `optiscaler-m-source-debug2.zip` contains the
|
||||||
|
successful dump capture and `optiscaler-source-stack-symbols.log` the source
|
||||||
|
attribution. The ordinary in-process crash filter remains available too.
|
||||||
|
|
||||||
## Initial PSO and enhanced texture layouts (2026-10-01)
|
## Initial PSO and enhanced texture layouts (2026-10-01)
|
||||||
|
|
||||||
The backend now captures the initial PSO supplied to public CreateCommandList,
|
The backend now captures the initial PSO supplied to public CreateCommandList,
|
||||||
|
|||||||
@@ -0,0 +1,328 @@
|
|||||||
|
From 33bbac250ac7e2ca4eff67e8817ada9dad578208 Mon Sep 17 00:00:00 2001
|
||||||
|
From: Codex <codex@localhost>
|
||||||
|
Date: Thu, 1 Oct 2026 16:10:08 +0300
|
||||||
|
Subject: [PATCH] Support explicitly selected Windows d4r DLSS backend on
|
||||||
|
physical AMD adapters
|
||||||
|
|
||||||
|
---
|
||||||
|
OptiScaler/Config.cpp | 3 ++
|
||||||
|
OptiScaler/Config.h | 1 +
|
||||||
|
OptiScaler/OptiScaler.vcxproj | 4 +--
|
||||||
|
OptiScaler/State.h | 1 +
|
||||||
|
OptiScaler/Util.cpp | 12 +++++--
|
||||||
|
OptiScaler/dllmain.cpp | 33 +++++++++++++++++--
|
||||||
|
OptiScaler/hooks/LibraryLoad_Hooks.cpp | 17 ++++++++++
|
||||||
|
OptiScaler/inputs/NVNGX_DLSS_Dx12.cpp | 17 ++++++++++
|
||||||
|
OptiScaler/misc/IdentifyGpu.cpp | 20 +++++++++++
|
||||||
|
OptiScaler/proxies/NVNGX_Proxy.h | 5 ++-
|
||||||
|
OptiScaler/upscalers/FeatureProvider_Dx12.cpp | 10 ++++++
|
||||||
|
11 files changed, 115 insertions(+), 8 deletions(-)
|
||||||
|
|
||||||
|
diff --git a/OptiScaler/Config.cpp b/OptiScaler/Config.cpp
|
||||||
|
index d8c62d3..f444387 100644
|
||||||
|
--- a/OptiScaler/Config.cpp
|
||||||
|
+++ b/OptiScaler/Config.cpp
|
||||||
|
@@ -323,6 +323,7 @@ bool Config::Reload(std::filesystem::path iniPath)
|
||||||
|
{
|
||||||
|
// Don't enable again if set false because of no nvngx found
|
||||||
|
DLSSEnabled.set_from_config(readBool("DLSS", "Enabled"));
|
||||||
|
+ DLSSAllowExternalBackend.set_from_config(readBool("DLSS", "AllowExternalBackend"));
|
||||||
|
UseGenericAppIdWithDlss.set_from_config(readBool("DLSS", "UseGenericAppIdWithDlss"));
|
||||||
|
|
||||||
|
RenderPresetOverride.set_from_config(readBool("DLSS", "RenderPresetOverride"));
|
||||||
|
@@ -1168,6 +1169,8 @@ bool Config::SaveIni()
|
||||||
|
// DLSS
|
||||||
|
{
|
||||||
|
ini.SetValue("DLSS", "Enabled", GetBoolValue(Instance()->DLSSEnabled.value_for_config()).c_str());
|
||||||
|
+ ini.SetValue("DLSS", "AllowExternalBackend",
|
||||||
|
+ GetBoolValue(Instance()->DLSSAllowExternalBackend.value_for_config()).c_str());
|
||||||
|
ini.SetValue("DLSS", "RenderPresetOverride",
|
||||||
|
GetBoolValue(Instance()->RenderPresetOverride.value_for_config()).c_str());
|
||||||
|
ini.SetValue("DLSS", "RenderPresetForAll",
|
||||||
|
diff --git a/OptiScaler/Config.h b/OptiScaler/Config.h
|
||||||
|
index 4ab62a4..0caf8c8 100644
|
||||||
|
--- a/OptiScaler/Config.h
|
||||||
|
+++ b/OptiScaler/Config.h
|
||||||
|
@@ -275,6 +275,7 @@ class Config
|
||||||
|
|
||||||
|
// DLSS
|
||||||
|
CustomOptional<bool> DLSSEnabled { true };
|
||||||
|
+ CustomOptional<bool> DLSSAllowExternalBackend { false };
|
||||||
|
CustomOptional<bool> RenderPresetOverride { false };
|
||||||
|
CustomOptional<uint32_t> RenderPresetForAll { 0 };
|
||||||
|
CustomOptional<uint32_t> RenderPresetDLAA { 0 };
|
||||||
|
diff --git a/OptiScaler/OptiScaler.vcxproj b/OptiScaler/OptiScaler.vcxproj
|
||||||
|
index 24ca80b..dfbfc97 100644
|
||||||
|
--- a/OptiScaler/OptiScaler.vcxproj
|
||||||
|
+++ b/OptiScaler/OptiScaler.vcxproj
|
||||||
|
@@ -236,7 +236,7 @@
|
||||||
|
<SubSystem>Windows</SubSystem>
|
||||||
|
<EnableCOMDATFolding>true</EnableCOMDATFolding>
|
||||||
|
<OptimizeReferences>true</OptimizeReferences>
|
||||||
|
- <GenerateDebugInformation>false</GenerateDebugInformation>
|
||||||
|
+ <GenerateDebugInformation>true</GenerateDebugInformation>
|
||||||
|
<EnableUAC>false</EnableUAC>
|
||||||
|
<AdditionalDependencies>winhttp.lib;WindowsApp.lib;Dbghelp.lib;dxgi.lib;d3d11.lib;d3d12.lib;vulkan-1.lib;dxguid.lib;freetype.lib;ffx_fsr2_api_x64.lib;ffx_fsr2_api_dx11_x64.lib;ffx_fsr2_api_dx12_x64.lib;ffx_fsr2_api_vk_x64.lib;ffx_fsr2_212_api_dx12_x64.lib;ffx_fsr2_212_api_vk_x64.lib;ffx_fsr2_212_api_x64.lib;d3dcompiler.lib;ffx_backend_dx11_x64.lib;ffx_fsr3_x64.lib;ffx_frameinterpolation_x64.lib;ffx_fsr3upscaler_x64.lib;ffx_opticalflow_x64.lib;%(AdditionalDependencies)</AdditionalDependencies>
|
||||||
|
<ModuleDefinitionFile>Source.def</ModuleDefinitionFile>
|
||||||
|
@@ -873,4 +873,4 @@ copy NUL "$(SolutionDir)x64\Release\a\!! EXTRACT ALL FILES TO GAME FOLDER !!" /Y
|
||||||
|
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
|
||||||
|
<ImportGroup Label="ExtensionTargets">
|
||||||
|
</ImportGroup>
|
||||||
|
-</Project>
|
||||||
|
\ No newline at end of file
|
||||||
|
+</Project>
|
||||||
|
diff --git a/OptiScaler/State.h b/OptiScaler/State.h
|
||||||
|
index 42164de..84f8263 100644
|
||||||
|
--- a/OptiScaler/State.h
|
||||||
|
+++ b/OptiScaler/State.h
|
||||||
|
@@ -265,6 +265,7 @@ class State
|
||||||
|
int dlssgDetectedInterpolationCount = 0;
|
||||||
|
|
||||||
|
// DLSS
|
||||||
|
+ bool dlssExternalBackend = false;
|
||||||
|
bool dlssPresetsOverriddenExternally = false;
|
||||||
|
bool dlssPresetsOverridenByOpti = false;
|
||||||
|
uint32_t dlssRenderPresetExternal = 0;
|
||||||
|
diff --git a/OptiScaler/Util.cpp b/OptiScaler/Util.cpp
|
||||||
|
index f7ac6fa..7367a91 100644
|
||||||
|
--- a/OptiScaler/Util.cpp
|
||||||
|
+++ b/OptiScaler/Util.cpp
|
||||||
|
@@ -486,8 +486,16 @@ bool Util::GetFileVersion(std::wstring dllPath, version_t* fileVersionOut, versi
|
||||||
|
|
||||||
|
bool Util::IsSubpath(const std::filesystem::path& path, const std::filesystem::path& base)
|
||||||
|
{
|
||||||
|
- auto rel = std::filesystem::relative(path, base);
|
||||||
|
- auto first = *rel.begin();
|
||||||
|
+ // Called by LdrLoadDll hooks: relative()/weakly_canonical() perform file
|
||||||
|
+ // queries, can re-enter the hooks, and throw when an optional directory or
|
||||||
|
+ // a bare DLL search name cannot be resolved. This is a lexical routing
|
||||||
|
+ // check, not a filesystem access control decision.
|
||||||
|
+ auto normalizedPath = std::filesystem::path(ToLower(path.lexically_normal().wstring()));
|
||||||
|
+ auto normalizedBase = std::filesystem::path(ToLower(base.lexically_normal().wstring()));
|
||||||
|
+ auto rel = normalizedPath.lexically_relative(normalizedBase);
|
||||||
|
+ if (rel.empty())
|
||||||
|
+ return false;
|
||||||
|
+ const auto first = *rel.begin();
|
||||||
|
return first != "." && first != "..";
|
||||||
|
}
|
||||||
|
|
||||||
|
diff --git a/OptiScaler/dllmain.cpp b/OptiScaler/dllmain.cpp
|
||||||
|
index cba14b1..b4b786e 100644
|
||||||
|
--- a/OptiScaler/dllmain.cpp
|
||||||
|
+++ b/OptiScaler/dllmain.cpp
|
||||||
|
@@ -948,7 +948,7 @@ static void CheckWorkingMode()
|
||||||
|
|
||||||
|
// NVAPI
|
||||||
|
// Doesn't seem to like GetModuleHandle for some reason, so call our load to make sure
|
||||||
|
- if (GetDllNameWModule(&nvapiNamesW) != nullptr)
|
||||||
|
+ if (!State::Instance().dlssExternalBackend && GetDllNameWModule(&nvapiNamesW) != nullptr)
|
||||||
|
{
|
||||||
|
// This hooks nvapi as well when possible
|
||||||
|
auto nvapi64 = LibraryLoadHooks::LoadNvApi();
|
||||||
|
@@ -1958,7 +1958,33 @@ BOOL APIENTRY DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserv
|
||||||
|
return nvidiaDetected;
|
||||||
|
};
|
||||||
|
|
||||||
|
- bool possibleNvidia = isNvidiaViaNvapi();
|
||||||
|
+ // Opt in only to a configured, versioned Windows d4r backend. The
|
||||||
|
+ // marker performs no HIP/NGX initialization under DLL_PROCESS_ATTACH.
|
||||||
|
+ // GPU support is still checked by its public NGX requirements API.
|
||||||
|
+ if (Config::Instance()->DLSSEnabled.value_or_default() &&
|
||||||
|
+ Config::Instance()->DLSSAllowExternalBackend.value_or_default() &&
|
||||||
|
+ Config::Instance()->NvngxPath.has_value())
|
||||||
|
+ {
|
||||||
|
+ auto backendPath = std::filesystem::path(Config::Instance()->NvngxPath.value()) / L"_nvngx.dll";
|
||||||
|
+ if (backendPath.is_absolute())
|
||||||
|
+ {
|
||||||
|
+ auto module = NtdllProxy::LoadLibraryExW_Ldr(backendPath.c_str(), nullptr,
|
||||||
|
+ LOAD_LIBRARY_SEARCH_DLL_LOAD_DIR | LOAD_LIBRARY_SEARCH_DEFAULT_DIRS);
|
||||||
|
+ if (module)
|
||||||
|
+ {
|
||||||
|
+ using BackendVersion = unsigned (*)();
|
||||||
|
+ auto version = reinterpret_cast<BackendVersion>(
|
||||||
|
+ KernelBaseProxy::GetProcAddress_()(module, "d4r_WindowsBackendVersion"));
|
||||||
|
+ State::Instance().dlssExternalBackend = version && version() == 1;
|
||||||
|
+ NtdllProxy::FreeLibrary_Ldr(module);
|
||||||
|
+ }
|
||||||
|
+ }
|
||||||
|
+ if (State::Instance().dlssExternalBackend)
|
||||||
|
+ spdlog::info("Validated external Windows d4r DLSS backend; retaining physical GPU identity");
|
||||||
|
+ else
|
||||||
|
+ spdlog::error("AllowExternalBackend requires an absolute NvngxPath containing Windows d4r ABI 1");
|
||||||
|
+ }
|
||||||
|
+ bool possibleNvidia = !State::Instance().dlssExternalBackend && isNvidiaViaNvapi();
|
||||||
|
|
||||||
|
spdlog::info("");
|
||||||
|
spdlog::info("Check for DLSS files");
|
||||||
|
@@ -1992,7 +2018,8 @@ BOOL APIENTRY DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserv
|
||||||
|
State::Instance().NVNGX_DLSSG_Path = Util::FindFilePath(exePath, "nvngx_dlssg.dll");
|
||||||
|
|
||||||
|
// Not 100% accurate for Nvidia cards without DLSS
|
||||||
|
- if (Config::Instance()->DLSSEnabled.value_or_default() && possibleNvidia)
|
||||||
|
+ if (Config::Instance()->DLSSEnabled.value_or_default() &&
|
||||||
|
+ (possibleNvidia || State::Instance().dlssExternalBackend))
|
||||||
|
{
|
||||||
|
if (State::Instance().NVNGX_DLSS_Path.has_value())
|
||||||
|
{
|
||||||
|
diff --git a/OptiScaler/hooks/LibraryLoad_Hooks.cpp b/OptiScaler/hooks/LibraryLoad_Hooks.cpp
|
||||||
|
index 90bdb89..108ea5b 100644
|
||||||
|
--- a/OptiScaler/hooks/LibraryLoad_Hooks.cpp
|
||||||
|
+++ b/OptiScaler/hooks/LibraryLoad_Hooks.cpp
|
||||||
|
@@ -46,6 +46,18 @@ HMODULE LibraryLoadHooks::LoadLibraryCheckW(std::wstring libName, LPCWSTR lpLibF
|
||||||
|
{
|
||||||
|
auto libNameA = wstring_to_string(libName);
|
||||||
|
|
||||||
|
+ // The external D3D12 shim itself uses a locally supplied CUDA NGX core.
|
||||||
|
+ // Keep that exact absolute path distinct from game-facing NGX interception.
|
||||||
|
+ if (State::Instance().dlssExternalBackend)
|
||||||
|
+ {
|
||||||
|
+ wchar_t corePath[32768] {};
|
||||||
|
+ const auto length = GetEnvironmentVariableW(L"D4R_NGX_CORE", corePath, 32768);
|
||||||
|
+ if (length && length < 32768 &&
|
||||||
|
+ Util::ToLower(std::filesystem::path(libName).lexically_normal().wstring()) ==
|
||||||
|
+ Util::ToLower(std::filesystem::path(corePath).lexically_normal().wstring()))
|
||||||
|
+ return nullptr;
|
||||||
|
+ }
|
||||||
|
+
|
||||||
|
#ifdef LOG_LIB_OPERATIONS
|
||||||
|
LOG_TRACE("{}", libNameA);
|
||||||
|
#endif
|
||||||
|
@@ -132,6 +144,11 @@ HMODULE LibraryLoadHooks::LoadLibraryCheckW(std::wstring libName, LPCWSTR lpLibF
|
||||||
|
// NvApi64.dll
|
||||||
|
if (CheckDllNameW(&libName, &nvapiNamesW))
|
||||||
|
{
|
||||||
|
+ // An explicit external CUDA backend owns its NVAPI topology provider.
|
||||||
|
+ // Returning OptiScaler's fake-NVAPI would replace that provider and can
|
||||||
|
+ // recurse into DXGI while the NGX loader holds the loader lock.
|
||||||
|
+ if (State::Instance().dlssExternalBackend)
|
||||||
|
+ return nullptr; // Continue through the original loader.
|
||||||
|
LOG_INFO("{} call!", libNameA);
|
||||||
|
|
||||||
|
return LibraryLoadHooks::LoadNvApi();
|
||||||
|
diff --git a/OptiScaler/inputs/NVNGX_DLSS_Dx12.cpp b/OptiScaler/inputs/NVNGX_DLSS_Dx12.cpp
|
||||||
|
index 68e1b42..89c9ba8 100644
|
||||||
|
--- a/OptiScaler/inputs/NVNGX_DLSS_Dx12.cpp
|
||||||
|
+++ b/OptiScaler/inputs/NVNGX_DLSS_Dx12.cpp
|
||||||
|
@@ -687,6 +687,7 @@ static NVSDK_NGX_Result TryCreateOptiFeature(ID3D12GraphicsCommandList* InCmdLis
|
||||||
|
}
|
||||||
|
|
||||||
|
// Assign handle
|
||||||
|
+ const bool allocatedHandle = *OutHandle == nullptr;
|
||||||
|
if (*OutHandle == nullptr)
|
||||||
|
*OutHandle = new NVSDK_NGX_Handle { handleId };
|
||||||
|
else
|
||||||
|
@@ -705,6 +706,16 @@ static NVSDK_NGX_Result TryCreateOptiFeature(ID3D12GraphicsCommandList* InCmdLis
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
+ if (state.dlssExternalBackend && upscalerBackend == Upscaler::DLSS)
|
||||||
|
+ {
|
||||||
|
+ LOG_ERROR("External DLSS initialization failed; refusing FSR fallback");
|
||||||
|
+ if (shouldRestoreSigs)
|
||||||
|
+ D3D12Hooks::RestoreRoot(InCmdList);
|
||||||
|
+ D3D12Hooks::SetRootSignatureTracking(true);
|
||||||
|
+ Dx12Contexts.erase(handleId);
|
||||||
|
+ if (allocatedHandle) { delete *OutHandle; *OutHandle = nullptr; }
|
||||||
|
+ return NVSDK_NGX_Result_Fail;
|
||||||
|
+ }
|
||||||
|
LOG_ERROR("Feature '{}' initialization failed falling back to FSR 2.1.2", UpscalerDisplayName(upscalerBackend));
|
||||||
|
state.newBackend = Upscaler::FSR21;
|
||||||
|
state.changeBackend[handleId] = true;
|
||||||
|
@@ -1027,6 +1038,12 @@ static NVSDK_NGX_Result TryEvaluateOptiFeature(ID3D12GraphicsCommandList* InCmdL
|
||||||
|
// Fallback to FSR 2.1.2 if feature failed to initialize and user didn't explicitly request it
|
||||||
|
if (!feature->IsInited() && cfg.Dx12Upscaler.value_or_default() != Upscaler::FSR21)
|
||||||
|
{
|
||||||
|
+ if (state.dlssExternalBackend && cfg.Dx12Upscaler.value_or_default() == Upscaler::DLSS)
|
||||||
|
+ {
|
||||||
|
+ LOG_ERROR("External DLSS feature is not initialized; refusing FSR fallback");
|
||||||
|
+ D3D12Hooks::SetRootSignatureTracking(true);
|
||||||
|
+ return NVSDK_NGX_Result_Fail;
|
||||||
|
+ }
|
||||||
|
LOG_WARN("Feature '{}' failed to initialize. Falling back to FSR 2.1.2", feature->Name());
|
||||||
|
ImGui::InsertNotification({ ImGuiToastType::Warning, 10000, "Falling back to FSR 2.1.2" });
|
||||||
|
|
||||||
|
diff --git a/OptiScaler/misc/IdentifyGpu.cpp b/OptiScaler/misc/IdentifyGpu.cpp
|
||||||
|
index 3877f9f..6c05904 100644
|
||||||
|
--- a/OptiScaler/misc/IdentifyGpu.cpp
|
||||||
|
+++ b/OptiScaler/misc/IdentifyGpu.cpp
|
||||||
|
@@ -8,6 +8,7 @@
|
||||||
|
#include <proxies/D3d12_Proxy.h>
|
||||||
|
#include "nvapi/NvApiTypes.h"
|
||||||
|
#include <hooks/Amdxc64_Hooks.h>
|
||||||
|
+#include <proxies/NVNGX_Proxy.h>
|
||||||
|
|
||||||
|
using Microsoft::WRL::ComPtr;
|
||||||
|
|
||||||
|
@@ -187,6 +188,25 @@ std::vector<GpuInformation> IdentifyGpu::checkGpuInfo()
|
||||||
|
|
||||||
|
for (auto& gpuInfo : localCachedInfo)
|
||||||
|
{
|
||||||
|
+ if (State::Instance().dlssExternalBackend && !gpuInfo.softwareAdapter)
|
||||||
|
+ {
|
||||||
|
+ // Query the configured D3D12 backend outside DLL_PROCESS_ATTACH.
|
||||||
|
+ // Preserve vendor/device/architecture data obtained from DXGI.
|
||||||
|
+ auto requirements = NVNGXProxy::D3D12_GetFeatureRequirements();
|
||||||
|
+ ComPtr<IDXGIAdapter1> physicalAdapter;
|
||||||
|
+ if (requirements && SUCCEEDED(factory->EnumAdapterByLuid(gpuInfo.luid, IID_PPV_ARGS(&physicalAdapter))))
|
||||||
|
+ {
|
||||||
|
+ NVSDK_NGX_FeatureDiscoveryInfo discovery {};
|
||||||
|
+ discovery.SDKVersion = NVSDK_NGX_Version_API;
|
||||||
|
+ discovery.FeatureID = NVSDK_NGX_Feature_SuperSampling;
|
||||||
|
+ NVSDK_NGX_FeatureRequirement supported {};
|
||||||
|
+ const auto result = requirements(physicalAdapter.Get(), &discovery, &supported);
|
||||||
|
+ gpuInfo.dlssCapable = result == NVSDK_NGX_Result_Success &&
|
||||||
|
+ supported.FeatureSupported == NVSDK_NGX_FeatureSupportResult_Supported;
|
||||||
|
+ LOG_INFO("External D3D12 DLSS backend: {} support={} result={:X}",
|
||||||
|
+ gpuInfo.name, gpuInfo.dlssCapable, (unsigned) result);
|
||||||
|
+ }
|
||||||
|
+ }
|
||||||
|
if (gpuInfo.vendorId == VendorId::Nvidia)
|
||||||
|
{
|
||||||
|
queryNvapi(gpuInfo);
|
||||||
|
diff --git a/OptiScaler/proxies/NVNGX_Proxy.h b/OptiScaler/proxies/NVNGX_Proxy.h
|
||||||
|
index f6c08e5..7892009 100644
|
||||||
|
--- a/OptiScaler/proxies/NVNGX_Proxy.h
|
||||||
|
+++ b/OptiScaler/proxies/NVNGX_Proxy.h
|
||||||
|
@@ -500,7 +500,10 @@ class NVNGXProxy
|
||||||
|
{
|
||||||
|
LOG_INFO("Getting nvngx method addresses");
|
||||||
|
|
||||||
|
- HookNgxApi(_module.dll);
|
||||||
|
+ // The external bridge reports physical adapter support itself.
|
||||||
|
+ // Do not overwrite its unsupported result with NVIDIA spoofing.
|
||||||
|
+ if (!State::Instance().dlssExternalBackend)
|
||||||
|
+ HookNgxApi(_module.dll);
|
||||||
|
|
||||||
|
_module.D3D11_Init =
|
||||||
|
(PFN_D3D11_Init) KernelBaseProxy::GetProcAddress_()(_module.dll, "NVSDK_NGX_D3D11_Init");
|
||||||
|
diff --git a/OptiScaler/upscalers/FeatureProvider_Dx12.cpp b/OptiScaler/upscalers/FeatureProvider_Dx12.cpp
|
||||||
|
index d738498..1191b29 100644
|
||||||
|
--- a/OptiScaler/upscalers/FeatureProvider_Dx12.cpp
|
||||||
|
+++ b/OptiScaler/upscalers/FeatureProvider_Dx12.cpp
|
||||||
|
@@ -42,6 +42,11 @@ bool FeatureProvider_Dx12::GetFeature(Upscaler upscaler, UINT handleId, NVSDK_NG
|
||||||
|
break;
|
||||||
|
|
||||||
|
case Upscaler::DLSS:
|
||||||
|
+ if (state.dlssExternalBackend && (!primaryGpu.dlssCapable || !state.NVNGX_DLSS_Path.has_value()))
|
||||||
|
+ {
|
||||||
|
+ LOG_ERROR("Configured external D3D12 DLSS backend is unavailable; refusing FSR fallback");
|
||||||
|
+ return false;
|
||||||
|
+ }
|
||||||
|
if (primaryGpu.dlssCapable && state.NVNGX_DLSS_Path.has_value())
|
||||||
|
{
|
||||||
|
*feature = std::make_unique<DLSSFeatureDx12>(handleId, parameters);
|
||||||
|
@@ -77,6 +82,11 @@ bool FeatureProvider_Dx12::GetFeature(Upscaler upscaler, UINT handleId, NVSDK_NG
|
||||||
|
|
||||||
|
if (!loaded)
|
||||||
|
{
|
||||||
|
+ if (state.dlssExternalBackend && upscaler == Upscaler::DLSS)
|
||||||
|
+ {
|
||||||
|
+ LOG_ERROR("External D3D12 DLSS module failed to load; refusing FSR fallback");
|
||||||
|
+ return false;
|
||||||
|
+ }
|
||||||
|
// Fail after the constructor
|
||||||
|
ImGui::InsertNotification({ ImGuiToastType::Warning, 10000, "Falling back to FSR 2.1.2" });
|
||||||
|
*feature = std::make_unique<FSR2FeatureDx12_212>(handleId, parameters);
|
||||||
|
--
|
||||||
|
2.55.0.windows.5
|
||||||
|
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
# Explicit Windows d4r backend
|
||||||
|
|
||||||
|
Base: OptiScaler `45a2001303ddff632e279f77aef85ceede5832cb` (2026-09-30).
|
||||||
|
The modifications to OptiScaler are GPL-3.0, matching its upstream license.
|
||||||
|
No NVIDIA DLL is supplied or patched.
|
||||||
|
|
||||||
|
`0001` introduces `[DLSS] AllowExternalBackend=true`, default false. Set
|
||||||
|
`[Libraries] NvngxPath` to the absolute directory containing the Windows d4r
|
||||||
|
shim as `_nvngx.dll`. Discovery requires its GPU-free ABI-1 marker. Physical
|
||||||
|
adapter support is queried through the bridge's D3D12 requirements API after
|
||||||
|
DLL startup; vendor, LUID and HIP architecture are retained.
|
||||||
|
|
||||||
|
The configured `D4R_NGX_CORE` absolute path bypasses game-facing NGX DLL
|
||||||
|
redirection. The external backend loads its own NVAPI provider. An explicitly
|
||||||
|
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.
|
||||||
|
|
||||||
|
Build with Microsoft Build Tools 2022, v143 and Windows SDK 10.0.26100:
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
.\scripts\windows\build-optiscaler-windows.ps1
|
||||||
|
```
|
||||||
|
|
||||||
|
The script clones the pinned source if absent, checks/applies the patch,
|
||||||
|
initializes pinned submodules, invokes MSBuild and writes the DLL, PDB, license
|
||||||
|
and build metadata into `dist/optiscaler-windows-d4r`. Existing source is not
|
||||||
|
reset. The upstream packaging post-build event is disabled for this build.
|
||||||
|
The official bootstrapper is https://aka.ms/vs/17/release/vs_buildtools.exe;
|
||||||
|
install the `Microsoft.VisualStudio.Workload.VCTools`,
|
||||||
|
`Microsoft.VisualStudio.Component.VC.Tools.x86.x64` and
|
||||||
|
`Microsoft.VisualStudio.Component.Windows11SDK.26100` components. The audited
|
||||||
|
local build uses MSVC 14.44.35207 / Build Tools 17.14.
|
||||||
|
|
||||||
|
For standalone verification add
|
||||||
|
`-OptiScalerDll <absolute-dist-path>/OptiScaler.dll -CaptureExceptions`
|
||||||
|
to the documented K/M runner. Its private INI selects DLSS, disables frame
|
||||||
|
generation and keeps RestoreComputeSignature, RestoreGraphicSignature and
|
||||||
|
ExtendedStateRestore false. Use `-RequireNativeNetwork`; finite output alone
|
||||||
|
cannot distinguish DLSS from an unwanted FSR replacement.
|
||||||
@@ -0,0 +1,75 @@
|
|||||||
|
[CmdletBinding()]
|
||||||
|
param(
|
||||||
|
[string]$SourceRoot,
|
||||||
|
[string]$BuildToolsRoot,
|
||||||
|
[string]$InstallRoot,
|
||||||
|
[int]$Jobs = 6
|
||||||
|
)
|
||||||
|
$ErrorActionPreference = 'Stop'
|
||||||
|
$repo = [IO.Path]::GetFullPath((Join-Path $PSScriptRoot '../..'))
|
||||||
|
if (!$SourceRoot) { $SourceRoot = Join-Path $repo 'external/OptiScaler' }
|
||||||
|
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'
|
||||||
|
$previousPath = $env:PATH
|
||||||
|
try {
|
||||||
|
# Git for Windows' submodule helper needs its POSIX utility directory.
|
||||||
|
$gitExe = (Get-Command git -ErrorAction Stop).Source
|
||||||
|
$gitInstall = [IO.Path]::GetFullPath((Join-Path (Split-Path $gitExe) '..'))
|
||||||
|
$gitUtilities = Join-Path $gitInstall 'usr/bin'
|
||||||
|
if (Test-Path -LiteralPath $gitUtilities) { $env:PATH = "$gitUtilities;$env:PATH" }
|
||||||
|
if (!(Test-Path -LiteralPath (Join-Path $SourceRoot '.git'))) {
|
||||||
|
& git clone --filter=blob:none --no-checkout https://github.com/optiscaler/OptiScaler.git $SourceRoot
|
||||||
|
if ($LASTEXITCODE) { throw 'OptiScaler clone failed.' }
|
||||||
|
& git -C $SourceRoot checkout --detach $base
|
||||||
|
if ($LASTEXITCODE) { throw 'Pinned OptiScaler checkout failed.' }
|
||||||
|
}
|
||||||
|
& 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.' }
|
||||||
|
}
|
||||||
|
& git -C $SourceRoot submodule update --init --depth 1
|
||||||
|
if ($LASTEXITCODE) { throw 'Pinned OptiScaler submodules failed.' }
|
||||||
|
if (!$BuildToolsRoot -and (Test-Path -LiteralPath (Join-Path $repo '.tools/vs2022/MSBuild/Current/Bin/MSBuild.exe'))) {
|
||||||
|
$BuildToolsRoot = Join-Path $repo '.tools/vs2022'
|
||||||
|
}
|
||||||
|
if (!$BuildToolsRoot) {
|
||||||
|
$vswhere = Join-Path ${env:ProgramFiles(x86)} 'Microsoft Visual Studio/Installer/vswhere.exe'
|
||||||
|
if (Test-Path -LiteralPath $vswhere) {
|
||||||
|
$BuildToolsRoot = & $vswhere -latest -products '*' -requires Microsoft.VisualStudio.Component.VC.Tools.x86.x64 -property installationPath
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!$BuildToolsRoot) { throw 'MSVC v143 Build Tools and Windows SDK 10.0.26100 are required; see patches/optiscaler/README.md.' }
|
||||||
|
$msbuild = Join-Path $BuildToolsRoot 'MSBuild/Current/Bin/MSBuild.exe'
|
||||||
|
if (!(Test-Path -LiteralPath $msbuild)) { throw "MSBuild missing: $msbuild" }
|
||||||
|
New-Item -ItemType Directory -Force $InstallRoot | Out-Null
|
||||||
|
$log = Join-Path $InstallRoot 'build.log'
|
||||||
|
$ErrorActionPreference = 'Continue'
|
||||||
|
& $msbuild (Join-Path $SourceRoot 'OptiScaler.sln') "/m:$Jobs" /p:Configuration=Release /p:Platform=x64 `
|
||||||
|
/p:PostBuildEventUseInBuild=false /verbosity:quiet "/flp:logfile=$log;verbosity=normal"
|
||||||
|
$ErrorActionPreference = 'Stop'
|
||||||
|
if ($LASTEXITCODE) { throw "OptiScaler build failed ($LASTEXITCODE); see $log" }
|
||||||
|
foreach ($file in @('OptiScaler.dll', 'OptiScaler.pdb')) {
|
||||||
|
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
|
||||||
|
$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;
|
||||||
|
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
|
||||||
|
Write-Host "Built OptiScaler with external Windows d4r backend: $InstallRoot"
|
||||||
|
} finally { $env:PATH = $previousPath }
|
||||||
@@ -15,6 +15,7 @@ param(
|
|||||||
[switch]$Trace,
|
[switch]$Trace,
|
||||||
[switch]$RequireNativeNetwork,
|
[switch]$RequireNativeNetwork,
|
||||||
[switch]$CommandListBackend,
|
[switch]$CommandListBackend,
|
||||||
|
[switch]$CaptureExceptions,
|
||||||
[ValidateSet('baseline','packed','unorm')][string]$PixelProfile = 'baseline',
|
[ValidateSet('baseline','packed','unorm')][string]$PixelProfile = 'baseline',
|
||||||
[ValidateSet('legacy','enhanced')][string]$BarrierMode = 'legacy',
|
[ValidateSet('legacy','enhanced')][string]$BarrierMode = 'legacy',
|
||||||
[int]$Iterations = 32,
|
[int]$Iterations = 32,
|
||||||
@@ -71,6 +72,11 @@ function Quote-Argument([string]$Value) {
|
|||||||
}
|
}
|
||||||
function Invoke-Probe([string]$Name, [string]$Exe, [string[]]$Arguments) {
|
function Invoke-Probe([string]$Name, [string]$Exe, [string[]]$Arguments) {
|
||||||
if (!(Test-Path -LiteralPath $Exe)) { throw "Executable missing: $Exe" }
|
if (!(Test-Path -LiteralPath $Exe)) { throw "Executable missing: $Exe" }
|
||||||
|
if ($CaptureExceptions) {
|
||||||
|
$Arguments = @('--output-directory', $OutputDirectory, '--', $Exe) + $Arguments
|
||||||
|
$Exe = Join-Path $PackageRoot 'bin/d4r_debug_launcher.exe'
|
||||||
|
if (!(Test-Path -LiteralPath $Exe)) { throw "Debugger launcher missing: $Exe" }
|
||||||
|
}
|
||||||
$stdout = Join-Path $OutputDirectory "$Name.stdout.log"
|
$stdout = Join-Path $OutputDirectory "$Name.stdout.log"
|
||||||
$stderr = Join-Path $OutputDirectory "$Name.stderr.log"
|
$stderr = Join-Path $OutputDirectory "$Name.stderr.log"
|
||||||
$argumentLine = ($Arguments | ForEach-Object { Quote-Argument $_ }) -join ' '
|
$argumentLine = ($Arguments | ForEach-Object { Quote-Argument $_ }) -join ' '
|
||||||
|
|||||||
@@ -193,7 +193,16 @@ int main(int argc, char** argv) {
|
|||||||
for (const char* name : {"DLSS.Hint.Render.Preset.DLAA", "DLSS.Hint.Render.Preset.Quality", "DLSS.Hint.Render.Preset.Balanced",
|
for (const char* name : {"DLSS.Hint.Render.Preset.DLAA", "DLSS.Hint.Render.Preset.Quality", "DLSS.Hint.Render.Preset.Balanced",
|
||||||
"DLSS.Hint.Render.Preset.Performance", "DLSS.Hint.Render.Preset.UltraPerformance", "DLSS.Hint.Render.Preset.UltraQuality"}) d4r_ngx_set_uint(parameters, name, args.preset);
|
"DLSS.Hint.Render.Preset.Performance", "DLSS.Hint.Render.Preset.UltraPerformance", "DLSS.Hint.Render.Preset.UltraQuality"}) d4r_ngx_set_uint(parameters, name, args.preset);
|
||||||
void* handle = nullptr;
|
void* handle = nullptr;
|
||||||
check(shim.symbol<Create>("NVSDK_NGX_D3D12_CreateFeature")(nullptr, 1, parameters, &handle), "D3D12 CreateFeature");
|
// Public NGX and OptiScaler require an open creation command list.
|
||||||
|
// Submit it independently from the pending synthetic input producer.
|
||||||
|
ComPtr<ID3D12CommandAllocator> creationAllocator;
|
||||||
|
ComPtr<ID3D12GraphicsCommandList> creationList;
|
||||||
|
dx(device->CreateCommandAllocator(D3D12_COMMAND_LIST_TYPE_DIRECT, IID_PPV_ARGS(creationAllocator.GetAddressOf())), "Create feature allocator");
|
||||||
|
dx(device->CreateCommandList(0, D3D12_COMMAND_LIST_TYPE_DIRECT, creationAllocator.Get(), nullptr,
|
||||||
|
IID_PPV_ARGS(creationList.GetAddressOf())), "Create feature command list");
|
||||||
|
check(shim.symbol<Create>("NVSDK_NGX_D3D12_CreateFeature")(creationList.Get(), 1, parameters, &handle), "D3D12 CreateFeature");
|
||||||
|
dx(creationList->Close(), "Close feature creation list");
|
||||||
|
ID3D12CommandList* creationBatch[] = {creationList.Get()}; queue->ExecuteCommandLists(1, creationBatch); drain();
|
||||||
auto release = shim.symbol<Release>("NVSDK_NGX_D3D12_ReleaseFeature");
|
auto release = shim.symbol<Release>("NVSDK_NGX_D3D12_ReleaseFeature");
|
||||||
struct FeatureCleanup { Release fn; void* h; ~FeatureCleanup() { (void)fn(h); } } featureCleanup{release, handle};
|
struct FeatureCleanup { Release fn; void* h; ~FeatureCleanup() { (void)fn(h); } } featureCleanup{release, handle};
|
||||||
static const char* names[] = {"Color", "Depth", "MotionVectors", "Output", "ExposureTexture"};
|
static const char* names[] = {"Color", "Depth", "MotionVectors", "Output", "ExposureTexture"};
|
||||||
|
|||||||
@@ -0,0 +1,130 @@
|
|||||||
|
// Out-of-process crash capture: unaffected by the frontend's loader hooks.
|
||||||
|
#include "diagnostic.h"
|
||||||
|
#include <map>
|
||||||
|
#include <fstream>
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
std::wstring quote(const std::wstring& value) {
|
||||||
|
std::wstring result = L"\""; unsigned slashes = 0;
|
||||||
|
for (wchar_t c : value) {
|
||||||
|
if (c == L'\\') { ++slashes; continue; }
|
||||||
|
result.append(slashes * (c == L'\"' ? 2 : 1), L'\\'); slashes = 0;
|
||||||
|
if (c == L'\"') result += L'\\';
|
||||||
|
result += c;
|
||||||
|
}
|
||||||
|
result.append(slashes * 2, L'\\'); return result + L'\"';
|
||||||
|
}
|
||||||
|
std::string file_path(HANDLE file) {
|
||||||
|
wchar_t path[32768]{};
|
||||||
|
const auto length = GetFinalPathNameByHandleW(file, path, 32768, FILE_NAME_NORMALIZED);
|
||||||
|
return length && length < 32768 ? d4r::diag::utf8(path) : "unknown";
|
||||||
|
}
|
||||||
|
struct Module { std::string path; uint32_t size; };
|
||||||
|
uint32_t module_size(HANDLE process, uintptr_t base) {
|
||||||
|
IMAGE_DOS_HEADER dos{}; IMAGE_NT_HEADERS64 nt{}; SIZE_T read = 0;
|
||||||
|
if (!ReadProcessMemory(process, reinterpret_cast<void*>(base), &dos, sizeof(dos), &read) ||
|
||||||
|
dos.e_magic != IMAGE_DOS_SIGNATURE || dos.e_lfanew < 0 || dos.e_lfanew > 65536 ||
|
||||||
|
!ReadProcessMemory(process, reinterpret_cast<void*>(base + dos.e_lfanew), &nt, sizeof(nt), &read) ||
|
||||||
|
nt.Signature != IMAGE_NT_SIGNATURE) return 0;
|
||||||
|
return nt.OptionalHeader.SizeOfImage;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
int main(int argc, char** argv) {
|
||||||
|
try {
|
||||||
|
if (argc < 5 || std::strcmp(argv[1], "--output-directory") || std::strcmp(argv[3], "--"))
|
||||||
|
throw std::runtime_error("Usage: d4r_debug_launcher --output-directory PATH -- EXE [arguments]");
|
||||||
|
const std::filesystem::path directory(d4r::diag::wide(argv[2]));
|
||||||
|
std::filesystem::create_directories(directory);
|
||||||
|
std::ofstream log(directory / L"debugger.log", std::ios::app);
|
||||||
|
const auto executable = d4r::diag::wide(argv[4]);
|
||||||
|
std::wstring command;
|
||||||
|
for (int i = 4; i < argc; ++i) { if (i > 4) command += L' '; command += quote(d4r::diag::wide(argv[i])); }
|
||||||
|
STARTUPINFOW startup{sizeof(startup)}; PROCESS_INFORMATION child{};
|
||||||
|
startup.dwFlags = STARTF_USESTDHANDLES;
|
||||||
|
startup.hStdInput = GetStdHandle(STD_INPUT_HANDLE);
|
||||||
|
startup.hStdOutput = GetStdHandle(STD_OUTPUT_HANDLE);
|
||||||
|
startup.hStdError = GetStdHandle(STD_ERROR_HANDLE);
|
||||||
|
HANDLE job = CreateJobObjectW(nullptr, nullptr);
|
||||||
|
JOBOBJECT_EXTENDED_LIMIT_INFORMATION limits{};
|
||||||
|
limits.BasicLimitInformation.LimitFlags = JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE;
|
||||||
|
if (!job || !SetInformationJobObject(job, JobObjectExtendedLimitInformation, &limits, sizeof(limits)))
|
||||||
|
throw std::runtime_error("Cannot create diagnostic process lifetime job");
|
||||||
|
if (!CreateProcessW(executable.c_str(), command.data(), nullptr, nullptr, TRUE,
|
||||||
|
DEBUG_ONLY_THIS_PROCESS | CREATE_NO_WINDOW, nullptr, nullptr, &startup, &child))
|
||||||
|
throw std::runtime_error("CreateProcess debugger error=" + std::to_string(GetLastError()));
|
||||||
|
if (!AssignProcessToJobObject(job, child.hProcess)) {
|
||||||
|
TerminateProcess(child.hProcess, 1); CloseHandle(job);
|
||||||
|
throw std::runtime_error("AssignProcessToJobObject debugger failed");
|
||||||
|
}
|
||||||
|
std::map<uintptr_t, Module> modules;
|
||||||
|
bool initialBreakpoint = true, dumped = false; DWORD exitCode = 1;
|
||||||
|
auto add_module = [&](void* base, HANDLE file) {
|
||||||
|
const auto address = reinterpret_cast<uintptr_t>(base);
|
||||||
|
modules[address] = {file_path(file), module_size(child.hProcess, address)};
|
||||||
|
log << "LOAD base=0x" << std::hex << address << " size=0x" << modules[address].size << " path=" << modules[address].path << std::endl;
|
||||||
|
if (file && file != INVALID_HANDLE_VALUE) CloseHandle(file);
|
||||||
|
};
|
||||||
|
auto address_info = [&](uintptr_t address) {
|
||||||
|
auto it = modules.upper_bound(address);
|
||||||
|
if (it != modules.begin()) {
|
||||||
|
--it;
|
||||||
|
if (address - it->first < it->second.size)
|
||||||
|
log << " module=" << it->second.path << "+0x" << std::hex << address - it->first;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
DEBUG_EVENT event{};
|
||||||
|
while (WaitForDebugEvent(&event, INFINITE)) {
|
||||||
|
DWORD disposition = DBG_CONTINUE;
|
||||||
|
if (event.dwDebugEventCode == CREATE_PROCESS_DEBUG_EVENT) {
|
||||||
|
add_module(event.u.CreateProcessInfo.lpBaseOfImage, event.u.CreateProcessInfo.hFile);
|
||||||
|
CloseHandle(event.u.CreateProcessInfo.hProcess); CloseHandle(event.u.CreateProcessInfo.hThread);
|
||||||
|
} else if (event.dwDebugEventCode == CREATE_THREAD_DEBUG_EVENT) CloseHandle(event.u.CreateThread.hThread);
|
||||||
|
else if (event.dwDebugEventCode == LOAD_DLL_DEBUG_EVENT) add_module(event.u.LoadDll.lpBaseOfDll, event.u.LoadDll.hFile);
|
||||||
|
else if (event.dwDebugEventCode == UNLOAD_DLL_DEBUG_EVENT) {
|
||||||
|
const auto base = reinterpret_cast<uintptr_t>(event.u.UnloadDll.lpBaseOfDll);
|
||||||
|
log << "UNLOAD base=0x" << std::hex << base << std::endl; modules.erase(base);
|
||||||
|
} else if (event.dwDebugEventCode == EXCEPTION_DEBUG_EVENT) {
|
||||||
|
const auto& exception = event.u.Exception;
|
||||||
|
const DWORD code = exception.ExceptionRecord.ExceptionCode;
|
||||||
|
log << "EXCEPTION code=0x" << std::hex << code << " firstChance=" << exception.dwFirstChance
|
||||||
|
<< " address=0x" << reinterpret_cast<uintptr_t>(exception.ExceptionRecord.ExceptionAddress) << std::endl;
|
||||||
|
disposition = DBG_EXCEPTION_NOT_HANDLED;
|
||||||
|
if (initialBreakpoint && code == EXCEPTION_BREAKPOINT) { initialBreakpoint = false; disposition = DBG_CONTINUE; }
|
||||||
|
if (!dumped && (!exception.dwFirstChance || code == EXCEPTION_ACCESS_VIOLATION || code == EXCEPTION_ILLEGAL_INSTRUCTION || code == EXCEPTION_STACK_OVERFLOW || code == EXCEPTION_IN_PAGE_ERROR)) {
|
||||||
|
dumped = true;
|
||||||
|
log << "FAULT code=0x" << std::hex << code << " address=0x" << reinterpret_cast<uintptr_t>(exception.ExceptionRecord.ExceptionAddress)
|
||||||
|
<< " thread=" << std::dec << event.dwThreadId << " firstChance=" << exception.dwFirstChance;
|
||||||
|
address_info(reinterpret_cast<uintptr_t>(exception.ExceptionRecord.ExceptionAddress)); log << std::endl;
|
||||||
|
HANDLE thread = OpenThread(THREAD_GET_CONTEXT | THREAD_QUERY_INFORMATION, FALSE, event.dwThreadId);
|
||||||
|
CONTEXT context{}; context.ContextFlags = CONTEXT_ALL;
|
||||||
|
if (thread && GetThreadContext(thread, &context)) {
|
||||||
|
log << "CONTEXT rip=0x" << std::hex << context.Rip << " rsp=0x" << context.Rsp << " rcx=0x" << context.Rcx << " rdx=0x" << context.Rdx << std::endl;
|
||||||
|
EXCEPTION_RECORD record = exception.ExceptionRecord;
|
||||||
|
EXCEPTION_POINTERS pointers{&record, &context};
|
||||||
|
MINIDUMP_EXCEPTION_INFORMATION info{event.dwThreadId, &pointers, FALSE};
|
||||||
|
const auto path = directory / (L"crash-" + std::to_wstring(child.dwProcessId) + L".dmp");
|
||||||
|
HANDLE file = CreateFileW(path.c_str(), GENERIC_WRITE, 0, nullptr, CREATE_ALWAYS, 0, nullptr);
|
||||||
|
if (file != INVALID_HANDLE_VALUE) {
|
||||||
|
const BOOL ok = MiniDumpWriteDump(child.hProcess, child.dwProcessId, file,
|
||||||
|
static_cast<MINIDUMP_TYPE>(MiniDumpNormal | MiniDumpWithThreadInfo), &info, nullptr, nullptr);
|
||||||
|
log << "MINIDUMP success=" << ok << " error=" << (ok ? 0 : GetLastError()) << std::endl; CloseHandle(file);
|
||||||
|
}
|
||||||
|
uintptr_t stack[256]{}; SIZE_T read = 0;
|
||||||
|
if (ReadProcessMemory(child.hProcess, reinterpret_cast<void*>(context.Rsp), stack, sizeof(stack), &read))
|
||||||
|
for (unsigned i = 0; i < read / sizeof(uintptr_t); ++i) {
|
||||||
|
log << "STACK slot=" << std::dec << i << " address=0x" << std::hex << stack[i]; address_info(stack[i]); log << '\n';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (thread) CloseHandle(thread); log.flush();
|
||||||
|
}
|
||||||
|
} else if (event.dwDebugEventCode == EXIT_PROCESS_DEBUG_EVENT) {
|
||||||
|
exitCode = event.u.ExitProcess.dwExitCode;
|
||||||
|
log << "EXIT code=0x" << std::hex << exitCode << std::endl;
|
||||||
|
ContinueDebugEvent(event.dwProcessId, event.dwThreadId, disposition); break;
|
||||||
|
}
|
||||||
|
ContinueDebugEvent(event.dwProcessId, event.dwThreadId, disposition);
|
||||||
|
}
|
||||||
|
CloseHandle(child.hThread); CloseHandle(child.hProcess); CloseHandle(job);
|
||||||
|
return static_cast<int>(exitCode);
|
||||||
|
} catch (const std::exception& error) { std::fprintf(stderr, "DEBUG_LAUNCH_FAILURE %s\n", error.what()); return 1; }
|
||||||
|
}
|
||||||
Reference in new issue
Block a user