1use crate::features::GpuFeatures;
2use std::sync::Arc;
3
4#[derive(Debug, Clone)]
6pub enum GraphicsError {
7 NoAdapter,
9
10 DeviceCreationFailed(String),
12
13 MissingRequiredFeatures {
15 missing: GpuFeatures,
16 adapter_name: String,
17 supported: GpuFeatures,
18 },
19
20 SurfaceCreationFailed(String),
22
23 SurfaceConfigurationFailed(String),
25
26 SurfaceTextureAcquisitionFailed(String),
28}
29
30impl std::fmt::Display for GraphicsError {
31 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
32 match self {
33 GraphicsError::NoAdapter => {
34 write!(f, "Failed to find a suitable GPU adapter")
35 }
36 GraphicsError::DeviceCreationFailed(msg) => {
37 write!(f, "Failed to create device: {}", msg)
38 }
39 GraphicsError::MissingRequiredFeatures { missing, adapter_name, supported } => {
40 write!(
41 f,
42 "Required GPU features not supported by adapter '{}': {:?}\nSupported: {:?}",
43 adapter_name, missing, supported
44 )
45 }
46 GraphicsError::SurfaceCreationFailed(msg) => {
47 write!(f, "Failed to create surface: {}", msg)
48 }
49 GraphicsError::SurfaceConfigurationFailed(msg) => {
50 write!(f, "Failed to get surface configuration: {}", msg)
51 }
52 GraphicsError::SurfaceTextureAcquisitionFailed(msg) => {
53 write!(f, "Failed to acquire surface texture: {}", msg)
54 }
55 }
56 }
57}
58
59impl std::error::Error for GraphicsError {}
60
61pub struct GraphicsContext {
87 pub instance: wgpu::Instance,
88 pub adapter: wgpu::Adapter,
89 pub device: wgpu::Device,
90 pub queue: wgpu::Queue,
91 enabled_features: GpuFeatures,
93}
94
95impl GraphicsContext {
96 pub async fn new_owned() -> Result<Arc<Self>, GraphicsError> {
111 Self::new_owned_with_descriptor(GraphicsContextDescriptor::default()).await
112 }
113
114 pub fn new_owned_sync() -> Result<Arc<Self>, GraphicsError> {
125 pollster::block_on(Self::new_owned())
126 }
127
128 pub fn new_owned_sync_or_panic() -> Arc<Self> {
138 Self::new_owned_sync().expect("Failed to create graphics context")
139 }
140
141 pub async fn new_owned_with_descriptor(descriptor: GraphicsContextDescriptor) -> Result<Arc<Self>, GraphicsError> {
143 let context = Self::create_context_internal(descriptor).await?;
144 Ok(Arc::new(context))
145 }
146
147 async fn create_context_internal(descriptor: GraphicsContextDescriptor) -> Result<Self, GraphicsError> {
149 let instance = wgpu::Instance::new(&wgpu::InstanceDescriptor {
150 backends: descriptor.backends,
151 ..Default::default()
152 });
153
154 let adapter = instance
155 .request_adapter(&wgpu::RequestAdapterOptions {
156 power_preference: descriptor.power_preference,
157 compatible_surface: None,
158 force_fallback_adapter: descriptor.force_fallback_adapter,
159 })
160 .await
161 .map_err(|_| GraphicsError::NoAdapter)?;
162
163 let required_result = descriptor.required_gpu_features.check_support(&adapter);
165 if let Some(missing) = required_result.missing() {
166 return Err(GraphicsError::MissingRequiredFeatures {
167 missing,
168 adapter_name: adapter.get_info().name.clone(),
169 supported: GpuFeatures::from_wgpu(adapter.features()),
170 });
171 }
172
173 let available_requested = descriptor.requested_gpu_features
175 & GpuFeatures::from_wgpu(adapter.features());
176
177 let unavailable_requested =
179 descriptor.requested_gpu_features - available_requested;
180 if !unavailable_requested.is_empty() {
181 tracing::warn!(
182 "Some requested GPU features are not available: {:?}",
183 unavailable_requested
184 );
185 }
186
187 let enabled_features = descriptor.required_gpu_features | available_requested;
189 let wgpu_features = enabled_features.to_wgpu() | descriptor.additional_wgpu_features;
190
191 let (device, queue) = adapter
192 .request_device(&wgpu::DeviceDescriptor {
193 required_features: wgpu_features,
194 required_limits: descriptor.limits.clone(),
195 label: descriptor.label,
196 ..Default::default()
197 })
198 .await
199 .map_err(|e| GraphicsError::DeviceCreationFailed(e.to_string()))?;
200
201 tracing::info!(
202 "Created graphics context with features: {:?}",
203 enabled_features
204 );
205
206 Ok(Self {
207 instance,
208 adapter,
209 device,
210 queue,
211 enabled_features,
212 })
213 }
214
215 pub fn info(&self) -> wgpu::AdapterInfo {
217 self.adapter.get_info()
218 }
219
220 pub fn limits(&self) -> wgpu::Limits {
222 self.device.limits()
223 }
224
225 pub fn wgpu_features(&self) -> wgpu::Features {
227 self.device.features()
228 }
229
230 pub fn gpu_features(&self) -> GpuFeatures {
232 self.enabled_features
233 }
234
235 pub fn has_feature(&self, feature: GpuFeatures) -> bool {
237 self.enabled_features.contains(feature)
238 }
239
240 pub fn has_all_features(&self, features: GpuFeatures) -> bool {
242 self.enabled_features.contains(features)
243 }
244
245 pub fn require_feature(&self, feature: GpuFeatures) {
249 if !self.has_feature(feature) {
250 panic!(
251 "GPU feature {:?} is required but not enabled.\n\
252 Enabled features: {:?}\n\
253 To use this feature, add it to `required_gpu_features` in GraphicsContextDescriptor.",
254 feature, self.enabled_features
255 );
256 }
257 }
258
259 pub fn supports_texture_format(
274 &self,
275 format: wgpu::TextureFormat,
276 usages: wgpu::TextureUsages,
277 ) -> bool {
278 let capabilities = self.adapter.get_texture_format_features(format);
279
280 if usages.contains(wgpu::TextureUsages::TEXTURE_BINDING)
282 && !capabilities
283 .allowed_usages
284 .contains(wgpu::TextureUsages::TEXTURE_BINDING)
285 {
286 return false;
287 }
288 if usages.contains(wgpu::TextureUsages::STORAGE_BINDING)
289 && !capabilities
290 .allowed_usages
291 .contains(wgpu::TextureUsages::STORAGE_BINDING)
292 {
293 return false;
294 }
295 if usages.contains(wgpu::TextureUsages::RENDER_ATTACHMENT)
296 && !capabilities
297 .allowed_usages
298 .contains(wgpu::TextureUsages::RENDER_ATTACHMENT)
299 {
300 return false;
301 }
302 if usages.contains(wgpu::TextureUsages::COPY_SRC)
303 && !capabilities
304 .allowed_usages
305 .contains(wgpu::TextureUsages::COPY_SRC)
306 {
307 return false;
308 }
309 if usages.contains(wgpu::TextureUsages::COPY_DST)
310 && !capabilities
311 .allowed_usages
312 .contains(wgpu::TextureUsages::COPY_DST)
313 {
314 return false;
315 }
316
317 true
318 }
319
320 pub fn texture_format_capabilities(
325 &self,
326 format: wgpu::TextureFormat,
327 ) -> wgpu::TextureFormatFeatures {
328 self.adapter.get_texture_format_features(format)
329 }
330
331 #[inline]
339 pub fn max_texture_dimension_2d(&self) -> u32 {
340 self.device.limits().max_texture_dimension_2d
341 }
342
343 #[inline]
347 pub fn max_buffer_size(&self) -> u64 {
348 self.device.limits().max_buffer_size
349 }
350
351 #[inline]
355 pub fn min_uniform_buffer_offset_alignment(&self) -> u32 {
356 self.device.limits().min_uniform_buffer_offset_alignment
357 }
358
359 #[inline]
363 pub fn min_storage_buffer_offset_alignment(&self) -> u32 {
364 self.device.limits().min_storage_buffer_offset_alignment
365 }
366
367 #[inline]
372 pub fn max_push_constant_size(&self) -> u32 {
373 self.device.limits().max_push_constant_size
374 }
375
376 #[inline]
378 pub fn max_texture_dimension_1d(&self) -> u32 {
379 self.device.limits().max_texture_dimension_1d
380 }
381
382 #[inline]
384 pub fn max_texture_dimension_3d(&self) -> u32 {
385 self.device.limits().max_texture_dimension_3d
386 }
387
388 #[inline]
390 pub fn max_texture_array_layers(&self) -> u32 {
391 self.device.limits().max_texture_array_layers
392 }
393
394 #[inline]
396 pub fn max_bind_groups(&self) -> u32 {
397 self.device.limits().max_bind_groups
398 }
399
400 #[inline]
402 pub fn max_bindings_per_bind_group(&self) -> u32 {
403 self.device.limits().max_bindings_per_bind_group
404 }
405
406 #[inline]
408 pub fn max_dynamic_uniform_buffers_per_pipeline_layout(&self) -> u32 {
409 self.device
410 .limits()
411 .max_dynamic_uniform_buffers_per_pipeline_layout
412 }
413
414 #[inline]
416 pub fn max_dynamic_storage_buffers_per_pipeline_layout(&self) -> u32 {
417 self.device
418 .limits()
419 .max_dynamic_storage_buffers_per_pipeline_layout
420 }
421
422 #[inline]
424 pub fn max_sampled_textures_per_shader_stage(&self) -> u32 {
425 self.device.limits().max_sampled_textures_per_shader_stage
426 }
427
428 #[inline]
430 pub fn max_samplers_per_shader_stage(&self) -> u32 {
431 self.device.limits().max_samplers_per_shader_stage
432 }
433
434 #[inline]
436 pub fn max_storage_buffers_per_shader_stage(&self) -> u32 {
437 self.device.limits().max_storage_buffers_per_shader_stage
438 }
439
440 #[inline]
442 pub fn max_storage_textures_per_shader_stage(&self) -> u32 {
443 self.device.limits().max_storage_textures_per_shader_stage
444 }
445
446 #[inline]
448 pub fn max_uniform_buffers_per_shader_stage(&self) -> u32 {
449 self.device.limits().max_uniform_buffers_per_shader_stage
450 }
451
452 #[inline]
454 pub fn max_uniform_buffer_binding_size(&self) -> u32 {
455 self.device.limits().max_uniform_buffer_binding_size
456 }
457
458 #[inline]
460 pub fn max_storage_buffer_binding_size(&self) -> u32 {
461 self.device.limits().max_storage_buffer_binding_size
462 }
463
464 #[inline]
466 pub fn max_vertex_buffers(&self) -> u32 {
467 self.device.limits().max_vertex_buffers
468 }
469
470 #[inline]
472 pub fn max_vertex_attributes(&self) -> u32 {
473 self.device.limits().max_vertex_attributes
474 }
475
476 #[inline]
478 pub fn max_vertex_buffer_array_stride(&self) -> u32 {
479 self.device.limits().max_vertex_buffer_array_stride
480 }
481
482 #[inline]
484 pub fn max_compute_workgroup_storage_size(&self) -> u32 {
485 self.device.limits().max_compute_workgroup_storage_size
486 }
487
488 #[inline]
490 pub fn max_compute_invocations_per_workgroup(&self) -> u32 {
491 self.device.limits().max_compute_invocations_per_workgroup
492 }
493
494 #[inline]
496 pub fn max_compute_workgroup_size_x(&self) -> u32 {
497 self.device.limits().max_compute_workgroup_size_x
498 }
499
500 #[inline]
502 pub fn max_compute_workgroup_size_y(&self) -> u32 {
503 self.device.limits().max_compute_workgroup_size_y
504 }
505
506 #[inline]
508 pub fn max_compute_workgroup_size_z(&self) -> u32 {
509 self.device.limits().max_compute_workgroup_size_z
510 }
511
512 #[inline]
514 pub fn max_compute_workgroups_per_dimension(&self) -> u32 {
515 self.device.limits().max_compute_workgroups_per_dimension
516 }
517}
518
519pub struct GraphicsContextDescriptor {
521 pub backends: wgpu::Backends,
523 pub power_preference: wgpu::PowerPreference,
525 pub force_fallback_adapter: bool,
527 pub required_gpu_features: GpuFeatures,
531 pub requested_gpu_features: GpuFeatures,
535 pub additional_wgpu_features: wgpu::Features,
537 pub limits: wgpu::Limits,
539 pub label: Option<&'static str>,
541}
542
543impl Default for GraphicsContextDescriptor {
544 fn default() -> Self {
545 Self {
546 backends: wgpu::Backends::all(),
547 power_preference: wgpu::PowerPreference::HighPerformance,
548 force_fallback_adapter: false,
549 required_gpu_features: GpuFeatures::empty(),
550 requested_gpu_features: GpuFeatures::empty(),
551 additional_wgpu_features: wgpu::Features::empty(),
552 limits: wgpu::Limits::default(),
553 label: None,
554 }
555 }
556}
557
558impl GraphicsContextDescriptor {
559 pub fn new() -> Self {
561 Self::default()
562 }
563
564 pub fn require_features(mut self, features: GpuFeatures) -> Self {
566 self.required_gpu_features = features;
567 self
568 }
569
570 pub fn request_features(mut self, features: GpuFeatures) -> Self {
572 self.requested_gpu_features = features;
573 self
574 }
575
576 pub fn with_required_features(mut self, features: GpuFeatures) -> Self {
578 self.required_gpu_features |= features;
579 self
580 }
581
582 pub fn with_requested_features(mut self, features: GpuFeatures) -> Self {
584 self.requested_gpu_features |= features;
585 self
586 }
587
588 pub fn with_wgpu_features(mut self, features: wgpu::Features) -> Self {
590 self.additional_wgpu_features = features;
591 self
592 }
593
594 pub fn power_preference(mut self, preference: wgpu::PowerPreference) -> Self {
596 self.power_preference = preference;
597 self
598 }
599
600 pub fn backends(mut self, backends: wgpu::Backends) -> Self {
602 self.backends = backends;
603 self
604 }
605
606 pub fn limits(mut self, limits: wgpu::Limits) -> Self {
608 self.limits = limits;
609 self
610 }
611
612 pub fn label(mut self, label: &'static str) -> Self {
614 self.label = Some(label);
615 self
616 }
617}