WIP: feat(vk): a Steam Deck profile, on by itself on a Deck #225

Draft
dukenheimer wants to merge 2 commits from dukenheimer/Longshot:feat/steam-deck-profile into main
First-time contributor

Why

On a Steam Deck the game runs at about 15 frames a second and the full shader precompile can run the Deck out of memory
(v2.2.0-rc.1 was OOM-killed 4 times in 3 starts). This adds one switch, the Steam Deck profile, that turns on what
measured fastest there, and Auto (this PC) turns it on by itself on a Deck.

What changed

  • rules: gpu/vk/auto_settings.h: Hardware::deck; deck_mode() (RX_VK_STEAM_DECK over steam_deck= in the options
    file, else Auto) and deck_on() (Auto: on where a Deck is found, Hardware::handheld); choose() with the profile on:
    Minimum, the game's own upscaler, Choice::framegen = 2 and Choice::vram_auto, said in Auto's why text.
    fingerprint() mixes the profile in only when it is on, so no other PC's Auto chooses again with this update.
  • renderer: gpu/vk/vk_main.cpp deck_profile_decide(), called at the end of pick_device() (the options read, the card
    known, before the device and its first command buffer): decides the profile, logs vk: Steam Deck profile: on|off (why),
    and asks for the recorder thread while RX_VK_RECORDER is unset. auto_profile() passes hw.deck; the Deck detection
    moved into deck_found() (same test as before).
  • recorder: gpu/vk/vk_recorder.h: g_unset_mode / set_unset_mode() / requested(): the mode used while
    RX_VK_RECORDER is unset, taken exactly as the variable would be (refused with RX_VK_PARALLEL too).
    gpu/vk/vk_replay.cpp recorder_profile_thread() sets it to THREAD.
  • precompile: gpu/vk/vk_draw.cpp precompile_graphics_none() and precompile_run(): RX_VK_PRECOMPILE_GRAPHICS=none
    keeps no graphics pipeline (nor a program only they need) ready before the title; computes and the mesh fallback's
    programs are kept ready as before. The notes stay noted, and no .built marker is written when none were built. Unset:
    none with the Steam Deck profile, else all. Never in a --prepare-shaders process. This is the Longshot form of
    https://github.com/NotRexed/Hookshot/pull/2814.
  • menu: gpu/vk/vk_menu.cpp apply_auto(): with the profile, Frame generation 2x (unless RX_VK_FRAMEGEN_MULT) and the
    VRAM budget row at Auto (a budget the player set stays theirs); menu_init() reads steam_deck=, which save() keeps
    (no row writes it). menu_steam_deck() in vk_menu.h.
  • tests: tests/test_auto_settings.cpp: the switch's rule, the profile's choice (handheld and docked, and turned on
    elsewhere), and that the fingerprint with the profile off is the one from before. tests/test_auto_settings.py
    check_deck_profile(): decided in pick_device before create_device, the recorder and precompile parts, apply_auto.
  • docs: README "Steam Deck" section (the profile, and the 1600 MHz GPU clock in Steam's Performance overlay, which the
    game cannot set itself); docs/development.md: RX_VK_STEAM_DECK, RX_VK_PRECOMPILE_GRAPHICS, steam_deck.

Left out on purpose, as Longshot doesn't have them: the low view distances and their rows
(https://github.com/NotRexed/Hookshot/pull/2669, RX_OOT_*; with them and Render resolution 33% the Deck went to
17.0-17.2 fps), game-ordered barriers (https://github.com/NotRexed/Hookshot/pull/2559, RX_VK_GAME_ORDER, 15.8 fps) and
partial descriptors (https://github.com/NotRexed/Hookshot/pull/2644, RX_VK_DESC_PARTIAL, 15.6 fps). The game's own
upscaler in Longshot steps 100/75/67/50%, so the profile keeps Minimum's 50%. The uniform-buffer copies on integrated GPUs
(https://github.com/NotRexed/Hookshot/pull/1795, 0 freezes in ~25 starts) are their own PR (fix/igpu-ub-snapshot); the
profile assumes it.

Flags and settings

Flag What it does Default now Recommendation Helps on Doesn't help on Untested on
RX_VK_STEAM_DECK (new), steam_deck= in longshot_options.ini (new, no row) The Steam Deck profile on (1) or off (0); the variable wins over the line Unset: on where a Steam Deck is found, off elsewhere On on the Deck: its measured best Steam Deck (CPU-bound, 16 GB shared) Other PCs: off, nothing changes Steam Deck LCD
RX_VK_PRECOMPILE_GRAPHICS (new) none: no graphics pipeline kept ready before the title; all: every noted one Unset: none with the Steam Deck profile, else all On the Deck only, for now Low-RAM PCs that run out of memory in the precompile Fast PCs with RAM to spare (a short hitch at each pipeline's first draw in a run) Anything but the Deck
RX_VK_RECORDER=thread (existing) The replay's commands run by a recorder thread Off; on with the Steam Deck profile while unset On the Deck: commands 9.6 -> 6.0 ms Steam Deck RX_VK_PARALLEL (refused, as before)
Frame generation (existing row) Auto (this PC) sets 2x with the profile As the player set it; 2x with the profile 2x on the Deck: paces better than Auto Steam Deck
VRAM budget (existing row) Auto (this PC) sets Auto with the profile: the integrated GPU's own rule, 2560 MB on the Deck Minimum's 2.2 GB on the Deck before; Auto with the profile Auto on the Deck: a 1200 MB cap dropped panning to 6 fps Steam Deck

Measurements

Hardware: Steam Deck OLED (AMD Custom GPU 0932), SteamOS 3.9.5 (build 20261023.100, kernel 7.2.7-valve2), RADV Mesa
26.2.0-devel (git-5c65d4fc31)
Scene: Kokiri Forest from the same save, a scripted route (walk, pan, Saria's area), Gaming Mode, 1280x720 at 60 Hz
Method: real (game) and shown frame rate over the route, p95 frame time, peak memory sampled every second; warm caches

Not yet measured: main against this PR alone. No paired run of the two exists yet. What was run on Longshot:

Build Real fps Shown fps (2x) p95 Peak RSS Peak swap MemAvailable min Memory guard
main @ 39647e1a, no profile (full graphics precompile) - - - 10.3 GB 2.9 GB 277 MB killed (out of memory)
e7ed2f7a + this profile + #158/#164 + the memory-pressure fix + draw distances + static TLS (the user's Deck build) 15.1 28.0 81 ms 7.9 GB 0.25 GB 1.1 GB completed
the same, GPU clock 1600 MHz (Steam overlay, not code) 19.8 38.8 46 ms completed, 68 °C max, ~19 W

The first two rows are not a pair: the second has more than this PR, so they only show that the Deck ran out of memory
without RX_VK_PRECOMPILE_GRAPHICS=none and not with it. The log of the second has the profile's lines:

vk: Steam Deck profile: on (a Steam Deck): the command recorder on a thread of its own (RX_VK_RECORDER=thread); no graphics pipelines kept ready before the title (RX_VK_PRECOMPILE_GRAPHICS=none); ...
vk: precompile: graphics pipelines: none kept ready before the title (25337 noted; RX_VK_PRECOMPILE_GRAPHICS=none, the Steam Deck profile's)
vk: RX_VK_RECORDER=thread: vkCmd* packed on the replay thread and run in order by a recorder thread; ...

The single changes that chose the profile were measured on the user's earlier Deck build (Hookshot upstream 6749ba21
with Deck patches), each against the same build:

that build with the change
RX_VK_RECORDER=thread 15.5 fps (commands 9.6 ms) 16.0 fps (6.0 ms)
Frame generation 2x vs Auto (shown fps, p95 frame time) Auto ~31, 54-66 ms 2x ~34, ~47 ms
Texture budget automatic 2560 MB RX_VK_VRAM_MB=1200: dips to 6 fps panning
Graphics precompile (https://github.com/NotRexed/Hookshot/pull/2814) 7.5 GB anonymous, 99 hitches 3.8 GB, 12 hitches
GPU clock 1600 MHz (Steam overlay, not code) ~15 fps ~23 fps (~45 shown with 2x)

To do before ready: main @ 0877184 and this PR on the Deck, same route, at least 2 interleaved runs each.

Testing

Tested:

  • python -I -B tests/run_all.py on Linux (Ubuntu 22.04 container, clang), at 0d372bf on main 0877184: 490 passed, 6
    failed, 29 skipped; the same 6 fail on main in the same container, which has the source but not its git history
    (test_display_pointer, test_texture_staging_publication, test_cpp_preflight, test_prof_clock, test_dev_packs,
    byte-compile), so none is this PR's.
  • On the Steam Deck: this commit as it was on e7ed2f7a, in the user's Deck build (above): a cold and several warm starts,
    the profile's log lines, no out-of-memory kill, the frame rates above. The user plays on that build.

Not tested:

  • These commits on main 0877184 in the game: the rebase dropped Auto's Output resolution part, which main removed in
    #172 (the profile never set the output). The Windows build. (The Linux build and a Deck A/B of main, this PR and #227
    are running; results here when done.)
  • Main against this PR alone, on the Deck (see Measurements).
  • Off the Deck: nothing should change (the profile is off, the fingerprint is the same, the switches keep their
    defaults), which wants one run on another PC to confirm the log has no Steam Deck profile line and Auto keeps its
    settings.

Needs other testers:

  • Steam Deck (OLED or LCD), SteamOS: Kokiri Forest from a fresh options file; the log says vk: Steam Deck profile: on (a Steam Deck), RX_VK_RECORDER=thread and graphics pipelines: none kept ready, Auto chooses Frame generation 2x and
    the VRAM budget Auto; expecting no OOM kill over 3 starts and the real fps at least main's (request to open)
  • Windows or Linux desktop: Auto (this PC) unchanged on an existing options file, no profile line (request to open)

Screenshots

Nothing on screen beyond the menu rows Auto sets (Frame generation, VRAM budget).

Risk and rollback

On the Deck: the recorder thread and the graphics-pipeline skip are the riskier parts (a recorder fault, or hitches at
first draws). RX_VK_STEAM_DECK=0 turns the whole profile off; RX_VK_RECORDER=off or RX_VK_PRECOMPILE_GRAPHICS=all
turn one part off. Off the Deck nothing changes unless a switch is set. Reverting the commit is enough.

Reviewer notes

Start with auto_settings.h (the rule and its comment, which has the measurements) and deck_profile_decide() in
vk_main.cpp. No menu row for the switch yet: a row needs its texts in every language; Auto turning it on covers the
Deck. A Deck owner who set Auto before gets the profile's Frame generation 2x and VRAM budget Auto once, as the profile
turns on (deck_turned: the fingerprint Auto chose for is this hardware's without the profile); after that they are the
player's when Auto chooses again (another screen or build). Turned off, the VRAM budget's Auto gives way to Minimum's cap.

Issues

See https://github.com/NotRexed/Hookshot/pull/2814 (the precompile part's original).

Checklist

For everyone

  • One topic.
  • The title is a Conventional Commit (type(scope): description).
  • Rebased on current main.
  • python -I -B tests/run_all.py run and passing (any failure that also fails on main is named above).
  • Tested in the game, or what wasn't is named under Testing.
  • Testing filled in: what was tested, what wasn't, and what needs other testers (with test requests).
  • Measured: main and this PR at named commits, on the same PC, scene and settings, at least 2 interleaved runs each, the median reported.
  • No game files, dumps, build output or generated files.
  • third_party/ untouched (a new folder with a PROVENANCE.md instead).
  • The license is OK: GPL-3.0-or-later, nothing incompatible and nothing decompiled.

If applicable

  • AI assistance recorded with Assisted-by: / Co-Authored-By: trailers.
  • Flags noted, and the switch reference updated (docs/development.md, the README).
  • A row in docs/tested-configurations.md for each measured run.
  • A check under tests/ that reads changed source updated in this PR, not weakened.
  • Host code builds on both Windows (llvm-mingw) and Linux, with UTF-8 paths.
  • Docs updated.
## Why On a Steam Deck the game runs at about 15 frames a second and the full shader precompile can run the Deck out of memory (v2.2.0-rc.1 was OOM-killed 4 times in 3 starts). This adds one switch, the Steam Deck profile, that turns on what measured fastest there, and Auto (this PC) turns it on by itself on a Deck. ## What changed - rules: `gpu/vk/auto_settings.h`: `Hardware::deck`; `deck_mode()` (`RX_VK_STEAM_DECK` over `steam_deck=` in the options file, else Auto) and `deck_on()` (Auto: on where a Deck is found, `Hardware::handheld`); `choose()` with the profile on: Minimum, the game's own upscaler, `Choice::framegen = 2` and `Choice::vram_auto`, said in Auto's `why` text. `fingerprint()` mixes the profile in only when it is on, so no other PC's Auto chooses again with this update. - renderer: `gpu/vk/vk_main.cpp` `deck_profile_decide()`, called at the end of `pick_device()` (the options read, the card known, before the device and its first command buffer): decides the profile, logs `vk: Steam Deck profile: on|off (why)`, and asks for the recorder thread while `RX_VK_RECORDER` is unset. `auto_profile()` passes `hw.deck`; the Deck detection moved into `deck_found()` (same test as before). - recorder: `gpu/vk/vk_recorder.h`: `g_unset_mode` / `set_unset_mode()` / `requested()`: the mode used while `RX_VK_RECORDER` is unset, taken exactly as the variable would be (refused with `RX_VK_PARALLEL` too). `gpu/vk/vk_replay.cpp` `recorder_profile_thread()` sets it to `THREAD`. - precompile: `gpu/vk/vk_draw.cpp` `precompile_graphics_none()` and `precompile_run()`: `RX_VK_PRECOMPILE_GRAPHICS=none` keeps no graphics pipeline (nor a program only they need) ready before the title; computes and the mesh fallback's programs are kept ready as before. The notes stay noted, and no `.built` marker is written when none were built. Unset: `none` with the Steam Deck profile, else `all`. Never in a `--prepare-shaders` process. This is the Longshot form of https://github.com/NotRexed/Hookshot/pull/2814. - menu: `gpu/vk/vk_menu.cpp` `apply_auto()`: with the profile, Frame generation 2x (unless `RX_VK_FRAMEGEN_MULT`) and the VRAM budget row at Auto (a budget the player set stays theirs); `menu_init()` reads `steam_deck=`, which `save()` keeps (no row writes it). `menu_steam_deck()` in `vk_menu.h`. - tests: `tests/test_auto_settings.cpp`: the switch's rule, the profile's choice (handheld and docked, and turned on elsewhere), and that the fingerprint with the profile off is the one from before. `tests/test_auto_settings.py` `check_deck_profile()`: decided in `pick_device` before `create_device`, the recorder and precompile parts, `apply_auto`. - docs: README "Steam Deck" section (the profile, and the 1600 MHz GPU clock in Steam's Performance overlay, which the game cannot set itself); `docs/development.md`: `RX_VK_STEAM_DECK`, `RX_VK_PRECOMPILE_GRAPHICS`, `steam_deck`. Left out on purpose, as Longshot doesn't have them: the low view distances and their rows (https://github.com/NotRexed/Hookshot/pull/2669, `RX_OOT_*`; with them and Render resolution 33% the Deck went to 17.0-17.2 fps), game-ordered barriers (https://github.com/NotRexed/Hookshot/pull/2559, `RX_VK_GAME_ORDER`, 15.8 fps) and partial descriptors (https://github.com/NotRexed/Hookshot/pull/2644, `RX_VK_DESC_PARTIAL`, 15.6 fps). The game's own upscaler in Longshot steps 100/75/67/50%, so the profile keeps Minimum's 50%. The uniform-buffer copies on integrated GPUs (https://github.com/NotRexed/Hookshot/pull/1795, 0 freezes in ~25 starts) are their own PR (`fix/igpu-ub-snapshot`); the profile assumes it. ## Flags and settings | Flag | What it does | Default now | Recommendation | Helps on | Doesn't help on | Untested on | |---|---|---|---|---|---|---| | `RX_VK_STEAM_DECK` (new), `steam_deck=` in `longshot_options.ini` (new, no row) | The Steam Deck profile on (1) or off (0); the variable wins over the line | Unset: on where a Steam Deck is found, off elsewhere | On on the Deck: its measured best | Steam Deck (CPU-bound, 16 GB shared) | Other PCs: off, nothing changes | Steam Deck LCD | | `RX_VK_PRECOMPILE_GRAPHICS` (new) | `none`: no graphics pipeline kept ready before the title; `all`: every noted one | Unset: `none` with the Steam Deck profile, else `all` | On the Deck only, for now | Low-RAM PCs that run out of memory in the precompile | Fast PCs with RAM to spare (a short hitch at each pipeline's first draw in a run) | Anything but the Deck | | `RX_VK_RECORDER=thread` (existing) | The replay's commands run by a recorder thread | Off; on with the Steam Deck profile while unset | On the Deck: commands 9.6 -> 6.0 ms | Steam Deck | `RX_VK_PARALLEL` (refused, as before) | | | Frame generation (existing row) | Auto (this PC) sets 2x with the profile | As the player set it; 2x with the profile | 2x on the Deck: paces better than Auto | Steam Deck | | | | VRAM budget (existing row) | Auto (this PC) sets Auto with the profile: the integrated GPU's own rule, 2560 MB on the Deck | Minimum's 2.2 GB on the Deck before; Auto with the profile | Auto on the Deck: a 1200 MB cap dropped panning to 6 fps | Steam Deck | | | ## Measurements Hardware: Steam Deck OLED (AMD Custom GPU 0932), SteamOS 3.9.5 (build 20261023.100, kernel 7.2.7-valve2), RADV Mesa 26.2.0-devel (git-5c65d4fc31) Scene: Kokiri Forest from the same save, a scripted route (walk, pan, Saria's area), Gaming Mode, 1280x720 at 60 Hz Method: real (game) and shown frame rate over the route, p95 frame time, peak memory sampled every second; warm caches **Not yet measured: main against this PR alone.** No paired run of the two exists yet. What was run on Longshot: | Build | Real fps | Shown fps (2x) | p95 | Peak RSS | Peak swap | MemAvailable min | Memory guard | |---|---|---|---|---|---|---|---| | main @ 39647e1a, no profile (full graphics precompile) | - | - | - | 10.3 GB | 2.9 GB | 277 MB | killed (out of memory) | | e7ed2f7a + this profile + #158/#164 + the memory-pressure fix + draw distances + static TLS (the user's Deck build) | 15.1 | 28.0 | 81 ms | 7.9 GB | 0.25 GB | 1.1 GB | completed | | the same, GPU clock 1600 MHz (Steam overlay, not code) | 19.8 | 38.8 | 46 ms | | | | completed, 68 °C max, ~19 W | The first two rows are not a pair: the second has more than this PR, so they only show that the Deck ran out of memory without `RX_VK_PRECOMPILE_GRAPHICS=none` and not with it. The log of the second has the profile's lines: ``` vk: Steam Deck profile: on (a Steam Deck): the command recorder on a thread of its own (RX_VK_RECORDER=thread); no graphics pipelines kept ready before the title (RX_VK_PRECOMPILE_GRAPHICS=none); ... vk: precompile: graphics pipelines: none kept ready before the title (25337 noted; RX_VK_PRECOMPILE_GRAPHICS=none, the Steam Deck profile's) vk: RX_VK_RECORDER=thread: vkCmd* packed on the replay thread and run in order by a recorder thread; ... ``` The single changes that chose the profile were measured on the user's earlier Deck build (Hookshot upstream 6749ba21 with Deck patches), each against the same build: | | that build | with the change | |---|---|---| | `RX_VK_RECORDER=thread` | 15.5 fps (commands 9.6 ms) | 16.0 fps (6.0 ms) | | Frame generation 2x vs Auto (shown fps, p95 frame time) | Auto ~31, 54-66 ms | 2x ~34, ~47 ms | | Texture budget | automatic 2560 MB | `RX_VK_VRAM_MB=1200`: dips to 6 fps panning | | Graphics precompile (https://github.com/NotRexed/Hookshot/pull/2814) | 7.5 GB anonymous, 99 hitches | 3.8 GB, 12 hitches | | GPU clock 1600 MHz (Steam overlay, not code) | ~15 fps | ~23 fps (~45 shown with 2x) | To do before ready: main @ 0877184 and this PR on the Deck, same route, at least 2 interleaved runs each. ## Testing **Tested:** - `python -I -B tests/run_all.py` on Linux (Ubuntu 22.04 container, clang), at 0d372bf on main 0877184: 490 passed, 6 failed, 29 skipped; the same 6 fail on main in the same container, which has the source but not its git history (`test_display_pointer`, `test_texture_staging_publication`, `test_cpp_preflight`, `test_prof_clock`, `test_dev_packs`, byte-compile), so none is this PR's. - On the Steam Deck: this commit as it was on e7ed2f7a, in the user's Deck build (above): a cold and several warm starts, the profile's log lines, no out-of-memory kill, the frame rates above. The user plays on that build. **Not tested:** - These commits on main 0877184 in the game: the rebase dropped Auto's Output resolution part, which main removed in #172 (the profile never set the output). The Windows build. (The Linux build and a Deck A/B of main, this PR and #227 are running; results here when done.) - Main against this PR alone, on the Deck (see Measurements). - Off the Deck: nothing should change (the profile is off, the fingerprint is the same, the switches keep their defaults), which wants one run on another PC to confirm the log has no `Steam Deck profile` line and Auto keeps its settings. **Needs other testers:** - Steam Deck (OLED or LCD), SteamOS: Kokiri Forest from a fresh options file; the log says `vk: Steam Deck profile: on (a Steam Deck)`, `RX_VK_RECORDER=thread` and `graphics pipelines: none kept ready`, Auto chooses Frame generation 2x and the VRAM budget Auto; expecting no OOM kill over 3 starts and the real fps at least main's (request to open) - Windows or Linux desktop: Auto (this PC) unchanged on an existing options file, no profile line (request to open) ## Screenshots Nothing on screen beyond the menu rows Auto sets (Frame generation, VRAM budget). ## Risk and rollback On the Deck: the recorder thread and the graphics-pipeline skip are the riskier parts (a recorder fault, or hitches at first draws). `RX_VK_STEAM_DECK=0` turns the whole profile off; `RX_VK_RECORDER=off` or `RX_VK_PRECOMPILE_GRAPHICS=all` turn one part off. Off the Deck nothing changes unless a switch is set. Reverting the commit is enough. ## Reviewer notes Start with `auto_settings.h` (the rule and its comment, which has the measurements) and `deck_profile_decide()` in `vk_main.cpp`. No menu row for the switch yet: a row needs its texts in every language; Auto turning it on covers the Deck. A Deck owner who set Auto before gets the profile's Frame generation 2x and VRAM budget Auto once, as the profile turns on (`deck_turned`: the fingerprint Auto chose for is this hardware's without the profile); after that they are the player's when Auto chooses again (another screen or build). Turned off, the VRAM budget's Auto gives way to Minimum's cap. ## Issues See https://github.com/NotRexed/Hookshot/pull/2814 (the precompile part's original). ## Checklist ### For everyone - [x] One topic. - [x] The title is a Conventional Commit (`type(scope): description`). - [x] Rebased on current `main`. - [x] `python -I -B tests/run_all.py` run and passing (any failure that also fails on `main` is named above). - [x] Tested in the game, or what wasn't is named under Testing. - [x] Testing filled in: what was tested, what wasn't, and what needs other testers (with test requests). - [ ] Measured: main and this PR at named commits, on the same PC, scene and settings, at least 2 interleaved runs each, the median reported. - [x] No game files, dumps, build output or generated files. - [x] `third_party/` untouched (a new folder with a `PROVENANCE.md` instead). - [x] The license is OK: GPL-3.0-or-later, nothing incompatible and nothing decompiled. ### If applicable - [x] AI assistance recorded with `Assisted-by:` / `Co-Authored-By:` trailers. - [x] Flags noted, and the switch reference updated (docs/development.md, the README). - [ ] A row in `docs/tested-configurations.md` for each measured run. - [x] A check under `tests/` that reads changed source updated in this PR, not weakened. - [ ] Host code builds on both Windows (llvm-mingw) and Linux, with UTF-8 paths. - [x] Docs updated.
feat(vk): a Steam Deck profile, on by itself on a Deck
Some checks failed
PR description / Description follows the template (pull_request_target) Successful in 18s
CI / Conventional Commit title (pull_request) Has been cancelled
CI / Python source checks (ubuntu-24.04, part 1/2) (pull_request) Has been cancelled
CI / Python source checks (ubuntu-24.04, part 2/2) (pull_request) Has been cancelled
CI / SPIR-V fixtures (global narrow load/store, direct surface atomics, F2I immediate) (pull_request) Has been cancelled
CI / Workflow lint (actionlint) (pull_request) Has been cancelled
Third-party parts / As their notes say (pull_request) Has been cancelled
Third-party parts / The same as upstream (pull_request) Has been cancelled
e26a858130
One switch, RX_VK_STEAM_DECK (or steam_deck= in longshot_options.ini), for what ran
fastest on a Steam Deck OLED; neither set, it is on where a Deck is found. With
Auto (this PC): Minimum, Frame generation 2x and the VRAM budget Auto. Whatever
the preset, while their own switches are unset: RX_VK_RECORDER=thread and the new
RX_VK_PRECOMPILE_GRAPHICS=none (no graphics pipelines kept ready before the
title: the full precompile ran the Deck out of memory). Off the Deck nothing
changes: Auto's fingerprint is the same with the profile off.

Co-authored-by: Copilot App <[email protected]>
HumanBean left a comment

Code review only: I didn't build or run anything.

The profile is easy to follow. It is decided in pick_device() before the device exists, so the recorder mode is set before the first vkCmd*. Every renderer part gives way to its own variable, a PC that isn't a Deck keeps its fingerprint, and steam_deck= survives save() because it falls into the kept "others" lines. Four things to look at before this leaves WIP:

  1. Frame generation is forced back to 2x every time Auto chooses again (vk_menu.cpp apply_auto). The Frame generation row is preset_exempt, so a player who turns it off stays on Auto. The fingerprint includes the screen size, so on a Deck the next re-choose (docking to another screen, a DLSS or FSR 4 build change, the first start after this update) sets 2x again. That breaks the rule in the comment just above: "Frame generation is the player's".
  2. SteamDeck=1 on its own turns on the whole profile (deck_found). That variable comes from Steam's Deck-style session, not from the hardware. Other handhelds running a SteamOS-like gamescope session may set it too (ROG Ally or Legion Go on Bazzite or ChimeraOS, Legion Go S with SteamOS). Before this PR it only lowered Auto's tier. Now it also turns on the recorder thread and Frame generation 2x, drops the graphics precompile, and logs "a Steam Deck". Both real Decks already match on deviceName, LCD (Custom GPU 0405) and OLED (Custom GPU 0932). Device IDs 0x1002:0x163F and 0x1002:0x1435 would be sturdier still.
  3. RX_VK_PRECOMPILE_GRAPHICS=none never runs in load-at-create mode (vk_draw.cpp precompile_run). The programs_load_at_create() branch returns before graphics_none is checked. load_default.h picks create wherever the GPU has native mesh shaders. In that mode a first start still warms every note into the pipeline cache before the title (precompile_warm, bounded and let go), and the "none kept ready" line never appears. Your Deck log shows the start-mode path. Please say which vk: programs are loaded ... line that run printed, and either cover create mode or say in the docs and comments that the switch only applies in start mode.
  4. Turning the profile off doesn't give back Minimum's VRAM cap. c.vram_auto sets opts.budget = 0, the same state as "the player set the VRAM budget row". After RX_VK_STEAM_DECK=0, the next re-choose has own_budget true and keeps Auto.

Smaller: with RX_VK_PARALLEL set, the profile's log still says the recorder is "on a thread of its own", but mode_init refuses it and logs RX_VK_RECORDER=thread refused although the variable is unset.

Nothing touches vk_api.h. The tests that read the source are updated, not weakened. The Measured box is rightly unticked: main and this PR still need a paired run on the Deck, and the LCD and off-Deck runs are already listed as needing testers.

Code review only: I didn't build or run anything. The profile is easy to follow. It is decided in `pick_device()` before the device exists, so the recorder mode is set before the first vkCmd*. Every renderer part gives way to its own variable, a PC that isn't a Deck keeps its fingerprint, and `steam_deck=` survives `save()` because it falls into the kept "others" lines. Four things to look at before this leaves WIP: 1. **Frame generation is forced back to 2x every time Auto chooses again** (`vk_menu.cpp` `apply_auto`). The Frame generation row is `preset_exempt`, so a player who turns it off stays on Auto. The fingerprint includes the screen size, so on a Deck the next re-choose (docking to another screen, a DLSS or FSR 4 build change, the first start after this update) sets 2x again. That breaks the rule in the comment just above: "Frame generation is the player's". 2. **`SteamDeck=1` on its own turns on the whole profile** (`deck_found`). That variable comes from Steam's Deck-style session, not from the hardware. Other handhelds running a SteamOS-like gamescope session may set it too (ROG Ally or Legion Go on Bazzite or ChimeraOS, Legion Go S with SteamOS). Before this PR it only lowered Auto's tier. Now it also turns on the recorder thread and Frame generation 2x, drops the graphics precompile, and logs "a Steam Deck". Both real Decks already match on `deviceName`, LCD (`Custom GPU 0405`) and OLED (`Custom GPU 0932`). Device IDs 0x1002:0x163F and 0x1002:0x1435 would be sturdier still. 3. **`RX_VK_PRECOMPILE_GRAPHICS=none` never runs in load-at-create mode** (`vk_draw.cpp` `precompile_run`). The `programs_load_at_create()` branch returns before `graphics_none` is checked. `load_default.h` picks create wherever the GPU has native mesh shaders. In that mode a first start still warms every note into the pipeline cache before the title (`precompile_warm`, bounded and let go), and the "none kept ready" line never appears. Your Deck log shows the start-mode path. Please say which `vk: programs are loaded ...` line that run printed, and either cover create mode or say in the docs and comments that the switch only applies in start mode. 4. **Turning the profile off doesn't give back Minimum's VRAM cap.** `c.vram_auto` sets `opts.budget = 0`, the same state as "the player set the VRAM budget row". After `RX_VK_STEAM_DECK=0`, the next re-choose has `own_budget` true and keeps Auto. Smaller: with `RX_VK_PARALLEL` set, the profile's log still says the recorder is "on a thread of its own", but `mode_init` refuses it and logs `RX_VK_RECORDER=thread refused` although the variable is unset. Nothing touches `vk_api.h`. The tests that read the source are updated, not weakened. The *Measured* box is rightly unticked: main and this PR still need a paired run on the Deck, and the LCD and off-Deck runs are already listed as needing testers.
@ -8664,0 +8674,4 @@
// ready, pushed a Steam Deck's 16 GB into earlyoom's kills; https://github.com/NotRexed/Hookshot/pull/2814 halved its memory
// there (7.5 to 3.8 GB) and its hitches (99 to 12). Each is made at its first draw in play instead, from the driver's own
// cache where it has it. The notes stay noted (notes_open_locked, above): play notes none twice)
const bool graphics_none = precompile_graphics_none();
Owner

This is only reached in start mode. The programs_load_at_create() branch at the top of precompile_run returns first, and load_default.h picks create wherever the GPU has native mesh shaders. In that mode a first start still runs precompile_warm over every note (built and let go), and the "none kept ready" line never appears. Which vk: programs are loaded ... line did the Deck run print? Please either handle create mode or say in the docs and the comment that the switch only applies in start mode.

This is only reached in start mode. The `programs_load_at_create()` branch at the top of `precompile_run` returns first, and `load_default.h` picks create wherever the GPU has native mesh shaders. In that mode a first start still runs `precompile_warm` over every note (built and let go), and the "none kept ready" line never appears. Which `vk: programs are loaded ...` line did the Deck run print? Please either handle create mode or say in the docs and the comment that the switch only applies in start mode.
@ -193,0 +197,4 @@
/// (longshot) A Steam Deck, LCD or OLED: its APU as RADV names it, or SteamOS's own flag
bool deck_found(const VkPhysicalDeviceProperties& p) {
const char* deck = getenv("SteamDeck");
return (deck && atoi(deck) == 1) || strstr(p.deviceName, "Custom GPU 0405") || strstr(p.deviceName, "Custom GPU 0932");
Owner

SteamDeck=1 comes from Steam's Deck-style session, not from the hardware. SteamOS-like gamescope sessions on other handhelds (ROG Ally or Legion Go on Bazzite or ChimeraOS, Legion Go S with SteamOS) may set it too. Before this PR it only lowered Auto's tier. Now it also turns on the recorder thread and Frame generation 2x, skips the graphics precompile, and logs "a Steam Deck". Both Deck models already match on deviceName here (0405 LCD, 0932 OLED), so dropping the variable costs nothing on a real Deck. Matching vendor 0x1002 with device 0x163F or 0x1435 would be sturdier than the name.

`SteamDeck=1` comes from Steam's Deck-style session, not from the hardware. SteamOS-like gamescope sessions on other handhelds (ROG Ally or Legion Go on Bazzite or ChimeraOS, Legion Go S with SteamOS) may set it too. Before this PR it only lowered Auto's tier. Now it also turns on the recorder thread and Frame generation 2x, skips the graphics precompile, and logs "a Steam Deck". Both Deck models already match on `deviceName` here (0405 LCD, 0932 OLED), so dropping the variable costs nothing on a real Deck. Matching vendor 0x1002 with device 0x163F or 0x1435 would be sturdier than the name.
@ -193,0 +216,4 @@
}
const char* recorder = "the recorder as RX_VK_RECORDER says";
if (!getenv("RX_VK_RECORDER"))
recorder = recorder_profile_thread() ? "the command recorder on a thread of its own (RX_VK_RECORDER=thread)"
Owner

Nit: with RX_VK_PARALLEL set, recorder_profile_thread() still returns true, so this line logs "on a thread of its own". mode_init then refuses it and logs RX_VK_RECORDER=thread refused, although the variable is unset.

Nit: with `RX_VK_PARALLEL` set, `recorder_profile_thread()` still returns true, so this line logs "on a thread of its own". `mode_init` then refuses it and logs `RX_VK_RECORDER=thread refused`, although the variable is unset.
@ -938,2 +939,3 @@
if (own_budget || c.vram_auto) {
opts.budget = 0;
opts.vram_budget = vram_budget;
opts.vram_budget = own_budget ? vram_budget : 0;
Owner

c.vram_auto leaves opts.budget = 0, the same state the player gets by setting the VRAM budget row. If the profile is later turned off (RX_VK_STEAM_DECK=0), the re-choose sees own_budget true and keeps Auto, not Minimum's 2.2 GB. Turning the profile off doesn't fully undo it. That's probably fine on a 16 GB Deck, but the README says =0 turns it off.

`c.vram_auto` leaves `opts.budget = 0`, the same state the player gets by setting the VRAM budget row. If the profile is later turned off (`RX_VK_STEAM_DECK=0`), the re-choose sees `own_budget` true and keeps Auto, not Minimum's 2.2 GB. Turning the profile off doesn't fully undo it. That's probably fine on a 16 GB Deck, but the README says `=0` turns it off.
@ -940,2 +942,4 @@
vram_budget_sync();
}
// (Frame generation is the player's, as with every preset, but for the Steam Deck profile's 2x; RX_VK_FRAMEGEN_MULT still wins)
if (c.framegen >= 0 && !getenv("RX_VK_FRAMEGEN_MULT")) opts.framegen = c.framegen;
Owner

Frame generation is preset_exempt, so a player who turns it off stays on Auto. This line then sets 2x again every time Auto chooses again. The fingerprint includes the screen size, so on a Deck that means docking to another screen, a DLSS or FSR 4 build change, or the first start after this update. That contradicts the comment above ("Frame generation is the player's"). Could it apply only on a first start or when Auto is picked on the row (!keep_own), the way the VRAM budget keeps the player's own?

Frame generation is `preset_exempt`, so a player who turns it off stays on Auto. This line then sets 2x again every time Auto chooses again. The fingerprint includes the screen size, so on a Deck that means docking to another screen, a DLSS or FSR 4 build change, or the first start after this update. That contradicts the comment above ("Frame generation is the player's"). Could it apply only on a first start or when Auto is picked on the row (`!keep_own`), the way the VRAM budget keeps the player's own?
dukenheimer force-pushed feat/steam-deck-profile from e26a858130
Some checks failed
PR description / Description follows the template (pull_request_target) Successful in 18s
CI / Conventional Commit title (pull_request) Has been cancelled
CI / Python source checks (ubuntu-24.04, part 1/2) (pull_request) Has been cancelled
CI / Python source checks (ubuntu-24.04, part 2/2) (pull_request) Has been cancelled
CI / SPIR-V fixtures (global narrow load/store, direct surface atomics, F2I immediate) (pull_request) Has been cancelled
CI / Workflow lint (actionlint) (pull_request) Has been cancelled
Third-party parts / As their notes say (pull_request) Has been cancelled
Third-party parts / The same as upstream (pull_request) Has been cancelled
to 0d372bf121
Some checks are pending
CI / Conventional Commit title (pull_request) Blocked by required conditions
CI / Python source checks (ubuntu-24.04, part 1/2) (pull_request) Blocked by required conditions
CI / Python source checks (ubuntu-24.04, part 2/2) (pull_request) Blocked by required conditions
CI / SPIR-V fixtures (global narrow load/store, direct surface atomics, F2I immediate) (pull_request) Blocked by required conditions
CI / Workflow lint (actionlint) (pull_request) Blocked by required conditions
Third-party parts / As their notes say (pull_request) Blocked by required conditions
Third-party parts / The same as upstream (pull_request) Blocked by required conditions
PR description / Description follows the template (pull_request_target) Successful in 3s
Website / Website on Cloudflare (pull_request_target) Has been skipped
2026-11-13 16:35:30 +00:00
Compare
Author
First-time contributor

Thanks, all five were right. Fixed in 0d372bf (on top of the profile commit, rebased onto main 0877184):

  1. Frame generation forced back to 2x: 2x and the VRAM budget Auto are now set only when the profile is chosen. That is a first start, Auto picked on its row (!keep_own), or the profile turned on since Auto last chose (auto_settings.h deck_turned: auto_for is this hardware's fingerprint with the profile flipped). So an existing Deck install gets them once with this update. After that, when Auto chooses again for another screen or build, they are the player's. Unit checks are in test_auto_settings.cpp; the source checks in test_auto_settings.py now ask for the gate.
  2. SteamDeck=1: deck_found now matches the APU only: vendor 0x1002 with 0x163F or 0x1435, or RADV's Custom GPU 0405 / 0932. SteamDeck=1 still lowers Auto's tier as before (handheld_found feeds Hardware::handheld), but no longer turns on the profile. The test fails if deck_found reads SteamDeck again.
  3. Create mode: the Deck run printed vk: programs are loaded before the title (start): this GPU draws mesh programs through the compute fallback, so it was start mode. In create mode the switch is now left as it is, and it logs RX_VK_PRECOMPILE_GRAPHICS=none ... left as it is: programs load at their create calls, which keep no graphics pipeline ready before the title anyway. The warm builds each pipeline and lets it go, so memory stays bounded there. The comment and docs/development.md say it only applies in start mode.
  4. Turning the profile off: when the profile turns off (deck_settings(...).restore_cap), a VRAM budget at Auto is no longer taken for the player's, so Minimum's cap comes back. A budget value the player picked stays. Frame generation stays as it is, since no preset sets that row; the README says so.
  5. Nit, RX_VK_PARALLEL: recorder_profile_thread() no longer asks for the thread when RX_VK_PARALLEL is 2 or more, so there's no refusal of an unset variable. The profile line says "the recorder off, as RX_VK_PARALLEL records on several threads".

Tests: Linux suite 490 passed, 6 failed. The same 6 fail on main 0877184 in the same container (one is new from #147: test_dev_packs needs a git checkout). Not yet run in the game: the Deck A/B (main 0877184 against this PR, with #227 on top) is building now and will run on these commits.

Thanks, all five were right. Fixed in 0d372bf (on top of the profile commit, rebased onto main 0877184): 1. **Frame generation forced back to 2x:** 2x and the VRAM budget Auto are now set only when the profile is chosen. That is a first start, Auto picked on its row (`!keep_own`), or the profile turned on since Auto last chose (`auto_settings.h` `deck_turned`: `auto_for` is this hardware's fingerprint with the profile flipped). So an existing Deck install gets them once with this update. After that, when Auto chooses again for another screen or build, they are the player's. Unit checks are in `test_auto_settings.cpp`; the source checks in `test_auto_settings.py` now ask for the gate. 2. **`SteamDeck=1`:** `deck_found` now matches the APU only: vendor 0x1002 with 0x163F or 0x1435, or RADV's `Custom GPU 0405` / `0932`. `SteamDeck=1` still lowers Auto's tier as before (`handheld_found` feeds `Hardware::handheld`), but no longer turns on the profile. The test fails if `deck_found` reads `SteamDeck` again. 3. **Create mode:** the Deck run printed `vk: programs are loaded before the title (start): this GPU draws mesh programs through the compute fallback`, so it was start mode. In create mode the switch is now left as it is, and it logs `RX_VK_PRECOMPILE_GRAPHICS=none ... left as it is: programs load at their create calls, which keep no graphics pipeline ready before the title anyway`. The warm builds each pipeline and lets it go, so memory stays bounded there. The comment and `docs/development.md` say it only applies in start mode. 4. **Turning the profile off:** when the profile turns off (`deck_settings(...).restore_cap`), a VRAM budget at Auto is no longer taken for the player's, so Minimum's cap comes back. A budget value the player picked stays. Frame generation stays as it is, since no preset sets that row; the README says so. 5. **Nit, `RX_VK_PARALLEL`:** `recorder_profile_thread()` no longer asks for the thread when `RX_VK_PARALLEL` is 2 or more, so there's no refusal of an unset variable. The profile line says "the recorder off, as RX_VK_PARALLEL records on several threads". Tests: Linux suite 490 passed, 6 failed. The same 6 fail on main 0877184 in the same container (one is new from #147: `test_dev_packs` needs a git checkout). Not yet run in the game: the Deck A/B (main 0877184 against this PR, with #227 on top) is building now and will run on these commits.
HumanBean added this to the v3.0.0 milestone 2026-11-13 16:53:10 +00:00
Some checks are pending
CI / Conventional Commit title (pull_request) Blocked by required conditions
CI / Python source checks (ubuntu-24.04, part 1/2) (pull_request) Blocked by required conditions
CI / Python source checks (ubuntu-24.04, part 2/2) (pull_request) Blocked by required conditions
CI / SPIR-V fixtures (global narrow load/store, direct surface atomics, F2I immediate) (pull_request) Blocked by required conditions
CI / Workflow lint (actionlint) (pull_request) Blocked by required conditions
Third-party parts / As their notes say (pull_request) Blocked by required conditions
Third-party parts / The same as upstream (pull_request) Blocked by required conditions
PR description / Description follows the template (pull_request_target) Successful in 3s
Website / Website on Cloudflare (pull_request_target) Has been skipped
This pull request is marked as a work in progress.
Some workflows are waiting to be reviewed.
This branch is out-of-date with the base branch
View command line instructions

Checkout

From your project repository, check out a new branch and test the changes.
git fetch -u 'feat/steam-deck-profile:dukenheimer-feat/steam-deck-profile'
git switch 'dukenheimer-feat/steam-deck-profile'
Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
2 participants
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
MagicBeanDev/Longshot!225
No description provided.