Skip to content

Support streaming a disconnected display after booting headless - #2372

Merged
matte-schwartz merged 5 commits into
ValveSoftware:masterfrom
matte-schwartz:headless-display-fix
Sep 23, 2026
Merged

matte-schwartz merged 5 commits into
ValveSoftware:masterfrom
matte-schwartz:headless-display-fix

Conversation

@matte-schwartz

Copy link
Copy Markdown
Collaborator

Gamescope currently fails to start on a Steam Machine that boots with no display connected, aborting in init_drm because finding a primary plane requires a CRTC and a CRTC requires a connected connector. That leaves the session in a login loop until something is plugged in.

This makes the DRM backend serve a stand-in connector while nothing is connected, so a headless machine comes up and can be streamed through Remote Play. The virtual screen keeps the resolution of the display that was last driven rather than falling back to 720p, and it offers that display's real mode list, so if you were on an ultrawide you can stay at 5120x2160 or drop to 1920x1080 from Steam's display settings even though the machine booted with the monitor disconnected or off. The mode list and the last display's identity come from a small sidecar file written next to the patched EDID whenever a real display is driven.

Deliberately not included are synthetic streaming resolutions beyond what the display really offered, and inheriting the last display's HDR support. Both need the Steam client side worked out first. I could see how a virtual-connector-based approach would fit a per-app streaming model better, but it pulls in focus and composite changes across steamcompmgr plus Steam-side work, so this tries to match how Remote Play streams a whole session today.

init_drm aborts with "Failed to find a primary plane" when nothing is
connected at boot, because find_primary_plane needs a CRTC and a CRTC
needs a connected connector.

Fall back to any primary plane so format selection has something to
work from. Without a CRTC have_overlay_planes is false, so the
overlay-less format switch also learns the alpha variants the picker can
return.
GetNextVBlank walks forward from the last marked vblank one interval at
a time. Nothing marks a vblank while no display is connected, so every
wakeup replays the whole uptime, roughly 60 iterations per second of
uptime per call. A headless streaming box eventually burns a core on it.

Compute the number of missed intervals directly.
drm_unset_mode falls back to 1280x720 whenever no display is connected,
so Steam sees a 720p virtual screen after a TV is turned off or on a
cold headless boot.

Keep the current mode when a connected display goes away. On a cold
start, identify the last driven display from the persisted patched EDID
and use its saved mode, else its EDID preferred mode. An explicit -W/-H
or -r still wins for the part it sets.
Nothing records which modes the last connected display offered, so a
later headless session has no mode list to work from.

Store one WxH@R line per mode next to the patched EDID file. Only the
DRM backend writes it, so a nested session cannot clobber it.
Headless is a null current connector, so consumers each need their own
null path for the mode list, identity, saved mode picks and the EDID
games read, and none of that state has a home.

Serve a headless connector while no real one is connected, keeping the
backend's own paths on the real connector so nothing writes the
persisted EDID for a screen that does not exist. It owns the headless
mode list, identity and saved mode key, offering the modes the last
display really had, and games read a copy of that display's EDID
stamped with Gamescope's identity. It reads as an external SDR screen,
so color management targets sRGB and the vblank red zone, external
touch handling and the app refresh cycle take the external paths.
@matte-schwartz
matte-schwartz merged commit ad2763d into ValveSoftware:master Sep 23, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant