use crate::gfx::render_types::{TextDrawCall, TextVertex};
use alloc::vec::Vec;
#[derive(Default)]
pub struct TextCallBuffer {
pub calls: Vec<TextDrawCall>,
spare: Vec<(Vec<TextVertex>, Vec<u16>)>,
sort_order: Vec<u32>,
sort_dest: Vec<u32>,
}
impl TextCallBuffer {
pub fn recycle(&mut self, mut spent: Vec<TextDrawCall>) {
debug_assert!(self.calls.is_empty(), "recycling over an untaken build");
for call in spent.drain(..) {
let (mut vertices, mut indices) = (call.vertices, call.indices);
vertices.clear();
indices.clear();
self.spare.push((vertices, indices));
}
self.calls = spent;
}
pub fn geometry(&mut self) -> (Vec<TextVertex>, Vec<u16>) {
self.spare.pop().unwrap_or_default()
}
pub fn park(&mut self, mut vertices: Vec<TextVertex>, mut indices: Vec<u16>) {
vertices.clear();
indices.clear();
self.spare.push((vertices, indices));
}
pub fn take(&mut self) -> Vec<TextDrawCall> {
core::mem::take(&mut self.calls)
}
pub fn sort_by_layer(&mut self) {
if self.calls.iter().all(|c| c.layer == 0) {
return;
}
let calls = &mut self.calls;
let order = &mut self.sort_order;
order.clear();
order.extend(0..calls.len() as u32);
order.sort_unstable_by_key(|&i| (calls[i as usize].layer, i));
let dest = &mut self.sort_dest;
dest.clear();
dest.resize(calls.len(), 0);
for (new, &old) in order.iter().enumerate() {
dest[old as usize] = new as u32;
}
for i in 0..calls.len() {
while dest[i] as usize != i {
let j = dest[i] as usize;
calls.swap(i, j);
dest.swap(i, j);
}
}
}
}
impl core::fmt::Debug for TextCallBuffer {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.debug_struct("TextCallBuffer")
.field("calls", &self.calls.len())
.field("spare", &self.spare.len())
.finish()
}
}
#[cfg(test)]
mod tests {
use super::*;
use alloc::vec;
fn call(quads: usize) -> TextDrawCall {
TextDrawCall {
vertices: Vec::with_capacity(4 * quads),
indices: Vec::with_capacity(6 * quads),
atlas_slot: 0,
clip_rect: None,
layer: 0,
}
}
#[test]
fn recycle_reuses_the_list_and_its_geometry() {
let mut buf = TextCallBuffer::default();
let mut spent = vec![call(8), call(4)];
spent[0].vertices.push(TextVertex {
pos: [0.0, 0.0],
uv: [0.0, 0.0],
color: [0.0; 3],
mode: 0.0,
});
let list_ptr = spent.as_ptr();
let geom_cap = spent[1].vertices.capacity();
buf.recycle(spent);
assert_eq!(buf.calls.as_ptr(), list_ptr, "list backing reused");
assert!(buf.calls.is_empty());
let (vertices, indices) = buf.geometry();
assert!(vertices.is_empty() && indices.is_empty());
assert!(vertices.capacity() >= geom_cap);
let (second, _) = buf.geometry();
assert!(second.is_empty(), "the pushed vertex was cleared");
assert!(second.capacity() >= 32);
assert_eq!(buf.geometry().0.capacity(), 0);
}
#[test]
fn take_leaves_an_empty_buffer() {
let mut buf = TextCallBuffer::default();
buf.calls.push(call(1));
assert_eq!(buf.take().len(), 1);
assert!(buf.calls.is_empty());
}
fn layered(layer: i32, tag: usize) -> TextDrawCall {
TextDrawCall {
layer,
atlas_slot: tag,
..call(0)
}
}
#[test]
fn sort_by_layer_orders_ascending_and_keeps_same_layer_insertion_order() {
let mut buf = TextCallBuffer::default();
for (layer, tag) in [(2, 0), (0, 1), (1, 2), (0, 3), (2, 4)] {
buf.calls.push(layered(layer, tag));
}
buf.sort_by_layer();
let got: Vec<(i32, usize)> = buf.calls.iter().map(|c| (c.layer, c.atlas_slot)).collect();
assert_eq!(got, [(0, 1), (0, 3), (1, 2), (2, 0), (2, 4)]);
}
#[test]
fn sort_by_layer_leaves_an_unlayered_list_untouched() {
let mut buf = TextCallBuffer::default();
for tag in 0..4 {
buf.calls.push(layered(0, tag));
}
buf.sort_by_layer();
let tags: Vec<usize> = buf.calls.iter().map(|c| c.atlas_slot).collect();
assert_eq!(tags, [0, 1, 2, 3]);
}
#[test]
fn sort_by_layer_is_correct_across_reuse() {
let mut buf = TextCallBuffer::default();
for (layer, tag) in [(3, 0), (1, 1), (2, 2)] {
buf.calls.push(layered(layer, tag));
}
buf.sort_by_layer();
buf.calls.clear();
for (layer, tag) in [(5, 0), (4, 1)] {
buf.calls.push(layered(layer, tag));
}
buf.sort_by_layer();
let got: Vec<(i32, usize)> = buf.calls.iter().map(|c| (c.layer, c.atlas_slot)).collect();
assert_eq!(got, [(4, 1), (5, 0)]);
}
}