Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| dab40ed98e |
@@ -51,6 +51,9 @@ const CODECS: &[(&str, &str)] = &[
|
||||
("hevc", "HEVC (H.265)"),
|
||||
("h264", "H.264 (AVC)"),
|
||||
("av1", "AV1"),
|
||||
// Preference-only by design: `resolve_codec` never auto-picks PyroWave, and asking for
|
||||
// it on a host or device that can't do it simply falls back down the ladder to HEVC.
|
||||
("pyrowave", "PyroWave (wired LAN)"),
|
||||
];
|
||||
/// Virtual-pad presets: `(stored value, display label)` — the pad the HOST creates. Same set the
|
||||
/// GTK client offers; "Automatic" resolves from the physical controller at connect.
|
||||
@@ -278,7 +281,7 @@ pub(crate) fn settings_page(
|
||||
s.codec = CODECS[i].0.to_string();
|
||||
})
|
||||
.tooltip(
|
||||
"A soft preference \u{2014} the host falls back to the best codec both sides support.",
|
||||
"A soft preference \u{2014} the host falls back to the best codec both sides support. PyroWave is the low-latency wavelet codec for a WIRED link: it trades bitrate (hundreds of Mb/s) for near-zero decode time, so it needs gigabit Ethernet.",
|
||||
);
|
||||
// Free-form Mb/s (0 = host default) instead of presets, so a speed-test recommendation
|
||||
// round-trips exactly.
|
||||
|
||||
@@ -24,6 +24,14 @@ ffmpeg-next = "8"
|
||||
opus = "0.3"
|
||||
|
||||
mdns-sd = "0.20"
|
||||
|
||||
# PyroWave decode (the opt-in wired-LAN wavelet codec, design/pyrowave-codec-plan.md
|
||||
# §4.5) — pure Vulkan compute on the presenter's shared device, so it builds wherever the
|
||||
# spawned Vulkan session presenter runs: Linux AND Windows (pyrowave-sys covers both; it
|
||||
# is an empty stub elsewhere). `ash` only wraps the presenter's existing raw handles
|
||||
# (same pinned version as pf-presenter).
|
||||
pyrowave-sys = { path = "../pyrowave-sys", optional = true }
|
||||
ash = { version = "0.38", optional = true }
|
||||
# Game-library fetch from the host's management API over mTLS + fingerprint pinning.
|
||||
# `ureq` is small + sync (the host uses it too) and its rustls unifies with the
|
||||
# workspace's (quinn's) 0.23; the pinning verifier mirrors core's private `PinVerify`.
|
||||
@@ -40,11 +48,6 @@ tracing = "0.1"
|
||||
[target.'cfg(target_os = "linux")'.dependencies]
|
||||
pipewire = "0.9"
|
||||
sdl3 = { version = "0.18", features = ["hidapi"] }
|
||||
# PyroWave decode (the opt-in wired-LAN wavelet codec, design/pyrowave-codec-plan.md
|
||||
# §4.5) — pure Vulkan compute on the presenter's shared device. `ash` only wraps the
|
||||
# presenter's existing raw handles (same pinned version as pf-presenter).
|
||||
pyrowave-sys = { path = "../pyrowave-sys", optional = true }
|
||||
ash = { version = "0.38", optional = true }
|
||||
|
||||
[target.'cfg(windows)'.dependencies]
|
||||
wasapi = "0.23"
|
||||
|
||||
@@ -41,11 +41,14 @@ mod video_software;
|
||||
mod video_vaapi;
|
||||
#[cfg(any(target_os = "linux", windows))]
|
||||
mod video_vulkan;
|
||||
// PyroWave decode — Linux + `pyrowave` feature only (plan §4.5; the Windows client's
|
||||
// present-path decision and the Apple Metal port are their own phases).
|
||||
// PyroWave decode — Linux + Windows (plan §4.5; the Apple Metal port is its own phase).
|
||||
// Windows joined once its client moved to the SAME spawned Vulkan session presenter as
|
||||
// Linux's: the decoder is plain Vulkan compute on the presenter's device (no fds, no
|
||||
// dmabuf, no D3D11 interop), so the old "Windows present-path decision" that gated it
|
||||
// resolved itself — the present path is now literally the same code.
|
||||
#[cfg(windows)]
|
||||
pub mod video_d3d11;
|
||||
#[cfg(all(target_os = "linux", feature = "pyrowave"))]
|
||||
#[cfg(all(any(target_os = "linux", windows), feature = "pyrowave"))]
|
||||
pub mod video_pyrowave;
|
||||
|
||||
pub mod wol;
|
||||
|
||||
@@ -227,7 +227,7 @@ fn pump(
|
||||
// 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"))]
|
||||
#[cfg(all(any(target_os = "linux", windows), 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;
|
||||
@@ -296,7 +296,7 @@ fn pump(
|
||||
// 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"))]
|
||||
#[cfg(all(any(target_os = "linux", windows), feature = "pyrowave"))]
|
||||
let built = if connector.codec == punktfunk_core::quic::CODEC_PYROWAVE {
|
||||
let mode = connector.mode();
|
||||
// The wavelet bitstream has no VUI: the negotiated Welcome colour signalling IS
|
||||
@@ -325,7 +325,7 @@ fn pump(
|
||||
} else {
|
||||
Decoder::new(codec_id, ¶ms.decoder, params.vulkan.as_ref())
|
||||
};
|
||||
#[cfg(not(all(target_os = "linux", feature = "pyrowave")))]
|
||||
#[cfg(not(all(any(target_os = "linux", windows), feature = "pyrowave")))]
|
||||
let built = Decoder::new(codec_id, ¶ms.decoder, params.vulkan.as_ref());
|
||||
let mut decoder = match built {
|
||||
Ok(d) => d,
|
||||
@@ -528,7 +528,7 @@ fn pump(
|
||||
DecodedImage::VkFrame(_) => "vulkan",
|
||||
#[cfg(windows)]
|
||||
DecodedImage::D3d11(_) => "d3d11va",
|
||||
#[cfg(all(target_os = "linux", feature = "pyrowave"))]
|
||||
#[cfg(all(any(target_os = "linux", windows), feature = "pyrowave"))]
|
||||
DecodedImage::PyroWave(_) => "pyrowave",
|
||||
};
|
||||
if total_frames == 1 {
|
||||
@@ -539,7 +539,10 @@ fn pump(
|
||||
DecodedImage::VkFrame(v) => (v.width, v.height, "vulkan-video"),
|
||||
#[cfg(windows)]
|
||||
DecodedImage::D3d11(d) => (d.width, d.height, "d3d11va"),
|
||||
#[cfg(all(target_os = "linux", feature = "pyrowave"))]
|
||||
#[cfg(all(
|
||||
any(target_os = "linux", windows),
|
||||
feature = "pyrowave"
|
||||
))]
|
||||
DecodedImage::PyroWave(f) => (f.width, f.height, "pyrowave"),
|
||||
};
|
||||
tracing::info!(width = w, height = h, path, "first frame decoded");
|
||||
|
||||
@@ -77,7 +77,7 @@ pub enum DecodedImage {
|
||||
/// PyroWave planar output: three R8 plane views on the presenter's own device,
|
||||
/// decode already fence-complete, GENERAL layout — the presenter's planar CSC
|
||||
/// samples them directly (BT.709 limited, the codec's fixed colour contract).
|
||||
#[cfg(all(target_os = "linux", feature = "pyrowave"))]
|
||||
#[cfg(all(any(target_os = "linux", windows), feature = "pyrowave"))]
|
||||
PyroWave(crate::video_pyrowave::PyroWavePlanarFrame),
|
||||
}
|
||||
|
||||
@@ -155,7 +155,7 @@ impl DecodedImage {
|
||||
DecodedImage::VkFrame(f) => f.keyframe,
|
||||
#[cfg(windows)]
|
||||
DecodedImage::D3d11(f) => f.keyframe,
|
||||
#[cfg(all(target_os = "linux", feature = "pyrowave"))]
|
||||
#[cfg(all(any(target_os = "linux", windows), feature = "pyrowave"))]
|
||||
DecodedImage::PyroWave(f) => f.keyframe,
|
||||
}
|
||||
}
|
||||
@@ -171,7 +171,7 @@ impl DecodedImage {
|
||||
DecodedImage::VkFrame(f) => (f.width, f.height),
|
||||
#[cfg(windows)]
|
||||
DecodedImage::D3d11(f) => (f.width, f.height),
|
||||
#[cfg(all(target_os = "linux", feature = "pyrowave"))]
|
||||
#[cfg(all(any(target_os = "linux", windows), feature = "pyrowave"))]
|
||||
DecodedImage::PyroWave(f) => (f.width, f.height),
|
||||
}
|
||||
}
|
||||
@@ -238,9 +238,10 @@ enum Backend {
|
||||
#[cfg(windows)]
|
||||
D3d11va(crate::video_d3d11::D3d11vaDecoder),
|
||||
/// PyroWave (wired-LAN wavelet codec): pyrowave compute on the presenter's device,
|
||||
/// no FFmpeg involvement. No demotion rung — there is no other decoder for it.
|
||||
/// no FFmpeg involvement (Linux + Windows — same Vulkan presenter on both). No demotion
|
||||
/// rung — there is no other decoder for it.
|
||||
/// Boxed: the decoder (pinned create-info hold + plane ring) dwarfs the other variants.
|
||||
#[cfg(all(target_os = "linux", feature = "pyrowave"))]
|
||||
#[cfg(all(any(target_os = "linux", windows), feature = "pyrowave"))]
|
||||
PyroWave(Box<crate::video_pyrowave::PyroWaveDecoder>),
|
||||
Software(SoftwareDecoder),
|
||||
}
|
||||
@@ -322,11 +323,11 @@ pub fn decodable_codecs() -> u8 {
|
||||
/// under its explicit opt-in.
|
||||
pub fn decodable_codecs_for(vk: Option<&VulkanDecodeDevice>) -> u8 {
|
||||
let bits = decodable_codecs();
|
||||
#[cfg(all(target_os = "linux", feature = "pyrowave"))]
|
||||
#[cfg(all(any(target_os = "linux", windows), feature = "pyrowave"))]
|
||||
if vk.map(|v| v.pyrowave_decode).unwrap_or(false) {
|
||||
return bits | punktfunk_core::quic::CODEC_PYROWAVE;
|
||||
}
|
||||
#[cfg(not(all(target_os = "linux", feature = "pyrowave")))]
|
||||
#[cfg(not(all(any(target_os = "linux", windows), feature = "pyrowave")))]
|
||||
let _ = vk;
|
||||
bits
|
||||
}
|
||||
@@ -572,7 +573,7 @@ impl Decoder {
|
||||
/// Open a PyroWave decoder for a `CODEC_PYROWAVE` session (plan §4.5): pyrowave
|
||||
/// compute on the presenter's device, no FFmpeg. `codec_id` is irrelevant (kept as
|
||||
/// HEVC so an — impossible — demotion path stays well-formed).
|
||||
#[cfg(all(target_os = "linux", feature = "pyrowave"))]
|
||||
#[cfg(all(any(target_os = "linux", windows), feature = "pyrowave"))]
|
||||
pub fn new_pyrowave(
|
||||
vk: &VulkanDecodeDevice,
|
||||
width: u32,
|
||||
@@ -596,6 +597,15 @@ impl Decoder {
|
||||
vaapi_fails: 0,
|
||||
first_fail: None,
|
||||
want_keyframe: false,
|
||||
// A PyroWave session never demotes (nothing else decodes it — a failure
|
||||
// renegotiates the codec instead), so the D3D11VA rebuild facts are unused
|
||||
// here; keep them well-formed rather than plumbing them in for nothing.
|
||||
#[cfg(windows)]
|
||||
d3d11_import: false,
|
||||
#[cfg(windows)]
|
||||
adapter_luid: None,
|
||||
#[cfg(windows)]
|
||||
d3d11_hdr10: false,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -639,7 +649,7 @@ impl Decoder {
|
||||
au: &[u8],
|
||||
// Only the PyroWave backend reads the flags; without that feature the param is unused.
|
||||
#[cfg_attr(
|
||||
not(all(target_os = "linux", feature = "pyrowave")),
|
||||
not(all(any(target_os = "linux", windows), feature = "pyrowave")),
|
||||
allow(unused_variables)
|
||||
)]
|
||||
user_flags: u32,
|
||||
@@ -657,7 +667,7 @@ impl Decoder {
|
||||
// No demote ladder below PyroWave (nothing else decodes it): propagate the
|
||||
// error; the pump surfaces it and the session falls back to HEVC by
|
||||
// renegotiation (plan §4.6), not by decoder swap.
|
||||
#[cfg(all(target_os = "linux", feature = "pyrowave"))]
|
||||
#[cfg(all(any(target_os = "linux", windows), feature = "pyrowave"))]
|
||||
Backend::PyroWave(p) => {
|
||||
let aligned = user_flags & punktfunk_core::packet::USER_FLAG_CHUNK_ALIGNED != 0;
|
||||
return Ok(p
|
||||
|
||||
@@ -194,7 +194,7 @@ struct StreamState {
|
||||
/// PyroWave present has no demote rung (nothing else decodes the codec), so a
|
||||
/// persistent non-device-lost present failure would warn on every frame. Latch it:
|
||||
/// warn on the first failure of a streak, then stay quiet until a present succeeds.
|
||||
#[cfg(all(target_os = "linux", feature = "pyrowave"))]
|
||||
#[cfg(all(any(target_os = "linux", windows), feature = "pyrowave"))]
|
||||
pyro_present_warned: bool,
|
||||
hw_fails: u32,
|
||||
/// The OSD's text (multi-line; rebuilt each Stats window and on a live tier cycle).
|
||||
@@ -267,7 +267,7 @@ impl StreamState {
|
||||
win_start: Instant::now(),
|
||||
presented: PresentedWindow::default(),
|
||||
dmabuf_demoted: false,
|
||||
#[cfg(all(target_os = "linux", feature = "pyrowave"))]
|
||||
#[cfg(all(any(target_os = "linux", windows), feature = "pyrowave"))]
|
||||
pyro_present_warned: false,
|
||||
hw_fails: 0,
|
||||
osd_text: String::new(),
|
||||
@@ -1007,7 +1007,7 @@ fn run_inner(mut opts: SessionOpts, mut mode: ModeCtl) -> Result<Option<Outcome>
|
||||
// PyroWave planar frames: already on the presenter's device and
|
||||
// fence-complete — a present failure has no demote rung (nothing
|
||||
// else decodes the codec); only device loss ends the session.
|
||||
#[cfg(all(target_os = "linux", feature = "pyrowave"))]
|
||||
#[cfg(all(any(target_os = "linux", windows), feature = "pyrowave"))]
|
||||
DecodedImage::PyroWave(f) => {
|
||||
// The wavelet stream carries the negotiated ColorInfo (no VUI): an
|
||||
// HDR (PQ) pyrowave session presents through the HDR10 path exactly
|
||||
|
||||
@@ -47,7 +47,7 @@ pub enum FrameInput<'a> {
|
||||
D3d11(pf_client_core::video::D3d11Frame),
|
||||
/// PyroWave planar output — three R8 plane views already on THIS device, decode
|
||||
/// fence-complete, GENERAL layout (`pf_client_core::video_pyrowave`).
|
||||
#[cfg(all(target_os = "linux", feature = "pyrowave"))]
|
||||
#[cfg(all(any(target_os = "linux", windows), feature = "pyrowave"))]
|
||||
PyroWave(pf_client_core::video_pyrowave::PyroWavePlanarFrame),
|
||||
}
|
||||
|
||||
@@ -136,7 +136,7 @@ pub struct Presenter {
|
||||
csc: CscPass,
|
||||
/// The planar (3-plane) CSC variant for PyroWave frames; built only when the device
|
||||
/// passed the pyrowave probe.
|
||||
#[cfg(all(target_os = "linux", feature = "pyrowave"))]
|
||||
#[cfg(all(any(target_os = "linux", windows), feature = "pyrowave"))]
|
||||
csc_planar: Option<CscPass>,
|
||||
/// FFmpeg Vulkan Video decode handles — `None` when the stack can't do it.
|
||||
video_export: Option<pf_client_core::video::VulkanDecodeDevice>,
|
||||
@@ -304,7 +304,7 @@ impl Drop for Presenter {
|
||||
#[cfg(target_os = "linux")]
|
||||
self.hw.take();
|
||||
self.csc.destroy(&self.device);
|
||||
#[cfg(all(target_os = "linux", feature = "pyrowave"))]
|
||||
#[cfg(all(any(target_os = "linux", windows), feature = "pyrowave"))]
|
||||
if let Some(p) = &self.csc_planar {
|
||||
p.destroy(&self.device);
|
||||
}
|
||||
|
||||
@@ -38,7 +38,7 @@ impl Presenter {
|
||||
FrameInput::VkFrame(v) => Some(v.color.is_pq()),
|
||||
#[cfg(windows)]
|
||||
FrameInput::D3d11(d) => Some(d.color.is_pq()),
|
||||
#[cfg(all(target_os = "linux", feature = "pyrowave"))]
|
||||
#[cfg(all(any(target_os = "linux", windows), feature = "pyrowave"))]
|
||||
FrameInput::PyroWave(f) => Some(f.color.is_pq()),
|
||||
};
|
||||
if let Some(pq) = frame_pq {
|
||||
@@ -68,7 +68,7 @@ impl Presenter {
|
||||
#[cfg(windows)]
|
||||
let mut win_frame: Option<crate::d3d11::HwFrame> = None;
|
||||
let mut vk_frame: Option<(VkVideoFrame, [vk::ImageView; 2])> = None;
|
||||
#[cfg(all(target_os = "linux", feature = "pyrowave"))]
|
||||
#[cfg(all(any(target_os = "linux", windows), feature = "pyrowave"))]
|
||||
let mut pyro_frame: Option<pf_client_core::video_pyrowave::PyroWavePlanarFrame> = None;
|
||||
let cpu_frame = match input {
|
||||
FrameInput::Redraw => None,
|
||||
@@ -96,7 +96,7 @@ impl Presenter {
|
||||
vk_frame = Some((v, views));
|
||||
None
|
||||
}
|
||||
#[cfg(all(target_os = "linux", feature = "pyrowave"))]
|
||||
#[cfg(all(any(target_os = "linux", windows), feature = "pyrowave"))]
|
||||
FrameInput::PyroWave(f) => {
|
||||
pyro_frame = Some(f);
|
||||
None
|
||||
@@ -155,7 +155,7 @@ impl Presenter {
|
||||
}
|
||||
self.csc.bind_planes(&self.device, views[0], views[1]);
|
||||
}
|
||||
#[cfg(all(target_os = "linux", feature = "pyrowave"))]
|
||||
#[cfg(all(any(target_os = "linux", windows), feature = "pyrowave"))]
|
||||
if let Some(f) = &pyro_frame {
|
||||
if self
|
||||
.video
|
||||
@@ -317,7 +317,7 @@ impl Presenter {
|
||||
// PyroWave frame: the planes are already on THIS device, decode
|
||||
// fence-complete and barriered to fragment sampling (GENERAL) by the
|
||||
// decoder — no acquire needed, just the planar CSC pass.
|
||||
#[cfg(all(target_os = "linux", feature = "pyrowave"))]
|
||||
#[cfg(all(any(target_os = "linux", windows), feature = "pyrowave"))]
|
||||
if let (Some(f), Some(v)) = (&pyro_frame, &self.video) {
|
||||
let extent = vk::Extent2D {
|
||||
width: v.width,
|
||||
@@ -695,7 +695,7 @@ impl Presenter {
|
||||
}
|
||||
|
||||
/// [`record_csc`] over the planar (PyroWave) pass — always 8-bit, no MSB packing.
|
||||
#[cfg(all(target_os = "linux", feature = "pyrowave"))]
|
||||
#[cfg(all(any(target_os = "linux", windows), feature = "pyrowave"))]
|
||||
unsafe fn record_csc_planar(
|
||||
&self,
|
||||
framebuffer: vk::Framebuffer,
|
||||
|
||||
@@ -207,7 +207,7 @@ impl Presenter {
|
||||
// The planar (PyroWave) pass renders to the same intermediate — rebuild it at the
|
||||
// new format too (an HDR pyrowave session needs the 10-bit intermediate exactly
|
||||
// like the H.26x path; 8-bit PQ bands visibly).
|
||||
#[cfg(all(target_os = "linux", feature = "pyrowave"))]
|
||||
#[cfg(all(any(target_os = "linux", windows), feature = "pyrowave"))]
|
||||
if let Some(p) = self.csc_planar.take() {
|
||||
p.destroy(&self.device);
|
||||
self.csc_planar = Some(CscPass::new_planar(&self.device, self.video_format)?);
|
||||
|
||||
@@ -318,7 +318,7 @@ impl Presenter {
|
||||
let csc = CscPass::new(&device, vk::Format::R8G8B8A8_UNORM)?;
|
||||
// Starts SDR like `csc`; an HDR (PQ) pyrowave session rebuilds it at the 10-bit
|
||||
// intermediate via `set_hdr_mode`, exactly like the H.26x pass.
|
||||
#[cfg(all(target_os = "linux", feature = "pyrowave"))]
|
||||
#[cfg(all(any(target_os = "linux", windows), feature = "pyrowave"))]
|
||||
let csc_planar = if pyrowave_ok {
|
||||
Some(CscPass::new_planar(&device, vk::Format::R8G8B8A8_UNORM)?)
|
||||
} else {
|
||||
@@ -463,7 +463,7 @@ impl Presenter {
|
||||
#[cfg(windows)]
|
||||
hw_win,
|
||||
csc,
|
||||
#[cfg(all(target_os = "linux", feature = "pyrowave"))]
|
||||
#[cfg(all(any(target_os = "linux", windows), feature = "pyrowave"))]
|
||||
csc_planar,
|
||||
video_export,
|
||||
overlay_pipe,
|
||||
|
||||
Reference in New Issue
Block a user