diff options
Diffstat (limited to 'stockton-skeleton/src/texture')
-rw-r--r-- | stockton-skeleton/src/texture/block.rs | 62 | ||||
-rw-r--r-- | stockton-skeleton/src/texture/image.rs | 43 | ||||
-rw-r--r-- | stockton-skeleton/src/texture/load.rs | 191 | ||||
-rw-r--r-- | stockton-skeleton/src/texture/loader.rs | 711 | ||||
-rw-r--r-- | stockton-skeleton/src/texture/mod.rs | 18 | ||||
-rw-r--r-- | stockton-skeleton/src/texture/repo.rs | 199 | ||||
-rw-r--r-- | stockton-skeleton/src/texture/resolver.rs | 55 | ||||
-rw-r--r-- | stockton-skeleton/src/texture/staging_buffer.rs | 59 |
8 files changed, 1338 insertions, 0 deletions
diff --git a/stockton-skeleton/src/texture/block.rs b/stockton-skeleton/src/texture/block.rs new file mode 100644 index 0000000..5ac3a94 --- /dev/null +++ b/stockton-skeleton/src/texture/block.rs @@ -0,0 +1,62 @@ +use super::{loader::BlockRef, repo::BLOCK_SIZE}; +use crate::types::*; + +use arrayvec::ArrayVec; +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 DeviceT, + 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<ImageT>, + pub img_view: ManuallyDrop<ImageViewT>, + pub sampler: ManuallyDrop<SamplerT>, + 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 DeviceT, + 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-skeleton/src/texture/image.rs b/stockton-skeleton/src/texture/image.rs new file mode 100644 index 0000000..f984b72 --- /dev/null +++ b/stockton-skeleton/src/texture/image.rs @@ -0,0 +1,43 @@ +use super::PIXEL_SIZE; + +use core::ptr::copy_nonoverlapping; +use std::convert::TryInto; + +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; + + /// # Safety + /// Ensure the ptr is at least width() * PIXEL_SIZE bytes. + unsafe fn copy_row(&self, y: u32, ptr: *mut u8); + + /// # Safety + /// Ensure the ptr is at least row_size * height() * PIXEL_SIZE bytes. + 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)); + } + } +} + +impl LoadableImage for RgbaImage { + fn width(&self) -> u32 { + self.width() + } + + fn height(&self) -> u32 { + self.height() + } + + unsafe fn copy_row(&self, y: u32, ptr: *mut u8) { + let row_size_bytes = self.width() as usize * PIXEL_SIZE; + let raw: &Vec<u8> = self.as_raw(); + let row = &raw[y as usize * row_size_bytes..(y as usize + 1) * row_size_bytes]; + + copy_nonoverlapping(row.as_ptr(), ptr, row.len()); + } +} diff --git a/stockton-skeleton/src/texture/load.rs b/stockton-skeleton/src/texture/load.rs new file mode 100644 index 0000000..1f33ad5 --- /dev/null +++ b/stockton-skeleton/src/texture/load.rs @@ -0,0 +1,191 @@ +use super::{ + block::LoadedImage, block::TexturesBlock, repo::BLOCK_SIZE, resolver::TextureResolver, + staging_buffer::StagingBuffer, LoadableImage, PIXEL_SIZE, +}; +use crate::types::*; + +use anyhow::{Context, Result}; +use arrayvec::ArrayVec; +use hal::{ + format::{Aspects, Format, Swizzle}, + image::{ + Filter, SamplerDesc, SubresourceLayers, SubresourceRange, Usage as ImgUsage, ViewKind, + WrapMode, + }, + memory::SparseFlags, + MemoryTypeId, +}; +use rendy_memory::{Allocator, Block}; +use std::mem::ManuallyDrop; +use thiserror::Error; + +#[derive(Error, Debug)] +pub enum TextureLoadError { + #[error("No available resources")] + NoResources, +} + +pub const FORMAT: Format = Format::Rgba8Srgb; +pub const RESOURCES: SubresourceRange = SubresourceRange { + aspects: Aspects::COLOR, + level_start: 0, + level_count: Some(1), + layer_start: 0, + layer_count: Some(1), +}; +pub const LAYERS: SubresourceLayers = SubresourceLayers { + aspects: Aspects::COLOR, + level: 0, + layers: 0..1, +}; + +pub struct TextureLoadConfig<R: TextureResolver> { + pub resolver: R, + pub filter: Filter, + pub wrap_mode: WrapMode, +} + +pub struct QueuedLoad<B: Block<back::Backend>> { + pub fence: FenceT, + pub buf: CommandBufferT, + pub block: TexturesBlock<B>, + pub staging_bufs: ArrayVec<[StagingBuffer; BLOCK_SIZE]>, +} + +impl<B: Block<back::Backend>> QueuedLoad<B> { + pub fn dissolve( + self, + ) -> ( + (FenceT, CommandBufferT), + ArrayVec<[StagingBuffer; BLOCK_SIZE]>, + TexturesBlock<B>, + ) { + ((self.fence, self.buf), self.staging_bufs, self.block) + } +} + +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) +} + +pub fn create_image_view<T, I>( + device: &mut DeviceT, + allocator: &mut T, + format: Format, + usage: ImgUsage, + img: &I, +) -> Result<(T::Block, ImageT)> +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, + SparseFlags::empty(), + ViewCapabilities::empty(), + ) + } + .context("Error creating image")?; + + // Allocate memory + let (block, _) = unsafe { + let requirements = device.get_image_requirements(&image_ref); + + allocator.alloc(device, requirements.size, requirements.alignment) + } + .context("Error allocating memory")?; + + unsafe { + device + .bind_image_memory(block.memory(), block.range().start, &mut image_ref) + .context("Error binding memory to image")?; + } + + Ok((block, image_ref)) +} + +pub unsafe fn load_image<I: LoadableImage, R: TextureResolver>( + device: &mut DeviceT, + staging_allocator: &mut DynamicAllocator, + tex_allocator: &mut DynamicAllocator, + staging_memory_type: MemoryTypeId, + obcpa: u64, + img_data: I, + config: &TextureLoadConfig<R>, +) -> Result<(StagingBuffer, LoadedImage<DynamicBlock>)> { + // Calculate buffer size + let (row_size, total_size) = tex_size_info(&img_data, obcpa); + + // Create staging buffer + let mut staging_buffer = StagingBuffer::new( + device, + staging_allocator, + total_size as u64, + staging_memory_type, + ) + .context("Error creating staging buffer")?; + + // Write to staging buffer + let mapped_memory = staging_buffer + .map_memory(device) + .context("Error mapping staged memory")?; + + img_data.copy_into(mapped_memory, row_size); + + staging_buffer.unmap_memory(device); + + // Create image + let (img_mem, img) = create_image_view( + device, + tex_allocator, + FORMAT, + ImgUsage::SAMPLED | ImgUsage::TRANSFER_DST, + &img_data, + ) + .context("Error creating image")?; + + // Create image view + let img_view = device + .create_image_view( + &img, + ViewKind::D2, + FORMAT, + Swizzle::NO, + ImgUsage::SAMPLED | ImgUsage::TRANSFER_DST, + RESOURCES, + ) + .context("Error creating image view")?; + + // Create sampler + let sampler = device + .create_sampler(&SamplerDesc::new(config.filter, config.wrap_mode)) + .context("Error creating sampler")?; + + Ok(( + staging_buffer, + 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(), + }, + )) +} diff --git a/stockton-skeleton/src/texture/loader.rs b/stockton-skeleton/src/texture/loader.rs new file mode 100644 index 0000000..5c85fd3 --- /dev/null +++ b/stockton-skeleton/src/texture/loader.rs @@ -0,0 +1,711 @@ +//! Manages the loading/unloading of textures + +use super::{ + block::{LoadedImage, TexturesBlock}, + load::{load_image, QueuedLoad, TextureLoadConfig, TextureLoadError, LAYERS, RESOURCES}, + repo::BLOCK_SIZE, + resolver::TextureResolver, + PIXEL_SIZE, +}; +use crate::{error::LockPoisoned, types::*, utils::find_memory_type_id}; + +use std::{ + array::IntoIter, + collections::VecDeque, + iter::{empty, once}, + mem::{drop, ManuallyDrop}, + sync::{ + mpsc::{Receiver, Sender}, + Arc, RwLock, + }, + thread::sleep, + time::Duration, +}; + +use anyhow::{Context, Result}; +use arrayvec::ArrayVec; +use hal::{ + command::{BufferImageCopy, CommandBufferFlags}, + format::{Aspects, Format}, + image::{Access, Extent, Layout, Offset, SubresourceLayers, SubresourceRange}, + memory::{Barrier, Dependencies, Properties as MemProps, SparseFlags}, + pso::{Descriptor, DescriptorSetWrite, ImageDescriptorType, PipelineStage, ShaderStageFlags}, + queue::family::QueueFamilyId, + MemoryTypeId, +}; +use image::{Rgba, RgbaImage}; +use log::*; +use rendy_descriptor::{DescriptorRanges, DescriptorSetLayoutBinding, DescriptorType}; +use rendy_memory::DynamicConfig; +use thiserror::Error; + +/// The number of command buffers to have in flight simultaneously. +pub const NUM_SIMULTANEOUS_CMDS: usize = 2; + +/// A reference to a texture of the current map +pub type BlockRef = usize; + +/// Manages the loading/unloading of textures +/// This is expected to load the textures, then send the loaded blocks back +pub struct TextureLoader<R: TextureResolver> { + /// Blocks for which commands have been queued and are done loading once the fence is triggered. + commands_queued: ArrayVec<[QueuedLoad<DynamicBlock>; NUM_SIMULTANEOUS_CMDS]>, + + /// The command buffers used and a fence to go with them + buffers: VecDeque<(FenceT, CommandBufferT)>, + + /// The command pool buffers were allocated from + pool: ManuallyDrop<CommandPoolT>, + + /// The GPU we're submitting to + device: Arc<RwLock<DeviceT>>, + + /// The command queue being used + queue: Arc<RwLock<QueueT>>, + + /// The memory allocator being used for textures + tex_allocator: ManuallyDrop<DynamicAllocator>, + + /// The memory allocator for staging memory + staging_allocator: ManuallyDrop<DynamicAllocator>, + + /// Allocator for descriptor sets + descriptor_allocator: ManuallyDrop<DescriptorAllocator>, + + ds_layout: Arc<RwLock<DescriptorSetLayoutT>>, + + /// Type ID for staging memory + staging_memory_type: MemoryTypeId, + + /// From adapter, used for determining alignment + optimal_buffer_copy_pitch_alignment: hal::buffer::Offset, + + /// Configuration for how to find and load textures + config: TextureLoadConfig<R>, + + /// The channel requests come in. + /// Requests should reference a texture **block**, for example textures 8..16 is block 1. + request_channel: Receiver<LoaderRequest>, + + /// The channel blocks are returned to. + return_channel: Sender<TexturesBlock<DynamicBlock>>, + + /// A filler image for descriptors that aren't needed but still need to be written to + blank_image: ManuallyDrop<LoadedImage<DynamicBlock>>, +} + +#[derive(Error, Debug)] +pub enum TextureLoaderError { + #[error("Couldn't find a suitable memory type")] + NoMemoryTypes, +} + +impl<R: TextureResolver> TextureLoader<R> { + pub fn loop_until_exit(mut self) -> Result<TextureLoaderRemains> { + debug!("TextureLoader starting main loop"); + let mut res = Ok(false); + while res.is_ok() { + res = self.main(); + if let Ok(true) = res { + break; + } + + sleep(Duration::from_secs(0)); + } + + match res { + Ok(true) => { + debug!("Starting to deactivate TextureLoader"); + + Ok(self.deactivate()) + } + Err(r) => Err(r.context("Error in TextureLoader loop")), + _ => unreachable!(), + } + } + fn main(&mut self) -> Result<bool> { + let mut device = self + .device + .write() + .map_err(|_| LockPoisoned::Device) + .context("Error getting device lock")?; + // Check for blocks that are finished, then send them back + let mut i = 0; + while i < self.commands_queued.len() { + let signalled = unsafe { device.get_fence_status(&self.commands_queued[i].fence) } + .context("Error checking fence status")?; + + if signalled { + let (assets, mut staging_bufs, block) = self.commands_queued.remove(i).dissolve(); + debug!("Load finished for texture block {:?}", block.id); + + // Destroy staging buffers + for buf in staging_bufs.drain(..) { + buf.deactivate(&mut device, &mut self.staging_allocator); + } + + self.buffers.push_back(assets); + self.return_channel + .send(block) + .context("Error returning texture block")?; + } else { + i += 1; + } + } + + drop(device); + + // 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 + debug!("Attempting to queue load for texture block {:?}", to_load); + + let result = unsafe { self.attempt_queue_load(to_load) }; + match result { + Ok(queued_load) => self.commands_queued.push(queued_load), + Err(x) => match x.downcast_ref::<TextureLoadError>() { + Some(TextureLoadError::NoResources) => { + debug!("No resources, trying again later"); + } + _ => return Err(x).context("Error queuing texture load"), + }, + } + } + LoaderRequest::End => return Ok(true), + } + } + + Ok(false) + } + + pub fn new( + adapter: &Adapter, + device_lock: Arc<RwLock<DeviceT>>, + (family, queue_lock): (QueueFamilyId, Arc<RwLock<QueueT>>), + ds_layout: Arc<RwLock<DescriptorSetLayoutT>>, + (request_channel, return_channel): ( + Receiver<LoaderRequest>, + Sender<TexturesBlock<DynamicBlock>>, + ), + config: TextureLoadConfig<R>, + ) -> Result<Self> { + let mut device = device_lock + .write() + .map_err(|_| LockPoisoned::Device) + .context("Error getting device lock")?; + let device_props = adapter.physical_device.properties(); + + 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, + SparseFlags::empty(), + ViewCapabilities::empty(), + ) + .context("Error creating test image to get buffer settings")?; + + let type_mask = device.get_image_requirements(&img).type_mask; + + device.destroy_image(img); + + type_mask + }; + + debug!("Using type mask {:?}", type_mask); + + // Tex Allocator + let mut tex_allocator = { + let props = MemProps::DEVICE_LOCAL; + + DynamicAllocator::new( + find_memory_type_id(adapter, type_mask, props) + .ok_or(TextureLoaderError::NoMemoryTypes) + .context("Couldn't create tex memory allocator")?, + props, + DynamicConfig { + block_size_granularity: 4 * 32 * 32, // 32x32 image + max_chunk_size: u64::pow(2, 63), + min_device_allocation: 4 * 32 * 32, + }, + device_props.limits.non_coherent_atom_size as u64, + ) + }; + + let (staging_memory_type, mut staging_allocator) = { + let props = MemProps::CPU_VISIBLE | MemProps::COHERENT; + let t = find_memory_type_id(adapter, u32::MAX, props) + .ok_or(TextureLoaderError::NoMemoryTypes) + .context("Couldn't create staging memory allocator")?; + ( + 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, + }, + device_props.limits.non_coherent_atom_size as u64, + ), + ) + }; + + // Pool + let mut pool = unsafe { + use hal::pool::CommandPoolCreateFlags; + + device.create_command_pool(family, CommandPoolCreateFlags::RESET_INDIVIDUAL) + } + .context("Error creating command pool")?; + + // Command buffers and fences + debug!("Creating resources..."); + let mut buffers = { + let mut data = VecDeque::with_capacity(NUM_SIMULTANEOUS_CMDS); + + for _ in 0..NUM_SIMULTANEOUS_CMDS { + unsafe { + data.push_back(( + device.create_fence(false).context("Error creating fence")?, + pool.allocate_one(hal::command::Level::Primary), + )); + }; + } + + data + }; + + let optimal_buffer_copy_pitch_alignment = + device_props.limits.optimal_buffer_copy_pitch_alignment; + + let blank_image = unsafe { + Self::get_blank_image( + &mut device, + &mut buffers[0].1, + &queue_lock, + (&mut staging_allocator, &mut tex_allocator), + staging_memory_type, + optimal_buffer_copy_pitch_alignment, + &config, + ) + } + .context("Error creating blank image")?; + + drop(device); + + Ok(TextureLoader { + commands_queued: ArrayVec::new(), + buffers, + pool: ManuallyDrop::new(pool), + device: device_lock, + queue: queue_lock, + 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, + + request_channel, + return_channel, + config, + blank_image: ManuallyDrop::new(blank_image), + }) + } + + unsafe fn attempt_queue_load(&mut self, block_ref: usize) -> Result<QueuedLoad<DynamicBlock>> { + let mut device = self + .device + .write() + .map_err(|_| LockPoisoned::Device) + .context("Error getting device lock")?; + + // Get assets to use + let (mut fence, mut buf) = self + .buffers + .pop_front() + .ok_or(TextureLoadError::NoResources) + .context("Error getting resources to use")?; + + // Create descriptor set + let mut descriptor_set = { + let mut v: ArrayVec<[RDescriptorSet; 1]> = ArrayVec::new(); + self.descriptor_allocator + .allocate( + &device, + &*self + .ds_layout + .read() + .map_err(|_| LockPoisoned::Other) + .context("Error reading descriptor set layout")?, + DescriptorRanges::from_bindings(&[ + DescriptorSetLayoutBinding { + binding: 0, + ty: DescriptorType::Image { + ty: ImageDescriptorType::Sampled { + with_sampler: false, + }, + }, + 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, + ) + .context("Error creating descriptor set")?; + + v.pop().unwrap() + }; + + // Get a command buffer + buf.begin_primary(CommandBufferFlags::ONE_TIME_SUBMIT); + + 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 { + // Resolve texture + let img_data = self.config.resolver.resolve(tex_idx as u32); + if img_data.is_none() { + // Write a blank descriptor + device.write_descriptor_set(DescriptorSetWrite { + set: descriptor_set.raw_mut(), + binding: 0, + array_offset: tex_idx % BLOCK_SIZE, + descriptors: once(Descriptor::Image( + &*self.blank_image.img_view, + Layout::ShaderReadOnlyOptimal, + )), + }); + device.write_descriptor_set(DescriptorSetWrite { + set: descriptor_set.raw_mut(), + binding: 1, + array_offset: tex_idx % BLOCK_SIZE, + descriptors: once(Descriptor::Sampler(&*self.blank_image.sampler)), + }); + + continue; + } + + let img_data = img_data.unwrap(); + + let array_offset = tex_idx % BLOCK_SIZE; + + let (staging_buffer, img) = load_image( + &mut device, + &mut self.staging_allocator, + &mut self.tex_allocator, + self.staging_memory_type, + self.optimal_buffer_copy_pitch_alignment, + img_data, + &self.config, + )?; + + // Write to descriptor set + { + device.write_descriptor_set(DescriptorSetWrite { + set: descriptor_set.raw_mut(), + binding: 0, + array_offset, + descriptors: once(Descriptor::Image( + &*img.img_view, + Layout::ShaderReadOnlyOptimal, + )), + }); + device.write_descriptor_set(DescriptorSetWrite { + set: descriptor_set.raw_mut(), + binding: 1, + array_offset, + descriptors: once(Descriptor::Sampler(&*img.sampler)), + }); + } + + imgs.push(img); + + staging_bufs.push(staging_buffer); + } + + // Add start pipeline barrier + buf.pipeline_barrier( + PipelineStage::TOP_OF_PIPE..PipelineStage::TRANSFER, + Dependencies::empty(), + imgs.iter().map(|li| Barrier::Image { + states: (Access::empty(), Layout::Undefined) + ..(Access::TRANSFER_WRITE, Layout::TransferDstOptimal), + target: &*li.img, + families: None, + range: SubresourceRange { + aspects: Aspects::COLOR, + level_start: 0, + level_count: None, + layer_start: 0, + layer_count: None, + }, + }), + ); + + // Record copy commands + for (li, sb) in imgs.iter().zip(staging_bufs.iter()) { + buf.copy_buffer_to_image( + &*sb.buf, + &*li.img, + Layout::TransferDstOptimal, + once(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, + }, + }), + ); + } + buf.pipeline_barrier( + PipelineStage::TRANSFER..PipelineStage::BOTTOM_OF_PIPE, + Dependencies::empty(), + imgs.iter().map(|li| Barrier::Image { + states: (Access::TRANSFER_WRITE, Layout::TransferDstOptimal) + ..(Access::empty(), Layout::ShaderReadOnlyOptimal), + target: &*li.img, + families: None, + range: RESOURCES, + }), + ); + + buf.finish(); + + // Submit command buffer + { + let mut queue = self.queue.write().map_err(|_| LockPoisoned::Queue)?; + + queue.submit(IntoIter::new([&buf]), empty(), empty(), Some(&mut fence)); + } + + Ok(QueuedLoad { + staging_bufs, + fence, + buf, + block: TexturesBlock { + id: block_ref, + imgs, + descriptor_set: ManuallyDrop::new(descriptor_set), + }, + }) + } + + unsafe fn get_blank_image( + device: &mut DeviceT, + buf: &mut CommandBufferT, + queue_lock: &Arc<RwLock<QueueT>>, + (staging_allocator, tex_allocator): (&mut DynamicAllocator, &mut DynamicAllocator), + staging_memory_type: MemoryTypeId, + obcpa: u64, + config: &TextureLoadConfig<R>, + ) -> Result<LoadedImage<DynamicBlock>> { + let img_data = RgbaImage::from_pixel(1, 1, Rgba([255, 0, 255, 255])); + + let height = img_data.height(); + let width = img_data.width(); + let row_alignment_mask = obcpa as u32 - 1; + let initial_row_size = PIXEL_SIZE * img_data.width() as usize; + let row_size = + ((initial_row_size as u32 + row_alignment_mask) & !row_alignment_mask) as usize; + + let (staging_buffer, img) = load_image( + device, + staging_allocator, + tex_allocator, + staging_memory_type, + obcpa, + img_data, + config, + )?; + + buf.begin_primary(CommandBufferFlags::ONE_TIME_SUBMIT); + + buf.pipeline_barrier( + PipelineStage::TOP_OF_PIPE..PipelineStage::TRANSFER, + Dependencies::empty(), + once(Barrier::Image { + states: (Access::empty(), Layout::Undefined) + ..(Access::TRANSFER_WRITE, Layout::TransferDstOptimal), + target: &*img.img, + families: None, + range: SubresourceRange { + aspects: Aspects::COLOR, + level_start: 0, + level_count: None, + layer_start: 0, + layer_count: None, + }, + }), + ); + buf.copy_buffer_to_image( + &*staging_buffer.buf, + &*img.img, + Layout::TransferDstOptimal, + once(BufferImageCopy { + buffer_offset: 0, + buffer_width: (row_size / super::PIXEL_SIZE) as u32, + buffer_height: height, + image_layers: LAYERS, + image_offset: Offset { x: 0, y: 0, z: 0 }, + image_extent: Extent { + width, + height, + depth: 1, + }, + }), + ); + + buf.pipeline_barrier( + PipelineStage::TRANSFER..PipelineStage::BOTTOM_OF_PIPE, + Dependencies::empty(), + once(Barrier::Image { + states: (Access::TRANSFER_WRITE, Layout::TransferDstOptimal) + ..(Access::empty(), Layout::ShaderReadOnlyOptimal), + target: &*img.img, + families: None, + range: RESOURCES, + }), + ); + buf.finish(); + + let mut fence = device.create_fence(false).context("Error creating fence")?; + + { + let mut queue = queue_lock.write().map_err(|_| LockPoisoned::Queue)?; + + queue.submit( + IntoIter::new([buf as &CommandBufferT]), + empty(), + empty(), + Some(&mut fence), + ); + } + + device + .wait_for_fence(&fence, std::u64::MAX) + .context("Error waiting for copy")?; + + device.destroy_fence(fence); + + staging_buffer.deactivate(device, staging_allocator); + + Ok(img) + } + + /// 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; + + let mut device = self.device.write().unwrap(); + + 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 = 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(); + + device.destroy_fence(assets.0); + // Command buffer will be freed when we reset the command pool + // Destroy staging buffers + for buf in staging_bufs.drain(..) { + buf.deactivate(&mut device, &mut self.staging_allocator); + } + + self.return_channel + .send(block) + .expect("Sending through return channel failed"); + } else { + i += 1; + } + } + + sleep(Duration::from_secs(0)); + } + + // Destroy blank image + read(&*self.blank_image).deactivate(&mut device, &mut *self.tex_allocator); + + // Destroy fences + + self.buffers + .drain(..) + .map(|(f, _)| device.destroy_fence(f)) + .for_each(|_| {}); + + // Free command pool + self.pool.reset(true); + 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)), + } + } + } +} + +pub struct TextureLoaderRemains { + pub tex_allocator: ManuallyDrop<DynamicAllocator>, + pub descriptor_allocator: ManuallyDrop<DescriptorAllocator>, +} + +pub enum LoaderRequest { + /// Load the given block + Load(BlockRef), + + /// Stop looping and deactivate + End, +} diff --git a/stockton-skeleton/src/texture/mod.rs b/stockton-skeleton/src/texture/mod.rs new file mode 100644 index 0000000..aef1b03 --- /dev/null +++ b/stockton-skeleton/src/texture/mod.rs @@ -0,0 +1,18 @@ +//! Everything related to loading textures into GPU memory + +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::load::TextureLoadConfig; +pub use self::loader::BlockRef; +pub use self::repo::{TexLoadQueue, TextureRepo}; + +/// The size of each pixel in an image +pub const PIXEL_SIZE: usize = std::mem::size_of::<u8>() * 4; diff --git a/stockton-skeleton/src/texture/repo.rs b/stockton-skeleton/src/texture/repo.rs new file mode 100644 index 0000000..341d355 --- /dev/null +++ b/stockton-skeleton/src/texture/repo.rs @@ -0,0 +1,199 @@ +use super::{ + block::TexturesBlock, + load::TextureLoadConfig, + loader::{BlockRef, LoaderRequest, TextureLoader, TextureLoaderRemains, NUM_SIMULTANEOUS_CMDS}, + resolver::TextureResolver, +}; +use crate::error::LockPoisoned; +use crate::queue_negotiator::QueueFamilySelector; +use crate::types::*; + +use std::{ + array::IntoIter, + collections::HashMap, + iter::empty, + mem::ManuallyDrop, + sync::{ + mpsc::{channel, Receiver, Sender}, + Arc, RwLock, RwLockReadGuard, + }, + thread::JoinHandle, +}; + +use anyhow::{Context, Result}; +use hal::{ + pso::{DescriptorSetLayoutBinding, DescriptorType, ImageDescriptorType, ShaderStageFlags}, + queue::family::QueueFamilyId, +}; +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 { + joiner: ManuallyDrop<JoinHandle<Result<TextureLoaderRemains>>>, + ds_layout: Arc<RwLock<DescriptorSetLayoutT>>, + req_send: Sender<LoaderRequest>, + resp_recv: Receiver<TexturesBlock<DynamicBlock>>, + blocks: HashMap<BlockRef, Option<TexturesBlock<DynamicBlock>>>, +} + +impl TextureRepo { + pub fn new<R: 'static + TextureResolver + Send + Sync>( + device_lock: Arc<RwLock<DeviceT>>, + family: QueueFamilyId, + queue: Arc<RwLock<QueueT>>, + adapter: &Adapter, + config: TextureLoadConfig<R>, + ) -> Result<Self> { + // Create Channels + let (req_send, req_recv) = channel(); + let (resp_send, resp_recv) = channel(); + let device = device_lock + .write() + .map_err(|_| LockPoisoned::Device) + .context("Error getting device lock")?; + + // Create descriptor set layout + let ds_lock = Arc::new(RwLock::new( + unsafe { + device.create_descriptor_set_layout( + IntoIter::new([ + DescriptorSetLayoutBinding { + binding: 0, + ty: DescriptorType::Image { + ty: ImageDescriptorType::Sampled { + with_sampler: false, + }, + }, + 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, + }, + ]), + empty(), + ) + } + .context("Error creating descriptor set layout")?, + )); + + debug!("Created descriptor set layout {:?}", ds_lock); + + drop(device); + + let joiner = { + let loader = TextureLoader::new( + adapter, + device_lock.clone(), + (family, queue), + ds_lock.clone(), + (req_recv, resp_send), + config, + )?; + + std::thread::spawn(move || loader.loop_until_exit()) + }; + + Ok(TextureRepo { + joiner: ManuallyDrop::new(joiner), + ds_layout: ds_lock, + blocks: HashMap::new(), + req_send, + resp_recv, + }) + } + + pub fn get_ds_layout(&self) -> Result<RwLockReadGuard<DescriptorSetLayoutT>> { + self.ds_layout + .read() + .map_err(|_| LockPoisoned::Other) + .context("Error locking descriptor set layout") + } + + pub fn queue_load(&mut self, block_id: BlockRef) -> Result<()> { + 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<()> { + self.req_send + .send(LoaderRequest::Load(block_id)) + .context("Error queuing texture block load")?; + + self.blocks.insert(block_id, None); + + Ok(()) + } + + pub fn attempt_get_descriptor_set(&mut self, block_id: BlockRef) -> Option<&DescriptorSetT> { + 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_lock: &Arc<RwLock<DeviceT>>) { + 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(); + + // Only now can we lock device without deadlocking + let mut device = device_lock.write().unwrap(); + + // Return all the texture memory and descriptors. + for (_, v) in self.blocks.drain() { + if let Some(block) = v { + block.deactivate( + &mut device, + &mut *remains.tex_allocator, + &mut remains.descriptor_allocator, + ); + } + } + + // Dispose of both allocators + read(&*remains.tex_allocator).dispose(); + read(&*remains.descriptor_allocator).dispose(&device); + + // Deactivate DS Layout + let ds_layout = Arc::try_unwrap(self.ds_layout) + .unwrap() + .into_inner() + .unwrap(); + device.destroy_descriptor_set_layout(ds_layout); + } + } +} + +pub struct TexLoadQueue; + +impl QueueFamilySelector for TexLoadQueue { + fn is_suitable(&self, family: &QueueFamilyT) -> bool { + family.queue_type().supports_transfer() && family.max_queues() >= NUM_SIMULTANEOUS_CMDS + } +} diff --git a/stockton-skeleton/src/texture/resolver.rs b/stockton-skeleton/src/texture/resolver.rs new file mode 100644 index 0000000..f66b724 --- /dev/null +++ b/stockton-skeleton/src/texture/resolver.rs @@ -0,0 +1,55 @@ +//! Resolves a texture in a BSP File to an image + +use crate::texture::image::LoadableImage; +use stockton_levels::{parts::IsTexture, prelude::HasTextures}; + +use std::{ + path::Path, + sync::{Arc, RwLock}, +}; + +use image::{io::Reader, RgbaImage}; + +/// An object that can be used to resolve a texture from a BSP File +pub trait TextureResolver { + type Image: LoadableImage; + + /// Get the given texture, or None if it's corrupt/not there. + fn resolve(&mut self, texture_id: u32) -> Option<Self::Image>; +} + +/// A basic filesystem resolver which gets the texture name from any HasTextures Object. +pub struct FsResolver<'a, T: HasTextures> { + path: &'a Path, + map_lock: Arc<RwLock<T>>, +} + +impl<'a, T: HasTextures> FsResolver<'a, T> { + pub fn new(path: &'a Path, map_lock: Arc<RwLock<T>>) -> Self { + FsResolver { path, map_lock } + } +} + +impl<'a, T: HasTextures> TextureResolver for FsResolver<'a, T> { + type Image = RgbaImage; + + fn resolve(&mut self, tex: u32) -> Option<Self::Image> { + let map = self.map_lock.read().unwrap(); + let tex = map.get_texture(tex)?; + let path = self.path.join(&tex.name()); + + // drop(tex); + // drop(map); + + if let Ok(file) = Reader::open(path) { + if let Ok(guessed) = file.with_guessed_format() { + if let Ok(decoded) = guessed.decode() { + return Some(decoded.into_rgba8()); + } + } + } + + log::warn!("Couldn't resolve texture {:?}", tex.name()); + None + } +} diff --git a/stockton-skeleton/src/texture/staging_buffer.rs b/stockton-skeleton/src/texture/staging_buffer.rs new file mode 100644 index 0000000..8d2ae17 --- /dev/null +++ b/stockton-skeleton/src/texture/staging_buffer.rs @@ -0,0 +1,59 @@ +#![allow(mutable_transmutes)] +use crate::types::*; + +use std::mem::ManuallyDrop; + +use anyhow::{Context, Result}; +use hal::{device::MapError, memory::SparseFlags, MemoryTypeId}; +use rendy_memory::{Allocator, Block}; + +pub struct StagingBuffer { + pub buf: ManuallyDrop<BufferT>, + pub mem: ManuallyDrop<DynamicBlock>, +} + +impl StagingBuffer { + const USAGE: hal::buffer::Usage = hal::buffer::Usage::TRANSFER_SRC; + + pub fn new( + device: &mut DeviceT, + alloc: &mut DynamicAllocator, + size: u64, + _memory_type_id: MemoryTypeId, + ) -> Result<StagingBuffer> { + let mut buffer = unsafe { device.create_buffer(size, Self::USAGE, SparseFlags::empty()) } + .context("Error creating buffer")?; + + let requirements = unsafe { device.get_buffer_requirements(&buffer) }; + + let (memory, _) = alloc + .alloc(device, requirements.size, requirements.alignment) + .context("Error allocating staging memory")?; + + unsafe { device.bind_buffer_memory(memory.memory(), 0, &mut buffer) } + .context("Error binding staging memory to buffer")?; + + Ok(StagingBuffer { + buf: ManuallyDrop::new(buffer), + mem: ManuallyDrop::new(memory), + }) + } + + pub unsafe fn map_memory(&mut self, device: &mut DeviceT) -> Result<*mut u8, MapError> { + let range = 0..(self.mem.range().end - self.mem.range().start); + Ok(self.mem.map(device, range)?.ptr().as_mut()) + } + pub unsafe fn unmap_memory(&mut self, device: &mut DeviceT) { + self.mem.unmap(device); + } + + pub fn deactivate(self, device: &mut DeviceT, alloc: &mut DynamicAllocator) { + unsafe { + use std::ptr::read; + // Destroy buffer + device.destroy_buffer(read(&*self.buf)); + // Free memory + alloc.free(device, read(&*self.mem)); + } + } +} |