fix(dlss): DLSS Frame Generation's own runtime in the game folder, and NGX left alone after refused sets #2598

Merged
pmwork-personal merged 2 commits from fix/dlssg-runtime into main 2026-11-12 01:32:30 +00:00
pmwork-personal commented 2026-11-12 00:58:46 +00:00 (Migrated from github.com)

The build puts only DLSS's runtime (nvngx_dlss.dll) in the game's ngx folder, so NGX takes the driver's own copy of DLSS Frame Generation's runtime. On an older driver that copy breaks DLSS-G: found and diagnosed by @nullifyshot on an RTX 5080, where the driver's nvngx_dlssg (310.2.1) makes 1 frame at most.

What null found (same build and spot, windowed, switching live 2x → 3x → 4x → 2x; game fps):

2x 3x 4x 2x
driver's nvngx_dlssg (310.2.1) 34.4 81.3 70.8 34.8
SDK's (310.9.16) in ngx\ 97.5 97.2 80.5 97.8
  • With the driver's copy, DLSS-G made 0 frames at every multiplier. At 3x and 4x Hookshot skipped it ("asked more than 1 frames") and its built-in interpolation made the frames, so they looked fine. At 2x NGX refused every set (0xbad00005, FAIL_InvalidParameter): each set paid for a wasted evaluation, and the game ran at half its rate with nothing made.
  • With the SDK's runtime beside DLSS's: "up to 5 made frames", 600 of 600 made by DLSS-G at every multiplier, nothing refused.

Our own 5090 runs never showed it because our test folder had a copy of nvngx_dlssg.dll put there by hand.

