1#[derive(Copy, Clone, Debug, Eq, PartialEq)]
3#[non_exhaustive]
4pub enum CapabilityTier {
5 TierA,
7 TierB,
9}
10
11#[derive(Copy, Clone, Debug, Eq, PartialEq)]
13#[non_exhaustive]
14pub enum IntermediateFormat {
15 Rgba16Float,
17 Rgba8Unorm,
19}
20
21#[derive(Clone, Debug)]
23#[non_exhaustive]
24pub struct Capabilities {
25 pub tier: CapabilityTier,
27 pub intermediate_format: IntermediateFormat,
29 pub adapter_name: String,
31 pub max_texture_dimension_2d: u32,
33 pub timestamp_queries: bool,
35 pub backend: String,
37 pub driver: String,
39 pub driver_info: String,
41 pub software_rasterizer: bool,
44 pub half_float_blendable: bool,
46 pub shader_compiler: String,
48}
49
50impl Capabilities {
51 #[must_use]
53 #[allow(clippy::too_many_arguments)]
54 pub fn new(
55 tier: CapabilityTier,
56 intermediate_format: IntermediateFormat,
57 adapter_name: impl Into<String>,
58 max_texture_dimension_2d: u32,
59 timestamp_queries: bool,
60 backend: impl Into<String>,
61 driver: impl Into<String>,
62 driver_info: impl Into<String>,
63 shader_compiler: impl Into<String>,
64 ) -> Self {
65 Self {
66 tier,
67 intermediate_format,
68 adapter_name: adapter_name.into(),
69 max_texture_dimension_2d,
70 timestamp_queries,
71 backend: backend.into(),
72 driver: driver.into(),
73 driver_info: driver_info.into(),
74 software_rasterizer: false,
75 half_float_blendable: intermediate_format == IntermediateFormat::Rgba16Float,
76 shader_compiler: shader_compiler.into(),
77 }
78 }
79
80 #[must_use]
83 pub fn summary(&self) -> String {
84 let driver = [self.driver.as_str(), self.driver_info.as_str()]
85 .into_iter()
86 .filter(|part| !part.is_empty())
87 .collect::<Vec<_>>()
88 .join(" ");
89 let mut parts = vec![self.backend.clone()];
90 if !driver.is_empty() {
91 parts.push(format!("driver {driver}"));
92 }
93 if !self.shader_compiler.is_empty() {
94 parts.push(self.shader_compiler.clone());
95 }
96 format!("{} ({})", self.adapter_name, parts.join(", "))
97 }
98
99 pub(crate) fn from_device(adapter: &wgpu::Adapter, device: &wgpu::Device) -> Self {
101 let info = adapter.get_info();
102 let shader_compiler = if info.backend == wgpu::Backend::Dx12 {
103 crate::dx12_shader_compiler().to_string()
104 } else {
105 String::new()
106 };
107 let half_float = adapter.get_texture_format_features(wgpu::TextureFormat::Rgba16Float);
108 let software_rasterizer = software_adapter(&info);
109 Self {
110 tier: tier_for(&adapter.get_downlevel_capabilities()),
111 intermediate_format: intermediate_format_for(half_float),
112 half_float_blendable: half_float
113 .allowed_usages
114 .contains(wgpu::TextureUsages::RENDER_ATTACHMENT)
115 && half_float
116 .flags
117 .contains(wgpu::TextureFormatFeatureFlags::BLENDABLE),
118 adapter_name: info.name,
119 max_texture_dimension_2d: device.limits().max_texture_dimension_2d,
120 timestamp_queries: device.features().contains(wgpu::Features::TIMESTAMP_QUERY),
121 backend: format!("{:?}", info.backend),
122 driver: info.driver,
123 driver_info: info.driver_info,
124 software_rasterizer,
125 shader_compiler,
126 }
127 }
128}
129
130const SOFTWARE_RENDERER_NAMES: [&str; 4] = [
133 "mesa offscreen",
134 "swiftshader",
135 "llvmpipe",
136 "microsoft basic render",
137];
138
139#[must_use]
142pub fn software_adapter(info: &wgpu::AdapterInfo) -> bool {
143 info.device_type == wgpu::DeviceType::Cpu || software_renderer_name(&info.name)
144}
145
146#[must_use]
149pub fn software_renderer_name(name: &str) -> bool {
150 let name = name.to_ascii_lowercase();
151 SOFTWARE_RENDERER_NAMES
152 .iter()
153 .any(|software| name.contains(software))
154}
155
156fn intermediate_format_for(half_float: wgpu::TextureFormatFeatures) -> IntermediateFormat {
159 let required = wgpu::TextureUsages::RENDER_ATTACHMENT
160 | wgpu::TextureUsages::TEXTURE_BINDING
161 | wgpu::TextureUsages::COPY_SRC;
162 if half_float.allowed_usages.contains(required)
163 && half_float
164 .flags
165 .contains(wgpu::TextureFormatFeatureFlags::FILTERABLE)
166 {
167 IntermediateFormat::Rgba16Float
168 } else {
169 IntermediateFormat::Rgba8Unorm
170 }
171}
172
173fn tier_for(downlevel: &wgpu::DownlevelCapabilities) -> CapabilityTier {
175 if downlevel.is_webgpu_compliant() {
176 CapabilityTier::TierA
177 } else {
178 CapabilityTier::TierB
179 }
180}
181
182#[cfg(test)]
183mod tests {
184 use super::*;
185 use wgpu::{TextureFormatFeatureFlags as Flags, TextureUsages as Usages};
186
187 fn half_float(allowed_usages: Usages, flags: Flags) -> wgpu::TextureFormatFeatures {
188 wgpu::TextureFormatFeatures {
189 allowed_usages,
190 flags,
191 }
192 }
193
194 #[test]
195 fn half_float_is_the_intermediate_only_where_it_renders_filters_and_copies() {
196 let required = Usages::RENDER_ATTACHMENT | Usages::TEXTURE_BINDING | Usages::COPY_SRC;
197 assert_eq!(
198 intermediate_format_for(half_float(required | Usages::COPY_DST, Flags::FILTERABLE)),
199 IntermediateFormat::Rgba16Float
200 );
201 for missing in [
202 Usages::RENDER_ATTACHMENT,
203 Usages::TEXTURE_BINDING,
204 Usages::COPY_SRC,
205 ] {
206 assert_eq!(
207 intermediate_format_for(half_float(required - missing, Flags::FILTERABLE)),
208 IntermediateFormat::Rgba8Unorm,
209 "without {missing:?}"
210 );
211 }
212 assert_eq!(
213 intermediate_format_for(half_float(required, Flags::empty())),
214 IntermediateFormat::Rgba8Unorm,
215 "without filtering"
216 );
217 assert_eq!(
219 intermediate_format_for(half_float(Usages::empty(), Flags::empty())),
220 IntermediateFormat::Rgba8Unorm
221 );
222 }
223
224 #[test]
225 fn software_adapters_are_named_by_type_or_renderer() {
226 let info = |name: &str, device_type| wgpu::AdapterInfo {
227 name: name.to_owned(),
228 vendor: 0,
229 device: 0,
230 device_type,
231 device_pci_bus_id: String::new(),
232 driver: String::new(),
233 driver_info: String::new(),
234 backend: wgpu::Backend::Gl,
235 subgroup_min_size: 0,
236 subgroup_max_size: 0,
237 transient_saves_memory: None,
238 limit_bucket: None,
239 };
240 assert!(software_adapter(&info("", wgpu::DeviceType::Cpu)));
241 assert!(software_adapter(&info(
242 "ANGLE (Microsoft, Microsoft Basic Render Driver (0x0000008C) Direct3D11 vs_5_0 \
243 ps_5_0, D3D11)",
244 wgpu::DeviceType::Other
245 )));
246 assert!(software_adapter(&info(
247 "ANGLE (Google, Vulkan 1.3.0 (SwiftShader Device (Subzero) (0x0000C0DE)), \
248 SwiftShader driver)",
249 wgpu::DeviceType::Other
250 )));
251 assert!(!software_adapter(&info(
252 "ANGLE (AMD, AMD Radeon Graphics (0x0000164E) Direct3D11 vs_5_0 ps_5_0, D3D11)",
253 wgpu::DeviceType::Other
254 )));
255 assert!(!software_adapter(&info(
256 "Intel(R) UHD Graphics",
257 wgpu::DeviceType::IntegratedGpu
258 )));
259 }
260
261 #[test]
262 fn the_tier_follows_webgpu_compliance_alone() {
263 assert_eq!(
264 tier_for(&wgpu::DownlevelCapabilities::default()),
265 CapabilityTier::TierA
266 );
267 let mut downlevel = wgpu::DownlevelCapabilities::default();
268 downlevel
269 .flags
270 .remove(wgpu::DownlevelFlags::COMPUTE_SHADERS);
271 assert_eq!(tier_for(&downlevel), CapabilityTier::TierB);
272 let shader_model = wgpu::DownlevelCapabilities {
273 shader_model: wgpu::ShaderModel::Sm4,
274 ..Default::default()
275 };
276 assert_eq!(tier_for(&shader_model), CapabilityTier::TierB);
277 }
278}