AMD FSR 4.1 as a second upscaler engine, on #841's frame chain #1008

Merged
madebyjordan merged 1 commit from amd/fsr4 into main 2026-11-10 16:11:17 +00:00
madebyjordan commented 2026-11-09 05:43:19 +00:00 (Migrated from github.com)

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

  • Shared frame chain. #841's inputs (scene camera and depth, colour, camera + actor motion, in-game camera jitter) aren't DLSS-specific, so they move out of vk_dlss.cpp into vk_temporal.cpp behind a small engine interface. vk_dlss.cpp keeps 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.
  • FSR 4 engine. vk_fsr4.cpp runs 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.
  • Menu. Upscaler row: Game's own, NVIDIA DLSS, NVIDIA DLAA, AMD FSR 3.1 (unchanged, still 3), AMD FSR 4 (4), AMD FSR 4 Native AA (5). It uses #1386's mask of what runs here, so it steps over what can't run. FSR 4 uses the same Render resolution modes. The saved key stays dlss.
  • Next to FSR 3.1. RX_VK_FSR stays FSR 3.1's switch; FSR 4's is RX_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.
  • Opt-in build, like DLSS. make_game.py --fsr / HOOKSHOT_FSR=1 / a Builder tick. Default builds are unchanged.
  • Fix for GPUs without cooperative matrices. The 67/58/50/33% Render resolution networks don't translate there (RDNA 2), so their upscale fell back to a linear stretch that no upscaler ever saw. That fallback now offers the frame to the shared chain first.
  • CI. The DLSS link-check job also builds FSR 4 and runs when the shared chain changes. The Linux AppImage job builds the back end once more with FSR 4, so its Linux side is compiled too.

