diff --git a/core/src/casts.rs b/core/src/casts.rs index 11952b8a..1e0b28fe 100644 --- a/core/src/casts.rs +++ b/core/src/casts.rs @@ -1,12 +1,16 @@ //! Contains functions for casting -use std::{any::TypeId, borrow::Cow}; +use std::{ + any::TypeId, + borrow::Cow, + mem::{align_of, size_of}, +}; /// Cast vec of some arbitrary type into vec of bytes. /// Can lead to UB if allocator changes. Use with caution. /// TODO: Replace with something safer. pub fn cast_vec(mut vec: Vec) -> Vec { - let len = std::mem::size_of::() * vec.len(); - let cap = std::mem::size_of::() * vec.capacity(); + let len = size_of::() * vec.len(); + let cap = size_of::() * vec.capacity(); let ptr = vec.as_mut_ptr(); std::mem::forget(vec); unsafe { Vec::from_raw_parts(ptr as _, len, cap) } @@ -14,11 +18,29 @@ pub fn cast_vec(mut vec: Vec) -> Vec { /// Cast slice of some arbitrary type into slice of bytes. pub fn cast_slice(slice: &[T]) -> &[u8] { - let len = std::mem::size_of::() * slice.len(); + let len = size_of::() * slice.len(); let ptr = slice.as_ptr(); unsafe { std::slice::from_raw_parts(ptr as _, len) } } +/// Cast slice of some arbitrary type into slice of bytes. +pub unsafe fn cast_arbitrary_slice(slice: &[T]) -> &[U] { + let bytes = size_of::() * slice.len(); + let u_s = bytes / size_of::(); + let ptr = slice.as_ptr(); + assert_eq!(ptr as usize % align_of::(), 0); + std::slice::from_raw_parts(ptr as _, u_s) +} + +/// Cast slice of some arbitrary type into slice of bytes. +pub unsafe fn cast_arbitrary_slice_mut(slice: &mut [T]) -> &mut [U] { + let bytes = size_of::() * slice.len(); + let u_s = bytes / size_of::(); + let ptr = slice.as_ptr(); + assert_eq!(ptr as usize % align_of::(), 0); + std::slice::from_raw_parts_mut(ptr as _, u_s) +} + /// Cast `cow` of some arbitrary type into `cow` of bytes. /// Can lead to UB if allocator changes. Use with caution. /// TODO: Replace with something safer. diff --git a/factory/src/factory.rs b/factory/src/factory.rs index b5233bdc..6ede6a77 100644 --- a/factory/src/factory.rs +++ b/factory/src/factory.rs @@ -28,7 +28,11 @@ use { HasRawWindowHandle, }, smallvec::SmallVec, - std::{borrow::BorrowMut, cmp::max, mem::ManuallyDrop}, + std::{ + borrow::BorrowMut, + cmp::max, + mem::{size_of_val, ManuallyDrop}, + }, thread_profiler::profile_scope, }; @@ -442,7 +446,7 @@ where /// Update content of the buffer bound to host visible memory. /// This function (unlike [`upload_buffer`]) update content immediatelly. /// - /// Buffers allocated from host-invisible memory types cannot be + /// Buffers allocated not from host-invisible memory types cannot be /// updated via this function. /// /// Updated content will be automatically made visible to device operations @@ -450,7 +454,7 @@ where /// /// # Panics /// - /// Panics if buffer size is less than `offset` + size of `content`. + /// Panics if buffer size is less than `offset + size_of_val(content)`. /// /// # Safety /// @@ -466,10 +470,8 @@ where where T: 'static + Copy, { - let content = std::slice::from_raw_parts( - content.as_ptr() as *const u8, - content.len() * std::mem::size_of::(), - ); + let content = + std::slice::from_raw_parts(content.as_ptr() as *const u8, size_of_val(content)); let mut mapped = buffer.map(&self.device, offset..offset + content.len() as u64)?; mapped @@ -512,7 +514,7 @@ where { assert!(buffer.info().usage.contains(buffer::Usage::TRANSFER_DST)); - let content_size = content.len() as u64 * std::mem::size_of::() as u64; + let content_size = size_of_val(content) as u64; let mut staging = self .create_buffer( BufferInfo { @@ -527,7 +529,18 @@ where .map_err(UploadError::Map)?; self.uploader - .upload_buffer(&self.device, buffer, offset, staging, last, next) + .upload_buffer( + &self.device, + buffer, + staging, + last, + next, + Some(rendy_core::hal::command::BufferCopy { + src: 0, + dst: offset, + size: content_size, + }), + ) .map_err(UploadError::Upload) } @@ -548,15 +561,15 @@ where pub unsafe fn upload_from_staging_buffer( &self, buffer: &Buffer, - offset: u64, staging: Escape>, last: Option, next: BufferState, + ranges: impl IntoIterator, ) -> Result<(), OutOfMemory> { assert!(buffer.info().usage.contains(buffer::Usage::TRANSFER_DST)); assert!(staging.info().usage.contains(buffer::Usage::TRANSFER_SRC)); self.uploader - .upload_buffer(&self.device, buffer, offset, staging, last, next) + .upload_buffer(&self.device, buffer, staging, last, next, ranges) } /// Update image layers content with provided data. diff --git a/factory/src/upload.rs b/factory/src/upload.rs index 7ff15f70..573a06ab 100644 --- a/factory/src/upload.rs +++ b/factory/src/upload.rs @@ -170,10 +170,10 @@ where &self, device: &Device, buffer: &Buffer, - offset: u64, staging: Escape>, last: Option, next: BufferState, + ranges: impl IntoIterator, ) -> Result<(), OutOfMemory> { let mut family_uploads = self.family_uploads[next.queue.family.index] .as_ref() @@ -195,15 +195,7 @@ where let next_upload = family_uploads.next_upload(device, next.queue.index)?; let mut encoder = next_upload.command_buffer.encoder(); - encoder.copy_buffer( - staging.raw(), - buffer.raw(), - Some(rendy_core::hal::command::BufferCopy { - src: 0, - dst: offset, - size: staging.size(), - }), - ); + encoder.copy_buffer(staging.raw(), buffer.raw(), ranges); next_upload.staging_buffers.push(staging); diff --git a/memory/src/mapping/mod.rs b/memory/src/mapping/mod.rs index c8071bcb..1ff2bead 100644 --- a/memory/src/mapping/mod.rs +++ b/memory/src/mapping/mod.rs @@ -4,7 +4,7 @@ pub(crate) mod write; use { crate::{memory::Memory, util::fits_usize}, gfx_hal::{device::Device as _, Backend}, - std::{ops::Range, ptr::NonNull}, + std::{mem::MaybeUninit, ops::Range, ptr::NonNull}, }; pub(crate) use self::range::{ @@ -118,12 +118,11 @@ where /// # Safety /// /// * Caller must ensure that device won't write to the memory region until the borrowing ends. - /// * `T` Must be plain-old-data type compatible with data in mapped region. pub unsafe fn read<'b, T>( &'b mut self, device: &B::Device, range: Range, - ) -> Result<&'b [T], gfx_hal::device::MapError> + ) -> Result<&'b [MaybeUninit], gfx_hal::device::MapError> where 'a: 'b, T: Copy, diff --git a/memory/src/mapping/range.rs b/memory/src/mapping/range.rs index f840cc75..f888375b 100644 --- a/memory/src/mapping/range.rs +++ b/memory/src/mapping/range.rs @@ -1,7 +1,7 @@ use { crate::util::fits_usize, std::{ - mem::{align_of, size_of}, + mem::{align_of, size_of, MaybeUninit}, ops::Range, ptr::NonNull, slice::{from_raw_parts, from_raw_parts_mut}, @@ -62,8 +62,10 @@ pub(crate) fn mapped_sub_range( /// User must ensure that: /// * this function won't create aliasing slices. /// * returned slice doesn't outlive mapping. -/// * `T` Must be plain-old-data type compatible with data in mapped region. -pub(crate) unsafe fn mapped_slice_mut<'a, T>(ptr: NonNull, size: usize) -> &'a mut [T] { +pub(crate) unsafe fn mapped_slice_mut<'a, T>( + ptr: NonNull, + size: usize, +) -> &'a mut [MaybeUninit] { assert_eq!( size % size_of::(), 0, @@ -76,15 +78,13 @@ pub(crate) unsafe fn mapped_slice_mut<'a, T>(ptr: NonNull, size: usize) -> & "Range offset must be multiple of element alignment" ); assert!(usize::max_value() - size >= ptr.as_ptr() as usize); - from_raw_parts_mut(ptr.as_ptr() as *mut T, size) + from_raw_parts_mut(ptr.as_ptr() as *mut MaybeUninit, size) } /// # Safety /// -/// User must ensure that: -/// * returned slice doesn't outlive mapping. -/// * `T` Must be plain-old-data type compatible with data in mapped region. -pub(crate) unsafe fn mapped_slice<'a, T>(ptr: NonNull, size: usize) -> &'a [T] { +/// User must ensure that returned slice doesn't outlive mapping. +pub(crate) unsafe fn mapped_slice<'a, T>(ptr: NonNull, size: usize) -> &'a [MaybeUninit] { assert_eq!( size % size_of::(), 0, @@ -97,5 +97,5 @@ pub(crate) unsafe fn mapped_slice<'a, T>(ptr: NonNull, size: usize) -> &'a [ "Range offset must be multiple of element alignment" ); assert!(usize::max_value() - size >= ptr.as_ptr() as usize); - from_raw_parts(ptr.as_ptr() as *const T, size) + from_raw_parts(ptr.as_ptr() as *const MaybeUninit, size) } diff --git a/memory/src/mapping/write.rs b/memory/src/mapping/write.rs index d067a612..c80fdd3d 100644 --- a/memory/src/mapping/write.rs +++ b/memory/src/mapping/write.rs @@ -1,4 +1,4 @@ -use std::ptr::copy_nonoverlapping; +use std::{mem::MaybeUninit, ptr::copy_nonoverlapping}; /// Trait for memory region suitable for host writes. pub trait Write { @@ -7,7 +7,7 @@ pub trait Write { /// # Safety /// /// * Returned slice should not be read. - unsafe fn slice(&mut self) -> &mut [T]; + unsafe fn slice(&mut self) -> &mut [MaybeUninit]; /// Write data into mapped memory sub-region. /// @@ -18,14 +18,13 @@ pub trait Write { unsafe { let slice = self.slice(); assert!(data.len() <= slice.len()); - copy_nonoverlapping(data.as_ptr(), slice.as_mut_ptr(), data.len()); + copy_nonoverlapping(data.as_ptr(), slice.as_mut_ptr() as *mut T, data.len()); } } } -#[derive(Debug)] pub(super) struct WriteFlush<'a, T, F: FnOnce() + 'a> { - pub(super) slice: &'a mut [T], + pub(super) slice: &'a mut [MaybeUninit], pub(super) flush: Option, } @@ -49,15 +48,13 @@ where /// # Safety /// /// [See doc comment for trait method](trait.Write#method.slice) - unsafe fn slice(&mut self) -> &mut [T] { + unsafe fn slice(&mut self) -> &mut [MaybeUninit] { self.slice } } -#[warn(dead_code)] -#[derive(Debug)] pub(super) struct WriteCoherent<'a, T> { - pub(super) slice: &'a mut [T], + pub(super) slice: &'a mut [MaybeUninit], } impl<'a, T> Write for WriteCoherent<'a, T> @@ -67,7 +64,7 @@ where /// # Safety /// /// [See doc comment for trait method](trait.Write#method.slice) - unsafe fn slice(&mut self) -> &mut [T] { + unsafe fn slice(&mut self) -> &mut [MaybeUninit] { self.slice } } diff --git a/mesh/src/mesh.rs b/mesh/src/builder.rs similarity index 51% rename from mesh/src/mesh.rs rename to mesh/src/builder.rs index 9285c713..2ec606d9 100644 --- a/mesh/src/mesh.rs +++ b/mesh/src/builder.rs @@ -1,31 +1,24 @@ -//! -//! Manage vertex and index buffers of single objects with ease. -//! - use crate::{ - command::{EncoderCommon, Graphics, QueueId, RenderPassEncoder, Supports}, - core::cast_cow, + align_by, + command::QueueId, + core::{ + cast_arbitrary_slice, cast_cow, cast_vec, + hal::{Backend, IndexType}, + }, + dynamic::{DynamicIndices, DynamicMesh, DynamicVertices}, factory::{BufferState, Factory, UploadError}, + index_stride, memory::{Data, Upload, Write}, - resource::{Buffer, BufferInfo, Escape}, + r#static::{IndexBuffer, Mesh, VertexBufferLayout}, + resource::BufferInfo, AsVertex, VertexFormat, }; use rendy_core::hal::adapter::PhysicalDevice; -use std::{borrow::Cow, mem::size_of}; - -/// Vertex buffer with it's format -#[derive(Debug)] -pub struct VertexBufferLayout { - offset: u64, - format: VertexFormat, -} - -/// Index buffer with it's type -#[derive(Debug)] -pub struct IndexBuffer { - buffer: Escape>, - index_type: rendy_core::hal::IndexType, -} +use std::{ + any::TypeId, + borrow::Cow, + mem::{align_of, MaybeUninit}, +}; /// Abstracts over two types of indices and their absence. #[derive(Debug)] @@ -91,22 +84,145 @@ pub struct MeshBuilder<'a> { #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] struct RawVertices<'a> { #[cfg_attr(feature = "serde", serde(with = "serde_bytes", borrow))] - vertices: Cow<'a, [u8]>, + bytes: Cow<'a, [u8]>, format: VertexFormat, + align: usize, + ty: TypeId, +} + +#[derive(Clone, Copy)] +#[repr(C, align(1))] +struct Aligned1(u8); +#[derive(Clone, Copy)] +#[repr(C, align(2))] +struct Aligned2(u8); +#[derive(Clone, Copy)] +#[repr(C, align(4))] +struct Aligned4(u8); +#[derive(Clone, Copy)] +#[repr(C, align(8))] +struct Aligned8(u8); +#[derive(Clone, Copy)] +#[repr(C, align(16))] +struct Aligned16(u8); +#[derive(Clone, Copy)] +#[repr(C, align(32))] +struct Aligned32(u8); +#[derive(Clone, Copy)] +#[repr(C, align(64))] +struct Aligned64(u8); +#[derive(Clone, Copy)] +#[repr(C, align(128))] +struct Aligned128(u8); +#[derive(Clone, Copy)] +#[repr(C, align(256))] +struct Aligned256(u8); +#[derive(Clone, Copy)] +#[repr(C, align(512))] +struct Aligned512(u8); + +impl RawVertices<'_> { + fn into_owned(self) -> RawVertices<'static> { + let bytes = match self.bytes { + Cow::Borrowed(bytes) => unsafe { + match self.align { + 1 => cast_vec(Vec::::from(cast_arbitrary_slice(bytes))), + 2 => cast_vec(Vec::::from(cast_arbitrary_slice(bytes))), + 4 => cast_vec(Vec::::from(cast_arbitrary_slice(bytes))), + 8 => cast_vec(Vec::::from(cast_arbitrary_slice(bytes))), + 16 => cast_vec(Vec::::from(cast_arbitrary_slice(bytes))), + 32 => cast_vec(Vec::::from(cast_arbitrary_slice(bytes))), + 64 => cast_vec(Vec::::from(cast_arbitrary_slice(bytes))), + 128 => cast_vec(Vec::::from(cast_arbitrary_slice(bytes))), + 256 => cast_vec(Vec::::from(cast_arbitrary_slice(bytes))), + 512 => cast_vec(Vec::::from(cast_arbitrary_slice(bytes))), + _ => panic!("Too aligned"), + } + }, + Cow::Owned(owned) => owned, + }; + + RawVertices { + bytes: Cow::Owned(bytes), + format: self.format, + align: self.align, + ty: self.ty, + } + } + + fn into_dynamic(self) -> DynamicVertices { + let bytes = match self.bytes { + Cow::Borrowed(bytes) => unsafe { + match self.align { + 1 => cast_vec(Vec::::from(cast_arbitrary_slice(bytes))), + 2 => cast_vec(Vec::::from(cast_arbitrary_slice(bytes))), + 4 => cast_vec(Vec::::from(cast_arbitrary_slice(bytes))), + 8 => cast_vec(Vec::::from(cast_arbitrary_slice(bytes))), + 16 => cast_vec(Vec::::from(cast_arbitrary_slice(bytes))), + 32 => cast_vec(Vec::::from(cast_arbitrary_slice(bytes))), + 64 => cast_vec(Vec::::from(cast_arbitrary_slice(bytes))), + 128 => cast_vec(Vec::::from(cast_arbitrary_slice(bytes))), + 256 => cast_vec(Vec::::from(cast_arbitrary_slice(bytes))), + 512 => cast_vec(Vec::::from(cast_arbitrary_slice(bytes))), + _ => panic!("Too aligned"), + } + }, + Cow::Owned(owned) => owned, + }; + + DynamicVertices { + bytes, + dirty: Vec::new(), + ty: self.ty, + offset: 0, + size: 0, + format: self.format, + } + } } #[derive(Clone, Debug)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] struct RawIndices<'a> { #[cfg_attr(feature = "serde", serde(with = "serde_bytes", borrow))] - indices: Cow<'a, [u8]>, - index_type: rendy_core::hal::IndexType, + bytes: Cow<'a, [u8]>, + ty: IndexType, } -fn index_stride(index_type: rendy_core::hal::IndexType) -> usize { - match index_type { - rendy_core::hal::IndexType::U16 => size_of::(), - rendy_core::hal::IndexType::U32 => size_of::(), +impl RawIndices<'_> { + fn into_owned(self) -> RawIndices<'static> { + let bytes = match self.bytes { + Cow::Borrowed(bytes) => unsafe { + match self.ty { + IndexType::U16 => cast_vec(Vec::::from(cast_arbitrary_slice(bytes))), + IndexType::U32 => cast_vec(Vec::::from(cast_arbitrary_slice(bytes))), + } + }, + Cow::Owned(owned) => owned, + }; + + RawIndices { + bytes: Cow::Owned(bytes), + ty: self.ty, + } + } + + fn into_dynamic(self) -> DynamicIndices { + let bytes = match self.bytes { + Cow::Borrowed(bytes) => unsafe { + match self.ty { + IndexType::U16 => cast_vec(Vec::::from(cast_arbitrary_slice(bytes))), + IndexType::U32 => cast_vec(Vec::::from(cast_arbitrary_slice(bytes))), + } + }, + Cow::Owned(owned) => owned, + }; + + DynamicIndices { + bytes, + dirty: Vec::new(), + ty: self.ty, + } } } @@ -127,15 +243,9 @@ impl<'a> MeshBuilder<'a> { vertices: self .vertices .into_iter() - .map(|v| RawVertices { - vertices: Cow::Owned(v.vertices.into_owned()), - format: v.format, - }) + .map(RawVertices::into_owned) .collect(), - indices: self.indices.map(|i| RawIndices { - indices: Cow::Owned(i.indices.into_owned()), - index_type: i.index_type, - }), + indices: self.indices.map(RawIndices::into_owned), prim: self.prim, } } @@ -157,12 +267,12 @@ impl<'a> MeshBuilder<'a> { self.indices = match indices.into() { Indices::None => None, Indices::U16(i) => Some(RawIndices { - indices: cast_cow(i), - index_type: rendy_core::hal::IndexType::U16, + bytes: cast_cow(i), + ty: IndexType::U16, }), Indices::U32(i) => Some(RawIndices { - indices: cast_cow(i), - index_type: rendy_core::hal::IndexType::U32, + bytes: cast_cow(i), + ty: IndexType::U32, }), }; self @@ -185,8 +295,10 @@ impl<'a> MeshBuilder<'a> { D: Into>, { self.vertices.push(RawVertices { - vertices: cast_cow(vertices.into()), + bytes: cast_cow(vertices.into()), format: V::vertex(), + align: align_of::(), + ty: TypeId::of::(), }); self } @@ -216,20 +328,20 @@ impl<'a> MeshBuilder<'a> { /// Note that contents of index buffer is not validated. pub fn build(&self, queue: QueueId, factory: &Factory) -> Result, UploadError> where - B: rendy_core::hal::Backend, + B: 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) + .map(|v| v.bytes.len() / v.format.stride as usize) .min() .unwrap_or(0); let buffer_size = self .vertices .iter() - .map(|v| (v.format.stride * len) as usize) + .map(|v| v.format.stride as usize * len) .sum(); let aligned_size = align_by(align, buffer_size) as u64; @@ -258,22 +370,37 @@ impl<'a> MeshBuilder<'a> { let mut mapped = staging .map(factory, 0..aligned_size) .map_err(UploadError::Map)?; - let mut writer = - unsafe { mapped.write(factory, 0..aligned_size) }.map_err(UploadError::Map)?; - let staging_slice = unsafe { writer.slice() }; + let mut writer = unsafe { + // New staging buffer cannot be accessed by device. + mapped.write(factory, 0..aligned_size) + } + .map_err(UploadError::Map)?; + + let staging_slice: &mut [MaybeUninit] = unsafe { + // Slize is never read. + writer.slice() + }; let mut offset = 0usize; let mut vertex_layouts: Vec<_> = self .vertices .iter() - .map(|RawVertices { vertices, format }| { - let size = (format.stride * len) as usize; - staging_slice[offset..offset + size].copy_from_slice(&vertices[0..size]); - let this_offset = offset as u64; + .map(|v| { + let size = v.format.stride as usize * len; + unsafe { + debug_assert!(v.bytes.len() >= size); // "Ensured by `len` calculation + // `staging_slice` size is sum of all `size`s in this loop + alignment. + std::ptr::copy_nonoverlapping( + v.bytes.as_ptr(), + staging_slice.as_mut_ptr().add(offset) as *mut u8, + size, + ); + } + let this_offset = offset; offset += size; VertexBufferLayout { offset: this_offset, - format: format.clone(), + format: v.format.clone(), } }) .collect(); @@ -282,19 +409,34 @@ impl<'a> MeshBuilder<'a> { drop(writer); drop(mapped); + unsafe { + factory + .upload_from_staging_buffer( + &mut buffer, + staging, + None, + BufferState::new(queue) + .with_access(rendy_core::hal::buffer::Access::VERTEX_BUFFER_READ) + .with_stage(rendy_core::hal::pso::PipelineStage::VERTEX_INPUT), + Some(rendy_core::hal::command::BufferCopy { + src: 0, + dst: 0, + size: buffer_size as u64, + }), + ) + .map_err(UploadError::Upload)?; + } + vertex_layouts.sort_unstable_by(|a, b| a.format.cmp(&b.format)); let index_buffer = match self.indices { None => None, - Some(RawIndices { - ref indices, - index_type, - }) => { - len = (indices.len() / index_stride(index_type)) as u32; + Some(RawIndices { ref bytes, ty }) => { + len = bytes.len() / index_stride(ty); let mut buffer = factory .create_buffer( BufferInfo { - size: indices.len() as _, + size: bytes.len() as _, usage: rendy_core::hal::buffer::Usage::INDEX | rendy_core::hal::buffer::Usage::TRANSFER_DST, }, @@ -306,7 +448,7 @@ impl<'a> MeshBuilder<'a> { factory.upload_buffer( &mut buffer, 0, - &indices, + &bytes, None, BufferState::new(queue) .with_access(rendy_core::hal::buffer::Access::INDEX_BUFFER_READ) @@ -314,231 +456,49 @@ impl<'a> MeshBuilder<'a> { )?; } - Some(IndexBuffer { buffer, index_type }) + Some(IndexBuffer { buffer, ty }) } }; - unsafe { - factory - .upload_from_staging_buffer( - &mut buffer, - 0, - staging, - None, - BufferState::new(queue) - .with_access(rendy_core::hal::buffer::Access::VERTEX_BUFFER_READ) - .with_stage(rendy_core::hal::pso::PipelineStage::VERTEX_INPUT), - ) - .map_err(UploadError::Upload)?; - } - Ok(Mesh { vertex_layouts, index_buffer, vertex_buffer: buffer, prim: self.prim, - len, + len: len as u32, }) } -} - -fn align_by(align: usize, value: usize) -> usize { - ((value + align - 1) / align) * align -} - -/// Single mesh is a collection of buffer ranges that provides available attributes. -/// Usually exactly one mesh is used per draw call. -#[derive(Debug)] -pub struct Mesh { - vertex_buffer: Escape>, - vertex_layouts: Vec, - index_buffer: Option>, - prim: rendy_core::hal::pso::Primitive, - len: u32, -} - -impl Mesh -where - B: rendy_core::hal::Backend, -{ - /// Build new mesh with `MeshBuilder` - pub fn builder<'a>() -> MeshBuilder<'a> { - MeshBuilder::new() - } - - /// rendy_core::hal::pso::Primitive type of the `Mesh` - pub fn primitive(&self) -> rendy_core::hal::pso::Primitive { - self.prim - } - - /// Returns the number of vertices that will be drawn - /// in the mesh. For a mesh with no index buffer, - /// this is the same as the number of vertices, or for - /// a mesh with indices, this is the same as the number - /// of indices. - pub fn len(&self) -> u32 { - self.len - } - fn get_vertex_iter<'a>( - &'a self, - formats: &[VertexFormat], - ) -> Result, Incompatible> { - debug_assert!(is_slice_sorted(formats), "Formats: {:#?}", formats); - debug_assert!(is_slice_sorted_by_key(&self.vertex_layouts, |l| &l.format)); - - let mut vertex = smallvec::SmallVec::<[_; 16]>::new(); - - let mut next = 0; - for format in formats { - if let Some(index) = find_compatible_buffer(&self.vertex_layouts[next..], format) { - next += index; - vertex.push(self.vertex_layouts[next].offset); - } else { - // Can't bind - return Err(Incompatible { - not_found: format.clone(), - in_formats: self - .vertex_layouts - .iter() - .map(|l| l.format.clone()) - .collect(), - }); - } - } - - let buffer = self.vertex_buffer.raw(); - Ok(vertex.into_iter().map(move |offset| (buffer, offset))) - } - - /// Bind buffers to specified attribute locations. - pub fn bind( - &self, - first_binding: u32, - formats: &[VertexFormat], - encoder: &mut EncoderCommon<'_, B, C>, - ) -> Result + /// Builds and returns the new dynamic mesh. + /// + /// A mesh expects all vertex buffers to have the same number of elements. + /// If those are not equal, the length of smallest vertex buffer is selected + /// + /// Note that contents of index buffer is not validated. + /// + /// In addition dynamic mesh can be modified and new vertices added. + /// Set of vertex attributes or presense of index buffer cannot be changed. + /// To apply modifications to underlying GPU buffers `DynamicMesh::update` must be called. + pub fn build_dynamic( + self, + queue: QueueId, + factory: &Factory, + ) -> Result, UploadError> where - C: Supports, + B: Backend, { - let vertex_iter = self.get_vertex_iter(formats)?; - match self.index_buffer.as_ref() { - Some(index_buffer) => unsafe { - encoder.bind_index_buffer(index_buffer.buffer.raw(), 0, index_buffer.index_type); - encoder.bind_vertex_buffers(first_binding, vertex_iter); - }, - None => unsafe { - encoder.bind_vertex_buffers(first_binding, vertex_iter); - }, - } - - Ok(self.len) - } - - /// Bind buffers to specified attribute locations and issue draw calls with given instance range. - pub fn bind_and_draw( - &self, - first_binding: u32, - formats: &[VertexFormat], - instance_range: std::ops::Range, - encoder: &mut RenderPassEncoder<'_, B>, - ) -> Result { - let vertex_iter = self.get_vertex_iter(formats)?; - unsafe { - match self.index_buffer.as_ref() { - Some(index_buffer) => { - encoder.bind_index_buffer( - index_buffer.buffer.raw(), - 0, - index_buffer.index_type, - ); - encoder.bind_vertex_buffers(first_binding, vertex_iter); - encoder.draw_indexed(0..self.len, 0, instance_range); - } - None => { - encoder.bind_vertex_buffers(first_binding, vertex_iter); - encoder.draw(0..self.len, instance_range); - } - } - } + let mesh = self.build(queue, factory)?; - Ok(self.len) - } -} - -/// Error type returned by `Mesh::bind` in case of mesh's vertex buffers are incompatible with requested vertex formats. -#[derive(Clone, Debug, PartialEq, Eq)] -pub struct Incompatible { - /// Format that was queried but was not found - pub not_found: VertexFormat, - /// List of formats that were available at query time - pub in_formats: Vec, -} - -impl std::fmt::Display for Incompatible { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!( - f, - "Vertex format {:?} is not compatible with any of {:?}.", - self.not_found, self.in_formats - ) - } -} -impl std::error::Error for Incompatible {} - -/// Helper function to find buffer with compatible format. -fn find_compatible_buffer( - vertex_layouts: &[VertexBufferLayout], - format: &VertexFormat, -) -> Option { - debug_assert!(is_slice_sorted(&*format.attributes)); - for (i, layout) in vertex_layouts.iter().enumerate() { - debug_assert!(is_slice_sorted(&*layout.format.attributes)); - if is_compatible(&layout.format, format) { - return Some(i); - } - } - None -} - -/// Check is vertex format `left` is compatible with `right`. -/// `left` must have same `stride` and contain all attributes from `right`. -fn is_compatible(left: &VertexFormat, right: &VertexFormat) -> bool { - if left.stride != right.stride { - return false; - } - - // Don't start searching from index 0 because attributes are sorted - let mut skip = 0; - right.attributes.iter().all(|r| { - left.attributes[skip..] - .iter() - .position(|l| l == r) - .map_or(false, |p| { - skip += p; - true - }) - }) -} - -/// Chech if slice o f ordered values is sorted. -fn is_slice_sorted(slice: &[T]) -> bool { - is_slice_sorted_by_key(slice, |i| i) -} - -/// Check if slice is sorted using ordered key and key extractor -fn is_slice_sorted_by_key<'a, T, K: Ord>(slice: &'a [T], f: impl Fn(&'a T) -> K) -> bool { - if let Some((first, slice)) = slice.split_first() { - let mut cmp = f(first); - for item in slice { - let item = f(item); - if cmp > item { - return false; - } - cmp = item; - } + Ok(DynamicMesh { + mesh, + vertices: self + .vertices + .into_iter() + .map(RawVertices::into_dynamic) + .collect(), + indices: self.indices.map(RawIndices::into_dynamic), + }) } - true } impl<'a, A> From> for MeshBuilder<'a> diff --git a/mesh/src/dynamic.rs b/mesh/src/dynamic.rs new file mode 100644 index 00000000..cde352d8 --- /dev/null +++ b/mesh/src/dynamic.rs @@ -0,0 +1,624 @@ +use crate::{ + align_by, + builder::MeshBuilder, + command::QueueId, + core::{ + cast_arbitrary_slice_mut, + hal::{Backend, IndexType}, + }, + factory::{BufferState, Factory, UploadError}, + index_stride, + memory::{Data, Upload, Write}, + r#static::{IndexBuffer, Mesh}, + resource::BufferInfo, + AsVertex, VertexFormat, +}; +use rendy_core::hal::adapter::PhysicalDevice; +use std::{ + any::TypeId, + mem::{align_of, size_of, MaybeUninit}, + ops::{Deref, Range}, +}; + +pub(crate) struct DynamicVertices { + pub(crate) bytes: Vec, + pub(crate) dirty: Vec>, + pub(crate) ty: TypeId, + pub(crate) offset: usize, + pub(crate) size: usize, + pub(crate) format: VertexFormat, +} + +pub(crate) struct DynamicIndices { + pub(crate) bytes: Vec, + pub(crate) dirty: Vec>, + pub(crate) ty: IndexType, +} + +/// Single mesh is a collection of buffer ranges that provides available attributes. +/// Usually exactly one mesh is used per draw call. +/// +/// Dynamic mesh also allows modifying vertices and indices between frames. +pub struct DynamicMesh { + pub(crate) mesh: Mesh, + pub(crate) vertices: Vec, + pub(crate) indices: Option, +} + +#[derive(Clone, Copy, Debug)] +pub enum VerticesModifyError { + OutOfRange { count: usize, requested: usize }, + TypeMismatch { expected: TypeId, found: TypeId }, +} + +#[derive(Clone, Copy, Debug)] +pub enum IndicesModifyError { + NoIndexBuffer, + TypeMismatch { + expected: IndexType, + found: IndexType, + }, +} + +impl Deref for DynamicMesh +where + B: Backend, +{ + type Target = Mesh; + + fn deref(&self) -> &Mesh { + &self.mesh + } +} + +impl AsRef> for DynamicMesh +where + B: Backend, +{ + fn as_ref(&self) -> &Mesh { + &self.mesh + } +} + +impl std::borrow::Borrow> for DynamicMesh +where + B: Backend, +{ + fn borrow(&self) -> &Mesh { + &self.mesh + } +} + +impl DynamicMesh +where + B: Backend, +{ + /// Build new mesh with `MeshBuilder` + pub fn builder<'a>() -> MeshBuilder<'a> { + MeshBuilder::new() + } + + /// Acquire slice of vertices from vertex buffer at `index`. + /// All vertices from specifed range will be marked dirty and flushed on `DynamicMesh::update`. + /// If requested range is larger than vertex array then it will be resized, filling new values with `fill` before returning. + /// + /// # Panics + /// + /// This function will panic if wrong vertex type is requested. + pub fn modify_vertices( + &mut self, + index: usize, + range: Range, + fill: V, + ) -> Result<&mut [V], VerticesModifyError> + where + V: AsVertex, + { + let len = self.vertices.len(); + let vertices = + self.vertices + .get_mut(index) + .ok_or_else(|| VerticesModifyError::OutOfRange { + count: len, + requested: index, + })?; + + if vertices.ty != TypeId::of::() { + return Err(VerticesModifyError::TypeMismatch { + expected: TypeId::of::(), + found: vertices.ty, + }); + } + + let bytes_range = + range.start as usize * size_of::()..range.end as usize * size_of::(); + vertices.dirty.push(bytes_range.clone()); + + if vertices.bytes.len() < bytes_range.end { + let additional_bytes = bytes_range.end - vertices.bytes.len(); + assert_eq!(additional_bytes % size_of::(), 0); + vertices.bytes.reserve_exact(additional_bytes); + unsafe { + let ptr = vertices.bytes.as_mut_ptr().add(vertices.bytes.len()); + assert_eq!( + ptr as usize % align_of::(), + 0, + "Vector contains `V`s and so must be aligned" + ); + let ptr = ptr as *mut V; + let additional = additional_bytes / size_of::(); + for ptr in std::iter::successors(Some(ptr), |p| Some(p.add(1))).take(additional) { + std::ptr::write(ptr, fill); + } + vertices.bytes.set_len(bytes_range.end); + } + } + + Ok(unsafe { cast_arbitrary_slice_mut(&mut vertices.bytes[bytes_range]) }) + } + + /// Acquire slice of indices from index buffer at `index`. + /// All indices from specifed range will be marked dirty and flushed on `DynamicMesh::update`. + /// If requested range is larger than index array then it will be resized, filling new values with `0` before returning. + /// + /// # Panics + /// + /// This function will panic if wrong index type is requested. + pub fn modify_indices_16( + &mut self, + range: Range, + ) -> Result<&mut [u16], IndicesModifyError> { + let indices = self + .indices + .as_mut() + .ok_or(IndicesModifyError::NoIndexBuffer)?; + + if indices.ty != IndexType::U16 { + return Err(IndicesModifyError::TypeMismatch { + expected: IndexType::U16, + found: indices.ty, + }); + } + + let bytes_range = + range.start as usize * size_of::()..range.end as usize * size_of::(); + + if indices.bytes.len() < bytes_range.end { + indices.bytes.resize(bytes_range.end, 0); + } + + indices.dirty.push(bytes_range.clone()); + + if indices.bytes.len() < bytes_range.end { + let additional_bytes = bytes_range.end - indices.bytes.len(); + assert_eq!(additional_bytes % size_of::(), 0); + indices.bytes.reserve_exact(additional_bytes); + unsafe { + let ptr = indices.bytes.as_mut_ptr().add(indices.bytes.len()); + std::ptr::write_bytes(ptr, 0, additional_bytes); + indices.bytes.set_len(bytes_range.end); + } + } + + Ok(unsafe { cast_arbitrary_slice_mut(&mut indices.bytes[bytes_range]) }) + } + + /// Acquire slice of indices from index buffer at `index`. + /// All indices from specifed range will be marked dirty and flushed on `DynamicMesh::update`. + /// If requested range is larger than index array then it will be resized, filling new values with `0` before returning. + /// + /// # Panics + /// + /// This function will panic if wrong index type is requested. + pub fn modify_indices_32( + &mut self, + range: Range, + ) -> Result<&mut [u32], IndicesModifyError> { + let indices = self + .indices + .as_mut() + .ok_or(IndicesModifyError::NoIndexBuffer)?; + + if indices.ty != IndexType::U32 { + return Err(IndicesModifyError::TypeMismatch { + expected: IndexType::U32, + found: indices.ty, + }); + } + + let bytes_range = + range.start as usize * size_of::()..range.end as usize * size_of::(); + + if indices.bytes.len() < bytes_range.end { + indices.bytes.resize(bytes_range.end, 0); + } + + indices.dirty.push(bytes_range.clone()); + + if indices.bytes.len() < bytes_range.end { + let additional_bytes = bytes_range.end - indices.bytes.len(); + assert_eq!(additional_bytes % size_of::(), 0); + indices.bytes.reserve_exact(additional_bytes); + unsafe { + let ptr = indices.bytes.as_mut_ptr().add(indices.bytes.len()); + std::ptr::write_bytes(ptr, 0, additional_bytes); + indices.bytes.set_len(bytes_range.end); + } + } + + Ok(unsafe { cast_arbitrary_slice_mut(&mut indices.bytes[bytes_range]) }) + } + + pub fn update(&mut self, queue: QueueId, factory: &Factory) -> Result<(), UploadError> { + let len = self + .vertices + .iter() + .map(|v| v.bytes.len() / v.format.stride as usize) + .min() + .unwrap_or(0); + + if self + .vertices + .iter() + .all(|v| v.size >= len * v.format.stride as usize) + { + self.update_vertex_buffer(queue, factory)?; + } else { + self.build_vertex_buffer(queue, factory)?; + } + + if let Some(indices) = &mut self.indices { + match &self.mesh.index_buffer { + Some(index_buffer) if index_buffer.buffer.size() >= indices.bytes.len() as u64 => { + self.update_index_buffer(queue, factory)?; + } + _ => { + self.build_index_buffer(queue, factory)?; + } + } + } + + Ok(()) + } + + fn build_vertex_buffer( + &mut self, + queue: QueueId, + factory: &Factory, + ) -> Result<(), UploadError> { + let align = factory.physical().limits().non_coherent_atom_size; + let len = self + .vertices + .iter() + .map(|v| v.bytes.len() / v.format.stride as usize) + .min() + .unwrap_or(0); + + let buffer_size = self + .vertices + .iter() + .map(|v| v.format.stride as usize * len) + .sum(); + + let staging_size = align_by(align, buffer_size) as u64; + + let mut staging = factory + .create_buffer( + BufferInfo { + size: staging_size, + usage: rendy_core::hal::buffer::Usage::TRANSFER_SRC, + }, + Upload, + ) + .map_err(UploadError::Create)?; + + let mut buffer = factory + .create_buffer( + BufferInfo { + size: buffer_size as _, + usage: rendy_core::hal::buffer::Usage::VERTEX + | rendy_core::hal::buffer::Usage::TRANSFER_DST, + }, + Data, + ) + .map_err(UploadError::Create)?; + + let mut mapped = staging + .map(factory, 0..staging_size) + .map_err(UploadError::Map)?; + let mut writer = + unsafe { mapped.write(factory, 0..staging_size) }.map_err(UploadError::Map)?; + let staging_slice: &mut [MaybeUninit] = unsafe { writer.slice() }; + + let mut offset = 0usize; + for v in &mut self.vertices { + let size = v.format.stride as usize * len; + unsafe { + debug_assert!(v.bytes.len() >= size); // "Ensured by `len` calculation + // `staging_slice` size is sum of all `size`s in this loop + alignment. + std::ptr::copy_nonoverlapping( + v.bytes.as_ptr(), + staging_slice.as_mut_ptr().add(offset) as *mut u8, + size, + ); + } + v.offset = offset; + v.size = size; + offset += size; + } + + drop(staging_slice); + drop(writer); + drop(mapped); + + unsafe { + factory + .upload_from_staging_buffer( + &mut buffer, + staging, + None, + BufferState::new(queue) + .with_access(rendy_core::hal::buffer::Access::VERTEX_BUFFER_READ) + .with_stage(rendy_core::hal::pso::PipelineStage::VERTEX_INPUT), + Some(rendy_core::hal::command::BufferCopy { + src: 0, + dst: 0, + size: buffer_size as u64, + }), + ) + .map_err(UploadError::Upload)?; + } + + assert_eq!(self.mesh.vertex_layouts.len(), self.vertices.len()); + for (l, v) in self.mesh.vertex_layouts.iter_mut().zip(&self.vertices) { + assert_eq!(l.format, v.format); + l.offset = v.offset; + } + self.mesh.vertex_buffer = buffer; + if self.indices.is_none() { + self.mesh.len = len as u32; + } + + Ok(()) + } + + fn update_vertex_buffer( + &mut self, + queue: QueueId, + factory: &Factory, + ) -> Result<(), UploadError> { + let dirty_bytes_total = self + .vertices + .iter_mut() + .flat_map(|v| { + v.dirty.sort_by_key(|d| d.start); + + let mut j = 0; + for i in 1..v.dirty.len() { + if v.dirty[j].end >= v.dirty[i].start { + // merge + v.dirty[j].end = std::cmp::max(v.dirty[j].end, v.dirty[i].end); + } else { + // next + j += 1; + } + } + + v.dirty.truncate(j); + v.dirty.iter().map(|d| d.end - d.start) + }) + .sum(); + + if dirty_bytes_total == 0 { + return Ok(()); + } + + let align = factory.physical().limits().non_coherent_atom_size; + let staging_size = align_by(align, dirty_bytes_total) as u64; + + let mut staging = factory + .create_buffer( + BufferInfo { + size: staging_size, + usage: rendy_core::hal::buffer::Usage::TRANSFER_SRC, + }, + Upload, + ) + .map_err(UploadError::Create)?; + + let mut mapped = staging + .map(factory, 0..staging_size) + .map_err(UploadError::Map)?; + let mut writer = + unsafe { mapped.write(factory, 0..staging_size) }.map_err(UploadError::Map)?; + let staging_slice: &mut [MaybeUninit] = unsafe { writer.slice() }; + + let mut offset = 0usize; + for v in &self.vertices { + for d in &v.dirty { + let size = d.end - d.start; + unsafe { + debug_assert!(v.bytes.len() >= d.end); // "Ensured by `len` calculation + // `staging_slice` size is sum of all `size`s in this loop + alignment. + std::ptr::copy_nonoverlapping( + v.bytes.as_ptr().add(d.start), + staging_slice.as_mut_ptr().add(offset) as *mut u8, + size, + ); + } + offset += size; + } + } + + drop(staging_slice); + drop(writer); + drop(mapped); + + let state = BufferState::new(queue) + .with_access(rendy_core::hal::buffer::Access::VERTEX_BUFFER_READ) + .with_stage(rendy_core::hal::pso::PipelineStage::VERTEX_INPUT); + + let mut offset = 0usize; + unsafe { + factory + .upload_from_staging_buffer( + &mut self.mesh.vertex_buffer, + staging, + Some(state), + state, + self.vertices.iter().flat_map(|v| v.dirty.iter()).map(|d| { + let size = d.end - d.start; + offset += size; + rendy_core::hal::command::BufferCopy { + src: (offset - size) as u64, + dst: d.start as u64, + size: size as u64, + } + }), + ) + .map_err(UploadError::Upload)?; + } + + for v in &mut self.vertices { + v.dirty.clear(); + } + + Ok(()) + } + + fn build_index_buffer( + &mut self, + queue: QueueId, + factory: &Factory, + ) -> Result<(), UploadError> { + if let Some(indices) = &mut self.indices { + let len = (indices.bytes.len() / index_stride(indices.ty)) as u32; + let mut buffer = factory + .create_buffer( + BufferInfo { + size: indices.bytes.len() as _, + usage: rendy_core::hal::buffer::Usage::INDEX + | rendy_core::hal::buffer::Usage::TRANSFER_DST, + }, + Data, + ) + .map_err(UploadError::Create)?; + unsafe { + // New buffer can't be touched by device yet. + factory.upload_buffer( + &mut buffer, + 0, + &indices.bytes, + None, + BufferState::new(queue) + .with_access(rendy_core::hal::buffer::Access::INDEX_BUFFER_READ) + .with_stage(rendy_core::hal::pso::PipelineStage::VERTEX_INPUT), + )?; + } + + self.mesh.index_buffer = Some(IndexBuffer { + buffer, + ty: indices.ty, + }); + self.mesh.len = len; + } + + Ok(()) + } + + fn update_index_buffer( + &mut self, + queue: QueueId, + factory: &Factory, + ) -> Result<(), UploadError> { + let indices = self.indices.as_mut().unwrap(); + let index_buffer = self.mesh.index_buffer.as_mut().unwrap(); + + indices.dirty.sort_by_key(|d| d.start); + + let mut j = 0; + for i in 1..indices.dirty.len() { + if indices.dirty[j].end >= indices.dirty[i].start { + // merge + indices.dirty[j].end = std::cmp::max(indices.dirty[j].end, indices.dirty[i].end); + } else { + // next + j += 1; + } + } + + indices.dirty.truncate(j); + let dirty_bytes_total = indices.dirty.iter().map(|d| d.end - d.start).sum(); + + if dirty_bytes_total == 0 { + return Ok(()); + } + + let align = factory.physical().limits().non_coherent_atom_size; + let staging_size = align_by(align, dirty_bytes_total) as u64; + + let mut staging = factory + .create_buffer( + BufferInfo { + size: staging_size, + usage: rendy_core::hal::buffer::Usage::TRANSFER_SRC, + }, + Upload, + ) + .map_err(UploadError::Create)?; + + let mut mapped = staging + .map(factory, 0..staging_size) + .map_err(UploadError::Map)?; + let mut writer = + unsafe { mapped.write(factory, 0..staging_size) }.map_err(UploadError::Map)?; + let staging_slice: &mut [MaybeUninit] = unsafe { writer.slice() }; + + let mut offset = 0usize; + for d in &indices.dirty { + let size = d.end - d.start; + unsafe { + debug_assert!(indices.bytes.len() >= d.end); // "Ensured by `len` calculation + // `staging_slice` size is sum of all `size`s in this loop + alignment. + std::ptr::copy_nonoverlapping( + indices.bytes.as_ptr().add(d.start), + staging_slice.as_mut_ptr().add(offset) as *mut u8, + size, + ); + } + offset += size; + } + + drop(staging_slice); + drop(writer); + drop(mapped); + + let state = BufferState::new(queue) + .with_access(rendy_core::hal::buffer::Access::INDEX_BUFFER_READ) + .with_stage(rendy_core::hal::pso::PipelineStage::VERTEX_INPUT); + + let mut offset = 0usize; + unsafe { + factory + .upload_from_staging_buffer( + &mut index_buffer.buffer, + staging, + Some(state), + state, + indices.dirty.iter().map(|d| { + let size = d.end - d.start; + offset += size; + rendy_core::hal::command::BufferCopy { + src: (offset - size) as u64, + dst: d.start as u64, + size: size as u64, + } + }), + ) + .map_err(UploadError::Upload)?; + } + + indices.dirty.clear(); + Ok(()) + } +} diff --git a/mesh/src/lib.rs b/mesh/src/lib.rs index 0faa6555..ab67ce13 100644 --- a/mesh/src/lib.rs +++ b/mesh/src/lib.rs @@ -22,8 +22,82 @@ use rendy_factory as factory; use rendy_memory as memory; use rendy_resource as resource; +mod builder; +mod dynamic; mod format; -mod mesh; +mod r#static; -pub use crate::{format::*, mesh::*}; +pub use crate::format::*; +pub use crate::{builder::*, dynamic::*, r#static::*}; pub use rendy_core::types::vertex::*; + +fn index_stride(ty: rendy_core::hal::IndexType) -> usize { + match ty { + rendy_core::hal::IndexType::U16 => std::mem::size_of::(), + rendy_core::hal::IndexType::U32 => std::mem::size_of::(), + } +} + +fn align_by(align: usize, value: usize) -> usize { + ((value + align - 1) / align) * align +} + +/// Error type returned by `Mesh::bind` in case of mesh's vertex buffers are incompatible with requested vertex formats. +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct Incompatible { + /// Format that was queried but was not found + pub not_found: VertexFormat, + /// List of formats that were available at query time + pub in_formats: Vec, +} + +impl std::fmt::Display for Incompatible { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!( + f, + "Vertex format {:?} is not compatible with any of {:?}.", + self.not_found, self.in_formats + ) + } +} +impl std::error::Error for Incompatible {} + +/// Check is vertex format `left` is compatible with `right`. +/// `left` must have same `stride` and contain all attributes from `right`. +fn is_compatible(left: &VertexFormat, right: &VertexFormat) -> bool { + if left.stride != right.stride { + return false; + } + + // Don't start searching from index 0 because attributes are sorted + let mut skip = 0; + right.attributes.iter().all(|r| { + left.attributes[skip..] + .iter() + .position(|l| l == r) + .map_or(false, |p| { + skip += p; + true + }) + }) +} + +/// Chech if slice o f ordered values is sorted. +fn is_slice_sorted(slice: &[T]) -> bool { + is_slice_sorted_by_key(slice, |i| i) +} + +/// Check if slice is sorted using ordered key and key extractor +fn is_slice_sorted_by_key<'a, T, K: Ord>(slice: &'a [T], f: impl Fn(&'a T) -> K) -> bool { + if let Some((first, slice)) = slice.split_first() { + let mut cmp = f(first); + for item in slice { + let item = f(item); + if cmp > item { + return false; + } + cmp = item; + } + } + true +} diff --git a/mesh/src/static.rs b/mesh/src/static.rs new file mode 100644 index 00000000..3ac7c090 --- /dev/null +++ b/mesh/src/static.rs @@ -0,0 +1,155 @@ +use crate::{ + builder::MeshBuilder, + command::{EncoderCommon, Graphics, RenderPassEncoder, Supports}, + core::hal::Backend, + is_compatible, is_slice_sorted, is_slice_sorted_by_key, + resource::{Buffer, Escape}, + Incompatible, VertexFormat, +}; + +/// Helper function to find buffer with compatible format. +fn find_compatible_buffer( + vertex_layouts: &[VertexBufferLayout], + format: &VertexFormat, +) -> Option { + debug_assert!(is_slice_sorted(&*format.attributes)); + for (i, layout) in vertex_layouts.iter().enumerate() { + debug_assert!(is_slice_sorted(&*layout.format.attributes)); + if is_compatible(&layout.format, format) { + return Some(i); + } + } + None +} + +/// Vertex buffer with it's format +#[derive(Debug)] +pub(crate) struct VertexBufferLayout { + pub(crate) offset: usize, + pub(crate) format: VertexFormat, +} + +/// Index buffer with it's type +#[derive(Debug)] +pub(crate) struct IndexBuffer { + pub(crate) buffer: Escape>, + pub(crate) ty: rendy_core::hal::IndexType, +} + +/// Single mesh is a collection of buffer ranges that provides available attributes. +/// Usually exactly one mesh is used per draw call. +#[derive(Debug)] +pub struct Mesh { + pub(crate) vertex_buffer: Escape>, + pub(crate) vertex_layouts: Vec, + pub(crate) index_buffer: Option>, + pub(crate) prim: rendy_core::hal::pso::Primitive, + pub(crate) len: u32, +} + +impl Mesh +where + B: Backend, +{ + /// Build new mesh with `MeshBuilder` + pub fn builder<'a>() -> MeshBuilder<'a> { + MeshBuilder::new() + } + + /// Primitive type of the `Mesh` + pub fn primitive(&self) -> rendy_core::hal::pso::Primitive { + self.prim + } + + /// Returns the number of vertices that will be drawn + /// in the mesh. For a mesh with no index buffer, + /// this is the same as the number of vertices, or for + /// a mesh with indices, this is the same as the number + /// of indices. + pub fn len(&self) -> u32 { + self.len + } + + fn get_vertex_iter<'a>( + &'a self, + formats: &[VertexFormat], + ) -> Result, Incompatible> { + debug_assert!(is_slice_sorted(formats), "Formats: {:#?}", formats); + debug_assert!(is_slice_sorted_by_key(&self.vertex_layouts, |l| &l.format)); + + let mut vertex = smallvec::SmallVec::<[_; 16]>::new(); + + let mut next = 0; + for format in formats { + if let Some(index) = find_compatible_buffer(&self.vertex_layouts[next..], format) { + next += index; + vertex.push(self.vertex_layouts[next].offset); + } else { + // Can't bind + return Err(Incompatible { + not_found: format.clone(), + in_formats: self + .vertex_layouts + .iter() + .map(|l| l.format.clone()) + .collect(), + }); + } + } + + let buffer = self.vertex_buffer.raw(); + Ok(vertex + .into_iter() + .map(move |offset| (buffer, offset as u64))) + } + + /// Bind buffers to specified attribute locations. + pub fn bind( + &self, + first_binding: u32, + formats: &[VertexFormat], + encoder: &mut EncoderCommon<'_, B, C>, + ) -> Result + where + C: Supports, + { + let vertex_iter = self.get_vertex_iter(formats)?; + match self.index_buffer.as_ref() { + Some(index_buffer) => unsafe { + encoder.bind_index_buffer(index_buffer.buffer.raw(), 0, index_buffer.ty); + encoder.bind_vertex_buffers(first_binding, vertex_iter); + }, + None => unsafe { + encoder.bind_vertex_buffers(first_binding, vertex_iter); + }, + } + + Ok(self.len) + } + + /// Bind buffers to specified attribute locations and issue draw calls with given instance range. + pub fn bind_and_draw( + &self, + first_binding: u32, + formats: &[VertexFormat], + instance_range: std::ops::Range, + encoder: &mut RenderPassEncoder<'_, B>, + ) -> Result { + let vertex_iter = self.get_vertex_iter(formats)?; + unsafe { + match self.index_buffer.as_ref() { + Some(index_buffer) => { + encoder.bind_index_buffer(index_buffer.buffer.raw(), 0, index_buffer.ty); + encoder.bind_vertex_buffers(first_binding, vertex_iter); + encoder.draw_indexed(0..self.len, 0, instance_range); + } + None => { + encoder.bind_vertex_buffers(first_binding, vertex_iter); + encoder.draw(0..self.len, instance_range); + } + } + } + + Ok(self.len) + } +}