docs(gamescope): the missing cursor was never the whole story

"The mouse cursor isn't included in the captured image" sat in Known Limits
contradicting the section above it, which already explained that the host draws
the pointer back in. Both are half-true and the difference matters to a reader
choosing whether to install anything: gamescope does leave the pointer out, you
do still see one, and what it costs is a full pass over every frame — plus, on
the fastest encode paths, the pointer itself, because a fixed-function front
end has nowhere to blend it.

Which is the real argument for `punktfunk-gamescope` on a box that will never
turn HDR on, and the page never made it.

Release notes gain the spawn-flag verification and the packaging wiring.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-07-28 18:03:30 +02:00
co-authored by Claude Opus 5
parent c801465469
commit 4065b5bd03
2 changed files with 13 additions and 3 deletions
+11 -3
View File
@@ -161,8 +161,11 @@ Two things to know:
- **On AMD and Intel, HDR follows what the GPU can encode.** The host asks the driver whether it
can encode your codec at 10 bits and picks the fast path when it can — which current AMD and
Intel GPUs do for HEVC, and newer ones for AV1 too. On a GPU that declines, the session still
streams HDR through a slower path, but the mouse pointer gamescope leaves out of its capture
can't be drawn back in there. `punktfunk-host hdr-probe` reports what your box answered.
streams HDR through a slower path. That slower path is also the only one that can draw a pointer
back into the picture — so on a declining GPU, a *stock* gamescope would cost you the cursor.
`punktfunk-gamescope` puts the pointer in the capture itself, which is why it is worth installing
even on a box where you never turn HDR on. `punktfunk-host hdr-probe` reports what your box
answered, including which side is drawing the cursor.
## Known limits
@@ -170,7 +173,12 @@ These apply to the **Gaming Mode (gamescope)** path only; the desktop path is un
- **gamescope 3.16.22 or newer is required.** Older versions can deadlock during capture. Bazzite's
and SteamOS's 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.
- **gamescope leaves the mouse cursor out of its captured image.** You still see a pointer: the
host reads it separately and draws it into every frame. That costs a full pass over the picture,
and on the fastest encode paths it cannot be done at all (see the AMD/Intel note above).
`punktfunk-gamescope` fixes it at the source — that build paints the pointer straight into the
capture, so the host stops redrawing it and the frame reaches the encoder untouched. Nothing to
configure; installing the build is enough, HDR or not.
- **Touch arrives as a single-finger pointer.** gamescope's virtual input device has no
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
+2
View File
@@ -48,6 +48,8 @@ On current KDE Plasma (6.7 and newer), Punktfunk's direct way of talking to the
- **GameStream advertises its 10-bit codec bits per codec.** `ServerCodecModeSupport` layered `SCM_HEVC_MAIN10` and never `SCM_AV1_MAIN10` — a blanket omission on the theory that the GameStream AV1 path was unconfirmed, even though the SDR baseline has always offered AV1 Main8, so the depth was never the uncertain part. Each 10-bit bit is now gated on that codec's own `can_encode_10bit` probe AND the baseline already advertising it, and the RTSP honor degrades a session whose NEGOTIATED codec can't carry 10 bits rather than labelling an 8-bit stream PQ. `host_hdr_capable` became codec-agnostic to match (any 10-bit-capable codec makes the host HDR-capable; which one a session gets is the session's question).
- **The Vulkan encode backend is now capability-probed per codec AND depth**, mirroring what the direct-SDK NVENC path already does with its GUID probe. `vkGetPhysicalDeviceVideoCapabilitiesKHR` is asked against the very profile the session open will build, so the dispatcher's prediction cannot disagree with reality: a device that can encode 10-bit keeps the Vulkan path, one that can't routes to libav VAAPI *before* burning a failed session open, and `can_encode_10bit` reports the union of what VAAPI and Vulkan Video can do rather than VAAPI's answer alone (which was under-reporting 10-bit on hardware that could do it).
- **A gamescope session can now be zero-copy end to end.** gamescope keeps the pointer out of its PipeWire node (it lives on a hardware plane for scanout), so the host always reconstructed it from XFixes and blended it into every frame — and *that blend* is what forced the encode path onto its compute colour-conversion arm, since the zero-copy RGB-direct source hands the captured buffer to a fixed-function front end with no blend stage. A second carried gamescope patch adds `--pipewire-composite-cursor`, which paints it in using the same `MouseCursor::paint` call the scanout composite uses; the repaint test grows the cursor's state alongside the commit ids, so a pointer-only move still produces a frame and a hidden cursor is erased. The host reads a monotonic `+pfhdr<N>` patch level from the `--version` banner — one probe now answering both "can it do HDR" and "does it paint the cursor" — and stops attaching the XFixes reader and blending when the answer is yes.
- **The two indirect spawn modes now verify that their flags arrived.** A host-managed `gamescope-session-plus` receives the HDR and cursor flags through `GAMESCOPE_BIN` + `PF_HDR_ARGS`, and SteamOS through a PATH shim — conventions, not guarantees. A session that ignored either would exec the distro's gamescope with none of them, and while the HDR half fails loudly (capture negotiation times out against the bit depth the Welcome already fixed), a lost `--pipewire-composite-cursor` was **silent**: the host had been told the compositor would paint the pointer, so it painted none, and the stream simply had no cursor. Both managed paths now read the running compositor's `/proc/<pid>/cmdline` once its node appears and refuse the session on a missing flag; since the plan is fixed by then (`cursor_blend` feeds the encoder open, which precedes the display), the capability is latched off for the process and the retry resolves a correct SDR host-composited session — one rejected attempt per boot, then it converges. The check fails **open** at every ambiguity: no flags expected, or no readable gamescope in `/proc`, says nothing.
- **`punktfunk-gamescope` is now built by CI on the channels that ship it.** `rpm.yml` builds it in the matching Fedora container and hands it to `build-sysext.sh --gamescope`; `arch.yml` builds `packaging/gamescope/PKGBUILD` into the same pacman repo. Both are cached on `packaging/gamescope/**` (that tree depends on nothing else in the repo, so a normal push restores a binary rather than spending ten minutes on someone else's C++) and both are best-effort, because the packages those workflows exist to publish must not hinge on a gamescope build. The Arch PKGBUILD and the nix derivation had each drifted to a stale patch list — the PKGBUILD naming two patches when there are three, the nix override naming the level-1 banner patch after level 2 landed — so both now read the patch *directory*, and the PKGBUILD delegates the whole build to the shared script (asserting its own pinned rev matches) instead of re-deriving the meson invocation, which had already lost the `force_fallback_for=wlroots` that keeps the binary startable off the build host. The nix override's `gamescope.unwrapped` requirement was wrong on current nixpkgs, where `gamescope` *is* the buildable derivation; it now prefers `.unwrapped` where it exists and checks the result is something `overrideAttrs` can actually patch.
- **The zero-CSC RGB-direct (EFC) source works in HDR too.** The `VK_VALVE_video_encode_rgb_conversion` probe now asks for the BT.2020 model and the captured 10-bit packed-RGB format instead of assuming BT.709/BGRA, and the session selects the matching model — so an HDR session with no pointer to composite (the GameStream desktop mirror) hands the captured buffer straight to the encoder's fixed-function front end and runs no host CSC at all. Sessions that DO composite a pointer keep the compute CSC, as before: the EFC cannot blend.
- **NVIDIA gained a zero-copy HDR leg**, and the VAAPI fallback needed no new encoder code. 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.