diff options
author | tcmal <me@aria.rip> | 2024-08-25 17:44:22 +0100 |
---|---|---|
committer | tcmal <me@aria.rip> | 2024-08-25 17:44:22 +0100 |
commit | c48b54f3fb7bbe9046915eb99eca02fa84dc55c9 (patch) | |
tree | 752831451d2bd3a658485df724a01ae39e80fae3 /stockton-render/src/draw/texture | |
parent | b437109ebf4da243fd643f0a31546d0d0155b0a4 (diff) |
feat(render): multithreaded texture loading
also a bunch of supporting changes
Diffstat (limited to 'stockton-render/src/draw/texture')
-rw-r--r-- | stockton-render/src/draw/texture/block.rs | 63 | ||||
-rw-r--r-- | stockton-render/src/draw/texture/chunk.rs | 207 | ||||
-rw-r--r-- | stockton-render/src/draw/texture/image.rs | 639 | ||||
-rw-r--r-- | stockton-render/src/draw/texture/load.rs | 352 | ||||
-rw-r--r-- | stockton-render/src/draw/texture/loader.rs | 569 | ||||
-rw-r--r-- | stockton-render/src/draw/texture/mod.rs | 22 | ||||
-rw-r--r-- | stockton-render/src/draw/texture/repo.rs | 192 | ||||
-rw-r--r-- | stockton-render/src/draw/texture/resolver.rs | 14 | ||||
-rw-r--r-- | stockton-render/src/draw/texture/staging_buffer.rs | 56 |
9 files changed, 1013 insertions, 1101 deletions
diff --git a/stockton-render/src/draw/texture/block.rs b/stockton-render/src/draw/texture/block.rs new file mode 100644 index 0000000..7735f5c --- /dev/null +++ b/stockton-render/src/draw/texture/block.rs @@ -0,0 +1,63 @@ +use super::{loader::BlockRef, repo::BLOCK_SIZE}; +use crate::types::*; + +use arrayvec::ArrayVec; +use hal::prelude::*; +use rendy_memory::{Allocator, Block}; +use std::{iter::once, mem::ManuallyDrop}; + +pub struct TexturesBlock<B: Block<back::Backend>> { + pub id: BlockRef, + pub descriptor_set: ManuallyDrop<RDescriptorSet>, + pub imgs: ArrayVec<[LoadedImage<B>; BLOCK_SIZE]>, +} + +impl<B: Block<back::Backend>> TexturesBlock<B> { + pub fn deactivate<T: Allocator<back::Backend, Block = B>>( + mut self, + device: &mut Device, + tex_alloc: &mut T, + desc_alloc: &mut DescriptorAllocator, + ) { + unsafe { + use std::ptr::read; + + // Descriptor set + desc_alloc.free(once(read(&*self.descriptor_set))); + + // Images + self.imgs + .drain(..) + .map(|x| x.deactivate(device, tex_alloc)) + .for_each(|_| {}); + } + } +} + +pub struct LoadedImage<B: Block<back::Backend>> { + pub mem: ManuallyDrop<B>, + pub img: ManuallyDrop<Image>, + pub img_view: ManuallyDrop<ImageView>, + pub sampler: ManuallyDrop<Sampler>, + pub row_size: usize, + pub height: u32, + pub width: u32, +} + +impl<B: Block<back::Backend>> LoadedImage<B> { + pub fn deactivate<T: Allocator<back::Backend, Block = B>>( + self, + device: &mut Device, + alloc: &mut T, + ) { + unsafe { + use std::ptr::read; + + device.destroy_image_view(read(&*self.img_view)); + device.destroy_image(read(&*self.img)); + device.destroy_sampler(read(&*self.sampler)); + + alloc.free(device, read(&*self.mem)); + } + } +} diff --git a/stockton-render/src/draw/texture/chunk.rs b/stockton-render/src/draw/texture/chunk.rs deleted file mode 100644 index 0cb6737..0000000 --- a/stockton-render/src/draw/texture/chunk.rs +++ /dev/null @@ -1,207 +0,0 @@ -/* - * Copyright (C) Oscar Shrimpton 2020 - * - * This program is free software: you can redistribute it and/or modify it - * under the terms of the GNU General Public License as published by the Free - * Software Foundation, either version 3 of the License, or (at your option) - * any later version. - * - * This program is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for - * more details. - * - * You should have received a copy of the GNU General Public License along - * with this program. If not, see <http://www.gnu.org/licenses/>. - */ - -//! A chunk of textures is an array of textures, the size of which is known at compile time. -//! This reduces the number of times we need to re-bind our descriptor sets - -use crate::draw::texture::image::LoadableImage; -use hal::prelude::*; -use image::{Rgba, RgbaImage}; - -use core::mem::replace; -use std::ops::Deref; - -use crate::{error, types::*}; - -use super::image::SampledImage; -use super::resolver::TextureResolver; -use log::debug; -use std::iter::Iterator; -use stockton_levels::traits::textures::Texture; - -/// The size of a chunk. Needs to match up with the fragment shader -pub const CHUNK_SIZE: usize = 8; - -/// An array of textures -pub struct TextureChunk { - pub(crate) descriptor_set: DescriptorSet, - sampled_images: Vec<SampledImage>, -} - -impl TextureChunk { - /// Create a new empty texture chunk - pub fn new_empty( - device: &mut Device, - adapter: &mut Adapter, - allocator: &mut DynamicAllocator, - command_queue: &mut CommandQueue, - command_pool: &mut CommandPool, - descriptor_set: DescriptorSet, - ) -> Result<TextureChunk, error::CreationError> { - let mut store = TextureChunk { - descriptor_set, - sampled_images: Vec::with_capacity(CHUNK_SIZE), - }; - - for i in 0..CHUNK_SIZE { - debug!("Putting a placeholder in slot {}", i); - store - .put_texture( - RgbaImage::from_pixel(1, 1, Rgba([0, 0, 0, 1])), - i, - device, - adapter, - allocator, - command_queue, - command_pool, - ) - .unwrap(); - } - - Ok(store) - } - - /// Create a new texture chunk and load in the textures specified by `range` from `file` using `resolver` - /// Can error if the descriptor pool is too small or if a texture isn't found - pub fn new<'a, I, R: TextureResolver<T>, T: LoadableImage>( - device: &mut Device, - adapter: &mut Adapter, - allocator: &mut DynamicAllocator, - command_queue: &mut CommandQueue, - command_pool: &mut CommandPool, - descriptor_set: DescriptorSet, - textures: I, - resolver: &mut R, - ) -> Result<TextureChunk, error::CreationError> - where - I: 'a + Iterator<Item = &'a Texture>, - { - let mut store = TextureChunk { - descriptor_set, - sampled_images: Vec::with_capacity(CHUNK_SIZE), - }; - - let mut local_idx = 0; - - debug!("Created descriptor set"); - for tex in textures { - if let Some(img) = resolver.resolve(tex) { - store - .put_texture( - img, - local_idx, - device, - adapter, - allocator, - command_queue, - command_pool, - ) - .unwrap(); - } else { - // Texture not found. For now, tear everything down. - store.deactivate(device, allocator); - - return Err(error::CreationError::BadDataError); - } - - local_idx += 1; - } - - // Pad out the end if needed - while local_idx < CHUNK_SIZE { - debug!("Putting a placeholder in slot {}", local_idx); - store - .put_texture( - RgbaImage::from_pixel(1, 1, Rgba([0, 0, 0, 1])), - local_idx, - device, - adapter, - allocator, - command_queue, - command_pool, - ) - .unwrap(); - - local_idx += 1; - } - - Ok(store) - } - - pub fn put_texture<T: LoadableImage>( - &mut self, - image: T, - idx: usize, - device: &mut Device, - adapter: &mut Adapter, - allocator: &mut DynamicAllocator, - command_queue: &mut CommandQueue, - command_pool: &mut CommandPool, - ) -> Result<(), &'static str> { - // Load the image - let texture = SampledImage::load_into_new( - image, - device, - adapter, - allocator, - command_queue, - command_pool, - hal::format::Format::Rgba8Srgb, // TODO - hal::image::Usage::empty(), - )?; - - // Write it to the descriptor set - unsafe { - use hal::image::Layout; - use hal::pso::{Descriptor, DescriptorSetWrite}; - - device.write_descriptor_sets(vec![ - DescriptorSetWrite { - set: &self.descriptor_set, - binding: 0, - array_offset: idx, - descriptors: Some(Descriptor::Image( - texture.image.image_view.deref(), - Layout::ShaderReadOnlyOptimal, - )), - }, - DescriptorSetWrite { - set: &self.descriptor_set, - binding: 1, - array_offset: idx, - descriptors: Some(Descriptor::Sampler(texture.sampler.deref())), - }, - ]); - }; - - // Store it so we can safely deactivate it when we need to - // Deactivate the old image if we need to - if idx < self.sampled_images.len() { - replace(&mut self.sampled_images[idx], texture).deactivate(device, allocator); - } else { - self.sampled_images.push(texture); - } - - Ok(()) - } - - pub fn deactivate(mut self, device: &mut Device, allocator: &mut DynamicAllocator) { - for img in self.sampled_images.drain(..) { - img.deactivate(device, allocator); - } - } -} 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 <http://www.gnu.org/licenses/>. */ -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::<u8>() * 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<Image>, - - /// The full view of the image - pub image_view: ManuallyDrop<ImageView>, - - /// The memory backing the image - memory: ManuallyDrop<DynamicBlock>, -} - -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<LoadedImage, &'static str> { - 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<T: LoadableImage>( - &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<LoadedImage>, - pub sampler: ManuallyDrop<Sampler>, -} - -impl SampledImage { - pub fn new( - device: &mut Device, - adapter: &Adapter, - allocator: &mut DynamicAllocator, - format: Format, - usage: ImgUsage, - width: usize, - height: usize, - ) -> Result<SampledImage, &'static str> { - 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<T: LoadableImage>( - img: T, - device: &mut Device, - adapter: &Adapter, - allocator: &mut DynamicAllocator, - command_queue: &mut CommandQueue, - command_pool: &mut CommandPool, - format: Format, - usage: ImgUsage, - ) -> Result<SampledImage, &'static str> { - 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<Image>, - - /// The full view of the image - pub image_view: ManuallyDrop<ImageView>, - - /// The memory backing the image - memory: ManuallyDrop<Memory>, -} - -impl DedicatedLoadedImage { - pub fn new( - device: &mut Device, - adapter: &Adapter, - format: Format, - usage: ImgUsage, - resources: SubresourceRange, - width: usize, - height: usize, - ) -> Result<DedicatedLoadedImage, &'static str> { - 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<T: LoadableImage>( - &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<T: LoadableImage>( - img: T, - device: &mut Device, - adapter: &Adapter, - command_queue: &mut CommandQueue, - command_pool: &mut CommandPool, - format: Format, - usage: ImgUsage, - ) -> Result<DedicatedLoadedImage, &'static str> { - 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)); } } } diff --git a/stockton-render/src/draw/texture/load.rs b/stockton-render/src/draw/texture/load.rs new file mode 100644 index 0000000..011ae29 --- /dev/null +++ b/stockton-render/src/draw/texture/load.rs @@ -0,0 +1,352 @@ +use super::{ + block::LoadedImage, block::TexturesBlock, loader::TextureLoader, repo::BLOCK_SIZE, + resolver::TextureResolver, staging_buffer::StagingBuffer, LoadableImage, PIXEL_SIZE, +}; +use crate::types::*; +use stockton_levels::prelude::*; + +use arrayvec::ArrayVec; +use hal::{ + command::{BufferImageCopy, CommandBufferFlags}, + format::{Aspects, Format, Swizzle}, + image::{ + Extent, Filter, Layout, Offset, SamplerDesc, SubresourceLayers, SubresourceRange, + Usage as ImgUsage, ViewKind, WrapMode, + }, + memory::{Barrier, Dependencies}, + prelude::*, + pso::{Descriptor, DescriptorSetWrite, PipelineStage, ShaderStageFlags}, + queue::Submission, +}; +use rendy_descriptor::{DescriptorRanges, DescriptorSetLayoutBinding, DescriptorType}; +use rendy_memory::{Allocator, Block}; +use std::mem::ManuallyDrop; + +pub struct QueuedLoad<B: Block<back::Backend>> { + pub fence: Fence, + pub buf: CommandBuffer, + pub block: TexturesBlock<B>, + pub staging_bufs: ArrayVec<[StagingBuffer; BLOCK_SIZE]>, +} + +impl<B: Block<back::Backend>> QueuedLoad<B> { + pub fn dissolve( + self, + ) -> ( + (Fence, CommandBuffer), + ArrayVec<[StagingBuffer; BLOCK_SIZE]>, + TexturesBlock<B>, + ) { + ((self.fence, self.buf), self.staging_bufs, self.block) + } +} + +impl<'a, T: HasTextures, R: TextureResolver<I>, I: LoadableImage> TextureLoader<'a, T, R, I> { + const FORMAT: Format = Format::Rgba8Srgb; + const RESOURCES: SubresourceRange = SubresourceRange { + aspects: Aspects::COLOR, + levels: 0..1, + layers: 0..1, + }; + const LAYERS: SubresourceLayers = SubresourceLayers { + aspects: Aspects::COLOR, + level: 0, + layers: 0..1, + }; + + pub(crate) unsafe fn attempt_queue_load( + &mut self, + block_ref: usize, + ) -> Option<QueuedLoad<DynamicBlock>> { + // Get assets to use + let (fence, mut buf) = self.buffers.pop_front()?; + + // Create descriptor set + let descriptor_set = self.create_descriptor_set(); + + // Get a command buffer + buf.begin_primary(CommandBufferFlags::ONE_TIME_SUBMIT); + + let mut copy_cmds: ArrayVec<[_; BLOCK_SIZE]> = ArrayVec::new(); + let mut imgs: ArrayVec<[_; BLOCK_SIZE]> = ArrayVec::new(); + let mut staging_bufs: ArrayVec<[_; BLOCK_SIZE]> = ArrayVec::new(); + + // For each texture in block + for tex_idx in (block_ref * BLOCK_SIZE)..(block_ref + 1) * BLOCK_SIZE { + // Get texture and Resolve image + let tex = self.textures.get_texture(tex_idx as u32); + if tex.is_none() { + break; // Past the end + // TODO: We should actually write blank descriptors + } + let tex = tex.unwrap(); + + let img_data = self.resolver.resolve(tex).expect("Invalid texture"); + + // Calculate buffer size + let (row_size, total_size) = + tex_size_info(&img_data, self.optimal_buffer_copy_pitch_alignment); + + // Create staging buffer + let mut staging_buffer = StagingBuffer::new( + &mut self.device, + &mut self.staging_allocator, + total_size as u64, + self.staging_memory_type, + ) + .expect("Couldn't create staging buffer"); + + // Write to staging buffer + let mapped_memory = staging_buffer + .map_memory(&mut self.device) + .expect("Error mapping staged memory"); + + img_data.copy_into(mapped_memory, row_size); + + staging_buffer.unmap_memory(&mut self.device); + + // Create image + let (img_mem, img) = create_image_view( + &mut self.device, + &mut *self.tex_allocator, + Self::FORMAT, + ImgUsage::SAMPLED, + &img_data, + ) + .unwrap(); + + // Create image view + let img_view = self + .device + .create_image_view( + &img, + ViewKind::D2, + Self::FORMAT, + Swizzle::NO, + Self::RESOURCES, + ) + .expect("Error creating image view"); + + // Queue copy from buffer to image + copy_cmds.push(BufferImageCopy { + buffer_offset: 0, + buffer_width: (row_size / super::PIXEL_SIZE) as u32, + buffer_height: img_data.height(), + image_layers: Self::LAYERS, + image_offset: Offset { x: 0, y: 0, z: 0 }, + image_extent: Extent { + width: img_data.width(), + height: img_data.height(), + depth: 1, + }, + }); + + // Create sampler + let sampler = self + .device + .create_sampler(&SamplerDesc::new(Filter::Nearest, WrapMode::Tile)) + .expect("Failed to create sampler"); + + // Write to descriptor set + { + self.device.write_descriptor_sets(vec![ + DescriptorSetWrite { + set: descriptor_set.raw(), + binding: 0, + array_offset: tex_idx % BLOCK_SIZE, + descriptors: Some(Descriptor::Image( + &img_view, + Layout::ShaderReadOnlyOptimal, + )), + }, + DescriptorSetWrite { + set: descriptor_set.raw(), + binding: 1, + array_offset: tex_idx % BLOCK_SIZE, + descriptors: Some(Descriptor::Sampler(&sampler)), + }, + ]); + } + + imgs.push(LoadedImage { + mem: ManuallyDrop::new(img_mem), + img: ManuallyDrop::new(img), + img_view: ManuallyDrop::new(img_view), + sampler: ManuallyDrop::new(sampler), + row_size, + height: img_data.height(), + width: img_data.width(), + }); + + staging_bufs.push(staging_buffer); + } + + // Add start pipeline barriers + for li in imgs.iter() { + use hal::image::Access; + + buf.pipeline_barrier( + PipelineStage::TOP_OF_PIPE..PipelineStage::TRANSFER, + Dependencies::empty(), + &[Barrier::Image { + states: (Access::empty(), Layout::Undefined) + ..(Access::TRANSFER_WRITE, Layout::TransferDstOptimal), + target: &*li.img, + families: None, + range: SubresourceRange { + aspects: Aspects::COLOR, + levels: 0..1, + layers: 0..1, + }, + }], + ); + } + + // Record copy commands + for (li, sb) in imgs.iter().zip(staging_bufs.iter()) { + buf.copy_buffer_to_image( + &*sb.buf, + &*li.img, + Layout::TransferDstOptimal, + &[BufferImageCopy { + buffer_offset: 0, + buffer_width: (li.row_size / super::PIXEL_SIZE) as u32, + buffer_height: li.height, + image_layers: SubresourceLayers { + aspects: Aspects::COLOR, + level: 0, + layers: 0..1, + }, + image_offset: Offset { x: 0, y: 0, z: 0 }, + image_extent: gfx_hal::image::Extent { + width: li.width, + height: li.height, + depth: 1, + }, + }], + ); + } + for li in imgs.iter() { + use hal::image::Access; + + buf.pipeline_barrier( + PipelineStage::TOP_OF_PIPE..PipelineStage::TRANSFER, + Dependencies::empty(), + &[Barrier::Image { + states: (Access::TRANSFER_WRITE, Layout::TransferDstOptimal) + ..(Access::SHADER_READ, Layout::ShaderReadOnlyOptimal), + target: &*li.img, + families: None, + range: Self::RESOURCES, + }], + ); + } + + buf.finish(); + + // Submit command buffer + self.gpu.queue_groups[self.cmd_queue_idx].queues[0].submit::<_, _, Semaphore, _, _>( + Submission { + command_buffers: &[&buf], + signal_semaphores: std::iter::empty(), + wait_semaphores: std::iter::empty(), + }, + Some(&fence), + ); + + Some(QueuedLoad { + staging_bufs, + fence, + buf, + block: TexturesBlock { + id: block_ref, + imgs, + descriptor_set: ManuallyDrop::new(descriptor_set), + }, + }) + } + + pub(crate) unsafe fn create_descriptor_set(&mut self) -> RDescriptorSet { + let mut v: ArrayVec<[RDescriptorSet; 1]> = ArrayVec::new(); + self.descriptor_allocator + .allocate( + self.device, + &*self.ds_layout, + DescriptorRanges::from_bindings(&[ + DescriptorSetLayoutBinding { + binding: 0, + ty: DescriptorType::SampledImage, + count: BLOCK_SIZE, + stage_flags: ShaderStageFlags::FRAGMENT, + immutable_samplers: false, + }, + DescriptorSetLayoutBinding { + binding: 1, + ty: DescriptorType::Sampler, + count: BLOCK_SIZE, + stage_flags: ShaderStageFlags::FRAGMENT, + immutable_samplers: false, + }, + ]), + 1, + &mut v, + ) + .unwrap(); + + v.pop().unwrap() + } +} + +pub fn tex_size_info<T: LoadableImage>(img: &T, obcpa: hal::buffer::Offset) -> (usize, usize) { + let initial_row_size = PIXEL_SIZE * img.width() as usize; + let row_alignment_mask = obcpa 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); + + (row_size, total_size as usize) +} + +fn create_image_view<T, I>( + device: &mut Device, + allocator: &mut T, + format: Format, + usage: ImgUsage, + img: &I, +) -> Result<(T::Block, Image), &'static str> +where + T: Allocator<back::Backend>, + I: LoadableImage, +{ + // Make the image + let mut image_ref = unsafe { + use hal::image::{Kind, Tiling, ViewCapabilities}; + + device.create_image( + Kind::D2(img.width(), img.height(), 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)) +} diff --git a/stockton-render/src/draw/texture/loader.rs b/stockton-render/src/draw/texture/loader.rs index 0cfe0c3..33025a6 100644 --- a/stockton-render/src/draw/texture/loader.rs +++ b/stockton-render/src/draw/texture/loader.rs @@ -15,284 +15,357 @@ * with this program. If not, see <http://www.gnu.org/licenses/>. */ -//! Deals with loading textures into GPU memory +//! Manages the loading/unloading of textures -use super::chunk::TextureChunk; -use crate::draw::texture::chunk::CHUNK_SIZE; -use crate::draw::texture::image::LoadableImage; -use crate::draw::texture::resolver::BasicFSResolver; -use core::mem::ManuallyDrop; -use std::path::Path; +use super::{block::TexturesBlock, load::QueuedLoad, resolver::TextureResolver, LoadableImage}; +use crate::{draw::utils::find_memory_type_id, types::*}; -use log::debug; +use std::{ + collections::VecDeque, + marker::PhantomData, + mem::ManuallyDrop, + sync::mpsc::{Receiver, Sender}, + thread::sleep, + time::Duration, +}; -use hal::prelude::*; +use arrayvec::ArrayVec; +use hal::{ + format::Format, memory::Properties as MemProps, prelude::*, queue::family::QueueFamilyId, + MemoryTypeId, +}; +use log::*; +use rendy_memory::DynamicConfig; +use stockton_levels::prelude::HasTextures; -use stockton_levels::prelude::*; +/// The number of command buffers to have in flight simultaneously. +pub const NUM_SIMULTANEOUS_CMDS: usize = 2; -use crate::error; -use crate::types::*; +/// A reference to a texture of the current map +pub type BlockRef = usize; -/// Stores all loaded textures in GPU memory. -/// When rendering, the descriptor sets are bound to the buffer -/// The descriptor set layout should have the same count of textures as this does. -/// All descriptors will be properly initialised images. -pub struct TextureStore { - descriptor_pool: ManuallyDrop<DescriptorPool>, - pub(crate) descriptor_set_layout: ManuallyDrop<DescriptorSetLayout>, - chunks: Box<[TextureChunk]>, +/// Manages the loading/unloading of textures +/// This is expected to load the textures, then send the loaded blocks back +pub struct TextureLoader<'a, T, R, I> { + /// Handle to the device we're using + pub(crate) device: &'a mut Device, + + /// Blocks for which commands have been queued and are done loading once the fence is triggered. + pub(crate) commands_queued: ArrayVec<[QueuedLoad<DynamicBlock>; NUM_SIMULTANEOUS_CMDS]>, + + /// The command buffers used and a fence to go with them + pub(crate) buffers: VecDeque<(Fence, CommandBuffer)>, + + /// The command pool buffers were allocated from + pub(crate) pool: ManuallyDrop<CommandPool>, + + /// The GPU we're submitting to + pub(crate) gpu: ManuallyDrop<Gpu>, + + /// The index of the command queue being used + pub(crate) cmd_queue_idx: usize, + + /// The memory allocator being used for textures + pub(crate) tex_allocator: ManuallyDrop<DynamicAllocator>, + + /// The memory allocator for staging memory + pub(crate) staging_allocator: ManuallyDrop<DynamicAllocator>, + + /// Allocator for descriptor sets + pub(crate) descriptor_allocator: ManuallyDrop<DescriptorAllocator>, + + pub(crate) ds_layout: &'a DescriptorSetLayout, + + /// Type ID for staging memory + pub(crate) staging_memory_type: MemoryTypeId, + + /// From adapter, used for determining alignment + pub(crate) optimal_buffer_copy_pitch_alignment: hal::buffer::Offset, + + /// The textures lump to get info from + pub(crate) textures: &'a T, + + /// The resolver which gets image data for a given texture. + pub(crate) resolver: R, + + /// The channel requests come in. + /// Requests should reference a texture **block**, for example textures 8..16 is block 1. + pub(crate) request_channel: Receiver<LoaderRequest>, + + /// The channel blocks are returned to. + pub(crate) return_channel: Sender<TexturesBlock<DynamicBlock>>, + + pub(crate) _li: PhantomData<I>, } -impl TextureStore { - pub fn new_empty( - device: &mut Device, - adapter: &mut Adapter, - allocator: &mut DynamicAllocator, - command_queue: &mut CommandQueue, - command_pool: &mut CommandPool, - size: usize, - ) -> Result<TextureStore, error::CreationError> { - // Figure out how many textures in this file / how many chunks needed - let num_chunks = { - let mut x = size / CHUNK_SIZE; - if size % CHUNK_SIZE != 0 { - x += 1; - } - x - }; - let rounded_size = num_chunks * CHUNK_SIZE; - - // Descriptor pool, where we get our sets from - let mut descriptor_pool = unsafe { - use hal::pso::{DescriptorPoolCreateFlags, DescriptorRangeDesc, DescriptorType}; - - device - .create_descriptor_pool( - num_chunks, - &[ - DescriptorRangeDesc { - ty: DescriptorType::SampledImage, - count: rounded_size, - }, - DescriptorRangeDesc { - ty: DescriptorType::Sampler, - count: rounded_size, - }, - ], - DescriptorPoolCreateFlags::empty(), - ) - .map_err(|e| { - println!("{:?}", e); - error::CreationError::OutOfMemoryError - })? - }; +impl<'a, T: HasTextures, R: TextureResolver<I>, I: LoadableImage> TextureLoader<'a, T, R, I> { + pub fn loop_forever(mut self) -> Result<TextureLoaderRemains, &'static str> { + debug!("TextureLoader starting main loop"); + let mut res = Ok(()); + while res.is_ok() { + res = self.main(); + sleep(Duration::from_secs(0)); + } - // Layout of our descriptor sets - let descriptor_set_layout = unsafe { - use hal::pso::{DescriptorSetLayoutBinding, DescriptorType, ShaderStageFlags}; - - device.create_descriptor_set_layout( - &[ - DescriptorSetLayoutBinding { - binding: 0, - ty: DescriptorType::SampledImage, - count: CHUNK_SIZE, - stage_flags: ShaderStageFlags::FRAGMENT, - immutable_samplers: false, - }, - DescriptorSetLayoutBinding { - binding: 1, - ty: DescriptorType::Sampler, - count: CHUNK_SIZE, - stage_flags: ShaderStageFlags::FRAGMENT, - immutable_samplers: false, - }, - ], - &[], - ) + match res { + Err(r) => match r { + LoopEndReason::Graceful => { + debug!("Starting to deactivate TextureLoader"); + + Ok(self.deactivate()) + } + LoopEndReason::Error(r) => Err(r), + }, + Ok(_) => Err(""), } - .map_err(|_| error::CreationError::OutOfMemoryError)?; - - log::debug!("texture ds layout: {:?}", descriptor_set_layout); - - // Create texture chunks - debug!("Starting to load textures..."); - let mut chunks = Vec::with_capacity(num_chunks); - for i in 0..num_chunks { - debug!("Chunk {} / {}", i + 1, num_chunks); - - let descriptor_set = unsafe { - descriptor_pool - .allocate_set(&descriptor_set_layout) - .map_err(|_| error::CreationError::OutOfMemoryError)? - }; - chunks.push(TextureChunk::new_empty( - device, - adapter, - allocator, - command_queue, - command_pool, - descriptor_set, - )?); + } + fn main(&mut self) -> Result<(), LoopEndReason> { + // Check for blocks that are finished, then send them back + let mut i = 0; + while i < self.commands_queued.len() { + let signalled = unsafe { self.device.get_fence_status(&self.commands_queued[i].fence) } + .map_err(|_| LoopEndReason::Error("Device lost by TextureManager"))?; + + if signalled { + let (assets, staging_bufs, block) = self.commands_queued.remove(i).dissolve(); + debug!("Done loading texture block {:?}", block.id); + + // Destroy staging buffers + staging_bufs + .into_iter() + .map(|x| x.deactivate(self.device, &mut self.staging_allocator)) + .for_each(|_| {}); + + self.buffers.push_back(assets); + self.return_channel.send(block).unwrap(); + } else { + i += 1; + } } - debug!("All textures loaded."); + // Check for messages to start loading blocks + let req_iter: Vec<_> = self.request_channel.try_iter().collect(); + for to_load in req_iter { + match to_load { + LoaderRequest::Load(to_load) => { + // Attempt to load given block + if let Some(queued_load) = unsafe { self.attempt_queue_load(to_load) } { + self.commands_queued.push(queued_load); + } + } + LoaderRequest::End => return Err(LoopEndReason::Graceful), + } + } - Ok(TextureStore { - descriptor_pool: ManuallyDrop::new(descriptor_pool), - descriptor_set_layout: ManuallyDrop::new(descriptor_set_layout), - chunks: chunks.into_boxed_slice(), - }) + Ok(()) } - /// Create a new texture store for the given file, loading all textures from it. - pub fn new<T: HasTextures>( - device: &mut Device, - adapter: &mut Adapter, - allocator: &mut DynamicAllocator, - command_queue: &mut CommandQueue, - command_pool: &mut CommandPool, - file: &T, - ) -> Result<TextureStore, error::CreationError> { - // Figure out how many textures in this file / how many chunks needed - let size = file.textures_iter().count(); - let num_chunks = { - let mut x = size / CHUNK_SIZE; - if size % CHUNK_SIZE != 0 { - x += 1; - } - x - }; - let rounded_size = num_chunks * CHUNK_SIZE; - - // Descriptor pool, where we get our sets from - let mut descriptor_pool = unsafe { - use hal::pso::{DescriptorPoolCreateFlags, DescriptorRangeDesc, DescriptorType}; - - device - .create_descriptor_pool( - num_chunks, - &[ - DescriptorRangeDesc { - ty: DescriptorType::SampledImage, - count: rounded_size, - }, - DescriptorRangeDesc { - ty: DescriptorType::Sampler, - count: rounded_size, - }, - ], - DescriptorPoolCreateFlags::empty(), + pub fn new( + device: &'a mut Device, + adapter: &Adapter, + family: QueueFamilyId, + gpu: Gpu, + ds_layout: &'a DescriptorSetLayout, + request_channel: Receiver<LoaderRequest>, + return_channel: Sender<TexturesBlock<DynamicBlock>>, + texs: &'a T, + resolver: R, + ) -> Result<Self, &'static str> { + // Pool + let mut pool = unsafe { + use hal::pool::CommandPoolCreateFlags; + + device.create_command_pool(family, CommandPoolCreateFlags::RESET_INDIVIDUAL) + } + .map_err(|_| "Couldn't create command pool")?; + + let type_mask = unsafe { + use hal::image::{Kind, Tiling, Usage, ViewCapabilities}; + + // We create an empty image with the same format as used for textures + // this is to get the type_mask required, which will stay the same for + // all colour images of the same tiling. (certain memory flags excluded). + + // Size and alignment don't necessarily stay the same, so we're forced to + // guess at the alignment for our allocator. + + // TODO: Way to tune these options + let img = device + .create_image( + Kind::D2(16, 16, 1, 1), + 1, + Format::Rgba8Srgb, + Tiling::Optimal, + Usage::SAMPLED, + ViewCapabilities::empty(), ) - .map_err(|e| { - println!("{:?}", e); - error::CreationError::OutOfMemoryError - })? + .map_err(|_| "Couldn't make image to get memory requirements")?; + + let type_mask = device.get_image_requirements(&img).type_mask; + + device.destroy_image(img); + + type_mask }; - // Layout of our descriptor sets - let descriptor_set_layout = unsafe { - use hal::pso::{DescriptorSetLayoutBinding, DescriptorType, ShaderStageFlags}; - - device.create_descriptor_set_layout( - &[ - DescriptorSetLayoutBinding { - binding: 0, - ty: DescriptorType::SampledImage, - count: CHUNK_SIZE, - stage_flags: ShaderStageFlags::FRAGMENT, - immutable_samplers: false, - }, - DescriptorSetLayoutBinding { - binding: 1, - ty: DescriptorType::Sampler, - count: CHUNK_SIZE, - stage_flags: ShaderStageFlags::FRAGMENT, - immutable_samplers: false, + // Tex Allocator + let tex_allocator = { + let props = MemProps::DEVICE_LOCAL; + + DynamicAllocator::new( + find_memory_type_id(&adapter, type_mask, props) + .ok_or("Couldn't find memory type supporting image")?, + props, + DynamicConfig { + block_size_granularity: 4 * 32 * 32, // 32x32 image + max_chunk_size: u64::pow(2, 63), + min_device_allocation: 4 * 32 * 32, + }, + ) + }; + + let (staging_memory_type, staging_allocator) = { + let props = MemProps::CPU_VISIBLE | MemProps::COHERENT; + let t = find_memory_type_id(&adapter, type_mask, props) + .ok_or("Couldn't find memory type supporting image")?; + ( + t, + DynamicAllocator::new( + t, + props, + DynamicConfig { + block_size_granularity: 4 * 32 * 32, // 32x32 image + max_chunk_size: u64::pow(2, 63), + min_device_allocation: 4 * 32 * 32, }, - ], - &[], + ), ) - } - .map_err(|_| error::CreationError::OutOfMemoryError)?; - - // TODO: Proper way to set up resolver - let mut resolver = BasicFSResolver::new(Path::new(".")); - - // Create texture chunks - debug!("Starting to load textures..."); - let mut chunks = Vec::with_capacity(num_chunks); - for i in 0..num_chunks { - debug!("Chunk {} / {}", i + 1, num_chunks); - - let descriptor_set = unsafe { - descriptor_pool - .allocate_set(&descriptor_set_layout) - .map_err(|_| error::CreationError::OutOfMemoryError)? - }; - chunks.push(TextureChunk::new( - device, - adapter, - allocator, - command_queue, - command_pool, - descriptor_set, - file.textures_iter().skip(i * CHUNK_SIZE).take(CHUNK_SIZE), - &mut resolver, - )?); - } + }; + + let buffers = { + let mut data = VecDeque::with_capacity(NUM_SIMULTANEOUS_CMDS); + + for _ in 0..NUM_SIMULTANEOUS_CMDS { + unsafe { + data.push_back(( + device + .create_fence(false) + .map_err(|_| "Couldn't create fence")?, + pool.allocate_one(hal::command::Level::Primary), + )); + }; + } + + data + }; - debug!("All textures loaded."); + let cmd_queue_idx = gpu + .queue_groups + .iter() + .position(|x| x.family == family) + .unwrap(); - Ok(TextureStore { - descriptor_pool: ManuallyDrop::new(descriptor_pool), - descriptor_set_layout: ManuallyDrop::new(descriptor_set_layout), - chunks: chunks.into_boxed_slice(), + Ok(TextureLoader { + device, + commands_queued: ArrayVec::new(), + buffers, + pool: ManuallyDrop::new(pool), + gpu: ManuallyDrop::new(gpu), + cmd_queue_idx, + ds_layout, + + tex_allocator: ManuallyDrop::new(tex_allocator), + staging_allocator: ManuallyDrop::new(staging_allocator), + descriptor_allocator: ManuallyDrop::new(DescriptorAllocator::new()), + + staging_memory_type, + optimal_buffer_copy_pitch_alignment: adapter + .physical_device + .limits() + .optimal_buffer_copy_pitch_alignment, + + request_channel, + return_channel, + textures: texs, + resolver, + _li: PhantomData::default(), }) } - /// Call this before dropping - pub fn deactivate(mut self, device: &mut Device, allocator: &mut DynamicAllocator) { - unsafe { - use core::ptr::read; + /// Safely destroy all the vulkan stuff in this instance + /// Note that this returns the memory allocators, from which should be freed any TextureBlocks + /// All in-progress things are sent to return_channel. + fn deactivate(mut self) -> TextureLoaderRemains { + use std::ptr::read; - for chunk in self.chunks.into_vec().drain(..) { - chunk.deactivate(device, allocator); + unsafe { + // Wait for any currently queued loads to be done + while self.commands_queued.len() > 0 { + let mut i = 0; + while i < self.commands_queued.len() { + let signalled = self + .device + .get_fence_status(&self.commands_queued[i].fence) + .expect("Device lost by TextureManager"); + + if signalled { + // Destroy finished ones + let (assets, mut staging_bufs, block) = + self.commands_queued.remove(i).dissolve(); + + self.device.destroy_fence(assets.0); + // Command buffer will be freed when we reset the command pool + staging_bufs + .drain(..) + .map(|x| x.deactivate(self.device, &mut self.staging_allocator)) + .for_each(|_| {}); + + self.return_channel + .send(block) + .expect("Sending through return channel failed"); + } else { + i += 1; + } + } + + sleep(Duration::from_secs(0)); } - self.descriptor_pool.reset(); - device.destroy_descriptor_set_layout(ManuallyDrop::into_inner(read( - &self.descriptor_set_layout, - ))); - device.destroy_descriptor_pool(ManuallyDrop::into_inner(read(&self.descriptor_pool))); + // Destroy fences + let vec: Vec<_> = self.buffers.drain(..).collect(); + + vec.into_iter() + .map(|(f, _)| self.device.destroy_fence(f)) + .for_each(|_| {}); + + // Free command pool + self.pool.reset(true); + self.device.destroy_command_pool(read(&*self.pool)); + + debug!("Done deactivating TextureLoader"); + + TextureLoaderRemains { + tex_allocator: ManuallyDrop::new(read(&*self.tex_allocator)), + descriptor_allocator: ManuallyDrop::new(read(&*self.descriptor_allocator)), + } } } +} - /// Get the descriptor set for a given chunk - pub fn get_chunk_descriptor_set(&self, idx: usize) -> &DescriptorSet { - &self.chunks[idx].descriptor_set - } +pub struct TextureLoaderRemains { + pub tex_allocator: ManuallyDrop<DynamicAllocator>, + pub descriptor_allocator: ManuallyDrop<DescriptorAllocator>, +} - pub fn put_texture<T: LoadableImage>( - &mut self, - idx: usize, - img: T, - device: &mut Device, - adapter: &mut Adapter, - allocator: &mut DynamicAllocator, - command_queue: &mut CommandQueue, - command_pool: &mut CommandPool, - ) -> Result<(), &'static str> { - // TODO: Resizing, etc? - let chunk = &mut self.chunks[idx / CHUNK_SIZE]; - chunk.put_texture( - img, - idx % CHUNK_SIZE, - device, - adapter, - allocator, - command_queue, - command_pool, - ) - } +enum LoopEndReason { + Graceful, + Error(&'static str), +} + +pub enum LoaderRequest { + /// Load the given block + Load(BlockRef), + + /// Stop looping and deactivate + End, } diff --git a/stockton-render/src/draw/texture/mod.rs b/stockton-render/src/draw/texture/mod.rs index 3878472..09896ab 100644 --- a/stockton-render/src/draw/texture/mod.rs +++ b/stockton-render/src/draw/texture/mod.rs @@ -17,14 +17,18 @@ //! Everything related to loading textures into GPU memory -// Since this is in the process of being rewritten, we ignore this for now -#![allow(clippy::too_many_arguments)] - -mod chunk; -pub mod image; -pub mod loader; -mod resolver; +mod block; +mod image; +mod load; +mod loader; +mod repo; +pub mod resolver; +mod staging_buffer; +pub use self::block::TexturesBlock; pub use self::image::LoadableImage; -pub use self::image::{LoadedImage, SampledImage}; -pub use self::loader::TextureStore; +pub use self::loader::BlockRef; +pub use self::repo::TextureRepo; + +/// The size of each pixel in an image +pub const PIXEL_SIZE: usize = std::mem::size_of::<u8>() * 4; diff --git a/stockton-render/src/draw/texture/repo.rs b/stockton-render/src/draw/texture/repo.rs new file mode 100644 index 0000000..7bc8fb5 --- /dev/null +++ b/stockton-render/src/draw/texture/repo.rs @@ -0,0 +1,192 @@ +use stockton_levels::prelude::HasTextures; + +use super::{ + block::TexturesBlock, + loader::{BlockRef, LoaderRequest, TextureLoader, TextureLoaderRemains, NUM_SIMULTANEOUS_CMDS}, + resolver::TextureResolver, + LoadableImage, +}; +use crate::types::*; + +use std::{ + collections::HashMap, + marker::PhantomData, + mem::ManuallyDrop, + pin::Pin, + sync::mpsc::{channel, Receiver, Sender}, + thread::JoinHandle, +}; + +use hal::{ + prelude::*, + pso::{DescriptorSetLayoutBinding, DescriptorType, ShaderStageFlags}, + Features, +}; +use log::debug; + +/// The number of textures in one 'block' +/// The textures of the loaded file are divided into blocks of this size. +/// Whenever a texture is needed, the whole block its in is loaded. +pub const BLOCK_SIZE: usize = 8; + +pub struct TextureRepo<'a> { + joiner: ManuallyDrop<JoinHandle<Result<TextureLoaderRemains, &'static str>>>, + ds_layout: Pin<Box<DescriptorSetLayout>>, + req_send: Sender<LoaderRequest>, + resp_recv: Receiver<TexturesBlock<DynamicBlock>>, + blocks: HashMap<BlockRef, Option<TexturesBlock<DynamicBlock>>>, + + _a: PhantomData<&'a ()>, +} + +impl<'a> TextureRepo<'a> { + pub fn new< + T: HasTextures + Send + Sync, + R: 'static + TextureResolver<I> + Send + Sync, + I: 'static + LoadableImage + Send, + >( + device: &'static mut Device, + adapter: &Adapter, + texs: &'static T, + resolver: R, + ) -> Result<Self, &'static str> { + let (req_send, req_recv) = channel(); + let (resp_send, resp_recv) = channel(); + let family = adapter + .queue_families + .iter() + .find(|family| { + family.queue_type().supports_transfer() + && family.max_queues() >= NUM_SIMULTANEOUS_CMDS + }) + .unwrap(); + let gpu = unsafe { + adapter + .physical_device + .open(&[(family, &[1.0])], Features::empty()) + .unwrap() + }; + + let mut ds_layout = Box::pin( + unsafe { + device.create_descriptor_set_layout( + &[ + DescriptorSetLayoutBinding { + binding: 0, + ty: DescriptorType::SampledImage, + count: BLOCK_SIZE, + stage_flags: ShaderStageFlags::FRAGMENT, + immutable_samplers: false, + }, + DescriptorSetLayoutBinding { + binding: 1, + ty: DescriptorType::Sampler, + count: BLOCK_SIZE, + stage_flags: ShaderStageFlags::FRAGMENT, + immutable_samplers: false, + }, + ], + &[], + ) + } + .map_err(|_| "Couldn't create descriptor set layout")?, + ); + + let long_ds_pointer: &'static DescriptorSetLayout = + unsafe { &mut *(&mut *ds_layout as *mut DescriptorSetLayout) }; + + let joiner = { + let loader = TextureLoader::new( + device, + adapter, + family.id(), + gpu, + long_ds_pointer, + req_recv, + resp_send, + texs, + resolver, + )?; + + std::thread::spawn(move || loader.loop_forever()) + }; + + Ok(TextureRepo { + joiner: ManuallyDrop::new(joiner), + ds_layout, + blocks: HashMap::new(), + req_send, + resp_recv, + _a: PhantomData::default(), + }) + } + + pub fn get_ds_layout(&self) -> &DescriptorSetLayout { + &*self.ds_layout + } + + pub fn queue_load(&mut self, block_id: BlockRef) -> Result<(), &'static str> { + if self.blocks.contains_key(&block_id) { + return Ok(()); + } + + self.force_queue_load(block_id) + } + + pub fn force_queue_load(&mut self, block_id: BlockRef) -> Result<(), &'static str> { + self.req_send + .send(LoaderRequest::Load(block_id)) + .map_err(|_| "Couldn't send load request")?; + + self.blocks.insert(block_id, None); + + Ok(()) + } + + pub fn attempt_get_descriptor_set(&mut self, block_id: BlockRef) -> Option<&DescriptorSet> { + self.blocks + .get(&block_id) + .and_then(|opt| opt.as_ref().map(|z| z.descriptor_set.raw())) + } + + pub fn process_responses(&mut self) { + let resp_iter: Vec<_> = self.resp_recv.try_iter().collect(); + for resp in resp_iter { + debug!("Got block {:?} back from loader", resp.id); + self.blocks.insert(resp.id, Some(resp)); + } + } + + pub fn deactivate(mut self, device: &mut Device) { + unsafe { + use std::ptr::read; + + // Join the loader thread + self.req_send.send(LoaderRequest::End).unwrap(); + let mut remains = read(&*self.joiner).join().unwrap().unwrap(); + + // Process any ones that just got done loading + self.process_responses(); + + // Return all the texture memory and descriptors. + for (i, v) in self.blocks.drain() { + debug!("Deactivating blockref {:?}", i); + if let Some(block) = v { + block.deactivate( + device, + &mut *remains.tex_allocator, + &mut remains.descriptor_allocator, + ); + } + } + + debug!("Deactivated all blocks"); + + // Dispose of both allocators + read(&*remains.tex_allocator).dispose(); + read(&*remains.descriptor_allocator).dispose(device); + + debug!("Disposed of allocators"); + } + } +} diff --git a/stockton-render/src/draw/texture/resolver.rs b/stockton-render/src/draw/texture/resolver.rs index f33f0a1..a0df7d8 100644 --- a/stockton-render/src/draw/texture/resolver.rs +++ b/stockton-render/src/draw/texture/resolver.rs @@ -20,10 +20,10 @@ use crate::draw::texture::image::LoadableImage; use stockton_levels::traits::textures::Texture; -use image::{io::Reader, RgbaImage}; - use std::path::Path; +use image::{io::Reader, RgbaImage}; + /// An object that can be used to resolve a texture from a BSP File pub trait TextureResolver<T: LoadableImage> { /// Get the given texture, or None if it's corrupt/not there. @@ -31,17 +31,17 @@ pub trait TextureResolver<T: LoadableImage> { } /// A basic filesystem resolver which expects no file extension and guesses the image format -pub struct BasicFSResolver<'a> { +pub struct BasicFsResolver<'a> { path: &'a Path, } -impl<'a> BasicFSResolver<'a> { - pub fn new(path: &'a Path) -> BasicFSResolver<'a> { - BasicFSResolver { path } +impl<'a> BasicFsResolver<'a> { + pub fn new(path: &'a Path) -> BasicFsResolver<'a> { + BasicFsResolver { path } } } -impl<'a> TextureResolver<RgbaImage> for BasicFSResolver<'a> { +impl<'a> TextureResolver<RgbaImage> for BasicFsResolver<'a> { fn resolve(&mut self, tex: &Texture) -> Option<RgbaImage> { let path = self.path.join(&tex.name); diff --git a/stockton-render/src/draw/texture/staging_buffer.rs b/stockton-render/src/draw/texture/staging_buffer.rs new file mode 100644 index 0000000..d1897ad --- /dev/null +++ b/stockton-render/src/draw/texture/staging_buffer.rs @@ -0,0 +1,56 @@ +use crate::types::*; + +use std::mem::ManuallyDrop; + +use hal::{device::MapError, prelude::*, MemoryTypeId}; +use rendy_memory::{Allocator, Block}; + +pub struct StagingBuffer { + pub buf: ManuallyDrop<Buffer>, + pub mem: ManuallyDrop<DynamicBlock>, +} + +impl StagingBuffer { + const USAGE: hal::buffer::Usage = hal::buffer::Usage::TRANSFER_SRC; + + pub fn new( + device: &mut Device, + alloc: &mut DynamicAllocator, + size: u64, + _memory_type_id: MemoryTypeId, + ) -> Result<StagingBuffer, &'static str> { + let mut buffer = unsafe { device.create_buffer(size, Self::USAGE) } + .map_err(|_| "Couldn't create staging buffer")?; + + let requirements = unsafe { device.get_buffer_requirements(&buffer) }; + + let (memory, _) = alloc + .alloc(device, requirements.size, requirements.alignment) + .map_err(|_| "Couldn't allocate staging memory")?; + + unsafe { device.bind_buffer_memory(memory.memory(), 0, &mut buffer) } + .map_err(|_| "Couldn't bind staging memory to buffer")?; + + Ok(StagingBuffer { + buf: ManuallyDrop::new(buffer), + mem: ManuallyDrop::new(memory), + }) + } + + pub unsafe fn map_memory(&mut self, device: &mut Device) -> Result<*mut u8, MapError> { + device.map_memory(self.mem.memory(), self.mem.range()) + } + pub unsafe fn unmap_memory(&mut self, device: &mut Device) { + device.unmap_memory(self.mem.memory()); // TODO: What if the same Memory is mapped in multiple places? + } + + pub fn deactivate(self, device: &mut Device, alloc: &mut DynamicAllocator) { + unsafe { + use std::ptr::read; + // Destroy buffer + device.destroy_buffer(read(&*self.buf)); + // Free memory + alloc.free(device, read(&*self.mem)); + } + } +} |