#[derive(Copy, Clone, Debug, Eq, PartialEq)]
#[non_exhaustive]
pub enum CapabilityTier {
TierA,
TierB,
}
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
#[non_exhaustive]
pub enum IntermediateFormat {
Rgba16Float,
Rgba8Unorm,
}
#[derive(Clone, Debug)]
#[non_exhaustive]
pub struct Capabilities {
pub tier: CapabilityTier,
pub intermediate_format: IntermediateFormat,
pub adapter_name: String,
pub max_texture_dimension_2d: u32,
pub timestamp_queries: bool,
pub backend: String,
pub driver: String,
pub driver_info: String,
pub software_rasterizer: bool,
pub half_float_blendable: bool,
}
impl Capabilities {
#[must_use]
#[allow(clippy::too_many_arguments)]
pub fn new(
tier: CapabilityTier,
intermediate_format: IntermediateFormat,
adapter_name: impl Into<String>,
max_texture_dimension_2d: u32,
timestamp_queries: bool,
backend: impl Into<String>,
driver: impl Into<String>,
driver_info: impl Into<String>,
) -> Self {
Self {
tier,
intermediate_format,
adapter_name: adapter_name.into(),
max_texture_dimension_2d,
timestamp_queries,
backend: backend.into(),
driver: driver.into(),
driver_info: driver_info.into(),
software_rasterizer: false,
half_float_blendable: intermediate_format == IntermediateFormat::Rgba16Float,
}
}
pub(crate) fn from_adapter(adapter: &wgpu::Adapter) -> Self {
let info = adapter.get_info();
let half_float = adapter.get_texture_format_features(wgpu::TextureFormat::Rgba16Float);
Self {
tier: tier_for(&adapter.get_downlevel_capabilities()),
intermediate_format: intermediate_format_for(half_float),
half_float_blendable: half_float
.allowed_usages
.contains(wgpu::TextureUsages::RENDER_ATTACHMENT)
&& half_float
.flags
.contains(wgpu::TextureFormatFeatureFlags::BLENDABLE),
adapter_name: info.name,
max_texture_dimension_2d: adapter.limits().max_texture_dimension_2d,
timestamp_queries: adapter.features().contains(wgpu::Features::TIMESTAMP_QUERY),
backend: format!("{:?}", info.backend),
driver: info.driver,
driver_info: info.driver_info,
software_rasterizer: info.device_type == wgpu::DeviceType::Cpu,
}
}
}
fn intermediate_format_for(half_float: wgpu::TextureFormatFeatures) -> IntermediateFormat {
let required = wgpu::TextureUsages::RENDER_ATTACHMENT
| wgpu::TextureUsages::TEXTURE_BINDING
| wgpu::TextureUsages::COPY_SRC;
if half_float.allowed_usages.contains(required)
&& half_float
.flags
.contains(wgpu::TextureFormatFeatureFlags::FILTERABLE)
{
IntermediateFormat::Rgba16Float
} else {
IntermediateFormat::Rgba8Unorm
}
}
fn tier_for(downlevel: &wgpu::DownlevelCapabilities) -> CapabilityTier {
if downlevel.is_webgpu_compliant() {
CapabilityTier::TierA
} else {
CapabilityTier::TierB
}
}
#[cfg(test)]
mod tests {
use super::*;
use wgpu::{TextureFormatFeatureFlags as Flags, TextureUsages as Usages};
fn half_float(allowed_usages: Usages, flags: Flags) -> wgpu::TextureFormatFeatures {
wgpu::TextureFormatFeatures {
allowed_usages,
flags,
}
}
#[test]
fn half_float_is_the_intermediate_only_where_it_renders_filters_and_copies() {
let required = Usages::RENDER_ATTACHMENT | Usages::TEXTURE_BINDING | Usages::COPY_SRC;
assert_eq!(
intermediate_format_for(half_float(required | Usages::COPY_DST, Flags::FILTERABLE)),
IntermediateFormat::Rgba16Float
);
for missing in [
Usages::RENDER_ATTACHMENT,
Usages::TEXTURE_BINDING,
Usages::COPY_SRC,
] {
assert_eq!(
intermediate_format_for(half_float(required - missing, Flags::FILTERABLE)),
IntermediateFormat::Rgba8Unorm,
"without {missing:?}"
);
}
assert_eq!(
intermediate_format_for(half_float(required, Flags::empty())),
IntermediateFormat::Rgba8Unorm,
"without filtering"
);
assert_eq!(
intermediate_format_for(half_float(Usages::empty(), Flags::empty())),
IntermediateFormat::Rgba8Unorm
);
}
#[test]
fn the_tier_follows_webgpu_compliance_alone() {
assert_eq!(
tier_for(&wgpu::DownlevelCapabilities::default()),
CapabilityTier::TierA
);
let mut downlevel = wgpu::DownlevelCapabilities::default();
downlevel
.flags
.remove(wgpu::DownlevelFlags::COMPUTE_SHADERS);
assert_eq!(tier_for(&downlevel), CapabilityTier::TierB);
let shader_model = wgpu::DownlevelCapabilities {
shader_model: wgpu::ShaderModel::Sm4,
..Default::default()
};
assert_eq!(tier_for(&shader_model), CapabilityTier::TierB);
}
}