#5839

(Steam Deck) Black picture in 4-7/10 streams when GAMESCOPE_COMPOSITE_FORCE set to 0 since 2026.906

Opencausa-primaopened 1mo ago

Is there an existing issue for this?

  • I have searched the existing issues

Is your issue described in the documentation?

  • I have read the documentation

Is your issue present in the latest beta/pre-release?

This issue is present in the latest pre-release

Describe the Bug

The stream stays black (but sound works) in 4 to 7 out of 10 tries when connecting with Moonlight to a Steam Deck (started as root via flatpak run --system from a Decky plugin, handheld, internal display) since 2026.906 when GAMESCOPE_COMPOSITE_FORCE is set to 0. When set to 1 (using DISPLAY=:0 xprop -root -f GAMESCOPE_COMPOSITE_FORCE 32c -set GAMESCOPE_COMPOSITE_FORCE 1) or using 2026.516, the stream works as expected. Switching that flag while streaming has no effect until you restart the stream. The logs show Warning: Couldn't get drm fb for plane [0]: Invalid argument with 60 entries per second for 13 seconds, but my session continued for another 9 seconds after that and the picture stayed black the whole time.

To reproduce, start a Sunshine instance with version >= 906 in Game Mode with GAMESCOPE_COMPOSITE_FORCE unset or set to 0. As I wrote, this issue only occurs about 4-7 out of 10 tries, and I sometimes got lucky and got 3 working streams in a row (see the log), so you might have to try multiple times.

Expected Behavior

The stream should show a picture all the time.

Additional Context

Note that in the logs I provided, I had to reconnect the stream 3 times for the issue to appear, so the initial logs might look fine. The initial 3 sessions didn't show a single fb warning.

While the stream was black, there have been two active planes on the CRTC - Primary (obj 73, zpos 3) and Overlay (obj 95, zpos 10), both 1280x800 ARGB8888, both with active AMD color pipeline (DEGAMMA sRGB, SHAPER, LUT3D, BLEND sRGB). When the stream worked normally, there was just the primary plane, XRGB2101010, Pipeline Default. Rotation (rotate-270) and size were the same in both instances. I can provide the drm_info snapshots if needed.

I also tried the latest pre-release (2026.929.125923), which is affected as well. I had to set system_tray = disabled there though, as otherwise Sunshine would exit right after startup because no system tray is available in game mode on the Steam Deck.

Issue #5659 is possibly related, as it shows similar symptoms (overlay planes disturbing kmsgrab)

