fix(vk): V-Sync Auto keeps its mode when DLSS Frame Gen's runtime makes fewer frames than the multiplier asks #2701

Closed
github-sync wants to merge 1 commit from github/pr/2701 into main
Owner

@hckrman101 opened this pull request on GitHub · 2026-11-12 07:16 UTC · original
Merged on GitHub in ee29dd55b5

With Frame generation at 3x or 4x, "Frame generation by" Auto and V-Sync Auto, a DLSS Frame Generation runtime that makes only 1 frame leaves the frames to the renderer's own generator, but the swapchain was still made FIFO as for DLSS-G. The game was then held at the refresh rate over the multiplier (36 fps on a 144 Hz display at 4x, none of 720 frames shown on time).

Fixes #2695

What was wrong

swapchain_dlssg() (V-Sync Auto's FIFO) follows dlss_fg_runs(), which checked that DLSS-G is asked for, that DLSS runs on the device and that NGX did not say no or give its feature up. It did not check how many frames the runtime makes. dlss_fg_frames leaves a set of more frames than that (count > g.most) to the renderer's own generator, every present, so at 3x and 4x with a runtime that makes 1 the swapchain stayed FIFO while the renderer's own made all the frames. The comment at the mode's choice already said the renderer's own frame generation keeps Auto's mailbox.

The fix

  • dlss_fg_runs(frames) now takes the number of made frames a set asks for (the multiplier less 1) and says no when the runtime makes fewer. The runtime's limit is published by fg_start once NGX has answered (most_known, 0 before).
  • swapchain_dlssg() passes the Frame generation row's multiplier less 1 (the row goes up to 8x; swapchain_framegen() stops at 4, for the images, so it is not the count).
  • NGX is asked at the first present with a fixed multiplier, after the swapchain is first made. Until then DLSS-G is taken to make the frames, as before. When the answer is fewer frames than asked, present_image sees swapchain_dlssg() change and makes the swapchain again, once, the same way it already does when NGX says no or the feature is given up. The limit is set once a run, so there is no second change.
  • The log says it: the "made frames asked, but its runtime makes N at most" line now ends with "V-Sync Auto keeps its mode without DLSS-G", and the swapchain logs "V-Sync Auto with frame generation at Nx, its frames the renderer's own (DLSS-G does not make them): Auto's mode, as without DLSS-G" where it logged "... DLSS Frame Generation at Nx: FIFO".
  • RX_VK_DLSS_FG_MOST=<n> caps what the runtime is taken to make, for checks: with =1 a current runtime behaves as the reporter's at 3x and 4x.

Unchanged: 2x with such a runtime (DLSS-G makes its 1 frame: FIFO), a runtime that makes as many as asked, V-Sync On and Off, GPUs without DLSS, builds without NGX, FSR's and the renderer's own frame generation, the swapchain's image count (it follows the multiplier, not DLSS-G).

Tested

  • python -I -B tests/test_vsync_row.py: it now compiles the real swapchain_dlssg and dlss_fg_runs into create_swapchain's choice of mode and checks, for Windows' and Linux's mode lists: before NGX's answer FIFO; a runtime that makes 1 at 2x FIFO, at 3x and 4x Auto's mailbox (the issue); a runtime that makes 3 at 4x FIFO, at 5x and 8x mailbox; given up, or a device without DLSS, mailbox; V-Sync On FIFO and Off immediate in every case. It fails on main (no dlss_fg_runs(int frames)).

  • tests/test_dlssg_runtime.py, tests/test_present_thread.py, tests/test_present_semaphores.py, tests/test_vk_swapchain_compat.py (they read the same code): pass.

  • Built on Linux (hookshot_vk.so, with NGX) and run in the game: RTX 3090, 2560x1440 at 240 Hz, Hyrule Field, DLSS, Frame generation 4x, Frame generation by Auto, V-Sync Auto, Reflex on, cap 120. This card has no DLSS Frame Generation (NGX: "not available (0xbad00004) ... needs an RTX 40 or newer"), so the renderer's own makes the frames: the swapchain is made FIFO before the first present, then once more as mailbox when NGX answers, as on main, and the log now says "V-Sync Auto with frame generation at 4x, its frames the renderer's own (DLSS-G does not make them): Auto's mode, as without DLSS-G". One FIFO-to-mailbox remake, no loop; 254 fps shown (about 63 game fps), "0 made by DLSS-G".

Not tested

  • Not run on a card with DLSS Frame Generation (RTX 40 or newer): the case of the issue (a runtime that makes 1 frame, at 3x or 4x), a runtime that makes as many as asked (FIFO, no remake) and 2x were not run in the game; the test above covers their choice of mode. RX_VK_DLSS_FG_MOST=1 with Frame generation 4x reproduces the issue's runtime on such a card: the log should show the FIFO line once, then the "renderer's own" line, and the frame rate should no longer sit at the refresh over 4.
  • Windows was not built here (CI builds it).
  • The full test suite was not run, only the checks above.
> [@hckrman101](https://github.com/hckrman101) opened this pull request on GitHub · 2026-11-12 07:16 UTC · [original](https://github.com/NotRexed/Hookshot/pull/2701) > **Merged on GitHub** in https://git.magicbean.dev/MagicBeanDev/Hookshot/commit/ee29dd55b593577ae0a7a6e2f871848ee5e176b4 With Frame generation at 3x or 4x, "Frame generation by" Auto and V-Sync Auto, a DLSS Frame Generation runtime that makes only 1 frame leaves the frames to the renderer's own generator, but the swapchain was still made FIFO as for DLSS-G. The game was then held at the refresh rate over the multiplier (36 fps on a 144 Hz display at 4x, none of 720 frames shown on time). Fixes #2695 ## What was wrong `swapchain_dlssg()` (V-Sync Auto's FIFO) follows `dlss_fg_runs()`, which checked that DLSS-G is asked for, that DLSS runs on the device and that NGX did not say no or give its feature up. It did not check how many frames the runtime makes. `dlss_fg_frames` leaves a set of more frames than that (`count > g.most`) to the renderer's own generator, every present, so at 3x and 4x with a runtime that makes 1 the swapchain stayed FIFO while the renderer's own made all the frames. The comment at the mode's choice already said the renderer's own frame generation keeps Auto's mailbox. ## The fix - `dlss_fg_runs(frames)` now takes the number of made frames a set asks for (the multiplier less 1) and says no when the runtime makes fewer. The runtime's limit is published by `fg_start` once NGX has answered (`most_known`, 0 before). - `swapchain_dlssg()` passes the Frame generation row's multiplier less 1 (the row goes up to 8x; `swapchain_framegen()` stops at 4, for the images, so it is not the count). - NGX is asked at the first present with a fixed multiplier, after the swapchain is first made. Until then DLSS-G is taken to make the frames, as before. When the answer is fewer frames than asked, `present_image` sees `swapchain_dlssg()` change and makes the swapchain again, once, the same way it already does when NGX says no or the feature is given up. The limit is set once a run, so there is no second change. - The log says it: the "made frames asked, but its runtime makes N at most" line now ends with "V-Sync Auto keeps its mode without DLSS-G", and the swapchain logs "V-Sync Auto with frame generation at Nx, its frames the renderer's own (DLSS-G does not make them): Auto's mode, as without DLSS-G" where it logged "... DLSS Frame Generation at Nx: FIFO". - `RX_VK_DLSS_FG_MOST=<n>` caps what the runtime is taken to make, for checks: with `=1` a current runtime behaves as the reporter's at 3x and 4x. Unchanged: 2x with such a runtime (DLSS-G makes its 1 frame: FIFO), a runtime that makes as many as asked, V-Sync On and Off, GPUs without DLSS, builds without NGX, FSR's and the renderer's own frame generation, the swapchain's image count (it follows the multiplier, not DLSS-G). ## Tested - `python -I -B tests/test_vsync_row.py`: it now compiles the real `swapchain_dlssg` and `dlss_fg_runs` into `create_swapchain`'s choice of mode and checks, for Windows' and Linux's mode lists: before NGX's answer FIFO; a runtime that makes 1 at 2x FIFO, at 3x and 4x Auto's mailbox (the issue); a runtime that makes 3 at 4x FIFO, at 5x and 8x mailbox; given up, or a device without DLSS, mailbox; V-Sync On FIFO and Off immediate in every case. It fails on `main` (no `dlss_fg_runs(int frames)`). - `tests/test_dlssg_runtime.py`, `tests/test_present_thread.py`, `tests/test_present_semaphores.py`, `tests/test_vk_swapchain_compat.py` (they read the same code): pass. - Built on Linux (`hookshot_vk.so`, with NGX) and run in the game: RTX 3090, 2560x1440 at 240 Hz, Hyrule Field, DLSS, Frame generation 4x, Frame generation by Auto, V-Sync Auto, Reflex on, cap 120. This card has no DLSS Frame Generation (NGX: "not available (0xbad00004) ... needs an RTX 40 or newer"), so the renderer's own makes the frames: the swapchain is made FIFO before the first present, then once more as mailbox when NGX answers, as on `main`, and the log now says "V-Sync Auto with frame generation at 4x, its frames the renderer's own (DLSS-G does not make them): Auto's mode, as without DLSS-G". One FIFO-to-mailbox remake, no loop; 254 fps shown (about 63 game fps), "0 made by DLSS-G". ## Not tested - Not run on a card with DLSS Frame Generation (RTX 40 or newer): the case of the issue (a runtime that makes 1 frame, at 3x or 4x), a runtime that makes as many as asked (FIFO, no remake) and 2x were not run in the game; the test above covers their choice of mode. `RX_VK_DLSS_FG_MOST=1` with Frame generation 4x reproduces the issue's runtime on such a card: the log should show the FIFO line once, then the "renderer's own" line, and the frame rate should no longer sit at the refresh over 4. - Windows was not built here (CI builds it). - The full test suite was not run, only the checks above. <!-- github-sync:item:2701 -->
With Frame generation 3x or 4x, Frame generation by Auto and V-Sync Auto, a DLSS-G runtime that makes 1 frame at most
leaves every set to the renderer's own generator (dlss_fg_frames: count > g.most), but swapchain_dlssg followed
dlss_fg_runs, which did not look at the runtime's limit: the swapchain stayed FIFO as for DLSS-G and the game was held
at the refresh over the multiplier (36 fps on 144 Hz at 4x).

dlss_fg_runs takes the made frames a set asks for and says no when the runtime makes fewer; fg_start publishes the
limit once NGX has answered (the first present with a fixed multiplier). Until then DLSS-G is taken to make them, as
before; when the answer is fewer, present_image sees swapchain_dlssg change and makes the swapchain again, once, as
it does when NGX says no. swapchain_dlssg passes the row's multiplier less 1 (up to 8x), not swapchain_framegen's,
which stops at 4. The log says which mode Auto took. RX_VK_DLSS_FG_MOST=<n> caps the runtime's limit, for checks.

tests/test_vsync_row.py compiles the real swapchain_dlssg and dlss_fg_runs into create_swapchain's choice of mode.

Fixes #2695

Co-Authored-By: Claude Fable 5.2 <[email protected]>
github-sync closed this pull request 2026-11-12 08:31:29 +00:00
Author
Owner

@hckrman101 commented on GitHub · 2026-11-12 07:53 UTC · original

@jabbaxipil this should fix #2695. I could only run it on an RTX 3090, which has no DLSS Frame Generation at all, so the exact case of the issue (a runtime that makes 1 frame, at 3x or 4x) is covered by the unit test but not by a run in the game. If you can build this branch on your RTX 4070: with Frame generation 4x, by Auto, and V-Sync Auto, the log should show the "V-Sync Auto with DLSS Frame Generation at 4x: FIFO" line once, then "V-Sync Auto with frame generation at 4x, its frames the renderer's own", and the game should no longer sit at 36 fps. 2x should behave as before.

> [@hckrman101](https://github.com/hckrman101) commented on GitHub · 2026-11-12 07:53 UTC · [original](https://github.com/NotRexed/Hookshot/pull/2701#issuecomment-6818004549) @jabbaxipil this should fix #2695. I could only run it on an RTX 3090, which has no DLSS Frame Generation at all, so the exact case of the issue (a runtime that makes 1 frame, at 3x or 4x) is covered by the unit test but not by a run in the game. If you can build this branch on your RTX 4070: with Frame generation 4x, by Auto, and V-Sync Auto, the log should show the "V-Sync Auto with DLSS Frame Generation at 4x: FIFO" line once, then "V-Sync Auto with frame generation at 4x, its frames the renderer's own", and the game should no longer sit at 36 fps. 2x should behave as before. <!-- github-sync:c:6818004549 -->
Author
Owner

@Jiro131 commented on GitHub · 2026-11-12 12:13 UTC · original

Ran this on a card with DLSS Frame Generation: RTX 4090, NVIDIA 621.01, Windows 11, 144 Hz display. Build hookshot v2.1.0-rc.3-256-g51da0464 (source 18ed87b5): main with this PR, built from source with NGX (SDK 310.9.16, its nvngx_dlssg.dll in the game's ngx folder).

Settings: Kokiri Forest from a save, DLSS DLAA (preset K) at 2560x1440, Frame generation by Auto, Frame pacing on, cap 60, V-Sync Auto. Each multiplier was set in the menu and the game restarted.

The mode choice works as described. At 2x, DLSS-G makes the frame and the swapchain is FIFO. At 4x, the runtime makes 1, and the swapchain is remade as mailbox once NGX has answered:

vk: DLSS Frame Gen (NGX): available, up to 1 made frames between two of the game's (...)
vk: V-Sync Auto with DLSS Frame Generation at 4x: FIFO
vk: V-Sync Auto with frame generation at 4x, its frames the renderer's own (DLSS-G does not make them): Auto's mode, as without DLSS-G
vk: swapchain 2558x1360, 6 images, mailbox (V-Sync Auto)
vk: DLSS Frame Gen (NGX): 3 made frames asked, but its runtime makes 1 at most (an older nvngx_dlssg.dll?): the renderer's own makes them, and V-Sync Auto keeps its mode without DLSS-G

But #2695's symptom stays at 4x: the game is still held at 36.0 fps (144 / 4), with the same bunched presents. Every status line in play shows it, in a window (2558x1360) and in fullscreen (5120x1440, Reflex on):

vk: 2400 presents, 36.0 fps; a frame: 3383 draws (0 skipped), ...
vk: frame interpolation: 0 frames shown on time, 720 late (over 2 ms); shown apart: <7 ms 540, 7-9 0, 9-12 0, 12-17 0, 17-25 0, over 25 180
vk: DLSS Frame Gen (NGX): of the last 600 presents 0 made by DLSS-G (...); the renderer's own made the rest: 0 without new inputs (no DLSS/DLAA this frame), 1 after a gap, 599 asked more than 1 frames, 0 refused

GPU time was about 9.5 ms a frame, so the GPU is not what holds it. It is not FIFO either: the swapchain is mailbox in both runs. In play it felt like uneven pacing and very high latency in the window; in fullscreen the pacing felt better, but the latency was just as bad.

At 2x and at 2x with Reflex on, DLSS-G works as intended: 600 of each 600 presents made by DLSS-G, 56-61 fps in Kokiri once past the load (1,400-3,800 draws), and (2x) 601 of 615 frames shown on time, 7-9 ms apart. It looked smooth, with no shimmer.

So on Windows the FIFO choice looks like only part of #2695: the game stays at refresh / multiplier with mailbox too. @jabbaxipil, does your RTX 4070 show the same on Linux with this PR? If it does, #2695 may need reopening. I can run builds with more logging on this machine.

Not tested here: 3x in play (it is the same fallback), Frame pacing off, an uncapped game, and Linux.

🤖 Generated with Claude Code

> [@Jiro131](https://github.com/Jiro131) commented on GitHub · 2026-11-12 12:13 UTC · [original](https://github.com/NotRexed/Hookshot/pull/2701#issuecomment-6823286881) Ran this on a card with DLSS Frame Generation: RTX 4090, NVIDIA 621.01, Windows 11, 144 Hz display. Build `hookshot v2.1.0-rc.3-256-g51da0464` (source 18ed87b5): `main` with this PR, built from source with NGX (SDK 310.9.16, its `nvngx_dlssg.dll` in the game's `ngx` folder). Settings: Kokiri Forest from a save, DLSS DLAA (preset K) at 2560x1440, Frame generation by Auto, Frame pacing on, cap 60, V-Sync Auto. Each multiplier was set in the menu and the game restarted. **The mode choice works as described.** At 2x, DLSS-G makes the frame and the swapchain is FIFO. At 4x, the runtime makes 1, and the swapchain is remade as mailbox once NGX has answered: ``` vk: DLSS Frame Gen (NGX): available, up to 1 made frames between two of the game's (...) vk: V-Sync Auto with DLSS Frame Generation at 4x: FIFO vk: V-Sync Auto with frame generation at 4x, its frames the renderer's own (DLSS-G does not make them): Auto's mode, as without DLSS-G vk: swapchain 2558x1360, 6 images, mailbox (V-Sync Auto) vk: DLSS Frame Gen (NGX): 3 made frames asked, but its runtime makes 1 at most (an older nvngx_dlssg.dll?): the renderer's own makes them, and V-Sync Auto keeps its mode without DLSS-G ``` **But #2695's symptom stays at 4x: the game is still held at 36.0 fps (144 / 4), with the same bunched presents.** Every status line in play shows it, in a window (2558x1360) and in fullscreen (5120x1440, Reflex on): ``` vk: 2400 presents, 36.0 fps; a frame: 3383 draws (0 skipped), ... vk: frame interpolation: 0 frames shown on time, 720 late (over 2 ms); shown apart: <7 ms 540, 7-9 0, 9-12 0, 12-17 0, 17-25 0, over 25 180 vk: DLSS Frame Gen (NGX): of the last 600 presents 0 made by DLSS-G (...); the renderer's own made the rest: 0 without new inputs (no DLSS/DLAA this frame), 1 after a gap, 599 asked more than 1 frames, 0 refused ``` GPU time was about 9.5 ms a frame, so the GPU is not what holds it. It is not FIFO either: the swapchain is mailbox in both runs. In play it felt like uneven pacing and very high latency in the window; in fullscreen the pacing felt better, but the latency was just as bad. At 2x and at 2x with Reflex on, DLSS-G works as intended: 600 of each 600 presents made by DLSS-G, 56-61 fps in Kokiri once past the load (1,400-3,800 draws), and (2x) 601 of 615 frames shown on time, 7-9 ms apart. It looked smooth, with no shimmer. So on Windows the FIFO choice looks like only part of #2695: the game stays at refresh / multiplier with mailbox too. @jabbaxipil, does your RTX 4070 show the same on Linux with this PR? If it does, #2695 may need reopening. I can run builds with more logging on this machine. Not tested here: 3x in play (it is the same fallback), Frame pacing off, an uncapped game, and Linux. 🤖 Generated with [Claude Code](https://claude.com/claude-code) <!-- github-sync:c:6823286881 -->
Author
Owner

@jabbaxipil commented on GitHub · 2026-11-12 16:05 UTC · original

@hckrman101 @Jiro131 Tested on the RTX 4070 on Linux: the fix works here, and the game is no longer held at refresh / multiplier.

Setup: RTX 4070, NVIDIA 615.71.09, DLSS-G runtime libnvidia-ngx-dlssg.so.310.9.16, Ryzen 7 5800X3D, Ubuntu 26.04.1 (kernel 7.0), GNOME Wayland, 2560x1440 fullscreen at 144 Hz. CI AppImage from main (v2.1.0-rc.3-256-g51da0464, which has #2701), built with NGX. DLSS, Frame generation by Auto, frame pacing on, V-Sync Auto, Reflex on; RX_VK_PARALLEL=4. Hyrule Field, standing still, about 60 s a run.

The mode choice is as described: at 4x the log shows V-Sync Auto with DLSS Frame Generation at 4x: FIFO, then 3 made frames asked, but its runtime makes 1 at most ... V-Sync Auto keeps its mode without DLSS-G, and the swapchain is made once more as mailbox. One change from FIFO to mailbox, no loop. At 2x every swapchain is FIFO.

4x, cap 144 (#2695) 4x, cap 144 4x, uncapped 2x, cap 144
Build rc.3-237 with #2701 with #2701 with #2701
Swapchain FIFO mailbox mailbox FIFO
Game fps, mean (min-max) 35.9 (35.7-36.0) 54.9 (52.9-56.5) 56.4 (54.8-57.3) 57.1 (53.0-58.2)
Frames shown on time / late 0 / 11,540 20,863 / 45 21,432 / 32 20,016 / 2
Made by DLSS-G (of each 600) 0 0 0 600
GPU time a frame (median) 3.7 ms 3.8 ms 3.7 ms 5.2 ms

No slow submit lines while standing in any run (with the fix, the only ones came earlier, at start-up and on the way to the spot), and no write-back timeout or device lost in the three runs with the fix. At 2x, 99.6% of shown frames were 7-12 ms apart.

So on Linux the FIFO choice was all of #2695: with mailbox, 4x runs at about the game's rate at 2x (54.9-56.4 fps against 57.1). @Jiro131's result on Windows (36.0 fps with mailbox) did not show up here, so it looks like a separate, Windows-side problem rather than a reason to reopen #2695. I did not test Windows, Frame pacing off, 3x or RX_VK_DLSS_FG_MOST.

> [@jabbaxipil](https://github.com/jabbaxipil) commented on GitHub · 2026-11-12 16:05 UTC · [original](https://github.com/NotRexed/Hookshot/pull/2701#issuecomment-6828823798) @hckrman101 @Jiro131 Tested on the RTX 4070 on Linux: the fix works here, and the game is no longer held at refresh / multiplier. **Setup:** RTX 4070, NVIDIA 615.71.09, DLSS-G runtime `libnvidia-ngx-dlssg.so.310.9.16`, Ryzen 7 5800X3D, Ubuntu 26.04.1 (kernel 7.0), GNOME Wayland, 2560x1440 fullscreen at 144 Hz. CI AppImage from `main` (`v2.1.0-rc.3-256-g51da0464`, which has #2701), built with NGX. DLSS, Frame generation by Auto, frame pacing on, V-Sync Auto, Reflex on; `RX_VK_PARALLEL=4`. Hyrule Field, standing still, about 60 s a run. **The mode choice** is as described: at 4x the log shows `V-Sync Auto with DLSS Frame Generation at 4x: FIFO`, then `3 made frames asked, but its runtime makes 1 at most ... V-Sync Auto keeps its mode without DLSS-G`, and the swapchain is made once more as mailbox. One change from FIFO to mailbox, no loop. At 2x every swapchain is FIFO. | | 4x, cap 144 (#2695) | 4x, cap 144 | 4x, uncapped | 2x, cap 144 | |---|---|---|---|---| | Build | rc.3-237 | with #2701 | with #2701 | with #2701 | | Swapchain | FIFO | mailbox | mailbox | FIFO | | Game fps, mean (min-max) | 35.9 (35.7-36.0) | 54.9 (52.9-56.5) | 56.4 (54.8-57.3) | 57.1 (53.0-58.2) | | Frames shown on time / late | 0 / 11,540 | 20,863 / 45 | 21,432 / 32 | 20,016 / 2 | | Made by DLSS-G (of each 600) | 0 | 0 | 0 | 600 | | GPU time a frame (median) | 3.7 ms | 3.8 ms | 3.7 ms | 5.2 ms | No `slow submit` lines while standing in any run (with the fix, the only ones came earlier, at start-up and on the way to the spot), and no write-back timeout or device lost in the three runs with the fix. At 2x, 99.6% of shown frames were 7-12 ms apart. So on Linux the FIFO choice was all of #2695: with mailbox, 4x runs at about the game's rate at 2x (54.9-56.4 fps against 57.1). @Jiro131's result on Windows (36.0 fps with mailbox) did not show up here, so it looks like a separate, Windows-side problem rather than a reason to reopen #2695. I did not test Windows, Frame pacing off, 3x or `RX_VK_DLSS_FG_MOST`. <!-- github-sync:c:6828823798 -->
Commenting is not possible because the repository is archived.
No description provided.