From 9eb9f74893ed331bd662ab921df588a13bb0f6bb Mon Sep 17 00:00:00 2001 From: enricobuehler Date: Tue, 28 Jul 2026 16:23:24 +0200 Subject: [PATCH] =?UTF-8?q?fix(gamescope):=20the=20patch=20did=20not=20com?= =?UTF-8?q?pile=20on=20Fedora/Bazzite=20=E2=80=94=20three=20real=20defects?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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. --- packaging/gamescope/README.md | 19 +++++++ .../gamescope/build-punktfunk-gamescope.sh | 15 +++++- ...0-bit-BT.2020-PQ-capture-formats-HDR.patch | 53 ++++++++++++------- 3 files changed, 67 insertions(+), 20 deletions(-) diff --git a/packaging/gamescope/README.md b/packaging/gamescope/README.md index 8aa0b89f..e7060417 100644 --- a/packaging/gamescope/README.md +++ b/packaging/gamescope/README.md @@ -81,6 +81,25 @@ ninja -C build/ install -Dm755 build/src/gamescope /usr/bin/punktfunk-gamescope ``` +### Build dependencies + +They are gamescope's, not ours, and they vary by distro. Two shortcuts that work: + +```sh +# Fedora / Bazzite (inside a toolbox/distrobox — the host is immutable) +sudo dnf install -y dnf-plugins-core meson ninja-build glslc +sudo dnf builddep -y gamescope +sudo dnf install -y xorg-x11-server-Xwayland-devel # NOT pulled by builddep; wlroots needs it + +# Arch / SteamOS — see the makedepends in ./PKGBUILD +``` + +⚠️ `dnf builddep gamescope` resolves Fedora's *packaged* gamescope, which is older than the master +we pin, so it can come up short. `xorg-x11-server-Xwayland-devel` is the one that actually bit +(2026-07-28, Fedora 43): without it wlroots' configure fails with `Neither a subproject directory +nor a xserver.wrap file was found`, several minutes into an otherwise clean run. If a different +one surfaces, meson names it — install and re-run with `--srcdir` so the clone is not repeated. + `gamescope` needs `CAP_SYS_NICE` for its realtime priority; the distro packages set it on their own binary. Mirror it if you install ours system-wide: diff --git a/packaging/gamescope/build-punktfunk-gamescope.sh b/packaging/gamescope/build-punktfunk-gamescope.sh index db0e8691..62e98b4d 100755 --- a/packaging/gamescope/build-punktfunk-gamescope.sh +++ b/packaging/gamescope/build-punktfunk-gamescope.sh @@ -75,10 +75,23 @@ fi BUILD="$SRCDIR/build-punktfunk" echo "==> configuring" +# We ship ONE binary out of this tree, so build only what leads to it: +# -Denable_tests=false gamescope's own unit tests want Catch2 **v3** +# (`catch2-with-main`); Fedora ships v2, and building someone else's +# test suite is not our job either way. +# -Denable_openvr_support the VR integration pulls the openvr submodule + its build for a +# code path a headless capture session never enters. +# -Denable_gamescope_wsi_layer the WSI layer is a SEPARATE artifact the distro's gamescope +# package already installs; ours must not collide with it. +# (The layer the nested games load is that one — it is version- +# independent of the compositor binary.) meson setup "$BUILD" "$SRCDIR" \ --prefix="$PREFIX" \ --buildtype=release \ - -Dpipewire=enabled + -Dpipewire=enabled \ + -Denable_tests=false \ + -Denable_openvr_support=false \ + -Denable_gamescope_wsi_layer=false echo "==> building" ninja -C "$BUILD" ${JOBS:+-j "$JOBS"} diff --git a/packaging/gamescope/patches/0001-pipewire-offer-10-bit-BT.2020-PQ-capture-formats-HDR.patch b/packaging/gamescope/patches/0001-pipewire-offer-10-bit-BT.2020-PQ-capture-formats-HDR.patch index 751e9889..81708ccc 100644 --- a/packaging/gamescope/patches/0001-pipewire-offer-10-bit-BT.2020-PQ-capture-formats-HDR.patch +++ b/packaging/gamescope/patches/0001-pipewire-offer-10-bit-BT.2020-PQ-capture-formats-HDR.patch @@ -32,24 +32,23 @@ follow what the app happens to render. Works on the headless backend as well as a real connector: no HDR display is involved anywhere in the LUT set. --- - src/pipewire.cpp | 99 ++++++++++++++++++++++++++++++++------------ - src/steamcompmgr.cpp | 21 ++++++++-- - 2 files changed, 91 insertions(+), 29 deletions(-) + src/pipewire.cpp | 114 +++++++++++++++++++++++++++++++++---------- + src/steamcompmgr.cpp | 21 ++++++-- + 2 files changed, 106 insertions(+), 29 deletions(-) diff --git a/src/pipewire.cpp b/src/pipewire.cpp -index 76b3ea8..0cf71d2 100644 +index 76b3ea8..6b56b01 100644 --- a/src/pipewire.cpp +++ b/src/pipewire.cpp -@@ -18,6 +18,25 @@ +@@ -18,6 +18,40 @@ static LogScope pwr_log("pipewire"); +// 10-bit HDR capture. +// -+// The packed 10-bit RGB SPA formats (xRGB_210LE / xBGR_210LE) and the SMPTE ST.2084 (PQ) -+// transfer function all predate PipeWire 1.0 by a wide margin; the guard only exists so the -+// file still compiles against an ancient libspa, in which case the stream offers exactly the -+// 8-bit formats it always did. ++// The packed 10-bit RGB SPA formats (xRGB_210LE / xBGR_210LE) predate PipeWire 1.0; the guard ++// only exists so this file still compiles against an ancient libspa, in which case the stream ++// offers exactly the 8-bit formats it always did. +#if PW_CHECK_VERSION(1, 0, 0) +#define GAMESCOPE_PIPEWIRE_HDR 1 +#else @@ -57,6 +56,22 @@ index 76b3ea8..0cf71d2 100644 +#endif + +#if GAMESCOPE_PIPEWIRE_HDR ++// The colorimetry VALUES, spelled out rather than taken from the enums, because the headers that ++// name them are much newer than the ones that name the formats above: SPA grew ++// BT2020_10 / SMPTE2084 / ARIB_STD_B67 as one block, and a libspa without it stops at ADOBERGB — ++// PipeWire 1.4.11 (Fedora 43 / Bazzite) does exactly that, so `SPA_VIDEO_TRANSFER_SMPTE2084` ++// fails to compile there even though the library parses the value perfectly well. ++// ++// Spelling them out is safe because the enum is WIRE ABI, not a private detail: SPA mirrors ++// GStreamer's GstVideoTransferFunction / GstVideoColorPrimaries, where these landed together, so ++// the numbers are identical on every implementation that has them at all. On one that does not, ++// a consumer simply fails to intersect this pod and negotiates 8-bit — the same outcome as not ++// offering HDR. (punktfunk's own consumer spells out the same 14 for the same reason.) ++static constexpr uint32_t kSpaVideoTransferSmpte2084 = 14; ++static constexpr uint32_t kSpaVideoColorPrimariesBt2020 = 7; ++#endif ++ ++#if GAMESCOPE_PIPEWIRE_HDR +static bool is_hdr_video_format(uint32_t format) +{ + return format == SPA_VIDEO_FORMAT_xRGB_210LE || format == SPA_VIDEO_FORMAT_xBGR_210LE; @@ -66,7 +81,7 @@ index 76b3ea8..0cf71d2 100644 static struct pipewire_state pipewire_state = { .stream_node_id = SPA_ID_INVALID }; static int nudgePipe[2] = { -1, -1 }; -@@ -94,6 +113,37 @@ static void calculate_capture_size() +@@ -94,6 +128,37 @@ static void calculate_capture_size() s_nCaptureHeight = SPA_ROUND_UP_N(s_nCaptureHeight, kCaptureAlign); } @@ -94,9 +109,9 @@ index 76b3ea8..0cf71d2 100644 +#if GAMESCOPE_PIPEWIRE_HDR + else if (is_hdr_video_format(format)) { + spa_pod_builder_prop(builder, SPA_FORMAT_VIDEO_transferFunction, SPA_POD_PROP_FLAG_MANDATORY); -+ spa_pod_builder_id(builder, SPA_VIDEO_TRANSFER_SMPTE2084); ++ spa_pod_builder_id(builder, kSpaVideoTransferSmpte2084); + spa_pod_builder_prop(builder, SPA_FORMAT_VIDEO_colorPrimaries, SPA_POD_PROP_FLAG_MANDATORY); -+ spa_pod_builder_id(builder, SPA_VIDEO_COLOR_PRIMARIES_BT2020); ++ spa_pod_builder_id(builder, kSpaVideoColorPrimariesBt2020); + } +#endif +} @@ -104,7 +119,7 @@ index 76b3ea8..0cf71d2 100644 static void build_format_params(struct spa_pod_builder *builder, spa_video_format format, std::vector ¶ms) { struct spa_rectangle size = SPA_RECTANGLE(s_nCaptureWidth, s_nCaptureHeight); struct spa_rectangle min_requested_size = { 0, 0 }; -@@ -112,18 +162,7 @@ static void build_format_params(struct spa_pod_builder *builder, spa_video_forma +@@ -112,18 +177,7 @@ static void build_format_params(struct spa_pod_builder *builder, spa_video_forma SPA_FORMAT_VIDEO_requested_size, SPA_POD_CHOICE_RANGE_Rectangle( &min_requested_size, &min_requested_size, &max_requested_size ), SPA_FORMAT_VIDEO_gamescope_focus_appid, SPA_POD_CHOICE_RANGE_Long( 0ll, INT64_MIN, INT64_MAX ), 0); @@ -124,7 +139,7 @@ index 76b3ea8..0cf71d2 100644 spa_pod_builder_prop(builder, SPA_FORMAT_VIDEO_modifier, SPA_POD_PROP_FLAG_MANDATORY); spa_pod_builder_push_choice(builder, &choice_frame, SPA_CHOICE_Enum, 0); spa_pod_builder_long(builder, modifier); // default -@@ -141,18 +180,7 @@ static void build_format_params(struct spa_pod_builder *builder, spa_video_forma +@@ -141,18 +195,7 @@ static void build_format_params(struct spa_pod_builder *builder, spa_video_forma SPA_FORMAT_VIDEO_requested_size, SPA_POD_CHOICE_RANGE_Rectangle( &min_requested_size, &min_requested_size, &max_requested_size ), SPA_FORMAT_VIDEO_gamescope_focus_appid, SPA_POD_CHOICE_RANGE_Long( 0ll, INT64_MIN, INT64_MAX ), 0); @@ -144,7 +159,7 @@ index 76b3ea8..0cf71d2 100644 params.push_back((const struct spa_pod *) spa_pod_builder_pop(builder, &obj_frame)); // for (auto& param : params) -@@ -166,6 +194,14 @@ static std::vector build_format_params(struct spa_pod_bu +@@ -166,6 +209,14 @@ static std::vector build_format_params(struct spa_pod_bu build_format_params(builder, SPA_VIDEO_FORMAT_BGRx, params); build_format_params(builder, SPA_VIDEO_FORMAT_NV12, params); @@ -159,7 +174,7 @@ index 76b3ea8..0cf71d2 100644 return params; } -@@ -288,7 +324,7 @@ static void dispatch_nudge(struct pipewire_state *state, int fd) +@@ -288,7 +339,7 @@ static void dispatch_nudge(struct pipewire_state *state, int fd) if (s_nCaptureWidth != state->video_info.size.width || s_nCaptureHeight != state->video_info.size.height) { pwr_log.debugf("renegotiating stream params (size: %dx%d)", s_nCaptureWidth, s_nCaptureHeight); @@ -168,7 +183,7 @@ index 76b3ea8..0cf71d2 100644 struct spa_pod_builder builder = SPA_POD_BUILDER_INIT(buf, sizeof(buf)); std::vector format_params = build_format_params(&builder); int ret = pw_stream_update_params(state->stream, format_params.data(), format_params.size()); -@@ -412,6 +448,12 @@ static void stream_handle_param_changed(void *data, uint32_t id, const struct sp +@@ -412,6 +463,12 @@ static void stream_handle_param_changed(void *data, uint32_t id, const struct sp state->video_info.size.width, state->video_info.size.height, s_nRequestedWidth, s_nRequestedHeight, state->video_info.format, state->shm_stride, shm_size, state->dmabuf); @@ -181,7 +196,7 @@ index 76b3ea8..0cf71d2 100644 } static void randname(char *buf) -@@ -450,6 +492,11 @@ uint32_t spa_format_to_drm(uint32_t spa_format) +@@ -450,6 +507,11 @@ uint32_t spa_format_to_drm(uint32_t spa_format) switch (spa_format) { case SPA_VIDEO_FORMAT_NV12: return DRM_FORMAT_NV12; @@ -193,7 +208,7 @@ index 76b3ea8..0cf71d2 100644 default: case SPA_VIDEO_FORMAT_BGR: return DRM_FORMAT_XRGB8888; } -@@ -715,7 +762,7 @@ bool init_pipewire(void) +@@ -715,7 +777,7 @@ bool init_pipewire(void) s_nOutputHeight = g_nOutputHeight; calculate_capture_size();