Deck: launching from desktop mode gets no performance treatment (GPU auto, governor powersave); pinning the GPU clock is +23% measured #716

Open
opened 2026-11-15 06:09:41 +00:00 by omgitsgela · 2 comments
Contributor

On a Steam Deck, Longshot launched from desktop mode (the AppImage or the shell launcher - how Deck testers run today) gets no performance treatment at all: the GPU sits at power_dpm_force_performance_level=auto, the CPU at the amd-pstate-epp powersave governor, and gamescope's Game Mode pinning is not in play because there is no gamescope. The renderer already detects the Deck (hw.handheld, vk_main.cpp), so the pieces to notice this are there.

Measured on a Steam Deck OLED, same save (Kokiri spawn), same wrapper and protocol across arms (cap 60, vsync off, FSR 3.1, internal 50%, the standard RX_VK_* switches, RX_VK_READBACK_LATE=newest), install on microSD, one launch and one full protocol per arm, two arms each, fps from the control bridge's rolling average:

launch state idle spin
as-shipped (GPU auto, governor powersave) 17.1-17.3 17.0-17.6
gamemoderun (SteamOS gamemode 1.8.2, stock config) 16.8-17.0 same
CPU governor performance, GPU auto 17.9-18.0 noise
GPU clock pinned high 21.0-21.3 21.0-21.6

Pinning the GPU clock is worth +23% at the ceiling and costs nothing visually; dukenheimer's Game Mode measurements report a similar +30%. The frame stays CPU-shaped underneath (#402), which is why pinning moves the ceiling rather than the floor - but the ceiling is what players feel at 17 fps.

Mechanism is the open question, and this issue is a proposal, not a design:

  • The sysfs file is root-owned; the game process itself cannot write it. A privileged helper (setuid, polkit rule, or a gamemode startup hook - SteamOS's gamemoded runs as root and its hooks inherit that) would have to, and packaging one into the AppImage is work.
  • Stock SteamOS gamemode does not do it in practice: with gamemoderun the idle level stayed auto and the governor stayed powersave (measured above: zero), and that build's strings report "no cpu governors found" on this image. Upstream gamemode can write amdgpu sysfs through its root daemon via gamemode.ini keys, so a gamemode hook is a plausible vehicle even on SteamOS - but nothing in the stock image sets it up.
  • It must not fight the overlay: Game Mode's performance layer pins through its own means already. Anything shipped should check SteamDeck=1, desktop mode, and whether the level is already forced before touching it, and restore on exit (our test rig restores on every exit path, including kills via a systemd unit ExecStopPost).
  • gamemoderun alone with the stock SteamOS config is not the answer (measured: nothing), so the launcher can't just wrap that.

Wanted: maintainers' read on whether a privileged launch mechanism belongs in Longshot at all, and if yes, which packaging shape is acceptable (AppImage-adjacent helper, gamemode hook with a config file the package installs, or docs-only). Numbers reproducible on request; the protocol script and raw logs are on this tester's Deck.

On a Steam Deck, Longshot launched from desktop mode (the AppImage or the shell launcher - how Deck testers run today) gets no performance treatment at all: the GPU sits at `power_dpm_force_performance_level=auto`, the CPU at the amd-pstate-epp `powersave` governor, and gamescope's Game Mode pinning is not in play because there is no gamescope. The renderer already detects the Deck (`hw.handheld`, `vk_main.cpp`), so the pieces to notice this are there. Measured on a Steam Deck OLED, same save (Kokiri spawn), same wrapper and protocol across arms (cap 60, vsync off, FSR 3.1, internal 50%, the standard `RX_VK_*` switches, `RX_VK_READBACK_LATE=newest`), install on microSD, one launch and one full protocol per arm, two arms each, fps from the control bridge's rolling average: | launch state | idle | spin | |---|---|---| | as-shipped (GPU auto, governor powersave) | 17.1-17.3 | 17.0-17.6 | | `gamemoderun` (SteamOS gamemode 1.8.2, stock config) | 16.8-17.0 | same | | CPU governor `performance`, GPU auto | 17.9-18.0 | noise | | GPU clock pinned `high` | **21.0-21.3** | **21.0-21.6** | Pinning the GPU clock is worth **+23%** at the ceiling and costs nothing visually; dukenheimer's Game Mode measurements report a similar +30%. The frame stays CPU-shaped underneath (#402), which is why pinning moves the ceiling rather than the floor - but the ceiling is what players feel at 17 fps. Mechanism is the open question, and this issue is a proposal, not a design: - The sysfs file is root-owned; the game process itself cannot write it. A privileged helper (setuid, polkit rule, or a gamemode startup hook - SteamOS's `gamemoded` runs as root and its hooks inherit that) would have to, and packaging one into the AppImage is work. - Stock SteamOS gamemode does not do it in practice: with `gamemoderun` the idle level stayed `auto` and the governor stayed `powersave` (measured above: zero), and that build's strings report "no cpu governors found" on this image. Upstream gamemode *can* write amdgpu sysfs through its root daemon via `gamemode.ini` keys, so a gamemode hook is a plausible vehicle even on SteamOS - but nothing in the stock image sets it up. - It must not fight the overlay: Game Mode's performance layer pins through its own means already. Anything shipped should check `SteamDeck=1`, desktop mode, and whether the level is already forced before touching it, and restore on exit (our test rig restores on every exit path, including kills via a systemd unit `ExecStopPost`). - `gamemoderun` alone with the stock SteamOS config is not the answer (measured: nothing), so the launcher can't just wrap that. Wanted: maintainers' read on whether a privileged launch mechanism belongs in Longshot at all, and if yes, which packaging shape is acceptable (AppImage-adjacent helper, gamemode hook with a config file the package installs, or docs-only). Numbers reproducible on request; the protocol script and raw logs are on this tester's Deck.
Author
Contributor

