From fa4df1de9e7b8351280749bd480e57468b4ebbe3 Mon Sep 17 00:00:00 2001 From: enricobuehler Date: Wed, 15 Jul 2026 01:48:36 +0200 Subject: [PATCH] =?UTF-8?q?feat(client):=20PyroWave=20session=20wiring=20?= =?UTF-8?q?=E2=80=94=20advertisement,=20opt-in,=20decoder=20selection=20(P?= =?UTF-8?q?hase=202b,=20part=203)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The pump now advertises decodable_codecs_for(presenter device) — the CODEC_PYROWAVE bit rides only when the device passed the compute-feature probe — and PUNKTFUNK_PREFER_PYROWAVE=1 is the Phase-2 lab opt-in that names the codec in preferred_codec (the only route resolve_codec will take it, plan §3; a Settings toggle is Phase-3 productization). A negotiated PyroWave session builds Decoder::new_pyrowave on the presenter's device instead of an FFmpeg decoder. clients/session grows the `pyrowave` feature forwarding both crate features. With this the Phase-2 client chain is code-complete: Hello bit → preference → Welcome::codec → pyrowave decode on the presenter device → planar CSC → present. On-glass .21 run + latency-probe/loss-harness numbers vs HEVC remain owed (plan Phase-2 exit criteria). Validated on .21: session client + all crates compile with and without the features, clippy clean, 26 + 308 tests green. Co-Authored-By: Claude Fable 5 --- clients/session/Cargo.toml | 3 +++ crates/pf-client-core/src/session.rs | 38 +++++++++++++++++++++++++--- 2 files changed, 38 insertions(+), 3 deletions(-) diff --git a/clients/session/Cargo.toml b/clients/session/Cargo.toml index 0144d2ae..1de25b18 100644 --- a/clients/session/Cargo.toml +++ b/clients/session/Cargo.toml @@ -14,6 +14,9 @@ path = "src/main.rs" [features] default = ["ui"] +# PyroWave client decode (the opt-in wired-LAN wavelet codec) — enables the decode +# backend + the planar present path; runtime opt-in stays PUNKTFUNK_PREFER_PYROWAVE=1. +pyrowave = ["pf-client-core/pyrowave", "pf-presenter/pyrowave"] # The Skia console UI (stats OSD, capture HUD, later the gamepad library). Dropping it # (`--no-default-features`) is the ~15 MB-smaller power-user build: same streaming, # stats on stdout only. diff --git a/crates/pf-client-core/src/session.rs b/crates/pf-client-core/src/session.rs index eb371b13..2dd44f03 100644 --- a/crates/pf-client-core/src/session.rs +++ b/crates/pf-client-core/src/session.rs @@ -211,6 +211,21 @@ fn pump( frame_tx: async_channel::Sender, stop: Arc, ) { + // PUNKTFUNK_PREFER_PYROWAVE=1 — the Phase-2 lab opt-in for the wired-LAN wavelet codec + // (a Settings toggle is the Phase-3 productization). Riding `preferred_codec` is exactly + // the plan-§3 contract: the host only ever picks PyroWave when the client names it. + #[allow(unused_mut)] + let mut preferred = params.preferred_codec; + #[cfg(all(target_os = "linux", feature = "pyrowave"))] + if std::env::var("PUNKTFUNK_PREFER_PYROWAVE").as_deref() == Ok("1") { + if params.vulkan.as_ref().is_some_and(|v| v.pyrowave_decode) { + preferred = punktfunk_core::quic::CODEC_PYROWAVE; + } else { + tracing::warn!( + "PUNKTFUNK_PREFER_PYROWAVE=1 but the presenter device failed the pyrowave probe — keeping the normal codec preference" + ); + } + } let connector = match NativeClient::connect( ¶ms.host, params.port, @@ -220,8 +235,9 @@ fn pump( params.bitrate_kbps, params.video_caps, params.audio_channels, - crate::video::decodable_codecs(), // codecs FFmpeg can decode (HEVC/H.264/AV1) - params.preferred_codec, // the user's soft codec preference (0 = auto) + // FFmpeg's codecs plus CODEC_PYROWAVE when the presenter device passed the probe. + crate::video::decodable_codecs_for(params.vulkan.as_ref()), + preferred, // the user's soft codec preference (0 = auto; see the pyrowave opt-in above) // This display's HDR volume → the host's virtual-display EDID. The env hatch wins so an // A/B run can pin an exact peak (PUNKTFUNK_CLIENT_PEAK_NITS=600). punktfunk_core::client::display_hdr_env_override().or(params.display_hdr), @@ -262,7 +278,23 @@ fn pump( welcome_codec = connector.codec, "negotiated video codec" ); - let mut decoder = match Decoder::new(codec_id, ¶ms.decoder, params.vulkan.as_ref()) { + let built = 'decoder: { + // A negotiated PyroWave session decodes on the presenter's device, no FFmpeg — + // reachable only through the explicit preference above (resolve_codec never + // auto-picks the bit), so failing loudly here is failing an opted-in experiment. + #[cfg(all(target_os = "linux", feature = "pyrowave"))] + if connector.codec == punktfunk_core::quic::CODEC_PYROWAVE { + let mode = connector.mode(); + break 'decoder match params.vulkan.as_ref() { + Some(vk) => Decoder::new_pyrowave(vk, mode.width, mode.height), + None => Err(anyhow::anyhow!( + "pyrowave session without a presenter device" + )), + }; + } + Decoder::new(codec_id, ¶ms.decoder, params.vulkan.as_ref()) + }; + let mut decoder = match built { Ok(d) => d, Err(e) => { let _ = ev_tx.send_blocking(SessionEvent::Ended(Some(format!("video decoder: {e}"))));