fix(dlss): a render size a pixel past a mode's range is that mode's, and a refused size keeps the feature #2193
No reviewers
Labels
No labels
accessibility
autorelease: pending
autorelease: tagged
bug
dependencies
documentation
duplicate
enhancement
github_actions
good first issue
help wanted
invalid
question
testing wanted
wontfix
No milestone
No assignees
1 participant
Due date
No due date set.
Dependencies
No dependencies set
Reference
MagicBeanDev/Hookshot!2193
Loading…
Reference in a new issue
No description provided.
Delete branch "fix/dlss-near-mode"
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?
At output sizes that don't divide evenly by 3, Ultra Performance never runs DLSS. The game's third of the output rounds a pixel past DLSS's Ultra Performance size, no mode takes it, and the game's own upscale network runs in DLSS's place; only a log line shows it. And when a size is refused,
preparehas already let its feature go, so DLSS rebuilds it a frame later.I found these while looking into reports in #dev-work of lower DLSS presets running worse than DLAA. Those turned out to be mostly something else: DLSS model M on an RTX 3060, and VRAM filling up over a session. So this PR doesn't claim to fix them; it fixes the two bugs above.
What happened (RTX 5090, 4416x1864 output: upstream's 3840x2160-pixel widescreen picture on a 21:9 screen)
NVIDIA DLSS: the game's upscale runs (no DLSS mode takes this render size).preparelet the feature go before looking for a mode. So after a refused size the feature was made again on the next frame DLSS could take (feature N made (again after it was let go ...), 10–25 ms of CPU each).What changed
dlss_checks.hmode_forpicks the mode as a plain, testable function:scene_size_fits: the output 1–3× the size). A size between two modes' ranges is still refused, for example 42% at 5120x2160, between Ultra Performance's third and the other modes' half.vk_dlss.cpppreparefinds the mode first and lets the old feature go only once a mode takes the size. A refused size keeps the feature, and the log names the size (no DLSS mode takes the render size WxH for OWxOH).Tested
In game, RTX 5090, driver 621.14, Windows 11, 4416x1864 output, preset M, frame generation off:
a little outside quality mode 3's range (1472x621 to 1472x621): taken by it), ~76–78 fpsOn this GPU the game's network is faster than DLSS at a third, so Ultra Performance goes down here; either way the player now gets DLSS, which is what they picked.
tests/test_dlss_checks.py/.cppcover Performance's half, Ultra Performance's exact and rounded third, a size between the modes, one below a third, a 2560x1440 output's Ultra Performance at the menu's own size, and no widening into a range NGX didn't give. The source check confirmsprepareusesmode_forand lets the old feature go after it. Both fail on main.tests/run_all.py(Windows, llvm-mingw): the DLSS, upscaler and temporal checks pass. The only failure istest_recorder_sync's exit case, which fails on main too.Not tested
Relation to #2145. That PR (Arkex) makes the switch to Native wait for the native scene and touches the same
prepare. This PR leaves the Native transition to it and changes only how the mode is found and when the old feature is let go, so whichever lands second needs a small rebase there.🤖 Generated with Claude Code
Looks good to me. Finding the mode first and only then letting the old feature go is the right order, and pulling the choice out into
mode_formakes the rounded-third case testable. I went through it againstvk_temporal.cpp:preparereturns 1 rather than 2, buttemporal_upscalealready resets when more than 2 frames passed since the last evaluation (now - last > 2), so no stale history gets used.same[]list was never searched, sincefound = aa).min_rw/feature_rw, so the early-out at the top ofpreparekeeps taking 1472x622 on later frames instead of making the feature again each frame. Good.scene_size_fitsaccepts, so it can't push DLSS below a third. The "no range" and "below a third" cases cover that.Nits only, none blocking:
vk_dlss.cpparound line 300: the comment says a size "a pixel or two" outside a range is taken, but the tolerance is 1/64 of the range's largest size. That's 23 px wide for Ultra Performance at 4416, and up to 69 px for the 50-100 % range. The header comment indlss_checks.hgets this right; I'd say the same here or just point to it.fail()'s dedup.CI is all green, including the DLSS link-check build and both Windows/Linux source checks. No other discussion on the PR yet.
Overlaps: #2145 changes
prepare's signature (addsnative_aa) and adds anelse ifto thewhychain plus afeature_aastore after creation. None of that touches the lines this PR rewrites, so I expect at most a trivial rebase. The new source check here keys onint prepare(VkCommandBuffer cmd, which still matches after #2145. #2118 changesvk_dlss.cpponly inngx_initand the frame-generation code belowdlss_probe, notprepare, so there's no conflict.Nit:
fail()invk_temporal.cpponly logs again when thewhypointer changes (or every 600th repeat).refusedis one static buffer, so if a second, different size is refused right after the first, it isn't logged until the 600th repeat, and by then the buffer holds whatever size came last. It's fine for the case this fixes. If you want each refused size logged, you could log it here whenrw/rh/ow/ohchange.Nit: there's a long run of spaces before the message string here, probably left over from joining two lines. Wrapping it onto two lines like the other asserts would read better.
Done in
89b21647, throughfail()itself: the refused message now has two buffers, and a new size goes into the other one, sofail()sees a new pointer and logs each refused size once, in its usual format. (Logging it inprepareas well could print a size twice when another reason came between two refusals, sincefail()'s last reason isn't reset.) The comment near line 300 now says the tolerance asdlss_checks.hdoes (at most 1/64 of the range's largest size on each axis, 2 pixels at the least).Fixed in
89b21647: the message is on its own line, like the other asserts.Thanks for the review. The three nits are in
89b21647(replies in each thread).github-sync referenced this pull request2026-11-12 06:22:54 +00:00