From c8eacf9c20af792829e35d489e34568b9ed31bfa Mon Sep 17 00:00:00 2001 From: Alex Parrill Date: Sun, 22 Dec 2019 12:11:44 -0500 Subject: [PATCH] Add methods and types for reading from `MeshBuilder` MeshBuilder is a convenient way of storing mesh data on the CPU, since it supports a wide variety of formats due to using arbitrary vertex attributes. However it does not have any way to read the data stored inside of it. This patch adds functionality for reading the mesh stored inside of the buffers: * `MeshBuilder::view_attr` finds an attribute in the stored buffers by name and returns a view into the buffer that reads that attribute. It can also be iterated over. * `MeshBuffer::iter_index` iterates over elements in the index buffer, or from 0..numvertices if there is no index buffer. * Various getters for `MeshBuffer` and `RawVertexBuffer` * `FromVertexBuffer` defines how to read a type from a vertex buffer and convert it from the vertex format its in (ex. normalizing and scaling integer values into floating-point). --- core/src/casts.rs | 34 ++- mesh/src/lib.rs | 3 +- mesh/src/mesh.rs | 400 +++++++++++++++++++++++- mesh/src/read.rs | 761 ++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 1188 insertions(+), 10 deletions(-) create mode 100644 mesh/src/read.rs diff --git a/core/src/casts.rs b/core/src/casts.rs index 11952b8a..c1691af5 100644 --- a/core/src/casts.rs +++ b/core/src/casts.rs @@ -1,5 +1,5 @@ //! Contains functions for casting -use std::{any::TypeId, borrow::Cow}; +use std::{any::TypeId, borrow::Cow, mem, slice}; /// Cast vec of some arbitrary type into vec of bytes. /// Can lead to UB if allocator changes. Use with caution. @@ -59,3 +59,35 @@ pub fn identical_cast(value: T) -> U { std::ptr::read(ptr as *mut U) } } + +/// Tries to cast a slice from one type to another. +/// +/// Can fail and return `None` if the from slice is not sized or aligned correctly. +/// +/// Safety +/// ====== +/// +/// Must be able to interpret `To` from the bytes in `From` safely (you could, for example, create invalid references). +pub unsafe fn cast_any_slice( + from: &[From], +) -> Option<&[To]> { + let from_size = mem::size_of::(); + let from_ptr = from.as_ptr(); + let from_len = from.len(); + let to_size = mem::size_of::(); + let to_align = mem::align_of::(); + if from_size == 0 || to_size == 0 { + // can't cast zero-sized types + return None; + } + if (from_len * from_size) % to_size != 0 { + // invalid size + return None; + } + if from_ptr.align_offset(to_align) != 0 { + // unaligned pointer + return None; + } + let to_len = (from_len * from_size) / to_size; + Some(slice::from_raw_parts(from_ptr as *const To, to_len)) +} diff --git a/mesh/src/lib.rs b/mesh/src/lib.rs index 0faa6555..27e22403 100644 --- a/mesh/src/lib.rs +++ b/mesh/src/lib.rs @@ -24,6 +24,7 @@ use rendy_resource as resource; mod format; mod mesh; +mod read; -pub use crate::{format::*, mesh::*}; +pub use crate::{format::*, mesh::*, read::*}; pub use rendy_core::types::vertex::*; diff --git a/mesh/src/mesh.rs b/mesh/src/mesh.rs index 9285c713..4c70407a 100644 --- a/mesh/src/mesh.rs +++ b/mesh/src/mesh.rs @@ -4,9 +4,10 @@ use crate::{ command::{EncoderCommon, Graphics, QueueId, RenderPassEncoder, Supports}, - core::cast_cow, + core::{cast_any_slice, cast_cow, hal::format::Format}, factory::{BufferState, Factory, UploadError}, memory::{Data, Upload, Write}, + read::FromVertexBuffer, resource::{Buffer, BufferInfo, Escape}, AsVertex, VertexFormat, }; @@ -76,6 +77,28 @@ impl<'a> From> for Indices<'a> { } } +impl<'a, 'b> From>> for Indices<'a> { + fn from(v: Option<&'a RawIndices<'b>>) -> Self { + let indices = match v { + Some(v) => v, + None => { + return Indices::None; + } + }; + + match indices.index_type { + rendy_core::hal::IndexType::U16 => { + let slice = unsafe { cast_any_slice(&indices.indices) }.unwrap(); + Indices::U16(slice.into()) + } + rendy_core::hal::IndexType::U32 => { + let slice = unsafe { cast_any_slice(&indices.indices) }.unwrap(); + Indices::U32(slice.into()) + } + } + } +} + /// Generics-free mesh builder. #[derive(Clone, Debug)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] @@ -87,13 +110,51 @@ pub struct MeshBuilder<'a> { prim: rendy_core::hal::pso::Primitive, } +/// Buffer of vertex data #[derive(Clone, Debug)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] -struct RawVertices<'a> { +pub struct RawVertices<'a> { #[cfg_attr(feature = "serde", serde(with = "serde_bytes", borrow))] vertices: Cow<'a, [u8]>, format: VertexFormat, } +impl<'a> RawVertices<'a> { + /// Gets the vertex format of this data + pub fn format(&self) -> &VertexFormat { + &self.format + } + + /// Gets the buffer as a raw byte slice + pub fn bytes(&self) -> &[u8] { + &self.vertices + } + + /// Reads data for an attribute. + /// + /// Returns a view into this buffer that reads the attribute + pub fn view_attr( + &self, + attr_index: usize, + ) -> Option> { + let attr = self.format.attributes.get(attr_index)?; + let format = attr.element().format; + if !Fmt::is_format_compatible(format) { + return None; + } + + let offset = attr.element().offset as usize; + let fmt_size = (format.surface_desc().bits / 8) as usize; + + Some(AttributeView { + buf: &self.vertices, + offset, + stride: self.format.stride as usize, + format, + fmt_size, + _ph: Default::default(), + }) + } +} #[derive(Clone, Debug)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] @@ -102,6 +163,15 @@ struct RawIndices<'a> { indices: Cow<'a, [u8]>, index_type: rendy_core::hal::IndexType, } +impl<'a> RawIndices<'a> { + /// Number of vertices in this index buffer. + /// + /// Should not exceed the capacity of a u32, but returns a usize for compatibility with + /// other slice-related operations. + pub fn len(&self) -> usize { + self.indices.len() / index_stride(self.index_type) + } +} fn index_stride(index_type: rendy_core::hal::IndexType) -> usize { match index_type { @@ -207,6 +277,80 @@ impl<'a> MeshBuilder<'a> { self } + /// Gets the buffers in this builder + pub fn buffers(&self) -> &[RawVertices] { + &self.vertices + } + + /// Gets the primitive type + pub fn prim_type(&self) -> rendy_core::hal::pso::Primitive { + self.prim + } + + /// Finds an attribute in the mesh and iterates over each vertex. + /// + /// Returns None if the attribute cannot be found, or the `Fmt` type parameter does not + /// match. + /// + /// See the documentation for `FromVertexBuffer` for supported output formats and types. + pub fn view_attr(&self, name: &str) -> Option> { + let (buf, attr_i, _) = self + .vertices + .iter() + .flat_map(|buf| { + buf.format + .attributes + .iter() + .enumerate() + .map(move |(attr_i, attr)| (buf, attr_i, attr)) + }) + .find(|(_, _, attr)| attr.name() == name)?; + buf.view_attr(attr_i) + } + + /// Number of vertices that this mesh builder stores. + pub fn num_vertices_stored(&self) -> usize { + self.vertices + .iter() + .map(|v| v.vertices.len() / v.format.stride as usize) + .min() + .unwrap_or(0) + } + + /// Number of vertices that this mesh builder draws. + /// + /// If there is no index buffer, this is the same as `num_vertices_stored`, otherwise + /// it is the number of elements in the index buffer. + pub fn num_vertices_drawn(&self) -> usize { + if let Some(ref index) = self.indices { + index.len() + } else { + self.num_vertices_stored() + } + } + + /// Gets the index buffer (if any). + /// + /// The `Cow` objects in the resulting `Indices` object will always + /// be borrowed, + pub fn indices(&self) -> Indices<'_> { + self.indices.as_ref().into() + } + + /// Iterates over the index buffer indices, or from 0 to `num_vertices_stored` if there is + /// no index buffer. + pub fn iter_indices(&self) -> IndicesIter<'_> { + let src = match self.indices() { + Indices::None => IndicesIterSrc::NoIndices(0..self.num_vertices_stored()), + Indices::U16(Cow::Borrowed(slice)) => IndicesIterSrc::U16(slice.iter()), + Indices::U32(Cow::Borrowed(slice)) => IndicesIterSrc::U32(slice.iter()), + _ => { + unreachable!(); + } + }; + IndicesIter(src) + } + /// Builds and returns the new mesh. /// /// A mesh expects all vertex buffers to have the same number of elements. @@ -219,12 +363,7 @@ impl<'a> MeshBuilder<'a> { B: rendy_core::hal::Backend, { let align = factory.physical().limits().non_coherent_atom_size; - let mut len = self - .vertices - .iter() - .map(|v| v.vertices.len() as u32 / v.format.stride) - .min() - .unwrap_or(0); + let mut len = self.num_vertices_stored() as u32; let buffer_size = self .vertices @@ -342,6 +481,121 @@ impl<'a> MeshBuilder<'a> { } } +/// View into a buffer that reads a certain attribute +#[derive(Debug, Clone)] +pub struct AttributeView<'a, Fmt: FromVertexBuffer> { + buf: &'a [u8], + offset: usize, + stride: usize, + format: Format, + fmt_size: usize, + _ph: std::marker::PhantomData Fmt + Send + Sync>, +} +impl<'a, Fmt: FromVertexBuffer> AttributeView<'a, Fmt> { + /// Number of elements in this buffer + pub fn len(&self) -> usize { + self.buf.len() / self.stride + } + + /// Gets the n'th value in the buffer for the attribute. + /// + /// Returns `None` if the index is out of range + pub fn get(&self, index: usize) -> Option { + if index >= self.len() { + return None; + } + Some(self.get_unwrap(index)) + } + + /// Gets the n'th value in the buffer for this attribute. + /// + /// Panic + /// ===== + /// + /// Panics if the index is out of range. + pub fn get_unwrap(&self, index: usize) -> Fmt { + let raw_index = index * self.stride + self.offset; + let section = &self.buf[raw_index..raw_index + self.fmt_size]; + Fmt::read_one(self.format, section) + } + + /// Iterates over all attributes in the buffer + pub fn iter(&self) -> AttributeIter<'_, 'a, Fmt> { + AttributeIter { + view: self, + range: 0..self.len(), + } + } +} + +/// Iterator over a specific attribute +#[derive(Debug, Clone)] +pub struct AttributeIter<'a, 'b, Fmt: FromVertexBuffer> { + view: &'a AttributeView<'b, Fmt>, + range: std::ops::Range, +} +impl<'a, 'b, Fmt: FromVertexBuffer> Iterator for AttributeIter<'a, 'b, Fmt> { + type Item = Fmt; + fn next(&mut self) -> Option { + self.range.next().map(|i| self.view.get_unwrap(i)) + } + + fn size_hint(&self) -> (usize, Option) { + self.range.size_hint() + } +} +impl<'a, 'b, Fmt: FromVertexBuffer> ExactSizeIterator for AttributeIter<'a, 'b, Fmt> {} +impl<'a, 'b, Fmt: FromVertexBuffer> DoubleEndedIterator for AttributeIter<'a, 'b, Fmt> { + fn next_back(&mut self) -> Option { + self.range.next_back().map(|i| self.view.get_unwrap(i)) + } +} + +#[derive(Debug, Clone)] +enum IndicesIterSrc<'a> { + NoIndices(std::ops::Range), + U16(std::slice::Iter<'a, u16>), + U32(std::slice::Iter<'a, u32>), +} + +/// Iterates over an index buffer. +/// +/// If an index buffer was available, iterates over each index +/// converted to `usize` for easy indexing. +/// +/// If no index buffer was available, just iterates from 0 to the number +/// of vertices in the other buffers. +#[derive(Debug, Clone)] +pub struct IndicesIter<'a>(IndicesIterSrc<'a>); +impl<'a> Iterator for IndicesIter<'a> { + type Item = usize; + fn next(&mut self) -> Option { + match self.0 { + IndicesIterSrc::NoIndices(ref mut i) => i.next(), + IndicesIterSrc::U16(ref mut i) => i.next().map(|v| *v as usize), + IndicesIterSrc::U32(ref mut i) => i.next().map(|v| *v as usize), + } + } + + fn size_hint(&self) -> (usize, Option) { + match self.0 { + IndicesIterSrc::NoIndices(ref i) => i.size_hint(), + IndicesIterSrc::U16(ref i) => i.size_hint(), + IndicesIterSrc::U32(ref i) => i.size_hint(), + } + } +} +impl<'a> ExactSizeIterator for IndicesIter<'a> {} +impl<'a> DoubleEndedIterator for IndicesIter<'a> { + fn next_back(&mut self) -> Option { + match self.0 { + IndicesIterSrc::NoIndices(ref mut i) => i.next_back(), + IndicesIterSrc::U16(ref mut i) => i.next_back().map(|v| *v as usize), + IndicesIterSrc::U32(ref mut i) => i.next_back().map(|v| *v as usize), + } + } +} + fn align_by(align: usize, value: usize) -> usize { ((value + align - 1) / align) * align } @@ -575,3 +829,133 @@ macro_rules! impl_builder_from_vec { } impl_builder_from_vec!(A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P); + +#[cfg(test)] +mod tests { + use super::*; + use crate::core::types::vertex::{Normal, PosNorm, Position}; + + fn check_attr_view( + view: AttributeView, + expected: &[T], + ) { + assert_eq!(view.len(), expected.len()); + for (i, v) in expected.iter().enumerate() { + assert_eq!(view.get(i), Some(v.clone())); + assert_eq!(view.get_unwrap(i), v.clone()); + } + assert_eq!(view.get(expected.len()), None); + + let mut iter = view.iter(); + for (i, v) in expected.iter().enumerate() { + assert_eq!(iter.len(), expected.len() - i); + assert_eq!(iter.next(), Some(v.clone())); + } + assert_eq!(iter.len(), 0); + assert_eq!(iter.next(), None); + } + + #[test] + fn attr_view_separate_buffers() { + let mut mesh = MeshBuilder::new(); + mesh.add_vertices(vec![ + Position([1., 0., 0.]), + Position([0., 1., 0.]), + Position([0., 0., 1.]), + ]); + mesh.add_vertices(vec![ + Normal([0., 1., 1.]), + Normal([1., 0., 1.]), + Normal([1., 1., 0.]), + ]); + + check_attr_view( + mesh.view_attr::<[f32; 3]>("position") + .expect("did not find position"), + &vec![[1., 0., 0.], [0., 1., 0.], [0., 0., 1.]], + ); + check_attr_view( + mesh.view_attr::<[f32; 3]>("normal") + .expect("did not find position"), + &vec![[0., 1., 1.], [1., 0., 1.], [1., 1., 0.]], + ); + assert!(mesh.view_attr::<[f32; 3]>("idontexist").is_none()); + assert!(mesh.view_attr::("position").is_none()); + } + + #[test] + fn attr_view_interleaved_buffer() { + let mut mesh = MeshBuilder::new(); + mesh.add_vertices(vec![ + PosNorm { + position: Position([1., 0., 0.]), + normal: Normal([0., 1., 1.]), + }, + PosNorm { + position: Position([0., 1., 0.]), + normal: Normal([1., 0., 1.]), + }, + PosNorm { + position: Position([0., 0., 1.]), + normal: Normal([1., 1., 0.]), + }, + ]); + + check_attr_view( + mesh.view_attr::<[f32; 3]>("position") + .expect("did not find position"), + &vec![[1., 0., 0.], [0., 1., 0.], [0., 0., 1.]], + ); + check_attr_view( + mesh.view_attr::<[f32; 3]>("normal") + .expect("did not find position"), + &vec![[0., 1., 1.], [1., 0., 1.], [1., 1., 0.]], + ); + assert!(mesh.view_attr::<[f32; 3]>("idontexist").is_none()); + assert!(mesh.view_attr::("position").is_none()); + } + + #[test] + fn index_iter_no_indices() { + let mut mesh = MeshBuilder::new(); + mesh.add_vertices(vec![ + Position([1., 0., 0.]), + Position([0., 1., 0.]), + Position([0., 0., 1.]), + ]); + assert_eq!(mesh.num_vertices_stored(), 3); + assert_eq!(mesh.num_vertices_drawn(), 3); + + assert!(mesh.iter_indices().eq(vec![0, 1, 2].into_iter())); + } + + #[test] + fn index_iter_u16_indices() { + let mut mesh = MeshBuilder::new(); + mesh.add_vertices(vec![ + Position([1., 0., 0.]), + Position([0., 1., 0.]), + Position([0., 0., 1.]), + ]); + mesh.set_indices(vec![2u16, 3, 1, 1, 2, 3]); + assert_eq!(mesh.num_vertices_stored(), 3); + assert_eq!(mesh.num_vertices_drawn(), 6); + + assert!(mesh.iter_indices().eq(vec![2, 3, 1, 1, 2, 3].into_iter())); + } + + #[test] + fn index_iter_u32_indices() { + let mut mesh = MeshBuilder::new(); + mesh.add_vertices(vec![ + Position([1., 0., 0.]), + Position([0., 1., 0.]), + Position([0., 0., 1.]), + ]); + mesh.set_indices(vec![2u32, 3, 1, 1, 2, 3]); + assert_eq!(mesh.num_vertices_stored(), 3); + assert_eq!(mesh.num_vertices_drawn(), 6); + + assert!(mesh.iter_indices().eq(vec![2, 3, 1, 1, 2, 3].into_iter())); + } +} diff --git a/mesh/src/read.rs b/mesh/src/read.rs new file mode 100644 index 00000000..3fc1e70f --- /dev/null +++ b/mesh/src/read.rs @@ -0,0 +1,761 @@ +use crate::core::hal::format::Format; + +/// Type that can be parsed from a vertex buffer. +/// +/// This module provides readers for: +/// * Integer types and fixed-sizes arrays of length `1-4`. Bitness, number of components, and sign must match. +/// `*norm` and `*scale` formats are not changed. SRGB values can be read as unsigned integers. +/// * `f32/64` and fixed-sizes arrays of length `1-4`: Number of components must match. `*norm` and `*scale` formats +/// are converted. +/// * 2-tuples, 3-tuples, and 4-tuples for all types that implement this trait +/// for size 2, 3, and 4 arrays. +/// +/// For the above, BGR, BGRA, and ARGB formats are rearranged into RGB(A). +pub trait FromVertexBuffer { + /// Can this type be parsed from a buffer containing this format? + fn is_format_compatible(format: Format) -> bool; + /// Parses a section of a vertex buffer as the supplied format and + /// returns the result. + /// + /// Panics + /// ====== + /// + /// Can panic if the format is not supported (`is_format_compatible(format)` would return false), + /// or the buffer is not sized according to the format. + fn read_one(format: Format, section: &[u8]) -> Self; +} + +/// Helper trait for reading components and converting them from `norm` and `scaled` formats. +trait ReadComponent { + const SLICE_SIZE: usize; + fn read_one_raw(buf: &[u8]) -> Self; + fn normalize32(&self) -> f32; + fn normalize64(&self) -> f64; + fn scale32(&self) -> f32; + fn scale64(&self) -> f64; +} + +macro_rules! impl_normalize_and_scale { + (unsigned) => { + fn normalize32(&self) -> f32 { *self as f32 / Self::max_value() as f32 } + fn normalize64(&self) -> f64 { *self as f64 / Self::max_value() as f64 } + fn scale32(&self) -> f32 { *self as f32 } + fn scale64(&self) -> f64 { *self as f64 } + }; + (signed) => { + fn normalize32(&self) -> f32 { (*self as f32 / Self::max_value() as f32).max(-1.0) } + fn normalize64(&self) -> f64 { (*self as f64 / Self::max_value() as f64).max(-1.0) } + fn scale32(&self) -> f32 { *self as f32 } + fn scale64(&self) -> f64 { *self as f64 } + }; +} + +impl ReadComponent for u8 { + const SLICE_SIZE: usize = 1; + fn read_one_raw(buf: &[u8]) -> Self { + buf[0] + } + impl_normalize_and_scale!(unsigned); +} +impl ReadComponent for i8 { + const SLICE_SIZE: usize = 1; + fn read_one_raw(buf: &[u8]) -> Self { + buf[0] as i8 + } + impl_normalize_and_scale!(signed); +} +impl ReadComponent for u16 { + const SLICE_SIZE: usize = 2; + fn read_one_raw(buf: &[u8]) -> Self { + Self::from_ne_bytes([buf[0], buf[1]]) + } + impl_normalize_and_scale!(unsigned); +} +impl ReadComponent for i16 { + const SLICE_SIZE: usize = 2; + fn read_one_raw(buf: &[u8]) -> Self { + Self::from_ne_bytes([buf[0], buf[1]]) + } + impl_normalize_and_scale!(signed); +} +impl ReadComponent for u32 { + const SLICE_SIZE: usize = 4; + fn read_one_raw(buf: &[u8]) -> Self { + Self::from_ne_bytes([buf[0], buf[1], buf[2], buf[3]]) + } + impl_normalize_and_scale!(unsigned); +} +impl ReadComponent for i32 { + const SLICE_SIZE: usize = 4; + fn read_one_raw(buf: &[u8]) -> Self { + Self::from_ne_bytes([buf[0], buf[1], buf[2], buf[3]]) + } + impl_normalize_and_scale!(signed); +} +impl ReadComponent for u64 { + const SLICE_SIZE: usize = 8; + fn read_one_raw(buf: &[u8]) -> Self { + Self::from_ne_bytes([ + buf[0], buf[1], buf[2], buf[3], buf[4], buf[5], buf[6], buf[7], + ]) + } + impl_normalize_and_scale!(unsigned); +} +impl ReadComponent for i64 { + const SLICE_SIZE: usize = 8; + fn read_one_raw(buf: &[u8]) -> Self { + Self::from_ne_bytes([ + buf[0], buf[1], buf[2], buf[3], buf[4], buf[5], buf[6], buf[7], + ]) + } + impl_normalize_and_scale!(signed); +} +impl ReadComponent for f32 { + const SLICE_SIZE: usize = 4; + fn read_one_raw(buf: &[u8]) -> Self { + f32::from_bits(u32::from_ne_bytes([buf[0], buf[1], buf[2], buf[3]])) + } + fn normalize32(&self) -> f32 { + *self + } + fn normalize64(&self) -> f64 { + *self as f64 + } + fn scale32(&self) -> f32 { + *self + } + fn scale64(&self) -> f64 { + *self as f64 + } +} +impl ReadComponent for f64 { + const SLICE_SIZE: usize = 8; + fn read_one_raw(buf: &[u8]) -> Self { + f64::from_bits(u64::from_ne_bytes([ + buf[0], buf[1], buf[2], buf[3], buf[4], buf[5], buf[6], buf[7], + ])) + } + fn normalize32(&self) -> f32 { + *self as f32 + } + fn normalize64(&self) -> f64 { + *self + } + fn scale32(&self) -> f32 { + *self as f32 + } + fn scale64(&self) -> f64 { + *self + } +} + +// Helper trait for using the appropriate `normalize`/`scale` functions +trait FromComponent { + fn from_normalized(v: T) -> Self; + fn from_scaled(v: T) -> Self; +} +impl FromComponent for f32 { + fn from_normalized(v: T) -> Self { + v.normalize32() + } + fn from_scaled(v: T) -> Self { + v.scale32() + } +} +impl FromComponent for f64 { + fn from_normalized(v: T) -> Self { + v.normalize64() + } + fn from_scaled(v: T) -> Self { + v.scale64() + } +} + +// Helper trait for casting f32 to f64 and vice versa +trait Cast { + fn cast(self) -> To; +} +impl Cast for T { + fn cast(self) -> T { + self + } +} +impl Cast for f64 { + fn cast(self) -> f32 { + self as f32 + } +} +impl Cast for f32 { + fn cast(self) -> f64 { + self as f64 + } +} + +fn iter_components<'a, T: ReadComponent>(slice: &'a [u8]) -> impl Iterator + 'a { + assert_eq!(slice.len() % T::SLICE_SIZE, 0); + (0..slice.len() / T::SLICE_SIZE) + .map(move |i| T::read_one_raw(&slice[i * T::SLICE_SIZE..(i + 1) * T::SLICE_SIZE])) +} + +trait CollectFixed { + fn collect_fixed(self) -> Out; +} +impl CollectFixed<[Iter::Item; 1]> for Iter { + fn collect_fixed(mut self) -> [Iter::Item; 1] { + let v = self.next().unwrap(); + assert!(self.next().is_none()); + [v] + } +} +impl CollectFixed<[Iter::Item; 2]> for Iter { + fn collect_fixed(mut self) -> [Iter::Item; 2] { + let v1 = self.next().unwrap(); + let v2 = self.next().unwrap(); + assert!(self.next().is_none()); + [v1, v2] + } +} +impl CollectFixed<[Iter::Item; 3]> for Iter { + fn collect_fixed(mut self) -> [Iter::Item; 3] { + let v1 = self.next().unwrap(); + let v2 = self.next().unwrap(); + let v3 = self.next().unwrap(); + assert!(self.next().is_none()); + [v1, v2, v3] + } +} +impl CollectFixed<[Iter::Item; 4]> for Iter { + fn collect_fixed(mut self) -> [Iter::Item; 4] { + let v1 = self.next().unwrap(); + let v2 = self.next().unwrap(); + let v3 = self.next().unwrap(); + let v4 = self.next().unwrap(); + assert!(self.next().is_none()); + [v1, v2, v3, v4] + } +} + +macro_rules! impl_from_vertex_buffer { + (|$slice:ident| {$( + $fmt1:pat $( | $fmtn:pat)* => $e:expr + ),+$(,)?}) => { + fn is_format_compatible(format: Format) -> bool { + match format { + $($fmt1 $( | $fmtn)* => true),+, + _ => false, + } + } + + fn read_one(format: Format, $slice: &[u8]) -> Self { + match format { + $($fmt1 $( | $fmtn)* => $e ),* + _ => { panic!("Can not read format {:?} as Self"); } + } + } + }; +} + +// Some helpers +fn ident(v: T) -> T { + v +} +fn bgr2rgb(v: [T; 3]) -> [T; 3] { + [v[2], v[1], v[0]] +} +fn bgra2rgba(v: [T; 4]) -> [T; 4] { + [v[2], v[1], v[0], v[3]] +} +fn abgr2rgba(v: [T; 4]) -> [T; 4] { + [v[3], v[2], v[1], v[0]] +} + +macro_rules! cpxf { + ($slice:expr, $in_typ:ty, scaled) => { + cpxf!($slice, $in_typ, FromComponent::from_scaled) + }; + ($slice:expr, $in_typ:ty, normalized) => { + cpxf!($slice, $in_typ, FromComponent::from_normalized) + }; + ($slice:expr, $in_typ:ty, $comp_xform:expr) => { + iter_components::<$in_typ>($slice) + .map($comp_xform) + .collect_fixed() + }; +} + +// TODO: clean up the copy-paste code below. +// +// The biggest issue is that we need to concatenate the number of components (R/Rg/Rgb/Rgba), the +// type width (8/16/32/64), the signedness (S/U), and the storage interpretation (norm/scaled/int/float) +// into an identifier (ex. R8Unorm). This currently is impossible, since `concat_idents` is nightly only, +// and procedural macros (ex. the paste crate) cannot be used in match patters as of this writing. + +impl FromVertexBuffer for [u8; 1] { + impl_from_vertex_buffer!(|slice| { + Format::R8Unorm | + Format::R8Uscaled | + Format::R8Uint | + Format::R8Srgb => { cpxf!(slice, u8, ident) }, + }); +} +impl FromVertexBuffer for [u8; 2] { + impl_from_vertex_buffer!(|slice| { + Format::Rg8Unorm | + Format::Rg8Uscaled | + Format::Rg8Uint | + Format::Rg8Srgb => { cpxf!(slice, u8, ident) }, + }); +} +impl FromVertexBuffer for [u8; 3] { + impl_from_vertex_buffer!(|slice| { + Format::Rgb8Unorm | + Format::Rgb8Uscaled | + Format::Rgb8Uint | + Format::Rgb8Srgb => { cpxf!(slice, u8, ident) }, + + Format::Bgr8Unorm | + Format::Bgr8Uscaled | + Format::Bgr8Uint | + Format::Bgr8Srgb => { bgr2rgb(cpxf!(slice, u8, ident)) }, + }); +} +impl FromVertexBuffer for [u8; 4] { + impl_from_vertex_buffer!(|slice| { + Format::Rgba8Unorm | + Format::Rgba8Uscaled | + Format::Rgba8Uint | + Format::Rgba8Srgb => { cpxf!(slice, u8, ident) }, + + Format::Bgra8Unorm | + Format::Bgra8Uscaled | + Format::Bgra8Uint | + Format::Bgra8Srgb => { bgra2rgba(cpxf!(slice, u8, ident)) }, + + Format::Abgr8Unorm | + Format::Abgr8Uscaled | + Format::Abgr8Uint | + Format::Abgr8Srgb => { abgr2rgba(cpxf!(slice, u8, ident)) }, + }); +} +impl FromVertexBuffer for u8 { + fn is_format_compatible(format: Format) -> bool { + <[u8; 1]>::is_format_compatible(format) + } + fn read_one(format: Format, section: &[u8]) -> Self { + <[u8; 1]>::read_one(format, section)[0] + } +} + +impl FromVertexBuffer for [u16; 1] { + impl_from_vertex_buffer!(|slice| { + Format::R16Unorm | + Format::R16Uscaled | + Format::R16Uint => { cpxf!(slice, u16, ident) }, + }); +} +impl FromVertexBuffer for [u16; 2] { + impl_from_vertex_buffer!(|slice| { + Format::Rg16Unorm | + Format::Rg16Uscaled | + Format::Rg16Uint => { cpxf!(slice, u16, ident) }, + }); +} +impl FromVertexBuffer for [u16; 3] { + impl_from_vertex_buffer!(|slice| { + Format::Rgb16Unorm | + Format::Rgb16Uscaled | + Format::Rgb16Uint => { cpxf!(slice, u16, ident) }, + }); +} +impl FromVertexBuffer for [u16; 4] { + impl_from_vertex_buffer!(|slice| { + Format::Rgba16Unorm | + Format::Rgba16Uscaled | + Format::Rgba16Uint => { cpxf!(slice, u16, ident) }, + }); +} +impl FromVertexBuffer for u16 { + fn is_format_compatible(format: Format) -> bool { + <[u16; 1]>::is_format_compatible(format) + } + fn read_one(format: Format, section: &[u8]) -> Self { + <[u16; 1]>::read_one(format, section)[0] + } +} + +impl FromVertexBuffer for [u32; 1] { + impl_from_vertex_buffer!(|slice| { + Format::R32Uint => { cpxf!(slice, u32, ident) }, + }); +} +impl FromVertexBuffer for [u32; 2] { + impl_from_vertex_buffer!(|slice| { + Format::Rg32Uint => { cpxf!(slice, u32, ident) }, + }); +} +impl FromVertexBuffer for [u32; 3] { + impl_from_vertex_buffer!(|slice| { + Format::Rgb32Uint => { cpxf!(slice, u32, ident) }, + }); +} +impl FromVertexBuffer for [u32; 4] { + impl_from_vertex_buffer!(|slice| { + Format::Rgba32Uint => { cpxf!(slice, u32, ident) }, + }); +} +impl FromVertexBuffer for u32 { + fn is_format_compatible(format: Format) -> bool { + <[u32; 1]>::is_format_compatible(format) + } + fn read_one(format: Format, section: &[u8]) -> Self { + <[u32; 1]>::read_one(format, section)[0] + } +} + +impl FromVertexBuffer for [u64; 1] { + impl_from_vertex_buffer!(|slice| { + Format::R64Uint => { cpxf!(slice, u64, ident) }, + }); +} +impl FromVertexBuffer for [u64; 2] { + impl_from_vertex_buffer!(|slice| { + Format::Rg64Uint => { cpxf!(slice, u64, ident) }, + }); +} +impl FromVertexBuffer for [u64; 3] { + impl_from_vertex_buffer!(|slice| { + Format::Rgb64Uint => { cpxf!(slice, u64, ident) }, + }); +} +impl FromVertexBuffer for [u64; 4] { + impl_from_vertex_buffer!(|slice| { + Format::Rgba64Uint => { cpxf!(slice, u64, ident) }, + }); +} +impl FromVertexBuffer for u64 { + fn is_format_compatible(format: Format) -> bool { + <[u64; 1]>::is_format_compatible(format) + } + fn read_one(format: Format, section: &[u8]) -> Self { + <[u64; 1]>::read_one(format, section)[0] + } +} + +impl FromVertexBuffer for [i8; 1] { + impl_from_vertex_buffer!(|slice| { + Format::R8Snorm | + Format::R8Sscaled | + Format::R8Sint => { cpxf!(slice, i8, ident) }, + }); +} +impl FromVertexBuffer for [i8; 2] { + impl_from_vertex_buffer!(|slice| { + Format::Rg8Snorm | + Format::Rg8Sscaled | + Format::Rg8Sint => { cpxf!(slice, i8, ident) }, + }); +} +impl FromVertexBuffer for [i8; 3] { + impl_from_vertex_buffer!(|slice| { + Format::Rgb8Snorm | + Format::Rgb8Sscaled | + Format::Rgb8Sint => { cpxf!(slice, i8, ident) }, + + Format::Bgr8Snorm | + Format::Bgr8Sscaled | + Format::Bgr8Sint => { bgr2rgb(cpxf!(slice, i8, ident)) }, + }); +} +impl FromVertexBuffer for [i8; 4] { + impl_from_vertex_buffer!(|slice| { + Format::Rgba8Snorm | + Format::Rgba8Sscaled | + Format::Rgba8Sint => { cpxf!(slice, i8, ident) }, + + Format::Bgra8Snorm | + Format::Bgra8Sscaled | + Format::Bgra8Sint => { bgra2rgba(cpxf!(slice, i8, ident)) }, + + Format::Abgr8Snorm | + Format::Abgr8Sscaled | + Format::Abgr8Sint => { abgr2rgba(cpxf!(slice, i8, ident)) }, + }); +} +impl FromVertexBuffer for i8 { + fn is_format_compatible(format: Format) -> bool { + <[i8; 1]>::is_format_compatible(format) + } + fn read_one(format: Format, section: &[u8]) -> Self { + <[i8; 1]>::read_one(format, section)[0] + } +} + +impl FromVertexBuffer for [i16; 1] { + impl_from_vertex_buffer!(|slice| { + Format::R16Snorm | + Format::R16Sscaled | + Format::R16Sint => { cpxf!(slice, i16, ident) }, + }); +} +impl FromVertexBuffer for [i16; 2] { + impl_from_vertex_buffer!(|slice| { + Format::Rg16Snorm | + Format::Rg16Sscaled | + Format::Rg16Sint => { cpxf!(slice, i16, ident) }, + }); +} +impl FromVertexBuffer for [i16; 3] { + impl_from_vertex_buffer!(|slice| { + Format::Rgb16Snorm | + Format::Rgb16Sscaled | + Format::Rgb16Sint => { cpxf!(slice, i16, ident) }, + }); +} +impl FromVertexBuffer for [i16; 4] { + impl_from_vertex_buffer!(|slice| { + Format::Rgba16Snorm | + Format::Rgba16Sscaled | + Format::Rgba16Sint => { cpxf!(slice, i16, ident) }, + }); +} +impl FromVertexBuffer for i16 { + fn is_format_compatible(format: Format) -> bool { + <[i16; 1]>::is_format_compatible(format) + } + fn read_one(format: Format, section: &[u8]) -> Self { + <[i16; 1]>::read_one(format, section)[0] + } +} + +impl FromVertexBuffer for [i32; 1] { + impl_from_vertex_buffer!(|slice| { + Format::R32Sint => { cpxf!(slice, i32, ident) }, + }); +} +impl FromVertexBuffer for [i32; 2] { + impl_from_vertex_buffer!(|slice| { + Format::Rg32Sint => { cpxf!(slice, i32, ident) }, + }); +} +impl FromVertexBuffer for [i32; 3] { + impl_from_vertex_buffer!(|slice| { + Format::Rgb32Sint => { cpxf!(slice, i32, ident) }, + }); +} +impl FromVertexBuffer for [i32; 4] { + impl_from_vertex_buffer!(|slice| { + Format::Rgba32Sint => { cpxf!(slice, i32, ident) }, + }); +} +impl FromVertexBuffer for i32 { + fn is_format_compatible(format: Format) -> bool { + <[i32; 1]>::is_format_compatible(format) + } + fn read_one(format: Format, section: &[u8]) -> Self { + <[i32; 1]>::read_one(format, section)[0] + } +} + +impl FromVertexBuffer for [i64; 1] { + impl_from_vertex_buffer!(|slice| { + Format::R64Sint => { cpxf!(slice, i64, ident) }, + }); +} +impl FromVertexBuffer for [i64; 2] { + impl_from_vertex_buffer!(|slice| { + Format::Rg64Sint => { cpxf!(slice, i64, ident) }, + }); +} +impl FromVertexBuffer for [i64; 3] { + impl_from_vertex_buffer!(|slice| { + Format::Rgb64Sint => { cpxf!(slice, i64, ident) }, + }); +} +impl FromVertexBuffer for [i64; 4] { + impl_from_vertex_buffer!(|slice| { + Format::Rgba64Sint => { cpxf!(slice, i64, ident) }, + }); +} +impl FromVertexBuffer for i64 { + fn is_format_compatible(format: Format) -> bool { + <[i64; 1]>::is_format_compatible(format) + } + fn read_one(format: Format, section: &[u8]) -> Self { + <[i64; 1]>::read_one(format, section)[0] + } +} + +macro_rules! impl_for_both_floats { + ($n:tt, |$slice:ident| $content:tt) => { + impl FromVertexBuffer for [f32; $n] { + impl_from_vertex_buffer!(|$slice| $content); + } + impl FromVertexBuffer for [f64; $n] { + impl_from_vertex_buffer!(|$slice| $content); + } + }; +} + +impl_for_both_floats!(1, |slice| { + Format::R8Unorm => { cpxf!(slice, u8, normalized) }, + Format::R8Uscaled => { cpxf!(slice, u8, scaled) }, + Format::R8Snorm => { cpxf!(slice, i8, normalized) }, + Format::R8Sscaled => { cpxf!(slice, i8, scaled) }, + + Format::R16Unorm => { cpxf!(slice, u16, normalized) }, + Format::R16Uscaled => { cpxf!(slice, u16, scaled) }, + Format::R16Snorm => { cpxf!(slice, i16, normalized) }, + Format::R16Sscaled => { cpxf!(slice, i16, scaled) }, + + Format::R32Sfloat => { cpxf!(slice, f32, Cast::cast) }, + Format::R64Sfloat => { cpxf!(slice, f64, Cast::cast) }, +}); + +impl_for_both_floats!(2, |slice| { + Format::Rg8Unorm => { cpxf!(slice, u8, normalized) }, + Format::Rg8Uscaled => { cpxf!(slice, u8, scaled) }, + Format::Rg8Snorm => { cpxf!(slice, i8, normalized) }, + Format::Rg8Sscaled => { cpxf!(slice, i8, scaled) }, + + Format::Rg16Unorm => { cpxf!(slice, u16, normalized) }, + Format::Rg16Uscaled => { cpxf!(slice, u16, scaled) }, + Format::Rg16Snorm => { cpxf!(slice, i16, normalized) }, + Format::Rg16Sscaled => { cpxf!(slice, i16, scaled) }, + + Format::Rg32Sfloat => { cpxf!(slice, f32, Cast::cast) }, + Format::Rg64Sfloat => { cpxf!(slice, f64, Cast::cast) }, +}); + +impl_for_both_floats!(3, |slice| { + Format::Rgb8Unorm => { cpxf!(slice, u8, normalized) }, + Format::Rgb8Uscaled => { cpxf!(slice, u8, scaled) }, + Format::Rgb8Snorm => { cpxf!(slice, i8, normalized) }, + Format::Rgb8Sscaled => { cpxf!(slice, i8, scaled) }, + + Format::Bgr8Unorm => { bgr2rgb(cpxf!(slice, u8, normalized)) }, + Format::Bgr8Uscaled => { bgr2rgb(cpxf!(slice, u8, scaled)) }, + Format::Bgr8Snorm => { bgr2rgb(cpxf!(slice, i8, normalized)) }, + Format::Bgr8Sscaled => { bgr2rgb(cpxf!(slice, i8, scaled)) }, + + Format::Rgb16Unorm => { cpxf!(slice, u16, normalized) }, + Format::Rgb16Uscaled => { cpxf!(slice, u16, scaled) }, + Format::Rgb16Snorm => { cpxf!(slice, i16, normalized) }, + Format::Rgb16Sscaled => { cpxf!(slice, i16, scaled) }, + + Format::Rgb32Sfloat => { cpxf!(slice, f32, Cast::cast) }, + Format::Rgb64Sfloat => { cpxf!(slice, f64, Cast::cast) }, +}); + +impl_for_both_floats!(4, |slice| { + Format::Rgba8Unorm => { cpxf!(slice, u8, normalized) }, + Format::Rgba8Uscaled => { cpxf!(slice, u8, scaled) }, + Format::Rgba8Snorm => { cpxf!(slice, i8, normalized) }, + Format::Rgba8Sscaled => { cpxf!(slice, i8, scaled) }, + + Format::Bgra8Unorm => { bgra2rgba(cpxf!(slice, u8, normalized)) }, + Format::Bgra8Uscaled => { bgra2rgba(cpxf!(slice, u8, scaled)) }, + Format::Bgra8Snorm => { bgra2rgba(cpxf!(slice, i8, normalized)) }, + Format::Bgra8Sscaled => { bgra2rgba(cpxf!(slice, i8, scaled)) }, + + Format::Abgr8Unorm => { abgr2rgba(cpxf!(slice, u8, normalized)) }, + Format::Abgr8Uscaled => { abgr2rgba(cpxf!(slice, u8, scaled)) }, + Format::Abgr8Snorm => { abgr2rgba(cpxf!(slice, i8, normalized)) }, + Format::Abgr8Sscaled => { abgr2rgba(cpxf!(slice, i8, scaled)) }, + + Format::Rgba16Unorm => { cpxf!(slice, u16, normalized) }, + Format::Rgba16Uscaled => { cpxf!(slice, u16, scaled) }, + Format::Rgba16Snorm => { cpxf!(slice, i16, normalized) }, + Format::Rgba16Sscaled => { cpxf!(slice, i16, scaled) }, + + Format::Rgba32Sfloat => { cpxf!(slice, f32, Cast::cast) }, + Format::Rgba64Sfloat => { cpxf!(slice, f64, Cast::cast) }, +}); + +impl FromVertexBuffer for f32 { + fn is_format_compatible(format: Format) -> bool { + <[f32; 1]>::is_format_compatible(format) + } + fn read_one(format: Format, section: &[u8]) -> Self { + <[f32; 1]>::read_one(format, section)[0] + } +} + +impl FromVertexBuffer for f64 { + fn is_format_compatible(format: Format) -> bool { + <[f64; 1]>::is_format_compatible(format) + } + fn read_one(format: Format, section: &[u8]) -> Self { + <[f64; 1]>::read_one(format, section)[0] + } +} + +impl FromVertexBuffer for (T, T) +where + T: Clone, + [T; 2]: FromVertexBuffer, +{ + fn is_format_compatible(format: Format) -> bool { + <[T; 2]>::is_format_compatible(format) + } + fn read_one(format: Format, section: &[u8]) -> Self { + let arr = <[T; 2]>::read_one(format, section); + (arr[0].clone(), arr[1].clone()) + } +} +impl FromVertexBuffer for (T, T, T) +where + T: Clone, + [T; 3]: FromVertexBuffer, +{ + fn is_format_compatible(format: Format) -> bool { + <[T; 3]>::is_format_compatible(format) + } + fn read_one(format: Format, section: &[u8]) -> Self { + let arr = <[T; 3]>::read_one(format, section); + (arr[0].clone(), arr[1].clone(), arr[2].clone()) + } +} +impl FromVertexBuffer for (T, T, T, T) +where + T: Clone, + [T; 4]: FromVertexBuffer, +{ + fn is_format_compatible(format: Format) -> bool { + <[T; 4]>::is_format_compatible(format) + } + fn read_one(format: Format, section: &[u8]) -> Self { + let arr = <[T; 4]>::read_one(format, section); + ( + arr[0].clone(), + arr[1].clone(), + arr[2].clone(), + arr[3].clone(), + ) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + fn unpack_floats(d: &[f32]) -> Vec { + let mut vec = Vec::with_capacity(d.len() * 4); + for v in d.iter() { + let u = v.to_bits(); + let bytes = u.to_ne_bytes(); + vec.extend(bytes.iter()); + } + vec + } + + #[test] + fn read_rgb32sfloat() { + assert_eq!( + <[f32; 3] as FromVertexBuffer>::read_one( + Format::Rgb32Sfloat, + &unpack_floats(&[1., 2., 3.]) + ), + [1., 2., 3.] + ); + } +}