From c48b54f3fb7bbe9046915eb99eca02fa84dc55c9 Mon Sep 17 00:00:00 2001 From: tcmal Date: Sun, 25 Aug 2024 17:44:22 +0100 Subject: feat(render): multithreaded texture loading also a bunch of supporting changes --- stockton-render/src/draw/texture/image.rs | 639 +----------------------------- 1 file changed, 9 insertions(+), 630 deletions(-) (limited to 'stockton-render/src/draw/texture/image.rs') diff --git a/stockton-render/src/draw/texture/image.rs b/stockton-render/src/draw/texture/image.rs index 7cf84a7..aea1cb0 100644 --- a/stockton-render/src/draw/texture/image.rs +++ b/stockton-render/src/draw/texture/image.rs @@ -15,34 +15,19 @@ * with this program. If not, see . */ -use core::{ - mem::{size_of, ManuallyDrop}, - ptr::copy_nonoverlapping, -}; -use hal::{ - buffer::Usage as BufUsage, - format::{Aspects, Format, Swizzle}, - image::{SubresourceRange, Usage as ImgUsage, ViewKind}, - memory::{Dependencies as MemDependencies, Properties as MemProperties}, - prelude::*, - queue::Submission, - MemoryTypeId, -}; -use image::RgbaImage; -use rendy_memory::{Allocator, Block}; -use std::{convert::TryInto, iter::once}; +use super::PIXEL_SIZE; -use crate::draw::buffer::create_buffer; -use crate::types::*; +use core::ptr::copy_nonoverlapping; +use std::convert::TryInto; -/// The size of each pixel in an image -const PIXEL_SIZE: usize = size_of::() * 4; +use image::RgbaImage; /// An object that can be loaded as an image into GPU memory pub trait LoadableImage { fn width(&self) -> u32; fn height(&self) -> u32; fn copy_row(&self, y: u32, ptr: *mut u8); + unsafe fn copy_into(&self, ptr: *mut u8, row_size: usize); } impl LoadableImage for RgbaImage { @@ -63,617 +48,11 @@ impl LoadableImage for RgbaImage { copy_nonoverlapping(row.as_ptr(), ptr, row.len()); } } -} - -/// Holds an image that's loaded into GPU memory and can be sampled from -pub struct LoadedImage { - /// The GPU Image handle - image: ManuallyDrop, - - /// The full view of the image - pub image_view: ManuallyDrop, - - /// The memory backing the image - memory: ManuallyDrop, -} - -pub fn create_image_view( - device: &mut Device, - adapter: &Adapter, - allocator: &mut DynamicAllocator, - format: Format, - usage: ImgUsage, - width: usize, - height: usize, -) -> Result<(DynamicBlock, Image), &'static str> { - // Round up the size to align properly - let initial_row_size = PIXEL_SIZE * width; - let limits = adapter.physical_device.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, - ViewCapabilities::empty(), - ) - } - .map_err(|_| "Couldn't create image")?; - - // Allocate memory - let (block, _) = unsafe { - let requirements = device.get_image_requirements(&image_ref); - - allocator.alloc(device, requirements.size, requirements.alignment) - } - .map_err(|_| "Out of memory")?; - - unsafe { - device - .bind_image_memory(&block.memory(), block.range().start, &mut image_ref) - .map_err(|_| "Couldn't bind memory to image")?; - } - - Ok((block, image_ref)) -} - -impl LoadedImage { - pub fn new( - device: &mut Device, - adapter: &Adapter, - allocator: &mut DynamicAllocator, - format: Format, - usage: ImgUsage, - resources: SubresourceRange, - width: usize, - height: usize, - ) -> Result { - let (memory, image_ref) = - create_image_view(device, adapter, allocator, format, usage, width, height)?; - - // Create ImageView and sampler - let image_view = unsafe { - device.create_image_view(&image_ref, ViewKind::D2, format, Swizzle::NO, resources) - } - .map_err(|_| "Couldn't create the image view!")?; - - Ok(LoadedImage { - image: ManuallyDrop::new(image_ref), - image_view: ManuallyDrop::new(image_view), - memory: ManuallyDrop::new(memory), - }) - } - - /// Load the given image - pub fn load( - &mut self, - img: T, - device: &mut Device, - adapter: &Adapter, - command_queue: &mut CommandQueue, - command_pool: &mut CommandPool, - ) -> Result<(), &'static str> { - let initial_row_size = PIXEL_SIZE * img.width() as usize; - let limits = adapter.physical_device.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, staging_memory) = create_buffer( - device, - adapter, - BufUsage::TRANSFER_SRC, - MemProperties::CPU_VISIBLE | MemProperties::COHERENT, - total_size, - ) - .map_err(|_| "Couldn't create staging buffer")?; - - // Copy everything into it - unsafe { - let mapped_memory: *mut u8 = std::mem::transmute( - device - .map_memory(&staging_memory, 0..total_size) - .map_err(|_| "Couldn't map buffer memory")?, - ); - - for y in 0..img.height() as usize { - let dest_base: isize = (y * row_size).try_into().unwrap(); - img.copy_row(y as u32, mapped_memory.offset(dest_base)); - } - - device.unmap_memory(&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, - levels: 0..1, - layers: 0..1, - }, - }; - buf.pipeline_barrier( - PipelineStage::TOP_OF_PIPE..PipelineStage::TRANSFER, - MemDependencies::empty(), - &[image_barrier], - ); - - // Copy from buffer to image - buf.copy_buffer_to_image( - &staging_buffer, - &(*self.image), - Layout::TransferDstOptimal, - &[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, - levels: 0..1, - layers: 0..1, - }, - }; - - buf.pipeline_barrier( - PipelineStage::TRANSFER..PipelineStage::FRAGMENT_SHADER, - MemDependencies::empty(), - &[image_barrier], - ); - - buf.finish(); - - buf - }; - - // Submit our commands and wait for them to finish - unsafe { - let setup_finished = device.create_fence(false).unwrap(); - command_queue.submit::<_, _, Semaphore, _, _>( - Submission { - command_buffers: &[&buf], - wait_semaphores: std::iter::empty::<_>(), - signal_semaphores: std::iter::empty::<_>(), - }, - Some(&setup_finished), - ); - - device - .wait_for_fence(&setup_finished, core::u64::MAX) - .unwrap(); - device.destroy_fence(setup_finished); - }; - - // Clean up temp resources - unsafe { - command_pool.free(once(buf)); - - device.free_memory(staging_memory); - device.destroy_buffer(staging_buffer); - } - - Ok(()) - } - - /// Properly frees/destroys all the objects in this struct - /// Dropping without doing this is a bad idea - pub fn deactivate(self, device: &mut Device, allocator: &mut DynamicAllocator) { - 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))); - allocator.free(device, ManuallyDrop::into_inner(read(&self.memory))); - } - } -} - -pub struct SampledImage { - pub image: ManuallyDrop, - pub sampler: ManuallyDrop, -} - -impl SampledImage { - pub fn new( - device: &mut Device, - adapter: &Adapter, - allocator: &mut DynamicAllocator, - format: Format, - usage: ImgUsage, - width: usize, - height: usize, - ) -> Result { - let image = LoadedImage::new( - device, - adapter, - allocator, - format, - usage | ImgUsage::SAMPLED, - SubresourceRange { - aspects: Aspects::COLOR, - levels: 0..1, - layers: 0..1, - }, - width, - height, - )?; - - let sampler = unsafe { - use hal::image::{Filter, SamplerDesc, WrapMode}; - - device.create_sampler(&SamplerDesc::new(Filter::Nearest, WrapMode::Tile)) - } - .map_err(|_| "Couldn't create the sampler!")?; - - Ok(SampledImage { - image: ManuallyDrop::new(image), - sampler: ManuallyDrop::new(sampler), - }) - } - - pub fn load_into_new( - img: T, - device: &mut Device, - adapter: &Adapter, - allocator: &mut DynamicAllocator, - command_queue: &mut CommandQueue, - command_pool: &mut CommandPool, - format: Format, - usage: ImgUsage, - ) -> Result { - let mut sampled_image = SampledImage::new( - device, - adapter, - allocator, - format, - usage | ImgUsage::TRANSFER_DST, - img.width() as usize, - img.height() as usize, - )?; - sampled_image - .image - .load(img, device, adapter, command_queue, command_pool)?; - - Ok(sampled_image) - } - - pub fn deactivate(self, device: &mut Device, allocator: &mut DynamicAllocator) { - unsafe { - use core::ptr::read; - - device.destroy_sampler(ManuallyDrop::into_inner(read(&self.sampler))); - - ManuallyDrop::into_inner(read(&self.image)).deactivate(device, allocator); - } - } -} - -/// 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, - - /// The full view of the image - pub image_view: ManuallyDrop, - - /// The memory backing the image - memory: ManuallyDrop, -} - -impl DedicatedLoadedImage { - pub fn new( - device: &mut Device, - adapter: &Adapter, - format: Format, - usage: ImgUsage, - resources: SubresourceRange, - width: usize, - height: usize, - ) -> Result { - let (memory, image_ref) = { - // Round up the size to align properly - let initial_row_size = PIXEL_SIZE * width; - let limits = adapter.physical_device.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, - ViewCapabilities::empty(), - ) - } - .map_err(|_| "Couldn't create image")?; - - // Allocate memory - - // 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(MemProperties::DEVICE_LOCAL) - }) - .map(|(id, _)| MemoryTypeId(id)) - .ok_or("Couldn't find a memory type for image memory")?; - - let memory = device - .allocate_memory(memory_type_id, requirements.size) - .map_err(|_| "Couldn't allocate image memory")?; - - device - .bind_image_memory(&memory, 0, &mut image_ref) - .map_err(|_| "Couldn't bind memory to image")?; - - Ok(memory) - }?; - - Ok((memory, image_ref)) - }?; - - // Create ImageView and sampler - let image_view = unsafe { - device.create_image_view(&image_ref, ViewKind::D2, format, Swizzle::NO, resources) - } - .map_err(|_| "Couldn't create the 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( - &mut self, - img: T, - device: &mut Device, - adapter: &Adapter, - command_queue: &mut CommandQueue, - command_pool: &mut CommandPool, - ) -> Result<(), &'static str> { - let initial_row_size = PIXEL_SIZE * img.width() as usize; - let limits = adapter.physical_device.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, staging_memory) = create_buffer( - device, - adapter, - BufUsage::TRANSFER_SRC, - MemProperties::CPU_VISIBLE | MemProperties::COHERENT, - total_size, - ) - .map_err(|_| "Couldn't create staging buffer")?; - - // Copy everything into it - unsafe { - let mapped_memory: *mut u8 = std::mem::transmute( - device - .map_memory(&staging_memory, 0..total_size) - .map_err(|_| "Couldn't map buffer memory")?, - ); - - for y in 0..img.height() as usize { - let dest_base: isize = (y * row_size).try_into().unwrap(); - img.copy_row(y as u32, mapped_memory.offset(dest_base)); - } - - device.unmap_memory(&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, - levels: 0..1, - layers: 0..1, - }, - }; - buf.pipeline_barrier( - PipelineStage::TOP_OF_PIPE..PipelineStage::TRANSFER, - MemDependencies::empty(), - &[image_barrier], - ); - - // Copy from buffer to image - buf.copy_buffer_to_image( - &staging_buffer, - &(*self.image), - Layout::TransferDstOptimal, - &[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, - levels: 0..1, - layers: 0..1, - }, - }; - - buf.pipeline_barrier( - PipelineStage::TRANSFER..PipelineStage::FRAGMENT_SHADER, - MemDependencies::empty(), - &[image_barrier], - ); - - buf.finish(); - - buf - }; - - // Submit our commands and wait for them to finish - unsafe { - let setup_finished = device.create_fence(false).unwrap(); - command_queue.submit::<_, _, Semaphore, _, _>( - Submission { - command_buffers: &[&buf], - wait_semaphores: std::iter::empty::<_>(), - signal_semaphores: std::iter::empty::<_>(), - }, - Some(&setup_finished), - ); - - device - .wait_for_fence(&setup_finished, core::u64::MAX) - .unwrap(); - device.destroy_fence(setup_finished); - }; - - // Clean up temp resources - unsafe { - command_pool.free(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( - img: T, - device: &mut Device, - adapter: &Adapter, - command_queue: &mut CommandQueue, - command_pool: &mut CommandPool, - format: Format, - usage: ImgUsage, - ) -> Result { - let mut loaded_image = Self::new( - device, - adapter, - format, - usage | ImgUsage::TRANSFER_DST, - SubresourceRange { - aspects: Aspects::COLOR, - levels: 0..1, - layers: 0..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 Device) { - 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))); + unsafe fn copy_into(&self, ptr: *mut u8, row_size: usize) { + for y in 0..self.height() as usize { + let dest_base: isize = (y * row_size).try_into().unwrap(); + self.copy_row(y as u32, ptr.offset(dest_base)); } } } -- cgit v1.2.3