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
pull from: feat/dlss-frame-generation
merge into: MagicBeanDev:main
MagicBeanDev:main
MagicBeanDev:github/pr/2893
MagicBeanDev:github/pr/2890
MagicBeanDev:github/pr/2887
MagicBeanDev:github/pr/2878
MagicBeanDev:github/pr/2875
MagicBeanDev:github/pr/2874
MagicBeanDev:github/pr/2873
MagicBeanDev:github/pr/2871
MagicBeanDev:github/pr/2756
MagicBeanDev:github/pr/2897
MagicBeanDev:github/pr/2896
MagicBeanDev:github/pr/2718
MagicBeanDev:github/pr/2842
MagicBeanDev:github/pr/2691
MagicBeanDev:github/pr/2895
MagicBeanDev:github/pr/2743
MagicBeanDev:github/pr/2765
MagicBeanDev:github/pr/2708
MagicBeanDev:github/pr/2732
MagicBeanDev:github/pr/2755
MagicBeanDev:github/pr/2817
MagicBeanDev:github/pr/2876
MagicBeanDev:release-please--branches--main
MagicBeanDev:release-please--branches--main--release-notes
MagicBeanDev:github/pr/2778
MagicBeanDev:github/pr/2888
MagicBeanDev:github/pr/2866
MagicBeanDev:github/pr/2877
MagicBeanDev:github/pr/2800
MagicBeanDev:github/pr/2829
MagicBeanDev:github/pr/2850
MagicBeanDev:github/pr/2889
MagicBeanDev:github/pr/2882
MagicBeanDev:github/pr/2860
MagicBeanDev:github/pr/2849
MagicBeanDev:github/pr/2886
MagicBeanDev:github/pr/2794
MagicBeanDev:github/pr/2840
MagicBeanDev:github/pr/2830
MagicBeanDev:github/pr/2834
MagicBeanDev:github/pr/2719
MagicBeanDev:github/pr/2742
MagicBeanDev:github/pr/2741
MagicBeanDev:github/pr/2820
MagicBeanDev:github/pr/2771
MagicBeanDev:github/pr/2859
MagicBeanDev:github/pr/2858
MagicBeanDev:github/pr/2823
MagicBeanDev:github/pr/2757
MagicBeanDev:github/pr/2769
MagicBeanDev:github/pr/2844
MagicBeanDev:github/pr/2843
MagicBeanDev:github/pr/2673
MagicBeanDev:github/pr/2855
MagicBeanDev:github/pr/2739
MagicBeanDev:github/pr/2807
MagicBeanDev:github/pr/2851
MagicBeanDev:github/pr/2789
MagicBeanDev:github/pr/2814
MagicBeanDev:github/pr/2818
MagicBeanDev:github/pr/2774
MagicBeanDev:github/pr/2772
MagicBeanDev:github/pr/2750
MagicBeanDev:github/pr/2786
MagicBeanDev:github/pr/2785
MagicBeanDev:github/pr/2782
MagicBeanDev:github/pr/2779
MagicBeanDev:github/pr/2775
MagicBeanDev:github/pr/2706
MagicBeanDev:github/pr/2766
MagicBeanDev:github/pr/2747
MagicBeanDev:github/pr/2759
MagicBeanDev:github/pr/2745
MagicBeanDev:github/pr/2740
MagicBeanDev:github/pr/2733
MagicBeanDev:github/pr/2725
MagicBeanDev:github/pr/2727
MagicBeanDev:github/pr/2728
MagicBeanDev:github/pr/2729
MagicBeanDev:github/pr/2721
MagicBeanDev:github/pr/2722
MagicBeanDev:github/pr/2715
MagicBeanDev:github/pr/2714
MagicBeanDev:github/pr/2707
MagicBeanDev:github/pr/2703
MagicBeanDev:github/pr/2705
MagicBeanDev:github/pr/2697
MagicBeanDev:github/pr/2679
MagicBeanDev:github/pr/2676
MagicBeanDev:github/pr/2669
MagicBeanDev:docs/what-hookshot-connects-to
MagicBeanDev:fix/hdr-encode-test
MagicBeanDev:fix/main-tests
MagicBeanDev:fix/play-cache-files
MagicBeanDev:perf/game-barrier-order
MagicBeanDev:feat/heap-lazy
MagicBeanDev:feat/texture-detail
MagicBeanDev:perf/astc-opaque-bc1
MagicBeanDev:fix/fs-dir-lock
MagicBeanDev:fix/framegen-native-kept
MagicBeanDev:fix/update-no-downgrade
MagicBeanDev:port/k10-gcn-f16-mirrors
MagicBeanDev:fix/framegen-mult-cap
MagicBeanDev:chore/release-as-2.1.1
MagicBeanDev:perf/pcache-load-switch
MagicBeanDev:fix/prepare-progress-log
MagicBeanDev:fix/update-closes-stale-game
MagicBeanDev:fix/hdr-scaled-sampled
MagicBeanDev:perf/first-start-preset
MagicBeanDev:perf/zero-upload
MagicBeanDev:fix/vcore-escape-2s
MagicBeanDev:test/fake-gh-atomic
MagicBeanDev:perf/batch-idle-flush
MagicBeanDev:test/finish-stub-a
MagicBeanDev:chore/record-2.1.0
MagicBeanDev:fix/main-red-tests
MagicBeanDev:fix/test-display-startup-driver-cache
MagicBeanDev:feat/mesh-shader-mode
MagicBeanDev:fix/menu-probes-at-startup
MagicBeanDev:fix/fps-counter-classic
MagicBeanDev:fix/test-log-batch-flake
MagicBeanDev:fix/narrow-screen-hud
MagicBeanDev:ci/prereleases
MagicBeanDev:release/v2.0.15
MagicBeanDev:hotfix/v2.0.15
MagicBeanDev:fix/present-ahead-replay
MagicBeanDev:release/v2.0.13
MagicBeanDev:fix/bracket-paths-2013
MagicBeanDev:fix/hdr-overlays-2013
MagicBeanDev:fix/fps-counter-scale-2013
MagicBeanDev:fix/stick-seen-at-rest
MagicBeanDev:hotfix/menu-fits-screen
MagicBeanDev:fix/f1-always-opens-menu
MagicBeanDev:fix/ini-keep-lines
MagicBeanDev:claude/cranky-pike-7cf247
MagicBeanDev:perf/texture-answer-flat
MagicBeanDev:fix/stamp-test-modules
MagicBeanDev:fix/appimage-fonts
MagicBeanDev:fix/display-awake
MagicBeanDev:release/v2.0.12
MagicBeanDev:fix/discord-windows
MagicBeanDev:rc/v2.0.12-tests
MagicBeanDev:fix/dlss-keep-feature
MagicBeanDev:rc/v2.0.12
MagicBeanDev:button-icons
No reviewers
Labels
Clear labels
accessibility
Barrier affecting people with disabilities
autorelease: pending
autorelease: tagged
bug
Something isn't working
dependencies
Pull requests that update a dependency file
documentation
Improvements or additions to documentation
duplicate
This issue or pull request already exists
enhancement
New feature or request
github_actions
Pull requests that update GitHub Actions code
good first issue
Good for newcomers
help wanted
Extra attention is needed
invalid
This doesn't seem right
question
Further information is requested
testing wanted
wontfix
This will not be worked on
No labels
accessibility
autorelease: pending
autorelease: tagged
bug
dependencies
documentation
duplicate
enhancement
github_actions
good first issue
help wanted
invalid
question
testing wanted
wontfix
Milestone
Clear milestone
No items
No milestone
Assignees
Clear assignees
No assignees
1 participant
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".
No due date set.
Dependencies
No dependencies set
Reference
MagicBeanDev/Hookshot!1944
Loading…
Reference in a new issue
No description provided.
Delete branch "feat/dlss-frame-generation"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
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)mkwvk_close_asked, which the window's close, Quit and Restart all go through before the host'sExitProcess.dlssg_shutdown:dlssg_*calls in;dlssg_sleepdrops the state lock during it) without holding the present lock;device_wait_idle_locked), turns DLSS-G off, then callsslShutdown.dlssg_*call does nothing afterwards, and both locks stay held until the process ends, so nothing reaches Streamline's hooks after it shut down.--prepare-shadersrun (no window, so no close) no longer starts Streamline.0xbad00004withnvngx_dlss.dllonly inngx\: NGX starts once per process and keeps the folder list from that first start. With Streamline, the first start issl.common's when the device is made.sl.commonpasses NGX the plugins' folder plus every entry ofPreferences::pathsToPlugins(commonEntry.cppin the 2.14.16 source). SopathsToPluginsnow lists, afterbin\(the first folder with plugins still wins for the plugins), the same foldersvk_dlss.cppgives NGX:RX_VK_DLSS_DIR, the game folder'sngx,%LOCALAPPDATA%\Hookshot\ngx. Nothing is copied beside the exe. With Streamline running,vk_dlss.cppnow 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_DLSSGis applied overopts.dlssginmenu_init, next toRX_VK_FRAMEGEN_MULT, so it also turns frame generation on for that run.motionVectorsInvalidValueis left at Streamline's default (INVALID_FLOAT).tests/upscale_independent_test.cppgets thedlssg_active()stub.ini_keyhas an explicitdlss_frame_genentry, whichsaved_dlssgreads.--accept-nvidia-license's help names the licenses for each fetch.What changed
gpu/vk/vk_dlssg.cpp/.h: Streamline 2.14 with manual hooking. With the DLSS Frame Gen row set,sl.interposer.dll'svkGetInstanceProcAddrreplacesvulkan-1.dllinload_global, after NVIDIA's signature is checked. The row is read fromhookshot_options.inibefore Vulkan starts;RX_VK_DLSSGoverrides it.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 beforepresent_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_askedends withdlssg_shutdown().vk_dlss.cpp: the0xbad00004log line with Streamline running.nx2/build.py:HOOKSHOT_SL_SDK(Windows) buildsvk_dlssg.cppwith Streamline's headers and links wintrust/crypt32.nx2/make_game.py: copies the seven runtime DLLs besidehookshot.exe;--fetch-sl-sdkfetches the SDK first. The DLSS runtime stays inngx\.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.test_dlssg.py,test_sl_sdk.py,test_make_game_sl.py.test_dlssg.pynow also covers the exit. It cutsState,dlssg_marker,dlssg_sleepanddlssg_shutdownout ofvk_dlssg.cppand runs them against a stand-in SDK whose Reflex sleep blocks:slShutdown;Each of these fails against a mutant that drops the matching step. It also checks
RX_VK_DLSSGinmenu_init, the NGX folders, and the close and Restart path inhost/display.c.test_reflex_swapchain_lifetime,test_shader_stamp_cache, the framegen and upscale_independent fixtures,test_audio_output_menuandtest_stick_dead_zoneknowvk_dlssg.cppand the row.Testing
8c92fe3) with this series.d7a7306:vk_dlssg.cppcompiles with and without Streamline,vk_dlss.cppwith 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_zoneon Windows: 32 passed, 3 failed.test_present_aspect_vulkanfails the same way on untouched main.test_reflex_swapchain_lifetimeandtest_present_semaphoresdefault to clang++, which this PC lacks. Both pass with--cxx g++.d7a7306with this PR): six sessions with DLSS Frame Gen on (2x and 3x), each closed through the window. Every one loggedStreamline 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 withnvngx_dlss.dllonly inngx\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
vkDeviceWaitIdlehas no timeout.sl_sdk.pywas checked against a local copy of the zip, not fetched from CI.Screenshots
HDR10 captures, tone-mapped to SDR for this page (paper white to white, highlights rolled off up to 800 nits).
🤖 Generated with Claude Code
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:
nvngx_dlss.dllis also besidehookshot.exe. With it only inngx\(wheremake_game.pyputs it), DLSS/DLAA fail with0xbad00004, so DLSS-G never gets inputs.RX_VK_DLSSG=1alone starts Streamline but never turns DLSS-G on. It doesn't setopts.dlssg, whichdlssg_inputsanddlssg_before_presentcheck, 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:The log agrees with PresentMon:
DLSS Frame Gen: 104.4 fps shown, 52.2 of the game's.Bug 1: DLSS needs the runtime beside hookshot.exe while Streamline is loaded
Without Streamline, NGX loads
nvngx_dlss.dllfrom the game'sngx\folder as before. With Streamline loaded (the DLSS Frame Gen row on), the same build and folder give:NVSDK_NGX_VULKAN_Initsucceeds, butSuperSampling_FeatureInitResultis0xbad00004. With a copy ofnvngx_dlss.dllnext tohookshot.exe, DLSS starts, and NGX's own log (RX_VK_DLSS_LOG=1) shows where it took the runtime from:I haven't confirmed why. My guess is that Streamline's own NGX start comes first, so the path list
vk_dlss.cpppasses later isn't used. A simple fix is formake_game.py(andsl_sdk.py --game) to also putnvngx_dlss.dllbesidehookshot.exewhenHOOKSHOT_SL_SDKis set. The log message is misleading too: it blames the GPU.Bug 2: RX_VK_DLSSG doesn't turn DLSS-G on
asked()invk_dlssg.cppreadsRX_VK_DLSSGto decide whether to start Streamline, butopts.dlssgkeeps the options file's value.dlssg_inputsreturns early onopts.dlssg <= 0, anddlssg_before_presentdecides "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=1inhookshot_options.iniworks. ApplyingRX_VK_DLSSGoveropts.dlssgwhere the options are read (asRX_VK_FRAMEGEN_MULTdoes invk_menu.cpp) would match the readme's "RX_VK_DLSSG=0..3for one run".Also worth knowing when testing
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.sl.logalso logsFailed to find global tag 'kBufferTypeDepth'/'kBufferTypeMotionVectors'once, around an area load. Harmless as far as I can see.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.
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:slShutdown, so DLSS-G tears down in its DLL detach afterExitProcess. The fix they describe (frommkwvk_close_asked: take the present lock with a timeout, wait idle, DLSS-G mode off,slShutdown, then make everydlssg_*do nothing) looks right to me. A crash on every quit is the blocker here.RX_VK_DLSSGstarts Streamline but never turns FG on (Zarigis' bug 2). Confirmed:saved_dlssg()reads it, butopts.dlssgkeeps the ini's value, anddlssg_inputs/dlssg_before_presentboth checkopts.dlssg. Apply it inmenu_initnext toRX_VK_FRAMEGEN_MULT(inline).0xbad00004) once Streamline is loaded unlessnvngx_dlss.dllsits besidehookshot.exe(Zarigis' bug 1), so DLSS-G gets no inputs on a stockmake_game.pyfolder. I'd rather find out why NGX ignoresvk_dlss.cpp's path list (probably because Streamline'ssl.commonstarts NGX first) than copy the DLSS runtime beside the exe, because main keeps it inngx\on purpose (place_ngx_runtime: "nothing beside the exe"). Either way the log line shouldn't blame the GPU.tests/test_upscale_independent.pybreaks. It cutsframegen_onout ofvk_framegen.cppand compiles it againsttests/upscale_independent_test.cpp, which has nodlssg_active(). That file needs a stub, the same asframegen_resources_test.cppgot. CI didn't run on this PR ("no checks reported"), so please runpython -I -B tests/run_all.pylocally.motionVectorsInvalidValue = 0.0fmarks 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:
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.vk_temporal.cpp,vk_replay.cpp,vk_framegen.cppandvk_menu.cppnear the same spots, and #1945/#1946 touchvk_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.pymirrorsngx_sdk.py: a pinned release, SHA-256 on the zip and on every file taken out, NVIDIA's notice printed,--accept-nvidia-licenseor 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 (onlywintrust/crypt32) or shipped; the release workflows don't setHOOKSHOT_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:MainWindow.xaml.cs(and the CONTRIBUTING note should say so, as the NGX section does).--accept-nvidia-licenseinmake_game.pynow 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-codesdlss_frame_gen=, which only matches becauseini_keyderives it from the row's name. Add an explicitini_keyentry foropts.dlssgso renaming the row can't silently break the pre-Vulkan read.pathsToPluginssays 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.@ -0,0 +1,608 @@// (hookshot) NVIDIA DLSS Frame Generation through NVIDIA Streamline 2.14 (vk_dlssg.h), on Vulkan with manual hooking (theWith 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 itint saved_dlssg() {if (const char* e = getenv("RX_VK_DLSSG")) return std::clamp(atoi(e), 0, 3);This makes Streamline start, but
opts.dlssgstill comes from the ini, so withdlss_frame_gen=0(or no ini) andRX_VK_DLSSG=1,dlssg_inputsreturns early anddlssg_before_presentsees "the row is off" every frame, and nothing is logged (Zarigis' bug 2). ApplyingRX_VK_DLSSGoveropts.dlssginmenu_init, whereRX_VK_FRAMEGEN_MULTis 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;Nothing ever undoes this: there is no
slShutdownanywhere, so the DLSS-G plugin tears down in DLL detach afterExitProcesshas ended its threads. That's the access violation on every exit/Restart that pmwork-personal reported. You need a shutdown frommkwvk_close_asked(present lock with a timeout, idle, DLSS-G off,slShutdown, then everydlssg_*is a no-op after it).tests/test_upscale_independent.pycuts this function intotests/upscale_independent_test.cpp, which has nodlssg_active(), so that test stops compiling. It needs abool dlssg_active() { return false; }stub, like the oneframegen_resources_test.cppgot.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.
@ -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 itint saved_dlssg() {if (const char* e = getenv("RX_VK_DLSSG")) return std::clamp(atoi(e), 0, 3);Fixed:
menu_initnow appliesRX_VK_DLSSGoveropts.dlssgnext toRX_VK_FRAMEGEN_MULT, beforerestart_init, so the env var both starts Streamline and turns frame generation on.test_dlssg.pychecks both places.Thanks for the careful review. All five are addressed, rebased on main
d7a7306, still one commit:Exit/Restart crash:
dlssg_shutdown()now runs at the end ofmkwvk_close_asked. Close, Quit and Restart all reach it throughrx_close_askedbeforehos_window_closed_exit. The order is the one you and the testers described:dlssg_*calls in;dlssg_sleepwith the present lock not held (Arkex-ai's point);device_wait_idle_locked(), DLSS-G off,slShutdown.It runs once, and both locks stay held until the process ends.
test_dlssg.pyruns the real functions against a stand-in SDK whose sleep blocks. A--prepare-shadersrun 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 loggedStreamline shut down for the exit (result 0)and exited with no crash report. A Restart with Streamline running is still to be tried.RX_VK_DLSSG: applied overopts.dlssginmenu_init, next toRX_VK_FRAMEGEN_MULT.0xbad00004: the root cause is that NGX keeps the folder list of its first start, and with Streamline that start issl.common's.sl.commonpasses NGX the plugins' folder plus everyPreferences::pathsToPluginsentry (commonEntry.cpp). The plugin manager takes plugins only from the first of those folders that has any. SopathsToPluginsnow listsbin\first, thenvk_dlss.cpp's folders:RX_VK_DLSS_DIR, the game folder'sngx,%LOCALAPPDATA%\Hookshot\ngx. Nothing is copied beside the exe. With Streamline running,vk_dlss.cppnow logs0xbad00004as 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.upscale_independent_test.cpp: has thedlssg_active()stub. I rantests/run_all.pyfiltered to the affected checks, not the whole suite. Of the 3 failures,test_present_aspect_vulkanfails the same way on untouched main. The other two default to clang++, which this PC lacks; both pass with g++.motionVectorsInvalidValue: removed; Streamline'sINVALID_FLOATapplies.Nits:
ini_keyhas an explicitdlss_frame_genentry, and the test checks thatsaved_dlssgreads that key.--accept-nvidia-license's help names the licenses for each fetch.On #1831: agreed, that's NotRexed's call.
@ -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;Added
dlssg_shutdown(), called at the end ofmkwvk_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 callsslShutdown, once. Everydlssg_*is a no-op afterwards.slShutdownis now one of the required exports. In the game (RTX 5080, 2x and 3x): six window closes, eachresult 0, no crash; a Restart with Streamline running is still to try.@ -0,0 +1,608 @@// (hookshot) NVIDIA DLSS Frame Generation through NVIDIA Streamline 2.14 (vk_dlssg.h), on Vulkan with manual hooking (theNot on purpose. Removed, so Streamline's default (
INVALID_FLOAT, no invalid value) applies, and the test keeps it unset.Added
bool dlssg_active() { return false; }totests/upscale_independent_test.cpp;test_upscale_independentpasses again.@Zarigis Thanks for the RTX 4080 SUPER numbers and the detailed write-ups. Both bugs are fixed in the update:
sl.common's, andsl.commonpasses NGX itspathsToPlugins. Those now include the game folder'sngx(plusRX_VK_DLSS_DIRand%LOCALAPPDATA%\Hookshot\ngx), so nothing needs to sit besidehookshot.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, withnvngx_dlss.dllonly inngx\?RX_VK_DLSSGnow setsopts.dlssgtoo.The DLAA-at-4K hint is in the row's help now.
@pmwork-personal Thanks for tracking the crash down and for the clear recipe; the update follows it:
mkwvk_close_asked: present lock with a 2 s limit, idle, DLSS-G off,slShutdown, then no-ops.motionVectorsInvalidValueis back to Streamline's default.upscale_independentstub is added.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.@Arkex-ai Thanks for the RTX 5070 Ti data and the concurrency note, which changed the design.
dlssg_sleepnow 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 usesdevice_wait_idle_locked(). A test does what your fixture did: the SDK sleep blocks, the test checks that idle andslShutdownwait 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 samemkwvk_close_askedpath. Like you, I haven't tried a stuck device or Restart in the game yet.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())).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!