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>>,
|
instance_ci: Box<vk::InstanceCreateInfo<'static>>,
|
||||||
_queue_prio: Box<[f32; 1]>,
|
_queue_prio: Box<[f32; 1]>,
|
||||||
_queue_ci: Box<[vk::DeviceQueueCreateInfo<'static>; 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>>,
|
_feat2: Box<vk::PhysicalDeviceFeatures2<'static>>,
|
||||||
_v12: Box<vk::PhysicalDeviceVulkan12Features<'static>>,
|
_v12: Box<vk::PhysicalDeviceVulkan12Features<'static>>,
|
||||||
_v13: Box<vk::PhysicalDeviceVulkan13Features<'static>>,
|
_v13: Box<vk::PhysicalDeviceVulkan13Features<'static>>,
|
||||||
@@ -329,6 +331,9 @@ pub struct PyroWaveEncoder {
|
|||||||
ext_fd: ash::khr::external_memory_fd::Device,
|
ext_fd: ash::khr::external_memory_fd::Device,
|
||||||
queue: vk::Queue,
|
queue: vk::Queue,
|
||||||
family: u32,
|
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,
|
mem_props: vk::PhysicalDeviceMemoryProperties,
|
||||||
_hold: DeviceHold,
|
_hold: DeviceHold,
|
||||||
|
|
||||||
@@ -452,11 +457,11 @@ impl PyroWaveEncoder {
|
|||||||
instance_ci: Box::new(vk::InstanceCreateInfo::default()),
|
instance_ci: Box::new(vk::InstanceCreateInfo::default()),
|
||||||
_queue_prio: Box::new([1.0f32]),
|
_queue_prio: Box::new([1.0f32]),
|
||||||
_queue_ci: Box::new([vk::DeviceQueueCreateInfo::default()]),
|
_queue_ci: Box::new([vk::DeviceQueueCreateInfo::default()]),
|
||||||
_dev_exts: Box::new([
|
_dev_exts: vec![
|
||||||
ash::khr::external_memory_fd::NAME.as_ptr(),
|
ash::khr::external_memory_fd::NAME.as_ptr(),
|
||||||
ash::ext::external_memory_dma_buf::NAME.as_ptr(),
|
ash::ext::external_memory_dma_buf::NAME.as_ptr(),
|
||||||
ash::ext::image_drm_format_modifier::NAME.as_ptr(),
|
ash::ext::image_drm_format_modifier::NAME.as_ptr(),
|
||||||
]),
|
],
|
||||||
_feat2: Box::new(vk::PhysicalDeviceFeatures2::default()),
|
_feat2: Box::new(vk::PhysicalDeviceFeatures2::default()),
|
||||||
_v12: Box::new(vk::PhysicalDeviceVulkan12Features::default()),
|
_v12: Box::new(vk::PhysicalDeviceVulkan12Features::default()),
|
||||||
_v13: Box::new(vk::PhysicalDeviceVulkan13Features::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._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;
|
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] = vk::DeviceQueueCreateInfo::default().queue_family_index(family);
|
||||||
hold._queue_ci[0].queue_count = 1;
|
hold._queue_ci[0].queue_count = 1;
|
||||||
hold._queue_ci[0].p_queue_priorities = hold._queue_prio.as_ptr();
|
hold._queue_ci[0].p_queue_priorities = hold._queue_prio.as_ptr();
|
||||||
@@ -560,9 +587,9 @@ impl PyroWaveEncoder {
|
|||||||
let device = instance
|
let device = instance
|
||||||
.create_device(pd, &hold.device_ci, None)
|
.create_device(pd, &hold.device_ci, None)
|
||||||
.context("create device")?;
|
.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,
|
Ok(v) => v,
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
instance.destroy_instance(None);
|
instance.destroy_instance(None);
|
||||||
@@ -588,6 +615,7 @@ impl PyroWaveEncoder {
|
|||||||
ext_fd,
|
ext_fd,
|
||||||
queue,
|
queue,
|
||||||
family,
|
family,
|
||||||
|
foreign_qfi,
|
||||||
mem_props,
|
mem_props,
|
||||||
_hold: hold,
|
_hold: hold,
|
||||||
pw_dev: std::ptr::null_mut(),
|
pw_dev: std::ptr::null_mut(),
|
||||||
@@ -1163,11 +1191,7 @@ impl PyroWaveEncoder {
|
|||||||
FramePayload::Dmabuf(d) => {
|
FramePayload::Dmabuf(d) => {
|
||||||
let (img, view, fresh) = self.import_cached(d, frame.width, frame.height)?;
|
let (img, view, fresh) = self.import_cached(d, frame.width, frame.height)?;
|
||||||
let (old, src_qf, dst_qf) = if fresh {
|
let (old, src_qf, dst_qf) = if fresh {
|
||||||
(
|
(vk::ImageLayout::UNDEFINED, self.foreign_qfi, self.family)
|
||||||
vk::ImageLayout::UNDEFINED,
|
|
||||||
vk::QUEUE_FAMILY_FOREIGN_EXT,
|
|
||||||
self.family,
|
|
||||||
)
|
|
||||||
} else {
|
} else {
|
||||||
(
|
(
|
||||||
vk::ImageLayout::SHADER_READ_ONLY_OPTIMAL,
|
vk::ImageLayout::SHADER_READ_ONLY_OPTIMAL,
|
||||||
|
|||||||
@@ -7,6 +7,18 @@ use anyhow::Result;
|
|||||||
use ash::vk;
|
use ash::vk;
|
||||||
use pf_frame::PixelFormat;
|
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 {
|
pub(crate) fn color_range(layer: u32) -> vk::ImageSubresourceRange {
|
||||||
vk::ImageSubresourceRange {
|
vk::ImageSubresourceRange {
|
||||||
aspect_mask: vk::ImageAspectFlags::COLOR,
|
aspect_mask: vk::ImageAspectFlags::COLOR,
|
||||||
@@ -398,6 +410,24 @@ pub(crate) unsafe fn make_plain_image(
|
|||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
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::*;
|
use super::*;
|
||||||
|
|
||||||
/// CSC mode (`bgra_target = false`): the 3→4 expand is a pure byte shuffle — no channel
|
/// 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,
|
compute_queue: vk::Queue,
|
||||||
encode_family: u32,
|
encode_family: u32,
|
||||||
compute_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,
|
mem_props: vk::PhysicalDeviceMemoryProperties,
|
||||||
|
|
||||||
// --- codec ---
|
// --- codec ---
|
||||||
@@ -995,6 +1001,24 @@ impl VulkanVideoEncoder {
|
|||||||
} else {
|
} else {
|
||||||
bitrate
|
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)
|
// logical device: encode + compute queues + video extensions (AV1 ext name is raw — ash lacks it)
|
||||||
let mut dev_exts = vec![
|
let mut dev_exts = vec![
|
||||||
ash::khr::video_queue::NAME.as_ptr(),
|
ash::khr::video_queue::NAME.as_ptr(),
|
||||||
@@ -1011,6 +1035,9 @@ impl VulkanVideoEncoder {
|
|||||||
if rgb_cfg.is_some() {
|
if rgb_cfg.is_some() {
|
||||||
dev_exts.push(vrgb::EXTENSION_NAME.as_ptr());
|
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 prio = [1.0f32];
|
||||||
let mut qcis = vec![vk::DeviceQueueCreateInfo::default()
|
let mut qcis = vec![vk::DeviceQueueCreateInfo::default()
|
||||||
.queue_family_index(encode_family)
|
.queue_family_index(encode_family)
|
||||||
@@ -1383,6 +1410,7 @@ impl VulkanVideoEncoder {
|
|||||||
encode_queue,
|
encode_queue,
|
||||||
compute_queue,
|
compute_queue,
|
||||||
encode_family,
|
encode_family,
|
||||||
|
foreign_qfi,
|
||||||
compute_family,
|
compute_family,
|
||||||
mem_props,
|
mem_props,
|
||||||
codec,
|
codec,
|
||||||
@@ -2083,7 +2111,7 @@ impl VulkanVideoEncoder {
|
|||||||
let (old, src_qf, dst_qf) = if fresh {
|
let (old, src_qf, dst_qf) = if fresh {
|
||||||
(
|
(
|
||||||
vk::ImageLayout::UNDEFINED,
|
vk::ImageLayout::UNDEFINED,
|
||||||
vk::QUEUE_FAMILY_FOREIGN_EXT,
|
self.foreign_qfi,
|
||||||
self.compute_family,
|
self.compute_family,
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
@@ -2432,7 +2460,7 @@ impl VulkanVideoEncoder {
|
|||||||
.dst_access_mask(vk::AccessFlags2::TRANSFER_READ)
|
.dst_access_mask(vk::AccessFlags2::TRANSFER_READ)
|
||||||
.old_layout(vk::ImageLayout::UNDEFINED)
|
.old_layout(vk::ImageLayout::UNDEFINED)
|
||||||
.new_layout(vk::ImageLayout::TRANSFER_SRC_OPTIMAL)
|
.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)
|
.dst_queue_family_index(self.compute_family)
|
||||||
} else {
|
} else {
|
||||||
vk::ImageMemoryBarrier2::default()
|
vk::ImageMemoryBarrier2::default()
|
||||||
@@ -2973,7 +3001,7 @@ impl VulkanVideoEncoder {
|
|||||||
.src_stage_mask(vk::PipelineStageFlags2::NONE)
|
.src_stage_mask(vk::PipelineStageFlags2::NONE)
|
||||||
.src_access_mask(vk::AccessFlags2::NONE)
|
.src_access_mask(vk::AccessFlags2::NONE)
|
||||||
.old_layout(vk::ImageLayout::UNDEFINED)
|
.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),
|
.dst_queue_family_index(self.encode_family),
|
||||||
SrcAcquire::DmabufCached => src_base
|
SrcAcquire::DmabufCached => src_base
|
||||||
.src_stage_mask(vk::PipelineStageFlags2::NONE)
|
.src_stage_mask(vk::PipelineStageFlags2::NONE)
|
||||||
|
|||||||
Reference in New Issue
Block a user