Skip to main content

dspk_app_core/data/
device.rs

1//! The compute device processing runs on, named for the status bar and Settings › Compute (as
2//! lumen's): the runtime chosen in Settings (`DSPK_RUNTIME` at start), else the first compiled in
3//! (wgpu); with wgpu, the adapter it opens: the discrete GPU first, then the integrated one, a
4//! virtual one, and a software one (llvmpipe) last.
5
6use dspk_core::ComputeTarget;
7
8/// What the status bar and Settings say about the device.
9#[derive(Debug, Clone, PartialEq, Eq)]
10pub struct DeviceInfo {
11    /// One short line: "GPU · Vulkan · Intel(R) UHD Graphics 630", "CPU".
12    pub label: String,
13    /// Every adapter wgpu found (name, kind, backend), for Settings.
14    pub adapters: Vec<String>,
15}
16
17impl DeviceInfo {
18    /// The device of the runtime chosen now (takes a moment with wgpu: call it off the UI thread).
19    pub fn discover() -> DeviceInfo {
20        match ComputeTarget::from_env() {
21            Ok(ComputeTarget::Wgpu) => wgpu_device(),
22            Ok(ComputeTarget::Cuda) => DeviceInfo { label: "GPU · CUDA".into(), adapters: Vec::new() },
23            Ok(ComputeTarget::Hip) => DeviceInfo { label: "GPU · ROCm".into(), adapters: Vec::new() },
24            Ok(ComputeTarget::Cpu) => DeviceInfo { label: "CPU".into(), adapters: Vec::new() },
25            Err(e) => DeviceInfo { label: format!("No compute device: {e}"), adapters: Vec::new() },
26        }
27    }
28}
29
30#[cfg(feature = "wgpu")]
31fn wgpu_device() -> DeviceInfo {
32    use wgpu::DeviceType as T;
33    let found = tokio::runtime::Builder::new_current_thread().build().map(|rt| rt.block_on(wgpu::Instance::default().enumerate_adapters(wgpu::Backends::all())));
34    let infos: Vec<wgpu::AdapterInfo> = found.map(|list| list.iter().map(|a| a.get_info()).collect()).unwrap_or_default();
35    let rank = |t: T| match t {
36        T::DiscreteGpu => 0,
37        T::IntegratedGpu => 1,
38        T::VirtualGpu => 2,
39        T::Other => 3,
40        T::Cpu => 4,
41    };
42    let kind = |t: T| match t {
43        T::DiscreteGpu => "discrete",
44        T::IntegratedGpu => "integrated",
45        T::VirtualGpu => "virtual",
46        T::Other => "other",
47        T::Cpu => "software",
48    };
49    let adapters = infos.iter().map(|i| format!("{} ({}, {:?})", i.name, kind(i.device_type), i.backend)).collect();
50    let label = match infos.iter().min_by_key(|i| rank(i.device_type)) {
51        Some(i) if i.device_type == T::Cpu => format!("GPU · {:?} · {} (software)", i.backend, i.name),
52        Some(i) => format!("GPU · {:?} · {}", i.backend, i.name),
53        None => "GPU · Vulkan / Metal (no adapter found)".to_string(),
54    };
55    DeviceInfo { label, adapters }
56}
57
58#[cfg(not(feature = "wgpu"))]
59fn wgpu_device() -> DeviceInfo {
60    DeviceInfo { label: "GPU · wgpu (not compiled in)".into(), adapters: Vec::new() }
61}
62
63#[cfg(test)]
64mod tests {
65    use super::*;
66
67    /// The device is named, and with wgpu (the default) every adapter it found is listed.
68    #[test]
69    fn the_device_is_named() {
70        let d = DeviceInfo::discover();
71        eprintln!("compute device: {} · adapters: {:?}", d.label, d.adapters);
72        assert!(!d.label.is_empty());
73        if ComputeTarget::from_env().ok() == Some(ComputeTarget::Wgpu) {
74            assert!(d.label.starts_with("GPU · "), "{}", d.label);
75        }
76    }
77}