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}