1#![allow(dead_code)]
7
8#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
10pub enum VertexAttribute {
11 Float32,
13 Float32x2,
15 Float32x3,
17 Float32x4,
19 Uint32,
21 Uint32x2,
23 Sint32,
25 Unorm8x4,
27}
28
29impl VertexAttribute {
30 #[must_use]
32 pub const fn byte_size(self) -> usize {
33 match self {
34 Self::Float32 => 4,
35 Self::Float32x2 => 8,
36 Self::Float32x3 => 12,
37 Self::Float32x4 => 16,
38 Self::Uint32 => 4,
39 Self::Uint32x2 => 8,
40 Self::Sint32 => 4,
41 Self::Unorm8x4 => 4,
42 }
43 }
44
45 #[must_use]
47 pub const fn component_count(self) -> usize {
48 match self {
49 Self::Float32 | Self::Uint32 | Self::Sint32 => 1,
50 Self::Float32x2 | Self::Uint32x2 => 2,
51 Self::Float32x3 => 3,
52 Self::Float32x4 | Self::Unorm8x4 => 4,
53 }
54 }
55}
56
57#[derive(Debug, Clone)]
59pub struct VertexSlot {
60 pub name: String,
62 pub attribute: VertexAttribute,
64 pub offset: usize,
66}
67
68#[derive(Debug, Clone, Default)]
73pub struct VertexLayout {
74 slots: Vec<VertexSlot>,
75}
76
77impl VertexLayout {
78 #[must_use]
80 pub fn new() -> Self {
81 Self::default()
82 }
83
84 pub fn add(&mut self, name: impl Into<String>, attribute: VertexAttribute) -> &mut Self {
88 let offset = self.stride();
89 self.slots.push(VertexSlot {
90 name: name.into(),
91 attribute,
92 offset,
93 });
94 self
95 }
96
97 #[must_use]
99 pub fn stride(&self) -> usize {
100 self.slots.iter().map(|s| s.attribute.byte_size()).sum()
101 }
102
103 #[must_use]
105 pub fn attribute_count(&self) -> usize {
106 self.slots.len()
107 }
108
109 #[must_use]
111 pub fn slots(&self) -> &[VertexSlot] {
112 &self.slots
113 }
114
115 #[must_use]
117 pub fn slot_by_name(&self, name: &str) -> Option<&VertexSlot> {
118 self.slots.iter().find(|s| s.name == name)
119 }
120}
121
122#[derive(Debug, Clone, PartialEq, Eq)]
124pub enum VertexBufferError {
125 StrideMismatch {
127 stride: usize,
129 data_len: usize,
131 },
132 Empty,
134}
135
136impl std::fmt::Display for VertexBufferError {
137 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
138 match self {
139 Self::StrideMismatch { stride, data_len } => write!(
140 f,
141 "data length {data_len} is not a multiple of stride {stride}"
142 ),
143 Self::Empty => write!(f, "vertex buffer is empty"),
144 }
145 }
146}
147
148impl std::error::Error for VertexBufferError {}
149
150#[derive(Debug, Clone)]
168pub struct VertexBuffer {
169 layout: VertexLayout,
170 data: Vec<u8>,
171}
172
173impl VertexBuffer {
174 pub fn new(layout: VertexLayout, data: Vec<u8>) -> Result<Self, VertexBufferError> {
183 let stride = layout.stride();
184 if stride == 0 {
185 return Err(VertexBufferError::Empty);
186 }
187 if data.len() % stride != 0 {
188 return Err(VertexBufferError::StrideMismatch {
189 stride,
190 data_len: data.len(),
191 });
192 }
193 Ok(Self { layout, data })
194 }
195
196 #[must_use]
198 pub fn stride(&self) -> usize {
199 self.layout.stride()
200 }
201
202 #[must_use]
204 pub fn vertex_count(&self) -> usize {
205 let s = self.stride();
206 if s == 0 {
207 0
208 } else {
209 self.data.len() / s
210 }
211 }
212
213 #[must_use]
215 pub fn byte_len(&self) -> usize {
216 self.data.len()
217 }
218
219 #[must_use]
221 pub fn as_bytes(&self) -> &[u8] {
222 &self.data
223 }
224
225 #[must_use]
227 pub fn layout(&self) -> &VertexLayout {
228 &self.layout
229 }
230
231 #[must_use]
233 pub fn vertex_bytes(&self, index: usize) -> Option<&[u8]> {
234 let s = self.stride();
235 let start = index * s;
236 let end = start + s;
237 self.data.get(start..end)
238 }
239}
240
241#[cfg(test)]
242mod tests {
243 use super::*;
244
245 #[test]
246 fn attribute_float32_size() {
247 assert_eq!(VertexAttribute::Float32.byte_size(), 4);
248 }
249
250 #[test]
251 fn attribute_float32x3_size() {
252 assert_eq!(VertexAttribute::Float32x3.byte_size(), 12);
253 }
254
255 #[test]
256 fn attribute_unorm8x4_size() {
257 assert_eq!(VertexAttribute::Unorm8x4.byte_size(), 4);
258 }
259
260 #[test]
261 fn attribute_component_counts() {
262 assert_eq!(VertexAttribute::Float32.component_count(), 1);
263 assert_eq!(VertexAttribute::Float32x2.component_count(), 2);
264 assert_eq!(VertexAttribute::Float32x3.component_count(), 3);
265 assert_eq!(VertexAttribute::Float32x4.component_count(), 4);
266 }
267
268 #[test]
269 fn layout_stride_single_attr() {
270 let mut l = VertexLayout::new();
271 l.add("POS", VertexAttribute::Float32x3);
272 assert_eq!(l.stride(), 12);
273 }
274
275 #[test]
276 fn layout_stride_multiple_attrs() {
277 let mut l = VertexLayout::new();
278 l.add("POS", VertexAttribute::Float32x3);
279 l.add("UV", VertexAttribute::Float32x2);
280 assert_eq!(l.stride(), 20);
281 }
282
283 #[test]
284 fn layout_offsets_are_cumulative() {
285 let mut l = VertexLayout::new();
286 l.add("POS", VertexAttribute::Float32x3);
287 l.add("UV", VertexAttribute::Float32x2);
288 assert_eq!(l.slots()[0].offset, 0);
289 assert_eq!(l.slots()[1].offset, 12);
290 }
291
292 #[test]
293 fn layout_slot_by_name() {
294 let mut l = VertexLayout::new();
295 l.add("NORMAL", VertexAttribute::Float32x3);
296 assert!(l.slot_by_name("NORMAL").is_some());
297 assert!(l.slot_by_name("UV").is_none());
298 }
299
300 #[test]
301 fn vertex_buffer_create_ok() {
302 let mut l = VertexLayout::new();
303 l.add("POS", VertexAttribute::Float32x3);
304 let vb = VertexBuffer::new(l, vec![0u8; 24]).unwrap();
305 assert_eq!(vb.vertex_count(), 2);
306 }
307
308 #[test]
309 fn vertex_buffer_stride_mismatch_error() {
310 let mut l = VertexLayout::new();
311 l.add("POS", VertexAttribute::Float32x3);
312 let err = VertexBuffer::new(l, vec![0u8; 13]).unwrap_err();
313 matches!(err, VertexBufferError::StrideMismatch { .. });
314 }
315
316 #[test]
317 fn vertex_buffer_empty_layout_error() {
318 let l = VertexLayout::new();
319 let err = VertexBuffer::new(l, vec![]).unwrap_err();
320 assert_eq!(err, VertexBufferError::Empty);
321 }
322
323 #[test]
324 fn vertex_buffer_vertex_bytes_valid() {
325 let mut l = VertexLayout::new();
326 l.add("POS", VertexAttribute::Uint32);
327 let data: Vec<u8> = (0u8..8).collect();
328 let vb = VertexBuffer::new(l, data.clone()).unwrap();
329 assert_eq!(vb.vertex_bytes(0), Some(&data[0..4]));
330 assert_eq!(vb.vertex_bytes(1), Some(&data[4..8]));
331 assert!(vb.vertex_bytes(2).is_none());
332 }
333
334 #[test]
335 fn vertex_buffer_byte_len() {
336 let mut l = VertexLayout::new();
337 l.add("POS", VertexAttribute::Float32x4);
338 let vb = VertexBuffer::new(l, vec![0u8; 32]).unwrap();
339 assert_eq!(vb.byte_len(), 32);
340 }
341}