Tested

  • RX 6700 XT, Windows, FSR 4.1.2 (INT8): it runs at 75%, 67%, 58%, 50% and 33%, and at Native AA; 600 of 600 frames in each status line, the jitter moving, no history resets in play. No blurring or ghosting seen in play.
  • Cost on RDNA 2: about 5–7 ms of GPU time per frame (13.7 → 18–21 ms), the same at every Render resolution, because FSR works at the output size. Native AA (no upscale) costs about the same.
  • After merging with #1386 (rebased on main at caaee0b): the renderer builds with and without FSR 4, no warnings; tests/run_all.py: 230 pass, and the same 19 fail on plain main here (local tools: Windows symlinks, clang, spirv-val). Not played again since: the play test above was before #1386.
  • Not tested here: the DLSS half in play (no RTX card; CI's DLSS job compiles it, and @hckrman101 checked the move against #841), Linux in play (CI compiles it), and FSR 3.1 with this PR in play.
  • Credits. The shared frame chain is @hckrman101's DLSS work from #841, moved, not rewritten (co-author). FSR 4 on Linux is @N3oTraX's (co-author). The readme's credits list both.
  • #1386 (FSR 3.1). Its inputs file was written to move onto this chain; that, and giving FSR 3.1 the fixed-share fallback above, are follow-ups.

🤖 Generated with Claude Code

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 - **Shared frame chain.** #841's inputs (scene camera and depth, colour, camera + actor motion, in-game camera jitter) aren't DLSS-specific, so they move out of `vk_dlss.cpp` into `vk_temporal.cpp` behind a small engine interface. `vk_dlss.cpp` keeps 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. - **FSR 4 engine.** `vk_fsr4.cpp` runs 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. - **Menu.** Upscaler row: Game's own, NVIDIA DLSS, NVIDIA DLAA, AMD FSR 3.1 (unchanged, still 3), **AMD FSR 4** (4), **AMD FSR 4 Native AA** (5). It uses #1386's mask of what runs here, so it steps over what can't run. FSR 4 uses the same Render resolution modes. The saved key stays `dlss`. - **Next to FSR 3.1.** `RX_VK_FSR` stays FSR 3.1's switch; FSR 4's is `RX_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. - **Opt-in build, like DLSS.** `make_game.py --fsr` / `HOOKSHOT_FSR=1` / a Builder tick. Default builds are unchanged. - **Fix for GPUs without cooperative matrices.** The 67/58/50/33% Render resolution networks don't translate there (RDNA 2), so their upscale fell back to a linear stretch that no upscaler ever saw. That fallback now offers the frame to the shared chain first. - **CI.** The DLSS link-check job also builds FSR 4 and runs when the shared chain changes. The Linux AppImage job builds the back end once more with FSR 4, so its Linux side is compiled too. ## Tested - **RX 6700 XT, Windows, FSR 4.1.2 (INT8):** it runs at 75%, 67%, 58%, 50% and 33%, and at Native AA; 600 of 600 frames in each status line, the jitter moving, no history resets in play. No blurring or ghosting seen in play. - **Cost on RDNA 2:** about 5–7 ms of GPU time per frame (13.7 → 18–21 ms), the same at every Render resolution, because FSR works at the output size. Native AA (no upscale) costs about the same. - **After merging with #1386 (rebased on `main` at caaee0b):** the renderer builds with and without FSR 4, no warnings; `tests/run_all.py`: 230 pass, and the same 19 fail on plain `main` here (local tools: Windows symlinks, clang, spirv-val). Not played again since: the play test above was before #1386. - **Not tested here:** the DLSS half in play (no RTX card; CI's DLSS job compiles it, and @hckrman101 checked the move against #841), Linux in play (CI compiles it), and FSR 3.1 with this PR in play. ## Related - **Credits.** The shared frame chain is @hckrman101's DLSS work from #841, moved, not rewritten (co-author). FSR 4 on Linux is @N3oTraX's (co-author). The readme's credits list both. - **#1386 (FSR 3.1).** Its inputs file was written to move onto this chain; that, and giving FSR 3.1 the fixed-share fallback above, are follow-ups. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
hckrman101 (Migrated from github.com) reviewed 2026-11-09 23:10:43 +00:00
hckrman101 (Migrated from github.com) left a comment

Read the rebuilt version (050fc55) against current main. Pulling the frame chain out into vk_temporal.cpp behind a small engine interface is a good shape, and the move looks faithful: I diffed temporal_upscale against main's dlss_upscale and prepare against make_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 new HOS_OPT ids (the new values 3/4 go under the existing dlss key), and nothing new needs adding to tests/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:

  • ~Context calls ffxDestroyContext from a function-static at process exit (likely a hang or crash on close).
  • The Linux-only code isn't compiled anywhere in CI.
  • Nit: FSR's inputs are made at the output size.

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're dirty now and would need rebasing onto the TemporalEngine interface 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.

Read the rebuilt version (050fc55) against current main. Pulling the frame chain out into `vk_temporal.cpp` behind a small engine interface is a good shape, and the move looks faithful: I diffed `temporal_upscale` against main's `dlss_upscale` and `prepare` against `make_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 new `HOS_OPT` ids (the new values 3/4 go under the existing `dlss` key), and nothing new needs adding to `tests/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: - `~Context` calls `ffxDestroyContext` from a function-static at process exit (likely a hang or crash on close). - The Linux-only code isn't compiled anywhere in CI. - Nit: FSR's inputs are made at the output size. 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're `dirty` now and would need rebasing onto the `TemporalEngine` interface 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.
hckrman101 (Migrated from github.com) commented 2026-11-09 23:10:43 +00:00

This only compiles the Windows side. All the #ifndef _WIN32 code in vk_fsr4.cpp (dlopen, the _DEPRECATED backend 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 example HOOKSHOT_FSR=1 on the AppImage/source-checks vk build, or a single -fsyntax-only of vk_fsr4.cpp with -DHOOKSHOT_FSR -Ihost/posix) would at least keep it building.

This only compiles the Windows side. All the `#ifndef _WIN32` code in vk_fsr4.cpp (dlopen, the `_DEPRECATED` backend 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 example `HOOKSHOT_FSR=1` on the AppImage/source-checks vk build, or a single `-fsyntax-only` of 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 is
hckrman101 (Migrated from github.com) commented 2026-11-09 23:10:43 +00:00

Nit: 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 = output for Auto, but the fixed modes could ask for the mode's size and only grow when the render size does. Not blocking.

Nit: 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 = output` for 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);
hckrman101 (Migrated from github.com) commented 2026-11-09 23:10:43 +00:00

This destructor also runs at process exit: st()'s function-static State (and any temporal_later lambdas still holding a shared_ptr<Context> in vk_temporal's dead) get destroyed then. Hookshot leaves through ExitProcess (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 for Library), so nothing runs at exit.

This destructor also runs at process exit: `st()`'s function-static `State` (and any `temporal_later` lambdas still holding a `shared_ptr<Context>` in vk_temporal's `dead`) get destroyed then. Hookshot leaves through `ExitProcess` (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 for `Library`), so nothing runs at exit.
madebyjordan commented 2026-11-09 23:37:52 +00:00 (Migrated from github.com)

Thanks for the careful read, and for checking the move against your originals. All three are addressed in the new commit:

  • Exit: lib() and st() in vk_fsr4.cpp, and the chain's st() in vk_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.
  • Linux in CI: the Linux AppImage job builds hookshot_vk.so once more with HOOKSHOT_FSR=1, in a copy of its work folder, so the #ifndef _WIN32 parts of vk_fsr4.cpp and the descriptor-buffer feature in vk_main.cpp are compiled. The AppImage itself is unchanged.
  • Input size: FSR's inputs now grow to the largest render size its context has had, capped at the output, so a fixed 33% keeps 33%-sized images.

A second pass turned up a few small things, fixed in the same commit:

  • RX_VK_DLSS=3 and up mean DLAA again, as main's clamp had it. The split had made them DLSS.
  • When AMD's loader is present but fails to load, the log now gives Windows' error instead of saying the file is missing.
  • make_game.py --fsr with 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 into vk_temporal.cpp as temporal_scene_size, so FSR gets it too, and the Linux NGX path handling and the DLSS_RUNTIME_NOT_ASCII reason stay in vk_dlss.cpp as you wrote them. test_dlss_checks.py now reads the chain for the size rule. On the other FSR PRs: agreed. FSR 3.1 would fit as a third engine on the TemporalEngine interface, and which FSR 4 path to take is NotRexed's call.

Thanks for the careful read, and for checking the move against your originals. All three are addressed in the new commit: - **Exit:** `lib()` and `st()` in `vk_fsr4.cpp`, and the chain's `st()` in `vk_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. - **Linux in CI:** the Linux AppImage job builds `hookshot_vk.so` once more with `HOOKSHOT_FSR=1`, in a copy of its work folder, so the `#ifndef _WIN32` parts of `vk_fsr4.cpp` and the descriptor-buffer feature in `vk_main.cpp` are compiled. The AppImage itself is unchanged. - **Input size:** FSR's inputs now grow to the largest render size its context has had, capped at the output, so a fixed 33% keeps 33%-sized images. A second pass turned up a few small things, fixed in the same commit: - `RX_VK_DLSS=3` and up mean DLAA again, as `main`'s clamp had it. The split had made them DLSS. - When AMD's loader is present but fails to load, the log now gives Windows' error instead of saying the file is missing. - `make_game.py --fsr` with 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 into `vk_temporal.cpp` as `temporal_scene_size`, so FSR gets it too, and the Linux NGX path handling and the `DLSS_RUNTIME_NOT_ASCII` reason stay in `vk_dlss.cpp` as you wrote them. `test_dlss_checks.py` now reads the chain for the size rule. On the other FSR PRs: agreed. FSR 3.1 would fit as a third engine on the `TemporalEngine` interface, and which FSR 4 path to take is NotRexed's call.
hckrman101 (Migrated from github.com) reviewed 2026-11-10 03:39:04 +00:00
@ -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);
hckrman101 (Migrated from github.com) commented 2026-11-10 03:39:03 +00:00

Fixed in c6d8520: lib() and st() here and the chain's st() in vk_temporal.cpp are leaked now, so nothing of FSR's runs at exit (and dead can't drop a context then either).

Fixed in c6d8520: `lib()` and `st()` here and the chain's `st()` in vk_temporal.cpp are leaked now, so nothing of FSR's runs at exit (and `dead` can't drop a context then either).
hckrman101 (Migrated from github.com) reviewed 2026-11-10 03:39:05 +00:00
hckrman101 (Migrated from github.com) commented 2026-11-10 03:39:05 +00:00

Fixed: the AppImage job now builds hookshot_vk.so again with HOOKSHOT_FSR=1 and checks the "with AMD FSR 4" line, and it passes. Small note: the vk_main.cpp descriptor-buffer code isn't behind HOOKSHOT_FSR, so it was already compiled in every build. The new step is what covers the #ifndef _WIN32 side of vk_fsr4.cpp.

Fixed: the AppImage job now builds `hookshot_vk.so` again with `HOOKSHOT_FSR=1` and checks the "with AMD FSR 4" line, and it passes. Small note: the vk_main.cpp descriptor-buffer code isn't behind `HOOKSHOT_FSR`, so it was already compiled in every build. The new step is what covers the `#ifndef _WIN32` side of vk_fsr4.cpp.
hckrman101 (Migrated from github.com) reviewed 2026-11-10 03:39:07 +00:00
@ -0,0 +1,471 @@
// (hookshot) AMD FSR 4, one of the temporal upscaler's engines (vk_fsr4.h). The frame chain it draws with is
hckrman101 (Migrated from github.com) commented 2026-11-10 03:39:07 +00:00

Thanks. prepare now 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_image remakes them when the size changes, which is fine since FSR keeps its own history.

Thanks. `prepare` now 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_image` remakes them when the size changes, which is fine since FSR keeps its own history.
hckrman101 (Migrated from github.com) approved these changes 2026-11-10 03:39:23 +00:00
hckrman101 (Migrated from github.com) left a comment

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):

  • Exit: FSR's lib()/st() and the chain's st() are leaked statics now, so nothing calls into AMD's library at process exit.
  • Linux CI: the AppImage job builds hookshot_vk.so again with HOOKSHOT_FSR=1, and it's green.
  • Input size: the inputs follow the largest render size the context has had, capped at the output.

The extra fixes look right too. RX_VK_DLSS>=2 maps to DLAA again, matching main's old clamp. A failed LoadLibraryExA logs the Windows error. --fsr with HOOKSHOT_VK_PREBUILT now warns that the prebuilt back end has no FSR. The opts.dlss rename keeps the saved dlss key, and there are still no new option ids.

The rebase onto #1364 kept what it should. scene_size_fits and the output note moved into vk_temporal.cpp as temporal_scene_size, and the Linux wide() path and DLSS_RUNTIME_NOT_ASCII are 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).

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): - Exit: FSR's `lib()`/`st()` and the chain's `st()` are leaked statics now, so nothing calls into AMD's library at process exit. - Linux CI: the AppImage job builds `hookshot_vk.so` again with `HOOKSHOT_FSR=1`, and it's green. - Input size: the inputs follow the largest render size the context has had, capped at the output. The extra fixes look right too. `RX_VK_DLSS>=2` maps to DLAA again, matching main's old clamp. A failed `LoadLibraryExA` logs the Windows error. `--fsr` with `HOOKSHOT_VK_PREBUILT` now warns that the prebuilt back end has no FSR. The `opts.dlss` rename keeps the saved `dlss` key, and there are still no new option ids. The rebase onto #1364 kept what it should. `scene_size_fits` and the output note moved into `vk_temporal.cpp` as `temporal_scene_size`, and the Linux `wide()` path and `DLSS_RUNTIME_NOT_ASCII` are 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).
FrozenGuy7 commented 2026-11-10 13:02:35 +00:00 (Migrated from github.com)

I own a 9070 XT and 'd be happy to help testing the FP8/ML FSR 4.1 implementation, just let me know how 🤝

I own a 9070 XT and 'd be happy to help testing the FP8/ML FSR 4.1 implementation, just let me know how 🤝
madebyjordan commented 2026-11-10 15:59:12 +00:00 (Migrated from github.com)

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.dll and amd_fidelityfx_upscaler_vk.dll from Kits\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.

$Nup = "C:\path\to\your-game.nup"     # your game file
$Out = "C:\Games\Hookshot-FSR4"       # a new folder for this test build

$Tools = "$env:LOCALAPPDATA\HookshotBuilder\versions\$((Get-Content "$env:LOCALAPPDATA\HookshotBuilder\versions\current.txt").Trim())\HookshotBuilder\tools"
$Src = "$env:USERPROFILE\hookshot-pr1008"
git clone -c core.longpaths=true --depth 1 -b amd/fsr4 https://github.com/madebyjordan/hookshot-amd.git $Src
Get-ChildItem "$Tools\third_party" -Recurse -File -Include *.dll,*.exe,*.lib,*.sha1 | ForEach-Object {
  $dest = Join-Path "$Src\third_party" $_.FullName.Substring("$Tools\third_party".Length + 1)
  New-Item -ItemType Directory -Force (Split-Path $dest) | Out-Null
  Copy-Item $_.FullName $dest -Force }
Copy-Item "$Tools\work" "$Src\work" -Recurse -Force
$env:RX_CPM = "$Tools\cpm"; $env:WGPU_NATIVE = "$Tools\wgpu-native"; $env:HOOKSHOT_MINGW = "$Tools\llvm-mingw"; $env:EMSDK = "$Tools\emsdk"
Set-Location $Src
& "$Tools\python\python.exe" -I nx2/make_game.py --nup $Nup --out $Out --language en-US --fsr

If you have romfs and exefs folders instead of a .nup, replace --nup $Nup with --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.exe in 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 the user folder 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:

  • The vk: AMD FSR 4: lines from hookshot_log.txt in 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.
  • FPS with Game's own vs FSR 4 at each Render resolution, standing in the same spot.
  • Anything that looks wrong, such as shimmering, ghosting, flicker, or trails behind moving characters (some trailing is a known limit for now). Screenshots or a short clip help.
  • If it crashes, hookshot_log.txt.

Thanks again!

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](https://git-scm.com/download/win), your game's `.nup`, and about 60 GB free. **1. Get AMD's FSR files.** From the [FidelityFX SDK](https://github.com/GPUOpen-LibrariesAndSDKs/FidelityFX-SDK/releases) 2.4 release, take `amd_fidelityfx_loader_vk.dll` and `amd_fidelityfx_upscaler_vk.dll` from `Kits\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. ```powershell $Nup = "C:\path\to\your-game.nup" # your game file $Out = "C:\Games\Hookshot-FSR4" # a new folder for this test build $Tools = "$env:LOCALAPPDATA\HookshotBuilder\versions\$((Get-Content "$env:LOCALAPPDATA\HookshotBuilder\versions\current.txt").Trim())\HookshotBuilder\tools" $Src = "$env:USERPROFILE\hookshot-pr1008" git clone -c core.longpaths=true --depth 1 -b amd/fsr4 https://github.com/madebyjordan/hookshot-amd.git $Src Get-ChildItem "$Tools\third_party" -Recurse -File -Include *.dll,*.exe,*.lib,*.sha1 | ForEach-Object { $dest = Join-Path "$Src\third_party" $_.FullName.Substring("$Tools\third_party".Length + 1) New-Item -ItemType Directory -Force (Split-Path $dest) | Out-Null Copy-Item $_.FullName $dest -Force } Copy-Item "$Tools\work" "$Src\work" -Recurse -Force $env:RX_CPM = "$Tools\cpm"; $env:WGPU_NATIVE = "$Tools\wgpu-native"; $env:HOOKSHOT_MINGW = "$Tools\llvm-mingw"; $env:EMSDK = "$Tools\emsdk" Set-Location $Src & "$Tools\python\python.exe" -I nx2/make_game.py --nup $Nup --out $Out --language en-US --fsr ``` If you have `romfs` and `exefs` folders instead of a `.nup`, replace `--nup $Nup` with `--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.exe` in 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 the `user` folder 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:** - The `vk: AMD FSR 4:` lines from `hookshot_log.txt` in 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. - FPS with Game's own vs FSR 4 at each Render resolution, standing in the same spot. - Anything that looks wrong, such as shimmering, ghosting, flicker, or trails behind moving characters (some trailing is a known limit for now). Screenshots or a short clip help. - If it crashes, `hookshot_log.txt`. Thanks again!
hckrman101 (Migrated from github.com) approved these changes 2026-11-10 16:09:51 +00:00
hckrman101 (Migrated from github.com) left a comment

Re-read at 2b7ce8c. It's one commit now, on main at caaee0b, 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 into temporal_idle, so FSR 4's context is kept the same way.

The fit with FSR 3.1 looks right to me:

  • Saved values keep their meaning: 3 is still FSR 3.1, FSR 4 is 4 and Native AA is 5, all under the dlss key. There are no new option ids. fsr4_bits goes into #1386's mask, so the row steps over what can't run here.
  • temporal_mode stands aside while fsr3_wanted(), so only one upscaler draws and only one jitter goes to the host. jitter_option reads temporal_jitter_size first, and fsr3_inputs::jitter_size is 0 unless FSR 3.1 is upscaling.
  • With FSR 4 upscaling, FSR 3.1's frame generation still gets its inputs. fsr3_stand_in runs first, fsr3_upscale prepares the inputs and returns false, and then temporal_stand_in takes the dispatch.
  • Switching between DLSS and FSR 4 at runtime still releases the old engine (s.drawing) and starts a new history, and the idle keep doesn't hold on to the old engine.
  • Build gating and CI are unchanged from last time. The changed tests are picked up by run_all.py's glob.

It merges cleanly with current main (05098f9), so there's no conflict any more. Main's commits since caaee0b touch ngx_init in 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.

Re-read at 2b7ce8c. It's one commit now, on main at caaee0b, 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 into `temporal_idle`, so FSR 4's context is kept the same way. The fit with FSR 3.1 looks right to me: - Saved values keep their meaning: 3 is still FSR 3.1, FSR 4 is 4 and Native AA is 5, all under the `dlss` key. There are no new option ids. `fsr4_bits` goes into #1386's mask, so the row steps over what can't run here. - `temporal_mode` stands aside while `fsr3_wanted()`, so only one upscaler draws and only one jitter goes to the host. `jitter_option` reads `temporal_jitter_size` first, and `fsr3_inputs::jitter_size` is 0 unless FSR 3.1 is upscaling. - With FSR 4 upscaling, FSR 3.1's frame generation still gets its inputs. `fsr3_stand_in` runs first, `fsr3_upscale` prepares the inputs and returns false, and then `temporal_stand_in` takes the dispatch. - Switching between DLSS and FSR 4 at runtime still releases the old engine (`s.drawing`) and starts a new history, and the idle keep doesn't hold on to the old engine. - Build gating and CI are unchanged from last time. The changed tests are picked up by run_all.py's glob. It merges cleanly with current main (05098f9), so there's no conflict any more. Main's commits since caaee0b touch `ngx_init` in 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;
hckrman101 (Migrated from github.com) commented 2026-11-10 16:09:51 +00:00

Nit: with the row saved on FSR 3.1 (dlss=3), RX_VK_FSR4=1 (or RX_VK_DLSS=1) is dropped here without a word, because fsr3_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 set RX_VK_FSR4/RX_VK_DLSS win over a saved 3, or say in the docs that a saved FSR 3.1 wins (only RX_VK_FSR=0 takes it out).

Nit: with the row saved on FSR 3.1 (`dlss=3`), `RX_VK_FSR4=1` (or `RX_VK_DLSS=1`) is dropped here without a word, because `fsr3_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 set `RX_VK_FSR4`/`RX_VK_DLSS` win over a saved 3, or say in the docs that a saved FSR 3.1 wins (only `RX_VK_FSR=0` takes it out).
FrozenGuy7 commented 2026-11-10 18:08:15 +00:00 (Migrated from github.com)

@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.

file-6d6a64482a70e68c0b737cc9950e373b file-fb92c30a7dad1635f9a2bb718cbd2186 [hookshot_log.txt](https://github.com/user-attachments/files/34643502/hookshot_log.txt)
@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. <img width="2559" height="1439" alt="file-6d6a64482a70e68c0b737cc9950e373b" src="https://github.com/user-attachments/assets/9233f57b-99fa-4773-99b6-8c63ea6d9045" /> <img width="2552" height="1439" alt="file-fb92c30a7dad1635f9a2bb718cbd2186" src="https://github.com/user-attachments/assets/94c04b9f-2c0b-49b9-beb7-a71f1e5ff33e" /> [hookshot_log.txt](https://github.com/user-attachments/files/34643502/hookshot_log.txt)
Commenting is not possible because the repository is archived.
No description provided.