densa_draw/instance.rs
1use bytemuck::{Pod, Zeroable};
2use densa_core::Color;
3
4/// One quad, as read by the vertex shader (per-instance step mode).
5///
6/// A glyph has a non-empty `uv` rectangle; a solid rectangle has `uv` all zero.
7#[repr(C)]
8#[derive(Debug, Clone, Copy, PartialEq, Pod, Zeroable)]
9pub struct Instance {
10 /// `[x, y, width, height]` in physical pixels, origin top-left.
11 pub rect: [f32; 4],
12 /// `[x0, y0, x1, y1]` of the glyph in the atlas, in texels (the shader normalizes).
13 pub uv: [f32; 4],
14 /// Linear RGBA, not premultiplied (a [`Color`] in the shader's layout).
15 pub color: [f32; 4],
16}
17
18impl Instance {
19 /// A filled rectangle.
20 pub const fn solid(rect: [f32; 4], color: Color) -> Self {
21 Self {
22 rect,
23 uv: [0.0; 4],
24 color: color.to_array(),
25 }
26 }
27
28 /// A glyph: `uv` selects its atlas cell, `color` tints its coverage.
29 pub const fn glyph(rect: [f32; 4], uv: [f32; 4], color: Color) -> Self {
30 Self {
31 rect,
32 uv,
33 color: color.to_array(),
34 }
35 }
36
37 /// `true` for glyphs, `false` for solid rectangles.
38 pub fn is_glyph(&self) -> bool {
39 self.uv[2] > self.uv[0]
40 }
41}
42
43/// Fixed-capacity instance list rebuilt every frame without allocating.
44///
45/// Pushing past capacity drops the instance and counts it, so an undersized buffer is
46/// visible in metrics instead of silently growing (and allocating) on the hot path.
47#[derive(Debug, Clone)]
48pub struct InstanceBuffer {
49 items: Box<[Instance]>,
50 len: usize,
51 dropped: u64,
52}
53
54impl InstanceBuffer {
55 /// An empty buffer holding up to `capacity` instances. Allocates once, here.
56 pub fn with_capacity(capacity: usize) -> Self {
57 Self {
58 items: vec![Instance::zeroed(); capacity].into_boxed_slice(),
59 len: 0,
60 dropped: 0,
61 }
62 }
63
64 /// Starts a new frame.
65 pub fn clear(&mut self) {
66 self.len = 0;
67 self.dropped = 0;
68 }
69
70 /// Appends `instance`; returns `false` (and counts a drop) when full.
71 #[inline]
72 pub fn push(&mut self, instance: Instance) -> bool {
73 match self.items.get_mut(self.len) {
74 Some(slot) => {
75 *slot = instance;
76 self.len += 1;
77 true
78 }
79 None => {
80 self.dropped += 1;
81 false
82 }
83 }
84 }
85
86 /// Instances pushed since the last [`clear`](Self::clear).
87 pub fn as_slice(&self) -> &[Instance] {
88 &self.items[..self.len]
89 }
90
91 /// Number of instances pushed.
92 pub fn len(&self) -> usize {
93 self.len
94 }
95
96 /// `true` if nothing was pushed.
97 pub fn is_empty(&self) -> bool {
98 self.len == 0
99 }
100
101 /// Maximum number of instances.
102 pub fn capacity(&self) -> usize {
103 self.items.len()
104 }
105
106 /// Instances dropped since the last [`clear`](Self::clear).
107 pub fn dropped(&self) -> u64 {
108 self.dropped
109 }
110}