protonscr

Steam client never writes to `~/.config/gamescope/modes.cfg`; docked Deck DRM output falls back to EDID-preferred (4K) instead of user's chosen resolution

steamclosed
ValveSoftware/steam-for-linux#13481 · opened 2026-08-03 by jabberwock · updated 2026-08-04 · 4 comments · github
Jjabberwock 2026-08-03 github

Summary

The Steam client never writes to ~/.config/gamescope/modes.cfg when a
docked Steam Deck's external display resolution is chosen in Gaming
Mode's Settings > Display picker. The chosen resolution is stored in
the client's own config.vdf but the gamescope mode-save file stays
empty, so gamescope has no way to know what mode to scan out at, and
falls back to the display's EDID-preferred mode (4K on a 4K TV) with a
2-3x GPU cost for no visual benefit.

Full end-user impact and workaround are documented at
ValveSoftware/SteamOS#2687.
This issue is scoped narrowly to the Steam client's missing write path.

What Steam does record

~/.local/share/Steam/config/config.vdf, UI.display.Current block:

"IsExternalDisplay"  "1"
"name"               "External: DP-1 55\"|||Fullscreen-1920x1080"

So the client knows the user picked 1080p, keyed by the connector
description string that matches gamescope's description argument to
get_saved_mode().

What Steam does not do

~/.config/gamescope/modes.cfg on my system: 0 bytes. The file is
created (session script touches it) and $GAMESCOPE_MODE_SAVE_FILE
points at it, but nothing ever writes an entry.

Why gamescope needs that write

src/Backends/DRMBackend.cpp in
setup_best_connector(),
priority order for the DRM scanout mode:

  1. drm->preferred_width/height/refresh (from CLI -W/-H/-r)
  2. get_saved_mode()
    from $GAMESCOPE_MODE_SAVE_FILE (external displays only)
  3. find_mode(0, 0, 0) fallback = EDID-preferred mode

The session script skips (1). (2) is empty because Steam never wrote
to it. So (3) fires and picks the TV's EDID-preferred 4K mode.

Two-channel distinction

The Settings > Display picker currently affects only the game render
size
(xwayland Updating mode for xwayland server log lines), which
Steam nudges over runtime IPC. It does not touch the DRM
output/scanout size
, which is what modes.cfg and priority (1) above
control. That mismatch is the whole bug: the game is told to render at
1080p, then that 1080p frame gets composited and scanned out at 4K
every frame.

Steps to reproduce

  1. Dock a Steam Deck OLED to a 4K TV.
  2. Boot into Gaming Mode.
  3. Settings > Display: uncheck "Automatically set resolution", set
    Resolution to 1920x1080.
  4. Launch any GPU-bound game.
  5. cat ~/.config/gamescope/modes.cfg from an SSH session or Desktop
    Mode later: file is 0 bytes.
  6. journalctl --user -b | grep -iE "gamescope.*(selecting mode|updating mode)"
    shows selecting mode 3840x2160@60Hz for the DRM output and
    Updating mode for xwayland server #0: 1920x1080@60 for the render
    surface.

Expected behavior

When the user picks a resolution for an external display in Settings >
Display, Steam should write a line to $GAMESCOPE_MODE_SAVE_FILE (or
~/.config/gamescope/modes.cfg) of the form:

<description>:<width>x<height>@<refresh> <broadcast_mode>

where <description> matches the string gamescope constructs for the
connector (same string Steam already stores in config.vdf's
UI.display.Current.name). Gamescope on next connector setup (or on
runtime mode change) would then pick that mode at priority (2) above
instead of falling through to EDID-preferred.

Alternatively, push the chosen mode through a runtime IPC that sets
drm->preferred_width/height/refresh after startup. Either mechanism
closes the loop.

Environment

  • Steam Deck OLED 1TB
  • SteamOS 3.8.16 (build 20260716.1)
  • Kernel 6.16.12-valve24.5-1-neptune-616-gb2f7cfe85e45
  • gamescope 3.16.23.4-1
  • Steam client: current stable
  • Dock: JSAUX USB-C dock
  • Display: Sony X80K 55" 4K LED TV

Credit

The DRM-backend code path was traced with help from "the Dragon Girl"
on Discord, who correctly pointed at the DRM backend as the place to
look.

Related

Kkisak-valve maintainer 2026-08-03 github

Hello @jabberwock, in general, the Steam client shouldn't be writing into the config files of other applications. This isn't a path to a solution.

Llostgoat 2026-08-03 github

Reopening this.

Writing to modes.cfg is the official interface between steam and gamescope for changing modes on demand.

When a manual resolution is set under Settings->Display steam will update modes.cfg with the new requested mode.

@jabberwock please follow the issue template and attach the relevant logs or submit a system report via Settings->System. The logs are needed to figure out why the write operation is failing on your deck.

The llm generated issue reports usually contain a lot of misinformation, so it’s best to keep things as brief as possible instead. E.g. editing the session script is not recommended and will break other Gamescope logic. So the real path that should be used is the modes.cfg path.

Can you also check the file permissions on the file to make sure it is user writeable?

Jjabberwock 2026-08-03 github

Will check when I have more time and upload what I have.

Remote-only, I restored the game-session script to the stock default, rebooted, started Brotato (all over SSH) and confirmed while the client reported setting to 1920x1080, the DRM itself was still scanning out 4K.

FYI the session-script patch was for me personally and I was not meaning to frame it as the ideal fix because I agree it is not.

Jjabberwock 2026-08-04 github

Retested on the current stable build (SteamOS 20260716.1, gamescope 3.16.23.4) with the session-script patch reverted:

  • Fresh Settings -> Display toggle (uncheck Auto, set 1920x1080@60) wrote ~/.config/gamescope/modes.cfg correctly: Sony SONY TV *30:1920x1080@60 0.
  • Gamescope live-reloaded the mode. drm: selecting mode 1920x1080@60Hz in the journal, CRTC-0 confirmed at 1920x1080@60 via /sys/kernel/debug/dri/0/state.
  • Survives reboot. Boot straight into 1080p scanout, modes.cfg still populated, no UI interaction needed.
  • Confirmed at scanout while a game is running.

The write path works on this build. Withdrawing, likely fixed between whatever version I was on before and 20260716.1.