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