My analysis (not verified):

  • according to my research the [0] in the warning message isn't the number of the plane, but of the framebuffer id, meaning that the plane Sunshine chose when starting the stream (kmsgrab.cpp#L1539-L1542) didn't have any framebuffer in the affected frames.
  • kmsgrab.cpp takes the first non-cursor plane with framebuffer and afterward only reads this plane. When there are two planes, the composited image is never read. Which plane Sunshine chooses isn't logged.
  • plane selection and refresh() are unchanged between 516 and 914
  • 906 brought code changes as well as a runtime change fdo 24.08 -> KDE 6.10, GL 24.08 -> 25.08 at the same time, and there are no intermediate builds on flathub, so I can't bisect this further. However, I can test with AppImages if that helps.
  • ruled out: privileged DRM/EGL worker (#5706/#5709) - was introduced in 914, but 906 is already affected

Side note for 914: card.fb() (fb() and drmModeGetFB2_privileged/drmModeGetFB_privileged) runs in the worker thread, but the warning prints errno of the calling thread (thread-local) - the error text (EINVAL/EAGAIN) is meaningless. This doesn't explain the black image though, as the black image is already present in 906.

Host Operating System

Linux

Operating System Version

SteamOS 3.8.28 (Build 20260922.1), Kernel 6.18.50-valve2, GameScope 3.16.23.6,

Architecture

amd64/x86_64

Package

Linux - flathub/flatpak

GPU Type

AMD

GPU Model

„AMD Custom GPU 0405" (Van Gogh) / Steam Deck LCD

GPU Driver/Mesa Version

26.2.2 (Flatpak GL.default//25.08, 2026.906+), 26.1.8 (//24.08, 2026.516)

Capture Method

KMS (Linux)

Apps

undefined

Log output

undefined

Online logs

https://gist.github.com/causa-prima/1c03a3b11e5b050044466fd433ace31c

Activity

5
baltimoresports1mo ago

Independent confirmation on different hardware/OS — desktop AMD dGPU on Bazzite, not Steam Deck/SteamOS — same symptom, same plane signature.

Setup: RX 9070 XT (desktop, discrete), Bazzite bazzite-deck:stable, Sunshine 2026.914.233613 (Homebrew and pvermeer COPR builds both tested, same result), capture = kms.

Log during a black session (44.20260928.1, Sunshine 2026.914.233613) — warning repeats continuously (60/sec) for the full duration of the connection, ~180ms after stream start through to disconnect:

[2026-09-30 04:16:10.463]: Warning: Couldn't get drm fb for plane [0]: Permission denied
[2026-09-30 04:16:10.480]: Warning: Couldn't get drm fb for plane [0]: Permission denied
[2026-09-30 04:16:10.493]: Warning: Couldn't get drm fb for plane [0]: Permission denied
...
[2026-09-30 04:16:13.780]: Warning: Couldn't get drm fb for plane [0]: Permission denied
[2026-09-30 04:16:13.797]: Warning: Couldn't get drm fb for plane [0]: Permission denied
[2026-09-30 04:16:20.758]: Info: CLIENT DISCONNECTED

Same warning also observed as No such file or directory on other runs, same build and hardware — consistent with @causa-prima's note that the errno text is unreliable on 914+.

Captured /sys/kernel/debug/dri/<card>/state live during that same black session, compared against a working session on a prior Bazzite image (44.20260908):

Working — one plane on crtc-0:

plane[80]: plane-3
        crtc=crtc-0
        fb=183
        size=3840x2160
        normalized-zpos=0

crtc[118]: crtc-0
        plane_mask=8

Broken — two planes on crtc-0, same live black-screen moment:

plane[80]: plane-3
        crtc=crtc-0
        fb=187
        size=3840x2160
        normalized-zpos=0

plane[105]: plane-6
        crtc=crtc-0
        fb=192
        size=1920x1080
        normalized-zpos=1

crtc[118]: crtc-0
        plane_mask=48

Both planes have a valid, actively-allocated framebuffer — the second plane isn't empty, gamescope is using it. This matches @causa-prima's description of two active planes (Primary + Overlay) exactly, and is consistent with the plane-selection mechanism described above: whichever plane Sunshine locks onto at stream start, it never re-evaluates.

Also tracking a possible trigger on the Bazzite side in [ublue-os/bazzite#5828](https://github.com/ublue-os/bazzite/issues/5828) — gamescope had a real version jump in the same window (137.7282613d-1 → 3.16.28^3-1, not a repackage), with a new Steam-overlay compositing mechanism in the diff. Different exact gamescope build than SteamOS's, so not asserting it's the same trigger — but worth cross-checking given the second-plane behavior lines up.

adamshidler-byte24d ago

I can reproduce what appears to be the same issue on a desktop SteamOS system with a discrete RDNA4 GPU.

Host

  • SteamOS 3.8.28
  • Build: 20260922.1
  • Kernel: 6.18.50-valve2-1-neptune-618-gc7289a96b14d
  • Gamescope: 3.16.23.6
  • CPU: AMD Ryzen 5 7600X
  • GPU: AMD Radeon RX 9060 XT 16 GB (RDNA4 / Navi 44)
  • Mesa in Sunshine Flatpak runtime: 26.2.2
  • Sunshine: 2026.914.233613
  • Sunshine package: system Flatpak
  • Sunshine is launched as root by Decky Sunshine
  • Decky Sunshine: 2025.10.27-dddf365
  • Capture method: KMS
  • Encoder selected: Vulkan
  • Physical output: RX 9060 XT DisplayPort -> active DP-to-HDMI adapter -> 3840x2160@60 TV
  • Connector: DP-1
  • Client: Steam Deck OLED running Moonlight Qt 6.1.0 AppImage

system_tray = disabled is set because Sunshine is running in SteamOS Gaming Mode.

Symptom

Moonlight connects successfully, audio works, and controller input works, but the video is completely black.

Sunshine correctly discovers the display:

Screencasting with KMS
Mapped 'DP-1' to kmsgrab monitor index 2
Found monitor for DRM screencasting
Found connector ID [120]
Found cursor plane [103]
Creating encoder [h264_vulkan]

Immediately afterward it continuously logs:

Warning: Couldn't get drm fb for plane [0]: Permission denied

During the failure:

  • /sys/class/drm/card0-DP-1/status remains connected
  • Sunshine remains running
  • Sunshine Web UI still responds
  • Audio continues
  • Moonlight controller input continues
  • I found no corresponding amdgpu reset/hang in the kernel log

Reproduction

I reproduced the failure at least twice in one testing session by disconnecting Moonlight and reconnecting.

One particularly useful reproduction:

  1. Start Crab Champions and confirm video/audio/input are working.
  2. Disconnect Moonlight without exiting the game.
  3. Wait about 30 seconds.
  4. Reconnect to Sunshine.
  5. Video is black, while game audio continues and controller input still reaches the host.

The game was actively rendering, so this does not require the Steam UI to be idle or the display to dim.

I have also seen the condition after the physical TV was turned off and after leaving the Steam UI idle, although DP-1 can remain reported as connected.

Recovery tests

I tested several recovery methods while the game was still running.

Restarting Sunshine / Decky:

sudo systemctl restart plugin_loader.service

does NOT restore video. Sunshine reconnects, audio/input work, but the stream remains black.

Changing GAMESCOPE_COMPOSITE_FORCE at runtime:

DISPLAY=:0 xprop -root \
  -f GAMESCOPE_COMPOSITE_FORCE 32c \
  -set GAMESCOPE_COMPOSITE_FORCE 1

and then setting it back to 0 did NOT restore an already-black stream.

Changing:

GAMESCOPE_DISPLAY_MODE_NUDGE

did not restore video.

Switching from tty1 -> tty2 -> tty1 with chvt also did not restore video.

The only reliable recovery I have found is:

systemctl --user restart gamescope-session.target

After Gamescope restarts, Moonlight video works again, even with the physical TV still powered off.

Unfortunately this terminates the running game, so it is not a usable workaround during gameplay.

Additional observation

During one black reconnect I briefly saw the Steam notification that the Sunshine virtual "Xbox Series" controller had connected, then the video returned to black. Audio and input continued afterward.

I have a captured failure log from immediately before recovery and can provide it if useful.

The log repeatedly shows Couldn't get drm fb for plane [0] beginning immediately after Sunshine successfully maps DP-1 and starts the stream.

This appears very close to the behavior described in this issue, but on a desktop RDNA4 GPU rather than a Steam Deck / Van Gogh GPU.

stream-black-2026-10-05_20-16-28.txt

MMagTech21d ago

Same here on a different AMD GPU, in case it helps: Radeon 890M (Strix Point, amdgpu, kernel 7.2), Sunshine 2026.914.233613 (Fedora 44 RPM, capture = kms, VA-API), gamescope 3.16.28 in Steam mode (gamescope-session-plus), HDMI to an LG C1.

  • Steam's menus stream fine: Mapped 'HDMI-A-1' to kmsgrab monitor index 0.
  • As soon as a game starts (two games tried, Proton), Sunshine logs Mapped 'HDMI-A-1' to kmsgrab monitor index 1 and the stream goes black with audio. A gamescopectl screenshot at the same moment shows the game drawn normally.
  • Turning on composite_force mid-stream doesn't bring it back: Sunshine then logs Couldn't get drm fb for plane [0]: Invalid argument every frame until the client reconnects.
  • With composite_force 1 set before the client connects, the game streams: index 0, no errors.

From kms_display_names() in 2026.914, count goes up once per active non-cursor plane and each CRTC's monitor_index is set to the last plane found on it. So once gamescope scans out on two planes, the connector maps to the second plane (the overlay) instead of the screen. refresh() then returns capture_e::timeout when that plane loses its framebuffer, so it never re-enumerates.

What would fix it for us:

  1. Capture per CRTC and blend every plane on it, the way the cursor plane is already handled.
  2. At minimum, return reinit instead of timeout when the tracked plane has no framebuffer, so a stream recovers when the planes change.

The 2026.1008 pre-releases still have the same code. For now we force composition while a stream is open. Happy to test a build on this hardware.

adamshidler-byte21d ago

Additional successful workaround test on SteamOS Gaming Mode with an AMD Radeon RX 9060 XT (RADV GFX1200).

Environment

  • OS: SteamOS, Gamescope Gaming Mode
  • GPU: AMD Radeon RX 9060 XT
  • Sunshine: Flatpak installation launched through Decky Sunshine
  • Capture: KMS
  • Encoder: Vulkan
  • Client: Moonlight over Tailscale

Workaround tested

Before connecting Moonlight, I set the Gamescope X11 root property:

DISPLAY=:0 xprop -root -f GAMESCOPE_COMPOSITE_FORCE 32c -set GAMESCOPE_COMPOSITE_FORCE 1

Confirmed the property changed from "CARDINAL = 0" to "CARDINAL = 1".

Results

Successful video streaming across:

  • Initial connection and multiple reconnections
  • Stopping and restarting Moonlight sessions
  • Moonlight client crash and reconnection
  • Game launches, gameplay, exits, and relaunches
  • Disconnecting/reconnecting while a game was running
  • Switching to Desktop Mode and back to Gaming Mode
  • Launching another game after returning to Gaming Mode

No black screens were observed during this test sequence.

Sunshine KMS logs

Eight KMS capture initializations between 11:35 and 12:28 all mapped the display to index 0:

Info: Screencasting with KMS Info: Mapped 'DP-1' to kmsgrab monitor index 0

No index 1 selections appeared in the retrieved logs.

One brief sequence of "Couldn't get drm fb for plane [0]: Permission denied" occurred during a session transition. Sunshine subsequently logged "Reinitializing capture after HDR metadata change", resumed KMS capture, and mapped DP-1 to index 0.

Additional observation

After switching between Desktop Mode and Gaming Mode, the property was observed back at "CARDINAL = 0", yet streaming remained functional and later capture initializations continued selecting index 0.

This suggests the initial forced-composition setting may help avoid the problematic capture-plane selection, but it doesn't establish whether composition must remain enabled continuously. A controlled A/B test is still needed.

These results are consistent with the DRM-plane selection issue described by @MMagTech. Happy to provide additional diagnostics or test a proposed fix.

b3nk314d ago

Adding a data point, a working persistent workaround, and what I believe is the underlying cause in Sunshine.

Setup: ROG Ally (Z1 Extreme, internal eDP-1), Bazzite bazzite-deck:stable 44.20261012.1 (gamescope 3.16.31), Homebrew Sunshine 2026.914.233613, capture = kms, encoder = vaapi. Black screen with audio on every Game Mode connect, Couldn't get drm fb for plane [0]: Invalid argument at ~60/s, and the QAM is visible only while it's open.

Cause (from reading src/platform/linux/kmsgrab.cpp on master): the KMS monitor list has one entry per non-cursor plane that has a framebuffer, and every plane on a CRTC gets the same connector name (eDP-1). When the CRTC-to-monitor map is built, monitor_index is overwritten for each plane, so a connector name resolves to the last plane on that CRTC. When gamescope puts the Steam overlay on its own plane, eDP-1 resolves to that overlay plane, which only has content while the overlay or QAM is visible. My logs show Mapped 'eDP-1' to kmsgrab monitor index 1 (sometimes 2) on every failing connect, and index 0 on every working one. Bazzite's gamescope build attaches the overlay plane permanently (plane dumps in ublue-os/bazzite#5828), which is why it fails every time there and only intermittently on SteamOS.

  • Setting output_name = 0 does not help: it gets mapped back to the name eDP-1, which every plane shares.
  • A possible fix would be to prefer the primary plane (or the lowest zpos) for a connector rather than the last one, or to composite all planes on the CRTC.

Workaround that works on every connect: force composition before capture starts, using an app prep command. Toggling it mid-stream doesn't help.

"prep-cmd": [
  {
    "do": "sh -c \"DISPLAY=:0 xprop -root -f GAMESCOPE_COMPOSITE_FORCE 32c -set GAMESCOPE_COMPOSITE_FORCE 1; sleep 1\"",
    "undo": "sh -c \"DISPLAY=:0 xprop -root -f GAMESCOPE_COMPOSITE_FORCE 32c -set GAMESCOPE_COMPOSITE_FORCE 0\""
  }
]

With this in place, the log shows Mapped 'eDP-1' to kmsgrab monitor index 0 and no capture errors. DISPLAY=:0 is needed when Sunshine runs as a user service without DISPLAY.