use super::texture::{AlphaMode, ColorFormat, TexBuf, Texture};
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum PlaneError {
InvalidAlignment,
InvalidStride { minimum: usize, actual: usize },
MisalignedStride { alignment: usize },
MisalignedAddress { alignment: usize },
Overflow,
InsufficientCapacity { required: usize, available: usize },
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct PlaneRequirements {
address_alignment: usize,
stride_alignment: usize,
}
impl PlaneRequirements {
pub const CPU: Self = Self {
address_alignment: 1,
stride_alignment: 1,
};
pub fn new(address_alignment: usize, stride_alignment: usize) -> Result<Self, PlaneError> {
if !address_alignment.is_power_of_two() || !stride_alignment.is_power_of_two() {
return Err(PlaneError::InvalidAlignment);
}
Ok(Self {
address_alignment,
stride_alignment,
})
}
pub const fn address_alignment(self) -> usize {
self.address_alignment
}
pub const fn stride_alignment(self) -> usize {
self.stride_alignment
}
pub fn validate_address(self, bytes: &[u8]) -> Result<(), PlaneError> {
if !bytes.is_empty() && (bytes.as_ptr() as usize) % self.address_alignment != 0 {
return Err(PlaneError::MisalignedAddress {
alignment: self.address_alignment,
});
}
Ok(())
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct PlaneLayout {
width: u16,
height: u16,
format: ColorFormat,
stride_bytes: usize,
required_bytes: usize,
requirements: PlaneRequirements,
}
impl PlaneLayout {
pub fn packed(width: u16, height: u16, format: ColorFormat) -> Result<Self, PlaneError> {
Self::aligned(width, height, format, PlaneRequirements::CPU)
}
pub fn aligned(
width: u16,
height: u16,
format: ColorFormat,
requirements: PlaneRequirements,
) -> Result<Self, PlaneError> {
let minimum = usize::from(width)
.checked_mul(format.bytes_per_pixel())
.ok_or(PlaneError::Overflow)?;
let mask = requirements.stride_alignment - 1;
let stride_bytes = minimum.checked_add(mask).ok_or(PlaneError::Overflow)? & !mask;
Self::new(width, height, format, stride_bytes, requirements)
}
pub fn new(
width: u16,
height: u16,
format: ColorFormat,
stride_bytes: usize,
requirements: PlaneRequirements,
) -> Result<Self, PlaneError> {
let minimum = usize::from(width)
.checked_mul(format.bytes_per_pixel())
.ok_or(PlaneError::Overflow)?;
if stride_bytes < minimum {
return Err(PlaneError::InvalidStride {
minimum,
actual: stride_bytes,
});
}
if stride_bytes % requirements.stride_alignment != 0 {
return Err(PlaneError::MisalignedStride {
alignment: requirements.stride_alignment,
});
}
let required_bytes = stride_bytes
.checked_mul(usize::from(height))
.ok_or(PlaneError::Overflow)?;
Ok(Self {
width,
height,
format,
stride_bytes,
required_bytes,
requirements,
})
}
pub const fn width(self) -> u16 {
self.width
}
pub const fn height(self) -> u16 {
self.height
}
pub const fn stride_bytes(self) -> usize {
self.stride_bytes
}
pub const fn format(self) -> ColorFormat {
self.format
}
pub const fn requirements(self) -> PlaneRequirements {
self.requirements
}
pub const fn required_bytes(self) -> usize {
self.required_bytes
}
pub fn bind<'a>(self, bytes: &'a mut [u8]) -> Result<AlignedPlane<'a>, PlaneError> {
if bytes.len() < self.required_bytes {
return Err(PlaneError::InsufficientCapacity {
required: self.required_bytes,
available: bytes.len(),
});
}
self.requirements
.validate_address(&bytes[..self.required_bytes])?;
Ok(AlignedPlane {
bytes,
layout: self,
})
}
}
pub struct AlignedPlane<'a> {
bytes: &'a mut [u8],
layout: PlaneLayout,
}
impl AlignedPlane<'_> {
pub const fn layout(&self) -> PlaneLayout {
self.layout
}
pub fn bytes_mut(&mut self) -> &mut [u8] {
&mut self.bytes[..self.layout.required_bytes]
}
pub fn texture(&mut self) -> Texture<'_> {
let layout = self.layout;
Texture {
buf: TexBuf::Mut(self.bytes_mut()),
width: layout.width,
height: layout.height,
format: layout.format,
stride: layout.stride_bytes,
alpha_mode: AlphaMode::Opaque,
cache_revision: 0,
transient: true,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn aligned_borrowed_plane_preserves_padded_stride() {
let requirements = PlaneRequirements::new(64, 64).unwrap();
let layout = PlaneLayout::new(17, 2, ColorFormat::RGBA8888, 128, requirements).unwrap();
assert_eq!(layout.required_bytes(), 256);
let mut storage = [0u8; 320];
let offset = (64 - (storage.as_ptr() as usize % 64)) % 64;
let mut plane = layout.bind(&mut storage[offset..offset + 256]).unwrap();
let texture = plane.texture();
assert_eq!(texture.stride, 128);
assert_eq!(texture.buf.as_slice().len(), 256);
assert!(texture.transient);
}
#[test]
fn rejects_bad_stride_address_and_capacity() {
let requirements = PlaneRequirements::new(64, 64).unwrap();
assert_eq!(
PlaneLayout::new(17, 2, ColorFormat::RGBA8888, 64, requirements),
Err(PlaneError::InvalidStride {
minimum: 68,
actual: 64,
})
);
assert_eq!(
PlaneLayout::new(17, 2, ColorFormat::RGBA8888, 72, requirements),
Err(PlaneError::MisalignedStride { alignment: 64 })
);
let layout = PlaneLayout::new(17, 2, ColorFormat::RGBA8888, 128, requirements).unwrap();
let mut storage = [0u8; 320];
let offset = (64 - (storage.as_ptr() as usize % 64)) % 64;
assert!(matches!(
layout.bind(&mut storage[offset..offset + 255]),
Err(PlaneError::InsufficientCapacity {
required: 256,
available: 255
})
));
assert!(matches!(
layout.bind(&mut storage[offset + 1..offset + 257]),
Err(PlaneError::MisalignedAddress { alignment: 64 })
));
}
#[test]
fn rejects_invalid_alignment_and_overflow() {
assert_eq!(
PlaneRequirements::new(0, 64),
Err(PlaneError::InvalidAlignment)
);
assert_eq!(
PlaneRequirements::new(64, 3),
Err(PlaneError::InvalidAlignment)
);
let requirements = PlaneRequirements::new(1, 1).unwrap();
assert_eq!(
PlaneLayout::new(1, 2, ColorFormat::RGBA8888, usize::MAX, requirements),
Err(PlaneError::Overflow)
);
}
#[test]
fn aligned_layout_rounds_stride_before_capacity() {
let requirements = PlaneRequirements::new(64, 64).unwrap();
let layout = PlaneLayout::aligned(17, 3, ColorFormat::RGBA8888, requirements).unwrap();
assert_eq!(layout.stride_bytes(), 128);
assert_eq!(layout.required_bytes(), 384);
assert_eq!(layout.requirements(), requirements);
}
}