9e7713eecf78cd5bf7d3dece2db56af494084b1f
9
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
9e7713eecf |
fix(gamescope): every session ended in a SIGSEGV at exit
Each gamescope-backed session left a coredump behind. It happened after the compositor had finished its work — "Primary child shut down!", then the crash — so the stream itself looked fine and it surfaced only as a steady drip of coredumps and a non-zero exit from the spawn. It is a static-destruction-order bug, not a race and not anything gamescope does wrong at runtime. `g_device` (CVulkanDevice) and `g_output` (VulkanOutput_t) were plain globals, so glibc ran their destructors from `__run_exit_handlers` once main() returned. Those destructors call back into the driver — `~CVulkanCmdBuffer` -> `vk.FreeCommandBuffers`, `~CVulkanTexture` -> `vk.Destroy*` — but the Vulkan ICD has already been torn down and unloaded by then, so each call jumps through a function pointer into an unmapped page. The faulting address equalling the instruction pointer is the signature: #0 0x00007fe8fd1d1070 in ?? () #1 CVulkanCmdBuffer::~CVulkanCmdBuffer at rendervulkan.cpp:1543 #9 std::vector<unique_ptr<CVulkanCmdBuffer>>::~vector (g_device+1792) #10 CVulkanDevice::~CVulkanDevice at rendervulkan.hpp:768 #11 __run_exit_handlers / exit() Patch 0006 gives both globals storage that is constructed exactly as before but never destroyed; a union member is destroyed only if the union's destructor says so, and ours deliberately does not. Nothing needs freeing there — the process is exiting and the kernel reclaims the device, its command buffers and every GPU allocation. Both objects are needed: pinning only the device relocated the fault into ~VulkanOutput_t. The `.pfhdrN` level deliberately stays at 4. It is a capability tier the host probes before it spawns, and this patch adds no capability — bumping it would advertise a tier that does not exist. Per the PKGBUILD's own rule this ships as a `pkgrel` bump instead. Verified on an NVIDIA box, all six patches `git am`-ing onto the pinned upstream commit and then a RELEASE build (the shipped configuration): version banner 3.16.25-7-gea635c1+pfhdr4 (marker intact) patched, real spawn shape (2752x2064@120 --steam --xwayland-count 1) 6/6 exit 0 distro control, same shape SIGSEGV Not filed upstream, though it is not punktfunk-specific and would apply as-is. Unrelated and left alone: `--xwayland-count 0` dies much earlier, in main() at wlserver.cpp:3215, dereferencing a null `gamescope_xwayland_server_t`. Punktfunk always spawns with `--xwayland-count 1`, so that path is never taken here. |
||
|
|
eb8c943572 |
feat(packaging): ship punktfunk-gamescope on RPM and apt too
ci / web (pull_request) Successful in 1m4s
ci / bun-nix (pull_request) Successful in 26s
apple / swift (pull_request) Successful in 1m40s
apple / screenshots (pull_request) Skipped
ci / rust (pull_request) Failing after 2m3s
ci / docs-site (pull_request) Successful in 1m41s
ci / rust-arm64 (pull_request) Successful in 2m44s
android / android (pull_request) Successful in 4m50s
nix / flake (pull_request) Failing after 16m12s
Until now the patched gamescope reached exactly four kinds of box: the Bazzite/Fedora-Atomic sysext, the Arch package, the SteamOS installer and a NixOS option. Everyone else was told to build gamescope from source. A traditional Fedora-family box — Nobara, plain Fedora, the HTPCs people actually stream from — therefore ran stock gamescope by default, which streams SDR, cursorless, and tells every game its display is 60 Hz. That is not a user error; there was no package to install. Both new packages REPACK the binary CI already builds rather than building gamescope again: it is a ~10-minute meson compile of an unrelated tree, cached per distro base because the binary is soname-coupled to it. The Arch PKGBUILD stays the one recipe that builds from source, because that is what makepkg is for. - packaging/gamescope/punktfunk-gamescope.spec + build-gamescope-rpm.sh. Version is derived from the binary's own banner (3.16.25.pfhdr4) — the only source that cannot drift from what is in the package. rpmbuild's automatic ELF Requires are what stop an f43 build installing on f44. - packaging/debian/build-gamescope-deb.sh, same shape, with dpkg-shlibdeps for Depends. - rpm.yml packages and publishes it beside the host RPMs; deb.yml gains a cached gamescope build (keyed on packaging/gamescope/** alone) and packages it into the existing publish loop. Both legs are best-effort, matching the sysext's existing rule: no binary, no package, and the host stays on its current SDR path. Neither package Provides or Conflicts with gamescope — it installs as /usr/bin/punktfunk-gamescope and only the sessions the host starts itself resolve it, so a box's own Game Mode keeps using the distro binary. Both refuse to package a binary without the +pfhdr marker. That marker is the host's entire capability probe, so a build that lost the patches would install fine and then silently stream SDR with no cursor. Verified: build-gamescope-deb.sh produces an installable .deb from a stand-in binary (correct version derived from the banner, 0755 tree, control fields) and exits 1 on an unmarked one. The .spec is not yet exercised — no rpm tooling on the box I had; CI's Fedora leg is its first run. |
||
|
|
818531a26e |
feat(gamescope): a headless session now reports its own mode, and the perf overlay reaches the stream
Two new patches on the pinned upstream, and the marker patch moves last so the banner is stamped after the capabilities it advertises. 0003 — headless: advertise the virtual display's mode and refresh rates. A headless gamescope is how we give a game a display: we pass the client's exact mode and the session runs at it. It never told anyone. CHeadlessConnector returned empty spans from GetModes() and GetValidDynamicRefreshRates() and reported GAMESCOPE_SCREEN_TYPE_INTERNAL, so update_mode_atoms DELETED the mode-list atom (no resolution list) and wlserver fell through to a one-entry refresh list built from g_nOutputRefresh (no refresh list). With --nested-refresh absent that entry is Init()'s 60 Hz default — which is why a field report on a 1920x1080@120 client saw "gamescope only shows 60hz, and there's no other option", and why Overwatch capped itself to 60 while the stream ran at 120. Populate both from the resolved mode, report EXTERNAL, and add --custom-refresh-rates so the offered set can be widened. gamescope-session-plus has probed for that flag for years; upstream never had it, so the CUSTOM_REFRESH_RATES env it plumbs was a no-op everywhere. 0004 — pipewire: optionally composite the external overlay into the capture stream. That layer is mangoapp: the fps/frametime readout the Deck UI turns on. paint_pipewire has never referenced it on any version, so a consumer whose only view of the session is the node sees the overlay it just enabled not appear, with nothing to configure. Behind --pipewire-composite-external-overlay, off by default, same argument as the cursor flag. Its commit id joins the repaint test — the numbers change while the picture behind them is static, exactly the case the existing test skips. Verified: the series git-am's cleanly onto the pinned 8c676c39, and both new functions were extracted verbatim and compiled with -Wall -Wextra under C++23 against stubs, with unit assertions for the parser and the mode/rate publication (sorting, dedup, the running rate always present, re-entrancy, zero rejected). A full gamescope build was not run — no box here has its dependency set; CI's per-Fedora-major leg is the first real compile. |
||
|
|
53ff313046 |
fix(packaging/gamescope): the shipped compositor starts on SteamOS — a rolling-distro libstdc++ never followed it there
docker / builders (--build-arg FEDORA_VERSION=44, ci/fedora-rpm.Dockerfile, punktfunk-fedora44-rpm, -f44) (push) Successful in 14s
docker / builders (ci/android-ci.Dockerfile, punktfunk-android-ci) (push) Successful in 11s
docker / builders (ci/arch-ci.Dockerfile, punktfunk-arch-ci) (push) Successful in 10s
docker / builders (ci/fedora-rpm.Dockerfile, punktfunk-fedora-rpm) (push) Successful in 8s
ci / web (push) Successful in 52s
docker / apps (., web/Dockerfile, punktfunk-web) (push) Successful in 10s
docker / apps (docs-site, docs-site/Dockerfile, punktfunk-docs) (push) Successful in 8s
docker / builders (ci/rust-ci-noble.Dockerfile, punktfunk-rust-ci-noble) (push) Successful in 1m6s
docker / builders (ci/rust-ci.Dockerfile, punktfunk-rust-ci) (push) Successful in 1m8s
ci / docs-site (push) Successful in 2m13s
docker / builders-arm64cross (push) Successful in 10s
docker / deploy-docs (push) Successful in 40s
ci / rust (push) Canceled after 5m49s
ci / rust-arm64 (push) Canceled after 5m47s
arch / build-publish (push) Successful in 15m59s
rpm / build-publish (43, bazzite, punktfunk-fedora-rpm) (push) Canceled after 16m7s
rpm / build-publish (44, fedora-44, punktfunk-fedora44-rpm) (push) Canceled after 15m46s
`punktfunk-gamescope-3.16.25.pfhdr2-1` off the pacman repo cannot start on SteamOS 3.8.16: `/usr/lib/libstdc++.so.6: version 'GLIBCXX_3.4.35' not found`. The Arch container CI builds it in is on gcc 16.1.1; SteamOS ships libstdc++ 3.4.34 and moves when Valve says so. Nothing else about the binary was wrong — every other soname resolved on the box, and glibc was never close (it asks for 2.38 at most against SteamOS's 2.41) — so the one dynamic C++ runtime was the whole reason the gamescope backend's own most important platform got a package that dies at `--version`. The C++ runtime therefore goes static, for the same reason wlroots already does: this binary is built on a rolling distro and has to start on a frozen one. It is safe here because gamescope links no shared C++ library at all — its NEEDED list is all C, and glslang/SPIRV are build-time only — so no C++ ABI crosses a shared boundary. Cost is ~1 MB (5.9 → 7.1). The flags are appended to LDFLAGS rather than passed as `-Dcpp_link_args`, which would replace the value meson derives from the environment and silently drop makepkg's `-z relro`/`-z now`/`--as-needed`. A static runtime is invisible in a passing build and only surfaces as a binary that will not start somewhere else, so the build now asserts it: no `libstdc++` in NEEDED, which needs no version threshold to check and turns the regression back into a build failure. Verified by building in `archlinux:base-devel` — the environment arch.yml uses, gcc 16.1.1 and glibc 2.44, both far newer than the target — and running the result on SteamOS 3.8.16 with nothing supplied: banner `punktfunk-gamescope version 3.16.25-4-g6bbe157+pfhdr2`, no libstdc++ in NEEDED, max GLIBC_2.38, every soname resolving. The host's whole HDR gate chain then answers on SteamOS for the first time (780M / RADV PHOENIX): 10-bit PQ capture offered, cursor painted in-node, native-plane HDR and GameStream HDR capable both true once `PUNKTFUNK_GAMESCOPE_HDR` is on, false with the distro's gamescope. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
c801465469 |
fix(packaging): every channel that ships punktfunk-gamescope now builds it
The docs already told users where to get it — the Bazzite sysext, an Arch package, a NixOS option — and none of the three were true. `rpm.yml` built the sysext without ever passing `--gamescope`, `arch.yml` did not know the PKGBUILD existed, and the nix derivation had never been evaluated once. Evaluating it found its central assumption wrong: `gamescope.unwrapped` does not exist on current nixpkgs, where `gamescope` IS the buildable derivation, so the override threw on every build. It now prefers `.unwrapped` where a future nixpkgs wraps it and checks the RESULT is patchable — `overrideAttrs` on a symlinkJoin succeeds and does nothing, which would install an UNPATCHED gamescope under a name the host reads as a promise of HDR. Both it and the PKGBUILD had also drifted to a stale patch list: two patches named where three exist, one of them under the level-1 filename retired when the cursor patch landed. Both read the patch directory now, so the list cannot go stale again. The PKGBUILD additionally delegates the whole build to `build-punktfunk-gamescope.sh` rather than re-deriving the meson invocation — its copy had already lost `force_fallback_for=wlroots`, and unlike Fedora 43, Arch ships wlroots, so that package would have linked it shared and shipped a binary that starts only on machines carrying the dev library. It asserts its own pinned rev matches the script's, the one thing makepkg needs statically. Both CI builds are cached on `packaging/gamescope/**`, which is the only reason this is affordable: that tree depends on nothing else in the repo, so a normal push restores a binary instead of spending ten minutes on someone else's C++. And both are best-effort. punktfunk works without this binary — SDR on the gamescope backend, which is what every release before this one did — so a hiccup building gamescope must not cost the packages those workflows exist to publish. A failure warns and is never cached, so the next run retries. `rpm.yml` also installs Fedora's own gamescope for its runtime libraries: the sysext verifies our binary by executing `--version`, and on a cache hit nothing else in the job would have pulled libavif/luajit/seatd in. Verified by evaluating and patch-phase-building the nix derivation against nixos-unstable: all three patches apply to nixpkgs' already-patched 3.16.25 tree, and the banner stamps +pfhdr2. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
0d2cc65f17 |
fix(gamescope): force the VENDORED wlroots — the binary's portability was luck
Built the patches on a second distro (Nobara 44 / Fedora 44, for the NVIDIA
leg) and the resulting binary would not start on its own host:
libwlroots-0.19.so: cannot open shared object file
gamescope vendors wlroots as a submodule, but meson prefers a SYSTEM one when
the build host has `wlroots-devel` — and links it shared. Fedora 43 has no such
package, so the `.116` build fell back to the submodule and linked it
statically; Fedora 44's `dnf builddep gamescope` pulls one in, so the same
script produced a binary bound to a library that exists only inside the build
container.
Confirmed rather than assumed: `ldd` on the f43 binary lists no wlroots at all
and its log says `Subproject wlroots finished`, while the f44 log says
`Dependency wlroots-0.19 found` from the system.
The consequence, had Fedora 43 ever shipped `wlroots-devel`: the sysext payload
would have carried a gamescope that cannot start, and it would have surfaced to
users as "HDR just doesn't work" rather than as a build failure. `-Dforce_
fallback_for=libliftoff,vkroots,wlroots` makes every distro produce the same
self-contained binary. All three entries go together — gamescope's own
meson.build hard-errors if the first two are dropped from that list.
Verified after the fix: `ldd` shows no wlroots/liftoff/vkroots and no missing
deps, the binary starts on the Nobara host, and its node offers the same four
formats with the colorimetry props — same result as Bazzite f43.
|
||
|
|
9eb9f74893 |
fix(gamescope): the patch did not compile on Fedora/Bazzite — three real defects
Built it for the first time, on the AMD Bazzite box (`.116`, RADV 780M, Fedora 43 toolbox). The patches applied cleanly to the pinned rev and the configure got all the way through wlroots — then found three things no amount of reading would have: **1. `SPA_VIDEO_TRANSFER_SMPTE2084` does not exist on Fedora 43.** PipeWire 1.4.11's transfer-function enum stops at `ADOBERGB`: SPA grew BT2020_10 / SMPTE2084 / ARIB_STD_B67 as one later block. So the patch simply did not compile on the distro it is primarily FOR. This is the same trap punktfunk's own consumer documents and works around — `pf-capture`'s `pw_pods.rs` spells the value out as `14` for exactly this reason — and the fix is the same: spell out both colorimetry values, because the enum is wire ABI mirroring GStreamer's, not a private detail. `SPA_VIDEO_COLOR_PRIMARIES_BT2020` is present here but gets the same treatment, since its own header comment says `\since 1.6` and there is no reason to depend on that. The `PW_CHECK_VERSION(1, 0, 0)` guard was also just wrong: it tests the library version, which says nothing about which enum members a header names. It now guards only the FORMAT constants, which are what it was actually right about. **2. The build pulled in three subprojects we never ship.** gamescope's own unit tests want Catch2 **v3** (`catch2-with-main`) and Fedora ships v2, so the configure died on a test suite that is not ours to build. Also disabled: the OpenVR integration (a code path a headless capture session never enters) and the WSI layer — which would have been WORSE than wasted work, since the distro gamescope package installs that same layer and ours would have collided with it file-for-file. **3. `dnf builddep gamescope` is not sufficient.** It resolves Fedora's *packaged* gamescope, which is older than the master we pin, and misses `xorg-x11-server-Xwayland-devel` — without which wlroots fails several minutes in with a `xserver.wrap` error that names nothing useful. Documented with the symptom, so the next person recognises it in one line instead of ten minutes. |
||
|
|
fd648aa776 |
feat(gamescope): put the cursor in the node, so a session can be zero-copy
gamescope keeps the pointer out of its PipeWire node — it lives on a hardware plane for scanout, and `paint_pipewire()` composites a separate, reduced frame that never includes it. So punktfunk has always reconstructed it from XFixes and blended it in host-side. That blend is what has been blocking the zero-CSC encode path, and the cost is larger than it sounds: `VulkanVideoEncoder::open` refuses the RGB-direct (EFC) source for any session with `cursor_blend`, because that front end is fixed-function and has no blend stage. `cursor_blend_for` sets it unconditionally for gamescope. Net effect: a gamescope session paid a full-frame colour-conversion pass per frame, forever, for a pointer. So put the cursor where it belongs. A second carried gamescope patch adds `--pipewire-composite-cursor` (off by default — the node has never carried it, and a consumer that draws its own would get two), painting it with the same `MouseCursor::paint` call the scanout composite uses. It scales for free: paint_pipewire has already set `currentOutputWidth/Height` to the capture size, which is what that function scales against. The repaint test grows the cursor's state beside the commit ids — a pointer-only move produces no commit, so without it the composited cursor would freeze on a static screen while the real one moved, and a cursor that became hidden would never be erased. Host side, the `+pfhdr` marker becomes a monotonic PATCH LEVEL, so one probe answers every capability the session must know before it is planned (level 1 = HDR formats, level 2 = the cursor flag). `cursor_blend_for` and the `gamescope_cursor` resolver both consult it through one helper, because they have to agree: the reader without the blend is a wasted X11 connection, the blend without the reader is a stream with no pointer, and both together with a gamescope that paints its own would draw two. ⚠ The two indirect spawn modes carry the flag through `PF_HDR_ARGS`, so this shares a dependency with the HDR flags: a session that ignores `GAMESCOPE_BIN`/`PATH` and execs the distro's gamescope gets neither. HDR fails loudly there (negotiation timeout + SDR latch); a missing cursor would be silent. Noted in packaging/gamescope/README.md as worth a post-spawn `/proc/<pid>/cmdline` check if it ever bites. |
||
|
|
d6818263ce |
feat(gamescope): a build of gamescope whose capture output can be HDR
gamescope's built-in PipeWire node has always been SDR-only: it offers BGRx and NV12, and `paint_pipewire()` hardcodes a Gamma-2.2 composite with the SDR screenshot LUT set. So an HDR game reaches punktfunk already tone-mapped down, and the whole gamescope backend streams 8-bit no matter what the client can decode. Games CAN render HDR on a headless gamescope today — only the capture half is missing. Carry the two patches that close it (packaging/gamescope/patches): the node additionally offers `xRGB_210LE`/`xBGR_210LE` with MANDATORY SMPTE ST.2084 + BT.2020 props, mapped to the same 10-bit capture texture the HDR AVIF screenshot path already allocates, and `paint_pipewire()` composites into them with the HDR screenshot LUTs + `EOTF_PQ` — which is exactly the in-tree `bHDRScreenshot` branch. The new formats are listed LAST, so every existing consumer keeps negotiating the 8-bit stream bit-for-bit. Offered upstream against ValveSoftware/gamescope#2126. Ship it as `punktfunk-gamescope`, beside the distro's own binary rather than replacing it: a Bazzite sysext payload (`build-sysext.sh --gamescope`, which refuses a binary without the marker), an Arch package, a nix derivation + `services.punktfunk.host.gamescopeHdr`, and one distro-agnostic build script the rest call. The second patch stamps `+pfhdr1` into the `--version` banner. That is not cosmetic: punktfunk fixes a session's bit depth in the Welcome, before the display exists and with no way to take it back, so its capability answer has to be a static property of the binary it will spawn. |