#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct Extent2D {
pub width: u32,
pub height: u32,
}
impl Extent2D {
pub const fn new(width: u32, height: u32) -> Self {
Self { width, height }
}
pub const fn area(self) -> u64 {
self.width as u64 * self.height as u64
}
pub const fn is_empty(self) -> bool {
self.width == 0 || self.height == 0
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum PixelFormat {
Rgba8Unorm,
Rgba8UnormSrgb,
Bgra8Unorm,
Bgra8UnormSrgb,
Rgb10A2Unorm,
Rgba16Float,
R8Unorm,
}
impl PixelFormat {
pub const fn bytes_per_pixel(self) -> u32 {
match self {
Self::Rgba8Unorm
| Self::Rgba8UnormSrgb
| Self::Bgra8Unorm
| Self::Bgra8UnormSrgb
| Self::Rgb10A2Unorm => 4,
Self::Rgba16Float => 8,
Self::R8Unorm => 1,
}
}
pub const fn intermediate(self) -> Self {
match self {
Self::Rgba16Float => Self::Rgba16Float,
_ => Self::Rgba8Unorm,
}
}
pub const fn quantization_step(self) -> f32 {
match self {
Self::Rgba8Unorm | Self::Rgba8UnormSrgb | Self::Bgra8Unorm | Self::Bgra8UnormSrgb => {
1.0 / 255.0
}
Self::Rgb10A2Unorm => 1.0 / 1023.0,
Self::Rgba16Float | Self::R8Unorm => 0.0,
}
}
pub const fn is_drawable(self) -> bool {
!self.is_srgb()
}
pub const fn is_srgb(self) -> bool {
matches!(self, Self::Rgba8UnormSrgb | Self::Bgra8UnormSrgb)
}
pub const fn fourcc(self) -> Option<Fourcc> {
match self {
Self::Rgba8Unorm | Self::Rgba8UnormSrgb => Some(Fourcc::ABGR8888),
Self::Bgra8Unorm | Self::Bgra8UnormSrgb => Some(Fourcc::ARGB8888),
Self::Rgb10A2Unorm => Some(Fourcc::XRGB2101010),
Self::Rgba16Float | Self::R8Unorm => None,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub struct Fourcc(pub u32);
impl Fourcc {
pub const fn new(code: [u8; 4]) -> Self {
Self(u32::from_le_bytes(code))
}
pub const ARGB8888: Self = Self::new(*b"AR24");
pub const XRGB8888: Self = Self::new(*b"XR24");
pub const ABGR8888: Self = Self::new(*b"AB24");
pub const XBGR8888: Self = Self::new(*b"XB24");
pub const XRGB2101010: Self = Self::new(*b"XR30");
pub const ARGB2101010: Self = Self::new(*b"AR30");
pub const fn to_bytes(self) -> [u8; 4] {
self.0.to_le_bytes()
}
}
impl std::fmt::Display for Fourcc {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let b = self.to_bytes();
for c in b {
write!(f, "{}", c as char)?;
}
Ok(())
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub struct Modifier(pub u64);
impl Modifier {
pub const LINEAR: Self = Self(0);
pub const INVALID: Self = Self(0x00ff_ffff_ffff_ffff);
pub const fn is_linear(self) -> bool {
self.0 == Self::LINEAR.0
}
pub const fn is_valid(self) -> bool {
self.0 != Self::INVALID.0
}
}
impl std::fmt::Display for Modifier {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
if self.is_linear() {
write!(f, "LINEAR")
} else if !self.is_valid() {
write!(f, "INVALID")
} else {
write!(f, "{:#018x}", self.0)
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct FormatModifierSet {
pub fourcc: Fourcc,
pub modifiers: Vec<Modifier>,
}
impl FormatModifierSet {
pub fn new(fourcc: Fourcc, modifiers: impl Into<Vec<Modifier>>) -> Self {
Self {
fourcc,
modifiers: modifiers.into(),
}
}
pub fn intersect(&self, other: &Self) -> Option<Self> {
if self.fourcc != other.fourcc {
return None;
}
let mut modifiers: Vec<Modifier> = self
.modifiers
.iter()
.filter(|m| other.modifiers.contains(m))
.copied()
.collect();
if modifiers.is_empty() {
return None;
}
modifiers.sort_by_key(|m| (m.is_linear(), m.0));
Some(Self {
fourcc: self.fourcc,
modifiers,
})
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn fourcc_round_trips_through_characters() {
assert_eq!(Fourcc::new(*b"AR24"), Fourcc::ARGB8888);
assert_eq!(&Fourcc::ARGB8888.to_bytes(), b"AR24");
assert_eq!(Fourcc::XRGB2101010.to_string(), "XR30");
}
#[test]
fn scanout_formats_map_to_fourcc_and_intermediates_do_not() {
assert_eq!(PixelFormat::Bgra8Unorm.fourcc(), Some(Fourcc::ARGB8888));
assert_eq!(PixelFormat::Rgba8Unorm.fourcc(), Some(Fourcc::ABGR8888));
assert_eq!(PixelFormat::Rgba16Float.fourcc(), None);
}
#[test]
fn srgb_variants_are_distinguished_from_linear_ones() {
assert!(PixelFormat::Bgra8UnormSrgb.is_srgb());
assert!(!PixelFormat::Bgra8Unorm.is_srgb());
assert_eq!(
PixelFormat::Bgra8UnormSrgb.bytes_per_pixel(),
PixelFormat::Bgra8Unorm.bytes_per_pixel()
);
}
#[test]
fn modifier_sentinels_are_distinct() {
assert!(Modifier::LINEAR.is_linear());
assert!(Modifier::LINEAR.is_valid());
assert!(!Modifier::INVALID.is_valid());
assert_eq!(Modifier::LINEAR.to_string(), "LINEAR");
assert_eq!(Modifier::INVALID.to_string(), "INVALID");
}
#[test]
fn intersection_prefers_non_linear_layouts() {
let vendor = Modifier(0x0100_0000_0000_0001);
let render = FormatModifierSet::new(Fourcc::ARGB8888, vec![Modifier::LINEAR, vendor]);
let scanout = FormatModifierSet::new(Fourcc::ARGB8888, vec![vendor, Modifier::LINEAR]);
let common = render.intersect(&scanout).expect("shared modifiers");
assert_eq!(common.modifiers.first(), Some(&vendor));
assert_eq!(common.modifiers.last(), Some(&Modifier::LINEAR));
}
#[test]
fn intersection_fails_loudly_rather_than_falling_back() {
let render = FormatModifierSet::new(Fourcc::ARGB8888, vec![Modifier(1)]);
let scanout = FormatModifierSet::new(Fourcc::ARGB8888, vec![Modifier(2)]);
assert_eq!(render.intersect(&scanout), None);
let other_format = FormatModifierSet::new(Fourcc::XRGB2101010, vec![Modifier(1)]);
assert_eq!(render.intersect(&other_format), None);
}
#[test]
fn extent_area_does_not_overflow_at_large_sizes() {
let huge = Extent2D::new(u32::MAX, 2);
assert_eq!(huge.area(), u32::MAX as u64 * 2);
assert!(Extent2D::new(0, 1080).is_empty());
}
}