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 self.data.len().checked_div(s).unwrap_or(0)
207 }
208
209 #[must_use]
211 pub fn byte_len(&self) -> usize {
212 self.data.len()
213 }
214
215 #[must_use]
217 pub fn as_bytes(&self) -> &[u8] {
218 &self.data
219 }
220
221 #[must_use]
223 pub fn layout(&self) -> &VertexLayout {
224 &self.layout
225 }
226
227 #[must_use]
229 pub fn vertex_bytes(&self, index: usize) -> Option<&[u8]> {
230 let s = self.stride();
231 let start = index * s;
232 let end = start + s;
233 self.data.get(start..end)
234 }
235}
236
237#[cfg(test)]
238mod tests {
239 use super::*;
240
241 #[test]
242 fn attribute_float32_size() {
243 assert_eq!(VertexAttribute::Float32.byte_size(), 4);
244 }
245
246 #[test]
247 fn attribute_float32x3_size() {
248 assert_eq!(VertexAttribute::Float32x3.byte_size(), 12);
249 }
250
251 #[test]
252 fn attribute_unorm8x4_size() {
253 assert_eq!(VertexAttribute::Unorm8x4.byte_size(), 4);
254 }
255
256 #[test]
257 fn attribute_component_counts() {
258 assert_eq!(VertexAttribute::Float32.component_count(), 1);
259 assert_eq!(VertexAttribute::Float32x2.component_count(), 2);
260 assert_eq!(VertexAttribute::Float32x3.component_count(), 3);
261 assert_eq!(VertexAttribute::Float32x4.component_count(), 4);
262 }
263
264 #[test]
265 fn layout_stride_single_attr() {
266 let mut l = VertexLayout::new();
267 l.add("POS", VertexAttribute::Float32x3);
268 assert_eq!(l.stride(), 12);
269 }
270
271 #[test]
272 fn layout_stride_multiple_attrs() {
273 let mut l = VertexLayout::new();
274 l.add("POS", VertexAttribute::Float32x3);
275 l.add("UV", VertexAttribute::Float32x2);
276 assert_eq!(l.stride(), 20);
277 }
278
279 #[test]
280 fn layout_offsets_are_cumulative() {
281 let mut l = VertexLayout::new();
282 l.add("POS", VertexAttribute::Float32x3);
283 l.add("UV", VertexAttribute::Float32x2);
284 assert_eq!(l.slots()[0].offset, 0);
285 assert_eq!(l.slots()[1].offset, 12);
286 }
287
288 #[test]
289 fn layout_slot_by_name() {
290 let mut l = VertexLayout::new();
291 l.add("NORMAL", VertexAttribute::Float32x3);
292 assert!(l.slot_by_name("NORMAL").is_some());
293 assert!(l.slot_by_name("UV").is_none());
294 }
295
296 #[test]
297 fn vertex_buffer_create_ok() {
298 let mut l = VertexLayout::new();
299 l.add("POS", VertexAttribute::Float32x3);
300 let vb =
301 VertexBuffer::new(l, vec![0u8; 24]).expect("vertex buffer creation should succeed");
302 assert_eq!(vb.vertex_count(), 2);
303 }
304
305 #[test]
306 fn vertex_buffer_stride_mismatch_error() {
307 let mut l = VertexLayout::new();
308 l.add("POS", VertexAttribute::Float32x3);
309 let err = VertexBuffer::new(l, vec![0u8; 13]).unwrap_err();
310 matches!(err, VertexBufferError::StrideMismatch { .. });
311 }
312
313 #[test]
314 fn vertex_buffer_empty_layout_error() {
315 let l = VertexLayout::new();
316 let err = VertexBuffer::new(l, vec![]).unwrap_err();
317 assert_eq!(err, VertexBufferError::Empty);
318 }
319
320 #[test]
321 fn vertex_buffer_vertex_bytes_valid() {
322 let mut l = VertexLayout::new();
323 l.add("POS", VertexAttribute::Uint32);
324 let data: Vec<u8> = (0u8..8).collect();
325 let vb = VertexBuffer::new(l, data.clone()).expect("vertex buffer creation should succeed");
326 assert_eq!(vb.vertex_bytes(0), Some(&data[0..4]));
327 assert_eq!(vb.vertex_bytes(1), Some(&data[4..8]));
328 assert!(vb.vertex_bytes(2).is_none());
329 }
330
331 #[test]
332 fn vertex_buffer_byte_len() {
333 let mut l = VertexLayout::new();
334 l.add("POS", VertexAttribute::Float32x4);
335 let vb =
336 VertexBuffer::new(l, vec![0u8; 32]).expect("vertex buffer creation should succeed");
337 assert_eq!(vb.byte_len(), 32);
338 }
339}