1use crate::{GpuError, Result};
4use std::sync::Arc;
5use wgpu::{
6 Adapter, Device, DeviceDescriptor, Features, Instance, Limits, PowerPreference, Queue,
7 RequestAdapterOptions,
8};
9
10#[derive(Debug, Clone)]
12pub struct GpuDeviceInfo {
13 pub name: String,
15 pub vendor: u32,
17 pub device: u32,
19 pub device_type: String,
21 pub backend: String,
23}
24
25pub struct GpuDevice {
30 device: Arc<Device>,
31 queue: Arc<Queue>,
32 info: GpuDeviceInfo,
33 #[allow(dead_code)]
34 adapter: Adapter,
35 pub is_fallback: bool,
38}
39
40impl GpuDevice {
41 pub fn new(device_index: Option<usize>) -> Result<Self> {
51 let instance = Self::create_instance();
52 let adapter = pollster::block_on(Self::select_adapter(&instance, device_index))?;
53
54 let info = Self::adapter_info(&adapter);
55
56 let (device, queue) = pollster::block_on(Self::request_device(&adapter))?;
57
58 Ok(Self {
59 device: Arc::new(device),
60 queue: Arc::new(queue),
61 info,
62 adapter,
63 is_fallback: false,
64 })
65 }
66
67 pub fn new_fallback() -> Result<Self> {
80 let instance = Self::create_instance();
81
82 let maybe_adapter = pollster::block_on(instance.request_adapter(&RequestAdapterOptions {
85 power_preference: PowerPreference::None,
86 compatible_surface: None,
87 force_fallback_adapter: true,
88 }));
89
90 if let Ok(adapter) = maybe_adapter {
91 let info = Self::adapter_info(&adapter);
92 if let Ok((device, queue)) = pollster::block_on(Self::request_device(&adapter)) {
93 return Ok(Self {
94 device: Arc::new(device),
95 queue: Arc::new(queue),
96 info,
97 adapter,
98 is_fallback: true,
99 });
100 }
101 }
102
103 Self::make_null_device().ok_or(GpuError::NoAdapter)
105 }
106
107 fn make_null_device() -> Option<Self> {
114 let null_info = GpuDeviceInfo {
115 name: "CPU Null Device".to_string(),
116 vendor: 0,
117 device: 0,
118 device_type: "cpu".to_string(),
119 backend: "Null".to_string(),
120 };
121
122 fn try_adapter(adapter: Adapter, info: GpuDeviceInfo) -> Option<GpuDevice> {
125 match pollster::block_on(GpuDevice::request_device(&adapter)) {
126 Ok((device, queue)) => Some(GpuDevice {
127 device: Arc::new(device),
128 queue: Arc::new(queue),
129 info,
130 adapter,
131 is_fallback: true,
132 }),
133 Err(_) => None,
134 }
135 }
136
137 let mut gl_desc = wgpu::InstanceDescriptor::new_without_display_handle();
139 gl_desc.backends = wgpu::Backends::GL;
140 let gl_instance = wgpu::Instance::new(gl_desc);
141 if let Ok(adapter) =
142 pollster::block_on(gl_instance.request_adapter(&RequestAdapterOptions {
143 power_preference: PowerPreference::None,
144 compatible_surface: None,
145 force_fallback_adapter: true,
146 }))
147 {
148 let info = Self::adapter_info(&adapter);
149 if let Some(dev) = try_adapter(adapter, info) {
150 return Some(dev);
151 }
152 }
153
154 let adapters = pollster::block_on(gl_instance.enumerate_adapters(wgpu::Backends::all()));
156 for adapter in adapters {
157 let info = Self::adapter_info(&adapter);
158 if let Some(dev) = try_adapter(adapter, info) {
159 return Some(dev);
160 }
161 }
162
163 let default_instance =
165 wgpu::Instance::new(wgpu::InstanceDescriptor::new_without_display_handle());
166 if let Ok(adapter) =
167 pollster::block_on(default_instance.request_adapter(&RequestAdapterOptions {
168 power_preference: PowerPreference::None,
169 compatible_surface: None,
170 force_fallback_adapter: true,
171 }))
172 {
173 let info = Self::adapter_info(&adapter);
174 if let Some(dev) = try_adapter(adapter, info) {
175 return Some(dev);
176 }
177 }
178
179 let adapters =
181 pollster::block_on(default_instance.enumerate_adapters(wgpu::Backends::all()));
182 for adapter in adapters {
183 let info = Self::adapter_info(&adapter);
184 if let Some(dev) = try_adapter(adapter, info) {
185 return Some(dev);
186 }
187 }
188
189 let _ = null_info;
191 None
192 }
193
194 pub fn list_devices() -> Result<Vec<GpuDeviceInfo>> {
196 let instance = Self::create_instance();
197
198 #[cfg(not(target_arch = "wasm32"))]
199 {
200 let adapters = pollster::block_on(instance.enumerate_adapters(wgpu::Backends::all()));
201 Ok(adapters.iter().map(Self::adapter_info).collect())
202 }
203
204 #[cfg(target_arch = "wasm32")]
205 {
206 let adapter = pollster::block_on(instance.request_adapter(&RequestAdapterOptions {
208 power_preference: PowerPreference::HighPerformance,
209 compatible_surface: None,
210 force_fallback_adapter: false,
211 }));
212 match adapter {
213 Ok(a) => Ok(vec![Self::adapter_info(&a)]),
214 Err(_) => Ok(Vec::new()),
215 }
216 }
217 }
218
219 #[must_use]
221 pub fn info(&self) -> &GpuDeviceInfo {
222 &self.info
223 }
224
225 #[must_use]
227 pub fn device(&self) -> &Arc<Device> {
228 &self.device
229 }
230
231 #[must_use]
233 pub fn queue(&self) -> &Arc<Queue> {
234 &self.queue
235 }
236
237 pub fn wait(&self) {
239 let _ = self.device.poll(wgpu::PollType::wait_indefinitely());
240 }
241
242 fn create_instance() -> Instance {
243 Instance::new(wgpu::InstanceDescriptor::new_without_display_handle())
244 }
245
246 async fn select_adapter(instance: &Instance, device_index: Option<usize>) -> Result<Adapter> {
247 if let Some(index) = device_index {
248 #[cfg(not(target_arch = "wasm32"))]
249 {
250 let adapters = instance.enumerate_adapters(wgpu::Backends::all()).await;
251 return adapters.into_iter().nth(index).ok_or(GpuError::NoAdapter);
252 }
253
254 #[cfg(target_arch = "wasm32")]
255 {
256 if index != 0 {
258 return Err(GpuError::NoAdapter);
259 }
260 return instance
261 .request_adapter(&RequestAdapterOptions {
262 power_preference: PowerPreference::HighPerformance,
263 compatible_surface: None,
264 force_fallback_adapter: false,
265 })
266 .await
267 .map_err(|_| GpuError::NoAdapter);
268 }
269 } else {
270 instance
272 .request_adapter(&RequestAdapterOptions {
273 power_preference: PowerPreference::HighPerformance,
274 compatible_surface: None,
275 force_fallback_adapter: false,
276 })
277 .await
278 .map_err(|_| GpuError::NoAdapter)
279 }
280 }
281
282 async fn request_device(adapter: &Adapter) -> Result<(Device, Queue)> {
283 adapter
284 .request_device(&DeviceDescriptor {
285 label: Some("OxiMedia GPU Device"),
286 required_features: Features::empty(),
287 required_limits: Limits::default(),
288 memory_hints: wgpu::MemoryHints::default(),
289 experimental_features: wgpu::ExperimentalFeatures::disabled(),
290 trace: wgpu::Trace::Off,
291 })
292 .await
293 .map_err(|e| GpuError::DeviceRequest(e.to_string()))
294 }
295
296 fn adapter_info(adapter: &Adapter) -> GpuDeviceInfo {
297 let info = adapter.get_info();
298
299 let device_type = match info.device_type {
300 wgpu::DeviceType::DiscreteGpu => "discrete",
301 wgpu::DeviceType::IntegratedGpu => "integrated",
302 wgpu::DeviceType::VirtualGpu => "virtual",
303 wgpu::DeviceType::Cpu => "cpu",
304 wgpu::DeviceType::Other => "unknown",
305 };
306
307 let backend = match info.backend {
308 wgpu::Backend::Vulkan => "Vulkan",
309 wgpu::Backend::Metal => "Metal",
310 wgpu::Backend::Dx12 => "DirectX 12",
311 wgpu::Backend::Gl => "OpenGL",
312 wgpu::Backend::BrowserWebGpu => "WebGPU",
313 _ => "Unknown",
314 };
315
316 GpuDeviceInfo {
317 name: info.name,
318 vendor: info.vendor,
319 device: info.device,
320 device_type: device_type.to_string(),
321 backend: backend.to_string(),
322 }
323 }
324}
325
326impl std::fmt::Debug for GpuDevice {
327 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
328 f.debug_struct("GpuDevice")
329 .field("info", &self.info)
330 .field("is_fallback", &self.is_fallback)
331 .finish()
332 }
333}