1use std::fmt;
2
3use ravenrs_core::{AdapterReport, GpuReport, SurfaceReport};
4
5#[derive(Debug, Clone)]
7pub struct GpuContext {
8 pub adapter: wgpu::Adapter,
10 pub device: wgpu::Device,
12 pub queue: wgpu::Queue,
14}
15
16#[derive(Debug)]
18pub enum GpuError {
19 NoAdapter(String),
21 Device(String),
23 Surface(String),
25}
26
27impl fmt::Display for GpuError {
28 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
29 match self {
30 Self::NoAdapter(e) => write!(f, "no compatible GPU adapter was found ({e})"),
31 Self::Device(e) => write!(f, "the GPU device could not be created ({e})"),
32 Self::Surface(e) => write!(f, "the drawing surface could not be created ({e})"),
33 }
34 }
35}
36
37impl std::error::Error for GpuError {}
38
39impl GpuContext {
40 pub async fn new(
49 instance: &wgpu::Instance,
50 surface: Option<&wgpu::Surface<'_>>,
51 ) -> Result<Self, GpuError> {
52 let adapter = instance
53 .request_adapter(&wgpu::RequestAdapterOptions {
54 power_preference: wgpu::PowerPreference::from_env()
55 .unwrap_or(wgpu::PowerPreference::LowPower),
56 force_fallback_adapter: false,
57 compatible_surface: surface,
58 ..Default::default()
59 })
60 .await
61 .map_err(|e| GpuError::NoAdapter(e.to_string()))?;
62 let (adapter, device, queue) = match request_device(&adapter).await {
63 Ok((device, queue)) => (adapter, device, queue),
64 Err(first) => {
65 tracing::warn!(adapter = %adapter.get_info().name, error = %first, "GPU refused; trying others");
66 let mut others = instance.enumerate_adapters(wgpu::Backends::all()).await;
67 others.retain(|other| {
68 other.get_info() != adapter.get_info()
69 && surface.is_none_or(|surface| other.is_surface_supported(surface))
70 });
71 others.sort_by_key(|other| other.get_info().device_type == wgpu::DeviceType::Cpu);
72 let mut found = None;
73 for other in others {
74 if let Ok((device, queue)) = request_device(&other).await {
75 found = Some((other, device, queue));
76 break;
77 }
78 }
79 found.ok_or(GpuError::Device(first))?
80 }
81 };
82 let info = adapter.get_info();
83 if info.device_type == wgpu::DeviceType::Cpu {
84 tracing::warn!(
85 adapter = %info.name,
86 driver = %info.driver,
87 "rendering on the CPU, so the interface will be slow; the GPU is not being used"
88 );
89 }
90 tracing::info!(adapter = ?info, "GPU ready");
91 Ok(Self { adapter, device, queue })
92 }
93}
94
95async fn request_device(adapter: &wgpu::Adapter) -> Result<(wgpu::Device, wgpu::Queue), String> {
96 let limits = if adapter.get_info().backend == wgpu::Backend::Gl && cfg!(target_arch = "wasm32") {
97 wgpu::Limits::downlevel_webgl2_defaults().using_resolution(adapter.limits())
99 } else if cfg!(target_arch = "wasm32") {
100 wgpu::Limits::default().using_resolution(adapter.limits())
101 } else {
102 wgpu::Limits::default()
103 };
104 adapter
105 .request_device(&wgpu::DeviceDescriptor {
106 label: Some("raven device"),
107 required_features: adapter.features()
110 & (wgpu::Features::FLOAT32_BLENDABLE | wgpu::Features::TIMESTAMP_QUERY),
111 required_limits: limits,
112 ..Default::default()
113 })
114 .await
115 .map_err(|e| e.to_string())
116}
117
118fn adapter_report(adapter: &wgpu::Adapter) -> AdapterReport {
119 let info = adapter.get_info();
120 AdapterReport {
121 name: info.name,
122 backend: info.backend.to_string(),
123 device_type: format!("{:?}", info.device_type),
124 driver: info.driver,
125 driver_info: info.driver_info,
126 software: info.device_type == wgpu::DeviceType::Cpu,
127 }
128}
129
130pub fn surface_report(
132 capabilities: &wgpu::SurfaceCapabilities,
133 format: wgpu::TextureFormat,
134 present_mode: wgpu::PresentMode,
135) -> SurfaceReport {
136 SurfaceReport {
137 format: format!("{format:?}"),
138 present_mode: format!("{present_mode:?}"),
139 supported_present_modes: capabilities.present_modes.iter().map(|m| format!("{m:?}")).collect(),
140 }
141}
142
143pub async fn gpu_report(
146 active: &wgpu::Adapter,
147 instance: &wgpu::Instance,
148 permissive: &wgpu::Instance,
149 surface: Option<SurfaceReport>,
150) -> GpuReport {
151 let list = async |instance: &wgpu::Instance| -> Vec<AdapterReport> {
152 instance.enumerate_adapters(wgpu::Backends::all()).await.iter().map(adapter_report).collect()
153 };
154 let available = list(instance).await;
155 let hidden = list(permissive).await.into_iter().filter(|adapter| !available.contains(adapter)).collect();
156 GpuReport { active: adapter_report(active), available, hidden, surface }
157}
158
159pub fn preferred_surface_format(formats: &[wgpu::TextureFormat]) -> Option<wgpu::TextureFormat> {
163 formats.iter().copied().find(|f| !f.is_srgb()).or_else(|| formats.first().copied())
164}