AMD FSR 4.1 as a second upscaler engine, on #841's frame chain #1008
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!1008
Loading…
Reference in a new issue
No description provided.
Delete branch "amd/fsr4"
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?
AMD FSR 4.1 as a second upscaler engine, on the frame chain #841 merged for DLSS, next to the FSR 3.1 that #1386 merged. FSR 4 is AMD's machine-learned upscaler and runs AMD's own DLLs; FSR 3.1 is built from AMD's shaders. They are separate options on the same row.
What changed
vk_dlss.cppintovk_temporal.cppbehind a small engine interface.vk_dlss.cppkeeps NGX, the feature and its evaluation, and does the same thing as before #1520's keeping DLSS loaded through loading screens moved with it, so FSR 4 gets it too.vk_fsr4.cppruns FSR 4.x through AMD's FSR API on those same inputs, taking the newest FSR 4 AMD's library offers (never an older FSR in its place). Only AMD's MIT headers are vendored (third_party/fidelityfx); AMD's signed DLLs are the player's.dlss.RX_VK_FSRstays FSR 3.1's switch; FSR 4's isRX_VK_FSR4. Only one upscaler draws at a time: the shared chain stands aside while FSR 3.1 upscales. FSR 3.1 keeps its own inputs (vk_fsr3_inputs.cpp) and its frame generation works with any upscaler, as merged.make_game.py --fsr/HOOKSHOT_FSR=1/ a Builder tick. Default builds are unchanged.Tested
mainatcaaee0b): the renderer builds with and without FSR 4, no warnings;tests/run_all.py: 230 pass, and the same 19 fail on plainmainhere (local tools: Windows symlinks, clang, spirv-val). Not played again since: the play test above was before #1386.Related
🤖 Generated with Claude Code
Read the rebuilt version (050fc55) against current main. Pulling the frame chain out into
vk_temporal.cppbehind a small engine interface is a good shape, and the move looks faithful: I diffedtemporal_upscaleagainst main'sdlss_upscaleandprepareagainstmake_feature. The only changes are the engine indirection,s.drawing's handover and the near/far/fov capture, so the DLSS half should behave as it did. It merges cleanly with main, CI is green (including the DLSS+FSR link check), there are no newHOS_OPTids (the new values 3/4 go under the existingdlsskey), and nothing new needs adding totests/run_all.py. Provenance is documented: the headers carry AMD's MIT notice, the README pins SDK v2.4.0, and the signed DLLs aren't vendored.What I'd still look at, inline:
~ContextcallsffxDestroyContextfrom a function-static at process exit (likely a hang or crash on close).On the other FSR PRs: #1008 isn't superseded. #1095 and #1313 only vendor FSR 3.1 shader sources, with no file overlap. #1096/#1097 were stacked on the old #1008 (
dlss_dispatch, the #850 base) and touch the same files (vk_fsr4., vk_dlss., vk_menu.cpp, vk_draw.cpp). They'redirtynow and would need rebasing onto theTemporalEngineinterface here, where FSR 3.1 would fit as a third engine. #1335 is a separate FSR 3.1 + FSR 4 + DLSS NR stack (its own vk_ffx.cpp/vk_fsr3.cpp/vk_ngx.cpp) that edits upscaler_rows.h, vk_dlss.cpp, vk_menu.cpp, vk_draw.cpp, vk_main.cpp, build.py and make_game.py too. The two will conflict whichever lands first, so it's worth deciding which FSR 4 path main takes before both go further.This only compiles the Windows side. All the
#ifndef _WIN32code in vk_fsr4.cpp (dlopen, the_DEPRECATEDbackend chain,unit_exposure) and the descriptor-buffer feature in vk_main.cpp never gets compiled in CI, and the PR says Linux wasn't tested. Because it's header-only, a cheap step in an existing Linux job (for exampleHOOKSHOT_FSR=1on the AppImage/source-checks vk build, or a single-fsyntax-onlyof vk_fsr4.cpp with-DHOOKSHOT_FSR -Ihost/posix) would at least keep it building.@ -0,0 +1,471 @@// (hookshot) AMD FSR 4, one of the temporal upscaler's engines (vk_fsr4.h). The frame chain it draws with isNit: this makes the chain's colour/depth/motion images output-sized even at 33%. At 4K that's about 130 MB (RGBA16F + R32F + RG16F) instead of about 15 MB. It's fine with
maxRenderSize = outputfor Auto, but the fixed modes could ask for the mode's size and only grow when the render size does. Not blocking.@ -0,0 +103,4 @@uint32_t width = 0, height = 0; // (its output, which is also the most the render size can be)uint64_t memory = 0; // (what AMD's library says it holds)~Context() {if (context) lib().destroy(&context, nullptr);This destructor also runs at process exit:
st()'s function-staticState(and anytemporal_laterlambdas still holding ashared_ptr<Context>in vk_temporal'sdead) get destroyed then. Hookshot leaves throughExitProcess(hos_main.c, hos_relaunch.c) and never destroys the device. So on Windows this calls into AMD's DLL after it has already had its DLL_PROCESS_DETACH (it was loaded after hookshot_vk.dll, so it detaches first), while other threads have been killed, possibly inside the driver holding its locks. On Linux,exit()runs it while the render thread may still be recording with the same context. Either way it's a likely crash or hang on close. DLSS on main avoids this because its handles are raw pointers with no destructor. The simplest fix is to leak the statics, e.g.static State& s = *new State;here (and the same forLibrary), so nothing runs at exit.Thanks for the careful read, and for checking the move against your originals. All three are addressed in the new commit:
lib()andst()invk_fsr4.cpp, and the chain'sst()invk_temporal.cpp(its deferred frees can hold an FSR context), are now leaked statics, so nothing of FSR's is destroyed at exit. DLSS's state is unchanged.hookshot_vk.soonce more withHOOKSHOT_FSR=1, in a copy of its work folder, so the#ifndef _WIN32parts ofvk_fsr4.cppand the descriptor-buffer feature invk_main.cppare compiled. The AppImage itself is unchanged.A second pass turned up a few small things, fixed in the same commit:
RX_VK_DLSS=3and up mean DLAA again, asmain's clamp had it. The split had made them DLSS.make_game.py --fsrwith the AppImage's prebuilt back end now says that back end has no FSR.Also rebased on current
main, which had started conflicting. That includes your #1364: the scene-size rule (scene_size_fits, the output noted from the upscale) moved intovk_temporal.cppastemporal_scene_size, so FSR gets it too, and the Linux NGX path handling and theDLSS_RUNTIME_NOT_ASCIIreason stay invk_dlss.cppas you wrote them.test_dlss_checks.pynow reads the chain for the size rule. On the other FSR PRs: agreed. FSR 3.1 would fit as a third engine on theTemporalEngineinterface, and which FSR 4 path to take is NotRexed's call.@ -0,0 +103,4 @@uint32_t width = 0, height = 0; // (its output, which is also the most the render size can be)uint64_t memory = 0; // (what AMD's library says it holds)~Context() {if (context) lib().destroy(&context, nullptr);Fixed in c6d8520:
lib()andst()here and the chain'sst()in vk_temporal.cpp are leaked now, so nothing of FSR's runs at exit (anddeadcan't drop a context then either).Fixed: the AppImage job now builds
hookshot_vk.soagain withHOOKSHOT_FSR=1and checks the "with AMD FSR 4" line, and it passes. Small note: the vk_main.cpp descriptor-buffer code isn't behindHOOKSHOT_FSR, so it was already compiled in every build. The new step is what covers the#ifndef _WIN32side of vk_fsr4.cpp.@ -0,0 +1,471 @@// (hookshot) AMD FSR 4, one of the temporal upscaler's engines (vk_fsr4.h). The frame chain it draws with isThanks.
preparenow grows the inputs to the largest render size the context has had, capped at the output, and resets that when the context is remade.make_imageremakes them when the size changes, which is fine since FSR keeps its own history.Re-read at edb090d. The PR's own changes since 050fc55 are c6d8520, edf4055, a3a8c00 and edb090d. The rest of the compare is the rebase onto main.
All three points from last time are fixed (I replied on each thread):
lib()/st()and the chain'sst()are leaked statics now, so nothing calls into AMD's library at process exit.hookshot_vk.soagain withHOOKSHOT_FSR=1, and it's green.The extra fixes look right too.
RX_VK_DLSS>=2maps to DLAA again, matching main's old clamp. A failedLoadLibraryExAlogs the Windows error.--fsrwithHOOKSHOT_VK_PREBUILTnow warns that the prebuilt back end has no FSR. Theopts.dlssrename keeps the saveddlsskey, and there are still no new option ids.The rebase onto #1364 kept what it should.
scene_size_fitsand the output note moved intovk_temporal.cppastemporal_scene_size, and the Linuxwide()path andDLSS_RUNTIME_NOT_ASCIIare still in vk_dlss.cpp unchanged. test_dlss_checks.py reads the new file.It merges cleanly with main now and all CI checks pass, including the DLSS+FSR link check and the AppImage FSR build. Approving. The open question from last time is still for NotRexed: which FSR 4 path main takes (this one or #1335).
I own a 9070 XT and 'd be happy to help testing the FP8/ML FSR 4.1 implementation, just let me know how 🤝
Thanks @FrozenGuy7, a 9070 XT is exactly what this needs: so far it has only run on RDNA 2 (the INT8 model). These steps are for Windows; if you're on Linux, say so and I'll post those.
You need: Hookshot built once with the normal Builder (this reuses its tools, so there's nothing else to install), Git for Windows, your game's
.nup, and about 60 GB free.1. Get AMD's FSR files. From the FidelityFX SDK 2.4 release, take
amd_fidelityfx_loader_vk.dllandamd_fidelityfx_upscaler_vk.dllfromKits\FidelityFX\signedbin. Hookshot doesn't include them.2. Build this PR. Open PowerShell, change the first two lines, then paste the whole block. It builds into a new folder, so your normal install isn't touched, and it takes about as long as a Builder build.
If you have
romfsandexefsfolders instead of a.nup, replace--nup $Nupwith--romfs "C:\path\romfs" --exefs "C:\path\exefs".3. Add AMD's files. Copy the two DLLs from step 1 into the new game folder (
C:\Games\Hookshot-FSR4).4. Start it. Run
hookshot.exein that folder. If it offers an update, skip it: that would replace this test build with the release. The first start prepares the shaders, which takes a while. To keep playing your saves, copy theuserfolder from your normal install into the new folder first.5. Turn FSR 4 on. In game, press F1, then go to Graphics → Upscaler → AMD FSR 4. Under Render resolution, try Quality, Balanced, Performance and Ultra Performance. Then try AMD FSR 4 Native AA. Gameplay → FPS counter shows your FPS so you can compare with Game's own.
What would help most:
vk: AMD FSR 4:lines fromhookshot_log.txtin the game folder. They say which FSR 4 AMD's library chose for your card (the FP8 model on RDNA 4 is what we want to confirm). If FSR 4 isn't offered in the menu, these lines say why.hookshot_log.txt.Thanks again!
Re-read at
2b7ce8c. It's one commit now, on main atcaaee0b, after #1386 merged. I compared its patch with the edb090d one I approved, file by file. Apart from fitting in next to FSR 3.1, it's the same code with renames (FSR→FSR4,RX_VK_FSR→RX_VK_FSR4). The one other change is #1520's keep-through-loading-screens, which moved intotemporal_idle, so FSR 4's context is kept the same way.The fit with FSR 3.1 looks right to me:
dlsskey. There are no new option ids.fsr4_bitsgoes into #1386's mask, so the row steps over what can't run here.temporal_modestands aside whilefsr3_wanted(), so only one upscaler draws and only one jitter goes to the host.jitter_optionreadstemporal_jitter_sizefirst, andfsr3_inputs::jitter_sizeis 0 unless FSR 3.1 is upscaling.fsr3_stand_inruns first,fsr3_upscaleprepares the inputs and returns false, and thentemporal_stand_intakes the dispatch.s.drawing) and starts a new history, and the idle keep doesn't hold on to the old engine.It merges cleanly with current main (
05098f9), so there's no conflict any more. Main's commits sincecaaee0btouchngx_initin vk_dlss.cpp (#1684's game-folder guard), but that function stays in vk_dlss.cpp here. vk_fsr4.cpp takes its search folders from the module paths, so it doesn't have the relative-folder problem #1684 fixed for NGX.One nit inline. All checks pass, including the DLSS+FSR 4 link check and the AppImage. Approving.
@ -0,0 +82,4 @@// (FSR 3.1, the row's 3, is not an engine of this chain: the game's own as far as it is concerned. Nor anything while// FSR 3.1 upscales, RX_VK_FSR=1 over the row too: one upscaler at a time, and one camera jitter)if (!ur::is_dlss(v) && !ur::is_fsr4(v)) return ur::GAME;if (fsr3_wanted()) return ur::GAME;Nit: with the row saved on FSR 3.1 (
dlss=3),RX_VK_FSR4=1(orRX_VK_DLSS=1) is dropped here without a word, becausefsr3_wanted()is true from the row alone. The header and development.md say these switches go "over" the row. It's fine for FSR 3.1 to win, since one upscaler and one jitter is the right rule. Either let a setRX_VK_FSR4/RX_VK_DLSSwin over a saved 3, or say in the docs that a saved FSR 3.1 wins (onlyRX_VK_FSR=0takes it out).@madebyjordan It works perfectly, I played for a few minutes using FSR4 and was able to play at ~120 FPS for a few minutes but I'm not able to share much right now because I'm busy with work now, sorry about that.