Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Single Member Construction Functions #46

Draft
wants to merge 5 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ imbl = { version = "3", default-features = false, optional = true }
static-rc = { version = "0.6", features = ["alloc"], default-features = false, optional = true }

[dev-dependencies]
bytemuck = { version = "1", default-features = false }
criterion = { version = "0.4", features = ["cargo_bench_support", "html_reports"], default-features = false }
rand = { version = "0.8", features = ["std_rng"], default-features = false }
mimalloc = { version = "0.1", default-features = false }
Expand Down
193 changes: 189 additions & 4 deletions src/core/buffers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -195,34 +195,133 @@ impl<B> AsMut<B> for DynamicStorageBuffer<B> {
}

impl<B: BufferMut> DynamicStorageBuffer<B> {
fn set_and_alloc_offset(&mut self, offset: usize) -> Result<()> {
self.offset = offset;

self.inner
.try_enlarge(self.offset)
.map_err(|_e| super::rw::Error::BufferTooSmall {
expected: self.offset as u64,
found: self.inner.capacity() as u64,
})
}

/// Layouts and writes an entire bound value into the buffer. The value is written at the
/// next available offset after the buffer's current offset, which is aligned to the required
/// dynamic binding alignment (defaults to 256).
///
/// Use this to write the entire struct you will be binding as a dynamic-offset storage buffer.
///
/// Returns the offset at which the value was written.
pub fn write<T>(&mut self, value: &T) -> Result<u64>
where
T: ?Sized + ShaderType + WriteInto,
{
self.write_struct_end()?;
let offset = self.offset;

let mut writer = Writer::new(value, &mut self.inner, offset)?;
value.write_into(&mut writer);

self.offset += self.alignment.round_up(value.size().get()) as usize;
self.set_and_alloc_offset(self.offset + value.size().get() as usize)?;

Ok(offset as u64)
}

/// Layouts and writes a single member into the buffer. The value is written at the
/// next available offset after the buffer's current offset, which is aligned the
/// alignment of `T`.
///
/// The use case is constructing a struct member by member in case the layout isn't
/// known at compile time. Combine this with [`Self::write_struct_end`] to "end"
/// the struct being written and start the next one.
///
/// Returns the offset at which the value was written.
pub fn write_struct_member<T>(&mut self, value: &T) -> Result<u64>
where
T: ShaderType + WriteInto,
{
let raw_alignment = T::METADATA.alignment();

self.offset = raw_alignment.round_up(self.offset as u64) as usize;
let offset = self.offset;

let mut writer = Writer::new(value, &mut self.inner, offset)?;
value.write_into(&mut writer);

self.set_and_alloc_offset(self.offset + value.size().get() as usize)?;

Ok(offset as u64)
}

/// Writes a "struct break" into the buffer. This takes the buffer offset and aligns it
/// to the required dynamic binding alignment (defaults to 256).
///
/// The use case is constructing a struct member by member in case the layout isn't
/// known at compile time. Combine this with [`Self::write_struct_member`] to add
/// each individual member of the struct being written.
///
/// Returns the offset which was rounded up to.
pub fn write_struct_end(&mut self) -> Result<u64> {
self.set_and_alloc_offset(self.alignment.round_up(self.offset as u64) as usize)?;

Ok(self.offset as u64)
}
}

