From 86f4f950ba01f9305ff6175ba068368e8b0835cf Mon Sep 17 00:00:00 2001 From: enricobuehler Date: Tue, 28 Jul 2026 14:43:22 +0200 Subject: [PATCH] docs: the gamescope path is no longer 8-bit-only MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Five pages asserted "gamescope's capture output is 8-bit" as a flat fact. It is a fact about the STOCK binary, so say that instead, and say what to install to change it: a new "HDR on gamescope" section covering the extra package, the two knobs, what has to line up for a session to go HDR at all, and the two things that surprise people — SDR content rides the same PQ stream at `--hdr-sdr-content-nits`, and AMD/Intel HDR sessions currently lose the composited pointer (the encode path that carries 10-bit is the one that cannot blend it). The roadmap's "parked / blocked" entry keeps the half that is still blocked (Mutter's `RecordVirtual` is SDR-only through the GNOME 51 dev branch) and drops the half that no longer is. --- docs-site/content/docs/bazzite.md | 5 ++- docs-site/content/docs/configuration.md | 5 ++- docs-site/content/docs/gamescope.md | 55 ++++++++++++++++++++++++- docs-site/content/docs/roadmap.md | 17 ++++---- docs-site/content/docs/status.md | 15 ++++--- docs/releases/v0.21.0.md | 21 ++++++++++ 6 files changed, 100 insertions(+), 18 deletions(-) diff --git a/docs-site/content/docs/bazzite.md b/docs-site/content/docs/bazzite.md index 82d0e0d6..3ae4dc44 100644 --- a/docs-site/content/docs/bazzite.md +++ b/docs-site/content/docs/bazzite.md @@ -152,8 +152,9 @@ These apply to the **Gaming Mode (gamescope)** path; the KDE Desktop path is una current gamescope is fine; this only bites if you've pinned an old one. - **The mouse cursor isn't included in the captured image** — a gamescope limitation for now. (The KDE Desktop path renders the cursor normally.) -- **HDR isn't supported yet** on the gamescope path — gamescope's capture output is 8-bit. SDR streams - normally. +- **HDR needs the punktfunk gamescope build** (`punktfunk-gamescope`, shipped in the sysext) plus + `PUNKTFUNK_GAMESCOPE_HDR=1`. Without it sessions stream SDR — correctly, including SDR versions + of HDR games. Canonical list: [gamescope → Known limits](/docs/gamescope#known-limits). diff --git a/docs-site/content/docs/configuration.md b/docs-site/content/docs/configuration.md index e24ecd46..cc5b946f 100644 --- a/docs-site/content/docs/configuration.md +++ b/docs-site/content/docs/configuration.md @@ -59,6 +59,9 @@ the full picture (and [Bazzite](/docs/bazzite) for that distro's specifics). | `PUNKTFUNK_GAMESCOPE_SESSION` | `steam` | The host owns a `gamescope-session-plus` (Steam) session at the client's mode (headless appliance; no physical session running). | | `PUNKTFUNK_GAMESCOPE_NODE` | `auto` · node id | Discover + capture a **running** gamescope's PipeWire node at a fixed mode. Do **not** combine with `SESSION`. | | `PUNKTFUNK_GAMESCOPE_APP` | command | For an ad-hoc bare-gamescope session, the nested command to run (e.g. `vkcube`). | +| `PUNKTFUNK_GAMESCOPE_HDR` | `1` · `0` *(default off)* | Allow HDR (10-bit BT.2020 PQ) sessions on the gamescope backend. Needs the `punktfunk-gamescope` build — see [HDR on gamescope](/docs/gamescope#hdr-on-gamescope). Without it, or without the build, sessions stream SDR. | +| `PUNKTFUNK_GAMESCOPE_SDR_NITS` | e.g. `400` | On an HDR gamescope session, the luminance SDR content (desktop, Steam overlay, SDR games) is mapped to inside the PQ container. Unset = gamescope's own default of 400. | +| `PUNKTFUNK_GAMESCOPE_BIN` | path | Force a specific gamescope binary for the sessions the host spawns. Unset = prefer `punktfunk-gamescope` on `PATH`, then `gamescope`. | | `PUNKTFUNK_SESSION_WATCH` | `1` · `0` | Follow a Gaming ↔ Desktop switch **mid-stream** (rebuild the backend in place, no reconnect). **On by default** on Bazzite/SteamOS; set `0` to disable. | ## Compositor-specific (Linux) @@ -89,7 +92,7 @@ See your desktop page ([KDE](/docs/kde), [GNOME](/docs/gnome)) for when to set t | Setting | Values | Meaning | |---|---|---| | `PUNKTFUNK_FEC_PCT` | `N` (percent) | Forward-error-correction redundancy for lossy links (the default is sensible for a normal LAN). Higher = more loss-resilient, more bandwidth. | -| `PUNKTFUNK_10BIT` | `1` · `0` *(default on)* | HEVC Main10 / HDR. **On by default** — the host permits 10-bit; a session goes 10-bit only when the client advertises it (behind the client's HDR setting). Set `0` to force 8-bit. Windows host, plus the Linux **GNOME 50+ GameStream desktop mirror** (`PUNKTFUNK_VIDEO_SOURCE=portal`, mirrored monitor in HDR mode — check with `punktfunk-host hdr-probe`). Linux **virtual displays** (native protocol, GameStream default) stay 8-bit: Mutter's virtual-monitor screencast is SDR-only upstream. | +| `PUNKTFUNK_10BIT` | `1` · `0` *(default on)* | HEVC Main10 / HDR. **On by default** — the host permits 10-bit; a session goes 10-bit only when the client advertises it (behind the client's HDR setting). Set `0` to force 8-bit. Windows host, plus the Linux **GNOME 50+ GameStream desktop mirror** (`PUNKTFUNK_VIDEO_SOURCE=portal`, mirrored monitor in HDR mode — check with `punktfunk-host hdr-probe`). Also the Linux **gamescope** backend with the `punktfunk-gamescope` build + `PUNKTFUNK_GAMESCOPE_HDR=1`. The other Linux virtual displays (Mutter, KWin, wlroots) stay 8-bit: their virtual-monitor screencasts are SDR-only upstream. | | `PUNKTFUNK_444` | `1` · `0` *(default on)* | Full-chroma HEVC 4:4:4 (Range Extensions) — sharper text/desktop, no chroma loss. **On by default** on the host; the client's own 4:4:4 setting (default off) is the real switch. Set `0` to force 4:2:0. **punktfunk/1 native only** (Moonlight stays 4:2:0), HEVC-only, honored only when the client advertises 4:4:4 **and** the GPU supports it (probed; NVENC is the validated path — VAAPI/AMF/QSV decline). Independent of 10-bit. | | `PUNKTFUNK_CHACHA20` | `1` · `0` *(default on)* | ChaCha20-Poly1305 session encryption for clients without hardware AES (old ARM TVs, e.g. webOS), lifting their ~100 Mbps software-AES decrypt ceiling. **On by default** on the host; a session uses it only when the client requests it — everyone else stays on AES-GCM. Purely a performance choice (both ciphers are full-strength); set `0` to force AES-GCM for all sessions. | | `PUNKTFUNK_PYROWAVE_MAX_MBPS` | `N` (Mbps) | Cap the [PyroWave](/docs/pyrowave) Automatic bitrate pin, for a host on a link that the open-loop pin can outrun (e.g. 4:4:4 + HDR at 5120×1440@240 pins ~5.3 Gbps, over a 5GbE link). Unset = no cap. Only affects Automatic (bitrate `0`) PyroWave sessions; an explicit client bitrate bypasses it. | diff --git a/docs-site/content/docs/gamescope.md b/docs-site/content/docs/gamescope.md index b9a554fc..6c3493a7 100644 --- a/docs-site/content/docs/gamescope.md +++ b/docs-site/content/docs/gamescope.md @@ -113,6 +113,54 @@ a model. See the full [Configuration reference](/docs/configuration) for every o | `PUNKTFUNK_GAMESCOPE_NODE` | `auto` · node id | Discover and capture a **running** gamescope's PipeWire node at a fixed mode. Do **not** combine with `SESSION`. | | `PUNKTFUNK_GAMESCOPE_APP` | command | For an ad-hoc bare-gamescope session, the nested command to run (e.g. `vkcube`). | | `PUNKTFUNK_SESSION_WATCH` | `1` · `0` | Follow a Gaming ↔ Desktop switch mid-stream (rebuild in place, no reconnect). On by default on Bazzite/SteamOS; set `0` to disable. | +| `PUNKTFUNK_GAMESCOPE_HDR` | `1` · `0` *(default off)* | Allow HDR (10-bit BT.2020 PQ) sessions. Needs `punktfunk-gamescope` — see below. | +| `PUNKTFUNK_GAMESCOPE_SDR_NITS` | e.g. `400` | On an HDR session, how bright SDR content (the desktop, the Steam overlay, an SDR game) is inside the PQ container. gamescope's default is 400. | +| `PUNKTFUNK_GAMESCOPE_BIN` | path | Force a specific gamescope binary. Otherwise the host prefers `punktfunk-gamescope` on `PATH`, then `gamescope`. | + +## HDR on gamescope + +Games can render HDR on a headless gamescope today, but a stock gamescope's **capture** output is +8-bit SDR: its PipeWire node offers only 8-bit formats, and it tone-maps the composite down before +handing it over. So a stock setup streams SDR — correctly, including a correct SDR rendition of an +HDR game — and there is nothing to configure. + +To stream real HDR you need `punktfunk-gamescope`: gamescope plus a small patch that adds the +10-bit BT.2020 PQ formats to that node (offered upstream as +[gamescope#2126](https://github.com/ValveSoftware/gamescope/issues/2126)). It installs under its +own name and does **not** replace your system gamescope — your Gaming Mode keeps using that one. + +- **Bazzite / Fedora Atomic** — included in the punktfunk sysext; `punktfunk-sysext update` gets it. +- **Arch / SteamOS** — the `punktfunk-gamescope` package. +- **NixOS** — `services.punktfunk.host.gamescopeHdr = true;` (it also sets the env var below). +- **Anything else** — `bash packaging/gamescope/build-punktfunk-gamescope.sh` from the source tree. + +Then turn it on: + +```sh +PUNKTFUNK_GAMESCOPE_HDR=1 +``` + +and check what the host thinks it can do: + +```sh +punktfunk-host hdr-probe +``` + +A session goes HDR when **all** of: the host allows it (`PUNKTFUNK_10BIT`, on by default, plus the +knob above), the resolved gamescope offers the 10-bit formats, the client advertises 10-bit +decoding with HDR enabled in its settings, the codec is HEVC or AV1 (never H.264), and the GPU +encodes Main10. Anything missing and the session streams 8-bit SDR — decided **before** the stream +starts, so you never get a mislabelled picture. The client overlay shows `10-bit · HDR (BT.2020 +PQ)` when it worked. + +Two things to know: + +- **SDR content rides the same PQ stream.** The desktop, the Steam overlay and SDR games are mapped + into the HDR container at `PUNKTFUNK_GAMESCOPE_SDR_NITS` (400 by default). If white looks too + bright or too dim on your TV, that is the knob. +- **On AMD and Intel the composited mouse pointer is currently missing from HDR sessions.** HDR + routes the encode through a path that cannot blend the cursor gamescope leaves out of its + capture. SDR sessions are unaffected, as are NVIDIA hosts. ## Known limits @@ -125,8 +173,11 @@ These apply to the **Gaming Mode (gamescope)** path only; the desktop path is un touchscreen, so the host maps a client's touchscreen to an absolute pointer: taps click exactly where you touch and drags work, but multi-touch gestures (pinch) aren't available in Gaming Mode. The desktop path has full multi-touch. -- **HDR isn't supported on the gamescope path** — gamescope's capture output is 8-bit. SDR streams - normally. +- **HDR needs the punktfunk gamescope build.** A stock gamescope's capture output is 8-bit SDR, so + sessions stream SDR — correctly, including SDR versions of HDR games. Install + `punktfunk-gamescope` (gamescope plus a small patch that teaches its capture node the 10-bit + BT.2020 PQ formats) and set `PUNKTFUNK_GAMESCOPE_HDR=1`, and a 10-bit-capable client streams + true HDR10. See [HDR on gamescope](#hdr-on-gamescope) below. To stream the KDE Plasma desktop of a Steam box instead, see [KDE Plasma](/docs/kde). To bring up the web console and pair a client, see [The Web Console](/docs/web-console). diff --git a/docs-site/content/docs/roadmap.md b/docs-site/content/docs/roadmap.md index 50bd68a9..dfbc475b 100644 --- a/docs-site/content/docs/roadmap.md +++ b/docs-site/content/docs/roadmap.md @@ -100,14 +100,17 @@ see [Status & Progress](/docs/status). ## ⛔ Parked / blocked -- **HDR / 10-bit on the *Linux* host — virtual displays.** GNOME 50 added HDR screencasting for - **monitor** streams, and the host now uses it: the GameStream desktop mirror +- **HDR / 10-bit on the *Linux* host — Mutter/KWin/wlroots virtual displays.** GNOME 50 added HDR + screencasting for **monitor** streams, and the host uses it: the GameStream desktop mirror (`PUNKTFUNK_VIDEO_SOURCE=portal`) negotiates the 10-bit PQ formats and encodes HEVC Main10 - BT.2020 PQ (`punktfunk-host hdr-probe` reports readiness; on-glass validation pending). What - stays blocked upstream is HDR on **virtual monitors** — Mutter's `RecordVirtual` streams are - still SDR-only (through the GNOME 51 dev branch), so the native protocol and GameStream's - default virtual-display source stream 8-bit until that lands; the host is ready the moment it - does. + BT.2020 PQ. The **gamescope** virtual output is no longer blocked either — the + `punktfunk-gamescope` build carries the small patch its capture node was missing, so + `PUNKTFUNK_GAMESCOPE_HDR=1` streams true HDR10 (on-glass validation pending; the patch is + offered upstream as + [gamescope#2126](https://github.com/ValveSoftware/gamescope/issues/2126)). What stays blocked + upstream is HDR on **Mutter's virtual monitors** — `RecordVirtual` streams are still SDR-only + (through the GNOME 51 dev branch) — so a GNOME/KWin/wlroots virtual display streams 8-bit until + that lands; the host is ready the moment it does. - **Advanced DualSense voice-coil haptics.** Scoped and shelved (it rides the controller's USB audio interface, with near-zero game support on Linux). Adaptive triggers, rumble, and the lightbar already ship. diff --git a/docs-site/content/docs/status.md b/docs-site/content/docs/status.md index 1de31eaf..93daa2c2 100644 --- a/docs-site/content/docs/status.md +++ b/docs-site/content/docs/status.md @@ -36,12 +36,15 @@ host is newer than the Linux host.) VAAPI or Vulkan Video on AMD/Intel — with automatic split-encode at very high resolutions. Stable 240 fps at 5120×1440 has been measured. A GPU-less software H.264 encoder exists as an explicit fallback. - **HDR (10-bit).** An HDR Windows desktop is captured and encoded as HEVC Main10 (BT.2020 PQ) to - HDR-capable clients (Windows, Android). On Linux, a **GNOME 50+** host can mirror an HDR monitor - over the GameStream desktop-capture source (`PUNKTFUNK_VIDEO_SOURCE=portal`): the portal - negotiates the 10-bit PQ screencast formats GNOME 50 added and encodes Main10 PQ (run - `punktfunk-host hdr-probe` to check readiness; pending on-glass validation). Linux **virtual - displays** — the native protocol and GameStream's default source — still stream 8-bit: Mutter's - virtual-monitor screencast is SDR-only upstream. + HDR-capable clients (Windows, Android). On Linux there are two routes. A **GNOME 50+** host can + mirror an HDR monitor over the GameStream desktop-capture source + (`PUNKTFUNK_VIDEO_SOURCE=portal`): the portal negotiates the 10-bit PQ screencast formats GNOME + 50 added and encodes Main10 PQ. And a **gamescope** host can stream HDR off its virtual output + with the `punktfunk-gamescope` build plus `PUNKTFUNK_GAMESCOPE_HDR=1` — games get real HDR + surfaces and the composite is captured as 10-bit BT.2020 PQ. (Run `punktfunk-host hdr-probe` to + check either; both are pending on-glass validation.) The **other** Linux virtual displays — + Mutter, KWin, wlroots — still stream 8-bit: their virtual-monitor screencasts are SDR-only + upstream. - **Secure by default.** A **SPAKE2 PIN pairing** ceremony establishes trust (the host shows a 4-digit PIN; an attacker gets a single online guess, no offline dictionary attack). Trust-on-first-use (TOFU) remains an explicit opt-in for fully trusted LANs. diff --git a/docs/releases/v0.21.0.md b/docs/releases/v0.21.0.md index 5159a93f..1bd52861 100644 --- a/docs/releases/v0.21.0.md +++ b/docs/releases/v0.21.0.md @@ -12,6 +12,24 @@ A machine you pin from the host's own configuration shows the choice in the cons For headless and unattended setups, two new commands help you find and verify the right monitor without a screen attached: `punktfunk-host list-monitors` lists what the machine has (names, geometry, which one is pinned), and `punktfunk-host mirror-test` confirms frames are actually flowing from the one you picked. +## New: HDR on the gamescope path (opt-in) + +Streaming Steam Gaming Mode from a Linux box has always been SDR — not because the encoder couldn't +do better, but because gamescope hands its picture to Punktfunk already tone-mapped down to 8-bit. +That gap is now closed, with a small companion package: install **`punktfunk-gamescope`** (gamescope +plus a patch that adds the 10-bit BT.2020 PQ formats to its capture output — offered upstream) and +set `PUNKTFUNK_GAMESCOPE_HDR=1`, and games render in real HDR while the stream carries HDR10 to an +HDR-capable client. + +It sits *beside* your system's gamescope rather than replacing it — your own Gaming Mode is +untouched — and Punktfunk only uses it for the sessions it starts itself. On Bazzite it rides in +the Punktfunk sysext; there's an Arch package, a NixOS option, and a build script for everything +else. `punktfunk-host hdr-probe` tells you exactly which pieces are in place. + +Opt-in for this release while it soaks: without the knob, or without the extra package, the +gamescope path streams SDR exactly as before. One known gap on AMD and Intel: the on-screen mouse +pointer is missing from HDR sessions (NVIDIA is unaffected) — SDR sessions still have it. + ## Fixed: newer KDE Plasma silently fell back to a slower, less reliable way to arrange displays On current KDE Plasma (6.7 and newer), Punktfunk's direct way of talking to the desktop about display layout stopped seeing any displays at all, with no visible error — every session quietly fell back to an external helper tool that's known to hang under load, exactly the thing the previous release's Plasma fix was meant to stop relying on. Fixed: Punktfunk now recognizes both the way older and newer Plasma releases announce their displays. @@ -25,6 +43,9 @@ On current KDE Plasma (6.7 and newer), Punktfunk's direct way of talking to the - **libei absolute-coordinate resolution changed from matching by mode size to `mapping_id` → origin → size → first**, since two outputs can share a size but never a top-left, and a mirrored head's region is not the client's stream size at all. A named anchor that matches nothing falls back down the ladder rather than stranding input, and both a miss and a match now log once per distinct answer. `punktfunk-host anchor-test` exercises the ladder against a live compositor with two same-sized outputs — the one case a unit test can only simulate. - **`SWAYSOCK` is now derived rather than required to be inherited** — by the compositor's known PID, then the newest socket owned by the host's user — closing the last session variable a `systemd --user` host lacked for sway. A new drop-in (`PartOf`/`WantedBy` on `graphical-session.target`, opt-in, shipped under `/usr/share`) restarts a desktop-login host with its desktop, so a Wayland socket and portal connection that die with a Plasma/GNOME restart don't leave the daemon silently unable to recover. - **KWin's in-process output-management path now also binds `kde_output_device_registry_v2`**, not just the per-output `kde_output_device_v2` globals it originally shipped with — KWin 6.7 stopped advertising the latter, so the module written specifically to avoid shelling out to `kscreen-doctor` (0.19.x) saw zero devices and silently degraded to it on every session. Both models are supported now; registry-sourced devices arrive one Wayland round-trip later, so the handshake gains one conditional extra barrier. +- **gamescope HDR is decided statically, before the display exists.** The punktfunk/1 Welcome fixes a session's bit depth up front and cannot take it back (PQ frames on an 8-bit encoder are a deliberate hard error), so the capability answer is the identity of the gamescope binary the host will spawn — a `+pfhdr` marker in its `--version` banner, probed once per boot — never an optimistic negotiation. The capture-side gate became source-aware (`capturer_supports_hdr_for(compositor)`), the HDR negotiation-failure latch became per-source (a wedged monitor mirror no longer disables a gamescope session's HDR, or vice versa), and the keep-alive reuse key gained `hdr` so a display brought up SDR can never be handed to an HDR session. The GameStream plane's live BT.2100 monitor probe is now scoped to the portal source — a headless gamescope box has no monitor to be in HDR mode. +- **The gamescope patch mirrors code already in gamescope's tree.** Its PipeWire node additionally offers `xRGB_210LE`/`xBGR_210LE` with MANDATORY SMPTE ST.2084 + BT.2020 properties, mapped to `DRM_FORMAT_XRGB2101010`/`XBGR2101010` — the same 10-bit capture texture the HDR AVIF screenshot path allocates — and `paint_pipewire()` composites into them with the HDR screenshot LUT set and `EOTF_PQ`, which is exactly the `bHDRScreenshot` branch. The new formats are listed last, so every existing consumer keeps negotiating the 8-bit stream bit-for-bit, and the fixed PQ container is deliberately *not* conditional on the focused app being HDR (a stream's colourimetry must not follow what the game happens to render). +- **AMD/Intel HDR needs no new encoder code**; NVIDIA gained a zero-copy leg. The VAAPI path already ingested XR30 dmabufs into `format=p010:out_color_matrix=bt2020`. On NVIDIA the packed 10-bit frame now travels LINEAR dmabuf → Vulkan bridge → CUDA → NVENC `ARGB10`/`ABGR10`, letting NVENC do the BT.2020 CSC itself: no host CSC pass, no depth loss, and the cursor-blend compute shader gained two 10-bit modes so the pointer survives. The tiled EGL de-tile blit is still 8-bit and HDR never routes through it. A host without the direct-SDK NVENC backend keeps HDR on the CPU path, since libav's HDR route swscales into a P010 hardware frame that a packed-10-bit CUDA buffer cannot fill. - **CI-only fix:** the winget release-verification step's `envs:` allow-list was a step-level sibling of `with:`/`env:` instead of nested inside `with:`, so `appleboy/ssh-action` never actually received it as an input and the step kept failing on every tag. Moved to match how `REGISTRY_TOKEN` is already forwarded elsewhere. See [v0.20.1](v0.20.1.md) for the fixes carried by that release (Windows install/winget, GameStream opt-in default, the gamescope Game Mode takeover hardening, a laptop-panel stall fix, and the PyroWave latency-creep bundle) — all included here too, since this release supersedes it.