use alloc::boxed::Box;
use crate::geom::{Rect, Size};
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub enum PixelFormat {
Argb8888,
Xrgb8888,
}
impl PixelFormat {
#[inline]
pub const fn bytes_per_pixel(self) -> usize {
4
}
#[inline]
pub const fn has_alpha(self) -> bool {
matches!(self, PixelFormat::Argb8888)
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub enum BufferAge {
Undefined,
Frames(u32),
}
#[derive(Debug)]
#[must_use = "a Frame must be drawn into and dropped before Surface::present"]
pub struct Frame<'a> {
pixels: &'a mut [u32],
size: Size,
stride: u32,
format: PixelFormat,
age: BufferAge,
}
#[inline]
pub const fn required_words(size: Size, stride: u32) -> u64 {
if size.is_empty() {
return 0;
}
stride as u64 * (size.height as u64 - 1) + size.width as u64
}
impl<'a> Frame<'a> {
pub fn new(
pixels: &'a mut [u32],
size: Size,
stride: u32,
format: PixelFormat,
age: BufferAge,
) -> Result<Self, SurfaceError> {
if size.is_empty() {
return Err(SurfaceError::NotReady);
}
if stride < size.width {
return Err(SurfaceError::BufferTooSmall {
required: size.width as usize,
actual: stride as usize,
});
}
let required = required_words(size, stride);
if (pixels.len() as u64) < required {
return Err(SurfaceError::BufferTooSmall {
required: usize::try_from(required).unwrap_or(usize::MAX),
actual: pixels.len(),
});
}
Ok(Self {
pixels,
size,
stride,
format,
age,
})
}
#[inline]
pub const fn size(&self) -> Size {
self.size
}
#[inline]
pub const fn stride(&self) -> u32 {
self.stride
}
#[inline]
pub const fn format(&self) -> PixelFormat {
self.format
}
#[inline]
pub const fn age(&self) -> BufferAge {
self.age
}
#[inline]
pub fn pixels(&self) -> &[u32] {
self.pixels
}
#[inline]
pub fn pixels_mut(&mut self) -> &mut [u32] {
self.pixels
}
#[inline]
pub fn row(&self, y: u32) -> Option<&[u32]> {
if y >= self.size.height {
return None;
}
let start = y as usize * self.stride as usize;
Some(&self.pixels[start..start + self.size.width as usize])
}
#[inline]
pub fn row_mut(&mut self, y: u32) -> Option<&mut [u32]> {
if y >= self.size.height {
return None;
}
let start = y as usize * self.stride as usize;
Some(&mut self.pixels[start..start + self.size.width as usize])
}
#[inline]
pub fn rows_mut(&mut self) -> impl Iterator<Item = &mut [u32]> {
let width = self.size.width as usize;
let height = self.size.height as usize;
self.pixels
.chunks_mut(self.stride as usize)
.take(height)
.map(move |row| &mut row[..width])
}
}
pub trait Surface {
fn size(&self) -> Size;
fn scale_factor(&self) -> f32;
fn format(&self) -> PixelFormat;
fn acquire(&mut self) -> Result<Frame<'_>, SurfaceError>;
fn present(&mut self, damage: &[Rect]) -> Result<(), SurfaceError>;
}
#[derive(Debug)]
#[non_exhaustive]
pub enum SurfaceError {
NotReady,
FrameInFlight,
NoFrame,
BufferTooSmall {
required: usize,
actual: usize,
},
CursorTooLarge {
limit: crate::geom::Size,
requested: crate::geom::Size,
},
Backend(Box<dyn core::error::Error + Send + Sync + 'static>),
}
impl core::fmt::Display for SurfaceError {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self {
Self::NotReady => f.write_str("surface is not ready to render"),
Self::FrameInFlight => {
f.write_str("a frame is already in flight; present it before acquiring another")
}
Self::NoFrame => f.write_str("no frame has been acquired"),
Self::BufferTooSmall { required, actual } => {
write!(f, "buffer too small: need {required} pixels, got {actual}")
}
Self::CursorTooLarge { limit, requested } => write!(
f,
"cursor sprite is {}x{} but the plane holds at most {}x{}",
requested.width, requested.height, limit.width, limit.height
),
Self::Backend(err) => write!(f, "backend error: {err}"),
}
}
}
impl core::error::Error for SurfaceError {}
impl SurfaceError {
pub fn backend<E: core::error::Error + Send + Sync + 'static>(err: E) -> Self {
SurfaceError::Backend(Box::new(err))
}
pub fn backend_msg(err: impl core::fmt::Display) -> Self {
use alloc::string::ToString;
SurfaceError::Backend(Box::new(BackendMessage(err.to_string())))
}
}
#[derive(Debug)]
struct BackendMessage(alloc::string::String);
impl core::fmt::Display for BackendMessage {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.write_str(&self.0)
}
}
impl core::error::Error for BackendMessage {}
#[cfg(test)]
mod tests {
use super::*;
use alloc::vec;
#[test]
fn the_messages_are_the_ones_the_derive_wrote() {
use alloc::string::ToString;
use core::error::Error as _;
assert_eq!(
SurfaceError::BufferTooSmall {
required: 8,
actual: 4
}
.to_string(),
"buffer too small: need 8 pixels, got 4"
);
assert_eq!(
SurfaceError::CursorTooLarge {
limit: crate::geom::Size::new(64, 64),
requested: crate::geom::Size::new(128, 96),
}
.to_string(),
"cursor sprite is 128x96 but the plane holds at most 64x64"
);
let wrapped = SurfaceError::backend_msg("the panel is on fire");
assert_eq!(wrapped.to_string(), "backend error: the panel is on fire");
assert!(wrapped.source().is_none());
}
#[test]
fn a_geometry_that_overflows_32_bit_arithmetic_is_still_measured_honestly() {
let size = Size::new(70_000, 65_000);
assert_eq!(required_words(size, 70_000), 4_550_000_000);
assert_ne!(4_550_000_000_u64 & 0xFFFF_FFFF, 4_550_000_000);
let mut buf = vec![0u32; 1024];
assert!(
Frame::new(
&mut buf,
size,
70_000,
PixelFormat::Xrgb8888,
BufferAge::Undefined,
)
.is_err()
);
}
#[test]
fn rejects_stride_below_width() {
let mut buf = vec![0u32; 64];
let err = Frame::new(
&mut buf,
Size::new(8, 8),
4,
PixelFormat::Xrgb8888,
BufferAge::Undefined,
);
assert!(matches!(err, Err(SurfaceError::BufferTooSmall { .. })));
}
#[test]
fn rejects_short_buffer() {
let mut buf = vec![0u32; 10];
let err = Frame::new(
&mut buf,
Size::new(8, 8),
8,
PixelFormat::Xrgb8888,
BufferAge::Undefined,
);
assert!(matches!(err, Err(SurfaceError::BufferTooSmall { .. })));
}
#[test]
fn accepts_exactly_sized_padded_buffer() {
let mut buf = vec![0u32; 10 * 3 + 6];
let mut frame = Frame::new(
&mut buf,
Size::new(6, 4),
10,
PixelFormat::Xrgb8888,
BufferAge::Frames(1),
)
.expect("geometry fits");
assert_eq!(frame.rows_mut().count(), 4);
assert!(frame.rows_mut().all(|r| r.len() == 6));
}
#[test]
fn rows_skip_padding() {
let mut buf = vec![0u32; 10 * 3];
let mut frame = Frame::new(
&mut buf,
Size::new(6, 3),
10,
PixelFormat::Xrgb8888,
BufferAge::Undefined,
)
.expect("geometry fits");
for row in frame.rows_mut() {
row.fill(0xFFFF_FFFF);
}
assert!(buf.chunks(10).all(|c| c[6..].iter().all(|&p| p == 0)));
}
#[test]
fn zero_size_is_not_ready() {
let mut buf = vec![0u32; 4];
assert!(matches!(
Frame::new(
&mut buf,
Size::ZERO,
0,
PixelFormat::Xrgb8888,
BufferAge::Undefined
),
Err(SurfaceError::NotReady)
));
}
}