diff --git a/crates/punktfunk-host/src/gamestream/rtsp.rs b/crates/punktfunk-host/src/gamestream/rtsp.rs index 5f155178..8bf85dea 100644 --- a/crates/punktfunk-host/src/gamestream/rtsp.rs +++ b/crates/punktfunk-host/src/gamestream/rtsp.rs @@ -450,6 +450,25 @@ fn stream_config(map: &HashMap) -> Option { ); hdr = false; } + // Second half of the same live gate: the process-wide HDR-capture latch. The colour-mode probe + // above only says the monitor is in BT.2100 RIGHT NOW — it says nothing about whether the + // portal will actually hand us the 10-bit PQ formats. Once a `want_hdr` negotiation has failed + // (the monitor left HDR mode between probe and negotiation, NVIDIA EGL not listing LINEAR for + // XR30, a pre-50 Mutter), `pf_capture::open_portal_monitor` permanently drops the HDR OFFER and + // captures SDR. Without this check we'd keep telling the client HDR while capturing and + // encoding SDR: it renders an SDR picture through PQ — washed out, wrong gamut, no error + // anywhere — and because the latch is sticky until host restart, every reconnect repeats it. + // Consulted here rather than folded into `host_hdr_capable` for the same reason as the probe + // above: that fn is the STATIC serverinfo capability, and this is a live per-session fact. + #[cfg(target_os = "linux")] + if hdr && pf_capture::hdr_capture_failed() { + tracing::warn!( + "client requested HDR and a monitor is in BT.2100 (HDR) colour mode, but an earlier \ + HDR capture negotiation on this host failed — the capturer offers SDR only for the \ + rest of the process lifetime, so streaming 8-bit SDR (restart the host to retry HDR)" + ); + hdr = false; + } // The client's requested CSC (moonlight-common-c SdpGenerator.c: `encoderCscMode = // (colorspace << 1) | fullRange` — colorspace 0=Rec601, 1=Rec709, 2=Rec2020). Moonlight // renderers configure their YUV→RGB from this REQUESTED value (not the bitstream VUI), so a diff --git a/crates/punktfunk-host/src/native/handshake.rs b/crates/punktfunk-host/src/native/handshake.rs index 067d7146..2a9da219 100644 --- a/crates/punktfunk-host/src/native/handshake.rs +++ b/crates/punktfunk-host/src/native/handshake.rs @@ -267,6 +267,12 @@ pub(super) async fn negotiate( // *monitor* streams only — the GameStream portal-mirror path uses that; see // `gamestream::host_hdr_capable`), so a Linux native session honestly stays 8-bit SDR even // though `can_encode_10bit` now probes true on a Main10-capable GPU. + // + // That `false` is also why this plane needs no equivalent of the GameStream path's + // `pf_capture::hdr_capture_failed()` check (rtsp.rs): that latch is a fact about the PORTAL + // capturer's HDR offer, and the native plane captures a virtual output instead — it never + // reaches the portal path, and on Linux it never negotiates 10-bit at all. Revisit both halves + // together if Mutter ever gains HDR for RecordVirtual streams. let capture_supports_hdr = crate::capture::capturer_supports_hdr(); // The GPU probe may open a tiny encoder on first use, so run it off the reactor like the // 4:4:4 probe below (blocking probes → spawn_blocking), short-circuited behind the cheap