Skip to main content

wallr_core/video/
gpu.rs

1use crate::video::error::{VideoError, VideoResult};
2use serde::{Deserialize, Serialize};
3use std::fmt;
4
5#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Default)]
6#[serde(rename_all = "lowercase")]
7pub enum GpuSelection {
8    #[default]
9    Auto,
10    Integrated,
11    Discrete,
12    Named(String),
13}
14
15impl fmt::Display for GpuSelection {
16    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
17        match self {
18            Self::Auto => write!(f, "auto"),
19            Self::Integrated => write!(f, "integrated"),
20            Self::Discrete => write!(f, "discrete"),
21            Self::Named(name) => write!(f, "{}", name),
22        }
23    }
24}
25
26#[derive(Debug, Clone)]
27pub struct AdapterInfo {
28    pub name: String,
29    pub backend: wgpu::Backend,
30    pub device_type: wgpu::DeviceType,
31    pub driver: String,
32    pub driver_info: String,
33}
34
35impl AdapterInfo {
36    pub fn is_integrated(&self) -> bool {
37        self.device_type == wgpu::DeviceType::IntegratedGpu
38    }
39
40    pub fn is_discrete(&self) -> bool {
41        self.device_type == wgpu::DeviceType::DiscreteGpu
42    }
43}
44
45impl fmt::Display for AdapterInfo {
46    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
47        write!(
48            f,
49            "{} ({:?}, {:?}) - {}",
50            self.name, self.device_type, self.backend, self.driver
51        )
52    }
53}
54
55pub async fn detect_adapters(instance: &wgpu::Instance) -> Vec<AdapterInfo> {
56    let mut adapters = Vec::new();
57    for adapter in instance.enumerate_adapters(wgpu::Backends::all()) {
58        let info = adapter.get_info();
59        adapters.push(AdapterInfo {
60            name: info.name.clone(),
61            backend: info.backend,
62            device_type: info.device_type,
63            driver: info.driver.clone(),
64            driver_info: info.driver_info.clone(),
65        });
66    }
67    tracing::info!("Detected {} GPU adapter(s)", adapters.len());
68    for (i, a) in adapters.iter().enumerate() {
69        tracing::info!("  [{}] {}", i, a);
70    }
71    adapters
72}
73
74pub async fn select_adapter(
75    instance: &wgpu::Instance,
76    preference: &GpuSelection,
77) -> VideoResult<wgpu::Adapter> {
78    let adapters = detect_adapters(instance).await;
79    if adapters.is_empty() {
80        return Err(VideoError::AdapterNotFound(
81            "No GPU adapters detected".to_string(),
82        ));
83    }
84
85    let selected = match preference {
86        GpuSelection::Auto => adapters
87            .iter()
88            .find(|a| a.is_integrated())
89            .or_else(|| adapters.first()),
90        GpuSelection::Integrated => adapters.iter().find(|a| a.is_integrated()),
91        GpuSelection::Discrete => adapters.iter().find(|a| a.is_discrete()),
92        GpuSelection::Named(name) => adapters.iter().find(|a| a.name.contains(name)),
93    };
94
95    let selected = selected.ok_or_else(|| {
96        VideoError::AdapterNotFound(format!("No adapter matching preference: {}", preference))
97    })?;
98
99    tracing::info!("Selected GPU adapter: {}", selected);
100
101    let adapter = instance
102        .request_adapter(&wgpu::RequestAdapterOptions {
103            power_preference: match preference {
104                GpuSelection::Integrated => wgpu::PowerPreference::LowPower,
105                GpuSelection::Discrete => wgpu::PowerPreference::HighPerformance,
106                _ => wgpu::PowerPreference::LowPower,
107            },
108            compatible_surface: None,
109            force_fallback_adapter: false,
110        })
111        .await
112        .ok_or_else(|| {
113            VideoError::AdapterNotFound(format!("Failed to request adapter for: {}", preference))
114        })?;
115
116    Ok(adapter)
117}
118
119pub fn adapter_diagnostics(adapter: &wgpu::Adapter) -> String {
120    let info = adapter.get_info();
121    format!(
122        "GPU: {} ({:?})\nBackend: {:?}\nDriver: {}\nDriver Info: {}",
123        info.name, info.device_type, info.backend, info.driver, info.driver_info
124    )
125}
126
127#[cfg(test)]
128mod tests {
129    use super::*;
130
131    #[test]
132    fn test_gpu_selection_display() {
133        assert_eq!(GpuSelection::Auto.to_string(), "auto");
134        assert_eq!(GpuSelection::Integrated.to_string(), "integrated");
135        assert_eq!(GpuSelection::Discrete.to_string(), "discrete");
136        assert_eq!(
137            GpuSelection::Named("NVIDIA".to_string()).to_string(),
138            "NVIDIA"
139        );
140    }
141
142    #[test]
143    fn test_gpu_selection_default() {
144        assert_eq!(GpuSelection::default(), GpuSelection::Auto);
145    }
146}