astrelis_render/
context.rs

1use crate::features::GpuFeatures;
2
3/// A globally shared graphics context.
4pub struct GraphicsContext {
5    pub instance: wgpu::Instance,
6    pub adapter: wgpu::Adapter,
7    pub device: wgpu::Device,
8    pub queue: wgpu::Queue,
9    /// The GPU features that were enabled on this context.
10    enabled_features: GpuFeatures,
11}
12
13impl GraphicsContext {
14    /// Creates a new graphics context synchronously.
15    ///
16    /// See [`GraphicsContext::new`] for the asynchronous version.
17    pub fn new_sync() -> &'static Self {
18        pollster::block_on(Self::new())
19    }
20
21    /// Creates a new graphics context asynchronously.
22    ///
23    /// This returns a static reference to simplify the public API and lifecycle
24    pub async fn new() -> &'static Self {
25        Self::new_with_descriptor(GraphicsContextDescriptor::default()).await
26    }
27
28    /// Creates a new graphics context with custom descriptor.
29    ///
30    /// # Panics
31    ///
32    /// Panics if any required features are not supported by the adapter.
33    pub async fn new_with_descriptor(descriptor: GraphicsContextDescriptor) -> &'static Self {
34        let instance = wgpu::Instance::new(&wgpu::InstanceDescriptor {
35            backends: descriptor.backends,
36            ..Default::default()
37        });
38
39        let adapter = instance
40            .request_adapter(&wgpu::RequestAdapterOptions {
41                power_preference: descriptor.power_preference,
42                compatible_surface: None,
43                force_fallback_adapter: descriptor.force_fallback_adapter,
44            })
45            .await
46            .expect("Failed to find a suitable GPU adapter");
47
48        // Check required features
49        let required_result = descriptor.required_gpu_features.check_support(&adapter);
50        if let Some(missing) = required_result.missing() {
51            panic!(
52                "Required GPU features are not supported by the adapter: {:?}\n\
53                 Adapter: {:?}\n\
54                 Supported features: {:?}",
55                missing,
56                adapter.get_info().name,
57                GpuFeatures::from_wgpu(adapter.features())
58            );
59        }
60
61        // Determine which requested features are available
62        let available_requested = descriptor.requested_gpu_features
63            & GpuFeatures::from_wgpu(adapter.features());
64
65        // Log which requested features were not available
66        let unavailable_requested =
67            descriptor.requested_gpu_features - available_requested;
68        if !unavailable_requested.is_empty() {
69            tracing::warn!(
70                "Some requested GPU features are not available: {:?}",
71                unavailable_requested
72            );
73        }
74
75        // Combine all features to enable
76        let enabled_features = descriptor.required_gpu_features | available_requested;
77        let wgpu_features = enabled_features.to_wgpu() | descriptor.additional_wgpu_features;
78
79        let (device, queue) = adapter
80            .request_device(&wgpu::DeviceDescriptor {
81                required_features: wgpu_features,
82                required_limits: descriptor.limits.clone(),
83                label: descriptor.label,
84                ..Default::default()
85            })
86            .await
87            .expect("Failed to create device");
88
89        tracing::info!(
90            "Created graphics context with features: {:?}",
91            enabled_features
92        );
93
94        Box::leak(Box::new(Self {
95            instance,
96            adapter,
97            device,
98            queue,
99            enabled_features,
100        }))
101    }
102
103    /// Get device info
104    pub fn info(&self) -> wgpu::AdapterInfo {
105        self.adapter.get_info()
106    }
107
108    /// Get device limits
109    pub fn limits(&self) -> wgpu::Limits {
110        self.device.limits()
111    }
112
113    /// Get raw wgpu device features
114    pub fn wgpu_features(&self) -> wgpu::Features {
115        self.device.features()
116    }
117
118    /// Get the enabled GPU features (high-level wrapper).
119    pub fn gpu_features(&self) -> GpuFeatures {
120        self.enabled_features
121    }
122
123    /// Check if a specific GPU feature is enabled.
124    pub fn has_feature(&self, feature: GpuFeatures) -> bool {
125        self.enabled_features.contains(feature)
126    }
127
128    /// Check if all specified GPU features are enabled.
129    pub fn has_all_features(&self, features: GpuFeatures) -> bool {
130        self.enabled_features.contains(features)
131    }
132
133    /// Assert that a feature is available, panicking with a clear message if not.
134    ///
135    /// Use this before operations that require specific features.
136    pub fn require_feature(&self, feature: GpuFeatures) {
137        if !self.has_feature(feature) {
138            panic!(
139                "GPU feature {:?} is required but not enabled.\n\
140                 Enabled features: {:?}\n\
141                 To use this feature, add it to `required_gpu_features` in GraphicsContextDescriptor.",
142                feature, self.enabled_features
143            );
144        }
145    }
146}
147
148/// Descriptor for configuring graphics context creation.
149pub struct GraphicsContextDescriptor {
150    /// GPU backends to use
151    pub backends: wgpu::Backends,
152    /// Power preference for adapter selection
153    pub power_preference: wgpu::PowerPreference,
154    /// Whether to force fallback adapter
155    pub force_fallback_adapter: bool,
156    /// Required GPU features (panics if not available).
157    ///
158    /// Use this for features that your application cannot function without.
159    pub required_gpu_features: GpuFeatures,
160    /// Requested GPU features (best-effort, logs warning if unavailable).
161    ///
162    /// Use this for features that would be nice to have but are not essential.
163    pub requested_gpu_features: GpuFeatures,
164    /// Additional raw wgpu features to enable (for features not covered by GpuFeatures).
165    pub additional_wgpu_features: wgpu::Features,
166    /// Required device limits
167    pub limits: wgpu::Limits,
168    /// Optional label for debugging
169    pub label: Option<&'static str>,
170}
171
172impl Default for GraphicsContextDescriptor {
173    fn default() -> Self {
174        Self {
175            backends: wgpu::Backends::all(),
176            power_preference: wgpu::PowerPreference::HighPerformance,
177            force_fallback_adapter: false,
178            required_gpu_features: GpuFeatures::empty(),
179            requested_gpu_features: GpuFeatures::empty(),
180            additional_wgpu_features: wgpu::Features::empty(),
181            limits: wgpu::Limits::default(),
182            label: None,
183        }
184    }
185}
186
187impl GraphicsContextDescriptor {
188    /// Create a new descriptor with default settings.
189    pub fn new() -> Self {
190        Self::default()
191    }
192
193    /// Set required GPU features (panics if not available).
194    pub fn require_features(mut self, features: GpuFeatures) -> Self {
195        self.required_gpu_features = features;
196        self
197    }
198
199    /// Set requested GPU features (best-effort, warns if unavailable).
200    pub fn request_features(mut self, features: GpuFeatures) -> Self {
201        self.requested_gpu_features = features;
202        self
203    }
204
205    /// Add additional required features.
206    pub fn with_required_features(mut self, features: GpuFeatures) -> Self {
207        self.required_gpu_features |= features;
208        self
209    }
210
211    /// Add additional requested features.
212    pub fn with_requested_features(mut self, features: GpuFeatures) -> Self {
213        self.requested_gpu_features |= features;
214        self
215    }
216
217    /// Set additional raw wgpu features (for features not covered by GpuFeatures).
218    pub fn with_wgpu_features(mut self, features: wgpu::Features) -> Self {
219        self.additional_wgpu_features = features;
220        self
221    }
222
223    /// Set the power preference.
224    pub fn power_preference(mut self, preference: wgpu::PowerPreference) -> Self {
225        self.power_preference = preference;
226        self
227    }
228
229    /// Set the backends to use.
230    pub fn backends(mut self, backends: wgpu::Backends) -> Self {
231        self.backends = backends;
232        self
233    }
234
235    /// Set the device limits.
236    pub fn limits(mut self, limits: wgpu::Limits) -> Self {
237        self.limits = limits;
238        self
239    }
240
241    /// Set the debug label.
242    pub fn label(mut self, label: &'static str) -> Self {
243        self.label = Some(label);
244        self
245    }
246}