Field report on 0.23.0: HDR not working, streaming from a SteamOS host to a Deck OLED. Confirmed on-glass as HDR→SDR in the stats overlay.
The host was clean — the host log proves PQ capture (transfer_function=14, color_primaries=7), HEVC Main10, SPS VUI CICP 9/16/9, HDR10 metadata sent, and the client advertising video_caps=59. The break was entirely in client-side presentation.
Root cause
gamescope's WSI layer decides whether to engage from one signal: isRunningUnderGamescope() reads GAMESCOPE_WAYLAND_DISPLAY and nothing else. flatpak does not forward host env into the sandbox, so it arrived unset, the layer's CreateInstance early-returned before creating a GamescopeInstance, no gamescope surface was ever made, and the HDR10/ST.2084 formats were never appended.
The trap: the layer still loads and still logs its generic bits in that state (Forcing on VK_EXT_swapchain_maintenance1, swapchain destroys), so it reads as working. The discriminator is the [Gamescope WSI] Surface state: block — absent means the bypass never engaged.
The three settings already in the manifest all sit downstream of that gate and bought nothing without it. The old comment asserted "THREE things are needed"; that was wrong, and is why this survived a previous on-glass session.
Measured on a Deck OLED (Galileo, SteamOS 3.8.16), client --browse
hdr formats exposed to client: true → Some(A2B10G10R10_UNORM_PACK32, HDR10_ST2084_EXT)
set
1
removed
zero [Gamescope WSI] lines → None
DXVK_HDR was ruled out by measurement — the layer only force-sets it to 0 for clients it has already denied.
Second half: the layer is now vendored
That last row is the other half of the problem. The layer came from the flathub runtime extension org.freedesktop.Platform.VulkanLayer.gamescope, which every user had to install by hand — documented only in a comment in this file, so in practice nobody did.
The layer is genuinely required, not legacy. On this Deck the gamescope-0 socket advertises gamescope_swapchain_factory_v2 but notwp_color_manager_v1, with HDR both off and on — so Mesa's Wayland WSI has no colour-management protocol to negotiate HDR10 through. (The gamescope binary contains wp_color_manager_v1 strings but never advertises it; strings misleads here.)
Vendored rather than declared via add-extensions autodownload, deliberately:
the extension is 94 MB of whole-gamescope to deliver one 4 MB .so
its layer JSON hardcodes a /usrlibrary_path that an app-scoped extension (mounted under /app) would not satisfy
it would make flathub a hard install-time dependency of an app we self-host on flatpak.unom.io
Build notes:
-Denable_gamescope=false skips subdir('src') and every compositor dependency (wlroots, SDL2, libliftoff, …) — only protocol/ and layer/ build
buildsystem: simple rather than meson, because glm and stb ship no meson.build of their own; the wraps' patch_directory supplies it, and without that copy configure dies with "Subproject exists but has no meson.build file"
meson generates the layer JSON from prefix+libdir, so it self-writes library_path: /app/lib/… into /app/share/vulkan/implicit_layer.d, already covered by XDG_DATA_DIRS. VK_ADD_IMPLICIT_LAYER_PATH is therefore dropped — keeping it would risk double-loading two same-named layers for anyone who still has the extension installed
pinned to the same gamescope rev as packaging/gamescope/PKGBUILD (8c676c39), so the client's layer and the host's punktfunk-gamescope come from one tree — bump both together
Verification
builds fully offline (--wrap-mode=nodownload) in org.gnome.Sdk//50 on the Deck
the resulting .so, staged against the Deck's system gamescope 3.16.23.4, drives it to hdr formats exposed to client: true with hdr10_format=Some(A2B10G10R10_UNORM_PACK32, HDR10_ST2084_EXT) — so our build works, not just flathub's
Not verified: the module has never been through a real flatpak-builder run — only meson-in-SDK. flatpak.yml publishes canary on manifest push, so that is the gate this needs.
Known remaining gate, not fixable here
gamescope's hdr_enabled convar (Steam's HDR display setting) drives the GAMESCOPE_HDR_OUTPUT_FEEDBACK X property the layer reads. With it off, no app on the Deck gets HDR, ours included. So this makes HDR work on a plain install provided HDR is on in Steam's Display settings. Flipping a user's display setting is not something a packaging change should do.
Still undocumented in docs-site (hdr.md, steam-deck.md, troubleshooting) — worth a follow-up.
Manifest-only; no client code changes.
Field report on 0.23.0: HDR not working, streaming from a SteamOS host to a Deck OLED. Confirmed on-glass as `HDR→SDR` in the stats overlay.
The host was clean — the host log proves PQ capture (`transfer_function=14`, `color_primaries=7`), HEVC Main10, SPS VUI CICP 9/16/9, HDR10 metadata sent, and the client advertising `video_caps=59`. The break was entirely in client-side presentation.
## Root cause
gamescope's WSI layer decides whether to engage from one signal: `isRunningUnderGamescope()` reads `GAMESCOPE_WAYLAND_DISPLAY` and nothing else. flatpak does not forward host env into the sandbox, so it arrived unset, the layer's `CreateInstance` early-returned before creating a `GamescopeInstance`, no gamescope surface was ever made, and the HDR10/ST.2084 formats were never appended.
**The trap:** the layer still loads and still logs its generic bits in that state (`Forcing on VK_EXT_swapchain_maintenance1`, swapchain destroys), so it reads as working. The discriminator is the `[Gamescope WSI] Surface state:` block — absent means the bypass never engaged.
The three settings already in the manifest all sit downstream of that gate and bought nothing without it. The old comment asserted "THREE things are needed"; that was wrong, and is why this survived a previous on-glass session.
## Measured on a Deck OLED (Galileo, SteamOS 3.8.16), client `--browse`
Reading `pf_presenter::vk::setup` "swapchain config":
| `GAMESCOPE_WAYLAND_DISPLAY` | `hdr_enabled` | layer ext | result |
|---|---|---|---|
| unset | 0 | installed | no `Surface state` block at all → `hdr10_format=None` |
| set | 0 | installed | `server hdr output enabled: false` → `None` |
| **set** | **1** | installed | `hdr formats exposed to client: true` → **`Some(A2B10G10R10_UNORM_PACK32, HDR10_ST2084_EXT)`** |
| set | 1 | **removed** | zero `[Gamescope WSI]` lines → `None` |
`DXVK_HDR` was ruled out by measurement — the layer only force-sets it to `0` for clients it has already denied.
## Second half: the layer is now vendored
That last row is the other half of the problem. The layer came from the flathub runtime extension `org.freedesktop.Platform.VulkanLayer.gamescope`, which every user had to install **by hand** — documented only in a comment in this file, so in practice nobody did.
The layer is genuinely required, not legacy. On this Deck the `gamescope-0` socket advertises `gamescope_swapchain_factory_v2` but **not** `wp_color_manager_v1`, with HDR both off and on — so Mesa's Wayland WSI has no colour-management protocol to negotiate HDR10 through. (The gamescope binary contains `wp_color_manager_v1` strings but never advertises it; `strings` misleads here.)
Vendored rather than declared via `add-extensions` autodownload, deliberately:
- the extension is 94 MB of whole-gamescope to deliver one 4 MB `.so`
- its layer JSON hardcodes a `/usr` `library_path` that an app-scoped extension (mounted under `/app`) would not satisfy
- it would make flathub a hard install-time dependency of an app we self-host on flatpak.unom.io
Build notes:
- `-Denable_gamescope=false` skips `subdir('src')` and every compositor dependency (wlroots, SDL2, libliftoff, …) — only `protocol/` and `layer/` build
- `buildsystem: simple` rather than `meson`, because glm and stb ship no `meson.build` of their own; the wraps' `patch_directory` supplies it, and without that copy configure dies with *"Subproject exists but has no meson.build file"*
- meson generates the layer JSON from prefix+libdir, so it self-writes `library_path: /app/lib/…` into `/app/share/vulkan/implicit_layer.d`, already covered by `XDG_DATA_DIRS`. `VK_ADD_IMPLICIT_LAYER_PATH` is therefore dropped — keeping it would risk double-loading two same-named layers for anyone who still has the extension installed
- pinned to the same gamescope rev as `packaging/gamescope/PKGBUILD` (`8c676c39`), so the client's layer and the host's `punktfunk-gamescope` come from one tree — bump both together
## Verification
- builds fully offline (`--wrap-mode=nodownload`) in `org.gnome.Sdk//50` on the Deck
- the resulting `.so`, staged against the Deck's **system** gamescope 3.16.23.4, drives it to `hdr formats exposed to client: true` with `hdr10_format=Some(A2B10G10R10_UNORM_PACK32, HDR10_ST2084_EXT)` — so our build works, not just flathub's
**Not verified:** the module has never been through a real `flatpak-builder` run — only meson-in-SDK. `flatpak.yml` publishes canary on manifest push, so that is the gate this needs.
## Known remaining gate, not fixable here
gamescope's `hdr_enabled` convar (Steam's HDR display setting) drives the `GAMESCOPE_HDR_OUTPUT_FEEDBACK` X property the layer reads. With it off, no app on the Deck gets HDR, ours included. So this makes HDR work on a plain install **provided HDR is on in Steam's Display settings**. Flipping a user's display setting is not something a packaging change should do.
Still undocumented in docs-site (`hdr.md`, `steam-deck.md`, troubleshooting) — worth a follow-up.
Manifest-only; no client code changes.
The gamescope WSI layer decides whether to engage from one signal:
isRunningUnderGamescope() reads $GAMESCOPE_WAYLAND_DISPLAY and nothing
else. flatpak does not forward host env into the sandbox, so it arrived
unset and the layer's CreateInstance early-returned before creating a
GamescopeInstance — no gamescope surface, so the HDR10/ST.2084 formats
were never appended and the surface stayed SDR.
The layer still loads and still logs its generic bits in that state, so
it reads as working. It is not: the three settings already here (layer
search path, ENABLE_GAMESCOPE_WSI, the socket bind) all sit downstream
of this gate and buy nothing without it.
Measured on a Deck OLED (Galileo, SteamOS 3.8.16), client --browse,
reading "swapchain config":
unset -> no [Gamescope WSI] Surface state block, None
set, hdr_enabled=0 -> server hdr output enabled: false, None
set, hdr_enabled=1 -> hdr formats exposed to client: true,
Some(A2B10G10R10_UNORM_PACK32, HDR10_ST2084_EXT)
Matches the field report of "HDR->SDR" in the stats overlay on a
correct HDR host. DXVK_HDR was ruled out by measurement. The remaining
gate (gamescope's hdr_enabled convar = Steam's HDR display setting) is
a user-side step, not a packaging one.
HDR on a Deck needed a manual second step nobody took:
flatpak install --user flathub org.freedesktop.Platform.VulkanLayer.gamescope//25.08
documented only in a comment in this file. Build the layer ourselves
instead, so a plain `flatpak install` is all it takes.
The layer is genuinely required, not legacy. Measured on SteamOS 3.8.16
(gamescope 3.16.23.4): the gamescope-0 socket advertises
gamescope_swapchain_factory_v2 but NOT wp_color_manager_v1, with HDR both
off and on — so Mesa's Wayland WSI has no colour-management protocol to
negotiate HDR10 through, and this layer is the only thing that can append
the ST.2084 surface formats. Removing the extension gives zero
[Gamescope WSI] lines and hdr10_format=None.
Vendored rather than declared via add-extensions autodownload: the
extension is 94 MB of whole-gamescope for one 4 MB .so, its layer JSON
hardcodes a /usr library_path that an app-scoped extension mounted under
/app would not satisfy, and it would make flathub a hard install-time
dependency of an app we self-host on flatpak.unom.io.
enable_gamescope=false skips subdir('src') and every compositor
dependency, so only protocol/ and layer/ build. buildsystem is simple
rather than meson because glm and stb ship no meson.build of their own -
the wraps' patch_directory supplies it, and without that copy configure
dies with "Subproject exists but has no meson.build file".
meson generates the layer JSON from prefix+libdir, so it self-writes
library_path=/app/lib/... into /app/share/vulkan/implicit_layer.d, which
XDG_DATA_DIRS already covers. VK_ADD_IMPLICIT_LAYER_PATH is therefore
dropped - keeping it would also risk double-loading two same-named layers
for anyone who still has the flathub extension installed.
Pinned to the same gamescope rev as packaging/gamescope/PKGBUILD so the
client's layer and the host's punktfunk-gamescope come from one tree.
Verified on a Deck OLED: builds offline (--wrap-mode=nodownload) in
org.gnome.Sdk//50, and the resulting .so drives the Deck's system
gamescope to "hdr formats exposed to client: true" with
hdr10_format=Some(A2B10G10R10_UNORM_PACK32, HDR10_ST2084_EXT).
Still user-side, and not fixable in packaging: gamescope's hdr_enabled
convar (Steam's HDR display setting) must be on.
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Field report on 0.23.0: HDR not working, streaming from a SteamOS host to a Deck OLED. Confirmed on-glass as
HDR→SDRin the stats overlay.The host was clean — the host log proves PQ capture (
transfer_function=14,color_primaries=7), HEVC Main10, SPS VUI CICP 9/16/9, HDR10 metadata sent, and the client advertisingvideo_caps=59. The break was entirely in client-side presentation.Root cause
gamescope's WSI layer decides whether to engage from one signal:
isRunningUnderGamescope()readsGAMESCOPE_WAYLAND_DISPLAYand nothing else. flatpak does not forward host env into the sandbox, so it arrived unset, the layer'sCreateInstanceearly-returned before creating aGamescopeInstance, no gamescope surface was ever made, and the HDR10/ST.2084 formats were never appended.The trap: the layer still loads and still logs its generic bits in that state (
Forcing on VK_EXT_swapchain_maintenance1, swapchain destroys), so it reads as working. The discriminator is the[Gamescope WSI] Surface state:block — absent means the bypass never engaged.The three settings already in the manifest all sit downstream of that gate and bought nothing without it. The old comment asserted "THREE things are needed"; that was wrong, and is why this survived a previous on-glass session.
Measured on a Deck OLED (Galileo, SteamOS 3.8.16), client
--browseReading
pf_presenter::vk::setup"swapchain config":GAMESCOPE_WAYLAND_DISPLAYhdr_enabledSurface stateblock at all →hdr10_format=Noneserver hdr output enabled: false→Nonehdr formats exposed to client: true→Some(A2B10G10R10_UNORM_PACK32, HDR10_ST2084_EXT)[Gamescope WSI]lines →NoneDXVK_HDRwas ruled out by measurement — the layer only force-sets it to0for clients it has already denied.Second half: the layer is now vendored
That last row is the other half of the problem. The layer came from the flathub runtime extension
org.freedesktop.Platform.VulkanLayer.gamescope, which every user had to install by hand — documented only in a comment in this file, so in practice nobody did.The layer is genuinely required, not legacy. On this Deck the
gamescope-0socket advertisesgamescope_swapchain_factory_v2but notwp_color_manager_v1, with HDR both off and on — so Mesa's Wayland WSI has no colour-management protocol to negotiate HDR10 through. (The gamescope binary containswp_color_manager_v1strings but never advertises it;stringsmisleads here.)Vendored rather than declared via
add-extensionsautodownload, deliberately:.so/usrlibrary_paththat an app-scoped extension (mounted under/app) would not satisfyBuild notes:
-Denable_gamescope=falseskipssubdir('src')and every compositor dependency (wlroots, SDL2, libliftoff, …) — onlyprotocol/andlayer/buildbuildsystem: simplerather thanmeson, because glm and stb ship nomeson.buildof their own; the wraps'patch_directorysupplies it, and without that copy configure dies with "Subproject exists but has no meson.build file"library_path: /app/lib/…into/app/share/vulkan/implicit_layer.d, already covered byXDG_DATA_DIRS.VK_ADD_IMPLICIT_LAYER_PATHis therefore dropped — keeping it would risk double-loading two same-named layers for anyone who still has the extension installedpackaging/gamescope/PKGBUILD(8c676c39), so the client's layer and the host'spunktfunk-gamescopecome from one tree — bump both togetherVerification
--wrap-mode=nodownload) inorg.gnome.Sdk//50on the Deck.so, staged against the Deck's system gamescope 3.16.23.4, drives it tohdr formats exposed to client: truewithhdr10_format=Some(A2B10G10R10_UNORM_PACK32, HDR10_ST2084_EXT)— so our build works, not just flathub'sNot verified: the module has never been through a real
flatpak-builderrun — only meson-in-SDK.flatpak.ymlpublishes canary on manifest push, so that is the gate this needs.Known remaining gate, not fixable here
gamescope's
hdr_enabledconvar (Steam's HDR display setting) drives theGAMESCOPE_HDR_OUTPUT_FEEDBACKX property the layer reads. With it off, no app on the Deck gets HDR, ours included. So this makes HDR work on a plain install provided HDR is on in Steam's Display settings. Flipping a user's display setting is not something a packaging change should do.Still undocumented in docs-site (
hdr.md,steam-deck.md, troubleshooting) — worth a follow-up.Manifest-only; no client code changes.
The gamescope WSI layer decides whether to engage from one signal: isRunningUnderGamescope() reads $GAMESCOPE_WAYLAND_DISPLAY and nothing else. flatpak does not forward host env into the sandbox, so it arrived unset and the layer's CreateInstance early-returned before creating a GamescopeInstance — no gamescope surface, so the HDR10/ST.2084 formats were never appended and the surface stayed SDR. The layer still loads and still logs its generic bits in that state, so it reads as working. It is not: the three settings already here (layer search path, ENABLE_GAMESCOPE_WSI, the socket bind) all sit downstream of this gate and buy nothing without it. Measured on a Deck OLED (Galileo, SteamOS 3.8.16), client --browse, reading "swapchain config": unset -> no [Gamescope WSI] Surface state block, None set, hdr_enabled=0 -> server hdr output enabled: false, None set, hdr_enabled=1 -> hdr formats exposed to client: true, Some(A2B10G10R10_UNORM_PACK32, HDR10_ST2084_EXT) Matches the field report of "HDR->SDR" in the stats overlay on a correct HDR host. DXVK_HDR was ruled out by measurement. The remaining gate (gamescope's hdr_enabled convar = Steam's HDR display setting) is a user-side step, not a packaging one.HDR on a Deck needed a manual second step nobody took: flatpak install --user flathub org.freedesktop.Platform.VulkanLayer.gamescope//25.08 documented only in a comment in this file. Build the layer ourselves instead, so a plain `flatpak install` is all it takes. The layer is genuinely required, not legacy. Measured on SteamOS 3.8.16 (gamescope 3.16.23.4): the gamescope-0 socket advertises gamescope_swapchain_factory_v2 but NOT wp_color_manager_v1, with HDR both off and on — so Mesa's Wayland WSI has no colour-management protocol to negotiate HDR10 through, and this layer is the only thing that can append the ST.2084 surface formats. Removing the extension gives zero [Gamescope WSI] lines and hdr10_format=None. Vendored rather than declared via add-extensions autodownload: the extension is 94 MB of whole-gamescope for one 4 MB .so, its layer JSON hardcodes a /usr library_path that an app-scoped extension mounted under /app would not satisfy, and it would make flathub a hard install-time dependency of an app we self-host on flatpak.unom.io. enable_gamescope=false skips subdir('src') and every compositor dependency, so only protocol/ and layer/ build. buildsystem is simple rather than meson because glm and stb ship no meson.build of their own - the wraps' patch_directory supplies it, and without that copy configure dies with "Subproject exists but has no meson.build file". meson generates the layer JSON from prefix+libdir, so it self-writes library_path=/app/lib/... into /app/share/vulkan/implicit_layer.d, which XDG_DATA_DIRS already covers. VK_ADD_IMPLICIT_LAYER_PATH is therefore dropped - keeping it would also risk double-loading two same-named layers for anyone who still has the flathub extension installed. Pinned to the same gamescope rev as packaging/gamescope/PKGBUILD so the client's layer and the host's punktfunk-gamescope come from one tree. Verified on a Deck OLED: builds offline (--wrap-mode=nodownload) in org.gnome.Sdk//50, and the resulting .so drives the Deck's system gamescope to "hdr formats exposed to client: true" with hdr10_format=Some(A2B10G10R10_UNORM_PACK32, HDR10_ST2084_EXT). Still user-side, and not fixable in packaging: gamescope's hdr_enabled convar (Steam's HDR display setting) must be on.