diff --git a/crates/pf-encode/src/enc/linux/pyrowave.rs b/crates/pf-encode/src/enc/linux/pyrowave.rs index 3d0444bb..86a66b6f 100644 --- a/crates/pf-encode/src/enc/linux/pyrowave.rs +++ b/crates/pf-encode/src/enc/linux/pyrowave.rs @@ -314,7 +314,9 @@ struct DeviceHold { instance_ci: Box>, _queue_prio: Box<[f32; 1]>, _queue_ci: Box<[vk::DeviceQueueCreateInfo<'static>; 1]>, - _dev_exts: Box<[*const c_char; 3]>, + // A plain Vec (not Box<[_; N]> like its siblings): Phase 8 pushes queue_family_foreign + // conditionally. The heap buffer as_ptr() feeds device_ci is move-stable like the Boxes. + _dev_exts: Vec<*const c_char>, _feat2: Box>, _v12: Box>, _v13: Box>, @@ -329,6 +331,9 @@ pub struct PyroWaveEncoder { ext_fd: ash::khr::external_memory_fd::Device, queue: vk::Queue, family: u32, + /// `src` family for the fresh-dmabuf acquire barrier: FOREIGN when the extension is + /// enabled, else the core EXTERNAL substitute (Phase 8 — see `open_inner`). + foreign_qfi: u32, mem_props: vk::PhysicalDeviceMemoryProperties, _hold: DeviceHold, @@ -452,11 +457,11 @@ impl PyroWaveEncoder { instance_ci: Box::new(vk::InstanceCreateInfo::default()), _queue_prio: Box::new([1.0f32]), _queue_ci: Box::new([vk::DeviceQueueCreateInfo::default()]), - _dev_exts: Box::new([ + _dev_exts: vec![ ash::khr::external_memory_fd::NAME.as_ptr(), ash::ext::external_memory_dma_buf::NAME.as_ptr(), ash::ext::image_drm_format_modifier::NAME.as_ptr(), - ]), + ], _feat2: Box::new(vk::PhysicalDeviceFeatures2::default()), _v12: Box::new(vk::PhysicalDeviceVulkan12Features::default()), _v13: Box::new(vk::PhysicalDeviceVulkan13Features::default()), @@ -548,6 +553,28 @@ impl PyroWaveEncoder { hold._feat2.p_next = &mut *hold._v12 as *mut _ as *mut std::ffi::c_void; hold._v12.p_next = &mut *hold._v13 as *mut _ as *mut std::ffi::c_void; + // VK_EXT_queue_family_foreign (Phase 8): the fresh-import acquire barrier names + // FOREIGN as src — enable the extension when advertised (`pf-presenter/dmabuf.rs` + // precedent), else fall back to the core QUEUE_FAMILY_EXTERNAL substitute. Must be + // pushed BEFORE the count/as_ptr wiring below. + let dev_ext_props = instance + .enumerate_device_extension_properties(pd) + .unwrap_or_default(); + let foreign_qfi = if crate::vk_util::ext_advertised( + &dev_ext_props, + ash::ext::queue_family_foreign::NAME, + ) { + hold._dev_exts + .push(ash::ext::queue_family_foreign::NAME.as_ptr()); + vk::QUEUE_FAMILY_FOREIGN_EXT + } else { + tracing::warn!( + "pyrowave: VK_EXT_queue_family_foreign not advertised — dmabuf acquires \ + use the core QUEUE_FAMILY_EXTERNAL substitute (no fleet hardware takes \ + this arm; report it)" + ); + vk::QUEUE_FAMILY_EXTERNAL + }; hold._queue_ci[0] = vk::DeviceQueueCreateInfo::default().queue_family_index(family); hold._queue_ci[0].queue_count = 1; hold._queue_ci[0].p_queue_priorities = hold._queue_prio.as_ptr(); @@ -560,9 +587,9 @@ impl PyroWaveEncoder { let device = instance .create_device(pd, &hold.device_ci, None) .context("create device")?; - Ok((pd, family, device)) + Ok((pd, family, device, foreign_qfi)) })(); - let (pd, family, device) = match selected { + let (pd, family, device, foreign_qfi) = match selected { Ok(v) => v, Err(e) => { instance.destroy_instance(None); @@ -588,6 +615,7 @@ impl PyroWaveEncoder { ext_fd, queue, family, + foreign_qfi, mem_props, _hold: hold, pw_dev: std::ptr::null_mut(), @@ -1163,11 +1191,7 @@ impl PyroWaveEncoder { FramePayload::Dmabuf(d) => { let (img, view, fresh) = self.import_cached(d, frame.width, frame.height)?; let (old, src_qf, dst_qf) = if fresh { - ( - vk::ImageLayout::UNDEFINED, - vk::QUEUE_FAMILY_FOREIGN_EXT, - self.family, - ) + (vk::ImageLayout::UNDEFINED, self.foreign_qfi, self.family) } else { ( vk::ImageLayout::SHADER_READ_ONLY_OPTIMAL, diff --git a/crates/pf-encode/src/enc/linux/vk_util.rs b/crates/pf-encode/src/enc/linux/vk_util.rs index fe9363ad..a46d9335 100644 --- a/crates/pf-encode/src/enc/linux/vk_util.rs +++ b/crates/pf-encode/src/enc/linux/vk_util.rs @@ -7,6 +7,18 @@ use anyhow::Result; use ash::vk; use pf_frame::PixelFormat; +/// Whether a device extension is in an enumerated properties list — the gate both Vulkan encode +/// backends use before enabling `VK_EXT_queue_family_foreign` (Phase 8: the FOREIGN queue-family +/// barriers were used without the extension ever being enabled; `pf-presenter/dmabuf.rs` is the +/// in-repo precedent that enables it). +pub(super) fn ext_advertised(exts: &[vk::ExtensionProperties], name: &std::ffi::CStr) -> bool { + exts.iter().any(|e| { + // SAFETY: `extension_name` is a spec-guaranteed NUL-terminated UTF-8 byte array inside + // the driver-filled `VkExtensionProperties` (VK_MAX_EXTENSION_NAME_SIZE bound). + unsafe { std::ffi::CStr::from_ptr(e.extension_name.as_ptr()) == name } + }) +} + pub(crate) fn color_range(layer: u32) -> vk::ImageSubresourceRange { vk::ImageSubresourceRange { aspect_mask: vk::ImageAspectFlags::COLOR, @@ -398,6 +410,24 @@ pub(crate) unsafe fn make_plain_image( #[cfg(test)] mod tests { + #[test] + fn ext_advertised_matches_exact_name() { + let mut e = ash::vk::ExtensionProperties::default(); + let name = b"VK_EXT_queue_family_foreign\0"; + for (i, b) in name.iter().enumerate() { + e.extension_name[i] = *b as std::ffi::c_char; + } + let exts = [ash::vk::ExtensionProperties::default(), e]; + assert!(super::ext_advertised( + &exts, + ash::ext::queue_family_foreign::NAME + )); + assert!(!super::ext_advertised( + &exts[..1], + ash::ext::queue_family_foreign::NAME + )); + } + use super::*; /// CSC mode (`bgra_target = false`): the 3→4 expand is a pure byte shuffle — no channel diff --git a/crates/pf-encode/src/enc/linux/vulkan_video.rs b/crates/pf-encode/src/enc/linux/vulkan_video.rs index 416e2088..85b944c0 100644 --- a/crates/pf-encode/src/enc/linux/vulkan_video.rs +++ b/crates/pf-encode/src/enc/linux/vulkan_video.rs @@ -508,6 +508,12 @@ pub struct VulkanVideoEncoder { compute_queue: vk::Queue, encode_family: u32, compute_family: u32, + /// The queue family the dmabuf-acquire barriers name as `src`: `QUEUE_FAMILY_FOREIGN_EXT` + /// when `VK_EXT_queue_family_foreign` is advertised+enabled (Phase 8 — the barriers used it + /// without the enable, tolerated by RADV), else the core-1.1 `QUEUE_FAMILY_EXTERNAL` + /// conservative substitute (adds a same-driver precondition FOREIGN doesn't have — the + /// pragmatic fallback for devices that were never valid targets before). + foreign_qfi: u32, mem_props: vk::PhysicalDeviceMemoryProperties, // --- codec --- @@ -995,6 +1001,24 @@ impl VulkanVideoEncoder { } else { bitrate }; + // VK_EXT_queue_family_foreign: enable when advertised so the dmabuf-acquire barriers' + // FOREIGN src family is spec-legal (Phase 8; `pf-presenter/dmabuf.rs` precedent). The + // rgb probe's enumerate is a probe-local (and skipped on native-NV12), so this is a + // fresh open-time query. + let dev_ext_props = instance + .enumerate_device_extension_properties(pd) + .unwrap_or_default(); + let foreign_ok = + crate::vk_util::ext_advertised(&dev_ext_props, ash::ext::queue_family_foreign::NAME); + let foreign_qfi = if foreign_ok { + vk::QUEUE_FAMILY_FOREIGN_EXT + } else { + tracing::warn!( + "VK_EXT_queue_family_foreign not advertised — dmabuf acquires use the core \ + QUEUE_FAMILY_EXTERNAL substitute (this arm has no fleet hardware; report it)" + ); + vk::QUEUE_FAMILY_EXTERNAL + }; // logical device: encode + compute queues + video extensions (AV1 ext name is raw — ash lacks it) let mut dev_exts = vec![ ash::khr::video_queue::NAME.as_ptr(), @@ -1011,6 +1035,9 @@ impl VulkanVideoEncoder { if rgb_cfg.is_some() { dev_exts.push(vrgb::EXTENSION_NAME.as_ptr()); } + if foreign_ok { + dev_exts.push(ash::ext::queue_family_foreign::NAME.as_ptr()); + } let prio = [1.0f32]; let mut qcis = vec![vk::DeviceQueueCreateInfo::default() .queue_family_index(encode_family) @@ -1383,6 +1410,7 @@ impl VulkanVideoEncoder { encode_queue, compute_queue, encode_family, + foreign_qfi, compute_family, mem_props, codec, @@ -2083,7 +2111,7 @@ impl VulkanVideoEncoder { let (old, src_qf, dst_qf) = if fresh { ( vk::ImageLayout::UNDEFINED, - vk::QUEUE_FAMILY_FOREIGN_EXT, + self.foreign_qfi, self.compute_family, ) } else { @@ -2432,7 +2460,7 @@ impl VulkanVideoEncoder { .dst_access_mask(vk::AccessFlags2::TRANSFER_READ) .old_layout(vk::ImageLayout::UNDEFINED) .new_layout(vk::ImageLayout::TRANSFER_SRC_OPTIMAL) - .src_queue_family_index(vk::QUEUE_FAMILY_FOREIGN_EXT) + .src_queue_family_index(self.foreign_qfi) .dst_queue_family_index(self.compute_family) } else { vk::ImageMemoryBarrier2::default() @@ -2973,7 +3001,7 @@ impl VulkanVideoEncoder { .src_stage_mask(vk::PipelineStageFlags2::NONE) .src_access_mask(vk::AccessFlags2::NONE) .old_layout(vk::ImageLayout::UNDEFINED) - .src_queue_family_index(vk::QUEUE_FAMILY_FOREIGN_EXT) + .src_queue_family_index(self.foreign_qfi) .dst_queue_family_index(self.encode_family), SrcAcquire::DmabufCached => src_base .src_stage_mask(vk::PipelineStageFlags2::NONE)