1use std::fmt;
4
5use crate::canvas::{ScaleFilter, ScaleMode};
6use crate::color::Color;
7
8pub type DeviceHook =
29 std::sync::Arc<dyn Fn(&wgpu::Adapter, &mut wgpu::DeviceDescriptor<'_>) + Send + Sync>;
30
31#[derive(Clone)]
36pub struct GraphicsConfig {
37 pub vsync: bool,
38 pub depth_format: Option<wgpu::TextureFormat>,
40 pub msaa_samples: u32,
42 pub required_limits: Option<wgpu::Limits>,
51 pub required_features: wgpu::Features,
54 pub preferred_limits: Option<wgpu::Limits>,
60 pub preferred_features: wgpu::Features,
63 pub continue_without_requirements: bool,
69 pub device_hook: Option<DeviceHook>,
71 pub virtual_resolution: Option<(u32, u32)>,
81 pub scale_mode: ScaleMode,
83 pub scale_filter: ScaleFilter,
85 pub border_color: Color,
87}
88
89impl Default for GraphicsConfig {
90 fn default() -> Self {
91 Self {
92 vsync: true,
93 depth_format: None,
94 msaa_samples: 1,
95 required_limits: None,
96 required_features: wgpu::Features::empty(),
97 preferred_limits: None,
98 preferred_features: wgpu::Features::empty(),
99 continue_without_requirements: false,
100 device_hook: None,
101 virtual_resolution: None,
102 scale_mode: ScaleMode::Integer,
103 scale_filter: ScaleFilter::Nearest,
104 border_color: Color::BLACK,
105 }
106 }
107}
108
109impl fmt::Debug for GraphicsConfig {
110 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
111 f.debug_struct("GraphicsConfig")
112 .field("vsync", &self.vsync)
113 .field("depth_format", &self.depth_format)
114 .field("msaa_samples", &self.msaa_samples)
115 .field("required_limits", &self.required_limits)
116 .field("required_features", &self.required_features)
117 .field("preferred_limits", &self.preferred_limits)
118 .field("preferred_features", &self.preferred_features)
119 .field(
120 "continue_without_requirements",
121 &self.continue_without_requirements,
122 )
123 .field("device_hook", &self.device_hook.is_some())
124 .field("virtual_resolution", &self.virtual_resolution)
125 .field("scale_mode", &self.scale_mode)
126 .field("scale_filter", &self.scale_filter)
127 .field("border_color", &self.border_color)
128 .finish()
129 }
130}
131
132#[derive(Clone, Debug, PartialEq, Eq)]
134pub struct MissingLimit {
135 pub name: &'static str,
137 pub requested: u64,
138 pub available: u64,
139}
140
141#[derive(Clone, Debug, PartialEq, Eq)]
153pub struct DeviceRequirementsError {
154 pub adapter: String,
156 pub backend: wgpu::Backend,
157 pub missing_limits: Vec<MissingLimit>,
158 pub missing_features: wgpu::Features,
159}
160
161impl fmt::Display for DeviceRequirementsError {
162 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
163 write!(
164 f,
165 "adapter {:?} ({:?}) can't provide the requested device",
166 self.adapter, self.backend
167 )?;
168 if !self.missing_features.is_empty() {
169 write!(f, "; missing features: {:?}", self.missing_features)?;
170 }
171 for m in &self.missing_limits {
172 write!(
173 f,
174 "; limit {}: requested {}, available {}",
175 m.name, m.requested, m.available
176 )?;
177 }
178 Ok(())
179 }
180}
181
182impl std::error::Error for DeviceRequirementsError {}
183
184pub(crate) fn toolkit_limits(backend: wgpu::Backend) -> wgpu::Limits {
186 if backend == wgpu::Backend::Gl {
189 wgpu::Limits::downlevel_webgl2_defaults()
190 } else if cfg!(feature = "vector") {
191 wgpu::Limits::default()
192 } else {
193 wgpu::Limits::downlevel_defaults()
194 }
195}
196
197pub(crate) fn merged_requirements(
203 config: &GraphicsConfig,
204 backend: wgpu::Backend,
205 adapter_limits: &wgpu::Limits,
206 adapter_features: wgpu::Features,
207 with_required: bool,
208) -> (wgpu::Limits, wgpu::Features) {
209 let mut limits = toolkit_limits(backend).using_resolution(adapter_limits.clone());
210 let mut features = config.preferred_features & adapter_features;
211 if let Some(preferred) = &config.preferred_limits {
212 limits =
213 limits.or_better_values_from(&preferred.clone().or_worse_values_from(adapter_limits));
214 }
215 if with_required {
216 if let Some(extra) = &config.required_limits {
217 limits = limits.or_better_values_from(extra);
218 }
219 features |= config.required_features;
220 }
221 (limits, features)
222}
223
224pub(crate) fn check_requirements(
226 adapter: &str,
227 backend: wgpu::Backend,
228 adapter_limits: &wgpu::Limits,
229 adapter_features: wgpu::Features,
230 limits: &wgpu::Limits,
231 features: wgpu::Features,
232) -> Result<(), DeviceRequirementsError> {
233 let mut missing_limits = Vec::new();
234 limits.check_limits_with_fail_fn(adapter_limits, false, |name, requested, available| {
235 missing_limits.push(MissingLimit {
236 name,
237 requested,
238 available,
239 });
240 });
241 let missing_features = features - adapter_features;
242 if missing_limits.is_empty() && missing_features.is_empty() {
243 return Ok(());
244 }
245 Err(DeviceRequirementsError {
246 adapter: adapter.to_owned(),
247 backend,
248 missing_limits,
249 missing_features,
250 })
251}
252
253#[cfg(test)]
254mod tests {
255 use super::*;
256
257 #[test]
258 fn game_limits_raise_only_what_they_ask_for() {
259 let config = GraphicsConfig {
260 required_limits: Some(wgpu::Limits {
261 max_storage_buffers_per_shader_stage: 8,
262 ..wgpu::Limits::downlevel_defaults()
263 }),
264 ..Default::default()
265 };
266 let adapter = wgpu::Limits::default();
267 let (limits, _) = merged_requirements(
268 &config,
269 wgpu::Backend::Vulkan,
270 &adapter,
271 wgpu::Features::empty(),
272 true,
273 );
274 assert_eq!(limits.max_storage_buffers_per_shader_stage, 8);
275 let base = toolkit_limits(wgpu::Backend::Vulkan);
276 assert_eq!(limits.max_bind_groups, base.max_bind_groups);
277 assert_eq!(
279 limits.max_texture_dimension_2d,
280 adapter.max_texture_dimension_2d
281 );
282 }
283
284 #[test]
285 fn missing_limits_and_features_are_named() {
286 let adapter = wgpu::Limits::downlevel_defaults();
287 let wanted = wgpu::Limits {
288 max_storage_buffers_per_shader_stage: 16,
289 ..adapter.clone()
290 };
291 let err = check_requirements(
292 "test",
293 wgpu::Backend::Vulkan,
294 &adapter,
295 wgpu::Features::empty(),
296 &wanted,
297 wgpu::Features::SHADER_F16,
298 )
299 .unwrap_err();
300 assert_eq!(
301 err.missing_limits,
302 vec![MissingLimit {
303 name: "max_storage_buffers_per_shader_stage",
304 requested: 16,
305 available: u64::from(adapter.max_storage_buffers_per_shader_stage),
306 }]
307 );
308 assert_eq!(err.missing_features, wgpu::Features::SHADER_F16);
309 let msg = err.to_string();
310 assert!(
311 msg.contains("max_storage_buffers_per_shader_stage"),
312 "{msg}"
313 );
314 assert!(msg.contains("SHADER_F16"), "{msg}");
315 }
316
317 #[test]
318 fn satisfiable_requirements_pass() {
319 let adapter = wgpu::Limits::default();
320 assert!(
321 check_requirements(
322 "test",
323 wgpu::Backend::Vulkan,
324 &adapter,
325 wgpu::Features::empty(),
326 &wgpu::Limits::downlevel_defaults(),
327 wgpu::Features::empty(),
328 )
329 .is_ok()
330 );
331 }
332
333 #[test]
334 fn preferred_limits_are_clamped_to_the_adapter() {
335 let config = GraphicsConfig {
336 preferred_limits: Some(wgpu::Limits {
337 max_storage_buffers_per_shader_stage: 8,
338 ..wgpu::Limits::downlevel_defaults()
339 }),
340 preferred_features: wgpu::Features::SHADER_F16 | wgpu::Features::TIMESTAMP_QUERY,
341 ..Default::default()
342 };
343 let webgpu = wgpu::Limits::default();
345 let (limits, features) = merged_requirements(
346 &config,
347 wgpu::Backend::BrowserWebGpu,
348 &webgpu,
349 wgpu::Features::SHADER_F16,
350 true,
351 );
352 assert_eq!(limits.max_storage_buffers_per_shader_stage, 8);
353 assert_eq!(features, wgpu::Features::SHADER_F16);
354 assert!(
355 check_requirements(
356 "t",
357 wgpu::Backend::BrowserWebGpu,
358 &webgpu,
359 features,
360 &limits,
361 features
362 )
363 .is_ok()
364 );
365
366 let gl = wgpu::Limits::downlevel_webgl2_defaults();
368 let (limits, features) = merged_requirements(
369 &config,
370 wgpu::Backend::Gl,
371 &gl,
372 wgpu::Features::empty(),
373 true,
374 );
375 assert_eq!(limits.max_storage_buffers_per_shader_stage, 0);
376 assert!(features.is_empty());
377 assert!(
378 check_requirements("t", wgpu::Backend::Gl, &gl, features, &limits, features).is_ok()
379 );
380 }
381
382 #[test]
383 fn without_required_leaves_out_only_the_required_part() {
384 let config = GraphicsConfig {
385 required_limits: Some(wgpu::Limits {
386 max_storage_buffers_per_shader_stage: 8,
387 ..wgpu::Limits::downlevel_defaults()
388 }),
389 required_features: wgpu::Features::SHADER_F16,
390 ..Default::default()
391 };
392 let gl = wgpu::Limits::downlevel_webgl2_defaults();
393 let (limits, features) = merged_requirements(
394 &config,
395 wgpu::Backend::Gl,
396 &gl,
397 wgpu::Features::empty(),
398 true,
399 );
400 assert!(
401 check_requirements(
402 "t",
403 wgpu::Backend::Gl,
404 &gl,
405 wgpu::Features::empty(),
406 &limits,
407 features
408 )
409 .is_err()
410 );
411 let (limits, features) = merged_requirements(
412 &config,
413 wgpu::Backend::Gl,
414 &gl,
415 wgpu::Features::empty(),
416 false,
417 );
418 assert!(
419 check_requirements(
420 "t",
421 wgpu::Backend::Gl,
422 &gl,
423 wgpu::Features::empty(),
424 &limits,
425 features
426 )
427 .is_ok()
428 );
429 }
430}