fix(vk): FSR 3.1 draws the fixed Render resolution steps where the game's networks are left out #2166
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!2166
Loading…
Reference in a new issue
No description provided.
Delete branch "fix/fsr3-fixed-steps"
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?
Problem
On a GPU that leaves the game's cooperative-matrix networks out, FSR 3.1's own Render resolution steps never reach FSR 3.1. That covers any GPU that isn't NVIDIA's, and NVIDIA's that multiply in software. With FSR 3.1 chosen, Quality, Balanced, Performance and Ultra Performance draw the 3D as a plain stretch, with no FSR 3.1 and no anti-aliasing, while the menu still says FSR 3.1. #1829 reports it on an RX 9060 XT ("FSR 3.1 looks noticeably worse. I don't think the upscaler is working"), and I see the same on an RX 5500M.
OoT draws its 3D at a share of the picture and scales it up with a network of its own for each share. In the logs, with the picture at 1536x864:
9dc98871eb7fc101fd029a317a9a4b946676619641320ba7f10782f9e58b6a7c72ba090c70fe26140775215f8da720e6On these GPUs only the first two are kept by default (
vk_shader.cpp,RX_VK_NETWORK_KEEP). The others don't translate ("a network left out on this card"), and their dispatch goes toupscale_stub. The stub already offers such a network to the temporal upscaler (DLSS, FSR 4) throughtemporal_rejected_network, but FSR 3.1 has a path of its own (vk_fsr3.cpp). With FSR 3.1 chosen,temporal_wanted()is false, so the network was never offered to it and the stub stretched the scene. The only way FSR 3.1 got used on these cards was Render resolution Auto, while the game's dynamic resolution sat at 3/4 or full.Change
temporal_rejected_networkoffers the network to FSR 3.1 first (fsr3_upscale, as a network it doesn't know), then to the temporal upscaler. That's the same order asfsr3_stand_inbeforetemporal_stand_infor the networks that do translate.upscale_stubcalls it when FSR 3.1 is asked for too (fsr3_inputs::wanted()).fsr3_upscalealready takes a network it doesn't know only when the input is this frame's scene and the output is the picture (B10G11R11, larger than the input), so anything else, the movies for example, is stretched as before. With FSR 3.1's frame interpolation on and another upscaler drawing, the same call now also makes the interpolation's inputs from these frames, asfsr3_stand_indoes for the networks that translate.Before and after
RX 5500M (RDNA1, 4 GB), Windows 11, AMD driver, Upscaler FSR 3.1. Counts come from the log:
rejected compute <network>when a share's network is stretched, andfsr3: N frames drawnevery 600 frames FSR 3.1 draws.rejected compute 6676619641320ba7in a session at Auto)network 6676619641320ba7 does not translate here: FSR 3.1 draws its upscale (1024x576 -> 1536x864), then 16,200 frames over about ten minutes of play, jitter over 18 phasesrejected compute f10782f9e58b6a7cin a session at Auto)network f10782f9e58b6a7c does not translate here: FSR 3.1 draws its upscale (896x504 -> 1536x864)(a short check)rejected compute 72ba090c70fe2614 ... 768x432 ... 1536x864)fsr3: 16800 frames drawn, 768x432 -> 1536x864, this one's jitter +0.125 -0.278 px of 32 phasesThe #1829 reporter's log (RX 9060 XT, v2.0.14) has the same
72ba090c70fe2614 not translated: a network left out on this cardandrejected compute 72ba090c....A note on jitter, since the logs above show
jitter -0.000 +0.000 pxon FSR 3.1's first frame: FSR 3.1 publishes its jitter once it has drawn (fsr3_inputs::upscaled), and the game's projection is jittered from the next frame on. Zero on the first frame is expected. Theframes drawnlines show it moving afterwards.Not fixed here: Ultra Performance
At 1/3 the game uses
0775215f8da720e6(512x288 into 1536x864). Its bindings hold a second 1536x864 image besides the picture, which the other shares' networks don't have. FSR 3.1 doesn't take it in this test: nofsr3line at 512x288 and noFSR 3.1 draws its upscaleline for that network.fsr3_upscaledeclines a network it doesn't know without logging why (the input not this frame's scene, formats, sizes), so I'll find out which check that is and send it separately. The other three steps don't depend on it.Tests
tests/test_upscaler_rows.py:upscale_stuboffers a network that didn't translate when FSR 3.1 is asked for, andtemporal_rejected_networkoffers it to FSR 3.1 before the temporal upscaler. This fails on current main.tests/test_upscale_fallback.py: a stub forfsr3_inputs::wantedin its harness.tests/run_all.pyon main0a28ff29with this change merged: 310 passed. The 6 that fail are the same 6 that fail on plain main on this PC.Seen while testing, not from this PR
On this card, a bright dotted line along distant mountain silhouettes gets stronger toward sunset. It shows the same with Game's own at 100% (no upscaling at all) as with FSR 3.1 at Quality, and Sharpness and the Window scaling filter don't change it, so it isn't from FSR 3.1 or this change. The build tested here is main
92907f7, from before #1657, which changes how quad operations in fragment shaders are handled on RDNA1. That may be it, but I haven't checked yet.Related: #1829, #1006 (AMD: no anti-aliasing, networks in software).
Thanks, this is a clean fix and the write-up makes it easy to follow.
I went through the path at
5bbfed6:upscale_stubnow also callstemporal_rejected_networkwhenfsr3_inputs::wanted(), and that offers the network tofsr3_upscale(..., known=false, ...)beforetemporal_upscale. That's the same order asfsr3_stand_inbeforetemporal_stand_in, and the unknown-network guards infsr3_upscale(B10G11R11 in and out, output larger than input,is_scene(in)) keep the movies and anything else on the stretch. With frame interpolation on and DLSS/FSR 4 drawing,fsr3_upscalemakes the FI inputs and returns false, sotemporal_upscalestill draws, as on the translated path. With only FI wanted and no upscaler, it falls through to the stretch like before. The new(temporal_wanted() || temporal_off())guard insidetemporal_rejected_networkkeepstemporal_upscalefrom running when only FSR 3.1 brought us in. The new check intest_upscaler_rows.py(FSR 3.1 offered before the temporal upscaler, on the definition viarindex) is a good one. Leaving Ultra Performance for a follow-up is fine.CI: the only red job is Windows Python part 1/2, and that's
tests/test_recorder_sync.py(93 s), which this PR doesn't touch. Everything else passes, including the llvm-mingw build.Overlap: #2305 renames
fsr3_wantedtofsr3_chosenand, by default, sends FSR 3.1 through the temporal chain. After both land, the left-out networks reach FSR 3.1 throughtemporal_upscaleanyway, and this path matters forRX_VK_FSR3_CHAIN=0and the FI inputs. Both PRs edittest_upscaler_rows.py, in different hunks, so whichever lands second may need a quick rebase. #2193 (merged) and #1943 don't touch these lines.Two comment nits inline. Not blocking.
@ -6919,1 +6918,3 @@if ((temporal_wanted() || temporal_off()) && temporal_rejected_network(cmd, key, src, &in, dst, &out)) return true;// offers the networks that did translate, and so is FSR 3.1, as fsr3_stand_in. It takes only the scene into the picture;// anything else goes on belowif ((temporal_wanted() || temporal_off() || fsr3_inputs::wanted()) && temporal_rejected_network(cmd, key, src, &in, dst, &out))Nit: this still reads as if the temporal upscaler goes first and FSR 3.1 after it, but
temporal_rejected_networknow tries FSR 3.1 first. Maybe "FSR 3.1 is offered it first, as fsr3_stand_in, then the temporal upscaler, as temporal_stand_in".@ -7213,7 +7216,11 @@ static bool temporal_rejected_network(VkCommandBuffer cmd, uint64_t key, Textureif (!in_view || !out_view || out_format != VK_FORMAT_B10G11R11_UFLOAT_PACK32 ||(in_format != VK_FORMAT_B10G11R11_UFLOAT_PACK32 && in_format != VK_FORMAT_E5B9G9R9_UFLOAT_PACK32))return false;Nit: the wrap went odd here: this line is ~140 columns, and the next is a short
software (vk_shader.cpp, RX_VK_NETWORK_KEEP), so. Worth rewrapping the three lines.github-sync referenced this pull request2026-11-12 06:22:54 +00:00