use av_denoise::Device;
use av_denoise::accelerate::Accelerator;
use av_denoise::enumerate::{BackendDevices, enumerate_devices};
pub fn run_list_devices(enable: &[Accelerator]) -> String {
format_table(&enumerate_devices(enable))
}
fn format_table(reported: &[BackendDevices]) -> String {
let rows = build_rows(reported);
let unavailable: Vec<&BackendDevices> = reported.iter().filter(|b| !b.available).collect();
let mut out = String::from("\n");
if rows.is_empty() {
out.push_str("No usable devices found.\n");
} else {
let width = rows
.iter()
.map(|(device, _)| device.len())
.chain(std::iter::once("DEVICE".len()))
.max()
.unwrap_or(0);
out.push_str(&format!("{:<width$} BACKENDS\n", "DEVICE"));
for (device, backends) in &rows {
out.push_str(&format!("{device:<width$} {}\n", backends.join(", ")));
}
}
if !unavailable.is_empty() {
let names: Vec<String> = unavailable.iter().map(|b| b.accelerator.to_string()).collect();
out.push_str(&format!(
"\nEnabled but unavailable on this machine: {}\n",
names.join(", "),
));
}
out
}
fn build_rows(reported: &[BackendDevices]) -> Vec<(String, Vec<String>)> {
let mut rows: Vec<(Device, Vec<String>)> = Vec::new();
let mut record = |device: Device, accelerator: Accelerator| {
let name = accelerator.to_string();
match rows.iter_mut().find(|(seen, _)| *seen == device) {
Some((_, backends)) if backends.contains(&name) => {},
Some((_, backends)) => backends.push(name),
None => rows.push((device, vec![name])),
}
};
for backend in reported.iter().filter(|b| b.available) {
record(Device::Default, backend.accelerator);
for device in &backend.devices {
record(device.clone(), backend.accelerator);
}
}
rows.sort_by_key(|(device, _)| sort_key(device));
rows.into_iter()
.map(|(device, backends)| (device.to_string(), backends))
.collect()
}
fn sort_key(device: &Device) -> (u8, usize) {
match device {
Device::Default => (0, 0),
Device::Discrete { index } => (1, *index),
Device::Integrated { index } => (2, *index),
Device::Virtual { index } => (3, *index),
Device::Cpu => (4, 0),
}
}
#[cfg(test)]
mod tests {
use super::*;
fn backend(accelerator: Accelerator, devices: Vec<Device>) -> BackendDevices {
BackendDevices {
accelerator,
available: true,
devices,
}
}
#[test]
fn rows_merge_backends_offering_the_same_device() {
let reported = vec![
backend(Accelerator::Vulkan, vec![Device::Discrete { index: 0 }]),
backend(Accelerator::Vulkan, vec![Device::Discrete { index: 0 }]),
];
let rows = build_rows(&reported);
assert_eq!(
rows,
vec![
("default".to_string(), vec!["vulkan".to_string()]),
("discrete:0".to_string(), vec!["vulkan".to_string()]),
],
);
}
#[test]
fn rows_are_sorted_by_kind_then_ordinal() {
let reported = vec![backend(
Accelerator::Vulkan,
vec![
Device::Cpu,
Device::Discrete { index: 1 },
Device::Integrated { index: 0 },
Device::Discrete { index: 0 },
],
)];
let names: Vec<String> = build_rows(&reported).into_iter().map(|(d, _)| d).collect();
assert_eq!(
names,
["default", "discrete:0", "discrete:1", "integrated:0", "cpu"],
);
}
#[test]
fn a_backend_that_did_not_start_contributes_no_rows() {
let reported = vec![BackendDevices {
accelerator: Accelerator::Vulkan,
available: false,
devices: Vec::new(),
}];
assert!(build_rows(&reported).is_empty());
}
#[test]
fn the_table_opens_with_a_blank_line() {
let reported = vec![backend(Accelerator::Vulkan, vec![Device::Cpu])];
assert!(
format_table(&reported).starts_with('\n'),
"driver notices on stderr run straight into the header",
);
}
#[test]
fn table_pads_the_device_column() {
let reported = vec![backend(
Accelerator::Vulkan,
vec![Device::Integrated { index: 0 }],
)];
assert_eq!(
format_table(&reported),
"\nDEVICE BACKENDS\ndefault vulkan\nintegrated:0 vulkan\n",
);
}
#[test]
fn table_names_backends_that_did_not_start() {
let reported = vec![BackendDevices {
accelerator: Accelerator::Vulkan,
available: false,
devices: Vec::new(),
}];
assert_eq!(
format_table(&reported),
"\nNo usable devices found.\n\nEnabled but unavailable on this machine: vulkan\n",
);
}
#[test]
fn nothing_enabled_says_so() {
assert_eq!(format_table(&[]), "\nNo usable devices found.\n");
}
}