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
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
use bytemuck::{Pod, Zeroable};
use crate::ShapeRecord;
/// Shape properties independent of an arc's angles, in instance-buffer layout.
#[repr(C)]
#[derive(Clone, Copy, Debug, PartialEq, Pod, Zeroable)]
pub struct ShapeRecordBody {
/// The stored local rectangle.
pub rect: [f32; 4],
/// The solid colour or first gradient stop.
pub color: [f32; 4],
/// The stroke width.
pub stroke_width: f32,
/// Packed shape, stroke, blend and arc facts from [`ShapeRecord::flags`].
pub flags: u32,
/// Zero for a solid brush, otherwise one plus its table index.
pub brush: u32,
/// The placement index when a renderer combines recordings.
pub placement: u32,
/// Arc centre x and y, followed by normalised inner and outer radii.
pub arc_geometry: [f32; 4],
}
/// Corner radii and arc-angle properties, in instance-buffer layout.
#[repr(C)]
#[derive(Clone, Copy, Debug, PartialEq, Pod, Zeroable)]
pub struct ShapeRecordCurve {
/// Corner radii for rectangles; zero for bands, whose trig the GPU
/// derives from `arc_normalized` once per uploaded band and writes here.
/// No other record's row equals the row of a band that draws: such a
/// band's sweep, `arc_normalized[1]`, is positive and its radii and
/// `arc_normalized[2..]` zero, while a rect's and an unbanded round
/// rect's `arc_normalized` is zero, a banded ring's radii are positive,
/// a line of positive length carries its half length in
/// `arc_normalized[2]` and one of no length points along +X. A record
/// that becomes or stops being a band therefore uploads a row of its
/// own, and a band whose row is unchanged keeps its trig.
pub radii: [f32; 4],
/// A band's normalised start and sweep, the rest zero; a line's unit
/// direction and half length.
pub arc_normalized: [f32; 4],
}
/// Recorded shapes stored in parallel columns, ready for GPU upload.
///
/// Angle changes leave the body column intact. Original arc arguments remain
/// on the CPU so materialisation preserves exactly what the caller supplied.
#[derive(Clone, Debug, Default, PartialEq)]
pub struct ShapeRecords {
bodies: Vec<ShapeRecordBody>,
curves: Vec<ShapeRecordCurve>,
sources: Vec<[f32; 4]>,
}
impl ShapeRecords {
pub(crate) fn with_capacity(capacity: usize) -> Self {
Self {
bodies: Vec::with_capacity(capacity),
curves: Vec::with_capacity(capacity),
sources: Vec::with_capacity(capacity),
}
}
/// The number of recorded shapes.
pub fn len(&self) -> usize {
self.bodies.len()
}
/// Whether the recording has no shapes.
pub fn is_empty(&self) -> bool {
self.bodies.is_empty()
}
/// The angle-independent column, directly usable as GPU instance data.
pub fn bodies(&self) -> &[ShapeRecordBody] {
&self.bodies
}
/// The radius and angle column, directly usable as GPU instance data.
pub fn curves(&self) -> &[ShapeRecordCurve] {
&self.curves
}
/// Whether the record at `index` is an opaque fill whose interior is worth
/// laying down ahead of later paint: the test that marks a segment's
/// [`occluders`](crate::RecordSegment::occluders). `false` past the end.
pub fn occludes(&self, index: usize) -> bool {
self.bodies
.get(index)
.zip(self.curves.get(index))
.is_some_and(|(body, curve)| crate::record::interior_occludes(body, curve))
}
/// Reconstructs one complete record, including the original arc arguments.
pub fn get(&self, index: usize) -> Option<ShapeRecord> {
self.bodies
.get(index)
.map(|body| reconstruct(body, &self.curves[index], self.sources[index]))
}
/// Iterates over complete records in draw order without allocating.
pub fn iter(&self) -> impl ExactSizeIterator<Item = ShapeRecord> + DoubleEndedIterator + '_ {
self.bodies
.iter()
.zip(&self.curves)
.zip(&self.sources)
.map(|((body, curve), &source)| reconstruct(body, curve, source))
}
pub(crate) fn capacity(&self) -> usize {
self.bodies
.capacity()
.min(self.curves.capacity())
.min(self.sources.capacity())
}
pub(crate) fn heap_bytes(&self) -> usize {
self.bodies.capacity() * std::mem::size_of::<ShapeRecordBody>()
+ self.curves.capacity() * std::mem::size_of::<ShapeRecordCurve>()
+ self.sources.capacity() * std::mem::size_of::<[f32; 4]>()
}
pub(crate) fn source_bytes(&self) -> &[u8] {
bytemuck::cast_slice(&self.sources)
}
pub(crate) fn clear(&mut self) {
self.bodies.clear();
self.curves.clear();
self.sources.clear();
}
pub(crate) fn reserve(&mut self, additional: usize) {
self.bodies.reserve(additional);
self.curves.reserve(additional);
self.sources.reserve(additional);
}
pub(crate) fn push(
&mut self,
body: ShapeRecordBody,
curve: ShapeRecordCurve,
source: [f32; 4],
) {
self.bodies.push(body);
self.curves.push(curve);
self.sources.push(source);
}
}
fn reconstruct(body: &ShapeRecordBody, curve: &ShapeRecordCurve, source: [f32; 4]) -> ShapeRecord {
ShapeRecord {
rect: body.rect,
radii: curve.radii,
color: body.color,
stroke_width: body.stroke_width,
flags: body.flags,
brush: body.brush,
reserved: body.placement,
arc: [
body.arc_geometry[0],
body.arc_geometry[1],
source[0],
source[1],
],
arc_band: [
source[2],
source[3],
body.arc_geometry[2],
body.arc_geometry[3],
],
arc_normalized: curve.arc_normalized,
}
}
#[cfg(test)]
#[path = "tests/shape_records_tests.rs"]
mod tests;