Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
75 changes: 57 additions & 18 deletions src/vulkan.rs
Original file line number Diff line number Diff line change
Expand Up @@ -256,13 +256,29 @@ impl DecodeQueueFamilies {
}
}

/// The queue family for the colour conversion's compute work, if there is one.
///
/// A family that does not also do graphics is preferred. A driver may back
/// each family with its own hardware queue (RADV does), and an application
/// that renders keeps the graphics one busy: a conversion submitted there
/// waits behind the frames the application has already queued.
fn find_compute_queue_family(queue_families: &[vk::QueueFamilyProperties]) -> Option<u32> {
queue_families
.iter()
.enumerate()
.filter(|(_, props)| props.queue_flags.contains(vk::QueueFlags::COMPUTE))
// `false` sorts first, and `min_by_key` keeps the first of equals.
.min_by_key(|(_, props)| props.queue_flags.contains(vk::QueueFlags::GRAPHICS))
.map(|(idx, _)| idx as u32)
}

/// Select the decode / transfer / compute queue families on `physical_device`,
/// failing if it cannot decode the required codecs.
///
/// Mirrors the selection [`VideoContext::new`] does inline, but scoped to the
/// decode path: a video-decode family, a transfer family (preferring a
/// dedicated engine over one that also does video — see the scoring), and any
/// compute family.
/// dedicated engine over one that also does video — see the scoring), and a
/// compute family (see [`find_compute_queue_family`]).
fn find_decode_queue_families(
entry: &ash::Entry,
instance: &ash::Instance,
Expand All @@ -275,7 +291,6 @@ fn find_decode_queue_families(
let mut decode = None;
let mut transfer = u32::MAX;
let mut transfer_score = -1i32;
let mut compute = u32::MAX;

for (idx, props) in queue_families.iter().enumerate() {
let idx = idx as u32;
Expand All @@ -301,9 +316,6 @@ fn find_decode_queue_families(
transfer = idx;
}
}
if flags.contains(vk::QueueFlags::COMPUTE) && compute == u32::MAX {
compute = idx;
}
}

let decode = decode.ok_or_else(|| {
Expand All @@ -316,11 +328,9 @@ fn find_decode_queue_families(
"Physical device has no transfer queue family".to_string(),
));
}
if compute == u32::MAX {
return Err(PixelForgeError::NoSuitableDevice(
"Physical device has no compute queue family".to_string(),
));
}
let compute = find_compute_queue_family(&queue_families).ok_or_else(|| {
PixelForgeError::NoSuitableDevice("Physical device has no compute queue family".to_string())
})?;

// Confirm the device actually decodes the codecs asked for.
let available = query_decode_codecs(entry, instance, physical_device);
Expand Down Expand Up @@ -688,7 +698,7 @@ impl VideoContext {
let mut decode_queue = None;
let mut transfer_q = u32::MAX;
let mut transfer_score = -1i32;
let mut compute_q = u32::MAX;
let compute_q = find_compute_queue_family(&queue_families).unwrap_or(u32::MAX);

for (idx, props) in queue_families.iter().enumerate() {
debug!(
Expand Down Expand Up @@ -737,12 +747,9 @@ impl VideoContext {
transfer_q = idx as u32;
}
}

// Check for compute queue (prefer dedicated compute, otherwise graphics+compute).
if flags.contains(vk::QueueFlags::COMPUTE) && compute_q == u32::MAX {
compute_q = idx as u32;
debug!("Found compute queue at family {}", idx);
}
}
if compute_q != u32::MAX {
debug!("Found compute queue at family {}", compute_q);
}

// Get list of available device extensions
Expand Down Expand Up @@ -1501,3 +1508,35 @@ impl VideoContext {
})
}
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn compute_family_is_one_without_graphics_when_the_device_has_one() {
let graphics =
vk::QueueFlags::GRAPHICS | vk::QueueFlags::COMPUTE | vk::QueueFlags::TRANSFER;
let compute = vk::QueueFlags::COMPUTE | vk::QueueFlags::TRANSFER;
let transfer = vk::QueueFlags::TRANSFER;
let encode = vk::QueueFlags::VIDEO_ENCODE_KHR;

// The queue family layouts of the three drivers, and the family each should get.
let layouts: [(&str, &[vk::QueueFlags], u32); 3] = [
("RADV", &[graphics, compute, encode], 1),
("NVIDIA", &[graphics, transfer, compute, encode], 2),
("ANV", &[graphics, encode], 0),
];
for (driver, flags, expected) in layouts {
let families: Vec<_> = flags
.iter()
.map(|&flags| vk::QueueFamilyProperties::default().queue_flags(flags))
.collect();
assert_eq!(
find_compute_queue_family(&families),
Some(expected),
"{driver}"
);
}
}
}
35 changes: 35 additions & 0 deletions tests/context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -67,3 +67,38 @@ fn context_creation_reaches_device_enumeration() {
Err(other) => panic!("expected to reach device selection, failed earlier with: {other}"),
}
}

#[test]
fn compute_family_avoids_graphics_when_the_device_allows() {
use ash::vk::QueueFlags;

let context = match build_decode_context() {
Ok(context) => context,
Err(error) if no_vulkan_driver(&error) => {
return skip_without_driver(
"compute_family_avoids_graphics_when_the_device_allows",
&error,
);
}
Err(PixelForgeError::NoSuitableDevice(_)) => return,
Err(other) => panic!("expected to reach device selection, failed earlier with: {other}"),
};
let families = unsafe {
context
.instance()
.get_physical_device_queue_family_properties(context.physical_device())
};
let computes = |flags: QueueFlags| flags.contains(QueueFlags::COMPUTE);
let graphics = |flags: QueueFlags| flags.contains(QueueFlags::GRAPHICS);

let chosen = families[context.compute_queue_family() as usize].queue_flags;
let has_compute_only = families
.iter()
.any(|family| computes(family.queue_flags) && !graphics(family.queue_flags));
assert!(computes(chosen));
assert_eq!(
graphics(chosen),
!has_compute_only,
"the conversion must not share the graphics family when another one computes"
);
}
Loading