What changes

  • scripts/ngx_sdk.py: DLSS Frame Generation's runtime in each platform's table (nvngx_dlssg.dll; libnvidia-ngx-dlssg.so.310.9.16), from the same pinned commit and SHA-256 checked, fetched with DLSS's runtime and installed beside it. An SDK folder fetched before this still installs DLSS's; --fetch-ngx-sdk (the Builder's DLSS tick) adds the new file on the next build, since it fetches whatever is missing.
  • nx2/make_game.py: place_ngx_runtime puts both in the game's ngx folder and names them; with an older SDK folder it says how to get frame generation's; a folder rebuilt without DLSS loses both.
  • gpu/vk/vk_dlss.cpp:
    • DLSS-G's start line says whether the runtime folders hold nvngx_dlssg or NGX takes the driver's own copy.
    • More frames asked than the runtime makes is said once (the 600-present lines already count them).
    • 3 refused sets in a row (kFgRefusalsOff) give DLSS-G up, as three failed feature creations already do (start_failed): the built-in makes the frames, and V-Sync Auto goes back to its mode without DLSS-G. A set made counts them from 0 again.
  • The three Builders' NVIDIA notices and scripts/linux/ngx-sdk.sh name both runtimes.

Tested

  • A build of this branch on Windows (RTX 5090) puts both runtimes in ngx\ with nothing copied by hand: NVIDIA DLSS Frame Generation: ...\ngx\nvngx_dlssg.dll (NVIDIA's too). A headless run then logs available, up to 5 made frames between two of the game's (its runtime: the newest nvngx_dlssg.dll in ...\ngx, ...).
  • tests/run_all.py (Windows, llvm-mingw): 435 passed, 35 skipped, 3 failed, all outside this PR and failing the same on main here: test_env_on (#2470's HOS_FPS_LOG), test_vram_budget_row, test_portable_builder_startup (its dotnet publish needs packages this PC doesn't have).
  • test_ngx_sdk.py and test_make_game_ngx.py cover both runtimes and an older SDK folder. The new test_dlssg_runtime.py checks the refusal limit, its reset after a made set, the once-said warning, the start line, and that vk_dlss.cpp looks for the same name ngx_sdk.py installs. Four mutations (no reset, no guard, no start_failed, a different name) each fail it.
  • A syntax-only compile of vk_dlss.cpp with the NGX headers.

Not tested here: the refusal limit in game. It needs a driver whose nvngx_dlssg refuses sets, and this PC's runs use the SDK's. @nullifyshot, if you can, a run on this branch with the driver's copy (no nvngx_dlssg.dll in ngx\) should show "3 sets in a row refused ... off" at 2x and the game back at its normal rate.

🤖 Generated with Claude Code

The build puts only DLSS's runtime (`nvngx_dlss.dll`) in the game's `ngx` folder, so NGX takes the driver's own copy of DLSS Frame Generation's runtime. On an older driver that copy breaks DLSS-G: found and diagnosed by @nullifyshot on an RTX 5080, where the driver's `nvngx_dlssg` (310.2.1) makes 1 frame at most. **What null found** (same build and spot, windowed, switching live 2x → 3x → 4x → 2x; game fps): | | 2x | 3x | 4x | 2x | |---|---|---|---|---| | driver's `nvngx_dlssg` (310.2.1) | 34.4 | 81.3 | 70.8 | 34.8 | | SDK's (310.9.16) in `ngx\` | 97.5 | 97.2 | 80.5 | 97.8 | - With the driver's copy, DLSS-G made 0 frames at every multiplier. At 3x and 4x Hookshot skipped it ("asked more than 1 frames") and its built-in interpolation made the frames, so they looked fine. At 2x NGX refused every set (`0xbad00005`, FAIL_InvalidParameter): each set paid for a wasted evaluation, and the game ran at half its rate with nothing made. - With the SDK's runtime beside DLSS's: "up to 5 made frames", 600 of 600 made by DLSS-G at every multiplier, nothing refused. Our own 5090 runs never showed it because our test folder had a copy of `nvngx_dlssg.dll` put there by hand. **What changes** - `scripts/ngx_sdk.py`: DLSS Frame Generation's runtime in each platform's table (`nvngx_dlssg.dll`; `libnvidia-ngx-dlssg.so.310.9.16`), from the same pinned commit and SHA-256 checked, fetched with DLSS's runtime and installed beside it. An SDK folder fetched before this still installs DLSS's; `--fetch-ngx-sdk` (the Builder's DLSS tick) adds the new file on the next build, since it fetches whatever is missing. - `nx2/make_game.py`: `place_ngx_runtime` puts both in the game's `ngx` folder and names them; with an older SDK folder it says how to get frame generation's; a folder rebuilt without DLSS loses both. - `gpu/vk/vk_dlss.cpp`: - DLSS-G's start line says whether the runtime folders hold `nvngx_dlssg` or NGX takes the driver's own copy. - More frames asked than the runtime makes is said once (the 600-present lines already count them). - 3 refused sets in a row (`kFgRefusalsOff`) give DLSS-G up, as three failed feature creations already do (`start_failed`): the built-in makes the frames, and V-Sync Auto goes back to its mode without DLSS-G. A set made counts them from 0 again. - The three Builders' NVIDIA notices and `scripts/linux/ngx-sdk.sh` name both runtimes. **Tested** - A build of this branch on Windows (RTX 5090) puts both runtimes in `ngx\` with nothing copied by hand: `NVIDIA DLSS Frame Generation: ...\ngx\nvngx_dlssg.dll (NVIDIA's too)`. A headless run then logs `available, up to 5 made frames between two of the game's (its runtime: the newest nvngx_dlssg.dll in ...\ngx, ...)`. - `tests/run_all.py` (Windows, llvm-mingw): 435 passed, 35 skipped, 3 failed, all outside this PR and failing the same on main here: `test_env_on` (#2470's `HOS_FPS_LOG`), `test_vram_budget_row`, `test_portable_builder_startup` (its `dotnet publish` needs packages this PC doesn't have). - `test_ngx_sdk.py` and `test_make_game_ngx.py` cover both runtimes and an older SDK folder. The new `test_dlssg_runtime.py` checks the refusal limit, its reset after a made set, the once-said warning, the start line, and that `vk_dlss.cpp` looks for the same name `ngx_sdk.py` installs. Four mutations (no reset, no guard, no `start_failed`, a different name) each fail it. - A syntax-only compile of `vk_dlss.cpp` with the NGX headers. **Not tested here:** the refusal limit in game. It needs a driver whose `nvngx_dlssg` refuses sets, and this PC's runs use the SDK's. @nullifyshot, if you can, a run on this branch with the driver's copy (no `nvngx_dlssg.dll` in `ngx\`) should show "3 sets in a row refused ... off" at 2x and the game back at its normal rate. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
hckrman101 (Migrated from github.com) approved these changes 2026-11-12 01:05:46 +00:00
hckrman101 (Migrated from github.com) left a comment

Thanks, and thanks to @nullifyshot for the diagnosis: the 2x numbers (34 fps against 97 with the SDK's nvngx_dlssg) make the case clearly.

What I checked:

  • The two new table entries exist at the pinned NVIDIA/DLSS commit, and their SHA-256s match the files there (Windows nvngx_dlssg.dll, Linux libnvidia-ngx-dlssg.so.310.9.16).
  • An SDK folder fetched before this keeps working: install_runtime skips the missing frame-gen file, and --fetch-ngx-sdk (fetch_ngx_sdk, then fetch_one) downloads only what's missing, so the Builder's tick adds the file on the next build as the description says.
  • The new name doesn't collide with the old prefix checks: nvngx_dlss.dll / libnvidia-ngx-dlss.so don't prefix-match the dlssg files, either in ngx_init's runtime check or in the Linux Builder's has_runtime.
  • The refusal limit works the same way as the existing three-failed-creations path: start_failed makes dlss_fg_runs say no, and a made set resets the count to 0. test_dlssg_runtime.py passes on both Linux and Windows in CI.
  • CI: the only failures are test_env_on.py in two shards. It fails the same way on main right now, so that's not this PR.

One small thing, inline: after the give-up, the DLSS-G feature stays allocated.

Nits, not blocking:

  • The readme's DLSS section (Windows and Linux) and CONTRIBUTING.md still name only nvngx_dlss.dll / libnvidia-ngx-dlss.so. The Linux tip about copying libnvidia-ngx-dlss.so.* into $XDG_DATA_HOME/hookshot/ngx for a folder whose path isn't ASCII now needs the dlssg file too. In that case, fg_start's new line would also say "its runtime: the newest ... in" while NGX actually can't load it from there.
  • fetch()'s docstring still says "runtime=False: not DLSS's runtime"; it now leaves out both runtimes.

Approving. It would be nice if the in-game check of the refusal path you asked @nullifyshot for happens, but it doesn't block this: with the SDK's runtime now in ngx/, that path is the fallback.

Thanks, and thanks to @nullifyshot for the diagnosis: the 2x numbers (34 fps against 97 with the SDK's `nvngx_dlssg`) make the case clearly. What I checked: - The two new table entries exist at the pinned NVIDIA/DLSS commit, and their SHA-256s match the files there (Windows `nvngx_dlssg.dll`, Linux `libnvidia-ngx-dlssg.so.310.9.16`). - An SDK folder fetched before this keeps working: `install_runtime` skips the missing frame-gen file, and `--fetch-ngx-sdk` (`fetch_ngx_sdk`, then `fetch_one`) downloads only what's missing, so the Builder's tick adds the file on the next build as the description says. - The new name doesn't collide with the old prefix checks: `nvngx_dlss.dll` / `libnvidia-ngx-dlss.so` don't prefix-match the `dlssg` files, either in `ngx_init`'s runtime check or in the Linux Builder's `has_runtime`. - The refusal limit works the same way as the existing three-failed-creations path: `start_failed` makes `dlss_fg_runs` say no, and a made set resets the count to 0. `test_dlssg_runtime.py` passes on both Linux and Windows in CI. - CI: the only failures are `test_env_on.py` in two shards. It fails the same way on main right now, so that's not this PR. One small thing, inline: after the give-up, the DLSS-G feature stays allocated. Nits, not blocking: - The readme's DLSS section (Windows and Linux) and CONTRIBUTING.md still name only `nvngx_dlss.dll` / `libnvidia-ngx-dlss.so`. The Linux tip about copying `libnvidia-ngx-dlss.so.*` into `$XDG_DATA_HOME/hookshot/ngx` for a folder whose path isn't ASCII now needs the `dlssg` file too. In that case, `fg_start`'s new line would also say "its runtime: the newest ... in" while NGX actually can't load it from there. - `fetch()`'s docstring still says "runtime=False: not DLSS's runtime"; it now leaves out both runtimes. Approving. It would be nice if the in-game check of the refusal path you asked @nullifyshot for happens, but it doesn't block this: with the SDK's runtime now in `ngx/`, that path is the fallback.
hckrman101 (Migrated from github.com) commented 2026-11-12 01:05:46 +00:00

After the give-up, dlss_fg_frames is still called on every present (vk_framegen.cpp calls it whenever m > 1). It returns here, but g.feature from the refused sets stays made, along with its memory, for the rest of the run at 2x and up. dlss_fg_idle only lets it go at Off/Auto. The three-failed-creations path has no feature to hold, so this is new. fg_release idles the queue first, so letting it go here once is safe:

if (g.refusals >= kFgRefusalsOff) {   // (given up: dlss_fg_runs says no from here)
    if (g.feature) fg_release(g);
    return g.refused++, 0;
}
After the give-up, `dlss_fg_frames` is still called on every present (vk_framegen.cpp calls it whenever m > 1). It returns here, but `g.feature` from the refused sets stays made, along with its memory, for the rest of the run at 2x and up. `dlss_fg_idle` only lets it go at Off/Auto. The three-failed-creations path has no feature to hold, so this is new. `fg_release` idles the queue first, so letting it go here once is safe: ```cpp if (g.refusals >= kFgRefusalsOff) { // (given up: dlss_fg_runs says no from here) if (g.feature) fg_release(g); return g.refused++, 0; } ```
pmwork-personal commented 2026-11-12 01:23:00 +00:00 (Migrated from github.com)

Thanks for checking the hashes against the pinned commit. All in 0f70ceaa:

  • The feature after the give-up: done as you suggested. The guard lets the refused sets' feature go once (fg_release, which idles the queue first) and then returns.
  • Linux, a non-ASCII folder: fg_start now runs the same real-path ASCII check as ngx_init does for DLSS's runtime. A libnvidia-ngx-dlssg.so found only in folders named otherwise is reported as not loadable, with the driver's own copy in use, instead of "its runtime: ...".
  • Docs: the readme's DLSS section (both Builders, the command lines, the non-ASCII folder tip, which now copies both .so files) and CONTRIBUTING.md name both runtimes. fetch()'s docstring says runtime=False leaves both out.
  • test_dlssg_runtime.py also checks the release and the Linux path check; six mutations each fail it. run_all.py here: 435 passed, the same 3 failing as on main (test_env_on, test_vram_budget_row, and test_portable_builder_startup, which needs dotnet packages this PC lacks).
Thanks for checking the hashes against the pinned commit. All in 0f70ceaa: - **The feature after the give-up:** done as you suggested. The guard lets the refused sets' feature go once (`fg_release`, which idles the queue first) and then returns. - **Linux, a non-ASCII folder:** `fg_start` now runs the same real-path ASCII check as `ngx_init` does for DLSS's runtime. A `libnvidia-ngx-dlssg.so` found only in folders named otherwise is reported as not loadable, with the driver's own copy in use, instead of "its runtime: ...". - **Docs:** the readme's DLSS section (both Builders, the command lines, the non-ASCII folder tip, which now copies both `.so` files) and CONTRIBUTING.md name both runtimes. `fetch()`'s docstring says `runtime=False` leaves both out. - `test_dlssg_runtime.py` also checks the release and the Linux path check; six mutations each fail it. `run_all.py` here: 435 passed, the same 3 failing as on main (`test_env_on`, `test_vram_budget_row`, and `test_portable_builder_startup`, which needs dotnet packages this PC lacks).
Commenting is not possible because the repository is archived.
No description provided.