feat(vk): reactive mask for DLSS and FSR 3.1 from the scene's transpa… #1653
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!1653
Loading…
Reference in a new issue
No description provided.
Delete branch "upscale-reactive-mask"
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?
…rent draws (RX_VK_REACTIVE=1)
The temporal upscalers follow a pixel by its depth and motion. What the scene blends over itself without writing depth (water, smoke, fire, glows) has neither, so it is taken for the surface behind and smears or ghosts.
With RX_VK_REACTIVE=1 the scene's colour is copied before its transparent draws (blended, depth tested, depth not written: reactive_policy.h), and at the upscale the scene's target is compared with that copy (shaders/reactive_mask.comp). The difference goes to FSR 3.1's reactivity pass as its reactive mask and to DLSS as its bias-current-colour mask.
Off by default: which draws are transparent is told from their state alone and has not been checked in the game. RX_VK_REACTIVE_DEBUG=1 shows the mask.
Thanks, the idea is sound and the policy/copy/compare split is easy to follow. The reactive_policy.h unit test is a good touch. I read the change through; I haven't run it, and it's off by default so the risk is contained. One real blocker:
This is based on a pre-#1008 main and no longer merges (mergeable_state: dirty). The upscaler code the hooks attach to has moved:
dlss_jitter_size()no longer exists on main (the DLSS side now lives in the TemporalEngine:temporal_jitter_sizein vk_temporal.h, andtemporal_scene_size()/temporal_note_scene_draw()in vk_draw.cpp).reactive_scene_draw()callsdlss_jitter_size(0/1), so it won't compile after a rebase as is. It probably wantstemporal_scene_size(at.width, at.height)(ortemporal_jitter_size) for the DLSS/FSR 4 side, keepingfsr3_inputs::jitter_sizefor FSR 3.1.evaluate(cmd, const TemporalFrame& f)in vk_dlss.cpp (ep.InReset = f.reset...); thedlss_upscale(... s.color.view, s.feature_rw ...)hunk where you setpInBiasCurrentColorMaskhas nothing to apply to. The mask call needs to go into that function using theTemporalFrame's colour view and size.in.reactiveswap into P_PREPARE_REACTIVITY look like they still apply (main still passestex(g.default_reactive)there).Also worth deciding while you rebase: with FSR 4 now going through the same TemporalEngine, does it have a reactive input you'd want to feed too, or should the doc say explicitly DLSS + FSR 3.1 only? (#1654 is open in the same area, so a quick look at how it lands is worthwhile.)
Things I checked that look fine: copy/mask recorded outside a pass (
end_passbefore the copy),full_barrieraround the copy and the dispatch,descriptors_disturbed()after the push-descriptor pass, destroy-generation check before touching the scene texture, deferred freeing of replaced images, constant-initialisedthread_locals (no llvm-mingw TLS-init cost), tests picked up by the run_all glob, R8 storage-image check inwanted().Nit: the mask is the whole difference between the copy and the final scene target, so a blended no-depth-test draw that comes after the last transparent run (a full-screen fade, say) will also light the mask. You say it's unverified in-game, so fine for an opt-in switch, just worth a line in the doc once you've looked at a few scenes with RX_VK_REACTIVE_DEBUG=1.
CI: no checks reported on the branch, so a rebase will also give it a run.
Heads-up: this conflicts with main now (a lot landed today). Could you merge main into the branch (or rebase) and resolve the conflicts? Thanks!
Done: main is merged in again and the conflicts are resolved. Each point from your review is answered below.
Fixed.
reactive_scene_draw()now usestemporal_jitter_sizefor the chain, withfsr3_inputs::jitter_sizekept for FSR 3.1.Done. The mask is made in
evaluatefrom the frame's colour view and sizes, and set aspInBiasCurrentColorMaskas before.They did; those hunks are unchanged.
DLSS + FSR 3.1 only for now, and the doc says so. FSR 4 is given no mask, and no scene copy is made while it draws. I'd add its reactive input as a follow-up once this has been looked at in-game.
Added a line to the doc saying so.
Also
RUN_ALL_ARGS = ["--cxx", "{cxx}"]itself, since the centralARGStable is retired on main.Checked
tests/test_reactive_mask.pypasses, and the changed files pass a g++ syntax pass (vk_dlss.cppagainst NVIDIA's NGX headers too).Thanks for the quick turnaround. All the points from my last review are covered:
dlss_jitter_sizeis gone:reactive_scene_draw()usestemporal_jitter_sizefor the frame chain and keepsfsr3_inputs::jitter_sizefor FSR 3.1. Fixed.evaluate(cmd, const TemporalFrame&)from the frame's colour view and sizes. Fixed.is_fsr4(temporal_mode()), and that enum matchesupscaler_rows). The doc says DLSS + FSR 3.1 only. Answered; a follow-up for FSR 4 sounds right.RUN_ALL_ARGSitself, which matches main since #1822.Still blocking: it conflicts with main again (mergeable_state: dirty). A lot landed after your 18:15 merge, and the overlaps with this PR are
gpu/vk/vk_dlss.cpp,gpu/vk/vk_draw.cpp,gpu/vk/vk_fsr3.cppanddocs/development.md. One of them is more than a textual conflict:RX_VK_RECORDER) put::mkwvk::rec::boundary(::mkwvk::rec::B_DIRECT);inevaluate()right beforeNGX_VULKAN_EVALUATE_DLSS_EXT, at exactly the line where your mask block goes. When you resolve it, thereactive::mask(...)call has to come before that boundary.mask()records its barrier, bind, push and dispatch throughvk_work.h, so withRX_VK_RECORDER=defer/threadthose packets are only flushed intocmdby the boundary. If the mask ends up after it, NGX would read the mask before the dispatch that writes it reaches the command buffer. See the inline comment.vk_reactive.cppalready includesvk_work.hlast, so its own commands go through the recorder correctly. Nothing to change there.docs/development.md: main added theRX_VK_COMPACTrow betweenRX_VK_VRAM_MBandRX_VK_NO_WRITEBACK, right where your row goes. Keep both.vk_draw.cpp/vk_fsr3.cpp: everything your hunks use (barrier_every,clear_after_draws,default_reactivein P_PREPARE_REACTIVITY,ai.reset = f.reset) is still on main, so those should resolve mechanically.CI: still no checks reported on the branch. A clean merge should give it a run.
Once it's merged and the mask sits before the B_DIRECT boundary, I'm happy with this as an opt-in switch.
@ -375,3 +384,4 @@}::mkwvk::rec::boundary(::mkwvk::rec::B_DIRECT); // (NGX records into cmd itself: what this thread recorded through vk_work.h reaches cmd first)const NVSDK_NGX_Result r = NGX_VULKAN_EVALUATE_DLSS_EXT(cmd, s.feature, s.params, &ep);note_work(); // (as at its creation: the barrier after it, and the game's next one, are recorded)Heads-up for the merge: main now has
::mkwvk::rec::boundary(::mkwvk::rec::B_DIRECT);just beforeNGX_VULKAN_EVALUATE_DLSS_EXThere (the recorder, #1473). This block needs to stay above that line.reactive::maskrecords throughvk_work.h, and underRX_VK_RECORDER=defer/threadit only reachescmdat the boundary, so NGX has to come after it.@ -375,3 +384,4 @@}::mkwvk::rec::boundary(::mkwvk::rec::B_DIRECT); // (NGX records into cmd itself: what this thread recorded through vk_work.h reaches cmd first)const NVSDK_NGX_Result r = NGX_VULKAN_EVALUATE_DLSS_EXT(cmd, s.feature, s.params, &ep);note_work(); // (as at its creation: the barrier after it, and the game's next one, are recorded)Thanks. Main is merged in again and the conflicts are resolved.
evaluate()now sits directly aboverec::boundary(B_DIRECT), so its dispatch reachescmdbefore NGX reads the mask.docs/development.mdkeeps both theRX_VK_COMPACTrow and this one.vk_draw.cppandvk_fsr3.cppmerged without a conflict, as you expected.tests/run_all.pyfails only where main does on my machine. I couldn't compilevk_dlss.cppthis time (no NGX headers here); the resolution there only keeps the existing block above main's new line. Still not run in-game.Thanks, the merge looks right. I compared the PR's patches before and after it file by file: apart from the merge context nothing in your hunks changed.
evaluate()now sits directly above::mkwvk::rec::boundary(::mkwvk::rec::B_DIRECT), so underRX_VK_RECORDER=defer/threadthe mask's dispatch reachescmdbefore NGX reads it. Fixed.docs/development.mdkeeps both theRX_VK_COMPACTrow and yours. Fixed.vk_draw.cpp/vk_fsr3.cpp: unchanged hunks, andfsr3_upscaleon main still buildsaifrom the samefsr3_inputs::Frame(f.color,f.width/height) after #2147. With the new Upscaler Off choice (#2097) both jitter sizes are 0, soreactive_scene_draw()copies nothing there. Fine.Heads-up for later, not for this PR: #2305 moves FSR 3.1 onto the temporal chain (
chain_evaluatein vk_fsr3.cpp). If it lands,reactive_scene_draw()will seetemporal_jitter_size != 0with a non-FSR 4 mode and make the copy, but the chain's FSR 3.1 path doesn't callreactive::mask, so the copy goes unused. Whichever of the two merges second should add thereactive::mask(...)→ai.reactiveline tochain_evaluate. #1654 touches the same files but you already checked that the two merge cleanly together.CI: the build and test workflows on this head are waiting for a maintainer to approve them (action_required), so nothing has actually built yet,
vk_dlss.cppincluded. Only the PR title check ran. It's off by default and the change since my last review is a placement-only move, so I'm approving. Someone with access should still approve the runs and look at them before merging.@ -375,3 +384,4 @@}::mkwvk::rec::boundary(::mkwvk::rec::B_DIRECT); // (NGX records into cmd itself: what this thread recorded through vk_work.h reaches cmd first)const NVSDK_NGX_Result r = NGX_VULKAN_EVALUATE_DLSS_EXT(cmd, s.feature, s.params, &ep);note_work(); // (as at its creation: the barrier after it, and the game's next one, are recorded)Confirmed: the mask block is right above the
B_DIRECTboundary on the new head. Thanks!github-sync referenced this pull request2026-11-12 06:22:54 +00:00