use crate::platform::portable::{required_end, SurfaceGeometry};
pub struct FrameBuffer {
pixels: Vec<u8>,
geometry: SurfaceGeometry,
}
impl FrameBuffer {
pub fn new() -> Self {
Self { pixels: Vec::new(), geometry: SurfaceGeometry::tight(0, 0) }
}
pub fn resize(&mut self, geometry: SurfaceGeometry) -> bool {
if geometry.stride < geometry.width as usize * 4 {
return false;
}
let Some(needed) = required_bytes(geometry) else {
return false;
};
if self.pixels.len() < needed {
self.pixels.resize(needed, 0);
}
self.geometry = geometry;
true
}
pub const fn geometry(&self) -> SurfaceGeometry {
self.geometry
}
pub fn allocated_bytes(&self) -> usize {
self.pixels.len()
}
pub fn frame(&self) -> &[u8] {
let end = self.visible_bytes();
&self.pixels[..end]
}
pub fn frame_mut(&mut self) -> &mut [u8] {
let end = self.visible_bytes();
&mut self.pixels[..end]
}
pub fn clear(&mut self) {
let end = self.visible_bytes();
self.pixels[..end].fill(0);
}
fn visible_bytes(&self) -> usize {
required_bytes(self.geometry).unwrap_or(0)
}
}
crate::impl_default_via_new!(FrameBuffer);
fn required_bytes(geometry: SurfaceGeometry) -> Option<usize> {
if geometry.width == 0 {
return Some(0);
}
required_end(geometry.stride, geometry.height as usize, geometry.width as usize * 4)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn resize_then_frame_describes_the_geometry() {
let mut buffer = FrameBuffer::new();
assert!(buffer.resize(SurfaceGeometry::tight(4, 3)));
assert_eq!(buffer.frame().len(), 4 * 3 * 4);
assert_eq!(buffer.geometry(), SurfaceGeometry::tight(4, 3));
}
#[test]
fn clear_resets_every_visible_pixel() {
let mut buffer = FrameBuffer::new();
assert!(buffer.resize(SurfaceGeometry::tight(2, 2)));
buffer.frame_mut().fill(0xff);
buffer.clear();
assert!(buffer.frame().iter().all(|byte| *byte == 0));
}
#[test]
fn repeated_frames_of_the_same_size_do_not_allocate() {
let mut buffer = FrameBuffer::new();
assert!(buffer.resize(SurfaceGeometry::tight(64, 64)));
let after_first = buffer.allocated_bytes();
for _ in 0..1000 {
buffer.clear();
assert!(buffer.resize(SurfaceGeometry::tight(64, 64)));
}
assert_eq!(buffer.allocated_bytes(), after_first);
}
#[test]
fn padded_stride_does_not_allocate_trailing_padding() {
let mut buffer = FrameBuffer::new();
assert!(buffer.resize(SurfaceGeometry { width: 2, height: 2, stride: 12 }));
assert_eq!(buffer.frame().len(), 12 + 8);
}
#[test]
fn shrinking_geometry_keeps_the_allocation_but_not_the_frame() {
let mut buffer = FrameBuffer::new();
assert!(buffer.resize(SurfaceGeometry::tight(8, 8)));
let allocated = buffer.allocated_bytes();
assert!(buffer.resize(SurfaceGeometry::tight(2, 2)));
assert_eq!(buffer.allocated_bytes(), allocated, "the high-water mark is kept");
assert_eq!(buffer.frame().len(), 2 * 2 * 4, "only the current region is exposed");
}
#[test]
fn a_stride_narrower_than_a_row_is_refused_and_changes_nothing() {
let mut buffer = FrameBuffer::new();
assert!(buffer.resize(SurfaceGeometry::tight(4, 4)));
assert!(!buffer.resize(SurfaceGeometry { width: 4, height: 4, stride: 8 }));
assert_eq!(buffer.geometry(), SurfaceGeometry::tight(4, 4));
}
}