Skip to main content

probe/
gpu.rs

1use std::sync::OnceLock;
2
3#[derive(Debug, Clone, Copy, PartialEq, Eq)]
4pub enum Backend {
5    Vulkan,
6    Metal,
7    Dx12,
8    Gl,
9    BrowserWebGpu,
10    Other,
11}
12
13impl From<wgpu::Backend> for Backend {
14    fn from(value: wgpu::Backend) -> Self {
15        match value {
16            wgpu::Backend::Vulkan => Backend::Vulkan,
17            wgpu::Backend::Metal => Backend::Metal,
18            wgpu::Backend::Dx12 => Backend::Dx12,
19            wgpu::Backend::Gl => Backend::Gl,
20            wgpu::Backend::BrowserWebGpu => Backend::BrowserWebGpu,
21            wgpu::Backend::Noop => Backend::Other,
22        }
23    }
24}
25
26#[derive(Debug, Clone, Copy, PartialEq, Eq)]
27pub enum DeviceType {
28    IntegratedGpu,
29    DiscreteGpu,
30    VirtualGpu,
31    Cpu,
32    Other,
33}
34
35impl From<wgpu::DeviceType> for DeviceType {
36    fn from(value: wgpu::DeviceType) -> Self {
37        match value {
38            wgpu::DeviceType::IntegratedGpu => DeviceType::IntegratedGpu,
39            wgpu::DeviceType::DiscreteGpu => DeviceType::DiscreteGpu,
40            wgpu::DeviceType::VirtualGpu => DeviceType::VirtualGpu,
41            wgpu::DeviceType::Cpu => DeviceType::Cpu,
42            wgpu::DeviceType::Other => DeviceType::Other,
43        }
44    }
45}
46
47#[derive(Debug, Clone, PartialEq, Eq)]
48pub struct AdapterInfo {
49    pub name: String,
50    pub backend: Backend,
51    pub device_type: DeviceType,
52}
53
54#[derive(Debug, Clone, PartialEq, Eq, Default)]
55pub struct GpuInfo {
56    pub adapters: Vec<AdapterInfo>,
57}
58
59static GPU: OnceLock<GpuInfo> = OnceLock::new();
60
61static VULKAN_GATE: std::sync::Mutex<()> = std::sync::Mutex::new(());
62
63pub fn serial_vulkan<T>(f: impl FnOnce() -> T) -> T {
64    let _serial = VULKAN_GATE.lock().unwrap_or_else(|p| p.into_inner());
65    f()
66}
67
68pub fn probe_gpu() -> GpuInfo {
69    GPU.get_or_init(|| {
70        let _serial = VULKAN_GATE.lock().unwrap_or_else(|p| p.into_inner());
71        let instance = wgpu::Instance::default();
72        let adapters = instance.enumerate_adapters(wgpu::Backends::all());
73        let adapters = adapters
74            .into_iter()
75            .map(|adapter| {
76                let info = adapter.get_info();
77                AdapterInfo {
78                    name: info.name,
79                    backend: info.backend.into(),
80                    device_type: info.device_type.into(),
81                }
82            })
83            .collect();
84        GpuInfo { adapters }
85    })
86    .clone()
87}
88
89static CAN_DRAW_VIDEO: OnceLock<bool> = OnceLock::new();
90
91pub fn can_draw_video() -> bool {
92    *CAN_DRAW_VIDEO.get_or_init(|| {
93        let _serial = VULKAN_GATE.lock().unwrap_or_else(|p| p.into_inner());
94        let instance = wgpu::Instance::default();
95        let Some(adapter) =
96            pollster::block_on(instance.request_adapter(&wgpu::RequestAdapterOptions {
97                power_preference: wgpu::PowerPreference::HighPerformance,
98                force_fallback_adapter: false,
99                compatible_surface: None,
100            }))
101            .ok()
102        else {
103            return false;
104        };
105        if adapter.get_info().device_type == wgpu::DeviceType::Cpu {
106            return false;
107        }
108        let asked = wgpu::DeviceDescriptor {
109            label: Some("PITO MD screen check"),
110            required_features: wgpu::Features::empty(),
111            required_limits: wgpu::Limits::downlevel_defaults(),
112            memory_hints: wgpu::MemoryHints::default(),
113            experimental_features: wgpu::ExperimentalFeatures::disabled(),
114            trace: wgpu::Trace::Off,
115        };
116        pollster::block_on(adapter.request_device(&asked)).is_ok()
117    })
118}
119
120#[cfg(test)]
121mod tests {
122    use super::*;
123
124    #[test]
125    fn enumeration_never_panics() {
126        std::hint::black_box(probe_gpu());
127    }
128
129    #[test]
130    fn concurrent_gpu_probes_all_observe_the_one_memoized_result() {
131        let handles: Vec<_> = (0..32).map(|_| std::thread::spawn(probe_gpu)).collect();
132        let results: Vec<GpuInfo> = handles.into_iter().map(|h| h.join().unwrap()).collect();
133        for result in &results[1..] {
134            assert_eq!(
135                result, &results[0],
136                "every concurrent caller must see the same memoized probe"
137            );
138        }
139    }
140
141    #[test]
142    fn concurrent_video_capability_checks_all_observe_the_one_memoized_result() {
143        let handles: Vec<_> = (0..32)
144            .map(|_| std::thread::spawn(can_draw_video))
145            .collect();
146        let results: Vec<bool> = handles.into_iter().map(|h| h.join().unwrap()).collect();
147        for result in &results[1..] {
148            assert_eq!(
149                result, &results[0],
150                "every concurrent caller must see the same memoized answer"
151            );
152        }
153    }
154}