Skip to main content

alpha2_gpu/
capabilities.rs

1/// The capability class exposed to renderer code.
2#[derive(Copy, Clone, Debug, Eq, PartialEq)]
3#[non_exhaustive]
4pub enum CapabilityTier {
5    /// Full WebGPU-capable adapter.
6    TierA,
7    /// Conservative downlevel adapter.
8    TierB,
9}
10
11/// Internal compositing texture format.
12#[derive(Copy, Clone, Debug, Eq, PartialEq)]
13#[non_exhaustive]
14pub enum IntermediateFormat {
15    /// Half-float RGBA storage (the format does not specify a transfer curve).
16    Rgba16Float,
17    /// Normalized RGBA fallback.
18    Rgba8Unorm,
19}
20
21/// Adapter facts relevant to rendering decisions.
22#[derive(Clone, Debug)]
23#[non_exhaustive]
24pub struct Capabilities {
25    /// Compatibility classification.
26    pub tier: CapabilityTier,
27    /// Chosen intermediate format.
28    pub intermediate_format: IntermediateFormat,
29    /// Platform adapter name.
30    pub adapter_name: String,
31    /// Maximum 2D texture dimension.
32    pub max_texture_dimension_2d: u32,
33    /// Whether render-pass GPU timestamp queries were enabled on the device.
34    pub timestamp_queries: bool,
35    /// Backend selected by wgpu.
36    pub backend: String,
37    /// Adapter driver name.
38    pub driver: String,
39    /// Adapter driver detail/version when exposed by the platform.
40    pub driver_info: String,
41    /// Whether the adapter rasterizes on the CPU (lavapipe, llvmpipe,
42    /// `SwiftShader`, WARP), where a shader's cost is paid for every pixel it
43    /// is run on whatever branches it skips.
44    pub software_rasterizer: bool,
45}
46
47impl Capabilities {
48    /// Creates an explicit capability snapshot for policy simulation and tests.
49    #[must_use]
50    #[allow(clippy::too_many_arguments)]
51    pub fn new(
52        tier: CapabilityTier,
53        intermediate_format: IntermediateFormat,
54        adapter_name: impl Into<String>,
55        max_texture_dimension_2d: u32,
56        timestamp_queries: bool,
57        backend: impl Into<String>,
58        driver: impl Into<String>,
59        driver_info: impl Into<String>,
60    ) -> Self {
61        Self {
62            tier,
63            intermediate_format,
64            adapter_name: adapter_name.into(),
65            max_texture_dimension_2d,
66            timestamp_queries,
67            backend: backend.into(),
68            driver: driver.into(),
69            driver_info: driver_info.into(),
70            software_rasterizer: false,
71        }
72    }
73
74    pub(crate) fn from_adapter(adapter: &wgpu::Adapter) -> Self {
75        let info = adapter.get_info();
76        Self {
77            tier: tier_for(&adapter.get_downlevel_capabilities()),
78            intermediate_format: intermediate_format_for(
79                adapter.get_texture_format_features(wgpu::TextureFormat::Rgba16Float),
80            ),
81            adapter_name: info.name,
82            max_texture_dimension_2d: adapter.limits().max_texture_dimension_2d,
83            timestamp_queries: adapter.features().contains(wgpu::Features::TIMESTAMP_QUERY),
84            backend: format!("{:?}", info.backend),
85            driver: info.driver,
86            driver_info: info.driver_info,
87            software_rasterizer: info.device_type == wgpu::DeviceType::Cpu,
88        }
89    }
90}
91
92/// The intermediate format for an adapter whose `Rgba16Float` support is
93/// `half_float`: half float where pictures can be rendered to it, sampled
94/// with filtering, and copied out; 8-bit everywhere else.
95///
96/// The tier does not enter into it: a WebGL2 or GL adapter with a float or
97/// half-float colour buffer is tier B and still gets half float.
98fn intermediate_format_for(half_float: wgpu::TextureFormatFeatures) -> IntermediateFormat {
99    let required = wgpu::TextureUsages::RENDER_ATTACHMENT
100        | wgpu::TextureUsages::TEXTURE_BINDING
101        | wgpu::TextureUsages::COPY_SRC;
102    if half_float.allowed_usages.contains(required)
103        && half_float
104            .flags
105            .contains(wgpu::TextureFormatFeatureFlags::FILTERABLE)
106    {
107        IntermediateFormat::Rgba16Float
108    } else {
109        IntermediateFormat::Rgba8Unorm
110    }
111}
112
113/// Tier A for an adapter that meets all of WebGPU, tier B for a downlevel
114/// one.
115fn tier_for(downlevel: &wgpu::DownlevelCapabilities) -> CapabilityTier {
116    if downlevel.is_webgpu_compliant() {
117        CapabilityTier::TierA
118    } else {
119        CapabilityTier::TierB
120    }
121}
122
123#[cfg(test)]
124mod tests {
125    use super::*;
126    use wgpu::{TextureFormatFeatureFlags as Flags, TextureUsages as Usages};
127
128    fn half_float(allowed_usages: Usages, flags: Flags) -> wgpu::TextureFormatFeatures {
129        wgpu::TextureFormatFeatures {
130            allowed_usages,
131            flags,
132        }
133    }
134
135    #[test]
136    fn half_float_is_the_intermediate_only_where_it_renders_filters_and_copies() {
137        let required = Usages::RENDER_ATTACHMENT | Usages::TEXTURE_BINDING | Usages::COPY_SRC;
138        assert_eq!(
139            intermediate_format_for(half_float(required | Usages::COPY_DST, Flags::FILTERABLE)),
140            IntermediateFormat::Rgba16Float
141        );
142        for missing in [
143            Usages::RENDER_ATTACHMENT,
144            Usages::TEXTURE_BINDING,
145            Usages::COPY_SRC,
146        ] {
147            assert_eq!(
148                intermediate_format_for(half_float(required - missing, Flags::FILTERABLE)),
149                IntermediateFormat::Rgba8Unorm,
150                "without {missing:?}"
151            );
152        }
153        assert_eq!(
154            intermediate_format_for(half_float(required, Flags::empty())),
155            IntermediateFormat::Rgba8Unorm,
156            "without filtering"
157        );
158        // No half float at all, as a WebGL2 context without a float colour buffer.
159        assert_eq!(
160            intermediate_format_for(half_float(Usages::empty(), Flags::empty())),
161            IntermediateFormat::Rgba8Unorm
162        );
163    }
164
165    #[test]
166    fn the_tier_follows_webgpu_compliance_alone() {
167        assert_eq!(
168            tier_for(&wgpu::DownlevelCapabilities::default()),
169            CapabilityTier::TierA
170        );
171        let mut downlevel = wgpu::DownlevelCapabilities::default();
172        downlevel
173            .flags
174            .remove(wgpu::DownlevelFlags::COMPUTE_SHADERS);
175        assert_eq!(tier_for(&downlevel), CapabilityTier::TierB);
176        let shader_model = wgpu::DownlevelCapabilities {
177            shader_model: wgpu::ShaderModel::Sm4,
178            ..Default::default()
179        };
180        assert_eq!(tier_for(&shader_model), CapabilityTier::TierB);
181    }
182}