use denise::BufferAge;
pub const MAX_BUFFERS: usize = 4;
pub const MIN_BUFFERS: usize = 2;
#[derive(Clone, Debug)]
pub struct Swapchain {
count: usize,
current: usize,
presented_at: [Option<u64>; MAX_BUFFERS],
frame: u64,
}
impl Swapchain {
pub fn new(count: usize) -> Self {
Self {
count: count.clamp(MIN_BUFFERS, MAX_BUFFERS),
current: 0,
presented_at: [None; MAX_BUFFERS],
frame: 0,
}
}
#[inline]
pub const fn count(&self) -> usize {
self.count
}
#[inline]
pub const fn current(&self) -> usize {
self.current
}
#[inline]
pub const fn frames_presented(&self) -> u64 {
self.frame
}
pub fn age(&self) -> BufferAge {
match self.presented_at[self.current] {
Some(then) => BufferAge::Frames((self.frame + 1 - then) as u32),
None => BufferAge::Undefined,
}
}
pub fn presented(&mut self) {
self.frame += 1;
self.presented_at[self.current] = Some(self.frame);
self.current = (self.current + 1) % self.count;
}
pub fn invalidate(&mut self) {
self.presented_at = [None; MAX_BUFFERS];
self.current = 0;
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn buffer_count_is_clamped() {
assert_eq!(Swapchain::new(0).count(), MIN_BUFFERS);
assert_eq!(Swapchain::new(1).count(), MIN_BUFFERS);
assert_eq!(Swapchain::new(3).count(), 3);
assert_eq!(Swapchain::new(99).count(), MAX_BUFFERS);
}
#[test]
fn first_pass_over_every_buffer_is_undefined() {
let mut sc = Swapchain::new(3);
for _ in 0..3 {
assert_eq!(sc.age(), BufferAge::Undefined);
sc.presented();
}
assert_ne!(sc.age(), BufferAge::Undefined);
}
#[test]
fn double_buffering_reports_age_two() {
let mut sc = Swapchain::new(2);
sc.presented(); sc.presented(); assert_eq!(sc.age(), BufferAge::Frames(2));
}
#[test]
fn triple_buffering_reports_age_three() {
let mut sc = Swapchain::new(3);
for _ in 0..3 {
sc.presented();
}
assert_eq!(sc.age(), BufferAge::Frames(3));
}
#[test]
fn age_equals_buffer_count_in_the_steady_state() {
for count in MIN_BUFFERS..=MAX_BUFFERS {
let mut sc = Swapchain::new(count);
for _ in 0..count * 5 {
sc.presented();
}
assert_eq!(
sc.age(),
BufferAge::Frames(count as u32),
"{count} buffers settled to the wrong age"
);
}
}
#[test]
fn buffers_are_visited_round_robin() {
let mut sc = Swapchain::new(3);
let visited: Vec<usize> = (0..7)
.map(|_| {
let i = sc.current();
sc.presented();
i
})
.collect();
assert_eq!(visited, vec![0, 1, 2, 0, 1, 2, 0]);
}
#[test]
fn invalidate_forces_a_full_repaint_everywhere() {
let mut sc = Swapchain::new(2);
for _ in 0..10 {
sc.presented();
}
sc.invalidate();
assert_eq!(sc.current(), 0);
for _ in 0..2 {
assert_eq!(
sc.age(),
BufferAge::Undefined,
"stale age survived a modeset"
);
sc.presented();
}
}
#[test]
fn age_never_exceeds_the_buffer_count() {
let mut sc = Swapchain::new(4);
for _ in 0..50 {
if let BufferAge::Frames(n) = sc.age() {
assert!(
n as usize <= sc.count(),
"age {n} exceeds {} buffers",
sc.count()
);
}
sc.presented();
}
}
}