aboutsummaryrefslogtreecommitdiff
path: root/stockton-render/src/draw/texture
diff options
context:
space:
mode:
authortcmal <me@aria.rip>2024-08-25 17:44:22 +0100
committertcmal <me@aria.rip>2024-08-25 17:44:22 +0100
commitc48b54f3fb7bbe9046915eb99eca02fa84dc55c9 (patch)
tree752831451d2bd3a658485df724a01ae39e80fae3 /stockton-render/src/draw/texture
parentb437109ebf4da243fd643f0a31546d0d0155b0a4 (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.rs63
-rw-r--r--stockton-render/src/draw/texture/chunk.rs207
-rw-r--r--stockton-render/src/draw/texture/image.rs639
-rw-r--r--stockton-render/src/draw/texture/load.rs352
-rw-r--r--stockton-render/src/draw/texture/loader.rs569
-rw-r--r--stockton-render/src/draw/texture/mod.rs22
-rw-r--r--stockton-render/src/draw/texture/repo.rs192
-rw-r--r--stockton-render/src/draw/texture/resolver.rs14
-rw-r--r--stockton-render/src/draw/texture/staging_buffer.rs56
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));
+ }
+ }
+}