From 00a8a167122301983753a2f4b43d136c79a7d5cb Mon Sep 17 00:00:00 2001 From: shan Date: Thu, 29 Sep 2022 10:52:58 -0700 Subject: Adds linear gradient support to 2D meshes in the canvas widget. --- wgpu/src/buffers/buffer.rs | 91 ++++++++++++++++ wgpu/src/buffers/dynamic_buffers.rs | 202 ++++++++++++++++++++++++++++++++++++ 2 files changed, 293 insertions(+) create mode 100644 wgpu/src/buffers/buffer.rs create mode 100644 wgpu/src/buffers/dynamic_buffers.rs (limited to 'wgpu/src/buffers') diff --git a/wgpu/src/buffers/buffer.rs b/wgpu/src/buffers/buffer.rs new file mode 100644 index 00000000..dae3b038 --- /dev/null +++ b/wgpu/src/buffers/buffer.rs @@ -0,0 +1,91 @@ +//! Utilities for static buffer operations. + +/// A generic buffer struct useful for items which have no alignment requirements +/// (e.g. Vertex, Index buffers) and are set once and never changed until destroyed. +/// +/// This buffer is mapped to the GPU on creation, so must be initialized with the correct capacity. +#[derive(Debug)] +pub(crate) struct StaticBuffer { + //stored sequentially per mesh iteration + offsets: Vec, + gpu: wgpu::Buffer, + //the static size of the buffer + size: wgpu::BufferAddress, +} + +impl StaticBuffer { + pub fn new( + device: &wgpu::Device, + label: &'static str, + size: u64, + usage: wgpu::BufferUsages, + total_offsets: usize, + ) -> Self { + Self { + offsets: Vec::with_capacity(total_offsets), + gpu: device.create_buffer(&wgpu::BufferDescriptor { + label: Some(label), + size, + usage, + mapped_at_creation: true, + }), + size, + } + } + + /// Resolves pending write operations & unmaps buffer from host memory. + pub fn flush(&self) { + (&self.gpu).unmap(); + } + + /// Returns whether or not the buffer needs to be recreated. This can happen whenever the mesh + /// data is re-submitted. + pub fn needs_recreate(&self, new_size: usize) -> bool { + self.size != new_size as u64 + } + + /// Writes the current vertex data to the gpu buffer with a memcpy & stores its offset. + pub fn write(&mut self, offset: u64, content: &[u8]) { + //offset has to be divisible by 8 for alignment reasons + let actual_offset = if offset % 8 != 0 { + offset + 4 + } else { + offset + }; + + let mut buffer = self + .gpu + .slice(actual_offset..(actual_offset + content.len() as u64)) + .get_mapped_range_mut(); + buffer.copy_from_slice(content); + self.offsets.push(actual_offset); + } + + fn offset_at(&self, index: usize) -> &wgpu::BufferAddress { + self.offsets + .get(index) + .expect(&format!("Offset index {} is not in range.", index)) + } + + /// Returns the slice calculated from the offset stored at the given index. + /// e.g. to calculate the slice for the 2nd mesh in the layer, this would be the offset at index + /// 1 that we stored earlier when writing. + pub fn slice_from_index( + &self, + index: usize, + ) -> wgpu::BufferSlice<'_> { + self.gpu.slice(self.offset_at(index)..) + } +} + +/// Returns true if the current buffer doesn't exist & needs to be created, or if it's too small +/// for the new content. +pub(crate) fn needs_recreate( + buffer: &Option, + new_size: usize, +) -> bool { + match buffer { + None => true, + Some(buf) => buf.needs_recreate(new_size), + } +} diff --git a/wgpu/src/buffers/dynamic_buffers.rs b/wgpu/src/buffers/dynamic_buffers.rs new file mode 100644 index 00000000..d81529ce --- /dev/null +++ b/wgpu/src/buffers/dynamic_buffers.rs @@ -0,0 +1,202 @@ +//! Utilities for uniform buffer operations. +use encase::private::WriteInto; +use encase::ShaderType; +use std::marker::PhantomData; + +// Currently supported dynamic buffers. +enum DynamicBufferType { + Uniform(encase::DynamicUniformBuffer>), + Storage(encase::DynamicStorageBuffer>), +} + +impl DynamicBufferType { + /// Writes the current value to its CPU buffer with proper alignment. + pub(super) fn write( + &mut self, + value: &T, + ) -> wgpu::DynamicOffset { + match self { + DynamicBufferType::Uniform(buf) => buf + .write(value) + .expect("Error when writing to dynamic uniform buffer.") + as u32, + DynamicBufferType::Storage(buf) => buf + .write(value) + .expect("Error when writing to dynamic storage buffer.") + as u32, + } + } + + /// Returns bytearray of aligned CPU buffer. + pub(super) fn get_ref(&self) -> &Vec { + match self { + DynamicBufferType::Uniform(buf) => buf.as_ref(), + DynamicBufferType::Storage(buf) => buf.as_ref(), + } + } + + /// Resets the CPU buffer. + pub(super) fn clear(&mut self) { + match self { + DynamicBufferType::Uniform(buf) => { + buf.as_mut().clear(); + buf.set_offset(0); + } + DynamicBufferType::Storage(buf) => { + buf.as_mut().clear(); + buf.set_offset(0); + } + } + } +} + +//TODO think about making cpu & gpu buffers optional +pub(crate) struct DynamicBuffer { + offsets: Vec, + cpu: DynamicBufferType, + gpu: wgpu::Buffer, + label: &'static str, + size: u64, + _data: PhantomData, +} + +impl DynamicBuffer { + /// Creates a new dynamic uniform buffer. + pub fn uniform(device: &wgpu::Device, label: &'static str) -> Self { + DynamicBuffer::new( + device, + DynamicBufferType::Uniform(encase::DynamicUniformBuffer::new( + Vec::new(), + )), + label, + wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST, + ) + } + + /// Creates a new dynamic storage buffer. + pub fn storage(device: &wgpu::Device, label: &'static str) -> Self { + DynamicBuffer::new( + device, + DynamicBufferType::Storage(encase::DynamicStorageBuffer::new( + Vec::new(), + )), + label, + wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_DST, + ) + } + + fn new( + device: &wgpu::Device, + dynamic_buffer_type: DynamicBufferType, + label: &'static str, + usage: wgpu::BufferUsages, + ) -> Self { + let initial_size = u64::from(T::min_size()); + + Self { + offsets: Vec::new(), + cpu: dynamic_buffer_type, + gpu: DynamicBuffer::::create_gpu_buffer( + device, + label, + usage, + initial_size, + ), + label, + size: initial_size, + _data: Default::default(), + } + } + + fn create_gpu_buffer( + device: &wgpu::Device, + label: &'static str, + usage: wgpu::BufferUsages, + size: u64, + ) -> wgpu::Buffer { + device.create_buffer(&wgpu::BufferDescriptor { + label: Some(label), + size, + usage, + mapped_at_creation: false, + }) + } + + /// Write a new value to the CPU buffer with proper alignment. Stores the returned offset value + /// in the buffer for future use. + pub fn push(&mut self, value: &T) { + //this write operation on the buffer will adjust for uniform alignment requirements + let offset = self.cpu.write(value); + self.offsets.push(offset as u32); + } + + /// Resize buffer contents if necessary. This will re-create the GPU buffer if current size is + /// less than the newly computed size from the CPU buffer. + pub fn resize(&mut self, device: &wgpu::Device) -> bool { + let new_size = self.cpu.get_ref().len() as u64; + + if self.size < new_size { + let usages = match self.cpu { + DynamicBufferType::Uniform(_) => { + wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST + } + DynamicBufferType::Storage(_) => { + wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_DST + } + }; + + //Re-create the GPU buffer since it needs to be resized. + self.gpu = DynamicBuffer::::create_gpu_buffer( + device, self.label, usages, new_size, + ); + self.size = new_size; + true + } else { + false + } + } + + /// Write the contents of this dynamic buffer to the GPU via staging belt command. + pub fn write( + &mut self, + device: &wgpu::Device, + staging_belt: &mut wgpu::util::StagingBelt, + encoder: &mut wgpu::CommandEncoder, + ) { + let size = self.cpu.get_ref().len(); + + if let Some(buffer_size) = wgpu::BufferSize::new(size as u64) { + let mut buffer = staging_belt.write_buffer( + encoder, + &self.gpu, + 0, + buffer_size, + device, + ); + + buffer.copy_from_slice(self.cpu.get_ref()); + } + } + + // Gets the aligned offset at the given index from the CPU buffer. + pub fn offset_at_index(&self, index: usize) -> wgpu::DynamicOffset { + let offset = self + .offsets + .get(index) + .expect(&format!("Index {} not found in offsets.", index)) + .clone(); + + offset + } + + /// Returns a reference to the GPU buffer. + pub fn raw(&self) -> &wgpu::Buffer { + &self.gpu + } + + /// Reset the buffer. + pub fn clear(&mut self) { + self.offsets.clear(); + self.cpu.clear(); + } +} -- cgit