feat(dlss): NVIDIA DLSS Frame Generation through Streamline (Vulkan, manual hooking, Reflex) #1944

Closed
j64esatxzdrhfg wants to merge 1 commit from feat/dlss-frame-generation into main
j64esatxzdrhfg commented 2026-11-10 21:23:36 +00:00 (Migrated from github.com)

On RTX 40/50 cards, DLSS Frame Generation gives smoother motion than the renderer's own interpolation, at lower latency. NVIDIA's network makes the frames and Streamline's Reflex paces them. This overlaps #1831, which does frame generation through NGX directly and keeps a Streamline path switched off. This is offered as an alternative: happy to close it if you prefer the NGX route.

Since the review (rebased on main d7a7306, still one commit)

  • Exit and Restart crash: Streamline is now shut down from mkwvk_close_asked, which the window's close, Quit and Restart all go through before the host's ExitProcess. dlssg_shutdown:
    • stops letting dlssg_* calls in;
    • waits for a Reflex sleep under way (dlssg_sleep drops the state lock during it) without holding the present lock;
    • takes the present lock and the queue's lock, 2 s at most each. Past that it leaves Streamline as it is, so a stuck present can't hang the exit;
    • waits for the device to go idle (device_wait_idle_locked), turns DLSS-G off, then calls slShutdown.
    • It runs once. Every dlssg_* call does nothing afterwards, and both locks stay held until the process ends, so nothing reaches Streamline's hooks after it shut down.
    • A --prepare-shaders run (no window, so no close) no longer starts Streamline.
  • 0xbad00004 with nvngx_dlss.dll only in ngx\: NGX starts once per process and keeps the folder list from that first start. With Streamline, the first start is sl.common's when the device is made. sl.common passes NGX the plugins' folder plus every entry of Preferences::pathsToPlugins (commonEntry.cpp in the 2.14.16 source). So pathsToPlugins now lists, after bin\ (the first folder with plugins still wins for the plugins), the same folders vk_dlss.cpp gives NGX: RX_VK_DLSS_DIR, the game folder's ngx, %LOCALAPPDATA%\Hookshot\ngx. Nothing is copied beside the exe. With Streamline running, vk_dlss.cpp now logs this case as NGX not finding the runtime in its folders instead of blaming the GPU. The game folder is also taken wide now, not through the ANSI code page.
  • RX_VK_DLSSG is applied over opts.dlssg in menu_init, next to RX_VK_FRAMEGEN_MULT, so it also turns frame generation on for that run.
  • motionVectorsInvalidValue is left at Streamline's default (INVALID_FLOAT).
  • tests/upscale_independent_test.cpp gets the dlssg_active() stub.
  • Nits:
    • ini_key has an explicit dlss_frame_gen entry, which saved_dlssg reads.
    • The row's help says the game's own rate is held to the shown-frame limit divided by the multiple (about 39 fps at 4x on 165 Hz), and that DLAA at a 4K output can overload even an RTX 4080.
    • The log has a line when the NVIDIA app's DLSS override makes Streamline use NVIDIA's plugins from ProgramData.
    • --accept-nvidia-license's help names the licenses for each fetch.
    • CONTRIBUTING notes that a future Builder tick needs the license text the DLSS tick has.

What changed

  • gpu/vk/vk_dlssg.cpp/.h: Streamline 2.14 with manual hooking. With the DLSS Frame Gen row set, sl.interposer.dll's vkGetInstanceProcAddr replaces vulkan-1.dll in load_global, after NVIDIA's signature is checked. The row is read from hookshot_options.ini before Vulkan starts; RX_VK_DLSSG overrides it.
  • Per frame:
    • the chain's depth and motion are tagged;
    • constants come from the scene camera;
    • PCL markers and Reflex sleep go through Streamline;
    • FG is on for game frames, off while the Insert menu is open, and off after 60 frames without inputs;
    • a Reflex limit on shown frames of refresh − refresh²/3600 (116 at 120 Hz).
  • The FPS counter shows made and game frames.
  • Inputs come from the temporal upscaler's frame chain (vk_temporal.cpp: DLSS, DLAA, FSR 4) after its engine drew. The chain keeps the scene camera's projection. Main's present-ahead split is respected: the hook sits right before present_out.
  • vk_reflex.cpp, vk_framegen.cpp, vk_fsr3fi.cpp: the renderer's own Reflex and frame generation stand aside while it runs.
  • vk_main.cpp: mkwvk_close_asked ends with dlssg_shutdown(). vk_dlss.cpp: the 0xbad00004 log line with Streamline running.
  • Menu: DLSS Frame Gen (Off, 2x–4x; restart). It is shown only in a build with Streamline on an NVIDIA GPU, and its help says why it isn't running when set.
  • Build:
    • nx2/build.py: HOOKSHOT_SL_SDK (Windows) builds vk_dlssg.cpp with Streamline's headers and links wintrust/crypt32.
    • nx2/make_game.py: copies the seven runtime DLLs beside hookshot.exe; --fetch-sl-sdk fetches the SDK first. The DLSS runtime stays in ngx\.
    • scripts/sl_sdk.py: downloads the pinned v2.14.16 release zip after NVIDIA's licenses are accepted, and checks the zip and every extracted file by SHA-256. NVIDIA's files are never committed or shipped.
  • Tests:
    • test_dlssg.py, test_sl_sdk.py, test_make_game_sl.py. test_dlssg.py now also covers the exit. It cuts State, dlssg_marker, dlssg_sleep and dlssg_shutdown out of vk_dlssg.cpp and runs them against a stand-in SDK whose Reflex sleep blocks:

      • shutdown waits for the sleep with the present lock free;
      • no marker and no new sleep gets in after shutdown begins;
      • the order is idle, DLSS-G off, slShutdown;
      • a second close does nothing;
      • a stuck present or a stuck sleep gives up after the wait.

      Each of these fails against a mutant that drops the matching step. It also checks RX_VK_DLSSG in menu_init, the NGX folders, and the close and Restart path in host/display.c.

    • test_reflex_swapchain_lifetime, test_shader_stamp_cache, the framegen and upscale_independent fixtures, test_audio_output_menu and test_stick_dead_zone know vk_dlssg.cpp and the row.

Testing

  • First version: RTX 5080, 120 Hz G-SYNC, VSync from the NVIDIA app, DLSS Balanced, in a build of main (8c92fe3) with this series.
    • With frame generation: 116 fps shown, 58 of the game's (screenshot); without it, 96.
    • No tearing with the driver's VSync and G-SYNC (on the v2.0.13 build).
    • Streamline itself doesn't support VSync with FG on Vulkan, but the driver's VSync works. Frame Warp isn't available on Vulkan.
  • Thanks to Zarigis (RTX 4080 SUPER), pmwork-personal (RTX 5090) and Arkex-ai (RTX 5070 Ti), whose runs found the crash and the bugs above.
  • This round, on main d7a7306:
    • vk_dlssg.cpp compiles with and without Streamline, vk_dlss.cpp with and without NGX, and the other touched files compile.
    • tests/run_all.py dlssg sl_sdk make_game_sl make_game_ngx ngx_sdk upscale_independent framegen present_ reflex fps_rate menu stick_dead_zone on Windows: 32 passed, 3 failed.
      • test_present_aspect_vulkan fails the same way on untouched main.
      • test_reflex_swapchain_lifetime and test_present_semaphores default to clang++, which this PC lacks. Both pass with --cxx g++.
  • In game, this round (RTX 5080, a build of main d7a7306 with this PR): six sessions with DLSS Frame Gen on (2x and 3x), each closed through the window. Every one logged Streamline shut down for the exit (result 0) and exited, and Windows recorded no crash. A Restart from the menu relaunched fine from a session without Streamline; a Restart with Streamline running was not tried. DLSS/DLAA with nvngx_dlss.dll only in ngx\ still needs a PC without NVIDIA's own DLSS download: this one has the NVIDIA app's override, which is likely why DLSS worked here without the copy.

