From 8dc5d672e2c799ea19185677e39281b1a6a626eb Mon Sep 17 00:00:00 2001 From: enricobuehler Date: Wed, 15 Jul 2026 09:30:59 +0200 Subject: [PATCH] feat(host): PyroWave capture advertises the Vulkan device's dmabuf modifiers MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The pyrowave passthrough rode VAAPI's LINEAR-only modifier policy, which starves it on Mutter+NVIDIA (tiled-only allocations → the compositor declines the offer → CPU capture fallback). The encoder imports through VK_EXT_image_drm_format_modifier, not libva, so the capture now extends the advertisement with every single-memory-plane modifier the PyroWave device samples from (probed via DrmFormatModifierPropertiesListEXT with the same device selection as the encoder). Live on .21 (Mutter+NVIDIA, RTX 5070 Ti): 7 modifiers advertised, the compositor negotiated block-linear (216172782120099861), no CPU downgrade, and the encoder's per-buffer import cache populated exactly as designed (8 PipeWire pool buffers imported once, silent reuse after). Zero-copy session numbers: static 60 fps, e2e 2.9-3.0 ms p50 (p95 3.4), host stage 1.6 ms; full-window motion 60 fps at ~80 Mb/s all-intra, decode ~1 ms. Also neutralizes the VAAPI-specific wording in the passthrough hand-off log. Co-Authored-By: Claude Fable 5 --- .../punktfunk-host/src/capture/linux/mod.rs | 19 +++++- crates/punktfunk-host/src/encode.rs | 8 +++ .../src/encode/linux/pyrowave.rs | 66 +++++++++++++++++++ 3 files changed, 92 insertions(+), 1 deletion(-) diff --git a/crates/punktfunk-host/src/capture/linux/mod.rs b/crates/punktfunk-host/src/capture/linux/mod.rs index 4ec46455..5290eb23 100644 --- a/crates/punktfunk-host/src/capture/linux/mod.rs +++ b/crates/punktfunk-host/src/capture/linux/mod.rs @@ -998,7 +998,7 @@ mod pipewire { h, modifier = ud.modifier, fourcc = format_args!("{:#010x}", fourcc), - "zero-copy: handing dmabuf to VAAPI (GPU import + CSC)" + "zero-copy: handing the raw dmabuf to the encoder (GPU import + CSC)" ); } return; @@ -1328,6 +1328,23 @@ mod pipewire { if (importer.is_some() || vaapi_passthrough) && !modifiers.contains(&0) { modifiers.push(0); // DRM_FORMAT_MOD_LINEAR } + // PyroWave passthrough: the encoder imports through Vulkan, not libva — extend the + // advertisement with every modifier its device samples from, so compositors that + // never allocate LINEAR (Mutter+NVIDIA) still negotiate zero-copy dmabufs. + #[cfg(feature = "pyrowave")] + if vaapi_passthrough && crate::config::config().encoder_pref.as_str() == "pyrowave" { + for m in crate::encode::pyrowave_capture_modifiers( + crate::zerocopy::drm_fourcc(PixelFormat::Bgrx).unwrap(), + ) { + if !modifiers.contains(&m) { + modifiers.push(m); + } + } + tracing::info!( + count = modifiers.len(), + "zero-copy: advertising the PyroWave device's Vulkan-importable dmabuf modifiers" + ); + } let want_dmabuf = (importer.is_some() || vaapi_passthrough) && !modifiers.is_empty() && !force_shm; if force_shm { diff --git a/crates/punktfunk-host/src/encode.rs b/crates/punktfunk-host/src/encode.rs index 2c2b5344..6b69ba8a 100644 --- a/crates/punktfunk-host/src/encode.rs +++ b/crates/punktfunk-host/src/encode.rs @@ -1023,6 +1023,14 @@ fn linux_auto_is_vaapi() -> bool { !nvidia_present() } +/// The dmabuf modifiers the PyroWave encoder's Vulkan device imports for the capture's +/// packed-RGB fourcc — advertised by the capture when the pyrowave passthrough is active +/// (the VAAPI LINEAR-only policy starves it on Mutter+NVIDIA, which allocates tiled only). +#[cfg(all(target_os = "linux", feature = "pyrowave"))] +pub(crate) fn pyrowave_capture_modifiers(fourcc: u32) -> Vec { + pyrowave::capture_modifiers(fourcc) +} + /// True if the Linux GPU encode backend resolves to VAAPI (AMD/Intel) rather than NVENC — mirrors /// [`open_video`]'s dispatch so the capturer can choose the matching zero-copy path (raw dmabuf /// passthrough for VAAPI vs the EGL→CUDA import for NVENC). diff --git a/crates/punktfunk-host/src/encode/linux/pyrowave.rs b/crates/punktfunk-host/src/encode/linux/pyrowave.rs index 0250ab18..4793c22d 100644 --- a/crates/punktfunk-host/src/encode/linux/pyrowave.rs +++ b/crates/punktfunk-host/src/encode/linux/pyrowave.rs @@ -44,6 +44,72 @@ const IMPORT_CACHE_CAP: usize = 16; /// the rate controller itself never exceeds the budget). const BS_SLACK: usize = 256 * 1024; +/// The DRM modifiers the PyroWave device can import as a SAMPLED image of the capture's +/// packed-RGB format. The capture advertises these for the pyrowave passthrough instead of +/// VAAPI's LINEAR-only policy — Mutter+NVIDIA never allocates LINEAR, but its tiled +/// dmabufs import fine through `VK_EXT_image_drm_format_modifier` (validated by upstream's +/// interop test). Instance + physical device only; probed per session setup (cheap). +pub(crate) fn capture_modifiers(fourcc: u32) -> Vec { + let Some(fmt) = super::vk_util::fourcc_to_vk(fourcc) else { + return Vec::new(); + }; + // SAFETY: fresh instance, plain physical-device property queries, destroyed before + // returning; nothing borrows across the call. + unsafe { + let Ok(entry) = ash::Entry::load() else { + return Vec::new(); + }; + let app = vk::ApplicationInfo::default().api_version(vk::API_VERSION_1_3); + let Ok(instance) = entry.create_instance( + &vk::InstanceCreateInfo::default().application_info(&app), + None, + ) else { + return Vec::new(); + }; + // Same device selection as `open_inner`: the first real GPU with graphics+compute. + let pd = instance + .enumerate_physical_devices() + .unwrap_or_default() + .into_iter() + .find(|&pd| { + instance.get_physical_device_properties(pd).device_type + != vk::PhysicalDeviceType::CPU + && instance + .get_physical_device_queue_family_properties(pd) + .iter() + .any(|q| { + q.queue_flags + .contains(vk::QueueFlags::GRAPHICS | vk::QueueFlags::COMPUTE) + }) + }); + let mods = pd + .map(|pd| { + let mut list = vk::DrmFormatModifierPropertiesListEXT::default(); + let mut fp2 = vk::FormatProperties2::default().push_next(&mut list); + instance.get_physical_device_format_properties2(pd, fmt, &mut fp2); + let n = list.drm_format_modifier_count as usize; + let mut props = vec![vk::DrmFormatModifierPropertiesEXT::default(); n]; + list.p_drm_format_modifier_properties = props.as_mut_ptr(); + let mut fp2 = vk::FormatProperties2::default().push_next(&mut list); + instance.get_physical_device_format_properties2(pd, fmt, &mut fp2); + props.truncate(list.drm_format_modifier_count as usize); + props + .into_iter() + .filter(|p| { + p.drm_format_modifier_tiling_features + .contains(vk::FormatFeatureFlags::SAMPLED_IMAGE) + // Single-memory-plane only: the capture hands one fd/offset/stride. + && p.drm_format_modifier_plane_count == 1 + }) + .map(|p| p.drm_format_modifier) + .collect() + }) + .unwrap_or_default(); + instance.destroy_instance(None); + mods + } +} + fn pw_check(r: pw::pyrowave_result, what: &str) -> Result<()> { if r == pw::pyrowave_result_PYROWAVE_SUCCESS { Ok(())