use crate::format::FormatModifierSet;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum Capability {
AdvancedBlend,
FloatRenderTargets,
}
impl Capability {
const fn bit(self) -> u32 {
match self {
Self::AdvancedBlend => 1 << 0,
Self::FloatRenderTargets => 1 << 1,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub struct Withheld(u32);
impl Withheld {
pub const fn none() -> Self {
Self(0)
}
pub const fn of(capability: Capability) -> Self {
Self(capability.bit())
}
pub const fn with(self, capability: Capability) -> Self {
Self(self.0 | capability.bit())
}
pub const fn contains(self, capability: Capability) -> bool {
(self.0 & capability.bit()) != 0
}
pub const fn is_empty(self) -> bool {
self.0 == 0
}
pub fn apply_to(self, capabilities: &mut Capabilities) {
for capability in [Capability::AdvancedBlend, Capability::FloatRenderTargets] {
if !self.contains(capability) {
continue;
}
match capability {
Capability::AdvancedBlend => capabilities.advanced_blend = false,
Capability::FloatRenderTargets => capabilities.float_render_targets = false,
}
}
}
}
impl From<Capability> for Withheld {
fn from(capability: Capability) -> Self {
Self::of(capability)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub struct SampleCounts(u32);
impl SampleCounts {
pub const fn from_mask(mask: u32) -> Self {
Self(mask)
}
pub const fn supports(self, count: u32) -> bool {
count.is_power_of_two() && (self.0 & count) != 0
}
pub const fn max(self) -> u32 {
if self.0 == 0 {
1
} else {
1 << (u32::BITS - 1 - self.0.leading_zeros())
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub struct DmaBufSupport {
pub import: bool,
pub export: bool,
pub modifiers: bool,
}
impl DmaBufSupport {
pub const fn can_allocate_scanout(self) -> bool {
self.export && self.modifiers
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub struct SyncSupport {
pub export_sync_file: bool,
pub import_sync_file: bool,
}
impl SyncSupport {
pub const fn supports_explicit_scanout(self) -> bool {
self.export_sync_file
}
}
#[derive(Debug, Clone, Default)]
pub struct Capabilities {
pub max_texture_size: u32,
pub sample_counts: SampleCounts,
pub dma_buf: DmaBufSupport,
pub sync: SyncSupport,
pub advanced_blend: bool,
pub float_render_targets: bool,
pub render_formats: Vec<FormatModifierSet>,
pub device_name: String,
pub driver_name: String,
pub software: bool,
}
impl Capabilities {
pub fn supports_scanout(&self) -> bool {
self.dma_buf.can_allocate_scanout() || self.dma_buf.import
}
pub fn check_blend_modes(&self, batch: &crate::Batch) -> crate::Result<()> {
if !self.advanced_blend && batch.draws().iter().any(|draw| draw.blend.is_advanced()) {
return Err(crate::Error::Unsupported(
"advanced blend modes; this device has no advanced-blend extension",
));
}
Ok(())
}
pub fn check_texture(&self, desc: &crate::TextureDescriptor) -> crate::Result<()> {
if desc.usage.render_target && !desc.format.is_drawable() {
return Err(crate::Error::Unsupported(
"an sRGB render target; this pipeline already holds encoded \
color and the format would encode it a second time",
));
}
if desc.usage.render_target
&& desc.format == crate::PixelFormat::Rgba16Float
&& !self.float_render_targets
{
return Err(crate::Error::Unsupported(
"a floating-point render target; this device can sample one but not draw into it",
));
}
Ok(())
}
pub fn can_allocate(&self, extent: crate::format::Extent2D) -> bool {
extent.width <= self.max_texture_size && extent.height <= self.max_texture_size
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::format::Extent2D;
#[test]
fn withholding_clears_its_own_field_and_no_other() {
let full = || Capabilities {
max_texture_size: 8192,
sample_counts: SampleCounts::from_mask(0b101),
dma_buf: DmaBufSupport {
import: true,
export: true,
modifiers: true,
},
sync: SyncSupport {
export_sync_file: true,
import_sync_file: true,
},
advanced_blend: true,
float_render_targets: true,
render_formats: Vec::new(),
device_name: String::from("a device"),
driver_name: String::from("a driver"),
software: true,
};
let mut caps = full();
Withheld::of(Capability::AdvancedBlend).apply_to(&mut caps);
assert!(!caps.advanced_blend, "the named field is not cleared");
assert!(caps.float_render_targets, "an unnamed field was cleared");
assert_eq!(caps.max_texture_size, 8192);
assert_eq!(caps.sample_counts, SampleCounts::from_mask(0b101));
assert!(caps.dma_buf.import && caps.dma_buf.export && caps.dma_buf.modifiers);
assert!(caps.sync.export_sync_file && caps.sync.import_sync_file);
assert!(caps.software);
let mut caps = full();
Withheld::of(Capability::FloatRenderTargets).apply_to(&mut caps);
assert!(!caps.float_render_targets);
assert!(caps.advanced_blend, "an unnamed field was cleared");
let mut caps = full();
Withheld::of(Capability::AdvancedBlend)
.with(Capability::FloatRenderTargets)
.apply_to(&mut caps);
assert!(!caps.advanced_blend && !caps.float_render_targets);
assert_eq!(caps.max_texture_size, 8192, "a set of two reached further");
}
#[test]
fn withholding_can_never_grant() {
let mut caps = Capabilities::default();
assert!(!caps.advanced_blend && !caps.float_render_targets);
Withheld::of(Capability::AdvancedBlend)
.with(Capability::FloatRenderTargets)
.apply_to(&mut caps);
assert!(
!caps.advanced_blend && !caps.float_render_targets,
"withholding a capability a device has not got turned it on"
);
}
#[test]
fn withholding_nothing_changes_nothing_and_applying_twice_is_the_same() {
let mut caps = Capabilities {
advanced_blend: true,
float_render_targets: true,
..Default::default()
};
Withheld::none().apply_to(&mut caps);
assert!(caps.advanced_blend && caps.float_render_targets);
assert!(Withheld::none().is_empty());
assert!(!Withheld::of(Capability::AdvancedBlend).is_empty());
let withheld = Withheld::of(Capability::AdvancedBlend);
withheld.apply_to(&mut caps);
let once = caps.advanced_blend;
withheld.apply_to(&mut caps);
assert_eq!(once, caps.advanced_blend, "a second application differed");
assert!(caps.float_render_targets, "the other field followed along");
}
#[test]
fn a_set_reports_what_it_withholds() {
let one = Withheld::of(Capability::AdvancedBlend);
assert!(one.contains(Capability::AdvancedBlend));
assert!(!one.contains(Capability::FloatRenderTargets));
let both = one.with(Capability::FloatRenderTargets);
assert!(both.contains(Capability::AdvancedBlend));
assert!(both.contains(Capability::FloatRenderTargets));
assert_eq!(Withheld::from(Capability::AdvancedBlend), one);
}
#[test]
fn an_advanced_mode_is_refused_only_where_the_extension_is_missing() {
const TRI: [[f32; 2]; 3] = [[0.0, 0.0], [1.0, 0.0], [0.0, 1.0]];
let batch = |blend| {
let mut batch = crate::Batch::new();
batch
.push(&TRI, &[0, 1, 2], crate::Material::solid([1.0; 4]), blend)
.expect("one triangle is within any batch's limits");
batch
};
let advanced = batch(crate::BlendMode::Multiply);
let ordinary = batch(crate::BlendMode::SrcOver);
assert!(
crate::BlendMode::Multiply.is_advanced() && !crate::BlendMode::SrcOver.is_advanced(),
"the two modes chosen here have to sit on opposite sides of the \
thing being checked, or this test checks nothing"
);
let without = Capabilities {
advanced_blend: false,
..Default::default()
};
let with = Capabilities {
advanced_blend: true,
..Default::default()
};
assert!(
without.check_blend_modes(&advanced).is_err(),
"an advanced mode was allowed on a device with no advanced-blend \
extension, which reaches the driver as a mode it cannot express"
);
assert!(
with.check_blend_modes(&advanced).is_ok(),
"an advanced mode was refused on a device that has the extension"
);
assert!(
without.check_blend_modes(&ordinary).is_ok(),
"an ordinary mode was refused for want of an extension it does not \
need -- the check is looking at the batch rather than at the mode"
);
}
#[test]
fn sample_counts_reject_non_powers_of_two() {
let counts = SampleCounts::from_mask(1 | 2 | 4);
assert!(counts.supports(1));
assert!(counts.supports(4));
assert!(!counts.supports(8));
assert!(!counts.supports(3));
}
#[test]
fn sample_counts_report_max_and_degrade_to_single_sampled() {
assert_eq!(SampleCounts::from_mask(1 | 2 | 4).max(), 4);
assert_eq!(SampleCounts::default().max(), 1);
}
#[test]
fn self_allocation_requires_both_export_and_modifiers() {
let full = DmaBufSupport {
import: true,
export: true,
modifiers: true,
};
assert!(full.can_allocate_scanout());
let no_modifiers = DmaBufSupport {
modifiers: false,
..full
};
assert!(!no_modifiers.can_allocate_scanout());
}
#[test]
fn import_only_devices_still_support_scanout() {
let caps = Capabilities {
dma_buf: DmaBufSupport {
import: true,
export: false,
modifiers: false,
},
..Default::default()
};
assert!(!caps.dma_buf.can_allocate_scanout());
assert!(caps.supports_scanout());
}
#[test]
fn scanout_capability_is_independent_of_fence_export() {
let caps = Capabilities {
dma_buf: DmaBufSupport {
import: true,
export: true,
modifiers: true,
},
sync: SyncSupport::default(),
..Default::default()
};
assert!(caps.supports_scanout());
assert!(!caps.sync.supports_explicit_scanout());
}
#[test]
fn allocation_limit_is_checked_on_both_axes() {
let caps = Capabilities {
max_texture_size: 4096,
..Default::default()
};
assert!(caps.can_allocate(Extent2D::new(4096, 4096)));
assert!(!caps.can_allocate(Extent2D::new(4097, 16)));
assert!(!caps.can_allocate(Extent2D::new(16, 4097)));
}
#[test]
fn a_float_target_is_refused_where_only_sampling_one_is_offered() {
let mut caps = Capabilities {
float_render_targets: false,
..Capabilities::default()
};
let extent = Extent2D::new(4, 4);
let target = crate::TextureDescriptor::offscreen(extent, crate::PixelFormat::Rgba16Float);
assert!(
caps.check_texture(&target).is_err(),
"a float attachment was allowed where the device offers none"
);
let sampled = crate::TextureDescriptor::sampled(extent, crate::PixelFormat::Rgba16Float);
assert!(caps.check_texture(&sampled).is_ok());
let ordinary = crate::TextureDescriptor::offscreen(extent, crate::PixelFormat::Rgba8Unorm);
assert!(caps.check_texture(&ordinary).is_ok());
caps.float_render_targets = true;
assert!(caps.check_texture(&target).is_ok());
}
}