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,
}
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 = stride as usize * (size.height as usize - 1) + size.width as usize;
if pixels.len() < required {
return Err(SurfaceError::BufferTooSmall {
required,
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, thiserror::Error)]
#[non_exhaustive]
pub enum SurfaceError {
#[error("surface is not ready to render")]
NotReady,
#[error("a frame is already in flight; present it before acquiring another")]
FrameInFlight,
#[error("no frame has been acquired")]
NoFrame,
#[error("buffer too small: need {required} pixels, got {actual}")]
BufferTooSmall {
required: usize,
actual: usize,
},
#[error(
"cursor sprite is {}x{} but the plane holds at most {}x{}",
requested.width, requested.height, limit.width, limit.height
)]
CursorTooLarge {
limit: crate::geom::Size,
requested: crate::geom::Size,
},
#[error("backend error: {0}")]
Backend(Box<dyn core::error::Error + Send + Sync + 'static>),
}
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 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)
));
}
}