Repository navigation
AMD FSR 3.1 upscaling and frame generation (split from #1335) - #1386
Open
notquiteog wants to merge 4 commits into
Open
notquiteog wants to merge 4 commits into
notquiteog wants to merge 4 commits into
Conversation
The first part of NotRexed#822, split out as asked: AMD's FidelityFX SDK GPU sources for the FSR 3.1 upscaler, frame interpolation and optical flow, and scripts/fsr3_shaders.py, which compiles them into gpu/vk/shaders like every other shader there. Nothing in the renderer uses them yet; the backend that records these passes is a later, separate PR. - third_party/fidelityfx-sdk: 76 files from FidelityFX SDK v1.1.4 (commit c6efa6bf), unmodified, MIT, with PROVENANCE.md saying what each folder is. - gpu/vk/shaders/fsr3_*_spv.h: the 28 compiled headers, checked in so a clone builds without glslang. - tests/test_fsr3_shaders.py (in run_all, Linux only): recompiles every pass and fails if a checked-in header no longer matches its source; source-regressions installs glslang-tools for it. Tested: every vendored file compared byte for byte with AMD's repository at c6efa6bf (76 of 76 identical); tests/test_fsr3_shaders.py with glslang 16 and spirv-val: 28 match. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
FSR 3.1's upscaler (FidelityFX SDK 1.1.4's passes, vendored and compiled in the commit before) takes the gameplay upscale network's dispatch, the same dispatch NVIDIA DLSS takes (NotRexed#841), found the same way: AMD's seven compute passes, one pass of ours before them that prepares their inputs and one after that writes the picture into the network's output. Nothing of AMD's is linked or loaded, so it is in every build. Reached with RX_VK_FSR=1 here; the menu row follows. Its inputs are NotRexed#841's where they exist on current main, in one file (vk_fsr3_inputs.cpp) so they can move onto the shared temporal inputs of NotRexed#1008 by changing that file alone: - jitter: NotRexed#841's camera jitter (nx2/oot_jitter.py), now lifted into every build, not only builds with NVIDIA's SDK. It stays zero unless DLSS or FSR 3.1 draws: rx_option HOS_OPT_DLSS_JITTER_* gives the host DLSS's size, or FSR's. FSR reads the offset back from the camera's uniform buffer as vk_dlss.cpp does, with dlss_math.h's functions. - camera: the same uniform buffer and checks as dlss_note_scene_draw. The near plane, far plane and field of view come from its projection (fsr3_math.h): no camera hook in the game. The scene's projection is near 0.1, far 1000, 45 degrees, depth 0 at the near plane; FSR's passes are compiled for reversed depth, so FSR's depth copy is 1 - d. - motion: the camera's, from the depth, with dlss_prep.comp's math (shaders/fsr3_prep.comp). NotRexed#841's actor motion is inside vk_dlss.cpp behind HOOKSHOT_NGX; FSR gets it when NotRexed#1008 moves it out. - colour: the network's input (its sqrt encoding), and the picture is squared back into the network's output by dlss_out.comp, as for DLSS. dlss_mode() no longer clamps a value past DLAA onto DLAA: 3 is FSR 3.1. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
FSR 3.1's frame interpolation and the optical flow it runs on (seventeen of AMD's compute passes, vendored and compiled two commits before) make the frame between two of the game's when Frame generation is 2x, from the presented colour and the scene's depth and motion (vk_fsr3_inputs.h, the same inputs as the upscaler, prepared at the gameplay upscale's dispatch whichever upscaler runs). The chain in vk_framegen.cpp is FSR 3.1, then the renderer's own blend for whatever it does not take: no inputs this present, a reset, a device that cannot run the passes. FSR 3.1 makes the midpoint only (no phase input), so 3x and more stay with the renderer's own. RX_VK_FSR_FG=0 never, =1 over the row. A present it did not see (a hitch, a present without inputs) breaks the sequence it keeps its previous frame by, and the next one is a reset: the interpolation never pairs this frame with a stale one. Its inputs are taken once, so a present never uses another frame's depth and motion. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The Upscaler row's fourth value (dlss=3 in hookshot_options.ini, saved values keep their meaning): offered where vk_fsr3.cpp says FSR 3.1 runs, and the row is shown on such a GPU even in a build without NVIDIA's SDK. The row steps over what does not run here (upscaler_rows.h: a mask of the upscalers that run, step_upscaler and upscaler_fixed over it). FSR 3.1's Render resolution steps are DLSS's shares, AMD's ratios too (Quality 1.5x, Balanced 1.7x, Performance 2x, Ultra Performance 3x). The DLSS model row waits while FSR 3.1 draws; the Sharpness hint for DLSS is DLSS's only. Frame generation's 2x hint says when FSR 3.1 makes the frame. readme.md: the rows, and what FSR 3.1 needs. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
notquiteog
added a commit
to notquiteog/Hookshot
that referenced
this pull request
Nov 10, 2026
…lour encoding (NotRexed#1386's inputs) Ports the fork onto the inputs of upstream PR NotRexed#1386, which found these bugs in the fork's FSR path: - The camera hook (nx2/oot_camera.py, host/hos_oot_camera.c) published sead's projection object (near 1, far 20000), not the projection the scene is drawn with (near 0.1, far 1000.5, fov 45), and the fork treated the scene's depth as reversed-infinite: it is 0 at the near plane. FSR's passes want reversed depth, so they are given 1 - d with factors from the real projection (gpu/vk/fsr3_math.h, checked by tests/test_fsr3_math). - FSR's output went into the network's output without undoing the network's square-root encoding; it is squared back now, as DLSS's picture is. - vk_work.h was missing from the FSR files (a barrier could be skipped), the motion image never got its first layout transition, and a resize freed images frames in flight still used. All FSR inputs are made in gpu/vk/vk_fsr3_inputs.cpp (one jitter, NotRexed#841's oot_jitter in every lift; camera from the projection; camera-only motion). FSR 4 (vk_ffx.cpp) and the frame generation chain take the same Frame. The camera hook, the fork's viewport jitter and the seam/contract headers and their tests are gone. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
github-actions Bot
pushed a commit
to notquiteog/Hookshot
that referenced
this pull request
Nov 10, 2026
## [2.0.17](v2.0.16...v2.0.17) (2026-11-10) ### Bug Fixes * **vk:** FSR 3.1 and FSR 4 take the scene's real camera, depth and colour encoding ([NotRexed#1386](https://github.com/notquiteog/Hookshot/issues/1386)'s inputs) ([366c268](366c268)) * **vk:** FSR 3.1 frame interpolation makes 2x sets only ([c719d8c](c719d8c))
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
AMD FSR 3.1 upscaling and FSR 3.1 frame generation, split out of #1335 as asked. Nothing else from #1335 is in it.
dlss=3inhookshot_options.ini; 1 and 2 keep meaning DLSS and DLAA). It takes the gameplay upscale network's dispatch: the same dispatch DLSS takes, found the same way.third_party/fidelityfx-sdkand compiled intogpu/vk/shaders/fsr3_*_spv.h. Every pass is a dispatch of ours. FSR 3.1 needs no AMD runtime and no DLL: nothing of AMD's is linked or loaded at run time, so it is in every build on every platform. AMD's runtime DLLs are only for FSR 4. FSR 4 is not in this PR (FSR 4.1 is AMD FSR 4.1 as a second upscaler engine, on #841's frame chain #1008's).vk_dlss.cppis indlss_mode(). It used to clamp the row's value into 0..2, so 3 would have turned into DLAA. Now any value other than 1 or 2 means "not DLSS".glslang-toolstosource-regressions.yml's apt line, becausetests/test_fsr3_shaders.pycompiles every vendored pass again and checks the checked-in headers.Supersedes #1313: its commit is this PR's first commit, unchanged apart from one context line in
tests/run_all.pyafter the rebase.Commits:
build(vk): the vendored passes,scripts/fsr3_shaders.py, the 28 headers and their test (build(vk): vendor AMD's FSR 3.1 passes and the script that compiles them #1313)feat(vk): FSR 3.1 upscaling (vk_fsr3.*,vk_fsr3_inputs.*,fsr3_math.h,shaders/fsr3_prep.comp), the jitter in every liftfeat(vk): FSR 3.1 frame interpolation (vk_fsr3fi.*, the chain invk_framegen.cpp)feat(menu): the Upscaler row's FSR 3.1 value, the Frame generation hint, andreadme.mdWhy
Players have asked for FSR 3.1 frame generation, and #1335 was too big to review and test in one piece.
Inputs: what comes from #841 and what does not yet
All of FSR's inputs are made in one file,
gpu/vk/vk_fsr3_inputs.cpp, behind one function,prepare(), which returns oneFramestruct. Moving FSR 3.1 onto #1008's shared temporal inputs means changing that file alone.nx2/oot_jitter.pywithhos_dlss_jitter_clip. The fork's own jitter (a viewport offset invk_draw.cpp) is not here.nx2/lift.pynow installs the jitter in every standalone lift, not only in builds with NVIDIA's SDK.make_game.pyno longer setsOOT_DLSS_JITTER, andoot_jitter.wanted()is gone. The lift key coversnx2/, so the first build after this PR lifts the game again.tests/test_make_game_ngx.pyno longer checks the old switch.rx_option(HOS_OPT_DLSS_JITTER_W/_H/_PHASES)returnsdlss_jitter_size(), or FSR'sfsr3_inputs::jitter_size()(the scene's size while FSR has drawn in the last 30 frames). The phase count isdlss_jitter_phases(), the host's own sequence length, and FSR is given the same count.RX_VK_FSR_JITTER=0turns it off for a run.vk_dlss.cppdoes. It usesdlss_math.h'sjitter_to_pixelsandunjitterto convert the offset and take it off the motion.HOS_OPT_DLSS_JITTER_*,hos_dlss_jitter_clip) are kept, so its interfaces do not churn under AMD FSR 4.1 as a second upscaler engine, on #841's frame chain #1008.nx2/oot_camera.pyandhost/hos_oot_camera.care not here.dlss_note_scene_draw.fsr3_math.hderives FSR'sdevice_to_view_depthfactors andtan(fov/2)from the projection at +0x70. This was checked in a run:p22 -1.0002, p23 -0.20002gives near 0.1, far 1000.5 and a 45.00 degree field of view.tests/test_fsr3_math.cppchecks the factors against FSR's own view-distance formula, for both depth directions and for finite and infinite far planes.shaders/fsr3_prep.compis the fork's camera-motion pass, reworked todlss_prep.comp's math: reproject the depth from this frame's jittered clip space to the previous frame's unjittered one, and take this frame's jitter off. The same pass also writes FSR's colour and depth copies.vk_dlss.cppbehindHOOKSHOT_NGX, so a build without NVIDIA's SDK has none of it. Moving it out from behind the gate is AMD FSR 4.1 as a second upscaler engine, on #841's frame chain #1008's refactor (vk_temporal.cpp), and this PR does not duplicate it.vk_fsr3_inputs.cpp'sprepare()takes AMD FSR 4.1 as a second upscaler engine, on #841's frame chain #1008's inputs and FSR gets the actor motion.dlss_out.comp, the same pass DLSS's picture goes through.Left out of the fork's version:
upscale_seam.h,upscale_contract.h,upscale_format.hand their tests (the stand-in takes the dispatch the waydlss_stand_indoes)RX_VK_FSR_STAGE, the VP hunt, the MRT census)Fixed in the port:
vk_work.h, sofull_barriercould leave out a barrier after their work.Frame generation
The chain in
vk_framegen.cppis: FSR 3.1, then the renderer's own blend for any present FSR does not take (no inputs for that present, a reset, a GPU that cannot run the passes).RX_VK_FSR_FG=0turns FSR's interpolation off for a run (=1forces it on over the row).GPUs and drivers
FSR 3.1 is offered where the device has what the passes need. The Upscaler row is shown on such a GPU even in a build without NVIDIA's SDK. These are on top of the renderer's own floor (Vulkan 1.3: dynamic rendering, synchronization2, timeline semaphores).
fsr3::supported()):VK_KHR_push_descriptorwithmaxPushDescriptorsof at least 15shaderStorageImageExtendedFormatsandshaderStorageImageWriteWithoutFormatfsr3fi::capable()):shaderStorageImageReadWithoutFormat,shaderStorageImageWriteWithoutFormatandshaderStorageImageExtendedFormatsThe log says which way it went:
vk: fsr3: AMD FSR 3.1 can run on this GPU, orvk: fsr3: not on this GPU: <why>, and the same forfsr3fi. Tested only on an NVIDIA RTX 5060 Ti with driver 615.71.09 on Linux.Testing
Linux, RTX 5060 Ti, driver 615.71.09.
D=<deps>isbuild-linux/deps.Builds (both link):
EMSDK=$D/emsdk RX_CPM=$D/cpm OOT_WORK=... RX_MARCH=x86-64-v3 RX_VK_ONLY=1 python -I nx2/build.py --standalone --jobs 8: compiled, 0 failed, linkedhookshot_vk.so(no NVIDIA SDK)HOOKSHOT_NGX_SDK=<ngx-sdk>: compiled, 0 failed, linked ("with NVIDIA DLSS")Tests:
tests/run_all.py(with the CPM headers found, so the SPIR-V and Vulkan header tests run too) gives 202 passed, 0 failed, 5 skipped.
tests/run_all.pyon main (a116390): 180 passed, 0 failed, 19 skippedtests/run_all.pyon this branch: 182 passed, 0 failed, the same 19 skipped. The two new ones aretest_fsr3_shaders.pyandtest_fsr3_math.py.Whole game:
make_game.py --update <a copy of the game folder> --jobs 10built the whole game from this branch in 10 minutes. The lift loggeddlss jitter: 3 of 3 sitesin a build without NVIDIA's SDK. A run of that build (FSR 3.1 + 2x, pacing on) gave a game fps of 58.4 and 117.2 frames shown a second, with FSR 3.1 making 600 of 600 frames and no refusal lines.In game:
HOS_INPUT_FRAME_CLOCK=1 HOS_REALTIME_STEP=0 RX_VK_PRECOMPILE=0, the sameHOS_INPUT_SCRIPTin every arm, and a stop at 6000 presents. The game's fps is the mean of thevk: N presents, X fpslines between presents 4800 and 6000.HOS_HEADLESS: frame generation is off without a window (framegen_on()checks!dev.offscreen).run_all.py. The whole-game run above is on the rebased branch.RX_VK_PASS_TIMING=1: FSR 3.1's seven passes take 0.60 ms a frame, against the game's network's 0.078 ms. Frame interpolation's 17 passes take 1.17 ms a frame (0.61 ms of that is the optical-flow search).vk: fsr3 1536x864 -> 2048x1152 with assembled depth and camera motion (network fd029a317a9a4b94 ...)vk: fsr3 inputs: ... near 0.1000 far 1000.53 fov 45.00 deg, depth 0 at the near plane: reversed for FSRvk: fsr3: N frames drawn ... this one's jitter +0.312 -0.352 px of 14 phases(the jitter changes every frame)vk: fsr3fi: 600 of the last 600 made frames by FSR 3.1Visual check:
RX_VK_SHOTS+RX_VK_SHOT_MADE=1(a shot every 150 presents, 6000 presents): FSR's frames and FSR's made frames look right, with correct colour and no garbage, and the renderer's own flash check wrote nowhite_*orflash_*file.RX_VK_DUMP=5400: the HUD pass after the upscale (step 266) draws over FSR's picture at 2048x1152 and looks right.Not covered
🤖 Generated with Claude Code