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:
@@ -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,
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user