av_denoise_core/
enumerate.rs1use cubecl::device::DeviceId;
2use cubecl::prelude::*;
3
4use crate::accelerate::Accelerator;
5use crate::device::Device;
6use crate::probe::open_client;
7
8#[derive(Debug, Clone, PartialEq, Eq)]
10pub struct BackendDevices {
11 pub accelerator: Accelerator,
13 pub available: bool,
19 pub devices: Vec<Device>,
23}
24
25pub fn enumerate_devices(enable: &[Accelerator]) -> Vec<BackendDevices> {
31 enable
32 .iter()
33 .map(|accelerator| match accelerator {
34 #[cfg(feature = "cuda")]
35 Accelerator::Cuda => match Device::Default.to_cuda() {
36 Ok(dev) => query_runtime::<cubecl::cuda::CudaRuntime>(*accelerator, &dev),
37 Err(_) => unavailable(*accelerator),
38 },
39 #[cfg(feature = "rocm")]
40 Accelerator::Rocm => match Device::Default.to_amd() {
41 Ok(dev) => query_runtime::<cubecl::hip::HipRuntime>(*accelerator, &dev),
42 Err(_) => unavailable(*accelerator),
43 },
44 #[cfg(feature = "vulkan")]
45 Accelerator::Vulkan => match Device::Default.to_wgpu() {
46 Ok(dev) => query_runtime::<cubecl::wgpu::WgpuRuntime>(*accelerator, &dev),
47 Err(_) => unavailable(*accelerator),
48 },
49 #[cfg(feature = "metal")]
50 Accelerator::Metal => match Device::Default.to_wgpu() {
51 Ok(dev) => query_runtime::<cubecl::wgpu::WgpuRuntime>(*accelerator, &dev),
52 Err(_) => unavailable(*accelerator),
53 },
54 #[cfg(docsrs)]
58 #[expect(
59 unreachable_patterns,
60 reason = "the arm only keeps the match exhaustive on docs.rs"
61 )]
62 _ => unreachable!(),
63 })
64 .collect()
65}
66
67fn unavailable(accelerator: Accelerator) -> BackendDevices {
68 BackendDevices {
69 accelerator,
70 available: false,
71 devices: Vec::new(),
72 }
73}
74
75fn query_runtime<R: Runtime>(accelerator: Accelerator, device: &R::Device) -> BackendDevices {
81 let Some(client) = open_client::<R>(accelerator, device) else {
82 return unavailable(accelerator);
83 };
84
85 let mut devices: Vec<Device> = Vec::new();
92 for type_id in 0..=3 {
93 for id in client.enumerate_devices(type_id) {
94 if let Some(device) = to_device(id)
95 && !devices.contains(&device)
96 {
97 devices.push(device);
98 }
99 }
100 }
101
102 BackendDevices {
103 accelerator,
104 available: true,
105 devices,
106 }
107}
108
109fn to_device(id: DeviceId) -> Option<Device> {
114 let index = id.index_id as usize;
115 match id.type_id {
116 0 => Some(Device::Discrete { index }),
117 1 => Some(Device::Integrated { index }),
118 2 => Some(Device::Virtual { index }),
119 3 => Some(Device::Cpu),
120 _ => None,
121 }
122}
123
124#[cfg(test)]
125mod tests {
126 use super::*;
127
128 #[test]
129 fn device_kinds_map_from_type_ids() {
130 assert_eq!(
131 to_device(DeviceId::new(0, 1)),
132 Some(Device::Discrete { index: 1 }),
133 );
134 assert_eq!(
135 to_device(DeviceId::new(1, 0)),
136 Some(Device::Integrated { index: 0 }),
137 );
138 assert_eq!(to_device(DeviceId::new(2, 2)), Some(Device::Virtual { index: 2 }),);
139 assert_eq!(to_device(DeviceId::new(3, 0)), Some(Device::Cpu));
140 }
141
142 #[test]
143 fn unknown_type_ids_are_skipped() {
144 assert_eq!(to_device(DeviceId::new(4, 0)), None);
145 }
146
147 #[test]
148 fn no_backends_lists_nothing() {
149 assert!(enumerate_devices(&[]).is_empty());
150 }
151
152 #[cfg(feature = "vulkan")]
153 #[test]
154 fn vulkan_reports_at_least_one_device() {
155 let reported = enumerate_devices(&[Accelerator::Vulkan]);
156 assert_eq!(reported.len(), 1);
157
158 let vulkan = &reported[0];
159 assert_eq!(vulkan.accelerator, Accelerator::Vulkan);
160 assert!(vulkan.available, "the vulkan backend did not start");
161 assert!(
162 !vulkan.devices.is_empty(),
163 "the vulkan backend started but listed no devices",
164 );
165
166 let mut unique = vulkan.devices.clone();
167 unique.dedup();
168 assert_eq!(
169 unique.len(),
170 vulkan.devices.len(),
171 "a device was listed more than once: {:?}",
172 vulkan.devices,
173 );
174 }
175}