Skip to main content

concinnity_core/render/
slot_rewrites.rs

1//! Propagates a streamed texture-pool slot swap across per-frame descriptor
2//! copies without stalling the device. Backends that bake the texture pool
3//! into per-frame-in-flight descriptor sets (Vulkan) or heap regions (DirectX)
4//! cannot legally rewrite a descriptor while a command buffer referencing it
5//! is pending; instead a swap queues its slot here, and each `draw_frame`
6//! applies the queued slots to the copy owned by the frame slot it just
7//! fence-waited (which is therefore not referenced by any pending work). An
8//! entry retires once every frame-in-flight copy has been rewritten.
9
10use alloc::vec::Vec;
11
12#[derive(Debug)]
13/// Defers draw-slot rewrites until every in-flight frame has retired.
14pub struct SlotRewriteQueue {
15    // (pool slot, applications remaining). A slot appears at most once.
16    entries: Vec<(usize, usize)>,
17    frames_in_flight: usize,
18}
19
20impl SlotRewriteQueue {
21    /// A queue sized for `frames_in_flight` in-flight frames.
22    pub fn new(frames_in_flight: usize) -> Self {
23        Self {
24            entries: Vec::new(),
25            frames_in_flight: frames_in_flight.max(1),
26        }
27    }
28
29    /// Queue pool `slot` for rewriting in every per-frame copy. Re-queueing a
30    /// slot mid-propagation restarts its countdown, so every copy ends on the
31    /// view the caller swapped in last (the rewrite reads the pool at apply
32    /// time, not at queue time).
33    pub fn queue(&mut self, slot: usize) {
34        if let Some(entry) = self.entries.iter_mut().find(|(s, _)| *s == slot) {
35            entry.1 = self.frames_in_flight;
36        } else {
37            self.entries.push((slot, self.frames_in_flight));
38        }
39    }
40
41    /// The slots whose descriptor must be rewritten in the frame copy about to
42    /// record. Call exactly once per frame, after the frame slot's fence wait.
43    /// Entries that have now covered every copy are dropped.
44    pub fn begin_frame(&mut self) -> Vec<usize> {
45        let slots: Vec<usize> = self.entries.iter().map(|(s, _)| *s).collect();
46        for entry in &mut self.entries {
47            entry.1 -= 1;
48        }
49        self.entries.retain(|(_, remaining)| *remaining > 0);
50        slots
51    }
52
53    /// Drop a queued slot: the caller rewrote every copy itself (e.g. a
54    /// fallback full rewrite under a device drain).
55    pub fn remove(&mut self, slot: usize) {
56        self.entries.retain(|(s, _)| *s != slot);
57    }
58
59    /// Whether nothing is queued.
60    pub fn is_empty(&self) -> bool {
61        self.entries.is_empty()
62    }
63}
64
65#[cfg(test)]
66mod tests {
67    use super::*;
68
69    use alloc::vec;
70    #[test]
71    fn a_slot_is_applied_once_per_frame_copy() {
72        let mut q = SlotRewriteQueue::new(3);
73        q.queue(7);
74        assert_eq!(q.begin_frame(), vec![7]);
75        assert_eq!(q.begin_frame(), vec![7]);
76        assert_eq!(q.begin_frame(), vec![7]);
77        assert!(q.begin_frame().is_empty());
78        assert!(q.is_empty());
79    }
80
81    #[test]
82    fn requeueing_mid_propagation_restarts_the_countdown() {
83        let mut q = SlotRewriteQueue::new(3);
84        q.queue(4);
85        assert_eq!(q.begin_frame(), vec![4]);
86        // A second swap of the same slot before propagation finished: the
87        // remaining copies must still converge on the latest view.
88        q.queue(4);
89        assert_eq!(q.begin_frame(), vec![4]);
90        assert_eq!(q.begin_frame(), vec![4]);
91        assert_eq!(q.begin_frame(), vec![4]);
92        assert!(q.begin_frame().is_empty());
93    }
94
95    #[test]
96    fn independent_slots_propagate_independently() {
97        let mut q = SlotRewriteQueue::new(2);
98        q.queue(1);
99        assert_eq!(q.begin_frame(), vec![1]);
100        q.queue(2);
101        let mut slots = q.begin_frame();
102        slots.sort_unstable();
103        assert_eq!(slots, vec![1, 2]);
104        assert_eq!(q.begin_frame(), vec![2]);
105        assert!(q.begin_frame().is_empty());
106    }
107
108    #[test]
109    fn remove_drops_a_queued_slot() {
110        let mut q = SlotRewriteQueue::new(3);
111        q.queue(5);
112        q.queue(6);
113        q.remove(5);
114        assert_eq!(q.begin_frame(), vec![6]);
115    }
116
117    #[test]
118    fn zero_frames_in_flight_is_clamped_to_one() {
119        let mut q = SlotRewriteQueue::new(0);
120        q.queue(9);
121        assert_eq!(q.begin_frame(), vec![9]);
122        assert!(q.is_empty());
123    }
124}