fix(vk): DLSS comes back after a feature that was not made, and frame generation takes the multiplier its sets were made for #2658
Closed
notquiteog wants to merge 1 commit from
notquiteog/fix/dlss-retry-and-framegen-multiplier into main
pull from: notquiteog/fix/dlss-retry-and-framegen-multiplier
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
2 participants
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!2658
Loading…
Reference in a new issue
No description provided.
Delete branch "notquiteog/fix/dlss-retry-and-framegen-multiplier"
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?
After one failed DLSS feature creation, DLSS stays off until the player changes the DLSS model or the output size; and stepping the Frame generation multiplier up in the menu can make a frame be copied into an image that does not exist. Both are in code that #2550 and #2574 touched, found by reading the merged result.
What changed
Frame generation: the multiplier is read once (
vk_framegen.cpp).framegen_recordcalledmultiplier()again afterensure_sizehad made the sets forfg.mult. The menu runs on the display thread (menu_tickfrompresent_image), the record on the replay thread, with a wait for a free set in between. A step from 2x to 3x in that window givesm = fg.mult + 1, and the lastvkCmdCopyImagetargetsset->frame[fg.mult], a null image, which the display thread then presents. It takesfg.multnow, and reads the Auto test once for the same reason.DLSS:
refusedis cleared where a retry should happen (vk_dlss.cpp). Since #2550 a creation that fails returns 0 withs.refused = key, and the key is the output size, preset and HDR. The old feature is already let go, so from then on:release()leftrefusedset).#2574's
temporal_reset_failuredoes not help there: the chain'sfailedflag is not what holds DLSS off. Now the quality mode is part of the key,release()clearsrefused, andtemporal_reset_failureclears it too (dlss_retry, called with the chain's lock held, aspreparehas it).DLSS: a failing evaluation no longer logs on every frame (
vk_dlss.cpp). Withretry = true(#2574) an evaluation that fails every frame wroteits evaluation failed60 times a second for the rest of the run. The line is spaced like the chain's own reason (when the result changes, and every 600th time), and after 30 failures in a row the feature is let go once sopreparemakes a new one.How it was tested
tests/run_all.py: with #2656 (which makes main's checks pass) and this together, 468 passed, 0 failed, 22 skipped. On this branch alone the 15 checks that are red on main stay red and nothing else fails.test_present_source_crop's fixture setsfg.multin itsensure_sizestand-in, as the real one does.feature 4 made (again after it was let go ...).Not tested
Review notes that are not fixed here, for whoever owns them: FSR 4's engine still has the permanent latch #2574 removed for DLSS (
vk_fsr4.cpppreparereturns -1, noretry); with Native HDR, frame generation's images are RGBA8 (vk_framegen.cppensure_size), so the game's 10-bit PQ picture passes through 8 bits; DLSS-G's feature keeps the render size it was created with when the Render resolution changes.🤖 Generated with Claude Code
A Windows test of the frame-generation race in the first part of this PR. Current
maincrashed twice here, and this PR did not crash under the same multiplier steps.main:hookshot v2.1.0-rc.3-256-g51da0464(source 18ed87b5);main+ this PR (343bee04cherry-picked):hookshot v2.1.0-rc.3-257-g204757a2(source 39f17cc9).main, two crashes, both the same:The three
hookshot_vk.dllframes, symbolized against that build's DLL withllvm-symbolizer, aremkwvk::framegen_record, called fromdo_present, called frompresent. The driver reads at 0x590 from a null base while it recordsframegen_record's commands.With this PR, no crash: three rounds of 2x → 3x → 4x and back in the menu, in a window: 11 swapchain remakes (4, 5 and 6 images), the game ran on and exited normally.
This PR also held up for the second case. At 4x before play, the game went from fullscreen to a window and back 25 times each (
display: fullscreen/display: windowed), with 62 swapchain remakes between 5120x1440 and 1280x720. There was no crash, and the game exited normally. Onmain, one such switch had been enough.These are small counts: one crash per case on
main, and a timing window. Still, both cases end in the same driver read insideframegen_record, and neither came back with this PR.Not tested: Linux, AMD and Intel; the DLSS retry part of this PR (no failed feature creation here).
🤖 Generated with Claude Code
The race in the first part happens in the game: a crash on Windows when stepping the multiplier up, at the copy this PR fixes.
51da046(v2.1.0-rc.3-260-g62c03b1), without this PR; Windows 11 Pro 25H2, RTX 5060 Ti 16 GB, driver 617.42; 3440x1440, DLAA, DLSS Frame Generation (nvngx_dlssg.dll310.9.16 inngx\), V-Sync Auto (FIFO).swapchain ... 7 imagesline for 6x.With the build's
hookshot_vk.map:hookshot_vk.dll+0x271ef1ismkwvk::framegen_record+0x1db1, the return from the lastvkCmdCopyImage(set->frame[set->made].image, loaded from the set's frame array); +0x346225 isdo_present, +0x32f32epresent. So the destination was the null image of a set made for 4x (frame[0..3]) whilemwas already 6: the driver reads 0x590 past a null handle.So it isn't only a window seen in reading: one step up was enough here.
🤖 Generated with Claude Code
I merged this into current main and ran the DLSS/framegen/present/temporal/HDR checks (
run_all.py dlss framegen present temporal hdr upscale: 26 passed, 4 skipped for Vulkan headers). The test_hdr_encode failure on this head was main's own (kEngine'sretry, fixed by #2648), so a re-run should clear it.The framegen fix is right and matters.
ensure_sizebuilds the sets forfg.mult, andframegen_recordre-readmultiplier()afterwards, so a menu step on the display thread in between made the lastvkCmdCopyImagetarget a frame that was never created. Takingfg.multat gpu/vk/vk_framegen.cpp:791 closes that. Jiro131 and polackangry both reproduced thenvoglv64.dllread at 0x590 inframegen_recordon main, and neither saw it with this change.On DLSS: adding the quality mode to the refused key, clearing
refusedinrelease()(vk_dlss.cpp:481) and callingdlss_retryfromtemporal_reset_failurelook correct. The key's mode bits (8-10) don't overlap the preset bits. Note that the release after 30 failed evaluations (:469) fires only once per run of the same error code, which I think is what you want. I found no open PR touching these same lines (#2788 and #2665 change other parts of these files).github-sync referenced this pull request2026-11-12 16:54:48 +00:00
Nit, nothing to change unless you see it in practice: with the mode in the key, a refused feature is also retried whenever the game's dynamic resolution moves the render size into another mode's range.
refusedholds one key, so a size that goes back and forth across a range boundary tries again on each crossing. That's the same cost a working feature already pays (a new feature on each crossing), so I think it's fine. I'd keep the mode here, because it covers the case where the menu'sdlss_retryis used up on a frame that still has the old render size.Nit: if two error codes alternate (A, B, A, B...),
r != s.eval_resultis true every frame and this logs every frame again, and the 30-in-a-row release never fires. That seems unlikely from NGX, so it's fine as it is. Keeping a separateeval_failscount that only a success resets would cover it, if you want to.Thanks, this is a good one. The frame generation half fixes a real crash, and the DLSS half is small and easy to follow.
Frame generation. Taking
fg.multinframegen_recordis the right fix.ensure_sizemade the sets for that count, and nothing on the display thread readsmultiplier()to size anything. Readingopts.framegen == 1once also holds up: both branches are now bounded byfg.mult, so a switch between Auto and a fixed count betweenensure_sizeand the record stays inside the sets that exist. Jiro131 (RTX 4090, Windows 11) and polackangry (RTX 5060 Ti, Windows 11) both crashed main with the same driver read insideframegen_recordwhen stepping the multiplier, and neither crashed with this change. Together with your own Linux runs, that's the in-game evidence this needs.DLSS retry. I checked that it can't turn into a retry every frame:
refusedis cleared in three places: when the key changes, inrelease(), and indlss_retry().release()only runs when the Upscaler switches engines, when the chain goes idle (and only once the row has left DLSS), or once after 30 failed evaluations.dlss_retry()only runs from the menu's DLSS, Render resolution and DLSS model rows. So a creation that keeps failing is tried again once per player change, not once per frame. While DLSS is off,preparereturns 0 with the same reason pointer, so the chain'sfail()doesn't log each frame either.refusedholds it off again. Therelease()insideevaluatedefers the handle throughtemporal_later, so the evaluation already recorded incmdkeeps a live feature.s.featureis null, the stalefeature_*sizes are only read behinds.feature,hdr()reports false, and the chain falls back to the game's upscale.dlss_retryruns under the chain's lock, asprepareandreleasedo, sorefusedhas a single owner. The non-DLSS stub is there too.Two small notes, inline. Neither blocks.
You couldn't make a creation or an evaluation fail on a 16 GB card, so the DLSS retry is checked by reading only. That's fine here: the normal DLSS path ran in the game with no regression, and if the new branches misbehaved, the worst they could do is what main already does. If someone with an 8 GB card hits a
its feature was not madeline, a log showingfeature N made (again after it was let go)after a row change would close it.CI is green and the branch merges cleanly. I found no open PR that touches these lines. #2788 and #2665 edit other parts of
vk_dlss.cpp/vk_framegen.cpp, so the merge order doesn't matter.Approving.