1use alloc::vec::Vec;
18
19use crate::gfx::render_types::draw_args_no_history;
20
21#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
23pub enum HistoryMode {
24 Track,
27 #[default]
31 Stale,
32 Untracked,
35}
36
37const KIND_DRAW: u64 = 0;
40const KIND_SKINNED: u64 = 1;
41
42const UNOBSERVED: u64 = u64::MAX;
45
46#[derive(Debug, Default)]
49pub struct ModelHistory {
50 draw_gen: Vec<u32>,
53 skinned_gen: Vec<u32>,
55 observed: Vec<u64>,
57 prime: bool,
60 mode: HistoryMode,
62}
63
64fn token(kind: u64, generation: u32, index: usize) -> u64 {
66 (kind << 62) | ((generation as u64 & 0x3FFF_FFFF) << 32) | (index as u64 & 0xFFFF_FFFF)
67}
68
69impl ModelHistory {
70 pub fn new() -> Self {
72 Self::default()
73 }
74
75 pub fn reset(&mut self, n_cull: usize) {
80 self.observed.clear();
81 self.observed.resize(n_cull, UNOBSERVED);
82 self.prime = true;
83 }
84
85 pub fn begin(&mut self, mode: HistoryMode, n_cull: usize) {
88 self.mode = mode;
89 match mode {
90 HistoryMode::Track if self.observed.len() == n_cull => {}
91 HistoryMode::Track | HistoryMode::Stale => self.reset(n_cull),
92 HistoryMode::Untracked => {}
93 }
94 }
95
96 pub fn draw_flags(&mut self, record: usize, draw_idx: usize) -> u32 {
101 if self.mode != HistoryMode::Track {
102 return draw_args_no_history();
103 }
104 let generation = self.draw_gen.get(draw_idx).copied().unwrap_or(0);
105 self.flags(record, token(KIND_DRAW, generation, draw_idx))
106 }
107
108 pub fn skinned_flags(&mut self, record: usize, skinned_idx: usize) -> u32 {
110 if self.mode != HistoryMode::Track {
111 return draw_args_no_history();
112 }
113 let generation = self.skinned_gen.get(skinned_idx).copied().unwrap_or(0);
114 self.flags(record, token(KIND_SKINNED, generation, skinned_idx))
115 }
116
117 fn flags(&mut self, record: usize, token: u64) -> u32 {
118 match self.observe(record, token) {
119 true => draw_args_no_history(),
120 false => 0,
121 }
122 }
123
124 pub fn take_prime(&mut self) -> bool {
128 core::mem::take(&mut self.prime)
129 }
130
131 pub fn reoccupy_draw(&mut self, draw_idx: usize) {
135 bump(&mut self.draw_gen, draw_idx);
136 }
137
138 pub fn reoccupy_skinned(&mut self, skinned_idx: usize) {
141 bump(&mut self.skinned_gen, skinned_idx);
142 }
143
144 fn observe(&mut self, record: usize, token: u64) -> bool {
145 let Some(slot) = self.observed.get_mut(record) else {
146 return true;
148 };
149 let stale = *slot != token;
150 *slot = token;
151 stale
152 }
153}
154
155fn bump(slots: &mut Vec<u32>, index: usize) {
157 if index >= slots.len() {
158 slots.resize(index + 1, 0);
159 }
160 slots[index] = slots[index].wrapping_add(1);
161}
162
163#[cfg(test)]
164mod tests {
165 use super::*;
166 use crate::gfx::render_types::draw_args_no_history;
167
168 const KEEP: u32 = 0;
169
170 #[test]
173 fn a_settled_record_is_stale_once_then_trusted() {
174 let mut h = ModelHistory::new();
175 h.begin(HistoryMode::Track, 4);
176 assert_eq!(h.draw_flags(2, 2), draw_args_no_history());
177 h.begin(HistoryMode::Track, 4);
178 assert_eq!(h.draw_flags(2, 2), KEEP);
179 h.begin(HistoryMode::Track, 4);
180 assert_eq!(h.draw_flags(2, 2), KEEP);
181 }
182
183 #[test]
186 fn reoccupying_a_slot_invalidates_its_record_for_one_frame() {
187 let mut h = ModelHistory::new();
188 h.begin(HistoryMode::Track, 4);
189 h.draw_flags(1, 1);
190 h.begin(HistoryMode::Track, 4);
191 assert_eq!(h.draw_flags(1, 1), KEEP);
192 h.reoccupy_draw(1);
193 h.begin(HistoryMode::Track, 4);
194 assert_eq!(h.draw_flags(1, 1), draw_args_no_history());
195 h.begin(HistoryMode::Track, 4);
196 assert_eq!(h.draw_flags(1, 1), KEEP);
197 }
198
199 #[test]
202 fn a_repacked_record_is_stale_even_though_neither_slot_changed() {
203 let mut h = ModelHistory::new();
204 h.begin(HistoryMode::Track, 8);
205 h.draw_flags(5, 40);
206 h.begin(HistoryMode::Track, 8);
207 assert_eq!(h.draw_flags(5, 40), KEEP);
208 h.begin(HistoryMode::Track, 8);
210 assert_eq!(h.draw_flags(5, 41), draw_args_no_history());
211 h.begin(HistoryMode::Track, 8);
212 assert_eq!(h.draw_flags(5, 41), KEEP);
213 }
214
215 #[test]
218 fn draw_and_skinned_occupants_never_alias() {
219 let mut h = ModelHistory::new();
220 h.begin(HistoryMode::Track, 4);
221 assert_eq!(h.draw_flags(0, 3), draw_args_no_history());
222 assert_eq!(h.skinned_flags(1, 3), draw_args_no_history());
223 h.begin(HistoryMode::Track, 4);
224 assert_eq!(h.draw_flags(0, 3), KEEP);
225 assert_eq!(h.skinned_flags(1, 3), KEEP);
226 h.reoccupy_skinned(3);
227 h.begin(HistoryMode::Track, 4);
228 assert_eq!(h.draw_flags(0, 3), KEEP);
230 assert_eq!(h.skinned_flags(1, 3), draw_args_no_history());
231 }
232
233 #[test]
236 fn a_record_count_change_invalidates_everything_and_asks_for_a_prime() {
237 let mut h = ModelHistory::new();
238 h.begin(HistoryMode::Track, 3);
239 assert!(h.take_prime());
240 for r in 0..3 {
241 assert_eq!(h.draw_flags(r, r), draw_args_no_history());
242 }
243 h.begin(HistoryMode::Track, 3);
244 assert!(!h.take_prime());
245 for r in 0..3 {
246 assert_eq!(h.draw_flags(r, r), KEEP);
247 }
248 h.begin(HistoryMode::Track, 5);
249 assert!(h.take_prime());
250 for r in 0..3 {
251 assert_eq!(h.draw_flags(r, r), draw_args_no_history());
252 }
253 }
254
255 #[test]
258 fn a_stale_frame_flags_everything_and_re_primes() {
259 let mut h = ModelHistory::new();
260 h.begin(HistoryMode::Track, 2);
261 h.draw_flags(0, 0);
262 h.begin(HistoryMode::Track, 2);
263 assert_eq!(h.draw_flags(0, 0), KEEP);
264 h.take_prime();
265 h.begin(HistoryMode::Stale, 2);
266 assert_eq!(h.draw_flags(0, 0), draw_args_no_history());
267 assert!(h.take_prime());
268 h.begin(HistoryMode::Track, 2);
269 assert_eq!(h.draw_flags(0, 0), draw_args_no_history());
270 }
271
272 #[test]
275 fn an_untracked_build_leaves_the_frames_tracker_alone() {
276 let mut h = ModelHistory::new();
277 h.begin(HistoryMode::Track, 2);
278 h.draw_flags(0, 0);
279 assert!(h.take_prime());
280 h.begin(HistoryMode::Untracked, 2);
281 assert_eq!(h.draw_flags(0, 0), draw_args_no_history());
282 assert!(!h.take_prime());
283 h.begin(HistoryMode::Track, 2);
284 assert_eq!(h.draw_flags(0, 0), KEEP);
285 }
286
287 #[test]
290 fn a_record_past_the_tracked_range_is_always_stale() {
291 let mut h = ModelHistory::new();
292 h.begin(HistoryMode::Track, 2);
293 assert_eq!(h.draw_flags(7, 7), draw_args_no_history());
294 h.begin(HistoryMode::Track, 2);
295 assert_eq!(h.draw_flags(7, 7), draw_args_no_history());
296 }
297
298 #[test]
301 fn reoccupying_an_untracked_slot_grows_the_generation_table() {
302 let mut h = ModelHistory::new();
303 h.begin(HistoryMode::Track, 4);
304 h.reoccupy_draw(9);
305 assert_eq!(h.draw_flags(0, 9), draw_args_no_history());
306 h.begin(HistoryMode::Track, 4);
307 assert_eq!(h.draw_flags(0, 9), KEEP);
308 h.reoccupy_draw(9);
309 h.begin(HistoryMode::Track, 4);
310 assert_eq!(h.draw_flags(0, 9), draw_args_no_history());
311 }
312}