aboutsummaryrefslogtreecommitdiff
path: root/stockton-render/src/draw/buffers
diff options
context:
space:
mode:
authortcmal <me@aria.rip>2024-08-25 17:44:23 +0100
committertcmal <me@aria.rip>2024-08-25 17:44:23 +0100
commitfb996488aa651cb2e7f46abc083c4318b47e77cd (patch)
treed1513d479b6069abca290e271f439cd1ef553383 /stockton-render/src/draw/buffers
parenta82e16c92a026b2fbe3a40a21d7e690242e32ba6 (diff)
wip refactor(render): more work on draw passes
Diffstat (limited to 'stockton-render/src/draw/buffers')
-rw-r--r--stockton-render/src/draw/buffers/dedicated_image.rs325
-rw-r--r--stockton-render/src/draw/buffers/draw_buffers.rs43
-rw-r--r--stockton-render/src/draw/buffers/mod.rs68
-rw-r--r--stockton-render/src/draw/buffers/staged.rs188
4 files changed, 624 insertions, 0 deletions
diff --git a/stockton-render/src/draw/buffers/dedicated_image.rs b/stockton-render/src/draw/buffers/dedicated_image.rs
new file mode 100644
index 0000000..38a23c9
--- /dev/null
+++ b/stockton-render/src/draw/buffers/dedicated_image.rs
@@ -0,0 +1,325 @@
+//! A dedicated image. Used for depth buffers.
+
+use super::create_buffer;
+use crate::draw::texture::{LoadableImage, PIXEL_SIZE};
+use crate::types::*;
+
+use std::{array::IntoIter, convert::TryInto, iter::empty, mem::ManuallyDrop};
+
+use anyhow::{Context, Result};
+use hal::{
+ buffer::Usage as BufUsage,
+ format::{Aspects, Format, Swizzle},
+ image::{SubresourceRange, Usage, Usage as ImgUsage, ViewKind},
+ memory,
+ memory::{Properties, Segment},
+ MemoryTypeId,
+};
+use thiserror::Error;
+
+/// Holds an image that's loaded into GPU memory dedicated only to that image, bypassing the memory allocator.
+pub struct DedicatedLoadedImage {
+ /// The GPU Image handle
+ image: ManuallyDrop<ImageT>,
+
+ /// The full view of the image
+ pub image_view: ManuallyDrop<ImageViewT>,
+
+ /// The memory backing the image
+ memory: ManuallyDrop<MemoryT>,
+}
+
+#[derive(Debug, Error)]
+pub enum ImageLoadError {
+ #[error("No suitable memory type for image memory")]
+ NoMemoryTypes,
+}
+
+impl DedicatedLoadedImage {
+ pub fn new(
+ device: &mut DeviceT,
+ adapter: &Adapter,
+ format: Format,
+ usage: Usage,
+ resources: SubresourceRange,
+ width: usize,
+ height: usize,
+ ) -> Result<DedicatedLoadedImage> {
+ let (memory, image_ref) = {
+ // Round up the size to align properly
+ let initial_row_size = PIXEL_SIZE * width;
+ let limits = adapter.physical_device.properties().limits;
+ let row_alignment_mask = limits.optimal_buffer_copy_pitch_alignment as u32 - 1;
+
+ let row_size =
+ ((initial_row_size as u32 + row_alignment_mask) & !row_alignment_mask) as usize;
+ debug_assert!(row_size as usize >= initial_row_size);
+
+ // Make the image
+ let mut image_ref = unsafe {
+ use hal::image::{Kind, Tiling, ViewCapabilities};
+
+ device.create_image(
+ Kind::D2(width as u32, height as u32, 1, 1),
+ 1,
+ format,
+ Tiling::Optimal,
+ usage,
+ memory::SparseFlags::empty(),
+ ViewCapabilities::empty(),
+ )
+ }
+ .context("Error creating image")?;
+
+ // Allocate memory
+ let memory = unsafe {
+ let requirements = device.get_image_requirements(&image_ref);
+
+ let memory_type_id = adapter
+ .physical_device
+ .memory_properties()
+ .memory_types
+ .iter()
+ .enumerate()
+ .find(|&(id, memory_type)| {
+ requirements.type_mask & (1 << id) != 0
+ && memory_type.properties.contains(Properties::DEVICE_LOCAL)
+ })
+ .map(|(id, _)| MemoryTypeId(id))
+ .ok_or(ImageLoadError::NoMemoryTypes)?;
+
+ let memory = device
+ .allocate_memory(memory_type_id, requirements.size)
+ .context("Error allocating memory for image")?;
+
+ device
+ .bind_image_memory(&memory, 0, &mut image_ref)
+ .context("Error binding memory to image")?;
+
+ memory
+ };
+
+ (memory, image_ref)
+ };
+
+ // Create ImageView and sampler
+ let image_view = unsafe {
+ device.create_image_view(
+ &image_ref,
+ ViewKind::D2,
+ format,
+ Swizzle::NO,
+ ImgUsage::DEPTH_STENCIL_ATTACHMENT,
+ resources,
+ )
+ }
+ .context("Error creating image view")?;
+
+ Ok(DedicatedLoadedImage {
+ image: ManuallyDrop::new(image_ref),
+ image_view: ManuallyDrop::new(image_view),
+ memory: ManuallyDrop::new(memory),
+ })
+ }
+
+ /// Load the given image
+ pub fn load<T: LoadableImage>(
+ &mut self,
+ img: T,
+ device: &mut DeviceT,
+ adapter: &Adapter,
+ command_queue: &mut QueueT,
+ command_pool: &mut CommandPoolT,
+ ) -> Result<()> {
+ let initial_row_size = PIXEL_SIZE * img.width() as usize;
+ let limits = adapter.physical_device.properties().limits;
+ let row_alignment_mask = limits.optimal_buffer_copy_pitch_alignment as u32 - 1;
+
+ let row_size =
+ ((initial_row_size as u32 + row_alignment_mask) & !row_alignment_mask) as usize;
+ let total_size = (row_size * (img.height() as usize)) as u64;
+ debug_assert!(row_size as usize >= initial_row_size);
+
+ // Make a staging buffer
+ let (staging_buffer, mut staging_memory) = create_buffer(
+ device,
+ adapter,
+ BufUsage::TRANSFER_SRC,
+ memory::Properties::CPU_VISIBLE | memory::Properties::COHERENT,
+ total_size,
+ )
+ .context("Error creating staging buffer")?;
+
+ // Copy everything into it
+ unsafe {
+ let mapped_memory: *mut u8 = std::mem::transmute(
+ device
+ .map_memory(
+ &mut staging_memory,
+ Segment {
+ offset: 0,
+ size: None,
+ },
+ )
+ .context("Error mapping staging memory")?,
+ );
+
+ for y in 0..img.height() as usize {
+ let dest_base: isize = (y * row_size).try_into()?;
+ img.copy_row(y as u32, mapped_memory.offset(dest_base));
+ }
+
+ device.unmap_memory(&mut staging_memory);
+ }
+
+ // Copy from staging to image memory
+ let buf = unsafe {
+ use hal::command::{BufferImageCopy, CommandBufferFlags};
+ use hal::image::{Access, Extent, Layout, Offset, SubresourceLayers};
+ use hal::memory::Barrier;
+ use hal::pso::PipelineStage;
+
+ // Get a command buffer
+ let mut buf = command_pool.allocate_one(hal::command::Level::Primary);
+ buf.begin_primary(CommandBufferFlags::ONE_TIME_SUBMIT);
+
+ // Setup the layout of our image for copying
+ let image_barrier = Barrier::Image {
+ states: (Access::empty(), Layout::Undefined)
+ ..(Access::TRANSFER_WRITE, Layout::TransferDstOptimal),
+ target: &(*self.image),
+ families: None,
+ range: SubresourceRange {
+ aspects: Aspects::COLOR,
+ level_start: 0,
+ level_count: Some(1),
+ layer_start: 0,
+ layer_count: Some(1),
+ },
+ };
+ buf.pipeline_barrier(
+ PipelineStage::TOP_OF_PIPE..PipelineStage::TRANSFER,
+ memory::Dependencies::empty(),
+ IntoIter::new([image_barrier]),
+ );
+
+ // Copy from buffer to image
+ buf.copy_buffer_to_image(
+ &staging_buffer,
+ &(*self.image),
+ Layout::TransferDstOptimal,
+ IntoIter::new([BufferImageCopy {
+ buffer_offset: 0,
+ buffer_width: (row_size / PIXEL_SIZE) as u32,
+ buffer_height: img.height(),
+ image_layers: SubresourceLayers {
+ aspects: Aspects::COLOR,
+ level: 0,
+ layers: 0..1,
+ },
+ image_offset: Offset { x: 0, y: 0, z: 0 },
+ image_extent: Extent {
+ width: img.width(),
+ height: img.height(),
+ depth: 1,
+ },
+ }]),
+ );
+
+ // Setup the layout of our image for shaders
+ let image_barrier = Barrier::Image {
+ states: (Access::TRANSFER_WRITE, Layout::TransferDstOptimal)
+ ..(Access::SHADER_READ, Layout::ShaderReadOnlyOptimal),
+ target: &(*self.image),
+ families: None,
+ range: SubresourceRange {
+ aspects: Aspects::COLOR,
+ level_start: 0,
+ level_count: Some(1),
+ layer_start: 0,
+ layer_count: Some(1),
+ },
+ };
+
+ buf.pipeline_barrier(
+ PipelineStage::TRANSFER..PipelineStage::FRAGMENT_SHADER,
+ memory::Dependencies::empty(),
+ IntoIter::new([image_barrier]),
+ );
+
+ buf.finish();
+
+ buf
+ };
+
+ // Submit our commands and wait for them to finish
+ unsafe {
+ let mut setup_finished = device
+ .create_fence(false)
+ .context("Error creating setup_finished fence")?;
+ command_queue.submit(
+ IntoIter::new([&buf]),
+ empty(),
+ empty(),
+ Some(&mut setup_finished),
+ );
+
+ device
+ .wait_for_fence(&setup_finished, core::u64::MAX)
+ .context("Error waiting for image load to finish")?;
+ device.destroy_fence(setup_finished);
+ };
+
+ // Clean up temp resources
+ unsafe {
+ command_pool.free(std::iter::once(buf));
+
+ device.free_memory(staging_memory);
+ device.destroy_buffer(staging_buffer);
+ }
+
+ Ok(())
+ }
+
+ /// Load the given image into a new buffer
+ pub fn load_into_new<T: LoadableImage>(
+ img: T,
+ device: &mut DeviceT,
+ adapter: &Adapter,
+ command_queue: &mut QueueT,
+ command_pool: &mut CommandPoolT,
+ format: Format,
+ usage: Usage,
+ ) -> Result<DedicatedLoadedImage> {
+ let mut loaded_image = Self::new(
+ device,
+ adapter,
+ format,
+ usage | Usage::TRANSFER_DST,
+ SubresourceRange {
+ aspects: Aspects::COLOR,
+ level_start: 0,
+ level_count: Some(1),
+ layer_start: 0,
+ layer_count: Some(1),
+ },
+ img.width() as usize,
+ img.height() as usize,
+ )?;
+ loaded_image.load(img, device, adapter, command_queue, command_pool)?;
+
+ Ok(loaded_image)
+ }
+
+ /// Properly frees/destroys all the objects in this struct
+ /// Dropping without doing this is a bad idea
+ pub fn deactivate(self, device: &mut DeviceT) {
+ unsafe {
+ use core::ptr::read;
+
+ device.destroy_image_view(ManuallyDrop::into_inner(read(&self.image_view)));
+ device.destroy_image(ManuallyDrop::into_inner(read(&self.image)));
+ device.free_memory(ManuallyDrop::into_inner(read(&self.memory)));
+ }
+ }
+}
diff --git a/stockton-render/src/draw/buffers/draw_buffers.rs b/stockton-render/src/draw/buffers/draw_buffers.rs
new file mode 100644
index 0000000..5baec92
--- /dev/null
+++ b/stockton-render/src/draw/buffers/draw_buffers.rs
@@ -0,0 +1,43 @@
+//! A vertex and index buffer set for drawing
+
+use super::StagedBuffer;
+use crate::types::*;
+
+use anyhow::{Context, Result};
+use hal::buffer::Usage;
+use std::mem::ManuallyDrop;
+
+/// Initial size of vertex buffer. TODO: Way of overriding this
+pub const INITIAL_VERT_SIZE: u64 = 3 * 3000;
+
+/// Initial size of index buffer. TODO: Way of overriding this
+pub const INITIAL_INDEX_SIZE: u64 = 3000;
+
+/// The buffers used for drawing, ie index and vertex buffer
+pub struct DrawBuffers<'a, T: Sized> {
+ pub vertex_buffer: ManuallyDrop<StagedBuffer<'a, T>>,
+ pub index_buffer: ManuallyDrop<StagedBuffer<'a, (u16, u16, u16)>>,
+}
+
+impl<'a, T> DrawBuffers<'a, T> {
+ pub fn new(device: &mut DeviceT, adapter: &Adapter) -> Result<DrawBuffers<'a, T>> {
+ let vert = StagedBuffer::new(device, adapter, Usage::VERTEX, INITIAL_VERT_SIZE)
+ .context("Error creating vertex buffer")?;
+ let index = StagedBuffer::new(device, adapter, Usage::INDEX, INITIAL_INDEX_SIZE)
+ .context("Error creating index buffer")?;
+
+ Ok(DrawBuffers {
+ vertex_buffer: ManuallyDrop::new(vert),
+ index_buffer: ManuallyDrop::new(index),
+ })
+ }
+
+ pub fn deactivate(self, device: &mut DeviceT) {
+ unsafe {
+ use core::ptr::read;
+
+ ManuallyDrop::into_inner(read(&self.vertex_buffer)).deactivate(device);
+ ManuallyDrop::into_inner(read(&self.index_buffer)).deactivate(device);
+ }
+ }
+}
diff --git a/stockton-render/src/draw/buffers/mod.rs b/stockton-render/src/draw/buffers/mod.rs
new file mode 100644
index 0000000..5093872
--- /dev/null
+++ b/stockton-render/src/draw/buffers/mod.rs
@@ -0,0 +1,68 @@
+//! All sorts of buffers
+
+use std::ops::IndexMut;
+
+use crate::{error::EnvironmentError, types::*};
+
+use anyhow::{Context, Result};
+use hal::{
+ buffer::Usage,
+ memory::{Properties, SparseFlags},
+ MemoryTypeId,
+};
+
+mod dedicated_image;
+mod draw_buffers;
+mod staged;
+
+pub use dedicated_image::DedicatedLoadedImage;
+pub use draw_buffers::DrawBuffers;
+pub use staged::StagedBuffer;
+
+/// Create a buffer of the given specifications, allocating more device memory.
+// TODO: Use a different memory allocator?
+pub(crate) fn create_buffer(
+ device: &mut DeviceT,
+ adapter: &Adapter,
+ usage: Usage,
+ properties: Properties,
+ size: u64,
+) -> Result<(BufferT, MemoryT)> {
+ let mut buffer = unsafe { device.create_buffer(size, usage, SparseFlags::empty()) }
+ .context("Error creating buffer")?;
+
+ let requirements = unsafe { device.get_buffer_requirements(&buffer) };
+ let memory_type_id = adapter
+ .physical_device
+ .memory_properties()
+ .memory_types
+ .iter()
+ .enumerate()
+ .find(|&(id, memory_type)| {
+ requirements.type_mask & (1 << id) != 0 && memory_type.properties.contains(properties)
+ })
+ .map(|(id, _)| MemoryTypeId(id))
+ .ok_or(EnvironmentError::NoMemoryTypes)?;
+
+ let memory = unsafe { device.allocate_memory(memory_type_id, requirements.size) }
+ .context("Error allocating memory")?;
+
+ unsafe { device.bind_buffer_memory(&memory, 0, &mut buffer) }
+ .context("Error binding memory to buffer")?;
+
+ Ok((buffer, memory))
+}
+
+/// A buffer that can be modified by the CPU
+pub trait ModifiableBuffer: IndexMut<usize> {
+ /// Get a handle to the underlying GPU buffer
+ fn get_buffer(&mut self) -> &BufferT;
+
+ /// Commit all changes to GPU memory, returning a handle to the GPU buffer
+ fn commit<'a>(
+ &'a mut self,
+ device: &DeviceT,
+ command_queue: &mut QueueT,
+ command_pool: &mut CommandPoolT,
+ ) -> Result<&'a BufferT>;
+}
diff --git a/stockton-render/src/draw/buffers/staged.rs b/stockton-render/src/draw/buffers/staged.rs
new file mode 100644
index 0000000..f92c41d
--- /dev/null
+++ b/stockton-render/src/draw/buffers/staged.rs
@@ -0,0 +1,188 @@
+//! A buffer that can be written to by the CPU using staging memory
+
+use super::{create_buffer, ModifiableBuffer};
+use crate::{error::EnvironmentError, types::*};
+
+use core::mem::{size_of, ManuallyDrop};
+use std::{
+ convert::TryInto,
+ iter::{empty, once},
+ ops::{Index, IndexMut},
+};
+
+use anyhow::{Context, Result};
+use hal::{
+ buffer::Usage,
+ memory::{Properties, Segment, SparseFlags},
+ MemoryTypeId,
+};
+
+/// A GPU buffer that is written to using a staging buffer
+pub struct StagedBuffer<'a, T: Sized> {
+ /// CPU-visible buffer
+ staged_buffer: ManuallyDrop<BufferT>,
+
+ /// CPU-visible memory
+ staged_memory: ManuallyDrop<MemoryT>,
+
+ /// GPU Buffer
+ buffer: ManuallyDrop<BufferT>,
+
+ /// GPU Memory
+ memory: ManuallyDrop<MemoryT>,
+
+ /// Where staged buffer is mapped in CPU memory
+ staged_mapped_memory: &'a mut [T],
+
+ /// If staged memory has been changed since last `commit`
+ staged_is_dirty: bool,
+
+ /// The highest index in the buffer that's been written to.
+ pub highest_used: usize,
+}
+
+impl<'a, T: Sized> StagedBuffer<'a, T> {
+ /// size is the size in T
+ pub fn new(device: &mut DeviceT, adapter: &Adapter, usage: Usage, size: u64) -> Result<Self> {
+ // Convert size to bytes
+ let size_bytes = size * size_of::<T>() as u64;
+
+ // Get CPU-visible buffer
+ let (staged_buffer, mut staged_memory) = create_buffer(
+ device,
+ adapter,
+ Usage::TRANSFER_SRC,
+ Properties::CPU_VISIBLE,
+ size_bytes,
+ )
+ .context("Error creating staging buffer")?;
+
+ // Get GPU Buffer
+ let (buffer, memory) = create_buffer(
+ device,
+ adapter,
+ Usage::TRANSFER_DST | usage,
+ Properties::DEVICE_LOCAL | Properties::COHERENT,
+ size_bytes,
+ )
+ .context("Error creating GPU buffer")?;
+
+ // Map it somewhere and get a slice to that memory
+ let staged_mapped_memory = unsafe {
+ let ptr = device
+ .map_memory(
+ &mut staged_memory,
+ Segment {
+ offset: 0,
+ size: Some(size_bytes),
+ },
+ )
+ .context("Error mapping staged memory")?;
+
+ std::slice::from_raw_parts_mut(ptr as *mut T, size.try_into()?)
+ };
+
+ Ok(StagedBuffer {
+ staged_buffer: ManuallyDrop::new(staged_buffer),
+ staged_memory: ManuallyDrop::new(staged_memory),
+ buffer: ManuallyDrop::new(buffer),
+ memory: ManuallyDrop::new(memory),
+ staged_mapped_memory,
+ staged_is_dirty: false,
+ highest_used: 0,
+ })
+ }
+
+ /// Call this before dropping
+ pub(crate) fn deactivate(mut self, device: &mut DeviceT) {
+ unsafe {
+ device.unmap_memory(&mut self.staged_memory);
+
+ device.free_memory(ManuallyDrop::take(&mut self.staged_memory));
+ device.destroy_buffer(ManuallyDrop::take(&mut self.staged_buffer));
+
+ device.free_memory(ManuallyDrop::take(&mut self.memory));
+ device.destroy_buffer(ManuallyDrop::take(&mut self.buffer));
+ };
+ }
+}
+
+impl<'a, T: Sized> ModifiableBuffer for StagedBuffer<'a, T> {
+ fn get_buffer(&mut self) -> &BufferT {
+ &self.buffer
+ }
+
+ fn commit<'b>(
+ &'b mut self,
+ device: &DeviceT,
+ command_queue: &mut QueueT,
+ command_pool: &mut CommandPoolT,
+ ) -> Result<&'b BufferT> {
+ // Only commit if there's changes to commit.
+ if self.staged_is_dirty {
+ // Copy from staged to buffer
+ let buf = unsafe {
+ use hal::command::{BufferCopy, CommandBufferFlags};
+ // Get a command buffer
+ let mut buf = command_pool.allocate_one(hal::command::Level::Primary);
+
+ // Put in our copy command
+ buf.begin_primary(CommandBufferFlags::ONE_TIME_SUBMIT);
+ buf.copy_buffer(
+ &self.staged_buffer,
+ &self.buffer,
+ std::iter::once(BufferCopy {
+ src: 0,
+ dst: 0,
+ size: ((self.highest_used + 1) * size_of::<T>()) as u64,
+ }),
+ );
+ buf.finish();
+
+ buf
+ };
+
+ // Submit it and wait for completion
+ // TODO: Proper management of transfer operations
+ unsafe {
+ let mut copy_finished = device.create_fence(false)?;
+ command_queue.submit(
+ once(&buf),
+ empty::<(&SemaphoreT, hal::pso::PipelineStage)>(),
+ empty::<&SemaphoreT>(),
+ Some(&mut copy_finished),
+ );
+
+ device
+ .wait_for_fence(&copy_finished, core::u64::MAX)
+ .context("Error waiting for fence")?;
+
+ // Destroy temporary resources
+ device.destroy_fence(copy_finished);
+ command_pool.free(once(buf));
+ }
+
+ self.staged_is_dirty = false;
+ }
+
+ Ok(&self.buffer)
+ }
+}
+
+impl<'a, T: Sized> Index<usize> for StagedBuffer<'a, T> {
+ type Output = T;
+
+ fn index(&self, index: usize) -> &Self::Output {
+ &self.staged_mapped_memory[index]
+ }
+}
+
+impl<'a, T: Sized> IndexMut<usize> for StagedBuffer<'a, T> {
+ fn index_mut(&mut self, index: usize) -> &mut Self::Output {
+ self.staged_is_dirty = true;
+ if index > self.highest_used {
+ self.highest_used = index;
+ }
+ &mut self.staged_mapped_memory[index]
+ }
+}