use alloc::vec::Vec;
pub const REFIT_LIMIT: u32 = 32;
#[derive(Clone, Copy, PartialEq, Eq, Debug, Default)]
pub struct SkinnedShape {
pub index_offset: usize,
pub index_count: usize,
pub vertex_extent: u32,
}
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub enum BlasUpdate {
Build,
Refit,
}
fn blas_update(shape_changed: bool, built: bool, refits: u32, limit: u32) -> BlasUpdate {
if shape_changed || !built || refits >= limit {
BlasUpdate::Build
} else {
BlasUpdate::Refit
}
}
#[derive(Default)]
pub struct SkinnedRefit {
shapes: Vec<SkinnedShape>,
built: bool,
refits: u32,
}
impl SkinnedRefit {
pub fn plan(&mut self, shapes: &[SkinnedShape], storage_changed: bool) -> BlasUpdate {
let changed = storage_changed || self.shapes != shapes;
let update = blas_update(changed, self.built, self.refits, REFIT_LIMIT);
if changed {
self.shapes.clear();
self.shapes.extend_from_slice(shapes);
}
match update {
BlasUpdate::Build => {
self.built = true;
self.refits = 0;
}
BlasUpdate::Refit => self.refits += 1,
}
update
}
pub fn reset(&mut self) {
self.shapes.clear();
self.built = false;
self.refits = 0;
}
}
#[cfg(test)]
mod tests {
use super::*;
fn shape(tag: usize) -> SkinnedShape {
SkinnedShape {
index_offset: tag,
index_count: 300,
vertex_extent: 512,
}
}
#[test]
fn a_changed_triangle_set_forces_a_full_build() {
assert_eq!(blas_update(true, true, 0, 32), BlasUpdate::Build);
}
#[test]
fn an_unbuilt_slot_cannot_be_refit() {
assert_eq!(blas_update(false, false, 0, 32), BlasUpdate::Build);
}
#[test]
fn a_stable_shape_refits_until_the_limit() {
assert_eq!(blas_update(false, true, 0, 32), BlasUpdate::Refit);
assert_eq!(blas_update(false, true, 31, 32), BlasUpdate::Refit);
assert_eq!(blas_update(false, true, 32, 32), BlasUpdate::Build);
assert_eq!(blas_update(false, true, 99, 32), BlasUpdate::Build);
}
#[test]
fn a_zero_limit_never_refits() {
assert_eq!(blas_update(false, true, 0, 0), BlasUpdate::Build);
}
#[test]
fn shape_equality_covers_slice_and_vertex_extent() {
let a = shape(12);
assert_eq!(a, a);
assert_ne!(a, shape(13));
assert_ne!(
a,
SkinnedShape {
index_count: 303,
..a
}
);
assert_ne!(
a,
SkinnedShape {
vertex_extent: 513,
..a
}
);
}
#[test]
fn a_fresh_slot_builds_then_refits() {
let mut slot = SkinnedRefit::default();
let shapes = [shape(0), shape(1)];
assert_eq!(slot.plan(&shapes, false), BlasUpdate::Build);
assert_eq!(slot.plan(&shapes, false), BlasUpdate::Refit);
assert_eq!(slot.plan(&shapes, false), BlasUpdate::Refit);
}
#[test]
fn reallocated_storage_rebuilds_even_on_an_unchanged_shape() {
let mut slot = SkinnedRefit::default();
let shapes = [shape(0)];
assert_eq!(slot.plan(&shapes, false), BlasUpdate::Build);
assert_eq!(slot.plan(&shapes, true), BlasUpdate::Build);
assert_eq!(slot.plan(&shapes, false), BlasUpdate::Refit);
}
#[test]
fn a_changed_object_set_rebuilds_and_restarts_the_run() {
let mut slot = SkinnedRefit::default();
assert_eq!(slot.plan(&[shape(0)], false), BlasUpdate::Build);
assert_eq!(slot.plan(&[shape(0)], false), BlasUpdate::Refit);
assert_eq!(slot.plan(&[shape(0), shape(1)], false), BlasUpdate::Build);
assert_eq!(slot.plan(&[shape(0), shape(1)], false), BlasUpdate::Refit);
}
#[test]
fn the_refit_run_is_bounded_by_the_limit() {
let mut slot = SkinnedRefit::default();
let shapes = [shape(0)];
assert_eq!(slot.plan(&shapes, false), BlasUpdate::Build);
for _ in 0..REFIT_LIMIT {
assert_eq!(slot.plan(&shapes, false), BlasUpdate::Refit);
}
assert_eq!(slot.plan(&shapes, false), BlasUpdate::Build);
assert_eq!(slot.plan(&shapes, false), BlasUpdate::Refit);
}
#[test]
fn reset_makes_the_next_update_a_build() {
let mut slot = SkinnedRefit::default();
let shapes = [shape(0)];
assert_eq!(slot.plan(&shapes, false), BlasUpdate::Build);
assert_eq!(slot.plan(&shapes, false), BlasUpdate::Refit);
slot.reset();
assert_eq!(slot.plan(&shapes, false), BlasUpdate::Build);
}
}