summaryrefslogtreecommitdiffstats
path: root/wgpu/src/buffers
diff options
context:
space:
mode:
authorLibravatar shan <shankern@protonmail.com>2022-09-29 10:52:58 -0700
committerLibravatar shan <shankern@protonmail.com>2022-09-29 10:52:58 -0700
commit00a8a167122301983753a2f4b43d136c79a7d5cb (patch)
tree99596e40da4150eab2d9e862d84373fcf02a548d /wgpu/src/buffers
parent97f385e093711c269df315b28f76e66e0220e22a (diff)
downloadiced-00a8a167122301983753a2f4b43d136c79a7d5cb.tar.gz
iced-00a8a167122301983753a2f4b43d136c79a7d5cb.tar.bz2
iced-00a8a167122301983753a2f4b43d136c79a7d5cb.zip
Adds linear gradient support to 2D meshes in the canvas widget.
Diffstat (limited to 'wgpu/src/buffers')
-rw-r--r--wgpu/src/buffers/buffer.rs91
-rw-r--r--wgpu/src/buffers/dynamic_buffers.rs202
2 files changed, 293 insertions, 0 deletions
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<wgpu::BufferAddress>,
+ 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<T>(
+ &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<StaticBuffer>,
+ 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<Vec<u8>>),
+ Storage(encase::DynamicStorageBuffer<Vec<u8>>),
+}
+
+impl DynamicBufferType {
+ /// Writes the current value to its CPU buffer with proper alignment.
+ pub(super) fn write<T: ShaderType + WriteInto>(
+ &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<u8> {
+ 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<T: ShaderType> {
+ offsets: Vec<wgpu::DynamicOffset>,
+ cpu: DynamicBufferType,
+ gpu: wgpu::Buffer,
+ label: &'static str,
+ size: u64,
+ _data: PhantomData<T>,
+}
+
+impl<T: ShaderType + WriteInto> DynamicBuffer<T> {
+ /// 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::<T>::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::<T>::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();
+ }
+}