Skip to main content

av_denoise_core/
enumerate.rs

1use cubecl::device::DeviceId;
2use cubecl::prelude::*;
3
4use crate::accelerate::Accelerator;
5use crate::device::Device;
6use crate::probe::open_client;
7
8/// What one backend reports about this machine.
9#[derive(Debug, Clone, PartialEq, Eq)]
10pub struct BackendDevices {
11    /// The backend that was asked.
12    pub accelerator: Accelerator,
13    /// Whether the backend started at all.
14    ///
15    /// A build can enable a backend the machine has no driver for, and
16    /// that backend reports no devices because it never ran, not
17    /// because the machine has no hardware.
18    pub available: bool,
19    /// The devices the backend can see, in the order it lists them.
20    ///
21    /// Always empty when `available` is false.
22    pub devices: Vec<Device>,
23}
24
25/// Asks each backend in `enable` which devices it can see.
26///
27/// Backends are reported in the order given, including the ones that
28/// could not start, so a caller can tell "no such hardware" apart from
29/// "no such driver".
30pub 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            // Keeps the match exhaustive on docs.rs, where `cfg(docsrs)`
55            // widens the `Accelerator` enum to include variants whose
56            // backend feature is not enabled. Never reached at runtime.
57            #[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
75/// Opens a client on `device` and lists what that backend can see.
76///
77/// A backend that cannot open a client at all, because its driver
78/// libraries are missing, is reported as unavailable rather than
79/// allowed to take the process down. See [`probe`](crate::probe).
80fn 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    // Type ids 0 to 3 are the device kinds `Device` can name. Anything
86    // else the runtime reports is hardware this tool cannot select.
87    //
88    // Not every backend filters by the type id it is given. ROCm and
89    // CUDA report their whole device list for each one, so the same
90    // device comes back on every pass and is kept only once.
91    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
109/// Maps a cubecl device id onto the selector that names it.
110///
111/// The type ids come from cubecl's own ordering of device kinds.
112/// Backends that report a kind this tool cannot select return `None`.
113fn 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}