1use std::sync::Arc;
12
13use crate::backend;
14use crate::exit;
15
16#[derive(Debug, Clone)]
18pub struct ContextOptions {
19 pub backends: wgpu::Backends,
21 pub adapter_name: Option<String>,
23 pub low_power: bool,
25 pub min_storage_buffers_per_stage: u32,
27 pub limits_cap: Option<LimitsCap>,
29 pub label: String,
31}
32
33impl ContextOptions {
34 pub fn new(backends: wgpu::Backends, label: impl Into<String>) -> Self {
36 Self {
37 backends,
38 adapter_name: None,
39 low_power: false,
40 min_storage_buffers_per_stage: 8,
41 limits_cap: None,
42 label: label.into(),
43 }
44 }
45}
46
47#[derive(Debug, Clone, Copy, PartialEq, Eq)]
49pub struct LimitsCap {
50 pub storage_binding_bytes: u64,
52 pub buffer_bytes: u64,
54}
55
56pub fn parse_limits_cap(text: &str) -> Option<LimitsCap> {
60 let v = text.trim().to_ascii_lowercase();
61 match v.as_str() {
62 "" => None,
63 "webgpu" | "downlevel" | "default" => {
64 let d = wgpu::Limits::default();
65 Some(LimitsCap {
66 storage_binding_bytes: d.max_storage_buffer_binding_size,
67 buffer_bytes: d.max_buffer_size,
68 })
69 }
70 n => {
71 let mb: u64 = n
72 .trim_end_matches("mib")
73 .trim_end_matches("mb")
74 .trim()
75 .parse()
76 .ok()?;
77 let bytes = mb.max(1) << 20;
78 Some(LimitsCap {
79 storage_binding_bytes: bytes,
80 buffer_bytes: 2 * bytes,
81 })
82 }
83 }
84}
85
86pub fn pick_adapter(names: &[String], wanted: &str) -> Option<usize> {
89 names.iter().position(|n| n == wanted)
90}
91
92pub struct GpuContext {
94 pub device: wgpu::Device,
95 pub queue: wgpu::Queue,
96 pub info: wgpu::AdapterInfo,
97 pub max_buffer: u64,
99}
100
101impl std::fmt::Debug for GpuContext {
102 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
103 f.debug_struct("GpuContext")
104 .field("adapter", &self.info.name)
105 .field("backend", &self.info.backend)
106 .field("max_buffer", &self.max_buffer)
107 .finish()
108 }
109}
110
111fn new_instance(backends: wgpu::Backends) -> wgpu::Instance {
113 let mut desc = wgpu::InstanceDescriptor::new_without_display_handle();
114 desc.backends = backends;
115 desc.backend_options = backend::backend_options();
117 desc.flags = backend::instance_flags_from_env(desc.flags);
119 wgpu::Instance::new(desc)
120}
121
122fn usable_adapters(instance: &wgpu::Instance, backends: wgpu::Backends) -> Vec<wgpu::Adapter> {
124 pollster::block_on(instance.enumerate_adapters(backends))
125 .into_iter()
126 .filter(|a| a.get_info().device_type != wgpu::DeviceType::Cpu)
127 .collect()
128}
129
130pub fn adapter_names(backends: wgpu::Backends) -> Vec<String> {
132 usable_adapters(&new_instance(backends), backends)
133 .iter()
134 .map(|a| a.get_info().name)
135 .collect()
136}
137
138impl GpuContext {
139 pub fn create(
145 options: &ContextOptions,
146 on_failure: impl Fn(String) + Send + Sync + 'static,
147 ) -> Result<GpuContext, String> {
148 let Some(_work) = exit::enter() else {
150 return Err("the process is ending".into());
151 };
152 let on_failure: Arc<dyn Fn(String) + Send + Sync> = Arc::new(on_failure);
153 backend::with_init_marker(options.backends, || {
154 std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
155 create_unguarded(options, &on_failure)
156 }))
157 .unwrap_or_else(|_| Err("device creation panicked".into()))
158 })
159 }
160}
161
162fn create_unguarded(
163 options: &ContextOptions,
164 on_failure: &Arc<dyn Fn(String) + Send + Sync>,
165) -> Result<GpuContext, String> {
166 let backends = options.backends;
167 let instance = new_instance(backends);
168 let power_preference = if options.low_power {
169 wgpu::PowerPreference::LowPower
170 } else {
171 wgpu::PowerPreference::HighPerformance
172 };
173 let by_name = options.adapter_name.as_deref().and_then(|wanted| {
174 let found = usable_adapters(&instance, backends);
175 let names: Vec<String> = found.iter().map(|a| a.get_info().name).collect();
176 let index = pick_adapter(&names, wanted);
177 if index.is_none() {
178 log::warn!(
179 "gpu: no adapter named {wanted:?} among {names:?}; using the default choice"
180 );
181 }
182 index.and_then(|i| found.into_iter().nth(i))
183 });
184 let adapter = match by_name {
185 Some(adapter) => adapter,
186 None => pollster::block_on(instance.request_adapter(&wgpu::RequestAdapterOptions {
187 power_preference,
188 ..Default::default()
189 }))
190 .map_err(|e| format!("no GPU adapter among {backends:?} ({e})"))?,
191 };
192 let info = adapter.get_info();
193 if info.device_type == wgpu::DeviceType::Cpu {
194 log::info!("gpu: only a software adapter ({})", info.name);
195 return Err(format!("software adapter ({}) skipped", info.name));
196 }
197 let mut limits = adapter.limits();
198 if limits.max_storage_buffers_per_shader_stage < options.min_storage_buffers_per_stage {
199 log::info!("gpu: {} has too few storage buffers per stage", info.name);
200 return Err(format!(
201 "{} has too few storage buffers per shader stage ({} < {})",
202 info.name,
203 limits.max_storage_buffers_per_shader_stage,
204 options.min_storage_buffers_per_stage
205 ));
206 }
207 if let Some(cap) = options.limits_cap {
208 limits.max_storage_buffer_binding_size = limits
209 .max_storage_buffer_binding_size
210 .min(cap.storage_binding_bytes);
211 limits.max_buffer_size = limits.max_buffer_size.min(cap.buffer_bytes);
212 log::info!(
213 "gpu: limits capped: storage bindings <= {} MiB, buffers <= {} MiB",
214 cap.storage_binding_bytes >> 20,
215 cap.buffer_bytes >> 20
216 );
217 }
218 let (device, queue) = pollster::block_on(adapter.request_device(&wgpu::DeviceDescriptor {
219 label: Some(options.label.as_str()),
220 required_limits: limits.clone(),
221 ..Default::default()
222 }))
223 .map_err(|e| format!("{}: device creation failed ({e})", info.name))?;
224
225 let uncaptured = Arc::clone(on_failure);
227 device.on_uncaptured_error(Arc::new(move |e| {
228 log::error!("gpu: {e}");
229 uncaptured(format!("device error: {e}"));
230 }));
231 let lost = Arc::clone(on_failure);
233 device.set_device_lost_callback(move |reason, msg| {
234 log::error!("gpu: device lost ({reason:?}): {msg}");
235 lost(format!("device lost ({reason:?}): {msg}"));
236 });
237
238 let max_buffer = limits
239 .max_storage_buffer_binding_size
240 .min(limits.max_buffer_size);
241 log::info!(
242 "gpu: {} ({:?}), buffers <= {} MiB",
243 info.name,
244 info.backend,
245 max_buffer >> 20
246 );
247 Ok(GpuContext {
248 device,
249 queue,
250 info,
251 max_buffer,
252 })
253}