feat(vk): AMD FSR 3.1 as the default Upscaler without matrix hardware #2110

Closed
addisonbair wants to merge 1 commit from fsr-default into main
addisonbair commented 2026-11-11 03:17:17 +00:00 (Migrated from github.com)

On GPUs without matrix hardware (AMD RDNA2 such as the Steam Deck, Intel), the game's own upscaler runs its network's matrix multiplies in a shader function. On the Steam Deck that single dispatch costs ~12 ms of the frame (#1000). Switching the Upscaler to AMD FSR 3.1 removes it: 11.9 → 13.9 fps docked in the #1000 measurements, and the network dispatch drops out of the GPU profile. Today a Deck user has to find that menu row themselves.

What changes

  • When the matrix multiplies run in software (coopmat_software(), i.e. mma_software) and FSR 3.1 is supported, an Upscaler nobody has chosen defaults to AMD FSR 3.1. A line in the log says so.
  • "Nobody has chosen" means no hookshot_options.ini yet, or a file from before options_version=5. Older files wrote dlss= whether or not it was chosen, which is the same reasoning as the options_version 4 step for Descriptor reuse. Saving now writes options_version=5, so a choice made in the menu after this is kept.
  • Where the cooperative matrices run on hardware (NVIDIA RTX, for one), nothing changes and the game's own upscaler stays.
  • The decision runs at the end of create_device, after the push-descriptor limit is read, because fsr3::supported() needs that limit and caches its answer.

Testing

  • tests/run_all.py: the same results on clean main (1a43a1e) and with the patch. Every failure is the same on both and comes from tools missing in my container (glslang etc.); none are new.
  • Headless start-up on a ThinkPad P16 (Intel Arc ARL iGPU, NVIDIA RTX PRO 2000):
    • Intel, no options file → FSR 3.1 by default
    • NVIDIA, no options file → the game's own upscaler, unchanged
    • Intel, options_version=5, dlss=0 → kept (chosen)
    • Intel, options_version=4, dlss=0 → FSR 3.1
    • Intel, options_version=4, dlss=1 → kept (not the game's own)

🤖 Generated with Claude Code

On GPUs without matrix hardware (AMD RDNA2 such as the Steam Deck, Intel), the game's own upscaler runs its network's matrix multiplies in a shader function. On the Steam Deck that single dispatch costs ~12 ms of the frame (#1000). Switching the Upscaler to AMD FSR 3.1 removes it: 11.9 → 13.9 fps docked in the #1000 measurements, and the network dispatch drops out of the GPU profile. Today a Deck user has to find that menu row themselves. **What changes** - When the matrix multiplies run in software (`coopmat_software()`, i.e. `mma_software`) and FSR 3.1 is supported, an Upscaler nobody has chosen defaults to AMD FSR 3.1. A line in the log says so. - "Nobody has chosen" means no `hookshot_options.ini` yet, or a file from before `options_version=5`. Older files wrote `dlss=` whether or not it was chosen, which is the same reasoning as the `options_version` 4 step for Descriptor reuse. Saving now writes `options_version=5`, so a choice made in the menu after this is kept. - Where the cooperative matrices run on hardware (NVIDIA RTX, for one), nothing changes and the game's own upscaler stays. - The decision runs at the end of `create_device`, after the push-descriptor limit is read, because `fsr3::supported()` needs that limit and caches its answer. **Testing** - `tests/run_all.py`: the same results on clean `main` (1a43a1e) and with the patch. Every failure is the same on both and comes from tools missing in my container (glslang etc.); none are new. - Headless start-up on a ThinkPad P16 (Intel Arc ARL iGPU, NVIDIA RTX PRO 2000): - Intel, no options file → FSR 3.1 by default - NVIDIA, no options file → the game's own upscaler, unchanged - Intel, `options_version=5`, `dlss=0` → kept (chosen) - Intel, `options_version=4`, `dlss=0` → FSR 3.1 - Intel, `options_version=4`, `dlss=1` → kept (not the game's own) 🤖 Generated with [Claude Code](https://claude.com/claude-code)
hckrman101 commented 2026-11-11 15:50:54 +00:00 (Migrated from github.com)

Heads-up: this conflicts with main now. Could you rebase onto main and resolve the conflicts? Thanks!

Heads-up: this conflicts with main now. Could you rebase onto main and resolve the conflicts? Thanks!
Joey-Magnus (Migrated from github.com) reviewed 2026-11-11 20:06:32 +00:00
Joey-Magnus (Migrated from github.com) left a comment

The logic holds up, and the reasoning matches the options_version 4 step. I checked the ordering:

  • menu_init() runs from pick_device() before create_device(), so upscaler_unchosen is already set when menu_upscaler_default() runs at the end of create_device() (gpu/vk/vk_main.cpp:501).
  • By then coopmat_init() has run and dev.max_push_descriptors is read, so fsr3::supported() gives an answer it can cache.
  • A chosen DLSS or FSR 3.1 is left alone. Hardware matrices change nothing.
  • fsr3_wanted() still gives way to an upscaler chosen through the environment (temporal_env_chosen()).

The menu tests that read vk_menu.cpp and vk_main.cpp pass at this head (51 checks).

What it still needs:

  • blocking Merge conflict with main in gpu/vk/vk_menu.cpp and gpu/vk/vk_menu.h, as @hckrman101 noted. Main has moved a lot there: FSR 4 (#2342), DLSS frame generation and others. When you rebase, check that upscaler_rows::GAME/FSR3 and the row indices still mean the same thing. Also check that no other PR has taken options_version=5 in the meantime. CI hasn't run on this PR yet (only the title check), so it will get its first full run after the rebase.
  • should fix It needs an in-game check on a Steam Deck. The Deck is Tier 1 in docs/workflow.md and the main hardware this targets. So far the testing is headless start-up on Intel Arc and NVIDIA, plus fps numbers taken from #1000. This changes what every Deck and Intel player sees on first start, so please add a run on a Deck (or an RDNA2 card) showing the picture with FSR 3.1 by default. CONTRIBUTING asks for screenshots when a change shows on screen. A before/after at the same spot, plus the log line, would cover it.
  • question Players with an older options file who picked "the game's own" on purpose will be moved to FSR 3.1 once. That's the same trade as the Descriptor reuse step, and probably the right call. It's worth a line in the release notes so nobody thinks their setting was lost.
  • minor gpu/vk/vk_menu.cpp:39 adds a second #include "vk_fsr3.h"; it's already included on line 41. The comment above options_version (line 1260) lists what 2, 3 and 4 mean, so add 5 ("the Upscaler: settled once the device is known, see menu_upscaler_default").
The logic holds up, and the reasoning matches the `options_version` 4 step. I checked the ordering: - `menu_init()` runs from `pick_device()` before `create_device()`, so `upscaler_unchosen` is already set when `menu_upscaler_default()` runs at the end of `create_device()` (`gpu/vk/vk_main.cpp:501`). - By then `coopmat_init()` has run and `dev.max_push_descriptors` is read, so `fsr3::supported()` gives an answer it can cache. - A chosen DLSS or FSR 3.1 is left alone. Hardware matrices change nothing. - `fsr3_wanted()` still gives way to an upscaler chosen through the environment (`temporal_env_chosen()`). The menu tests that read `vk_menu.cpp` and `vk_main.cpp` pass at this head (51 checks). What it still needs: - **blocking** Merge conflict with main in `gpu/vk/vk_menu.cpp` and `gpu/vk/vk_menu.h`, as @hckrman101 noted. Main has moved a lot there: FSR 4 (#2342), DLSS frame generation and others. When you rebase, check that `upscaler_rows::GAME`/`FSR3` and the row indices still mean the same thing. Also check that no other PR has taken `options_version=5` in the meantime. CI hasn't run on this PR yet (only the title check), so it will get its first full run after the rebase. - **should fix** It needs an in-game check on a Steam Deck. The Deck is Tier 1 in `docs/workflow.md` and the main hardware this targets. So far the testing is headless start-up on Intel Arc and NVIDIA, plus fps numbers taken from #1000. This changes what every Deck and Intel player sees on first start, so please add a run on a Deck (or an RDNA2 card) showing the picture with FSR 3.1 by default. CONTRIBUTING asks for screenshots when a change shows on screen. A before/after at the same spot, plus the log line, would cover it. - **question** Players with an older options file who picked "the game's own" on purpose will be moved to FSR 3.1 once. That's the same trade as the Descriptor reuse step, and probably the right call. It's worth a line in the release notes so nobody thinks their setting was lost. - **minor** `gpu/vk/vk_menu.cpp:39` adds a second `#include "vk_fsr3.h"`; it's already included on line 41. The comment above `options_version` (line 1260) lists what 2, 3 and 4 mean, so add 5 ("the Upscaler: settled once the device is known, see menu_upscaler_default").
Joey-Magnus (Migrated from github.com) reviewed 2026-11-11 21:26:06 +00:00
Joey-Magnus (Migrated from github.com) left a comment

The logic is right (coopmat_init() runs before the new call, and menu_env_on's version < since leaves Descriptor reuse alone), but the scope is wider than the description says:

  • "Without matrix hardware" covers RDNA2 and older, many Intel cards, and GTX 10/16 (vk_coopmat.cpp:342). NVIDIA is only unchanged for RTX. Existing users with options_version 4 who chose the game's upscaler are switched once.
  • The default is saved on the first row change, so a test run with RX_VK_MMA_SOFTWARE=1 on RTX makes FSR 3.1 permanent.
  • It's never reconsidered after a GPU upgrade.
  • There are no before/after screenshots.
  • vk_fsr3.h is included twice in vk_menu.cpp.
  • It conflicts with main's vk_menu.cpp (options_version=4 at :1430, migration at :3113), vk_menu.h and vk_coopmat.cpp. options_version=5 is also used by #2119 and #2197.

Whether this should cover every non-matrix GPU or only Deck/RDNA2 is a maintainer call.

The logic is right (`coopmat_init()` runs before the new call, and `menu_env_on`'s `version < since` leaves Descriptor reuse alone), but the scope is wider than the description says: - "Without matrix hardware" covers RDNA2 and older, many Intel cards, and GTX 10/16 (`vk_coopmat.cpp:342`). NVIDIA is only unchanged for RTX. Existing users with `options_version` 4 who chose the game's upscaler are switched once. - The default is saved on the first row change, so a test run with `RX_VK_MMA_SOFTWARE=1` on RTX makes FSR 3.1 permanent. - It's never reconsidered after a GPU upgrade. - There are no before/after screenshots. - `vk_fsr3.h` is included twice in `vk_menu.cpp`. - It conflicts with main's `vk_menu.cpp` (`options_version=4` at `:1430`, migration at `:3113`), `vk_menu.h` and `vk_coopmat.cpp`. `options_version=5` is also used by #2119 and #2197. Whether this should cover every non-matrix GPU or only Deck/RDNA2 is a maintainer call.
hckrman101 (Migrated from github.com) reviewed 2026-11-11 22:19:21 +00:00
hckrman101 (Migrated from github.com) left a comment

Thanks for the rebase. The new upscaler_default flag is a nice answer to two of Joey-Magnus's earlier points: an RX_VK_MMA_SOFTWARE=1 test run on RTX no longer makes FSR 3.1 stick, and the default is worked out again on another GPU until the player touches the row. The duplicate vk_fsr3.h include is gone, and the options_version comment now covers the new step. I also checked the other ways the Upscaler can change: the launcher bridge goes through set_row, so it clears the flag too. Presets don't touch opts.dlss.

One thing breaks it, though:

  • blocking The call in create_device now runs before dev.max_push_descriptors is read (it's set in the if (dev.push_descriptor) block a few lines below, and nowhere else). When the matrices run in software and no Upscaler has been chosen, fsr3::supported() is asked while the limit is still 0. It fails the < 15 check, and because dev.device is already set it caches that false for the whole run. So on the Deck and on Intel, FSR 3.1 is never picked by default. Worse, it also drops out of the Upscaler row for that run, which is a regression from main. The description says the call runs after the limit is read, and Joey's review of the first version said the same, so I think the rebase moved it. Moving the call after the push-descriptor block (just before return true;) fixes it. Details inline.
  • should fix options_version=6 is also used by #2119 (still open). Whichever lands second needs to bump to 7 and move its version < N check with it.
  • The description still talks about options_version=5 throughout, and the tests in it were run before the rebase. Please update them, and re-run the Intel "no options file → FSR 3.1" case after the fix. It would have caught the problem above.
  • Still open from Joey's review: an in-game run on a Deck (or another RDNA2 card) with before/after screenshots and the log line, and a release-note line for players whose game-upscaler setting moves once. Whether this should apply to every GPU without matrix hardware (GTX 10/16 and older Intel too), or only to the Deck/RDNA2, is still @NotRexed's call.

CI has only run the title check so far, so the full build and tests haven't run on this head yet.

Thanks for the rebase. The new `upscaler_default` flag is a nice answer to two of Joey-Magnus's earlier points: an `RX_VK_MMA_SOFTWARE=1` test run on RTX no longer makes FSR 3.1 stick, and the default is worked out again on another GPU until the player touches the row. The duplicate `vk_fsr3.h` include is gone, and the `options_version` comment now covers the new step. I also checked the other ways the Upscaler can change: the launcher bridge goes through `set_row`, so it clears the flag too. Presets don't touch `opts.dlss`. One thing breaks it, though: - **blocking** The call in `create_device` now runs *before* `dev.max_push_descriptors` is read (it's set in the `if (dev.push_descriptor)` block a few lines below, and nowhere else). When the matrices run in software and no Upscaler has been chosen, `fsr3::supported()` is asked while the limit is still 0. It fails the `< 15` check, and because `dev.device` is already set it caches that `false` for the whole run. So on the Deck and on Intel, FSR 3.1 is never picked by default. Worse, it also drops out of the Upscaler row for that run, which is a regression from main. The description says the call runs after the limit is read, and Joey's review of the first version said the same, so I think the rebase moved it. Moving the call after the push-descriptor block (just before `return true;`) fixes it. Details inline. - **should fix** `options_version=6` is also used by #2119 (still open). Whichever lands second needs to bump to 7 and move its `version < N` check with it. - The description still talks about `options_version=5` throughout, and the tests in it were run before the rebase. Please update them, and re-run the Intel "no options file → FSR 3.1" case after the fix. It would have caught the problem above. - Still open from Joey's review: an in-game run on a Deck (or another RDNA2 card) with before/after screenshots and the log line, and a release-note line for players whose game-upscaler setting moves once. Whether this should apply to every GPU without matrix hardware (GTX 10/16 and older Intel too), or only to the Deck/RDNA2, is still @NotRexed's call. CI has only run the title check so far, so the full build and tests haven't run on this head yet.
hckrman101 (Migrated from github.com) commented 2026-11-11 22:19:21 +00:00

dev.max_push_descriptors is still 0 here: it's read in the if (dev.push_descriptor) block below. When the flag is set and the matrices run in software, fsr3::supported() logs "fewer than 15 (0)" and caches false for the run (its static const bool). That turns FSR 3.1 off on exactly the GPUs this targets, including in the menu. Moving this line after that block, just before return true;, fixes it.

`dev.max_push_descriptors` is still 0 here: it's read in the `if (dev.push_descriptor)` block below. When the flag is set and the matrices run in software, `fsr3::supported()` logs "fewer than 15 (0)" and caches `false` for the run (its `static const bool`). That turns FSR 3.1 off on exactly the GPUs this targets, including in the menu. Moving this line after that block, just before `return true;`, fixes it.
addisonbair commented 2026-11-12 01:52:52 +00:00 (Migrated from github.com)

Closing this one: on current main it makes the Steam Deck slower, the opposite of what it was for.

I did the Deck check you asked for, and also measured what each upscaler costs. Setup: main (eaef1460 game, this PR's renderer for both rows, only the Upscaler row changed), the first-start Minimum preset, the docked profile (what a first start sets), the standing Kokiri scene at a 1200 MHz GPU clock, two passes each:

Deck (Van Gogh, RADV) the game's own FSR 3.1 FSR's extra GPU
2048x1152 output 12.4 / 12.3 fps, GPU 39.5 ms 11.2 / 11.2 fps, GPU 47.5 ms +8.0 ms
1280x800 output 13.2 / 13.2 fps, GPU 34.5 ms 12.2 / 12.2 fps, GPU 38.5 ms +4.0 ms

The handheld profile shows the same thing (2048x1152: 14.1 vs 13.2 fps, +5 ms GPU).

The default itself works on the Deck. "Upscaler AMD FSR 3.1 by default" is logged on a first start, again on the next start from the file it wrote, and from a file main wrote. Intel ARL behaves the same in start-up checks. The pictures are equivalent.

The reason the premise no longer holds: the ~12 ms this was meant to save was the game's TAAU network on v2.0.14, where FSR 3.1 really was a win on the Deck (#1000). On current main, AMD runs a cheap bilinear stand-in in its place (the context of #1439), so on these GPUs FSR 3.1 only adds cost. For image quality on AMD, #1439 (FSR 1 EASU + RCAS over the stand-in, ~0.5 ms) looks like the better path. I couldn't measure Intel in-game here (pipelines fail with -13 on this ARL iGPU), so I have no evidence to narrow it to Intel either.

Thanks for the careful reviews. The ordering bug you caught would have been a real regression.

🤖 Generated with Claude Code

Closing this one: on current main it makes the Steam Deck slower, the opposite of what it was for. I did the Deck check you asked for, and also measured what each upscaler costs. Setup: main (eaef1460 game, this PR's renderer for both rows, only the Upscaler row changed), the first-start Minimum preset, the docked profile (what a first start sets), the standing Kokiri scene at a 1200 MHz GPU clock, two passes each: | Deck (Van Gogh, RADV) | the game's own | FSR 3.1 | FSR's extra GPU | |---|---|---|---| | 2048x1152 output | 12.4 / 12.3 fps, GPU 39.5 ms | 11.2 / 11.2 fps, GPU 47.5 ms | +8.0 ms | | 1280x800 output | 13.2 / 13.2 fps, GPU 34.5 ms | 12.2 / 12.2 fps, GPU 38.5 ms | +4.0 ms | The handheld profile shows the same thing (2048x1152: 14.1 vs 13.2 fps, +5 ms GPU). The default itself works on the Deck. "Upscaler AMD FSR 3.1 by default" is logged on a first start, again on the next start from the file it wrote, and from a file main wrote. Intel ARL behaves the same in start-up checks. The pictures are equivalent. The reason the premise no longer holds: the ~12 ms this was meant to save was the game's TAAU network on v2.0.14, where FSR 3.1 really was a win on the Deck (#1000). On current main, AMD runs a cheap bilinear stand-in in its place (the context of #1439), so on these GPUs FSR 3.1 only adds cost. For image quality on AMD, #1439 (FSR 1 EASU + RCAS over the stand-in, ~0.5 ms) looks like the better path. I couldn't measure Intel in-game here (pipelines fail with -13 on this ARL iGPU), so I have no evidence to narrow it to Intel either. Thanks for the careful reviews. The ordering bug you caught would have been a real regression. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
Commenting is not possible because the repository is archived.
No description provided.