use bytemuck::{Pod, Zeroable};
use crate::ShapeRecord;
#[repr(C)]
#[derive(Clone, Copy, Debug, PartialEq, Pod, Zeroable)]
pub struct ShapeRecordBody {
pub rect: [f32; 4],
pub color: [f32; 4],
pub stroke_width: f32,
pub flags: u32,
pub brush: u32,
pub placement: u32,
pub arc_geometry: [f32; 4],
}
#[repr(C)]
#[derive(Clone, Copy, Debug, PartialEq, Pod, Zeroable)]
pub struct ShapeRecordCurve {
pub radii: [f32; 4],
pub arc_normalized: [f32; 4],
}
#[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),
}
}
pub fn len(&self) -> usize {
self.bodies.len()
}
pub fn is_empty(&self) -> bool {
self.bodies.is_empty()
}
pub fn bodies(&self) -> &[ShapeRecordBody] {
&self.bodies
}
pub fn curves(&self) -> &[ShapeRecordCurve] {
&self.curves
}
pub fn get(&self, index: usize) -> Option<ShapeRecord> {
self.bodies
.get(index)
.map(|body| reconstruct(body, &self.curves[index], self.sources[index]))
}
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)]
mod tests {
use super::*;
fn sample() -> ShapeRecord {
ShapeRecord {
rect: [1.0, 2.0, 3.0, 4.0],
radii: [5.0, 6.0, 7.0, 8.0],
color: [0.2, 0.4, 0.6, 0.8],
stroke_width: 9.0,
flags: 10,
brush: 11,
reserved: 12,
arc: [13.0, 14.0, 15.0, 16.0],
arc_band: [17.0, 18.0, 19.0, 20.0],
arc_normalized: [21.0, 22.0, 23.0, 24.0],
}
}
fn append_sample(records: &mut ShapeRecords) {
records.push(
ShapeRecordBody {
rect: [1.0, 2.0, 3.0, 4.0],
color: [0.2, 0.4, 0.6, 0.8],
stroke_width: 9.0,
flags: 10,
brush: 11,
placement: 12,
arc_geometry: [13.0, 14.0, 19.0, 20.0],
},
ShapeRecordCurve {
radii: [5.0, 6.0, 7.0, 8.0],
arc_normalized: [21.0, 22.0, 23.0, 24.0],
},
[15.0, 16.0, 17.0, 18.0],
);
}
#[test]
fn columns_preserve_every_record_bit_and_gpu_field() {
let record = sample();
let mut special = record;
special.arc = [
f32::NEG_INFINITY,
-0.0,
f32::from_bits(0x7fc0_0021),
f32::INFINITY,
];
special.arc_band = [-0.0, f32::from_bits(0xffc0_0001), 1.0, 2.0];
let mut records = ShapeRecords::default();
assert!(records.is_empty());
assert_eq!(records.get(0), None);
append_sample(&mut records);
let mut body = records.bodies()[0];
body.arc_geometry = [f32::NEG_INFINITY, -0.0, 1.0, 2.0];
records.push(
body,
records.curves()[0],
[
special.arc[2],
special.arc[3],
special.arc_band[0],
special.arc_band[1],
],
);
assert_eq!(records.len(), 2);
assert_eq!(records.iter().len(), 2);
for (actual, expected) in records.iter().zip([record, special]) {
assert_eq!(bytemuck::bytes_of(&actual), bytemuck::bytes_of(&expected));
}
assert_eq!(records.get(0), Some(record));
assert_eq!(records.get(2), None);
assert_eq!(records.iter().rev().nth(1), Some(record));
assert_eq!(records.bodies()[0].arc_geometry, [13.0, 14.0, 19.0, 20.0]);
assert_eq!(records.curves()[0].radii, record.radii);
assert_eq!(records.curves()[0].arc_normalized, record.arc_normalized);
assert_eq!(std::mem::size_of::<ShapeRecordBody>(), 64);
assert_eq!(std::mem::size_of::<ShapeRecordCurve>(), 32);
assert_eq!(
&records.source_bytes()[..16],
bytemuck::bytes_of(&[15.0f32, 16.0, 17.0, 18.0])
);
}
#[test]
fn clearing_and_reserving_keep_columns_aligned_without_reallocating() {
let mut records = ShapeRecords::with_capacity(4);
append_sample(&mut records);
let capacity = records.capacity();
let bytes = records.heap_bytes();
let bodies = records.bodies().as_ptr();
let curves = records.curves().as_ptr();
records.clear();
records.reserve(2);
append_sample(&mut records);
assert_eq!(records.capacity(), capacity);
assert_eq!(records.heap_bytes(), bytes);
assert_eq!(records.bodies().as_ptr(), bodies);
assert_eq!(records.curves().as_ptr(), curves);
assert_eq!(records.len(), records.curves().len());
assert_eq!(records.get(0), Some(sample()));
assert_eq!(records.clone(), records);
}
}