impl<B: BufferRef> DynamicStorageBuffer<B> {
/// Reads and un-layouts an entire bound value from the buffer. The value is read from the
/// next available offset after the buffer's current offset, which is aligned to the required
/// dynamic binding alignment (defaults to 256).
///
/// Use this to read the entire struct you bound as a dynamic-offset storage buffer.
pub fn read<T>(&mut self, value: &mut T) -> Result<()>
where
T: ?Sized + ShaderType + ReadFrom,
{
let mut writer = Reader::new::<T>(&self.inner, self.offset)?;
value.read_from(&mut writer);
self.read_dynamic_struct_break();

self.offset += self.alignment.round_up(value.size().get()) as usize;
let mut reader = Reader::new::<T>(&self.inner, self.offset)?;
value.read_from(&mut reader);

self.offset += value.size().get() as usize;

Ok(())
}

/// Reads a single member from the buffer. The value is read at the
/// next available offset after the buffer's current offset, which is aligned the
/// alignment of `T`.
///
/// The use case is deconstructing a struct member by member in case the layout isn't
/// known at compile time. Combine this with [`Self::read_dynamic_struct_break`] to "end"
/// the struct being read and start the next one.
///
/// Returns the offset at which the value was written.
pub fn read_single_member<T>(&mut self, value: &mut T) -> Result<()>
where
T: ShaderType + ReadFrom,
{
self.offset = T::METADATA.alignment().round_up(self.offset as u64) as usize;

let mut reader = Reader::new::<T>(&self.inner, self.offset)?;
value.read_from(&mut reader);

self.offset += value.size().get() as usize;

Ok(())
}

/// Reads a "struct break" from the buffer. This takes the buffer offset and aligns it
/// to the required dynamic binding alignment (defaults to 256).
///
/// The use case is constructing a struct member by member in case the layout isn't
/// known at compile time. Combine this with [`Self::read_single_member`] to add
/// each individual member of the struct being written.
pub fn read_dynamic_struct_break(&mut self) {
self.offset = self.alignment.round_up(self.offset as u64) as usize;
}

pub fn create<T>(&mut self) -> Result<T>
where
T: ShaderType + CreateFrom,
Expand Down Expand Up @@ -289,16 +388,75 @@ impl<B> AsMut<B> for DynamicUniformBuffer<B> {
}

impl<B: BufferMut> DynamicUniformBuffer<B> {
/// Layouts and writes an entire bound value into the buffer. The value is written at the
/// next available offset after the buffer's current offset, which is aligned to the required
/// dynamic binding alignment (defaults to 256).
///
/// Use this to write the entire struct you will be binding as a dynamic-offset storage buffer.
///
/// Returns the offset at which the value was written.
pub fn write<T>(&mut self, value: &T) -> Result<u64>
where
T: ?Sized + ShaderType + WriteInto,
{
T::assert_uniform_compat();
self.inner.write(value)
}

/// Layouts and writes a single member into the buffer. The value is written at the
/// next available offset after the buffer's current offset, which is aligned the
/// alignment of `T`.
///
/// The use case is constructing a struct member by member in case the layout isn't
/// known at compile time. Combine this with [`Self::write_struct_end`] to "end"
/// the struct being written and start the next one.
///
/// Returns the offset at which the value was written.
pub fn write_struct_member<T>(&mut self, value: &T) -> Result<u64>
where
T: ShaderType + WriteInto,
{
let uniform_min_alignment = T::METADATA.uniform_min_alignment();
let raw_alignment = T::METADATA.alignment();

let max_alignment = match uniform_min_alignment {
Some(uniform_min_alignment) => {
AlignmentValue::max([uniform_min_alignment, raw_alignment])
}
None => raw_alignment,
};

self.inner.offset = max_alignment.round_up(self.inner.offset as u64) as usize;
let offset = self.inner.offset;

let mut writer = Writer::new(value, &mut self.inner.inner, offset)?;
value.write_into(&mut writer);

self.inner
.set_and_alloc_offset(self.inner.offset + value.size().get() as usize)?;

Ok(offset as u64)
}

/// Writes a "struct break" into the buffer. This takes the buffer offset and aligns it
/// to the required dynamic binding alignment (defaults to 256).
///
/// The use case is constructing a struct member by member in case the layout isn't
/// known at compile time. Combine this with [`Self::write_struct_member`] to add
/// each individual member of the struct being written.
///
/// Returns the offset which was rounded up to.
pub fn write_struct_end(&mut self) -> Result<u64> {
self.inner.write_struct_end()
}
}

impl<B: BufferRef> DynamicUniformBuffer<B> {
/// Reads and un-layouts an entire bound value from the buffer. The value is read from the
/// next available offset after the buffer's current offset, which is aligned to the required
/// dynamic binding alignment (defaults to 256).
///
/// Use this to read the entire struct you bound as a dynamic-offset storage buffer.
pub fn read<T>(&mut self, value: &mut T) -> Result<()>
where
T: ?Sized + ShaderType + ReadFrom,
Expand All @@ -307,6 +465,33 @@ impl<B: BufferRef> DynamicUniformBuffer<B> {
self.inner.read(value)
}

/// Reads a single member from the buffer. The value is read at the
/// next available offset after the buffer's current offset, which is aligned the
/// alignment of `T`.
///
/// The use case is deconstructing a struct member by member in case the layout isn't
/// known at compile time. Combine this with [`Self::read_dynamic_struct_break`] to "end"
/// the struct being read and start the next one.
///
/// Returns the offset at which the value was written.
pub fn read_single_member<T>(&mut self, value: &mut T) -> Result<()>
where
T: ShaderType + ReadFrom,
{
T::assert_uniform_compat();
self.inner.read_single_member(value)
}

/// Reads a "struct break" from the buffer. This takes the buffer offset and aligns it
/// to the required dynamic binding alignment (defaults to 256).
///
/// The use case is constructing a struct member by member in case the layout isn't
/// known at compile time. Combine this with [`Self::read_single_member`] to add
/// each individual member of the struct being written.
pub fn read_dynamic_struct_break(&mut self) {
self.inner.read_dynamic_struct_break()
}

pub fn create<T>(&mut self) -> Result<T>
where
T: ShaderType + CreateFrom,
Expand Down
54 changes: 54 additions & 0 deletions tests/single_member.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
use encase::ShaderType;

#[derive(ShaderType)]
struct Test {
a: u32,
}

#[test]
fn single_member_uniform() {
let mut buffer = encase::DynamicUniformBuffer::new(Vec::<u8>::new());

assert_eq!(buffer.write_struct_member(&1_u32).unwrap(), 0);
assert_eq!(
buffer.write_struct_member(&glam::UVec2::new(2, 3)).unwrap(),
8
);
assert_eq!(buffer.write_struct_member(&4_u32).unwrap(), 16);
assert_eq!(
buffer
.write_struct_member(&glam::UVec3::new(5, 6, 7))
.unwrap(),
32
);
assert_eq!(buffer.write_struct_member(&[8, 9]).unwrap(), 48);
assert_eq!(buffer.write_struct_member(&Test { a: 10 }).unwrap(), 64);

let cast: &[u32] = bytemuck::cast_slice(buffer.as_ref());

assert_eq!(cast, &[1, 0, 2, 3, 4, 0, 0, 0, 5, 6, 7, 0, 8, 9, 0, 0, 10]);
}

#[test]
fn single_member_storage() {
let mut buffer = encase::DynamicStorageBuffer::new(Vec::<u8>::new());

assert_eq!(buffer.write_struct_member(&1_u32).unwrap(), 0);
assert_eq!(
buffer.write_struct_member(&glam::UVec2::new(2, 3)).unwrap(),
8
);
assert_eq!(buffer.write_struct_member(&4_u32).unwrap(), 16);
assert_eq!(
buffer
.write_struct_member(&glam::UVec3::new(5, 6, 7))
.unwrap(),
32
);
assert_eq!(buffer.write_struct_member(&[8, 9]).unwrap(), 44);
assert_eq!(buffer.write_struct_member(&Test { a: 10 }).unwrap(), 52);

let cast: &[u32] = bytemuck::cast_slice(buffer.as_ref());

assert_eq!(cast, &[1, 0, 2, 3, 4, 0, 0, 0, 5, 6, 7, 8, 9, 10]);
}
Loading