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        /// Timestamp queries for GPU profiling.
76        /// Allows measuring GPU execution time with high precision.
77        const TIMESTAMP_QUERY = 1 << 17;
78    }
79}
80
81impl GpuFeatures {
82    /// Convert to wgpu::Features.
83    pub fn to_wgpu(self) -> wgpu::Features {
84        let mut features = wgpu::Features::empty();
85
86        if self.contains(GpuFeatures::INDIRECT_FIRST_INSTANCE) {
87            features |= wgpu::Features::INDIRECT_FIRST_INSTANCE;
88        }
89        if self.contains(GpuFeatures::MULTI_DRAW_INDIRECT) {
90            features |= wgpu::Features::MULTI_DRAW_INDIRECT_COUNT;
91        }
92        if self.contains(GpuFeatures::PUSH_CONSTANTS) {
93            features |= wgpu::Features::PUSH_CONSTANTS;
94        }
95        if self.contains(GpuFeatures::TEXTURE_COMPRESSION_BC) {
96            features |= wgpu::Features::TEXTURE_COMPRESSION_BC;
97        }
98        if self.contains(GpuFeatures::DEPTH_CLIP_CONTROL) {
99            features |= wgpu::Features::DEPTH_CLIP_CONTROL;
100        }
101        if self.contains(GpuFeatures::SHADER_F16) {
102            features |= wgpu::Features::SHADER_F16;
103        }
104        if self.contains(GpuFeatures::POLYGON_MODE_LINE) {
105            features |= wgpu::Features::POLYGON_MODE_LINE;
106        }
107        if self.contains(GpuFeatures::POLYGON_MODE_POINT) {
108            features |= wgpu::Features::POLYGON_MODE_POINT;
109        }
110        if self.contains(GpuFeatures::CONSERVATIVE_RASTERIZATION) {
111            features |= wgpu::Features::CONSERVATIVE_RASTERIZATION;
112        }
113        if self.contains(GpuFeatures::TEXTURE_BINDING_ARRAY) {
114            features |= wgpu::Features::TEXTURE_BINDING_ARRAY;
115        }
116        if self.contains(GpuFeatures::BUFFER_BINDING_ARRAY) {
117            features |= wgpu::Features::BUFFER_BINDING_ARRAY;
118        }
119        if self.contains(GpuFeatures::STORAGE_RESOURCE_BINDING_ARRAY) {
120            features |= wgpu::Features::STORAGE_RESOURCE_BINDING_ARRAY;
121        }
122        if self.contains(GpuFeatures::PARTIALLY_BOUND_BINDING_ARRAY) {
123            features |= wgpu::Features::PARTIALLY_BOUND_BINDING_ARRAY;
124        }
125        if self.contains(GpuFeatures::FLOAT32_FILTERABLE) {
126            features |= wgpu::Features::FLOAT32_FILTERABLE;
127        }
128        if self.contains(GpuFeatures::RG11B10UFLOAT_RENDERABLE) {
129            features |= wgpu::Features::RG11B10UFLOAT_RENDERABLE;
130        }
131        if self.contains(GpuFeatures::BGRA8UNORM_STORAGE) {
132            features |= wgpu::Features::BGRA8UNORM_STORAGE;
133        }
134        if self.contains(GpuFeatures::TIMESTAMP_QUERY) {
135            features |= wgpu::Features::TIMESTAMP_QUERY;
136        }
137
138        features
139    }
140
141    /// Convert from wgpu::Features to GpuFeatures.
142    ///
143    /// Note: Only features that have a corresponding GpuFeatures flag will be included.
144    pub fn from_wgpu(features: wgpu::Features) -> Self {
145        let mut gpu_features = GpuFeatures::empty();
146
147        if features.contains(wgpu::Features::INDIRECT_FIRST_INSTANCE) {
148            gpu_features |= GpuFeatures::INDIRECT_FIRST_INSTANCE;
149        }
150        if features.contains(wgpu::Features::MULTI_DRAW_INDIRECT_COUNT) {
151            gpu_features |= GpuFeatures::MULTI_DRAW_INDIRECT;
152        }
153        if features.contains(wgpu::Features::PUSH_CONSTANTS) {
154            gpu_features |= GpuFeatures::PUSH_CONSTANTS;
155        }
156        if features.contains(wgpu::Features::TEXTURE_COMPRESSION_BC) {
157            gpu_features |= GpuFeatures::TEXTURE_COMPRESSION_BC;
158        }
159        if features.contains(wgpu::Features::DEPTH_CLIP_CONTROL) {
160            gpu_features |= GpuFeatures::DEPTH_CLIP_CONTROL;
161        }
162        if features.contains(wgpu::Features::SHADER_F16) {
163            gpu_features |= GpuFeatures::SHADER_F16;
164        }
165        if features.contains(wgpu::Features::POLYGON_MODE_LINE) {
166            gpu_features |= GpuFeatures::POLYGON_MODE_LINE;
167        }
168        if features.contains(wgpu::Features::POLYGON_MODE_POINT) {
169            gpu_features |= GpuFeatures::POLYGON_MODE_POINT;
170        }
171        if features.contains(wgpu::Features::CONSERVATIVE_RASTERIZATION) {
172            gpu_features |= GpuFeatures::CONSERVATIVE_RASTERIZATION;
173        }
174        if features.contains(wgpu::Features::TEXTURE_BINDING_ARRAY) {
175            gpu_features |= GpuFeatures::TEXTURE_BINDING_ARRAY;
176        }
177        if features.contains(wgpu::Features::BUFFER_BINDING_ARRAY) {
178            gpu_features |= GpuFeatures::BUFFER_BINDING_ARRAY;
179        }
180        if features.contains(wgpu::Features::STORAGE_RESOURCE_BINDING_ARRAY) {
181            gpu_features |= GpuFeatures::STORAGE_RESOURCE_BINDING_ARRAY;
182        }
183        if features.contains(wgpu::Features::PARTIALLY_BOUND_BINDING_ARRAY) {
184            gpu_features |= GpuFeatures::PARTIALLY_BOUND_BINDING_ARRAY;
185        }
186        if features.contains(wgpu::Features::FLOAT32_FILTERABLE) {
187            gpu_features |= GpuFeatures::FLOAT32_FILTERABLE;
188        }
189        if features.contains(wgpu::Features::RG11B10UFLOAT_RENDERABLE) {
190            gpu_features |= GpuFeatures::RG11B10UFLOAT_RENDERABLE;
191        }
192        if features.contains(wgpu::Features::BGRA8UNORM_STORAGE) {
193            gpu_features |= GpuFeatures::BGRA8UNORM_STORAGE;
194        }
195        if features.contains(wgpu::Features::TIMESTAMP_QUERY) {
196            gpu_features |= GpuFeatures::TIMESTAMP_QUERY;
197        }
198
199        gpu_features
200    }
201
202    /// Check if all the specified features are supported by the adapter.
203    pub fn check_support(self, adapter: &wgpu::Adapter) -> FeatureSupportResult {
204        let adapter_features = GpuFeatures::from_wgpu(adapter.features());
205        let missing = self - (self & adapter_features);
206
207        if missing.is_empty() {
208            FeatureSupportResult::Supported
209        } else {
210            FeatureSupportResult::Missing(missing)
211        }
212    }
213}
214
215impl Default for GpuFeatures {
216    fn default() -> Self {
217        GpuFeatures::empty()
218    }
219}
220
221/// Result of checking feature support.
222#[derive(Debug, Clone, Copy, PartialEq, Eq)]
223pub enum FeatureSupportResult {
224    /// All requested features are supported.
225    Supported,
226    /// Some features are missing.
227    Missing(GpuFeatures),
228}
229
230impl FeatureSupportResult {
231    /// Returns true if all features are supported.
232    pub fn is_supported(&self) -> bool {
233        matches!(self, FeatureSupportResult::Supported)
234    }
235
236    /// Returns the missing features, if any.
237    pub fn missing(&self) -> Option<GpuFeatures> {
238        match self {
239            FeatureSupportResult::Supported => None,
240            FeatureSupportResult::Missing(features) => Some(*features),
241        }
242    }
243}
244
245#[cfg(test)]
246mod tests {
247    use super::*;
248
249    #[test]
250    fn test_gpu_features_empty() {
251        let features = GpuFeatures::empty();
252        assert!(features.is_empty());
253        assert_eq!(features.to_wgpu(), wgpu::Features::empty());
254    }
255
256    #[test]
257    fn test_gpu_features_roundtrip() {
258        let features = GpuFeatures::INDIRECT_FIRST_INSTANCE
259            | GpuFeatures::MULTI_DRAW_INDIRECT
260            | GpuFeatures::PUSH_CONSTANTS
261            | GpuFeatures::TIMESTAMP_QUERY;
262
263        let wgpu_features = features.to_wgpu();
264        let back = GpuFeatures::from_wgpu(wgpu_features);
265
266        assert_eq!(features, back);
267    }
268
269    #[test]
270    fn test_gpu_features_contains() {
271        let features = GpuFeatures::INDIRECT_FIRST_INSTANCE | GpuFeatures::PUSH_CONSTANTS;
272
273        assert!(features.contains(GpuFeatures::INDIRECT_FIRST_INSTANCE));
274        assert!(features.contains(GpuFeatures::PUSH_CONSTANTS));
275        assert!(!features.contains(GpuFeatures::MULTI_DRAW_INDIRECT));
276    }
277}