fix(pf-encode): enable VK_EXT_queue_family_foreign for the dmabuf acquires (Phase 8)

Both Linux Vulkan encode backends named QUEUE_FAMILY_FOREIGN_EXT as the
acquire barriers' src family without ever enabling the extension —
spec-invalid on every device, tolerated by RADV. The audit filed
vulkan_video's three sites; pyrowave's fresh-import acquire had the
identical defect on its own device (critic catch).

Enable when advertised (a fresh open-time enumerate — the rgb probe's
is a probe-local and skipped entirely on native-NV12, so there was
nothing to reuse; pf-presenter/dmabuf.rs is the in-repo precedent that
already enables this extension). Not advertised → the core-1.1
QUEUE_FAMILY_EXTERNAL conservative substitute, chosen once at open and
warn-logged (no fleet hardware takes that arm; such devices were never
valid targets before). All four sites are acquire-only (src=FOREIGN,
EXCLUSIVE images, oldLayout=UNDEFINED) — the swap is index-only.

On-glass: 780M under validation layers — vulkan smokes + pyrowave
smokes green, FOREIGN advertised and enabled, no fallback engaged.
Shared ext_advertised helper in vk_util (cfg = the union of both
consumers) with a unit test.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-07-26 10:08:44 +02:00
co-authored by Claude Opus 5
parent 1cefd37603
commit bf9fb3fb22
3 changed files with 95 additions and 13 deletions
+34 -10
View File
@@ -314,7 +314,9 @@ struct DeviceHold {
instance_ci: Box<vk::InstanceCreateInfo<'static>>,
_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<vk::PhysicalDeviceFeatures2<'static>>,
_v12: Box<vk::PhysicalDeviceVulkan12Features<'static>>,
_v13: Box<vk::PhysicalDeviceVulkan13Features<'static>>,
@@ -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,
+30
View File
@@ -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
+31 -3
View File
@@ -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)