1use crate::render_types::{TextDrawCall, TextVertex};
7use alloc::vec::Vec;
8
9#[derive(Default)]
10pub struct TextCallBuffer {
12 pub calls: Vec<TextDrawCall>,
14 spare: Vec<(Vec<TextVertex>, Vec<u16>)>,
16 sort_order: Vec<u32>,
18 sort_dest: Vec<u32>,
19}
20
21impl TextCallBuffer {
22 pub fn recycle(&mut self, mut spent: Vec<TextDrawCall>) {
25 debug_assert!(self.calls.is_empty(), "recycling over an untaken build");
26 for call in spent.drain(..) {
27 let (mut vertices, mut indices) = (call.vertices, call.indices);
28 vertices.clear();
29 indices.clear();
30 self.spare.push((vertices, indices));
31 }
32 self.calls = spent;
33 }
34
35 pub fn geometry(&mut self) -> (Vec<TextVertex>, Vec<u16>) {
38 self.spare.pop().unwrap_or_default()
39 }
40
41 pub fn park(&mut self, mut vertices: Vec<TextVertex>, mut indices: Vec<u16>) {
44 vertices.clear();
45 indices.clear();
46 self.spare.push((vertices, indices));
47 }
48
49 pub fn take(&mut self) -> Vec<TextDrawCall> {
51 core::mem::take(&mut self.calls)
52 }
53
54 pub fn sort_by_layer(&mut self) {
60 if self.calls.iter().all(|c| c.layer == 0) {
61 return;
62 }
63 let calls = &mut self.calls;
64 let order = &mut self.sort_order;
67 order.clear();
68 order.extend(0..calls.len() as u32);
69 order.sort_unstable_by_key(|&i| (calls[i as usize].layer, i));
70 let dest = &mut self.sort_dest;
72 dest.clear();
73 dest.resize(calls.len(), 0);
74 for (new, &old) in order.iter().enumerate() {
75 dest[old as usize] = new as u32;
76 }
77 for i in 0..calls.len() {
78 while dest[i] as usize != i {
79 let j = dest[i] as usize;
80 calls.swap(i, j);
81 dest.swap(i, j);
82 }
83 }
84 }
85}
86
87impl core::fmt::Debug for TextCallBuffer {
89 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
90 f.debug_struct("TextCallBuffer")
91 .field("calls", &self.calls.len())
92 .field("spare", &self.spare.len())
93 .finish()
94 }
95}
96
97#[cfg(test)]
98mod tests {
99 use super::*;
100
101 use alloc::vec;
102 fn call(quads: usize) -> TextDrawCall {
103 TextDrawCall {
104 vertices: Vec::with_capacity(4 * quads),
105 indices: Vec::with_capacity(6 * quads),
106 atlas_slot: 0,
107 clip_rect: None,
108 layer: 0,
109 }
110 }
111
112 #[test]
113 fn recycle_reuses_the_list_and_its_geometry() {
114 let mut buf = TextCallBuffer::default();
115 let mut spent = vec![call(8), call(4)];
116 spent[0].vertices.push(TextVertex {
117 pos: [0.0, 0.0],
118 uv: [0.0, 0.0],
119 color: [0.0; 3],
120 mode: 0.0,
121 });
122 let list_ptr = spent.as_ptr();
123 let geom_cap = spent[1].vertices.capacity();
124
125 buf.recycle(spent);
126 assert_eq!(buf.calls.as_ptr(), list_ptr, "list backing reused");
127 assert!(buf.calls.is_empty());
128
129 let (vertices, indices) = buf.geometry();
131 assert!(vertices.is_empty() && indices.is_empty());
132 assert!(vertices.capacity() >= geom_cap);
133 let (second, _) = buf.geometry();
134 assert!(second.is_empty(), "the pushed vertex was cleared");
135 assert!(second.capacity() >= 32);
136 assert_eq!(buf.geometry().0.capacity(), 0);
138 }
139
140 #[test]
141 fn take_leaves_an_empty_buffer() {
142 let mut buf = TextCallBuffer::default();
143 buf.calls.push(call(1));
144 assert_eq!(buf.take().len(), 1);
145 assert!(buf.calls.is_empty());
146 }
147
148 fn layered(layer: i32, tag: usize) -> TextDrawCall {
150 TextDrawCall {
151 layer,
152 atlas_slot: tag,
153 ..call(0)
154 }
155 }
156
157 #[test]
158 fn sort_by_layer_orders_ascending_and_keeps_same_layer_insertion_order() {
159 let mut buf = TextCallBuffer::default();
160 for (layer, tag) in [(2, 0), (0, 1), (1, 2), (0, 3), (2, 4)] {
161 buf.calls.push(layered(layer, tag));
162 }
163 buf.sort_by_layer();
164 let got: Vec<(i32, usize)> = buf.calls.iter().map(|c| (c.layer, c.atlas_slot)).collect();
165 assert_eq!(got, [(0, 1), (0, 3), (1, 2), (2, 0), (2, 4)]);
166 }
167
168 #[test]
169 fn sort_by_layer_leaves_an_unlayered_list_untouched() {
170 let mut buf = TextCallBuffer::default();
171 for tag in 0..4 {
172 buf.calls.push(layered(0, tag));
173 }
174 buf.sort_by_layer();
175 let tags: Vec<usize> = buf.calls.iter().map(|c| c.atlas_slot).collect();
176 assert_eq!(tags, [0, 1, 2, 3]);
177 }
178
179 #[test]
182 fn sort_by_layer_is_correct_across_reuse() {
183 let mut buf = TextCallBuffer::default();
184 for (layer, tag) in [(3, 0), (1, 1), (2, 2)] {
185 buf.calls.push(layered(layer, tag));
186 }
187 buf.sort_by_layer();
188 buf.calls.clear();
189 for (layer, tag) in [(5, 0), (4, 1)] {
190 buf.calls.push(layered(layer, tag));
191 }
192 buf.sort_by_layer();
193 let got: Vec<(i32, usize)> = buf.calls.iter().map(|c| (c.layer, c.atlas_slot)).collect();
194 assert_eq!(got, [(4, 1), (5, 0)]);
195 }
196}