Fix OptiScaler NGX routing and align marker polling defaults

- Explicitly route OptiScaler to d4r\nvngx.dll in release packages.

- Extract INI generation into a helper that preserves line endings
  and rejects missing or duplicate settings.

- Check NGX routing separately from file presence and return failure
  when the installation is incomplete or misconfigured.

- Add an optional OptiScaler patch for external DLSS backends while
  preserving AMD identity for FSR4, with matching launch guidance.

- Align portable and launcher marker polling defaults at 200 us.

- Log when d4r's model override replaces game or OptiScaler presets.

- Add configuration, package, routing and coexistence regression tests.

- Update installation documentation, add FSR/DLSS comparison videos
  and ignore local .tools artifacts.

Refs: #7, #14
This commit is contained in:
ayo committed 2026-10-02 16:32:49 -04:00
1 parent f66cccaf28
commit cb08e1c313
17 files changed
+738 -31

No files matched your search

+1
View File
@@ -24,6 +24,7 @@ __pycache__/
/vkd3d-proton.cache /vkd3d-proton.cache
.vscode/ .vscode/
videos/ videos/
.tools/
# Machine-specific notes, CI runner setup, local experiments, benchmark automation (kept locally, never published) # Machine-specific notes, CI runner setup, local experiments, benchmark automation (kept locally, never published)
/private/ /private/
+8
View File
@@ -31,6 +31,12 @@ For reference, native 2560×1440 without upscaling (the game's TSR at 100%) runs
On the same GPU DLSS starts at a disadvantage: its networks were designed for NVIDIA's tensor cores, and parts of them still run as translated NVIDIA code. How the numbers were measured, and what each optimisation contributed, is in [docs/performance.md](docs/performance.md). On the same GPU DLSS starts at a disadvantage: its networks were designed for NVIDIA's tensor cores, and parts of them still run as translated NVIDIA code. How the numbers were measured, and what each optimisation contributed, is in [docs/performance.md](docs/performance.md).
## FSR 4.1.1 vs. DLSS 4.5 comparison
- [Side-by-side comparison: FSR 4.1.1 (left) and DLSS 4.5 preset M (right)](https://cdn.ayois.gay/sk-ant-api03-4Dr0yN5YAzsuAOcyp7KEfeRyB0pbh2iAv2vfywa9QITHIbQScnVQnef9jsxJzDcvWMD13r8C1qGAoQQevjLqzGPT0bAQ9AA)
- [FSR 4.1.1 standalone video](https://cdn.ayois.gay/sk-ant-api03-YZpWqINTI2HBt6KWJTeyY2b9JJbxUTZdBVRR06KZD7b1x27QLjvQrZZX8Um5sZqkquuOlzJTHUh84FOOLsetNy-PAIiWxAA)
- [DLSS 4.5 preset M standalone video](https://cdn.ayois.gay/sk-ant-api03-0GLTr6qEYGfUKWYoZeseLdTzPslh1AvlVLdlBEUvV893bi19b7qha5wKnuQXN2tl6l4ffaRKj5warfAFDDjOlYJNYWVQ3AA)
## Known issues ## Known issues
- In some games using some models native upscaling can show visible artifacting. - In some games using some models native upscaling can show visible artifacting.
@@ -41,6 +47,8 @@ For users who prioritize fidelity over speed, set `[Kernels] PreferAccuracy = tr
d4r builds for RDNA3 and RDNA4. A newly built release compiles native DLSS 4 and 4.5 network kernels for the targets below and selects the matching set at runtime. Only the RX 7700 XT has been tested by this project on a real GPU; an external video reports DLSS 4.5 running through d4r 0.1.2 on an RX 7900 XTX. RDNA4 runtime and performance remain unverified on hardware. d4r builds for RDNA3 and RDNA4. A newly built release compiles native DLSS 4 and 4.5 network kernels for the targets below and selects the matching set at runtime. Only the RX 7700 XT has been tested by this project on a real GPU; an external video reports DLSS 4.5 running through d4r 0.1.2 on an RX 7900 XTX. RDNA4 runtime and performance remain unverified on hardware.
!WE ARE LOOKING FOR VIDEOS OF IT WORKING, IF YOU HAVE A MENTIONED HERE AND DOESNT HAVE A VIDEO PLEASE TAKE ONE!
| GPU | Chip | FP8 math | Runtime testing | | GPU | Chip | FP8 math | Runtime testing |
|---|---|---|---| |---|---|---|---|
| RX 7700 XT, RX 7800 XT, RX 7700, Radeon PRO W7700 | gfx1101 | widened to f16 | RX 7700 XT only | | RX 7700 XT, RX 7800 XT, RX 7700, Radeon PRO W7700 | gfx1101 | widened to f16 | RX 7700 XT only |
+7 -1
View File
@@ -63,7 +63,7 @@ The release zip (`scripts/package_release.sh`) is unpacked into the folder that
| File | Role | | File | Role |
|---|---| |---|---|
| `dxgi.dll`, `OptiScaler.ini` | OptiScaler 0.9.4. Its INI sets DLSS as the DX12 upscaler and `OptiDllPath=d4r`, so OptiScaler loads the shim as `d4r\nvngx.dll`. | | `dxgi.dll`, `OptiScaler.ini` | OptiScaler 0.9.4. Its INI sets DLSS as the DX12 upscaler, `NvngxPath=d4r\nvngx.dll` to select the shim explicitly, and `OptiDllPath=d4r` for the other libraries. |
| `d3d12.dll`, `d3d12core.dll` | the d4r-patched vkd3d-proton. Proton loads DLLs from the game folder before `system32`. | | `d3d12.dll`, `d3d12core.dll` | the d4r-patched vkd3d-proton. Proton loads DLLs from the game folder before `system32`. |
| `d4r\nvngx.dll`, `d4r\nvcuda.dll` | the shim and the CUDA bridge | | `d4r\nvngx.dll`, `d4r\nvcuda.dll` | the shim and the CUDA bridge |
| `d4r\zluda\libcuda.so` | ZLUDA | | `d4r\zluda\libcuda.so` | ZLUDA |
@@ -71,6 +71,12 @@ The release zip (`scripts/package_release.sh`) is unpacked into the folder that
| `d4r\d4r.ini` | this game's settings | | `d4r\d4r.ini` | this game's settings |
| `d4r\nvngx_dlss.dll`, `d4r\ngx\_nvngx.dll` | NVIDIA's files (the packager's `D4R_BUNDLE_NVIDIA=0` leaves them for the user to add) | | `d4r\nvngx_dlss.dll`, `d4r\ngx\_nvngx.dll` | NVIDIA's files (the packager's `D4R_BUNDLE_NVIDIA=0` leaves them for the user to add) |
**NGX routing.** OptiScaler tries `_nvngx.dll` before `nvngx.dll`, with system fallback for each name. `OptiDllPath=d4r` alone can therefore select Proton's system `_nvngx.dll` before reaching the shim. A file-valued `NvngxPath` takes priority on the first probe, regardless of the probe's name. It must select `d4r\nvngx.dll`, never `d4r\ngx\_nvngx.dll`: the latter is NVIDIA's core, which the shim loads internally. See OptiScaler 0.9.4's [NGX loader](https://github.com/optiscaler/OptiScaler/blob/v0.9.4/OptiScaler/proxies/NVNGX_Proxy.h) and [override handling](https://github.com/optiscaler/OptiScaler/blob/v0.9.4/OptiScaler/Util.cpp).
OptiScaler 0.9.4 checks a relative `NvngxPath` against the process working directory; it does not anchor that override to the executable like it does `OptiDllPath`. The portable INI assumes the game's working directory is the executable folder. Launchers that use another working directory need an absolute Windows filename for the shim. The Proton developer game launcher and OptiScaler harness already generate that form for `bin/d4r_nvngx.dll`.
`sh d4r/d4r-check.sh` checks file presence and the explicit NGX route separately; it does not prove runtime initialization. OptiScaler's startup `nvngx.dll not found!` warning checks the executable folder and already loaded modules, not the override. It can appear before the shim loads from `d4r/`; verify the later `NVNGXProxy::InitNVNGX Loaded from ...\d4r\nvngx.dll` line and `d4r/d4r_nvngx.log` instead. Keep the shim in `d4r/`.
**Settings.** When an `d4r.ini` sits next to the shim, the shim turns it into the same environment variables the developer launcher sets, with the release's defaults. A variable that is already set, for example in the launch options, wins. Settings for the Linux side (ZLUDA's and the bridge's) are handed to the bridge through `d4rSetEnv` right after it is loaded, before its first CUDA call. **Settings.** When an `d4r.ini` sits next to the shim, the shim turns it into the same environment variables the developer launcher sets, with the release's defaults. A variable that is already set, for example in the launch options, wins. Settings for the Linux side (ZLUDA's and the bridge's) are handed to the bridge through `d4rSetEnv` right after it is loaded, before its first CUDA call.
**Loading ROCm.** Steam runs GE-Proton 11 inside its container runtime (SteamLinuxRuntime_4), whose library search path has no ROCm. The bridge therefore: **Loading ROCm.** Steam runs GE-Proton 11 inside its container runtime (SteamLinuxRuntime_4), whose library search path has no ROCm. The bridge therefore:
+2 -1
View File
@@ -110,7 +110,7 @@ scripts/package_release.sh # -> dist/d4r-<version>.zip
The script: The script:
- builds the shim, the bridge and both fast and accuracy network-layer kernels for gfx1100–gfx1103 and gfx1200–gfx1201 by default (`D4R_GPU_ARCHS` can select fewer targets); each gfx12 target also gets an `<arch>-fp8` variant; - builds the shim, the bridge and both fast and accuracy network-layer kernels for gfx1100–gfx1103 and gfx1200–gfx1201 by default (`D4R_GPU_ARCHS` can select fewer targets); each gfx12 target also gets an `<arch>-fp8` variant;
- writes the kernel manifest from the DLLs you list (it records hashes of their PTX, nothing else); - writes the kernel manifest from the DLLs you list (it records hashes of their PTX, nothing else);
- stages OptiScaler as `dxgi.dll` with the settings in `packaging/optiscaler.settings`; - stages OptiScaler as `dxgi.dll` with the settings in `packaging/optiscaler.settings`, applied by `scripts/configure_optiscaler.py`; both full and clean ZIPs set `NvngxPath=d4r\nvngx.dll` explicitly, retaining `OptiDllPath=d4r` for other libraries;
- adds NVIDIA's two DLLs and any supplied texture kernels (built from NVIDIA's PTX by `kernels/build.sh tex`); builds accuracy texture sets with `D4R_ZLUDA_EMIT` unless marked prebuilt sets are supplied in `D4R_BUNDLE_TEX/accuracy/<target>`; - adds NVIDIA's two DLLs and any supplied texture kernels (built from NVIDIA's PTX by `kernels/build.sh tex`); builds accuracy texture sets with `D4R_ZLUDA_EMIT` unless marked prebuilt sets are supplied in `D4R_BUNDLE_TEX/accuracy/<target>`;
- zips the result together with the ZLUDA and vkd3d-proton builds, the ROCm runtime (as `d4r/rocm`), `packaging/d4r.ini`, the licenses and the patches. - zips the result together with the ZLUDA and vkd3d-proton builds, the ROCm runtime (as `d4r/rocm`), `packaging/d4r.ini`, the licenses and the patches.
@@ -143,6 +143,7 @@ Run `bash scripts/build_release_glibc241.sh` inside the container. The script ch
## Checks ## Checks
- `python3 -m unittest discover -s tests -v` includes OptiScaler INI generation, full/clean package staging and NGX-routing checker regressions. Package tests use fixture binaries and GPU-build stand-ins; real DLL loading still needs the OptiScaler/Proton harness.
- `scripts/check_environment.sh` lists the tools, GPU and Proton builds it finds. - `scripts/check_environment.sh` lists the tools, GPU and Proton builds it finds.
- The D3D12 harness (`scripts/run_d3d12_dlss_harness_proton.sh`) drives DLSS outside a game. - The D3D12 harness (`scripts/run_d3d12_dlss_harness_proton.sh`) drives DLSS outside a game.
- Native kernels have their own validation path; see [native-kernels.md](native-kernels.md). - Native kernels have their own validation path; see [native-kernels.md](native-kernels.md).
Vendored Submodule
+1
Submodule external/MinHook added at c3fcafdc10.
+10 -2
View File
@@ -78,6 +78,11 @@ Settings
some GPU time. Restart after changing it; other scenes and RDNA4 hardware still need testing. some GPU time. Restart after changing it; other scenes and RDNA4 hardware still need testing.
- OptiScaler.ini: OptiScaler's settings, such as its fps overlay and the render resolution of each - OptiScaler.ini: OptiScaler's settings, such as its fps overlay and the render resolution of each
quality mode. Press Insert in game for OptiScaler's menu. quality mode. Press Insert in game for OptiScaler's menu.
[Libraries] NvngxPath=d4r\nvngx.dll explicitly selects d4r's shim; OptiDllPath=d4r is also
needed for the other DLLs. Keep this route when restoring a customized INI during an update.
Do not point NvngxPath at d4r\ngx\_nvngx.dll: that is NVIDIA's core for the shim's internal use.
OptiScaler 0.9.4 resolves this relative override from the game's working directory. If a custom
launcher uses a different working directory, use the shim's absolute Windows filename instead.
Each game has its own copy of both files. Each game has its own copy of both files.
@@ -112,8 +117,11 @@ If something goes wrong
- d4r's log is d4r/d4r_nvngx.log. It is rewritten at every launch. Lines starting with "d4r:" and - d4r's log is d4r/d4r_nvngx.log. It is rewritten at every launch. Lines starting with "d4r:" and
"nvcuda bridge:" name missing files or libraries. "nvcuda bridge:" name missing files or libraries.
- DLSS missing in the game's menu, or no d4r_nvngx.log: check the launch options and that the - DLSS missing in the game's menu, or no d4r_nvngx.log: check the launch options and that the
files are next to the game's main .exe. For OptiScaler's own log, set LogToFile=true in files are next to the game's main .exe. Run sh d4r/d4r-check.sh: it checks file presence and
OptiScaler.ini's [Log] section. OptiScaler's explicit NGX route separately. For OptiScaler's own log, set LogToFile=true in
OptiScaler.ini's [Log] section. Its later "NVNGXProxy::InitNVNGX Loaded from" line should name
d4r\nvngx.dll, not C:\windows\system32\_nvngx.dll. The startup "nvngx.dll not found!" warning
can be expected with the shim under d4r/; do not move it into the game root to silence that check.
- A wrong image: try Model = E in d4r/d4r.ini, and report the problem with both logs. - A wrong image: try Model = E in d4r/d4r.ini, and report the problem with both logs.
- Do not use d4r in games with anti-cheat. OptiScaler's DLL injection can get an account banned. - Do not use d4r in games with anti-cheat. OptiScaler's DLL injection can get an account banned.
- Not supported: DLSS Frame Generation, DLSS Ray Reconstruction, DirectX 11 and Vulkan games. - Not supported: DLSS Frame Generation, DLSS Ray Reconstruction, DirectX 11 and Vulkan games.
+45 -2
View File
@@ -14,6 +14,15 @@ problems=0
ok() { printf ' ok %s\n' "$1"; } ok() { printf ' ok %s\n' "$1"; }
bad() { printf ' MISSING %s\n' "$1"; problems=$((problems + 1)); } bad() { printf ' MISSING %s\n' "$1"; problems=$((problems + 1)); }
note() { printf ' note %s\n' "$1"; } note() { printf ' note %s\n' "$1"; }
misconfigured() { printf ' CONFIG %s\n' "$1"; problems=$((problems + 1)); }
external_backend=$(awk '
{ sub(/\r$/, ""); line = $0; sub(/^[ \t]+/, "", line); sub(/[ \t]+$/, "", line) }
line ~ /^\[/ { dlss = (tolower(line) == "[dlss]"); next }
dlss && tolower(line) ~ /^allowexternalbackend[ \t]*=/ {
count++; sub(/^[^=]*=[ \t]*/, "", line); value = tolower(line)
}
END { if (count == 1 && value == "true") print "true" }
' "$GAME/OptiScaler.ini" 2>/dev/null)
printf 'd4r install in %s\n' "$(cd "$GAME" 2>/dev/null && pwd || echo "$GAME")" printf 'd4r install in %s\n' "$(cd "$GAME" 2>/dev/null && pwd || echo "$GAME")"
[ -d "$D4R" ] || { printf 'no d4r folder here; run this from the folder with the game .exe\n'; exit 1; } [ -d "$D4R" ] || { printf 'no d4r folder here; run this from the folder with the game .exe\n'; exit 1; }
@@ -21,6 +30,34 @@ ls "$GAME"/*.exe >/dev/null 2>&1 && ok "game executable next to d4r/" || note "n
for f in dxgi.dll OptiScaler.ini d3d12.dll d3d12core.dll d4r/nvngx.dll d4r/nvcuda.dll d4r/zluda/libcuda.so d4r/d4r.ini; do for f in dxgi.dll OptiScaler.ini d3d12.dll d3d12core.dll d4r/nvngx.dll d4r/nvcuda.dll d4r/zluda/libcuda.so d4r/d4r.ini; do
[ -f "$GAME/$f" ] && ok "$f" || bad "$f (re-extract the d4r zip)" [ -f "$GAME/$f" ] && ok "$f" || bad "$f (re-extract the d4r zip)"
done done
# File presence alone does not establish NGX routing: OptiDllPath=d4r can still
# select system32/_nvngx.dll before d4r/nvngx.dll. Check the per-DLL override.
if [ -f "$GAME/OptiScaler.ini" ]; then
nvngx_path=$(awk '
{ sub(/\r$/, ""); line = $0; sub(/^[ \t]+/, "", line); sub(/[ \t]+$/, "", line) }
line ~ /^\[/ { libraries = (tolower(line) == "[libraries]"); next }
libraries && tolower(line) ~ /^nvngxpath[ \t]*=/ {
count++; sub(/^[^=]*=[ \t]*/, "", line); value = line
}
END { if (count != 1) exit 1; print value }
' "$GAME/OptiScaler.ini") && unique_path=yes || unique_path=no
normalized_path=$(printf '%s' "$nvngx_path" | tr '\\' '/' | tr '[:upper:]' '[:lower:]')
# Also recognize an absolute Wine Z: path to this install's shim (as used
# by developer launches); do not accept an arbitrary nvngx.dll elsewhere.
game_path=$(cd "$GAME" 2>/dev/null && pwd -P)
absolute_path=$(printf 'z:%s/d4r/nvngx.dll' "$game_path" | tr '[:upper:]' '[:lower:]')
if [ "$unique_path" = yes ] && { [ "$normalized_path" = 'd4r/nvngx.dll' ] ||
[ "$normalized_path" = './d4r/nvngx.dll' ] || [ "$normalized_path" = "$absolute_path" ]; }; then
ok "OptiScaler NGX routing: NvngxPath=$nvngx_path (d4r shim)"
case "$normalized_path" in
z:*) ;;
*) [ "$external_backend" = true ] || note "relative NvngxPath requires the game's working directory to be this folder" ;;
esac
else
misconfigured 'OptiScaler NGX routing: set [Libraries] NvngxPath=d4r\nvngx.dll (exactly once); OptiDllPath alone can load system _nvngx.dll'
note 'd4r/ngx/_nvngx.dll is the NVIDIA core for the shim, not the OptiScaler target'
fi
fi
[ -f "$D4R/nvngx_dlss.dll" ] && ok "d4r/nvngx_dlss.dll (NVIDIA DLSS library)" || bad "d4r/nvngx_dlss.dll: copy NVIDIA's DLSS library here" [ -f "$D4R/nvngx_dlss.dll" ] && ok "d4r/nvngx_dlss.dll (NVIDIA DLSS library)" || bad "d4r/nvngx_dlss.dll: copy NVIDIA's DLSS library here"
[ -f "$D4R/ngx/_nvngx.dll" ] && ok "d4r/ngx/_nvngx.dll (NVIDIA NGX runtime)" || bad "d4r/ngx/_nvngx.dll: copy NVIDIA's NGX runtime here" [ -f "$D4R/ngx/_nvngx.dll" ] && ok "d4r/ngx/_nvngx.dll (NVIDIA NGX runtime)" || bad "d4r/ngx/_nvngx.dll: copy NVIDIA's NGX runtime here"
if [ -f "$D4R/nvngx_dlss.dll" ] && command -v strings >/dev/null 2>&1; then if [ -f "$D4R/nvngx_dlss.dll" ] && command -v strings >/dev/null 2>&1; then
@@ -49,5 +86,11 @@ if [ -n "$target" ]; then
else note "GPU $arch: no native kernels for it in this release (DLSS runs, much slower)"; fi else note "GPU $arch: no native kernels for it in this release (DLSS runs, much slower)"; fi
fi fi
printf '\nSteam launch options for this game:\n PROTON_FORCE_NVAPI=1 DXVK_NVAPI_GPU_ARCH=AD100 %%command%%\n' if [ "$external_backend" = true ]; then
[ "$problems" -eq 0 ] && printf '\nEverything d4r needs is in place.\n' || printf '\n%d thing(s) to fix above.\n' "$problems" note "AllowExternalBackend requires the patched OptiScaler DLL; keep native AMD identity visible for FSR4"
printf '\nSteam launch options for this patched setup:\n PROTON_ENABLE_NVAPI=1 DXVK_NVAPI_ALLOW_OTHER_DRIVERS=1 DXVK_NVAPI_GPU_ARCH=AD100 WINE_HIDE_AMD_GPU=0 %%command%%\n'
else
printf '\nSteam launch options for this game:\n PROTON_FORCE_NVAPI=1 DXVK_NVAPI_GPU_ARCH=AD100 %%command%%\n'
fi
[ "$problems" -eq 0 ] && printf '\nFiles and OptiScaler NGX routing checks passed (runtime initialization is not tested).\n' || printf '\n%d thing(s) to fix above.\n' "$problems"
[ "$problems" -eq 0 ]
+2
View File
@@ -52,6 +52,8 @@ NativeFp8 = on
MotionVectorDilation = 0 MotionVectorDilation = 0
; Keep inputs and output in video memory. Input and output readiness waits use CPU sleeps. ; Keep inputs and output in video memory. Input and output readiness waits use CPU sleeps.
VramInterop = true VramInterop = true
; Microseconds between checks for the game's input marker (default 200; 0 yields).
MarkerPollUs = 200
LinearInputs = true LinearInputs = true
ElideNgxSync = true ElideNgxSync = true
; Lets DLSS's last kernel write straight into the game's output buffer (DLSS 4 / K only). It takes ; Lets DLSS's last kernel write straight into the game's output buffer (DLSS 4 / K only). It takes
+4 -1
View File
@@ -1,9 +1,12 @@
# OptiScaler.ini values the d4r release sets (Section.Key=Value); everything else keeps OptiScaler's # OptiScaler.ini values the d4r release sets (Section.Key=Value); everything else keeps OptiScaler's
# default. scripts/package_release.sh applies them to the OptiScaler.ini of the bundled release. # default. scripts/package_release.sh applies them to the OptiScaler.ini of the bundled release.
# DLSS as the DX12 upscaler, loaded from the d4r folder (OptiScaler looks for nvngx.dll there) # DLSS as the DX12 upscaler. Select the shim by filename: OptiDllPath alone allows
# OptiScaler's first _nvngx.dll probe to load Proton's system core before trying nvngx.dll.
Upscalers.Dx12Upscaler=dlss Upscalers.Dx12Upscaler=dlss
DLSS.Enabled=true DLSS.Enabled=true
Libraries.OptiDllPath=d4r Libraries.OptiDllPath=d4r
Libraries.NvngxPath=d4r\nvngx.dll
# Do not use d4r\ngx\_nvngx.dll here: that NVIDIA core is loaded internally by the shim.
# The game's DLSS calls reach OptiScaler; d4r's own load of NVIDIA's NGX core is left alone # The game's DLSS calls reach OptiScaler; d4r's own load of NVIDIA's NGX core is left alone
Inputs.EnableDlssInputs=true Inputs.EnableDlssInputs=true
Hooks.HookOriginalNvngxOnly=true Hooks.HookOriginalNvngxOnly=true
@@ -0,0 +1,205 @@
diff --git a/OptiScaler.ini b/OptiScaler.ini
index a9b7a88..adbb206 100644
--- a/OptiScaler.ini
+++ b/OptiScaler.ini
@@ -612,6 +612,11 @@ FsrAgilitySDKUpgrade=auto
; true or false - Default (auto) is true
Enabled=auto
+; Permit an explicitly configured external NGX shim on non-Nvidia hardware.
+; NvngxPath must name an existing DLL file; keep physical GPU identity for FSR4.
+; false by default; this does not make Nvidia's driver NGX core work on AMD.
+AllowExternalBackend=false
+
; Set this to true to enable custom render preset overrides
; true or false - Default (auto) is false
RenderPresetOverride=auto
diff --git a/OptiScaler/Config.cpp b/OptiScaler/Config.cpp
index d0265c4..5403430 100644
--- a/OptiScaler/Config.cpp
+++ b/OptiScaler/Config.cpp
@@ -263,6 +263,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"));
@@ -998,6 +999,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 d7909b7..0225c87 100644
--- a/OptiScaler/Config.h
+++ b/OptiScaler/Config.h
@@ -218,6 +218,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/dllmain.cpp b/OptiScaler/dllmain.cpp
index 7580c65..e5f047c 100644
--- a/OptiScaler/dllmain.cpp
+++ b/OptiScaler/dllmain.cpp
@@ -1,4 +1,5 @@
#include "pch.h"
+#include "spoofing/BackendIdentity.h"
#include "dllmain.h"
#include "Util.h"
@@ -1977,9 +1978,29 @@ BOOL APIENTRY DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserv
spdlog::info("");
State::Instance().isRunningOnNvidia = isNvidia();
- if (State::Instance().isRunningOnNvidia)
+ // External NGX backends (e.g. d4r) do not require a physical Nvidia GPU.
+ // Keep isRunningOnNvidia truthful so FSR4 and other hardware checks remain valid.
+ bool externalBackendFile = false;
+ if (Config::Instance()->DLSSAllowExternalBackend.value_or_default() &&
+ Config::Instance()->NvngxPath.has_value())
+ {
+ auto backendPath = std::filesystem::path(Config::Instance()->NvngxPath.value());
+ if (backendPath.is_relative())
+ backendPath = Util::ExePath().parent_path() / backendPath;
+ std::error_code error;
+ externalBackendFile = std::filesystem::is_regular_file(backendPath, error);
+ if (externalBackendFile)
+ Config::Instance()->NvngxPath.set_volatile_value(backendPath.wstring());
+ else
+ spdlog::warn("External DLSS requires NvngxPath to name an existing shim DLL file");
+ }
+
+ if (BackendIdentity::AllowDlss(State::Instance().isRunningOnNvidia,
+ Config::Instance()->DLSSAllowExternalBackend.value_or_default(),
+ externalBackendFile))
{
- spdlog::info("Running on Nvidia");
+ spdlog::info(State::Instance().isRunningOnNvidia ? "Running on Nvidia" :
+ "External DLSS backend enabled; preserving physical GPU identity for FSR4");
if (!State::Instance().NVNGX_DLSS_Path.has_value() &&
Config::Instance()->NVNGX_DLSS_Library.has_value())
@@ -2020,7 +2041,7 @@ BOOL APIENTRY DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserv
Config::Instance()->DLSSEnabled.set_volatile_value(false);
}
- if (!Config::Instance()->DxgiSpoofing.has_value())
+ if (State::Instance().isRunningOnNvidia && !Config::Instance()->DxgiSpoofing.has_value())
{
spdlog::info("Disabling DxgiSpoofing");
Config::Instance()->DxgiSpoofing.set_volatile_value(false);
diff --git a/OptiScaler/spoofing/BackendIdentity.h b/OptiScaler/spoofing/BackendIdentity.h
new file mode 100644
index 0000000..c0e6a19
--- /dev/null
+++ b/OptiScaler/spoofing/BackendIdentity.h
@@ -0,0 +1,41 @@
+#pragma once
+
+#include <string_view>
+
+// Physical hardware identity is independent of external DLSS availability.
+namespace BackendIdentity
+{
+inline bool AllowDlss(bool isNvidia, bool allowExternalBackend, bool explicitBackendFile)
+{
+ return isNvidia || (allowExternalBackend && explicitBackendFile);
+}
+
+inline bool IsAmdFsrCaller(std::string_view caller)
+{
+ constexpr std::string_view modules[] = {
+ "amdxc64.dll", "amdxcffx64.dll", "amd_fidelityfx_dx12.dll",
+ "amd_fidelityfx_loader_dx12.dll", "amd_fidelityfx_upscaler_dx12.dll",
+ "amd_fidelityfx_framegeneration_dx12.dll", "amd_fidelityfx_denoiser_dx12.dll",
+ "amd_fidelityfx_radiancecache_dx12.dll"
+ };
+ for (auto module : modules)
+ {
+ if (caller.size() != module.size())
+ continue;
+ bool match = true;
+ for (size_t index = 0; index < caller.size(); ++index)
+ {
+ const char c = caller[index];
+ const char lower = c >= 'A' && c <= 'Z' ? c + ('a' - 'A') : c;
+ if (lower != module[index])
+ {
+ match = false;
+ break;
+ }
+ }
+ if (match)
+ return true;
+ }
+ return false;
+}
+}
diff --git a/OptiScaler/spoofing/Dxgi_Spoofing.cpp b/OptiScaler/spoofing/Dxgi_Spoofing.cpp
index 41b61b6..bd98862 100644
--- a/OptiScaler/spoofing/Dxgi_Spoofing.cpp
+++ b/OptiScaler/spoofing/Dxgi_Spoofing.cpp
@@ -1,5 +1,6 @@
#include "pch.h"
#include "Dxgi_Spoofing.h"
+#include "BackendIdentity.h"
#include <Config.h>
@@ -48,7 +49,7 @@ HRESULT DxgiSpoofing::hkGetDesc3(IDXGIAdapter4* This, DXGI_ADAPTER_DESC3* pDesc)
auto caller = Util::WhoIsTheCaller(_ReturnAddress());
- if (iequals(caller, "fakenvapi.dll") || iequals(caller, "vulkan-1.dll") || iequals(caller, "amdvlk64.dll") ||
+ if (BackendIdentity::IsAmdFsrCaller(caller) || iequals(caller, "fakenvapi.dll") || iequals(caller, "vulkan-1.dll") || iequals(caller, "amdvlk64.dll") ||
iequals(caller, "dxgi.dll") || iequals(caller, "d3d12.dll") || iequals(caller, "d3d12Core.dll"))
{
return result;
@@ -109,7 +110,7 @@ HRESULT DxgiSpoofing::hkGetDesc2(IDXGIAdapter2* This, DXGI_ADAPTER_DESC2* pDesc)
auto caller = Util::WhoIsTheCaller(_ReturnAddress());
- if (iequals(caller, "fakenvapi.dll") || iequals(caller, "vulkan-1.dll") || iequals(caller, "amdvlk64.dll") ||
+ if (BackendIdentity::IsAmdFsrCaller(caller) || iequals(caller, "fakenvapi.dll") || iequals(caller, "vulkan-1.dll") || iequals(caller, "amdvlk64.dll") ||
iequals(caller, "dxgi.dll") || iequals(caller, "d3d12.dll") || iequals(caller, "d3d12Core.dll"))
{
return result;
@@ -170,7 +171,7 @@ HRESULT DxgiSpoofing::hkGetDesc1(IDXGIAdapter1* This, DXGI_ADAPTER_DESC1* pDesc)
auto caller = Util::WhoIsTheCaller(_ReturnAddress());
- if (iequals(caller, "fakenvapi.dll") || iequals(caller, "vulkan-1.dll") || iequals(caller, "amdvlk64.dll") ||
+ if (BackendIdentity::IsAmdFsrCaller(caller) || iequals(caller, "fakenvapi.dll") || iequals(caller, "vulkan-1.dll") || iequals(caller, "amdvlk64.dll") ||
iequals(caller, "dxgi.dll") || iequals(caller, "d3d12.dll") || iequals(caller, "d3d12Core.dll"))
{
return result;
@@ -231,7 +232,7 @@ HRESULT DxgiSpoofing::hkGetDesc(IDXGIAdapter* This, DXGI_ADAPTER_DESC* pDesc)
auto caller = Util::WhoIsTheCaller(_ReturnAddress());
- if (iequals(caller, "fakenvapi.dll") || iequals(caller, "vulkan-1.dll") || iequals(caller, "amdvlk64.dll") ||
+ if (BackendIdentity::IsAmdFsrCaller(caller) || iequals(caller, "fakenvapi.dll") || iequals(caller, "vulkan-1.dll") || iequals(caller, "amdvlk64.dll") ||
iequals(caller, "dxgi.dll") || iequals(caller, "d3d12.dll") || iequals(caller, "d3d12Core.dll"))
{
return result;
diff --git a/OptiScaler/upscalers/fsr31/FSR31Feature_Dx12.cpp b/OptiScaler/upscalers/fsr31/FSR31Feature_Dx12.cpp
--- a/OptiScaler/upscalers/fsr31/FSR31Feature_Dx12.cpp
+++ b/OptiScaler/upscalers/fsr31/FSR31Feature_Dx12.cpp
@@ -848,6 +848,7 @@
State::Instance().ffxUpscalerVersionNames[Config::Instance()->FfxUpscalerIndex.value_or_default()];
_name = "FSR";
parse_version(version);
+ LOG_INFO("FSR context initialized: provider {}, versionId {:#x}", version, override.versionId);
}
SetInit(true);
+43
View File
@@ -0,0 +1,43 @@
#!/usr/bin/env python3
"""Apply release settings to the supplied OptiScaler INI, preserving its defaults/CRLF."""
import re
import sys
from pathlib import Path
def configure(source, settings):
lines = source.split("\n")
for setting in settings.splitlines():
setting = setting.strip()
if not setting or setting.startswith("#"):
continue
path, value = setting.split("=", 1)
section, key = path.split(".", 1)
starts = [i for i, line in enumerate(lines) if line.strip().casefold() == f"[{section}]".casefold()]
if len(starts) != 1:
raise ValueError(f"OptiScaler.ini must have exactly one [{section}]")
start = starts[0]
end = next((i for i in range(start + 1, len(lines)) if lines[i].lstrip().startswith("[")), len(lines))
matches = [i for i in range(start + 1, end)
if re.match(rf"\s*{re.escape(key)}\s*=", lines[i], re.IGNORECASE)]
if len(matches) != 1:
raise ValueError(f"OptiScaler.ini must have exactly one {key} in [{section}]")
i = matches[0]
lines[i] = f"{key}={value}" + ("\r" if lines[i].endswith("\r") else "")
return "\n".join(lines)
def main():
source, settings, output = map(Path, sys.argv[1:])
try:
# read_text() would translate CRLF before we could preserve it.
with source.open(encoding="utf-8", newline="") as handle:
result = configure(handle.read(), settings.read_text(encoding="utf-8"))
except ValueError as error:
sys.exit(str(error))
with output.open("w", encoding="utf-8", newline="") as handle:
handle.write(result)
if __name__ == "__main__":
main()
+1 -1
View File
@@ -146,7 +146,7 @@ def main(argv):
value = flag("Interop", key) value = flag("Interop", key)
if value is not None: if value is not None:
env[var] = "1" if value else "0" env[var] = "1" if value else "0"
value = get("Interop", "MarkerPollUs") value = get("Interop", "MarkerPollUs") or "200"
dilation = get("Interop", "MotionVectorDilation") dilation = get("Interop", "MotionVectorDilation")
if dilation is not None and dilation not in ("0", "1", "2"): if dilation is not None and dilation not in ("0", "1", "2"):
fail("[Interop] MotionVectorDilation must be 0, 1 or 2") fail("[Interop] MotionVectorDilation must be 0, 1 or 2")
+2 -21
View File
@@ -77,27 +77,8 @@ else
esac esac
fi fi
cp "$OPTI/OptiScaler.dll" "$STAGE/dxgi.dll" cp "$OPTI/OptiScaler.dll" "$STAGE/dxgi.dll"
python3 - "$OPTI/OptiScaler.ini" "$ROOT/packaging/optiscaler.settings" "$STAGE/OptiScaler.ini" <<'PY' python3 "$ROOT/scripts/configure_optiscaler.py" \
import re, sys "$OPTI/OptiScaler.ini" "$ROOT/packaging/optiscaler.settings" "$STAGE/OptiScaler.ini"
lines = open(sys.argv[1], encoding="utf-8", newline="").read().split("\n") # keeps the file's line endings
for setting in open(sys.argv[2], encoding="utf-8"):
setting = setting.strip()
if not setting or setting.startswith("#"):
continue
path, value = setting.split("=", 1)
section, key = path.split(".", 1)
start = next((i for i, l in enumerate(lines) if l.strip() == f"[{section}]"), None)
if start is None:
sys.exit(f"OptiScaler.ini has no [{section}]")
end = next((i for i in range(start + 1, len(lines)) if lines[i].startswith("[")), len(lines))
for i in range(start + 1, end):
if re.match(rf"{re.escape(key)}\s*=", lines[i]):
lines[i] = f"{key}={value}" + ("\r" if lines[i].endswith("\r") else "")
break
else:
sys.exit(f"OptiScaler.ini has no {key} in [{section}]")
open(sys.argv[3], "w", encoding="utf-8", newline="").write("\n".join(lines))
PY
# d4r-patched vkd3d-proton: same-frame DLSS results # d4r-patched vkd3d-proton: same-frame DLSS results
cp "$VKD3D/d3d12.dll" "$VKD3D/d3d12core.dll" "$STAGE/" cp "$VKD3D/d3d12.dll" "$VKD3D/d3d12core.dll" "$STAGE/"
+80
View File
@@ -0,0 +1,80 @@
"""CPU regression checks for the upstream OptiScaler coexistence patch."""
from pathlib import Path
import subprocess
import tempfile
import unittest
ROOT = Path(__file__).resolve().parents[1]
PATCH = ROOT / 'patches/optiscaler/0001-proton-external-dlss-fsr4-coexistence.patch'
class OptiScalerCoexistenceTests(unittest.TestCase):
def test_backend_identity_policy(self):
patch = PATCH.read_text()
start = patch.index('diff --git a/OptiScaler/spoofing/BackendIdentity.h ')
section = patch[start:].split('\ndiff --git ', 1)[0]
# Compile the exact added header that an upstream build will consume.
header = '\n'.join(line[1:] for line in section.splitlines()
if line.startswith('+') and not line.startswith('+++'))
code = r'''
#include "BackendIdentity.h"
#include <cassert>
int main() {
for (bool nvidia : {false, true})
for (bool optedIn : {false, true})
for (bool file : {false, true})
assert(BackendIdentity::AllowDlss(nvidia, optedIn, file) ==
(nvidia || (optedIn && file)));
const char* amd[] = {"amdxc64.dll", "AMDXCFFX64.DLL", "amd_fidelityfx_dx12.dll",
"amd_fidelityfx_loader_dx12.dll", "amd_fidelityfx_upscaler_dx12.dll",
"amd_fidelityfx_framegeneration_dx12.dll", "amd_fidelityfx_denoiser_dx12.dll",
"amd_fidelityfx_radiancecache_dx12.dll"};
for (auto caller : amd) assert(BackendIdentity::IsAmdFsrCaller(caller));
const char* game[] = {"Game.exe", "Game-Win64-Shipping.exe", "nvngx.dll",
"nvngx_dlss.dll", "notamdxc64.dll", "amdxc64.dll.exe", "", "AMDXC64"};
for (auto caller : game) assert(!BackendIdentity::IsAmdFsrCaller(caller));
}
'''
with tempfile.TemporaryDirectory() as temporary:
directory = Path(temporary)
(directory / 'BackendIdentity.h').write_text(header)
runner = directory / 'identity'
subprocess.run(['c++', '-std=c++17', '-Wall', '-Wextra', '-Werror', '-I', temporary,
'-x', 'c++', '-', '-o', str(runner)], input=code,
text=True, capture_output=True, check=True)
subprocess.run([str(runner)], check=True)
def test_upstream_integration_preserves_hardware_identity(self):
additions = '\n'.join(line[1:] for line in PATCH.read_text().splitlines()
if line.startswith('+') and not line.startswith('+++'))
self.assertEqual(additions.count('BackendIdentity::IsAmdFsrCaller(caller)'), 4)
self.assertIn('DLSSAllowExternalBackend { false }', additions)
self.assertIn('readBool("DLSS", "AllowExternalBackend")', additions)
self.assertIn('ini.SetValue("DLSS", "AllowExternalBackend"', additions)
self.assertIn('std::filesystem::is_regular_file(backendPath, error)', additions)
self.assertIn('backendPath = Util::ExePath().parent_path() / backendPath', additions)
self.assertIn('NvngxPath.set_volatile_value(backendPath.wstring())', additions)
self.assertIn('if (State::Instance().isRunningOnNvidia && !Config::Instance()->DxgiSpoofing.has_value())', additions)
self.assertNotIn('isRunningOnNvidia = true', additions)
self.assertNotIn('isRunningOnRDNA4 = true', additions)
def test_checker_launch_options_follow_external_backend_config(self):
with tempfile.TemporaryDirectory() as temporary:
game = Path(temporary)
(game / 'd4r').mkdir()
for flag in ('true', 'false', 'auto'):
(game / 'OptiScaler.ini').write_bytes(
(f'[DLSS]\r\nAllowExternalBackend={flag}\r\n'
'[Libraries]\r\nNvngxPath=d4r\\nvngx.dll\r\n').encode())
result = subprocess.run(['sh', str(ROOT / 'packaging/d4r-check.sh'), temporary],
text=True, capture_output=True)
if flag == 'true':
self.assertIn('WINE_HIDE_AMD_GPU=0 %command%', result.stdout)
self.assertNotIn('PROTON_FORCE_NVAPI=1', result.stdout)
self.assertNotIn('requires the game\'s working directory', result.stdout)
else:
self.assertIn('PROTON_FORCE_NVAPI=1', result.stdout)
if __name__ == '__main__':
unittest.main()
+228
View File
@@ -0,0 +1,228 @@
"""Release NGX routing: real INI generation, full/clean ZIP staging, install diagnostics.
Package tests use disposable runtime files and stand-ins for GPU kernel compilation.
Actual OptiScaler/Proton loading additionally needs the D3D12 harness.
"""
import configparser
import os
from pathlib import Path
import shutil
import subprocess
import tempfile
import unittest
import zipfile
ROOT = Path(__file__).resolve().parents[1]
SETTINGS = ROOT / 'packaging/optiscaler.settings'
GENERATOR = ROOT / 'scripts/configure_optiscaler.py'
def upstream_ini():
sections = {}
for line in SETTINGS.read_text().splitlines():
if not line or line.startswith('#'):
continue
path, _ = line.split('=', 1)
section, key = path.split('.', 1)
sections.setdefault(section, []).append(key)
# Keep NvngxPath independent of the settings file so removing the release
# override reproduces the original bug rather than removing the fixture key.
if 'NvngxPath' not in sections['Libraries']:
sections['Libraries'].append('NvngxPath')
return '; upstream comment\n' + ''.join(
f'[{section}]\n' + ''.join(f'{key}=auto\n' for key in keys)
for section, keys in sections.items()) + '[Menu]\nShowFps=true\n'
class OptiScalerConfigTests(unittest.TestCase):
def generate(self, directory, source):
source_path = directory / 'upstream.ini'
output = directory / 'OptiScaler.ini'
source_path.write_bytes(source.encode())
result = subprocess.run(['python3', str(GENERATOR), str(source_path), str(SETTINGS), str(output)],
capture_output=True, text=True)
return result, output
def assert_routing(self, data):
ini = configparser.ConfigParser()
ini.read_string(data.decode())
self.assertEqual(ini['Libraries']['NvngxPath'], r'd4r\nvngx.dll')
self.assertEqual(ini['Libraries']['OptiDllPath'], 'd4r')
self.assertEqual(ini['Upscalers']['Dx12Upscaler'], 'dlss')
self.assertEqual(ini['DLSS']['Enabled'], 'true')
self.assertEqual(ini['Hooks']['HookOriginalNvngxOnly'], 'true')
self.assertEqual(ini['Menu']['ShowFps'], 'true')
def test_generated_config_and_line_endings(self):
for newline in ('\n', '\r\n'):
with self.subTest(newline=repr(newline)), tempfile.TemporaryDirectory() as temporary:
source = upstream_ini().replace('\n', newline)
result, output = self.generate(Path(temporary), source)
self.assertEqual(result.returncode, 0, result.stderr)
data = output.read_bytes()
self.assert_routing(data)
self.assertTrue(data.startswith(b'; upstream comment' + newline.encode()))
self.assertEqual(data.count(b'\r\n'), data.count(b'\n') if newline == '\r\n' else 0)
self.assertEqual(source.count(newline), data.count(newline.encode()))
def test_missing_or_duplicate_override_fails_without_output(self):
for replacement in ('', 'NvngxPath=auto\nnvngxpath=d4r\\ngx\\_nvngx.dll\n'):
with self.subTest(replacement=replacement), tempfile.TemporaryDirectory() as temporary:
result, output = self.generate(Path(temporary), upstream_ini().replace('NvngxPath=auto\n', replacement))
self.assertNotEqual(result.returncode, 0)
self.assertIn('exactly one NvngxPath', result.stderr)
self.assertFalse(output.exists())
def test_missing_or_duplicate_libraries_section_fails(self):
for source in (upstream_ini().replace('[Libraries]', '[Other]'),
upstream_ini() + '[libraries]\nNvngxPath=auto\n'):
with tempfile.TemporaryDirectory() as temporary:
result, output = self.generate(Path(temporary), source)
self.assertNotEqual(result.returncode, 0)
self.assertIn('exactly one [Libraries]', result.stderr)
self.assertFalse(output.exists())
def test_full_and_clean_release_archives(self):
with tempfile.TemporaryDirectory() as temporary:
repo = Path(temporary) / 'repo'
inputs = Path(temporary) / 'inputs'
inputs.mkdir()
(repo / 'scripts').mkdir(parents=True)
for name in ('package_release.sh', 'configure_optiscaler.py', 'check_glibc_compat.sh'):
shutil.copy2(ROOT / 'scripts' / name, repo / 'scripts' / name)
shutil.copytree(ROOT / 'packaging', repo / 'packaging')
for name in ('LICENSE', 'NOTICE'):
shutil.copy2(ROOT / name, repo / name)
for component in ('zluda', 'vkd3d-proton'):
(repo / 'patches' / component).mkdir(parents=True)
(repo / 'patches' / component / 'fixture.patch').write_text('fixture\n')
# All shell staging, INI generation, ABI checks and archiving are real;
# replace only expensive GPU builds/manifests with fixture artifacts.
(repo / 'kernels/tools').mkdir(parents=True)
kernel_build = repo / 'kernels/build.sh'
kernel_build.write_text('#!/bin/sh\nmkdir -p "$2"\nprintf fixture > "$2/layer.hsaco"\n')
kernel_build.chmod(0o755)
(repo / 'kernels/tools/kernel_manifest.py').write_text(
'import pathlib, sys\npathlib.Path(sys.argv[1], "d4r-kernels.txt").write_text("layer 1234\\n")\n')
def fixture(path, data=b'fixture'):
path.parent.mkdir(parents=True, exist_ok=True)
path.write_bytes(data)
return path
# A real minimal ELF lets the package's existing GLIBC checker run.
elf = inputs / 'library.so'
subprocess.run(['cc', '-shared', '-x', 'c', '-o', str(elf), '-'],
input='int fixture(void) { return 0; }\n', text=True, check=True)
fixture(repo / 'build/d4r_nvngx.dll')
fixture(repo / 'build/wine-nvcuda/x86_64-unix/nvcuda.dll.so', elf.read_bytes())
fixture(inputs / 'zluda/libnvcuda.so', elf.read_bytes())
fixture(inputs / 'rocm/lib/libamdhip64.so.7', elf.read_bytes())
fixture(inputs / 'rocm/licenses/runtime.txt')
for name in ('d3d12.dll', 'd3d12core.dll'):
fixture(inputs / 'vkd3d' / name)
fixture(inputs / 'opti/OptiScaler.dll')
fixture(inputs / 'opti/OptiScaler.ini', upstream_ini().replace('\n', '\r\n').encode())
dlss = fixture(inputs / 'nvngx_dlss.dll')
core = fixture(inputs / '_nvngx.dll')
license_path = fixture(inputs / 'license.txt')
for name in ('LICENSE-APACHE', 'LICENSE-MIT', 'ext/llvm-project/llvm/LICENSE.TXT', 'LICENSE', 'COPYING'):
fixture(inputs / 'source' / name)
# Prebuilt L texture names keep the full flow from calling an emitter.
names = ['rrlite_dec0_4x4']
for mv in ('mvhi', 'mvlo'):
for dynamic_range in ('hdr', 'ldr'):
names.append(f'rrlite_enc0_4x4_{mv}_{dynamic_range}')
names.extend(f'rrlite_post_{v}_{mv}_{dynamic_range}' for v in ('3_1', '3_2'))
for prefix in ('gfx1101', 'accuracy/gfx1101'):
for name in names:
fixture(inputs / 'tex' / prefix / (name + '.hsaco'))
fixture(inputs / 'tex' / prefix / 'd4r-accuracy.txt', b'1\n')
env = {k: v for k, v in os.environ.items() if not k.startswith('D4R_')}
env.update(D4R_SKIP_BUILD='1', D4R_GPU_ARCHS='gfx1101', SOURCE_DATE_EPOCH='1700000000',
D4R_OPTISCALER=str(inputs / 'opti'), D4R_DLSS_DLLS=str(dlss),
D4R_ZLUDA_DIR=str(inputs / 'zluda'), D4R_VKD3D_DIR=str(inputs / 'vkd3d'),
D4R_ROCM_RUNTIME=str(inputs / 'rocm'), D4R_OPTISCALER_LICENSE=str(license_path),
D4R_ZLUDA_SRC=str(inputs / 'source'), D4R_VKD3D_SRC=str(inputs / 'source'),
D4R_BUNDLE_DLSS=str(dlss), D4R_BUNDLE_NGX=str(core), D4R_BUNDLE_TEX=str(inputs / 'tex'))
version = (repo / 'packaging/VERSION').read_text().strip()
configs = []
for bundled in ('1', '0'):
with self.subTest(bundled=bundled):
env['D4R_BUNDLE_NVIDIA'] = bundled
result = subprocess.run(['bash', str(repo / 'scripts/package_release.sh'), str(repo / 'dist')],
env=env, text=True, capture_output=True)
self.assertEqual(result.returncode, 0, result.stdout + result.stderr)
name = f'd4r-{version}' + ('-nonvidia' if bundled == '0' else '')
with zipfile.ZipFile(repo / 'dist' / (name + '.zip')) as archive:
self.assertIsNone(archive.testzip())
data = archive.read('OptiScaler.ini')
self.assert_routing(data)
configs.append(data)
self.assertEqual(data, (repo / 'dist' / name / 'OptiScaler.ini').read_bytes())
self.assertIn('d4r/nvngx.dll', archive.namelist())
self.assertNotIn('nvngx.dll', archive.namelist())
self.assertNotIn('d4r/_nvngx.dll', archive.namelist())
self.assertEqual('d4r/ngx/_nvngx.dll' in archive.namelist(), bundled == '1')
self.assertEqual('d4r/nvngx_dlss.dll' in archive.namelist(), bundled == '1')
self.assertEqual(archive.read('d4r/d4r-check.sh'), (ROOT / 'packaging/d4r-check.sh').read_bytes())
self.assertEqual(configs[0], configs[1])
class InstallCheckTests(unittest.TestCase):
def test_routing_separate_from_file_presence(self):
with tempfile.TemporaryDirectory() as temporary:
game = Path(temporary) / 'game with spaces'
for name in ('Game.exe', 'dxgi.dll', 'd3d12.dll', 'd3d12core.dll', 'd4r/nvngx.dll',
'd4r/nvcuda.dll', 'd4r/zluda/libcuda.so', 'd4r/d4r.ini', 'd4r/nvngx_dlss.dll',
'd4r/ngx/_nvngx.dll', 'd4r/rocm/lib/libamdhip64.so.7'):
path = game / name
path.parent.mkdir(parents=True, exist_ok=True)
path.touch()
for value, good in ((r'd4r\nvngx.dll', True), ('./d4r/nvngx.dll', True),
('Z:' + str(game / 'd4r/nvngx.dll').replace('/', '\\'), True),
('auto', False), ('d4r', False), (r'd4r\ngx\_nvngx.dll', False),
(r'C:\windows\system32\_nvngx.dll', False), ('', False)):
with self.subTest(value=value):
setting = f'NvngxPath={value}\n' if value else ''
(game / 'OptiScaler.ini').write_bytes(
('[Libraries]\nOptiDllPath=d4r\n' + setting).replace('\n', '\r\n').encode())
result = subprocess.run(['sh', str(ROOT / 'packaging/d4r-check.sh'), str(game)],
text=True, capture_output=True)
self.assertIn('ok d4r/nvngx.dll', result.stdout)
if good:
self.assertIn('OptiScaler NGX routing: NvngxPath=', result.stdout)
self.assertNotIn('CONFIG', result.stdout)
else:
self.assertIn('CONFIG', result.stdout)
self.assertIn('OptiDllPath alone can load system _nvngx.dll', result.stdout)
self.assertNotIn('checks passed', result.stdout)
self.assertNotEqual(result.returncode, 0)
def test_section_and_duplicate_keys(self):
for ini in ('[Other]\nNvngxPath=d4r\\nvngx.dll\n[Libraries]\nOptiDllPath=d4r\n',
'[Libraries]\nNvngxPath=d4r\\nvngx.dll\nnvngxpath=auto\n'):
with tempfile.TemporaryDirectory() as temporary:
game = Path(temporary)
(game / 'd4r').mkdir()
(game / 'OptiScaler.ini').write_text(ini)
result = subprocess.run(['sh', str(ROOT / 'packaging/d4r-check.sh'), temporary],
capture_output=True, text=True)
self.assertIn('CONFIG', result.stdout)
self.assertNotEqual(result.returncode, 0)
def test_correct_routing_does_not_hide_missing_shim(self):
with tempfile.TemporaryDirectory() as temporary:
game = Path(temporary)
(game / 'd4r').mkdir()
(game / 'OptiScaler.ini').write_text('[Libraries]\nNvngxPath=d4r\\nvngx.dll\n')
result = subprocess.run(['sh', str(ROOT / 'packaging/d4r-check.sh'), temporary],
capture_output=True, text=True)
self.assertIn('MISSING d4r/nvngx.dll', result.stdout)
self.assertIn('OptiScaler NGX routing: NvngxPath=', result.stdout)
self.assertNotIn('checks passed', result.stdout)
self.assertNotEqual(result.returncode, 0)
if __name__ == '__main__':
unittest.main()
+92
View File
@@ -0,0 +1,92 @@
"""Marker polling defaults and the shim's model-override diagnostics."""
import configparser
import os
from pathlib import Path
import re
import subprocess
import tempfile
import unittest
ROOT = Path(__file__).resolve().parents[1]
SHIM = ROOT / 'tools/d4r_nvngx_shim.cpp'
class ShimConfigTests(unittest.TestCase):
def test_marker_poll_defaults_agree(self):
for name in ('packaging/d4r.ini', 'config/d4r.ini.default'):
ini = configparser.ConfigParser()
ini.read(ROOT / name)
self.assertEqual(ini.getint('Interop', 'MarkerPollUs'), 200, name)
source = SHIM.read_text()
portable = re.search(r'portable_set\("D4R_SHIM_MARKER_POLL_US", poll.empty\(\) \? "(\d+)" : poll\);', source)
runtime = re.search(r'env_uint\("D4R_SHIM_MARKER_POLL_US", (\d+)\)', source)
self.assertIsNotNone(portable, 'portable marker polling fallback missing')
self.assertIsNotNone(runtime, 'runtime marker polling fallback missing')
self.assertEqual(int(portable[1]), 200)
self.assertEqual(int(runtime[1]), 200)
def test_launcher_marker_poll_defaults_overrides_and_validation(self):
with tempfile.TemporaryDirectory() as temporary:
ini = Path(temporary) / 'd4r.ini'
env = {key: value for key, value in os.environ.items() if not key.startswith('D4R_')}
cmd = ['python3', str(ROOT / 'scripts/d4r_config.py'), '--config', str(ini)]
for value, expected in ((None, '200'), ('auto', '200'), ('200', '200'), ('0', '0'), ('75', '75')):
ini.write_text('[Interop]\n' + (f'MarkerPollUs = {value}\n' if value is not None else ''))
result = subprocess.run(cmd, env=env, text=True, capture_output=True, check=True)
self.assertIn(f'export D4R_SHIM_MARKER_POLL_US={expected}\n', result.stdout)
# An explicit environment variable keeps its existing precedence.
result = subprocess.run(cmd, env=dict(env, D4R_SHIM_MARKER_POLL_US='35'),
text=True, capture_output=True, check=True)
self.assertNotIn('export D4R_SHIM_MARKER_POLL_US=', result.stdout)
for value in ('-1', '0.5', 'invalid'):
ini.write_text(f'[Interop]\nMarkerPollUs = {value}\n')
result = subprocess.run(cmd, env=env, text=True, capture_output=True)
self.assertEqual(result.returncode, 2)
self.assertIn('MarkerPollUs must be a number of microseconds', result.stderr)
def test_production_model_override_logs_and_presets(self):
# Compile the actual feature-creation preset block with fake NGX inputs
# and a captured logger, so the test exercises the production message
# and override behavior without requiring Windows, NGX or a GPU.
source = SHIM.read_text()
start = source.index(' const char* presetNames[] =')
end = source.index(' feature->preset = presets[1];', start)
block = source[start:end]
runner_source = r'''
#include <cstdio>
#include <cstdlib>
#include <cstdarg>
#include <string>
static unsigned nextPreset = 21;
unsigned get_uint_or(void*, const char*, unsigned) { return nextPreset++; }
std::string env_string(const char* name) { const char* value = getenv(name); return value ? value : ""; }
void logf(const char* format, ...) {
va_list args; va_start(args, format); vprintf(format, args); va_end(args); puts("");
}
int main() {
void* parameters = nullptr;
''' + block + r'''
printf("presets:"); for (unsigned preset : presets) printf(" %u", preset); puts("");
}
'''
with tempfile.TemporaryDirectory() as temporary:
runner = Path(temporary) / 'model-override'
subprocess.run(['c++', '-std=c++17', '-Wall', '-Wextra', '-Werror', '-x', 'c++', '-', '-o', str(runner)],
input=runner_source, text=True, capture_output=True, check=True)
env = {key: value for key, value in os.environ.items() if key != 'D4R_DLSS_PRESET'}
for preset, model in ((5, 'E'), (11, 'K'), (12, 'L'), (13, 'M')):
with self.subTest(model=model):
output = subprocess.check_output([str(runner)], env=dict(env, D4R_DLSS_PRESET=str(preset)), text=True)
self.assertIn(f'D4R_DLSS_PRESET={preset} (model {model}) overrides game/OptiScaler presets', output)
self.assertIn('[DLAA=21 Quality=22 Balanced=23 Performance=24 UltraPerformance=25 UltraQuality=26]', output)
self.assertIn(f'using preset={preset} for all quality modes', output)
self.assertIn('presets:' + f' {preset}' * 6, output)
for value in (None, ''):
current = env if value is None else dict(env, D4R_DLSS_PRESET=value)
output = subprocess.check_output([str(runner)], env=current, text=True)
self.assertNotIn('model override', output)
self.assertEqual(output.strip(), 'presets: 21 22 23 24 25 26')
if __name__ == '__main__':
unittest.main()
+7 -2
View File
@@ -273,7 +273,7 @@ static FILE* g_log = nullptr;
static HMODULE g_selfModule = nullptr; static HMODULE g_selfModule = nullptr;
// --- portable install ---------------------------------------------------------- // --- portable install ----------------------------------------------------------
// The drag-in release keeps this DLL (as nvngx.dll, which OptiScaler finds through its OptiDllPath), // The drag-in release keeps this DLL (as nvngx.dll, explicitly selected by OptiScaler's NvngxPath),
// the nvcuda bridge, ZLUDA and the native kernels in an d4r folder next to the game's executable, // the nvcuda bridge, ZLUDA and the native kernels in an d4r folder next to the game's executable,
// with that game's settings in d4r\d4r.ini. The file's settings become the environment variables the // with that game's settings in d4r\d4r.ini. The file's settings become the environment variables the
// developer launcher sets (config/d4r.ini.default documents them), and a variable that is already set, // developer launcher sets (config/d4r.ini.default documents them), and a variable that is already set,
@@ -563,7 +563,7 @@ static void load_portable_config()
portable_set(setting.variable, ini_flag(ini, "interop", setting.key, setting.fallback) ? "1" : "0"); portable_set(setting.variable, ini_flag(ini, "interop", setting.key, setting.fallback) ? "1" : "0");
portable_set_unix("D4R_ELIDE_NGX_SYNC", ini_flag(ini, "interop", "ElideNgxSync", 1) ? "1" : "0"); portable_set_unix("D4R_ELIDE_NGX_SYNC", ini_flag(ini, "interop", "ElideNgxSync", 1) ? "1" : "0");
const std::string poll = ini_value(ini, "interop", "MarkerPollUs"); const std::string poll = ini_value(ini, "interop", "MarkerPollUs");
portable_set("D4R_SHIM_MARKER_POLL_US", poll.empty() ? "20" : poll); portable_set("D4R_SHIM_MARKER_POLL_US", poll.empty() ? "200" : poll);
// Direct output takes effect only while NGX runs the native output kernel (the bridge reports it); the // Direct output takes effect only while NGX runs the native output kernel (the bridge reports it); the
// release cannot ship that kernel, which is built from NVIDIA's PTX. // release cannot ship that kernel, which is built from NVIDIA's PTX.
portable_set("D4R_SHIM_OUTPUT_DIRECT", ini_flag(ini, "interop", "DirectOutput", nativeOn) ? "1" : "0"); portable_set("D4R_SHIM_OUTPUT_DIRECT", ini_flag(ini, "interop", "DirectOutput", nativeOn) ? "1" : "0");
@@ -4429,6 +4429,11 @@ D4R_EXPORT NgxResult NVSDK_NGX_D3D12_CreateFeature(ID3D12GraphicsCommandList*, u
if (!forced.empty()) if (!forced.empty())
{ {
const unsigned int value = static_cast<unsigned int>(strtoul(forced.c_str(), nullptr, 0)); const unsigned int value = static_cast<unsigned int>(strtoul(forced.c_str(), nullptr, 0));
const char* model = value == 5 ? "E" : value == 11 ? "K" : value == 12 ? "L" : value == 13 ? "M" : "custom";
logf("DLSS model override: D4R_DLSS_PRESET=%s (model %s) overrides game/OptiScaler presets "
"[DLAA=%u Quality=%u Balanced=%u Performance=%u UltraPerformance=%u UltraQuality=%u]; "
"using preset=%u for all quality modes",
forced.c_str(), model, presets[0], presets[1], presets[2], presets[3], presets[4], presets[5], value);
for (unsigned int& preset : presets) for (unsigned int& preset : presets)
preset = value; preset = value;
} }