Skip to main content

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}