Update with the combined arms (same Deck, same protocol as the table above): GPU pinned high + governor performance together, threaded Off, everything else unchanged - still on microSD:

arm idle spin walk
rec1 22.3 23.0 28.6
rec2 21.5 23.8 29.1 (samples tight at 29.4-29.5)

So the launch-state recipe moves the SD install from 17.1-17.6 idle/spin to 21.5-23.8, and walk to ~29. That is the same combination Game Mode's performance layer applies, measured where it currently does not apply. Both settings were restored automatically on stage exit (verified auto / powersave after).

Still open, honestly: spin/idle at 22-24 on SD does not reach 25-30; the remaining gap is #386's storage penalty (these exact arms on internal storage should land near 26-28 idle/spin by its measured 1.19x - arithmetic, not measurement) and this is with the CPU-bound submit path (#402) underneath. A mechanism in the launcher would not change either.

Update with the combined arms (same Deck, same protocol as the table above): GPU pinned high + governor performance together, threaded Off, everything else unchanged - still on microSD: | arm | idle | spin | walk | |---|---|---|---| | rec1 | 22.3 | 23.0 | 28.6 | | rec2 | 21.5 | 23.8 | 29.1 (samples tight at 29.4-29.5) | So the launch-state recipe moves the SD install from 17.1-17.6 idle/spin to 21.5-23.8, and walk to ~29. That is the same combination Game Mode's performance layer applies, measured where it currently does not apply. Both settings were restored automatically on stage exit (verified `auto` / `powersave` after). Still open, honestly: spin/idle at 22-24 on SD does not reach 25-30; the remaining gap is #386's storage penalty (these exact arms on internal storage should land near 26-28 idle/spin by its measured 1.19x - arithmetic, not measurement) and this is with the CPU-bound submit path (#402) underneath. A mechanism in the launcher would not change either.
Contributor

A Game Mode data point: at stock, Game Mode doesn't pin the GPU either. Steam Deck OLED, SteamOS 3.9.5, docked in Gaming Mode, Kokiri route, 2 interleaved runs per arm.

Clocks at stock (our sysfs sampler over the measured window; the player's installed build at 1280x800, 100% render, frame generation off):

setting real fps GPU clock GPU busy power CPU clock
game's own upscaler 14.5, 13.7 958, 953 MHz 79% 10.4, 11.6 W 2.6-2.8 GHz
upscaler off (less GPU work) 14.0, 14.2 918, 827 MHz 78-80% 12.0, 14.0 W 2.9-3.3 GHz

With less GPU work, the power manager moved the clocks: the GPU went down, the CPU went up, and fps stayed level.

The pin, in Game Mode. Longshot user build 92640867 (main e7ed2f7a + Deck patches; output 2048x1152, FSR 3.1 ~50%, frame generation 2x):

  • Stock: 15.5 and 14.8 real fps (median 15.2).
  • GPU at 1600 MHz: 19.8 real (+30%), p95 46 ms, at 19 W and 68 °C.
  • On an earlier Hookshot build in a static scene: stock 17.3; GPU 1600 MHz 23.0; CPU minimum 3.5 GHz alone 22.8; both 23.0. With the GPU pinned, the CPU also ran at ~3.4 GHz and ~17 W, so on that build the CPU clock is what paid. That differs from your governor result (+4%); the build and scene differ too.

On the mechanism: on SteamOS 3.9.5, steamos-priv-write (the helper Steam's performance overlay uses) wrote power_dpm_force_performance_level and pp_od_clk_voltage as the deck user without a password, from our test harness over ssh. SteamOS itself needs no setuid helper for this. Whether the game should do it is the maintainers' call. It costs power and heat: 19 W at 1600 MHz, against 10-14 W at stock in these runs.

A Game Mode data point: at stock, Game Mode doesn't pin the GPU either. Steam Deck OLED, SteamOS 3.9.5, docked in Gaming Mode, Kokiri route, 2 interleaved runs per arm. **Clocks at stock** (our sysfs sampler over the measured window; the player's installed build at 1280x800, 100% render, frame generation off): | setting | real fps | GPU clock | GPU busy | power | CPU clock | |---|---|---|---|---|---| | game's own upscaler | 14.5, 13.7 | 958, 953 MHz | 79% | 10.4, 11.6 W | 2.6-2.8 GHz | | upscaler off (less GPU work) | 14.0, 14.2 | 918, 827 MHz | 78-80% | 12.0, 14.0 W | 2.9-3.3 GHz | With less GPU work, the power manager moved the clocks: the GPU went down, the CPU went up, and fps stayed level. **The pin, in Game Mode.** Longshot user build 92640867 (main e7ed2f7a + Deck patches; output 2048x1152, FSR 3.1 ~50%, frame generation 2x): - Stock: 15.5 and 14.8 real fps (median 15.2). - GPU at 1600 MHz: 19.8 real (+30%), p95 46 ms, at 19 W and 68 °C. - On an earlier Hookshot build in a static scene: stock 17.3; GPU 1600 MHz 23.0; CPU minimum 3.5 GHz alone 22.8; both 23.0. With the GPU pinned, the CPU also ran at ~3.4 GHz and ~17 W, so on that build the CPU clock is what paid. That differs from your governor result (+4%); the build and scene differ too. **On the mechanism:** on SteamOS 3.9.5, `steamos-priv-write` (the helper Steam's performance overlay uses) wrote `power_dpm_force_performance_level` and `pp_od_clk_voltage` as the `deck` user without a password, from our test harness over ssh. SteamOS itself needs no setuid helper for this. Whether the game should do it is the maintainers' call. It costs power and heat: 19 W at 1600 MHz, against 10-14 W at stock in these runs.
Sign in to join this conversation.
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#716
No description provided.