diff options
Diffstat (limited to 'stockton-render/src/draw/buffers')
-rw-r--r-- | stockton-render/src/draw/buffers/dedicated_image.rs | 325 | ||||
-rw-r--r-- | stockton-render/src/draw/buffers/draw_buffers.rs | 43 | ||||
-rw-r--r-- | stockton-render/src/draw/buffers/mod.rs | 68 | ||||
-rw-r--r-- | stockton-render/src/draw/buffers/staged.rs | 188 |
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(©_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] + } +} |