astrelis_render/
features.rs

1//! GPU feature detection and management.
2//!
3//! This module provides a type-safe wrapper around wgpu features with support for
4//! required vs requested features.
5
6use bitflags::bitflags;
7
8bitflags! {
9    /// GPU features that can be requested or required.
10    ///
11    /// These map to common wgpu features that are useful for rendering applications.
12    /// Use `GpuFeatures::to_wgpu()` to convert to `wgpu::Features`.
13    #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
14    pub struct GpuFeatures: u32 {
15        /// Allows the use of `first_instance` parameter in indirect draw calls.
16        /// Required for GPU-driven rendering with indirect buffers.
17        const INDIRECT_FIRST_INSTANCE = 1 << 0;
18
19        /// Allows multiple indirect draw calls in a single command.
20        /// Enables efficient batched rendering with `multi_draw_indirect`.
21        const MULTI_DRAW_INDIRECT = 1 << 1;
22
23        /// Allows push constants in shaders for small, frequently updated data.
24        /// More efficient than uniform buffers for small amounts of per-draw data.
25        const PUSH_CONSTANTS = 1 << 2;
26
27        /// BC texture compression (DXT1, DXT3, DXT5).
28        /// Common on desktop platforms, reduces texture memory usage.
29        const TEXTURE_COMPRESSION_BC = 1 << 3;
30
31        /// Allows disabling depth clipping in the rasterizer.
32        /// Useful for certain shadow mapping techniques.
33        const DEPTH_CLIP_CONTROL = 1 << 4;
34
35        /// 16-bit floating point support in shaders.
36        /// Can improve performance on some GPUs for certain workloads.
37        const SHADER_F16 = 1 << 5;
38
39        /// Allows non-zero `first_vertex` and `first_instance` in indirect draw calls.
40        /// Note: This is a subset of INDIRECT_FIRST_INSTANCE on some platforms.
41        const INDIRECT_FIRST_VERTEX = 1 << 6;
42
43        /// Polygon mode: line (wireframe rendering).
44        const POLYGON_MODE_LINE = 1 << 7;
45
46        /// Polygon mode: point.
47        const POLYGON_MODE_POINT = 1 << 8;
48
49        /// Conservative rasterization for better triangle coverage.
50        const CONSERVATIVE_RASTERIZATION = 1 << 9;
51
52        /// Texture binding arrays (bindless textures).
53        /// Enables more flexible texture access patterns in shaders.
54        const TEXTURE_BINDING_ARRAY = 1 << 10;
55
56        /// Sampled texture and storage buffer binding arrays.
57        const BUFFER_BINDING_ARRAY = 1 << 11;
58
59        /// Storage resource binding arrays with dynamic indexing.
60        const STORAGE_RESOURCE_BINDING_ARRAY = 1 << 12;
61
62        /// Partially bound binding arrays.
63        /// Allows leaving some array slots unbound.
64        const PARTIALLY_BOUND_BINDING_ARRAY = 1 << 13;
65
66        /// 32-bit floating point texture filtering.
67        const FLOAT32_FILTERABLE = 1 << 14;
68
69        /// RG11B10 unsigned floating point render target format.
70        const RG11B10UFLOAT_RENDERABLE = 1 << 15;
71
72        /// BGRA8 unorm storage texture support.
73        const BGRA8UNORM_STORAGE = 1 << 16;
74    }
75}
76
77impl GpuFeatures {
78    /// Convert to wgpu::Features.
79    pub fn to_wgpu(self) -> wgpu::Features {
80        let mut features = wgpu::Features::empty();
81
82        if self.contains(GpuFeatures::INDIRECT_FIRST_INSTANCE) {
83            features |= wgpu::Features::INDIRECT_FIRST_INSTANCE;
84        }
85        if self.contains(GpuFeatures::MULTI_DRAW_INDIRECT) {
86            features |= wgpu::Features::MULTI_DRAW_INDIRECT_COUNT;
87        }
88        if self.contains(GpuFeatures::PUSH_CONSTANTS) {
89            features |= wgpu::Features::PUSH_CONSTANTS;
90        }
91        if self.contains(GpuFeatures::TEXTURE_COMPRESSION_BC) {
92            features |= wgpu::Features::TEXTURE_COMPRESSION_BC;
93        }
94        if self.contains(GpuFeatures::DEPTH_CLIP_CONTROL) {
95            features |= wgpu::Features::DEPTH_CLIP_CONTROL;
96        }
97        if self.contains(GpuFeatures::SHADER_F16) {
98            features |= wgpu::Features::SHADER_F16;
99        }
100        if self.contains(GpuFeatures::POLYGON_MODE_LINE) {
101            features |= wgpu::Features::POLYGON_MODE_LINE;
102        }
103        if self.contains(GpuFeatures::POLYGON_MODE_POINT) {
104            features |= wgpu::Features::POLYGON_MODE_POINT;
105        }
106        if self.contains(GpuFeatures::CONSERVATIVE_RASTERIZATION) {
107            features |= wgpu::Features::CONSERVATIVE_RASTERIZATION;
108        }
109        if self.contains(GpuFeatures::TEXTURE_BINDING_ARRAY) {
110            features |= wgpu::Features::TEXTURE_BINDING_ARRAY;
111        }
112        if self.contains(GpuFeatures::BUFFER_BINDING_ARRAY) {
113            features |= wgpu::Features::BUFFER_BINDING_ARRAY;
114        }
115        if self.contains(GpuFeatures::STORAGE_RESOURCE_BINDING_ARRAY) {
116            features |= wgpu::Features::STORAGE_RESOURCE_BINDING_ARRAY;
117        }
118        if self.contains(GpuFeatures::PARTIALLY_BOUND_BINDING_ARRAY) {
119            features |= wgpu::Features::PARTIALLY_BOUND_BINDING_ARRAY;
120        }
121        if self.contains(GpuFeatures::FLOAT32_FILTERABLE) {
122            features |= wgpu::Features::FLOAT32_FILTERABLE;
123        }
124        if self.contains(GpuFeatures::RG11B10UFLOAT_RENDERABLE) {
125            features |= wgpu::Features::RG11B10UFLOAT_RENDERABLE;
126        }
127        if self.contains(GpuFeatures::BGRA8UNORM_STORAGE) {
128            features |= wgpu::Features::BGRA8UNORM_STORAGE;
129        }
130
131        features
132    }
133
134    /// Convert from wgpu::Features to GpuFeatures.
135    ///
136    /// Note: Only features that have a corresponding GpuFeatures flag will be included.
137    pub fn from_wgpu(features: wgpu::Features) -> Self {
138        let mut gpu_features = GpuFeatures::empty();
139
140        if features.contains(wgpu::Features::INDIRECT_FIRST_INSTANCE) {
141            gpu_features |= GpuFeatures::INDIRECT_FIRST_INSTANCE;
142        }
143        if features.contains(wgpu::Features::MULTI_DRAW_INDIRECT_COUNT) {
144            gpu_features |= GpuFeatures::MULTI_DRAW_INDIRECT;
145        }
146        if features.contains(wgpu::Features::PUSH_CONSTANTS) {
147            gpu_features |= GpuFeatures::PUSH_CONSTANTS;
148        }
149        if features.contains(wgpu::Features::TEXTURE_COMPRESSION_BC) {
150            gpu_features |= GpuFeatures::TEXTURE_COMPRESSION_BC;
151        }
152        if features.contains(wgpu::Features::DEPTH_CLIP_CONTROL) {
153            gpu_features |= GpuFeatures::DEPTH_CLIP_CONTROL;
154        }
155        if features.contains(wgpu::Features::SHADER_F16) {
156            gpu_features |= GpuFeatures::SHADER_F16;
157        }
158        if features.contains(wgpu::Features::POLYGON_MODE_LINE) {
159            gpu_features |= GpuFeatures::POLYGON_MODE_LINE;
160        }
161        if features.contains(wgpu::Features::POLYGON_MODE_POINT) {
162            gpu_features |= GpuFeatures::POLYGON_MODE_POINT;
163        }
164        if features.contains(wgpu::Features::CONSERVATIVE_RASTERIZATION) {
165            gpu_features |= GpuFeatures::CONSERVATIVE_RASTERIZATION;
166        }
167        if features.contains(wgpu::Features::TEXTURE_BINDING_ARRAY) {
168            gpu_features |= GpuFeatures::TEXTURE_BINDING_ARRAY;
169        }
170        if features.contains(wgpu::Features::BUFFER_BINDING_ARRAY) {
171            gpu_features |= GpuFeatures::BUFFER_BINDING_ARRAY;
172        }
173        if features.contains(wgpu::Features::STORAGE_RESOURCE_BINDING_ARRAY) {
174            gpu_features |= GpuFeatures::STORAGE_RESOURCE_BINDING_ARRAY;
175        }
176        if features.contains(wgpu::Features::PARTIALLY_BOUND_BINDING_ARRAY) {
177            gpu_features |= GpuFeatures::PARTIALLY_BOUND_BINDING_ARRAY;
178        }
179        if features.contains(wgpu::Features::FLOAT32_FILTERABLE) {
180            gpu_features |= GpuFeatures::FLOAT32_FILTERABLE;
181        }
182        if features.contains(wgpu::Features::RG11B10UFLOAT_RENDERABLE) {
183            gpu_features |= GpuFeatures::RG11B10UFLOAT_RENDERABLE;
184        }
185        if features.contains(wgpu::Features::BGRA8UNORM_STORAGE) {
186            gpu_features |= GpuFeatures::BGRA8UNORM_STORAGE;
187        }
188
189        gpu_features
190    }
191
192    /// Check if all the specified features are supported by the adapter.
193    pub fn check_support(self, adapter: &wgpu::Adapter) -> FeatureSupportResult {
194        let adapter_features = GpuFeatures::from_wgpu(adapter.features());
195        let missing = self - (self & adapter_features);
196
197        if missing.is_empty() {
198            FeatureSupportResult::Supported
199        } else {
200            FeatureSupportResult::Missing(missing)
201        }
202    }
203}
204
205impl Default for GpuFeatures {
206    fn default() -> Self {
207        GpuFeatures::empty()
208    }
209}
210
211/// Result of checking feature support.
212#[derive(Debug, Clone, Copy, PartialEq, Eq)]
213pub enum FeatureSupportResult {
214    /// All requested features are supported.
215    Supported,
216    /// Some features are missing.
217    Missing(GpuFeatures),
218}
219
220impl FeatureSupportResult {
221    /// Returns true if all features are supported.
222    pub fn is_supported(&self) -> bool {
223        matches!(self, FeatureSupportResult::Supported)
224    }
225
226    /// Returns the missing features, if any.
227    pub fn missing(&self) -> Option<GpuFeatures> {
228        match self {
229            FeatureSupportResult::Supported => None,
230            FeatureSupportResult::Missing(features) => Some(*features),
231        }
232    }
233}
234
235#[cfg(test)]
236mod tests {
237    use super::*;
238
239    #[test]
240    fn test_gpu_features_empty() {
241        let features = GpuFeatures::empty();
242        assert!(features.is_empty());
243        assert_eq!(features.to_wgpu(), wgpu::Features::empty());
244    }
245
246    #[test]
247    fn test_gpu_features_roundtrip() {
248        let features = GpuFeatures::INDIRECT_FIRST_INSTANCE
249            | GpuFeatures::MULTI_DRAW_INDIRECT
250            | GpuFeatures::PUSH_CONSTANTS;
251
252        let wgpu_features = features.to_wgpu();
253        let back = GpuFeatures::from_wgpu(wgpu_features);
254
255        assert_eq!(features, back);
256    }
257
258    #[test]
259    fn test_gpu_features_contains() {
260        let features = GpuFeatures::INDIRECT_FIRST_INSTANCE | GpuFeatures::PUSH_CONSTANTS;
261
262        assert!(features.contains(GpuFeatures::INDIRECT_FIRST_INSTANCE));
263        assert!(features.contains(GpuFeatures::PUSH_CONSTANTS));
264        assert!(!features.contains(GpuFeatures::MULTI_DRAW_INDIRECT));
265    }
266}