Not done / not tested

  • No Hookshot Builder tick yet: the command line only.
  • No rows past 4x, though Streamline offers up to 6x on RTX 50.
  • Not tried in the game: Restart with Streamline running, the 2 s give-up paths and a stuck device. vkDeviceWaitIdle has no timeout.
  • Not tried in the game: the NGX folder fix.
  • The download URL in sl_sdk.py was checked against a local copy of the zip, not fetched from CI.
  • Not tested: FSR 4 as the input, AMD Anti-Lag 2 alongside, Linux.
  • The menu screenshot below shows the row's help before this round's added sentence.

Screenshots

DLSS Frame Gen row and its help

DLSS Frame Gen 2x: 116 fps shown, 58 of the game's Off: 96 fps
DLSS Frame Generation on: 116 fps, game 58 DLSS Frame Generation off: 96 fps

HDR10 captures, tone-mapped to SDR for this page (paper white to white, highlights rolled off up to 800 nits).

🤖 Generated with Claude Code

On RTX 40/50 cards, DLSS Frame Generation gives smoother motion than the renderer's own interpolation, at lower latency. NVIDIA's network makes the frames and Streamline's Reflex paces them. This overlaps #1831, which does frame generation through NGX directly and keeps a Streamline path switched off. This is offered as an alternative: happy to close it if you prefer the NGX route. **Since the review** (rebased on main d7a7306, still one commit) - **Exit and Restart crash:** Streamline is now shut down from `mkwvk_close_asked`, which the window's close, Quit and Restart all go through before the host's `ExitProcess`. `dlssg_shutdown`: - stops letting `dlssg_*` calls in; - waits for a Reflex sleep under way (`dlssg_sleep` drops the state lock during it) without holding the present lock; - takes the present lock and the queue's lock, 2 s at most each. Past that it leaves Streamline as it is, so a stuck present can't hang the exit; - waits for the device to go idle (`device_wait_idle_locked`), turns DLSS-G off, then calls `slShutdown`. - It runs once. Every `dlssg_*` call does nothing afterwards, and both locks stay held until the process ends, so nothing reaches Streamline's hooks after it shut down. - A `--prepare-shaders` run (no window, so no close) no longer starts Streamline. - **`0xbad00004` with `nvngx_dlss.dll` only in `ngx\`:** NGX starts once per process and keeps the folder list from that first start. With Streamline, the first start is `sl.common`'s when the device is made. `sl.common` passes NGX the plugins' folder plus every entry of `Preferences::pathsToPlugins` (`commonEntry.cpp` in the 2.14.16 source). So `pathsToPlugins` now lists, after `bin\` (the first folder with plugins still wins for the plugins), the same folders `vk_dlss.cpp` gives NGX: `RX_VK_DLSS_DIR`, the game folder's `ngx`, `%LOCALAPPDATA%\Hookshot\ngx`. Nothing is copied beside the exe. With Streamline running, `vk_dlss.cpp` now logs this case as NGX not finding the runtime in its folders instead of blaming the GPU. The game folder is also taken wide now, not through the ANSI code page. - **`RX_VK_DLSSG`** is applied over `opts.dlssg` in `menu_init`, next to `RX_VK_FRAMEGEN_MULT`, so it also turns frame generation on for that run. - **`motionVectorsInvalidValue`** is left at Streamline's default (`INVALID_FLOAT`). - **`tests/upscale_independent_test.cpp`** gets the `dlssg_active()` stub. - Nits: - `ini_key` has an explicit `dlss_frame_gen` entry, which `saved_dlssg` reads. - The row's help says the game's own rate is held to the shown-frame limit divided by the multiple (about 39 fps at 4x on 165 Hz), and that DLAA at a 4K output can overload even an RTX 4080. - The log has a line when the NVIDIA app's DLSS override makes Streamline use NVIDIA's plugins from ProgramData. - `--accept-nvidia-license`'s help names the licenses for each fetch. - CONTRIBUTING notes that a future Builder tick needs the license text the DLSS tick has. **What changed** - `gpu/vk/vk_dlssg.cpp/.h`: Streamline 2.14 with manual hooking. With the DLSS Frame Gen row set, `sl.interposer.dll`'s `vkGetInstanceProcAddr` replaces `vulkan-1.dll` in `load_global`, after NVIDIA's signature is checked. The row is read from `hookshot_options.ini` before Vulkan starts; `RX_VK_DLSSG` overrides it. - Per frame: - the chain's depth and motion are tagged; - constants come from the scene camera; - PCL markers and Reflex sleep go through Streamline; - FG is on for game frames, off while the Insert menu is open, and off after 60 frames without inputs; - a Reflex limit on shown frames of refresh − refresh²/3600 (116 at 120 Hz). - The FPS counter shows made and game frames. - Inputs come from the temporal upscaler's frame chain (`vk_temporal.cpp`: DLSS, DLAA, FSR 4) after its engine drew. The chain keeps the scene camera's projection. Main's present-ahead split is respected: the hook sits right before `present_out`. - `vk_reflex.cpp`, `vk_framegen.cpp`, `vk_fsr3fi.cpp`: the renderer's own Reflex and frame generation stand aside while it runs. - `vk_main.cpp`: `mkwvk_close_asked` ends with `dlssg_shutdown()`. `vk_dlss.cpp`: the `0xbad00004` log line with Streamline running. - Menu: DLSS Frame Gen (Off, 2x–4x; restart). It is shown only in a build with Streamline on an NVIDIA GPU, and its help says why it isn't running when set. - Build: - `nx2/build.py`: `HOOKSHOT_SL_SDK` (Windows) builds `vk_dlssg.cpp` with Streamline's headers and links wintrust/crypt32. - `nx2/make_game.py`: copies the seven runtime DLLs beside `hookshot.exe`; `--fetch-sl-sdk` fetches the SDK first. The DLSS runtime stays in `ngx\`. - `scripts/sl_sdk.py`: downloads the pinned v2.14.16 release zip after NVIDIA's licenses are accepted, and checks the zip and every extracted file by SHA-256. NVIDIA's files are never committed or shipped. - Tests: - `test_dlssg.py`, `test_sl_sdk.py`, `test_make_game_sl.py`. `test_dlssg.py` now also covers the exit. It cuts `State`, `dlssg_marker`, `dlssg_sleep` and `dlssg_shutdown` out of `vk_dlssg.cpp` and runs them against a stand-in SDK whose Reflex sleep blocks: - shutdown waits for the sleep with the present lock free; - no marker and no new sleep gets in after shutdown begins; - the order is idle, DLSS-G off, `slShutdown`; - a second close does nothing; - a stuck present or a stuck sleep gives up after the wait. Each of these fails against a mutant that drops the matching step. It also checks `RX_VK_DLSSG` in `menu_init`, the NGX folders, and the close and Restart path in `host/display.c`. - `test_reflex_swapchain_lifetime`, `test_shader_stamp_cache`, the framegen and upscale_independent fixtures, `test_audio_output_menu` and `test_stick_dead_zone` know `vk_dlssg.cpp` and the row. **Testing** - First version: RTX 5080, 120 Hz G-SYNC, VSync from the NVIDIA app, DLSS Balanced, in a build of main (8c92fe3) with this series. - With frame generation: 116 fps shown, 58 of the game's (screenshot); without it, 96. - No tearing with the driver's VSync and G-SYNC (on the v2.0.13 build). - Streamline itself doesn't support VSync with FG on Vulkan, but the driver's VSync works. Frame Warp isn't available on Vulkan. - Thanks to Zarigis (RTX 4080 SUPER), pmwork-personal (RTX 5090) and Arkex-ai (RTX 5070 Ti), whose runs found the crash and the bugs above. - This round, on main d7a7306: - `vk_dlssg.cpp` compiles with and without Streamline, `vk_dlss.cpp` with and without NGX, and the other touched files compile. - `tests/run_all.py dlssg sl_sdk make_game_sl make_game_ngx ngx_sdk upscale_independent framegen present_ reflex fps_rate menu stick_dead_zone` on Windows: 32 passed, 3 failed. - `test_present_aspect_vulkan` fails the same way on untouched main. - `test_reflex_swapchain_lifetime` and `test_present_semaphores` default to clang++, which this PC lacks. Both pass with `--cxx g++`. - **In game, this round** (RTX 5080, a build of main d7a7306 with this PR): six sessions with DLSS Frame Gen on (2x and 3x), each closed through the window. Every one logged `Streamline shut down for the exit (result 0)` and exited, and Windows recorded no crash. A Restart from the menu relaunched fine from a session without Streamline; a Restart with Streamline running was not tried. DLSS/DLAA with `nvngx_dlss.dll` only in `ngx\` still needs a PC without NVIDIA's own DLSS download: this one has the NVIDIA app's override, which is likely why DLSS worked here without the copy. **Not done / not tested** - No Hookshot Builder tick yet: the command line only. - No rows past 4x, though Streamline offers up to 6x on RTX 50. - Not tried in the game: Restart with Streamline running, the 2 s give-up paths and a stuck device. `vkDeviceWaitIdle` has no timeout. - Not tried in the game: the NGX folder fix. - The download URL in `sl_sdk.py` was checked against a local copy of the zip, not fetched from CI. - Not tested: FSR 4 as the input, AMD Anti-Lag 2 alongside, Linux. - The menu screenshot below shows the row's help before this round's added sentence. **Screenshots** ![DLSS Frame Gen row and its help](https://raw.githubusercontent.com/j64esatxzdrhfg/Hookshot/pr-screenshots/dlss-fg/menu-dlss-frame-gen.jpg) | DLSS Frame Gen 2x: 116 fps shown, 58 of the game's | Off: 96 fps | |---|---| | ![DLSS Frame Generation on: 116 fps, game 58](https://raw.githubusercontent.com/j64esatxzdrhfg/Hookshot/pr-screenshots/dlss-fg/fg-on-116-of-58.jpg) | ![DLSS Frame Generation off: 96 fps](https://raw.githubusercontent.com/j64esatxzdrhfg/Hookshot/pr-screenshots/dlss-fg/fg-off-96.jpg) | _HDR10 captures, tone-mapped to SDR for this page (paper white to white, highlights rolled off up to 800 nits)._ 🤖 Generated with [Claude Code](https://claude.com/claude-code)
Zarigis commented 2026-11-10 22:34:53 +00:00 (Migrated from github.com)

Tested on an RTX 4080 SUPER (so RTX 40 now has a data point). With DLSS Quality it works well: the frames double, all of them reach the screen, and pacing is the most even of the frame generation options we have. The cost is small: the game's own frame rate (not counting made frames) went from 53.4 to 51.2 fps. Two bugs got in the way first, both small:

  1. DLSS does not start once Streamline is loaded unless nvngx_dlss.dll is also beside hookshot.exe. With it only in ngx\ (where make_game.py puts it), DLSS/DLAA fail with 0xbad00004, so DLSS-G never gets inputs.
  2. RX_VK_DLSSG=1 alone starts Streamline but never turns DLSS-G on. It doesn't set opts.dlssg, which dlssg_inputs and dlssg_before_present check, and nothing is logged.
Measurements

Market gate, a scripted 20 s camera spin, 4K output (3840x2160), HDR10, windowed (1920x1080 window, "Composed: Flip"), 239 Hz display, MAILBOX. RTX 4080 SUPER, driver 617.42, Windows 11. A build of main (907ec4e) with this PR merged. PresentMon 2.6 captured display times for every present. One run each:

Setup Game fps Frames shown/s Display changes 2-3 refreshes apart Single-refresh changes 95th pct game frame
DLSS Quality, no frame generation 53.4 53 – 0% 24.4 ms
DLSS Quality + DLSS Frame Gen 2x 51.2 102.6 78% 18% 25.2 ms
DLAA + DLSS Frame Gen 2x 28.3 56 37% 8% 67.7 ms
The renderer's own 2x, Frame pacing on (100%, no upscaler) 41 ~80 37% 24% –

The log agrees with PresentMon: DLSS Frame Gen: 104.4 fps shown, 52.2 of the game's.

  • At 239 Hz, half of a ~51 fps frame is about 2.3 refreshes, so alternating 2 and 3 is the best possible. DLSS-G was close to that.
  • DLAA plus DLSS-G at 4K overloads this GPU: 28 game fps and frames up to 200 ms. A hint on the row about DLAA at high output resolutions might save people some confusion.
Bug 1: DLSS needs the runtime beside hookshot.exe while Streamline is loaded

Without Streamline, NGX loads nvngx_dlss.dll from the game's ngx\ folder as before. With Streamline loaded (the DLSS Frame Gen row on), the same build and folder give:

vk: NVIDIA DLSS: not available on this GPU (0xbad00004): DLSS needs an NVIDIA RTX GPU

NVSDK_NGX_VULKAN_Init succeeds, but SuperSampling_FeatureInitResult is 0xbad00004. With a copy of nvngx_dlss.dll next to hookshot.exe, DLSS starts, and NGX's own log (RX_VK_DLSS_LOG=1) shows where it took the runtime from:

ngx: [NGXInitLog:226] Loaded from path "...\nvngx_dlss.dll" by hookshot.exe   (the exe's folder, not ngx\)

I haven't confirmed why. My guess is that Streamline's own NGX start comes first, so the path list vk_dlss.cpp passes later isn't used. A simple fix is for make_game.py (and sl_sdk.py --game) to also put nvngx_dlss.dll beside hookshot.exe when HOOKSHOT_SL_SDK is set. The log message is misleading too: it blames the GPU.

Bug 2: RX_VK_DLSSG doesn't turn DLSS-G on

asked() in vk_dlssg.cpp reads RX_VK_DLSSG to decide whether to start Streamline, but opts.dlssg keeps the options file's value. dlssg_inputs returns early on opts.dlssg <= 0, and dlssg_before_present decides "the row is off" every frame. Since that never changes the mode, it returns before logging anything. The result is Streamline started, DLSS-G ready, and no frames made, with nothing in the log saying why.

Setting dlss_frame_gen=1 in hookshot_options.ini works. Applying RX_VK_DLSSG over opts.dlssg where the options are read (as RX_VK_FRAMEGEN_MULT does in vk_menu.cpp) would match the readme's "RX_VK_DLSSG=0..3 for one run".

Also worth knowing when testing
  • Streamline doesn't interpolate while the window isn't focused (sl.log: DLSS-G disabled: window not focused). A test launched in the background shows DLSS-G "on, 2x" with "fps shown" equal to the game's.
  • In that state it still costs a lot. With DLAA: 34 game fps against 43 with the row off and 46 without Streamline. Not a problem in normal play, but it can mislead an unattended benchmark.
  • sl.log also logs Failed to find global tag 'kBufferTypeDepth' / 'kBufferTypeMotionVectors' once, around an area load. Harmless as far as I can see.
Tested on an RTX 4080 SUPER (so RTX 40 now has a data point). With DLSS Quality it works well: the frames double, all of them reach the screen, and pacing is the most even of the frame generation options we have. The cost is small: the game's own frame rate (not counting made frames) went from 53.4 to 51.2 fps. Two bugs got in the way first, both small: 1. **DLSS does not start once Streamline is loaded** unless `nvngx_dlss.dll` is also beside `hookshot.exe`. With it only in `ngx\` (where `make_game.py` puts it), DLSS/DLAA fail with `0xbad00004`, so DLSS-G never gets inputs. 2. **`RX_VK_DLSSG=1` alone starts Streamline but never turns DLSS-G on.** It doesn't set `opts.dlssg`, which `dlssg_inputs` and `dlssg_before_present` check, and nothing is logged. <details><summary>Measurements</summary> Market gate, a scripted 20 s camera spin, 4K output (3840x2160), HDR10, windowed (1920x1080 window, "Composed: Flip"), 239 Hz display, MAILBOX. RTX 4080 SUPER, driver 617.42, Windows 11. A build of main (907ec4e) with this PR merged. PresentMon 2.6 captured display times for every present. One run each: | Setup | Game fps | Frames shown/s | Display changes 2-3 refreshes apart | Single-refresh changes | 95th pct game frame | |---|---|---|---|---|---| | DLSS Quality, no frame generation | 53.4 | 53 | – | 0% | 24.4 ms | | **DLSS Quality + DLSS Frame Gen 2x** | 51.2 | 102.6 | **78%** | 18% | 25.2 ms | | DLAA + DLSS Frame Gen 2x | 28.3 | 56 | 37% | 8% | 67.7 ms | | The renderer's own 2x, Frame pacing on (100%, no upscaler) | 41 | ~80 | 37% | 24% | – | The log agrees with PresentMon: `DLSS Frame Gen: 104.4 fps shown, 52.2 of the game's`. - At 239 Hz, half of a ~51 fps frame is about 2.3 refreshes, so alternating 2 and 3 is the best possible. DLSS-G was close to that. - DLAA plus DLSS-G at 4K overloads this GPU: 28 game fps and frames up to 200 ms. A hint on the row about DLAA at high output resolutions might save people some confusion. </details> <details><summary>Bug 1: DLSS needs the runtime beside hookshot.exe while Streamline is loaded</summary> Without Streamline, NGX loads `nvngx_dlss.dll` from the game's `ngx\` folder as before. With Streamline loaded (the DLSS Frame Gen row on), the same build and folder give: ``` vk: NVIDIA DLSS: not available on this GPU (0xbad00004): DLSS needs an NVIDIA RTX GPU ``` `NVSDK_NGX_VULKAN_Init` succeeds, but `SuperSampling_FeatureInitResult` is `0xbad00004`. With a copy of `nvngx_dlss.dll` next to `hookshot.exe`, DLSS starts, and NGX's own log (`RX_VK_DLSS_LOG=1`) shows where it took the runtime from: ``` ngx: [NGXInitLog:226] Loaded from path "...\nvngx_dlss.dll" by hookshot.exe (the exe's folder, not ngx\) ``` I haven't confirmed why. My guess is that Streamline's own NGX start comes first, so the path list `vk_dlss.cpp` passes later isn't used. A simple fix is for `make_game.py` (and `sl_sdk.py --game`) to also put `nvngx_dlss.dll` beside `hookshot.exe` when `HOOKSHOT_SL_SDK` is set. The log message is misleading too: it blames the GPU. </details> <details><summary>Bug 2: RX_VK_DLSSG doesn't turn DLSS-G on</summary> `asked()` in `vk_dlssg.cpp` reads `RX_VK_DLSSG` to decide whether to start Streamline, but `opts.dlssg` keeps the options file's value. `dlssg_inputs` returns early on `opts.dlssg <= 0`, and `dlssg_before_present` decides "the row is off" every frame. Since that never changes the mode, it returns before logging anything. The result is Streamline started, DLSS-G ready, and no frames made, with nothing in the log saying why. Setting `dlss_frame_gen=1` in `hookshot_options.ini` works. Applying `RX_VK_DLSSG` over `opts.dlssg` where the options are read (as `RX_VK_FRAMEGEN_MULT` does in `vk_menu.cpp`) would match the readme's "`RX_VK_DLSSG=0..3` for one run". </details> <details><summary>Also worth knowing when testing</summary> - Streamline doesn't interpolate while the window isn't focused (`sl.log`: `DLSS-G disabled: window not focused`). A test launched in the background shows DLSS-G "on, 2x" with "fps shown" equal to the game's. - In that state it still costs a lot. With DLAA: 34 game fps against 43 with the row off and 46 without Streamline. Not a problem in normal play, but it can mislead an unattended benchmark. - `sl.log` also logs `Failed to find global tag 'kBufferTypeDepth'` / `'kBufferTypeMotionVectors'` once, around an area load. Harmless as far as I can see. </details>
pmwork-personal commented 2026-11-11 01:48:30 +00:00 (Migrated from github.com)

Tested this at 5120x2160 on an RTX 5090 (driver 621.14, Windows 11), DLAA, Streamline 2.14.16 matching the pinned hashes. Frame generation works: 2x showed ~91 fps from ~46 game fps, 3x 134–152 from 45–51, 4x 146–157 from 37–39. A few things from the run, in order of how much they hurt:

Every exit and every Restart crashed with DLSS Frame Gen on: an access violation inside NVIDIA's DLSS-G (read at 0x48). Nothing calls slShutdown, so the process exits with Streamline running, and the DLSS-G plugin tears its frame generation down in its DLL detach, after ExitProcess has already ended the threads it needs. What fixed it here: from mkwvk_close_asked (before the host's ExitProcess), take the present lock (with a ~2 s timeout so a stuck present can't hang the exit), wait for the device to go idle, set DLSS-G's mode to off, then slShutdown, and have every dlssg_* call do nothing after that. Clean exits and restarts afterwards, with "Streamline shut down for the exit (result 0)" in the log. Happy to share the diff.
Link's shield and hair stepped in made frames (no motion on those draws). #1831/#1834's actor-motion layouts (one bone, two bones, rigid on a joint) fixed both. His arms and feet still step a little when jumping, which I'm still looking into.
motionVectorsInvalidValue = 0.0f marks every pixel with exactly zero motion as invalid (anything still while the camera is still). Streamline's default is INVALID_FLOAT ("no invalid value"). Probably worth using that unless 0 was chosen on purpose.
On current main, tests/upscale_independent_test.cpp needs a dlssg_active() stub (framegen_on asks for it now).
Streamline reports up to 6x on RTX 50; the row stops at 4x. At 165 Hz Reflex holds shown frames to 157, so 4x caps the game at ~39 fps. The row's help could say that.
Also, with the NVIDIA app's DLSS override on, Streamline loads NVIDIA's OTA plugins from ProgramData instead of the ones next to the game. That's worth a log line, or a per-exe opt-out like other Streamline integrations use.

Tested this at 5120x2160 on an RTX 5090 (driver 621.14, Windows 11), DLAA, Streamline 2.14.16 matching the pinned hashes. Frame generation works: 2x showed ~91 fps from ~46 game fps, 3x 134–152 from 45–51, 4x 146–157 from 37–39. A few things from the run, in order of how much they hurt: Every exit and every Restart crashed with DLSS Frame Gen on: an access violation inside NVIDIA's DLSS-G (read at 0x48). Nothing calls slShutdown, so the process exits with Streamline running, and the DLSS-G plugin tears its frame generation down in its DLL detach, after ExitProcess has already ended the threads it needs. What fixed it here: from mkwvk_close_asked (before the host's ExitProcess), take the present lock (with a ~2 s timeout so a stuck present can't hang the exit), wait for the device to go idle, set DLSS-G's mode to off, then slShutdown, and have every dlssg_* call do nothing after that. Clean exits and restarts afterwards, with "Streamline shut down for the exit (result 0)" in the log. Happy to share the diff. Link's shield and hair stepped in made frames (no motion on those draws). #1831/#1834's actor-motion layouts (one bone, two bones, rigid on a joint) fixed both. His arms and feet still step a little when jumping, which I'm still looking into. motionVectorsInvalidValue = 0.0f marks every pixel with exactly zero motion as invalid (anything still while the camera is still). Streamline's default is INVALID_FLOAT ("no invalid value"). Probably worth using that unless 0 was chosen on purpose. On current main, tests/upscale_independent_test.cpp needs a dlssg_active() stub (framegen_on asks for it now). Streamline reports up to 6x on RTX 50; the row stops at 4x. At 165 Hz Reflex holds shown frames to 157, so 4x caps the game at ~39 fps. The row's help could say that. Also, with the NVIDIA app's DLSS override on, Streamline loads NVIDIA's OTA plugins from ProgramData instead of the ones next to the game. That's worth a log line, or a per-exe opt-out like other Streamline integrations use.
hckrman101 (Migrated from github.com) reviewed 2026-11-11 02:21:14 +00:00
hckrman101 (Migrated from github.com) left a comment

Thanks for this, and for the clear write-up. The Streamline side is put together carefully: manual hooking from load_global, the interposer's signature checked before it loads, Reflex/PCL markers through Streamline, and the renderer's own FG and Reflex standing aside. The two tester reports above (RTX 4080 SUPER, RTX 5090) show it working and pacing well. There are a few things to fix first, though, most of them already found by the testers:

  1. Every exit and Restart crashes with it on (@pmwork-personal's report). I checked: nothing calls slShutdown, so DLSS-G tears down in its DLL detach after ExitProcess. The fix they describe (from mkwvk_close_asked: take the present lock with a timeout, wait idle, DLSS-G mode off, slShutdown, then make every dlssg_* do nothing) looks right to me. A crash on every quit is the blocker here.
  2. RX_VK_DLSSG starts Streamline but never turns FG on (Zarigis' bug 2). Confirmed: saved_dlssg() reads it, but opts.dlssg keeps the ini's value, and dlssg_inputs / dlssg_before_present both check opts.dlssg. Apply it in menu_init next to RX_VK_FRAMEGEN_MULT (inline).
  3. DLSS/DLAA fail (0xbad00004) once Streamline is loaded unless nvngx_dlss.dll sits beside hookshot.exe (Zarigis' bug 1), so DLSS-G gets no inputs on a stock make_game.py folder. I'd rather find out why NGX ignores vk_dlss.cpp's path list (probably because Streamline's sl.common starts NGX first) than copy the DLSS runtime beside the exe, because main keeps it in ngx\ on purpose (place_ngx_runtime: "nothing beside the exe"). Either way the log line shouldn't blame the GPU.
  4. tests/test_upscale_independent.py breaks. It cuts framegen_on out of vk_framegen.cpp and compiles it against tests/upscale_independent_test.cpp, which has no dlssg_active(). That file needs a stub, the same as framegen_resources_test.cpp got. CI didn't run on this PR ("no checks reported"), so please run python -I -B tests/run_all.py locally.
  5. motionVectorsInvalidValue = 0.0f marks every pixel with exactly zero motion as invalid, which is anything still under a still camera. Streamline's default (INVALID_FLOAT) seems to be what's wanted (inline).

Overlap:

  • #1831 also adds gpu/vk/vk_dlssg.cpp / vk_dlssg.h (through NGX, with a different API), so whichever lands second has a hard conflict on the same file names. You already offered this as an alternative. Which route to take is @NotRexed's call, so I'm not approving either one over the other.
  • The branch is 63 commits behind main. Within this series, #1940/#1941/#1942 touch vk_temporal.cpp, vk_replay.cpp, vk_framegen.cpp and vk_menu.cpp near the same spots, and #1945/#1946 touch vk_menu.cpp/.h (Options). I expect textual conflicts only, but a rebase on main is due anyway.

Licensing / redistribution: this matches how main handles DLSS. scripts/sl_sdk.py mirrors ngx_sdk.py: a pinned release, SHA-256 on the zip and on every file taken out, NVIDIA's notice printed, --accept-nvidia-license or a typed yes required, the licenses saved beside the files, the zip deleted afterwards, sl-sdk/ and the zip gitignored. Nothing from NVIDIA is committed, linked (only wintrust/crypt32) or shipped; the release workflows don't set HOOKSHOT_SL_SDK, and the packaging only picks up the script. FSR 4's "bring your own DLLs" model is stricter still, and this is closer to the NGX one, which is fine. Two small points:

  • When the Builder gets a tick for this, it needs the same license text the DLSS tick has in MainWindow.xaml.cs (and the CONTRIBUTING note should say so, as the NGX section does).
  • --accept-nvidia-license in make_game.py now stands for both NVIDIA license sets. That's fine since each fetch has its own flag, but the help text could say which licenses it accepts for which fetch.

Nits:

  • saved_dlssg() hard-codes dlss_frame_gen=, which only matches because ini_key derives it from the row's name. Add an explicit ini_key entry for opts.dlssg so renaming the row can't silently break the pre-Vulkan read.
  • The comment at pathsToPlugins says no OTA plugins, but pmwork saw the NVIDIA app's DLSS override load OTA plugins from ProgramData anyway. A log line when that happens would help.
  • Row help: Streamline reports up to 6x on RTX 50 but the row stops at 4x. Also, as both testers saw, the Reflex cap divided by the multiplier caps the game's own rate (about 39 fps at 4x on 165 Hz), and DLAA at 4K can overload even a 4080. Both are worth a sentence in the help.
Thanks for this, and for the clear write-up. The Streamline side is put together carefully: manual hooking from `load_global`, the interposer's signature checked before it loads, Reflex/PCL markers through Streamline, and the renderer's own FG and Reflex standing aside. The two tester reports above (RTX 4080 SUPER, RTX 5090) show it working and pacing well. There are a few things to fix first, though, most of them already found by the testers: 1. **Every exit and Restart crashes with it on** (@pmwork-personal's report). I checked: nothing calls `slShutdown`, so DLSS-G tears down in its DLL detach after `ExitProcess`. The fix they describe (from `mkwvk_close_asked`: take the present lock with a timeout, wait idle, DLSS-G mode off, `slShutdown`, then make every `dlssg_*` do nothing) looks right to me. A crash on every quit is the blocker here. 2. **`RX_VK_DLSSG` starts Streamline but never turns FG on** (Zarigis' bug 2). Confirmed: `saved_dlssg()` reads it, but `opts.dlssg` keeps the ini's value, and `dlssg_inputs` / `dlssg_before_present` both check `opts.dlssg`. Apply it in `menu_init` next to `RX_VK_FRAMEGEN_MULT` (inline). 3. **DLSS/DLAA fail (`0xbad00004`) once Streamline is loaded unless `nvngx_dlss.dll` sits beside `hookshot.exe`** (Zarigis' bug 1), so DLSS-G gets no inputs on a stock `make_game.py` folder. I'd rather find out why NGX ignores `vk_dlss.cpp`'s path list (probably because Streamline's `sl.common` starts NGX first) than copy the DLSS runtime beside the exe, because main keeps it in `ngx\` on purpose (`place_ngx_runtime`: "nothing beside the exe"). Either way the log line shouldn't blame the GPU. 4. **`tests/test_upscale_independent.py` breaks.** It cuts `framegen_on` out of `vk_framegen.cpp` and compiles it against `tests/upscale_independent_test.cpp`, which has no `dlssg_active()`. That file needs a stub, the same as `framegen_resources_test.cpp` got. CI didn't run on this PR ("no checks reported"), so please run `python -I -B tests/run_all.py` locally. 5. `motionVectorsInvalidValue = 0.0f` marks every pixel with exactly zero motion as invalid, which is anything still under a still camera. Streamline's default (`INVALID_FLOAT`) seems to be what's wanted (inline). **Overlap:** - **#1831** also adds `gpu/vk/vk_dlssg.cpp` / `vk_dlssg.h` (through NGX, with a different API), so whichever lands second has a hard conflict on the same file names. You already offered this as an alternative. Which route to take is @NotRexed's call, so I'm not approving either one over the other. - The branch is 63 commits behind main. Within this series, #1940/#1941/#1942 touch `vk_temporal.cpp`, `vk_replay.cpp`, `vk_framegen.cpp` and `vk_menu.cpp` near the same spots, and #1945/#1946 touch `vk_menu.cpp/.h` (`Options`). I expect textual conflicts only, but a rebase on main is due anyway. **Licensing / redistribution:** this matches how main handles DLSS. `scripts/sl_sdk.py` mirrors `ngx_sdk.py`: a pinned release, SHA-256 on the zip and on every file taken out, NVIDIA's notice printed, `--accept-nvidia-license` or a typed yes required, the licenses saved beside the files, the zip deleted afterwards, `sl-sdk/` and the zip gitignored. Nothing from NVIDIA is committed, linked (only `wintrust`/`crypt32`) or shipped; the release workflows don't set `HOOKSHOT_SL_SDK`, and the packaging only picks up the script. FSR 4's "bring your own DLLs" model is stricter still, and this is closer to the NGX one, which is fine. Two small points: - When the Builder gets a tick for this, it needs the same license text the DLSS tick has in `MainWindow.xaml.cs` (and the CONTRIBUTING note should say so, as the NGX section does). - `--accept-nvidia-license` in `make_game.py` now stands for both NVIDIA license sets. That's fine since each fetch has its own flag, but the help text could say which licenses it accepts for which fetch. **Nits:** - `saved_dlssg()` hard-codes `dlss_frame_gen=`, which only matches because `ini_key` derives it from the row's name. Add an explicit `ini_key` entry for `opts.dlssg` so renaming the row can't silently break the pre-Vulkan read. - The comment at `pathsToPlugins` says no OTA plugins, but pmwork saw the NVIDIA app's DLSS override load OTA plugins from ProgramData anyway. A log line when that happens would help. - Row help: Streamline reports up to 6x on RTX 50 but the row stops at 4x. Also, as both testers saw, the Reflex cap divided by the multiplier caps the game's own rate (about 39 fps at 4x on 165 Hz), and DLAA at 4K can overload even a 4080. Both are worth a sentence in the help.
@ -0,0 +1,608 @@
// (hookshot) NVIDIA DLSS Frame Generation through NVIDIA Streamline 2.14 (vk_dlssg.h), on Vulkan with manual hooking (the
hckrman101 (Migrated from github.com) commented 2026-11-11 02:21:14 +00:00

With 0 as the invalid value, every pixel with exactly zero motion counts as invalid, which is everything static while the camera is still. Unless that was on purpose, leave Streamline's default (INVALID_FLOAT, "no invalid value").

With 0 as the invalid value, every pixel with exactly zero motion counts as invalid, which is everything static while the camera is still. Unless that was on purpose, leave Streamline's default (`INVALID_FLOAT`, "no invalid value").
@ -0,0 +72,4 @@
/// The row's value as saved, read before the menu reads its file (menu_init comes after the instance): 0 off, 1 to 3 (2x to
/// 4x). RX_VK_DLSSG=<0..3> over it
int saved_dlssg() {
if (const char* e = getenv("RX_VK_DLSSG")) return std::clamp(atoi(e), 0, 3);
hckrman101 (Migrated from github.com) commented 2026-11-11 02:21:14 +00:00

This makes Streamline start, but opts.dlssg still comes from the ini, so with dlss_frame_gen=0 (or no ini) and RX_VK_DLSSG=1, dlssg_inputs returns early and dlssg_before_present sees "the row is off" every frame, and nothing is logged (Zarigis' bug 2). Applying RX_VK_DLSSG over opts.dlssg in menu_init, where RX_VK_FRAMEGEN_MULT is applied, fixes it and matches the readme.

This makes Streamline start, but `opts.dlssg` still comes from the ini, so with `dlss_frame_gen=0` (or no ini) and `RX_VK_DLSSG=1`, `dlssg_inputs` returns early and `dlssg_before_present` sees "the row is off" every frame, and nothing is logged (Zarigis' bug 2). Applying `RX_VK_DLSSG` over `opts.dlssg` in `menu_init`, where `RX_VK_FRAMEGEN_MULT` is applied, fixes it and matches the readme.
@ -0,0 +362,4 @@
logf("vk: Streamline did not start (result %d): DLSS Frame Gen off", (int)r);
return nullptr; // (the module stays loaded: Streamline may have hooked something already)
}
s.active = true, s.why = DLSSG_OK;
hckrman101 (Migrated from github.com) commented 2026-11-11 02:21:14 +00:00

Nothing ever undoes this: there is no slShutdown anywhere, so the DLSS-G plugin tears down in DLL detach after ExitProcess has ended its threads. That's the access violation on every exit/Restart that pmwork-personal reported. You need a shutdown from mkwvk_close_asked (present lock with a timeout, idle, DLSS-G off, slShutdown, then every dlssg_* is a no-op after it).

Nothing ever undoes this: there is no `slShutdown` anywhere, so the DLSS-G plugin tears down in DLL detach after `ExitProcess` has ended its threads. That's the access violation on every exit/Restart that pmwork-personal reported. You need a shutdown from `mkwvk_close_asked` (present lock with a timeout, idle, DLSS-G off, `slShutdown`, then every `dlssg_*` is a no-op after it).
hckrman101 (Migrated from github.com) commented 2026-11-11 02:21:14 +00:00

tests/test_upscale_independent.py cuts this function into tests/upscale_independent_test.cpp, which has no dlssg_active(), so that test stops compiling. It needs a bool dlssg_active() { return false; } stub, like the one framegen_resources_test.cpp got.

`tests/test_upscale_independent.py` cuts this function into `tests/upscale_independent_test.cpp`, which has no `dlssg_active()`, so that test stops compiling. It needs a `bool dlssg_active() { return false; }` stub, like the one `framegen_resources_test.cpp` got.
Arkex-ai commented 2026-11-11 04:41:04 +00:00 (Migrated from github.com)

Additional RTX 5070 Ti / Windows11 data point:3440x1440, DLSS Quality2294x960, Streamline2.14.16 pinned runtimes. I tested this PR merged locally with mainf8efe057/API14, not the PR head alone.

I reproduced the exit access violation twice (read at0x48 inside nvngx_dlssg.dll). Calling slShutdown from mkwvk_close_asked fixed normal exits locally, consistent with @pmwork-personal's report. One concurrency detail: dlssg_sleep drops the state mutex while executing SDK sleep. Shutdown must stop admitting calls and drain that in-flight sleep before slShutdown. Do not hold the present lock while waiting for that sleep to return. My local causal fixture blocks SDK sleep, starts shutdown and verifies that idle/shutdown are delayed until sleep returns; it also checks no trailing marker after shutdown begins and idempotent close. It fails against my first shutdown attempt and passes after this coordination. Use device_wait_idle_locked() so the recorder boundary is preserved. Active-FG normal exits returned0. A stuck-device timeout and Restart remain untested.

Matched local snapshot: about60native/120displayed fps before pause and after stabilization. First second after inventory return:59.6native/111.4displayed, displayed P95=12.55ms; inventory about60/60. PresentMon2.6, no simultaneous pixel capture, one run per mode. Presented HUD/inventory text remained legible in inspected frames; this does not identify an individual generated frame or cover the campaign. Off/integrated/Streamline controls used the same host/backend/checkpoint, without a claim of an overall winner or input latency.

The runtime-beside-exe, RX_VK_DLSSG option and foreground prerequisites reported above also reproduced. Sharing the shutdown findings here rather than opening an overlapping FG integration.

Additional RTX 5070 Ti / Windows11 data point:3440x1440, DLSS Quality2294x960, Streamline2.14.16 pinned runtimes. I tested this PR merged locally with mainf8efe057/API14, not the PR head alone. I reproduced the exit access violation twice (read at0x48 inside nvngx_dlssg.dll). Calling slShutdown from mkwvk_close_asked fixed normal exits locally, consistent with @pmwork-personal's report. One concurrency detail: dlssg_sleep drops the state mutex while executing SDK sleep. Shutdown must stop admitting calls and drain that in-flight sleep before slShutdown. Do not hold the present lock while waiting for that sleep to return. My local causal fixture blocks SDK sleep, starts shutdown and verifies that idle/shutdown are delayed until sleep returns; it also checks no trailing marker after shutdown begins and idempotent close. It fails against my first shutdown attempt and passes after this coordination. Use device_wait_idle_locked() so the recorder boundary is preserved. Active-FG normal exits returned0. A stuck-device timeout and Restart remain untested. Matched local snapshot: about60native/120displayed fps before pause and after stabilization. First second after inventory return:59.6native/111.4displayed, displayed P95=12.55ms; inventory about60/60. PresentMon2.6, no simultaneous pixel capture, one run per mode. Presented HUD/inventory text remained legible in inspected frames; this does not identify an individual generated frame or cover the campaign. Off/integrated/Streamline controls used the same host/backend/checkpoint, without a claim of an overall winner or input latency. The runtime-beside-exe, RX_VK_DLSSG option and foreground prerequisites reported above also reproduced. Sharing the shutdown findings here rather than opening an overlapping FG integration.
j64esatxzdrhfg (Migrated from github.com) reviewed 2026-11-11 10:39:32 +00:00
@ -0,0 +72,4 @@
/// The row's value as saved, read before the menu reads its file (menu_init comes after the instance): 0 off, 1 to 3 (2x to
/// 4x). RX_VK_DLSSG=<0..3> over it
int saved_dlssg() {
if (const char* e = getenv("RX_VK_DLSSG")) return std::clamp(atoi(e), 0, 3);
j64esatxzdrhfg (Migrated from github.com) commented 2026-11-11 10:39:32 +00:00

Fixed: menu_init now applies RX_VK_DLSSG over opts.dlssg next to RX_VK_FRAMEGEN_MULT, before restart_init, so the env var both starts Streamline and turns frame generation on. test_dlssg.py checks both places.

Fixed: `menu_init` now applies `RX_VK_DLSSG` over `opts.dlssg` next to `RX_VK_FRAMEGEN_MULT`, before `restart_init`, so the env var both starts Streamline and turns frame generation on. `test_dlssg.py` checks both places.
j64esatxzdrhfg commented 2026-11-11 10:39:32 +00:00 (Migrated from github.com)

Thanks for the careful review. All five are addressed, rebased on main d7a7306, still one commit:

  1. Exit/Restart crash: dlssg_shutdown() now runs at the end of mkwvk_close_asked. Close, Quit and Restart all reach it through rx_close_asked before hos_window_closed_exit. The order is the one you and the testers described:

    • stop letting dlssg_* calls in;
    • wait for an in-flight dlssg_sleep with the present lock not held (Arkex-ai's point);
    • take the present and queue locks, 2 s at most each, else leave Streamline as it is;
    • device_wait_idle_locked(), DLSS-G off, slShutdown.

    It runs once, and both locks stay held until the process ends. test_dlssg.py runs the real functions against a stand-in SDK whose sleep blocks. A --prepare-shaders run no longer starts Streamline, since it never closes a window. In the game (RTX 5080, 2x and 3x): six sessions closed through the window, each logged Streamline shut down for the exit (result 0) and exited with no crash report. A Restart with Streamline running is still to be tried.

  2. RX_VK_DLSSG: applied over opts.dlssg in menu_init, next to RX_VK_FRAMEGEN_MULT.

  3. 0xbad00004: the root cause is that NGX keeps the folder list of its first start, and with Streamline that start is sl.common's. sl.common passes NGX the plugins' folder plus every Preferences::pathsToPlugins entry (commonEntry.cpp). The plugin manager takes plugins only from the first of those folders that has any. So pathsToPlugins now lists bin\ first, then vk_dlss.cpp's folders: RX_VK_DLSS_DIR, the game folder's ngx, %LOCALAPPDATA%\Hookshot\ngx. Nothing is copied beside the exe. With Streamline running, vk_dlss.cpp now logs 0xbad00004 as NGX not finding the runtime in its folders instead of blaming the GPU, and the menu shows "runtime not found" rather than "needs RTX". Not yet tried on a PC without NVIDIA's own DLSS download.

  4. upscale_independent_test.cpp: has the dlssg_active() stub. I ran tests/run_all.py filtered to the affected checks, not the whole suite. Of the 3 failures, test_present_aspect_vulkan fails the same way on untouched main. The other two default to clang++, which this PC lacks; both pass with g++.

  5. motionVectorsInvalidValue: removed; Streamline's INVALID_FLOAT applies.

Nits:

  • ini_key has an explicit dlss_frame_gen entry, and the test checks that saved_dlssg reads that key.
  • A log line appears when the NVIDIA app's DLSS override has Streamline use NVIDIA's plugins from ProgramData.
  • The row's help covers the shown-frame cap divided by the multiple (about 39 fps at 4x on 165 Hz) and DLAA at 4K.
  • --accept-nvidia-license's help names the licenses for each fetch.
  • CONTRIBUTING notes the license text a future Builder tick needs.
  • I left the row at 4x for now; 5x/6x for RTX 50 can follow separately.

On #1831: agreed, that's NotRexed's call.

Thanks for the careful review. All five are addressed, rebased on main d7a7306, still one commit: 1. **Exit/Restart crash:** `dlssg_shutdown()` now runs at the end of `mkwvk_close_asked`. Close, Quit and Restart all reach it through `rx_close_asked` before `hos_window_closed_exit`. The order is the one you and the testers described: - stop letting `dlssg_*` calls in; - wait for an in-flight `dlssg_sleep` with the present lock *not* held (Arkex-ai's point); - take the present and queue locks, 2 s at most each, else leave Streamline as it is; - `device_wait_idle_locked()`, DLSS-G off, `slShutdown`. It runs once, and both locks stay held until the process ends. `test_dlssg.py` runs the real functions against a stand-in SDK whose sleep blocks. A `--prepare-shaders` run no longer starts Streamline, since it never closes a window. In the game (RTX 5080, 2x and 3x): six sessions closed through the window, each logged `Streamline shut down for the exit (result 0)` and exited with no crash report. A Restart with Streamline running is still to be tried. 2. **`RX_VK_DLSSG`:** applied over `opts.dlssg` in `menu_init`, next to `RX_VK_FRAMEGEN_MULT`. 3. **`0xbad00004`:** the root cause is that NGX keeps the folder list of its first start, and with Streamline that start is `sl.common`'s. `sl.common` passes NGX the plugins' folder plus every `Preferences::pathsToPlugins` entry (`commonEntry.cpp`). The plugin manager takes plugins only from the first of those folders that has any. So `pathsToPlugins` now lists `bin\` first, then `vk_dlss.cpp`'s folders: `RX_VK_DLSS_DIR`, the game folder's `ngx`, `%LOCALAPPDATA%\Hookshot\ngx`. Nothing is copied beside the exe. With Streamline running, `vk_dlss.cpp` now logs `0xbad00004` as NGX not finding the runtime in its folders instead of blaming the GPU, and the menu shows "runtime not found" rather than "needs RTX". Not yet tried on a PC without NVIDIA's own DLSS download. 4. **`upscale_independent_test.cpp`:** has the `dlssg_active()` stub. I ran `tests/run_all.py` filtered to the affected checks, not the whole suite. Of the 3 failures, `test_present_aspect_vulkan` fails the same way on untouched main. The other two default to clang++, which this PC lacks; both pass with g++. 5. **`motionVectorsInvalidValue`:** removed; Streamline's `INVALID_FLOAT` applies. Nits: - `ini_key` has an explicit `dlss_frame_gen` entry, and the test checks that `saved_dlssg` reads that key. - A log line appears when the NVIDIA app's DLSS override has Streamline use NVIDIA's plugins from ProgramData. - The row's help covers the shown-frame cap divided by the multiple (about 39 fps at 4x on 165 Hz) and DLAA at 4K. - `--accept-nvidia-license`'s help names the licenses for each fetch. - CONTRIBUTING notes the license text a future Builder tick needs. - I left the row at 4x for now; 5x/6x for RTX 50 can follow separately. On #1831: agreed, that's NotRexed's call.
j64esatxzdrhfg (Migrated from github.com) reviewed 2026-11-11 10:39:34 +00:00
@ -0,0 +362,4 @@
logf("vk: Streamline did not start (result %d): DLSS Frame Gen off", (int)r);
return nullptr; // (the module stays loaded: Streamline may have hooked something already)
}
s.active = true, s.why = DLSSG_OK;
j64esatxzdrhfg (Migrated from github.com) commented 2026-11-11 10:39:34 +00:00

Added dlssg_shutdown(), called at the end of mkwvk_close_asked. It stops new calls, drains an in-flight Reflex sleep without the present lock, takes the present and queue locks with a 2 s limit, waits for idle, turns DLSS-G off, then calls slShutdown, once. Every dlssg_* is a no-op afterwards. slShutdown is now one of the required exports. In the game (RTX 5080, 2x and 3x): six window closes, each result 0, no crash; a Restart with Streamline running is still to try.

Added `dlssg_shutdown()`, called at the end of `mkwvk_close_asked`. It stops new calls, drains an in-flight Reflex sleep without the present lock, takes the present and queue locks with a 2 s limit, waits for idle, turns DLSS-G off, then calls `slShutdown`, once. Every `dlssg_*` is a no-op afterwards. `slShutdown` is now one of the required exports. In the game (RTX 5080, 2x and 3x): six window closes, each `result 0`, no crash; a Restart with Streamline running is still to try.
j64esatxzdrhfg (Migrated from github.com) reviewed 2026-11-11 10:39:36 +00:00
@ -0,0 +1,608 @@
// (hookshot) NVIDIA DLSS Frame Generation through NVIDIA Streamline 2.14 (vk_dlssg.h), on Vulkan with manual hooking (the
j64esatxzdrhfg (Migrated from github.com) commented 2026-11-11 10:39:36 +00:00

Not on purpose. Removed, so Streamline's default (INVALID_FLOAT, no invalid value) applies, and the test keeps it unset.

Not on purpose. Removed, so Streamline's default (`INVALID_FLOAT`, no invalid value) applies, and the test keeps it unset.
j64esatxzdrhfg (Migrated from github.com) reviewed 2026-11-11 10:39:37 +00:00
j64esatxzdrhfg (Migrated from github.com) commented 2026-11-11 10:39:37 +00:00

Added bool dlssg_active() { return false; } to tests/upscale_independent_test.cpp; test_upscale_independent passes again.

Added `bool dlssg_active() { return false; }` to `tests/upscale_independent_test.cpp`; `test_upscale_independent` passes again.
j64esatxzdrhfg commented 2026-11-11 10:39:39 +00:00 (Migrated from github.com)

@Zarigis Thanks for the RTX 4080 SUPER numbers and the detailed write-ups. Both bugs are fixed in the update:

  1. NGX keeps the folders of its first start, which with Streamline is sl.common's, and sl.common passes NGX its pathsToPlugins. Those now include the game folder's ngx (plus RX_VK_DLSS_DIR and %LOCALAPPDATA%\Hookshot\ngx), so nothing needs to sit beside hookshot.exe. The log line no longer blames the GPU for this case. It likely worked here only because this PC has NGX's own DLSS download in ProgramData. Could you check it on yours, with nvngx_dlss.dll only in ngx\?
  2. RX_VK_DLSSG now sets opts.dlssg too.

The DLAA-at-4K hint is in the row's help now.

@Zarigis Thanks for the RTX 4080 SUPER numbers and the detailed write-ups. Both bugs are fixed in the update: 1. NGX keeps the folders of its first start, which with Streamline is `sl.common`'s, and `sl.common` passes NGX its `pathsToPlugins`. Those now include the game folder's `ngx` (plus `RX_VK_DLSS_DIR` and `%LOCALAPPDATA%\Hookshot\ngx`), so nothing needs to sit beside `hookshot.exe`. The log line no longer blames the GPU for this case. It likely worked here only because this PC has NGX's own DLSS download in ProgramData. Could you check it on yours, with `nvngx_dlss.dll` only in `ngx\`? 2. `RX_VK_DLSSG` now sets `opts.dlssg` too. The DLAA-at-4K hint is in the row's help now.
j64esatxzdrhfg commented 2026-11-11 10:39:40 +00:00 (Migrated from github.com)

@pmwork-personal Thanks for tracking the crash down and for the clear recipe; the update follows it:

  • the shutdown runs from mkwvk_close_asked: present lock with a 2 s limit, idle, DLSS-G off, slShutdown, then no-ops.
  • motionVectorsInvalidValue is back to Streamline's default.
  • The upscale_independent stub is added.
  • The row's help mentions the Reflex cap per multiple.
  • A log line says when the NVIDIA app's override loads NVIDIA's plugins from ProgramData.

5x/6x, a per-exe OTA opt-out, and the actor-motion layouts from #1831/#1834 are left for later. The exit fix held in six window closes here (2x and 3x, result 0, no crash). If you can run Exit and especially Restart with DLSS Frame Gen on again, that would confirm it on your setup.

@pmwork-personal Thanks for tracking the crash down and for the clear recipe; the update follows it: - the shutdown runs from `mkwvk_close_asked`: present lock with a 2 s limit, idle, DLSS-G off, `slShutdown`, then no-ops. - `motionVectorsInvalidValue` is back to Streamline's default. - The `upscale_independent` stub is added. - The row's help mentions the Reflex cap per multiple. - A log line says when the NVIDIA app's override loads NVIDIA's plugins from ProgramData. 5x/6x, a per-exe OTA opt-out, and the actor-motion layouts from #1831/#1834 are left for later. The exit fix held in six window closes here (2x and 3x, `result 0`, no crash). If you can run Exit and especially Restart with DLSS Frame Gen on again, that would confirm it on your setup.
j64esatxzdrhfg commented 2026-11-11 10:39:41 +00:00 (Migrated from github.com)

@Arkex-ai Thanks for the RTX 5070 Ti data and the concurrency note, which changed the design. dlssg_sleep now counts itself in under the state lock, and shutdown waits for that count to drop to zero (2 s at most) before it touches the present lock. It uses device_wait_idle_locked(). A test does what your fixture did: the SDK sleep blocks, the test checks that idle and slShutdown wait for it with the present lock free, that no marker gets in after shutdown begins, and that a second close is a no-op. It fails against mutants that skip the drain or hold the present lock. Restart goes through the same mkwvk_close_asked path. Like you, I haven't tried a stuck device or Restart in the game yet.

@Arkex-ai Thanks for the RTX 5070 Ti data and the concurrency note, which changed the design. `dlssg_sleep` now counts itself in under the state lock, and shutdown waits for that count to drop to zero (2 s at most) *before* it touches the present lock. It uses `device_wait_idle_locked()`. A test does what your fixture did: the SDK sleep blocks, the test checks that idle and `slShutdown` wait for it with the present lock free, that no marker gets in after shutdown begins, and that a second close is a no-op. It fails against mutants that skip the drain or hold the present lock. Restart goes through the same `mkwvk_close_asked` path. Like you, I haven't tried a stuck device or Restart in the game yet.
j64esatxzdrhfg commented 2026-11-11 15:07:29 +00:00 (Migrated from github.com)

Rebased onto current main. One conflict: main now colours the FPS counter by the game's own rate against its cap when frame generation is on; that rule now covers DLSS Frame Generation too (made = opts.framegen >= 1 || (opts.dlssg >= 1 && dlssg_active())).

Rebased onto current main. One conflict: main now colours the FPS counter by the game's own rate against its cap when frame generation is on; that rule now covers DLSS Frame Generation too (`made = opts.framegen >= 1 || (opts.dlssg >= 1 && dlssg_active())`).
hckrman101 commented 2026-11-11 16:22:15 +00:00 (Migrated from github.com)

Thank you for your contribution, I will close this for now as we are going with NGX for framegen at the moment, since we already have NGX and it supports Linux. Please test those PRs instead!

Thank you for your contribution, I will close this for now as we are going with NGX for framegen at the moment, since we already have NGX and it supports Linux. Please test those PRs instead!
Commenting is not possible because the repository is archived.
No description provided.