use slint::{Rgba8Pixel, SharedPixelBuffer};
const FRAME_BUFFERS: usize = 3;
pub struct FramebufferTransfer {
width: u32,
height: u32,
buffers: [Option<SharedPixelBuffer<Rgba8Pixel>>; FRAME_BUFFERS],
next: usize,
}
impl FramebufferTransfer {
#[must_use]
pub const fn new(width: u32, height: u32) -> Self {
Self {
width,
height,
buffers: [const { None }; FRAME_BUFFERS],
next: 0,
}
}
pub fn fill_with(&mut self, fill: impl FnOnce(&mut [u8])) -> SharedPixelBuffer<Rgba8Pixel> {
let index = self.next;
self.next = (index + 1) % FRAME_BUFFERS;
let (width, height) = (self.width, self.height);
let buffer =
self.buffers[index].get_or_insert_with(|| SharedPixelBuffer::new(width, height));
fill(bytemuck::cast_slice_mut(buffer.make_mut_slice()));
buffer.clone()
}
pub fn copy_from_gpu_slice(&mut self, gpu_bytes: &[u8]) -> SharedPixelBuffer<Rgba8Pixel> {
self.fill_with(|bytes| bytes.copy_from_slice(gpu_bytes))
}
}
#[cfg(test)]
mod tests {
use super::*;
fn bytes_of(frame: &SharedPixelBuffer<Rgba8Pixel>) -> &[u8] {
bytemuck::cast_slice(frame.as_slice())
}
#[test]
fn a_held_frame_is_never_overwritten() {
let mut transfer = FramebufferTransfer::new(2, 1);
let held: Vec<_> = (1..=7_u8)
.map(|shade| transfer.copy_from_gpu_slice(&[shade; 8]))
.collect();
for (shade, frame) in (1..=7_u8).zip(&held) {
assert_eq!(bytes_of(frame), [shade; 8], "frame {shade}");
}
}
#[test]
fn released_frames_are_reused_without_allocating() {
let mut transfer = FramebufferTransfer::new(4, 4);
let first = transfer.copy_from_gpu_slice(&[1; 64]).as_slice().as_ptr();
for shade in 2..=FRAME_BUFFERS as u8 {
drop(transfer.copy_from_gpu_slice(&[shade; 64]));
}
let again = transfer.copy_from_gpu_slice(&[9; 64]);
assert_eq!(again.as_slice().as_ptr(), first);
assert_eq!(bytes_of(&again), [9; 64]);
}
}