Repository navigation
Description
Is there an existing issue for this?
- I have searched the existing issues
Are you using any gamescope patches or a forked version of gamescope?
- The issue occurs on upstream gamescope without any modifications
Current Behavior
A Vulkan app's FIFO swapchain (X11 window, one present per frame) stops blocking under the WSI layer when SteamVR's vrclient has created its own VkInstance (steamvr_vrclient_interop) in the app's process before the app's own. Here that happens because the app starts OpenXR through SteamVR. The headset isn't worn and audio is off, so the window's present is the app's only pacing, and the app runs several times too fast. It happens in the normal SteamOS session (OpenVR backend, 120 Hz) and under --backend headless (60 Hz).
| Run | Backend | Change | Wall | App play time | App present rate |
|---|---|---|---|---|---|
| A | headless, 60 Hz | OpenXR on (vrclient instance first) | 75.5 s | 8:56 | 417 Hz |
| B | headless | A + ENABLE_GAMESCOPE_WSI=0 DISABLE_GAMESCOPE_WSI=1 |
75.2 s | 1:12 | 60 Hz |
| C | headless | A + MESA_VK_WSI_PRESENT_MODE=fifo |
75.4 s | 1:12 | 60 Hz |
| D | headless | OpenXR off (no vrclient instance), layer on | 74.5 s | 1:11 | 59 Hz |
| E | OpenVR (SteamOS session), 120 Hz | OpenXR on, launched from Steam | 75.3 s | 4:38 | 260 Hz |
| F | OpenVR, 120 Hz | E + MESA_VK_WSI_PRESENT_MODE=fifo |
75.2 s | 2:18 | 117 Hz |
App play time counts the app's 60 fps content frames, so in F it reads 2x because presents block at the session's 120 Hz; the present rate is the measure that matters.
The layer gives Mesa MAILBOX and does FIFO itself (gamescope_swapchain_set_present_mode), so it looks like the layer's own FIFO stops pacing; forcing FIFO in Mesa works around it.
A second instance alone is not enough. A minimal program that first creates an instance named steamvr_vrclient_interop (with or without a device and a thread of its own), then presents FIFO, blocks at 60 Hz under the same 3.16.30 headless gamescope on the Frame, directly and inside Steam Linux Runtime 4, with the layer inserted for both instances. Whatever else SteamVR's client does in the process seems to be needed.
Steps To Reproduce
- Steam Frame, SteamVR running, headset not worn. Run RetroArch with
video_driver = "vulkan",video_openxr_enable = "true",audio_enable = "false"and vsync on (FIFO), with a Vulkan core (Flycast here), either:- from a non-Steam shortcut in the normal SteamOS session (OpenVR backend), launching
SteamLinuxRuntime_4-arm64/run -- retroarch --config retroarch.cfg -L flycast_libretro.so game.chd, or PRESSURE_VESSEL_FILESYSTEMS_RO=/opt/steamvr gamescope --backend headless -W 1280 -H 720 -- SteamLinuxRuntime_4-arm64/run -- retroarch --config retroarch.cfg -L flycast_libretro.so game.chd
- from a non-Steam shortcut in the normal SteamOS session (OpenVR backend), launching
- After ~75 s, compare the wall time with RetroArch's "Content ran for" line, or read its present rate from the log.
- Repeat with the env changes from the table.
Hardware information
- Distro: SteamOS 0.6.0 (Steam Frame), build 20261103.6153902
- CPU: 8 cores, Cortex-X4 + Cortex-A720 + Cortex-A520
- GPU: Adreno 750 (Turnip)
- Driver Version: Mesa 26.3.0-devel (git-6a15f899c8); Vulkan loader 1.4.309Software information
- Desktop environment: SteamOS session (and none, for headless)
- Session type: gamescope OpenVR backend (SteamOS session) and `--backend headless`, app on Xwayland
- Gamescope version: 3.16.30 (SteamOS package 3.16.30-4), layer `VK_LAYER_FROG_gamescope_wsi_aarch64`
- Gamescope launch command(s): the SteamOS session's own; and see Steps To Reproduce for headless. SteamVR 2.18.4, Steam Linux Runtime 4.0 arm64Which gamescope backends have the issue you are reporting?
- Wayland (default for nested gamescope)
- DRM (default for embedded gamescope, i.e. gamescope-session)
- SDL
- OpenVR
Logging, screenshots, or anything else
Also --backend headless, which isn't in the backend list.
Layer output in run A (VK_LOADER_DEBUG=layer); the layer is inserted for both instances, and run E shows the same pair:
[Gamescope WSI] Application info:
pApplicationName: steamvr_vrclient_interop
[Gamescope WSI] Executable name: retroarch
ATTENTION: default value of option vk_wsi_force_swapchain_to_current_extent overridden by environment.
[Gamescope WSI] Application info:
pApplicationName: Flycast
[Gamescope WSI] Creating swapchain for xid: 0x400002 - oldSwapchain: (nil) - provided minImageCount: 3 - minImageCount: 3 - format: VK_FORMAT_B8G8R8A8_UNORM - colorspace: VK_COLOR_SPACE_SRGB_NONLINEAR_KHR - flip: true
[Gamescope WSI] Swapchain received new refresh cycle: 16.67ms
Run D shows only the Flycast instance and no ATTENTION line; its swapchain lines are the same.
Not tested: gamescope newer than 3.16.30 (a minimal two-instance program doesn't reproduce on 3.17.0 on desktop RADV either, but that doesn't involve SteamVR).
Full stderr (loader + layer) and RetroArch logs for each run are attached; the letters match the table.
A-headless-openxr-on.txt
B-headless-wsi-layer-off.txt
C-headless-mesa-fifo.txt
D-headless-openxr-off.txt
E-openvr-session-openxr-on.txt
F-openvr-session-mesa-fifo.txt
Activity
matte-schwartz commentedon Nov 7, 2026
can you try #2544?
Davey-Hughes commentedon Nov 8, 2026
Thanks! #2544 fixes the headless free-run from this issue.
I built gamescope from the PR head vs a base with only the
import_commithunk reverted (same WSI layer), ran run A (Flycast,--backend headless, SteamVR up, headset off, ~75s):Twice each, trigger confirmed both times (
steamvr_vrclient_interopinstance first, thevk_wsi_force_swapchain_to_current_extentline).The only thing I couldn't test is the
!g_bVRStandbyscene-app path (openvr backend / run E) - that needs the PR build running as the session compositor, which I didn't want to do on my Frame in case I put it in a state that's hard for me to recover. If you want to check it: run a FIFO scene app (e.g. RetroArch + Flycast, audio off) in a SteamVR session on the PR, let the headset go to standby (actual standby, not just idle), and it should pace instead of free-running.If you want to test the above and need some more specific instructions lmk, but I assume you probably have something equivalent you prefer to test if you need.