aboutsummaryrefslogtreecommitdiff
path: root/stockton-skeleton/src/texture
diff options
context:
space:
mode:
Diffstat (limited to 'stockton-skeleton/src/texture')
-rw-r--r--stockton-skeleton/src/texture/block.rs45
-rw-r--r--stockton-skeleton/src/texture/load.rs223
-rw-r--r--stockton-skeleton/src/texture/loader.rs259
-rw-r--r--stockton-skeleton/src/texture/mod.rs1
-rw-r--r--stockton-skeleton/src/texture/repo.rs80
-rw-r--r--stockton-skeleton/src/texture/staging_buffer.rs59
6 files changed, 265 insertions, 402 deletions
diff --git a/stockton-skeleton/src/texture/block.rs b/stockton-skeleton/src/texture/block.rs
index 5ac3a94..1b195c2 100644
--- a/stockton-skeleton/src/texture/block.rs
+++ b/stockton-skeleton/src/texture/block.rs
@@ -1,21 +1,22 @@
use super::{loader::BlockRef, repo::BLOCK_SIZE};
-use crate::types::*;
+use crate::{buffers::image::SampledImage, mem::MemoryPool, types::*};
use arrayvec::ArrayVec;
-use rendy_memory::{Allocator, Block};
use std::{iter::once, mem::ManuallyDrop};
-pub struct TexturesBlock<B: Block<back::Backend>> {
+/// A block of loaded textures
+pub struct TexturesBlock<TP: MemoryPool> {
pub id: BlockRef,
pub descriptor_set: ManuallyDrop<RDescriptorSet>,
- pub imgs: ArrayVec<[LoadedImage<B>; BLOCK_SIZE]>,
+ pub imgs: ArrayVec<[SampledImage<TP>; BLOCK_SIZE]>,
}
-impl<B: Block<back::Backend>> TexturesBlock<B> {
- pub fn deactivate<T: Allocator<back::Backend, Block = B>>(
+impl<TP: MemoryPool> TexturesBlock<TP> {
+ /// Destroy all Vulkan objects. Must be called before dropping.
+ pub fn deactivate(
mut self,
device: &mut DeviceT,
- tex_alloc: &mut T,
+ tex_alloc: &mut TP,
desc_alloc: &mut DescriptorAllocator,
) {
unsafe {
@@ -27,36 +28,8 @@ impl<B: Block<back::Backend>> TexturesBlock<B> {
// Images
self.imgs
.drain(..)
- .map(|x| x.deactivate(device, tex_alloc))
+ .map(|x| x.deactivate_with_device_pool(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/load.rs b/stockton-skeleton/src/texture/load.rs
index 1f33ad5..6cb4f4d 100644
--- a/stockton-skeleton/src/texture/load.rs
+++ b/stockton-skeleton/src/texture/load.rs
@@ -1,31 +1,31 @@
-use super::{
- block::LoadedImage, block::TexturesBlock, repo::BLOCK_SIZE, resolver::TextureResolver,
- staging_buffer::StagingBuffer, LoadableImage, PIXEL_SIZE,
+use std::sync::{Arc, RwLock};
+
+use super::{block::TexturesBlock, repo::BLOCK_SIZE, resolver::TextureResolver, LoadableImage};
+use crate::{
+ buffers::{
+ image::{ImageSpec, SampledImage, COLOR_RESOURCES},
+ staging::StagingBuffer,
+ },
+ error::LockPoisoned,
+ mem::{Block, MappableBlock, MemoryPool},
+ types::*,
};
-use crate::types::*;
use anyhow::{Context, Result};
use arrayvec::ArrayVec;
use hal::{
- format::{Aspects, Format, Swizzle},
+ format::{Aspects, Format},
image::{
- Filter, SamplerDesc, SubresourceLayers, SubresourceRange, Usage as ImgUsage, ViewKind,
- WrapMode,
+ Filter, SamplerDesc, SubresourceLayers, SubresourceRange, Usage as ImgUsage, 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,
-}
-
+/// The format used by the texture repo
+// TODO: This should be customisable.
pub const FORMAT: Format = Format::Rgba8Srgb;
+
+/// The resources used by each texture. ie one colour aspect
pub const RESOURCES: SubresourceRange = SubresourceRange {
aspects: Aspects::COLOR,
level_start: 0,
@@ -33,159 +33,112 @@ pub const RESOURCES: SubresourceRange = SubresourceRange {
layer_start: 0,
layer_count: Some(1),
};
+
+/// The layers used by each texture. ie one colour layer
pub const LAYERS: SubresourceLayers = SubresourceLayers {
aspects: Aspects::COLOR,
level: 0,
layers: 0..1,
};
+/// Configuration required to load a texture
pub struct TextureLoadConfig<R: TextureResolver> {
+ /// The resolver to use
pub resolver: R,
+
+ /// How to sample the image
pub filter: Filter,
+
+ /// How to deal with texture coordinates outside the image.
pub wrap_mode: WrapMode,
}
-pub struct QueuedLoad<B: Block<back::Backend>> {
+/// A texture load that has been queued, and is finished when the fence triggers.
+pub struct QueuedLoad<TP: MemoryPool, SP: MemoryPool> {
pub fence: FenceT,
pub buf: CommandBufferT,
- pub block: TexturesBlock<B>,
- pub staging_bufs: ArrayVec<[StagingBuffer; BLOCK_SIZE]>,
+ pub block: TexturesBlock<TP>,
+ pub staging_bufs: ArrayVec<[StagingBuffer<SP>; BLOCK_SIZE]>,
}
-impl<B: Block<back::Backend>> QueuedLoad<B> {
+impl<TP: MemoryPool, SP: MemoryPool> QueuedLoad<TP, SP> {
+ /// Break down into a tuple
pub fn dissolve(
self,
) -> (
(FenceT, CommandBufferT),
- ArrayVec<[StagingBuffer; BLOCK_SIZE]>,
- TexturesBlock<B>,
+ ArrayVec<[StagingBuffer<SP>; BLOCK_SIZE]>,
+ TexturesBlock<TP>,
) {
((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>(
+/// Create a SampledImage for the given LoadableImage, and load the image data into a StagingBuffer
+/// Note that this doesn't queue up transferring from the buffer to the image.
+pub unsafe fn load_image<I, R, SP, TP>(
device: &mut DeviceT,
- allocator: &mut T,
- format: Format,
- usage: ImgUsage,
- img: &I,
-) -> Result<(T::Block, ImageT)>
+ staging_allocator: &Arc<RwLock<SP>>,
+ tex_allocator: &Arc<RwLock<TP>>,
+ obcpa: u32,
+ img_data: I,
+ config: &TextureLoadConfig<R>,
+) -> Result<(StagingBuffer<SP>, SampledImage<TP>)>
where
- T: Allocator<back::Backend>,
I: LoadableImage,
+ R: TextureResolver,
+ SP: MemoryPool,
+ TP: MemoryPool,
+ SP::Block: MappableBlock,
{
- // 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")?;
+ // Create sampled image
+ let sampled_image = {
+ let mut tex_allocator = tex_allocator
+ .write()
+ .map_err(|_| LockPoisoned::MemoryPool)?;
+
+ SampledImage::from_device_allocator(
+ device,
+ &mut *tex_allocator,
+ obcpa as u32,
+ &ImageSpec {
+ width: img_data.width(),
+ height: img_data.height(),
+ format: FORMAT,
+ usage: ImgUsage::TRANSFER_DST | ImgUsage::SAMPLED,
+ resources: COLOR_RESOURCES,
+ },
+ &SamplerDesc::new(config.filter, config.wrap_mode),
+ )?
+ };
- // 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")?;
- }
+ // Create staging buffer
+ let total_size = sampled_image.bound_image().mem().size();
- Ok((block, image_ref))
-}
+ let mut staging_buffer = {
+ let mut staging_allocator = staging_allocator
+ .write()
+ .map_err(|_| LockPoisoned::MemoryPool)?;
-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")?;
+ StagingBuffer::from_device_pool(device, &mut *staging_allocator, total_size as u64)
+ .context("Error creating staging buffer")?
+ };
// Write to staging buffer
let mapped_memory = staging_buffer
- .map_memory(device)
+ .map(device, 0..total_size)
.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(),
- },
- ))
+ img_data.copy_into(mapped_memory, sampled_image.row_size() as usize);
+
+ staging_buffer.unmap(device)?;
+
+ Ok((staging_buffer, sampled_image))
+}
+
+/// Errors that can be encountered when loading a texture.
+#[derive(Error, Debug)]
+pub enum TextureLoadError {
+ #[error("No available resources")]
+ NoResources,
}
diff --git a/stockton-skeleton/src/texture/loader.rs b/stockton-skeleton/src/texture/loader.rs
index 5c85fd3..7f630ab 100644
--- a/stockton-skeleton/src/texture/loader.rs
+++ b/stockton-skeleton/src/texture/loader.rs
@@ -1,13 +1,23 @@
//! Manages the loading/unloading of textures
use super::{
- block::{LoadedImage, TexturesBlock},
- load::{load_image, QueuedLoad, TextureLoadConfig, TextureLoadError, LAYERS, RESOURCES},
+ block::TexturesBlock,
+ load::{
+ load_image, QueuedLoad, TextureLoadConfig, TextureLoadError, FORMAT, LAYERS, RESOURCES,
+ },
repo::BLOCK_SIZE,
resolver::TextureResolver,
PIXEL_SIZE,
};
-use crate::{error::LockPoisoned, types::*, utils::find_memory_type_id};
+use crate::{
+ buffers::image::SampledImage,
+ context::RenderingContext,
+ error::{EnvironmentError, LockPoisoned},
+ mem::{MappableBlock, MemoryPool},
+ queue_negotiator::QueueFamilySelector,
+ types::*,
+ utils::get_pixel_size,
+};
use std::{
array::IntoIter,
@@ -26,18 +36,14 @@ use anyhow::{Context, Result};
use arrayvec::ArrayVec;
use hal::{
command::{BufferImageCopy, CommandBufferFlags},
- format::{Aspects, Format},
+ format::Aspects,
image::{Access, Extent, Layout, Offset, SubresourceLayers, SubresourceRange},
- memory::{Barrier, Dependencies, Properties as MemProps, SparseFlags},
+ memory::{Barrier, Dependencies},
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;
@@ -47,9 +53,15 @@ 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> {
+pub struct TextureLoader<R, TP, SP>
+where
+ R: TextureResolver,
+ TP: MemoryPool,
+ SP: MemoryPool,
+ SP::Block: MappableBlock,
+{
/// Blocks for which commands have been queued and are done loading once the fence is triggered.
- commands_queued: ArrayVec<[QueuedLoad<DynamicBlock>; NUM_SIMULTANEOUS_CMDS]>,
+ commands_queued: ArrayVec<[QueuedLoad<TP, SP>; NUM_SIMULTANEOUS_CMDS]>,
/// The command buffers used and a fence to go with them
buffers: VecDeque<(FenceT, CommandBufferT)>,
@@ -64,21 +76,18 @@ pub struct TextureLoader<R: TextureResolver> {
queue: Arc<RwLock<QueueT>>,
/// The memory allocator being used for textures
- tex_allocator: ManuallyDrop<DynamicAllocator>,
+ tex_mempool: Arc<RwLock<TP>>,
/// The memory allocator for staging memory
- staging_allocator: ManuallyDrop<DynamicAllocator>,
+ staging_mempool: Arc<RwLock<SP>>,
/// 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,
+ optimal_buffer_copy_pitch_alignment: u32,
/// Configuration for how to find and load textures
config: TextureLoadConfig<R>,
@@ -88,19 +97,20 @@ pub struct TextureLoader<R: TextureResolver> {
request_channel: Receiver<LoaderRequest>,
/// The channel blocks are returned to.
- return_channel: Sender<TexturesBlock<DynamicBlock>>,
+ return_channel: Sender<TexturesBlock<TP>>,
/// 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,
+ blank_image: ManuallyDrop<SampledImage<TP>>,
}
-impl<R: TextureResolver> TextureLoader<R> {
+impl<R, TP, SP> TextureLoader<R, TP, SP>
+where
+ R: TextureResolver,
+ TP: MemoryPool,
+ SP: MemoryPool,
+ SP::Block: MappableBlock,
+{
+ /// Keep loading textures until asked to stop. This should be called from a seperate thread.
pub fn loop_until_exit(mut self) -> Result<TextureLoaderRemains> {
debug!("TextureLoader starting main loop");
let mut res = Ok(false);
@@ -123,12 +133,15 @@ impl<R: TextureResolver> TextureLoader<R> {
_ => unreachable!(),
}
}
+
fn main(&mut self) -> Result<bool> {
+ // Get a device lock so we can check fence status
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() {
@@ -139,12 +152,21 @@ impl<R: TextureResolver> TextureLoader<R> {
let (assets, mut staging_bufs, block) = self.commands_queued.remove(i).dissolve();
debug!("Load finished for texture block {:?}", block.id);
+ // Lock staging memory pool
+ let mut staging_mempool = self
+ .staging_mempool
+ .write()
+ .map_err(|_| LockPoisoned::MemoryPool)?;
+
// Destroy staging buffers
for buf in staging_bufs.drain(..) {
- buf.deactivate(&mut device, &mut self.staging_allocator);
+ buf.deactivate_device_pool(&mut device, &mut *staging_mempool);
}
+ // Return assets used for loading
self.buffers.push_back(assets);
+
+ // Send back our loaded block
self.return_channel
.send(block)
.context("Error returning texture block")?;
@@ -153,6 +175,7 @@ impl<R: TextureResolver> TextureLoader<R> {
}
}
+ // Release device lock
drop(device);
// Check for messages to start loading blocks
@@ -181,97 +204,42 @@ impl<R: TextureResolver> TextureLoader<R> {
Ok(false)
}
- pub fn new(
- adapter: &Adapter,
- device_lock: Arc<RwLock<DeviceT>>,
- (family, queue_lock): (QueueFamilyId, Arc<RwLock<QueueT>>),
+ /// Create a new loader from the given context.
+ pub fn new<Q: QueueFamilySelector>(
+ context: &mut RenderingContext,
ds_layout: Arc<RwLock<DescriptorSetLayoutT>>,
- (request_channel, return_channel): (
- Receiver<LoaderRequest>,
- Sender<TexturesBlock<DynamicBlock>>,
- ),
+ (request_channel, return_channel): (Receiver<LoaderRequest>, Sender<TexturesBlock<TP>>),
config: TextureLoadConfig<R>,
) -> Result<Self> {
+ // Queue family & Lock
+ let family = context
+ .queue_negotiator_mut()
+ .family::<Q>()
+ .ok_or(EnvironmentError::NoSuitableFamilies)?;
+ let queue_lock = context
+ .queue_negotiator_mut()
+ .get_queue::<Q>()
+ .ok_or(EnvironmentError::NoQueues)?;
+
+ // Memory pools
+ let tex_mempool = context.memory_pool()?.clone();
+ let staging_mempool = context.memory_pool()?.clone();
+
+ // Lock device
+ let device_lock = context.device().clone();
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,
- ),
- )
- };
+ // Physical properties
+ let device_props = context.physical_device_properties();
+ let optimal_buffer_copy_pitch_alignment =
+ device_props.limits.optimal_buffer_copy_pitch_alignment as u32;
// Pool
let mut pool = unsafe {
use hal::pool::CommandPoolCreateFlags;
-
device.create_command_pool(family, CommandPoolCreateFlags::RESET_INDIVIDUAL)
}
.context("Error creating command pool")?;
@@ -293,16 +261,13 @@ impl<R: TextureResolver> TextureLoader<R> {
data
};
- let optimal_buffer_copy_pitch_alignment =
- device_props.limits.optimal_buffer_copy_pitch_alignment;
-
+ // Blank image (for padding descriptors)
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,
+ (&staging_mempool, &tex_mempool),
optimal_buffer_copy_pitch_alignment,
&config,
)
@@ -319,11 +284,10 @@ impl<R: TextureResolver> TextureLoader<R> {
queue: queue_lock,
ds_layout,
- tex_allocator: ManuallyDrop::new(tex_allocator),
- staging_allocator: ManuallyDrop::new(staging_allocator),
+ tex_mempool,
+ staging_mempool,
descriptor_allocator: ManuallyDrop::new(DescriptorAllocator::new()),
- staging_memory_type,
optimal_buffer_copy_pitch_alignment,
request_channel,
@@ -333,7 +297,7 @@ impl<R: TextureResolver> TextureLoader<R> {
})
}
- unsafe fn attempt_queue_load(&mut self, block_ref: usize) -> Result<QueuedLoad<DynamicBlock>> {
+ unsafe fn attempt_queue_load(&mut self, block_ref: usize) -> Result<QueuedLoad<TP, SP>> {
let mut device = self
.device
.write()
@@ -403,7 +367,7 @@ impl<R: TextureResolver> TextureLoader<R> {
binding: 0,
array_offset: tex_idx % BLOCK_SIZE,
descriptors: once(Descriptor::Image(
- &*self.blank_image.img_view,
+ &*self.blank_image.img_view(),
Layout::ShaderReadOnlyOptimal,
)),
});
@@ -411,7 +375,7 @@ impl<R: TextureResolver> TextureLoader<R> {
set: descriptor_set.raw_mut(),
binding: 1,
array_offset: tex_idx % BLOCK_SIZE,
- descriptors: once(Descriptor::Sampler(&*self.blank_image.sampler)),
+ descriptors: once(Descriptor::Sampler(&*self.blank_image.sampler())),
});
continue;
@@ -423,9 +387,8 @@ impl<R: TextureResolver> TextureLoader<R> {
let (staging_buffer, img) = load_image(
&mut device,
- &mut self.staging_allocator,
- &mut self.tex_allocator,
- self.staging_memory_type,
+ &mut self.staging_mempool,
+ &mut self.tex_mempool,
self.optimal_buffer_copy_pitch_alignment,
img_data,
&self.config,
@@ -438,7 +401,7 @@ impl<R: TextureResolver> TextureLoader<R> {
binding: 0,
array_offset,
descriptors: once(Descriptor::Image(
- &*img.img_view,
+ img.img_view(),
Layout::ShaderReadOnlyOptimal,
)),
});
@@ -446,7 +409,7 @@ impl<R: TextureResolver> TextureLoader<R> {
set: descriptor_set.raw_mut(),
binding: 1,
array_offset,
- descriptors: once(Descriptor::Sampler(&*img.sampler)),
+ descriptors: once(Descriptor::Sampler(img.sampler())),
});
}
@@ -462,7 +425,7 @@ impl<R: TextureResolver> TextureLoader<R> {
imgs.iter().map(|li| Barrier::Image {
states: (Access::empty(), Layout::Undefined)
..(Access::TRANSFER_WRITE, Layout::TransferDstOptimal),
- target: &*li.img,
+ target: &*li.img(),
families: None,
range: SubresourceRange {
aspects: Aspects::COLOR,
@@ -477,13 +440,13 @@ impl<R: TextureResolver> TextureLoader<R> {
// Record copy commands
for (li, sb) in imgs.iter().zip(staging_bufs.iter()) {
buf.copy_buffer_to_image(
- &*sb.buf,
- &*li.img,
+ &*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,
+ buffer_width: (li.row_size() / get_pixel_size(FORMAT)) as u32,
+ buffer_height: li.height(),
image_layers: SubresourceLayers {
aspects: Aspects::COLOR,
level: 0,
@@ -491,8 +454,8 @@ impl<R: TextureResolver> TextureLoader<R> {
},
image_offset: Offset { x: 0, y: 0, z: 0 },
image_extent: gfx_hal::image::Extent {
- width: li.width,
- height: li.height,
+ width: li.unpadded_row_size(),
+ height: li.height(),
depth: 1,
},
}),
@@ -504,7 +467,7 @@ impl<R: TextureResolver> TextureLoader<R> {
imgs.iter().map(|li| Barrier::Image {
states: (Access::TRANSFER_WRITE, Layout::TransferDstOptimal)
..(Access::empty(), Layout::ShaderReadOnlyOptimal),
- target: &*li.img,
+ target: &*li.img(),
families: None,
range: RESOURCES,
}),
@@ -535,11 +498,10 @@ impl<R: TextureResolver> TextureLoader<R> {
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,
+ (staging_mempool, tex_mempool): (&Arc<RwLock<SP>>, &Arc<RwLock<TP>>),
+ obcpa: u32,
config: &TextureLoadConfig<R>,
- ) -> Result<LoadedImage<DynamicBlock>> {
+ ) -> Result<SampledImage<TP>> {
let img_data = RgbaImage::from_pixel(1, 1, Rgba([255, 0, 255, 255]));
let height = img_data.height();
@@ -551,9 +513,8 @@ impl<R: TextureResolver> TextureLoader<R> {
let (staging_buffer, img) = load_image(
device,
- staging_allocator,
- tex_allocator,
- staging_memory_type,
+ staging_mempool,
+ tex_mempool,
obcpa,
img_data,
config,
@@ -567,7 +528,7 @@ impl<R: TextureResolver> TextureLoader<R> {
once(Barrier::Image {
states: (Access::empty(), Layout::Undefined)
..(Access::TRANSFER_WRITE, Layout::TransferDstOptimal),
- target: &*img.img,
+ target: &*img.img(),
families: None,
range: SubresourceRange {
aspects: Aspects::COLOR,
@@ -579,8 +540,8 @@ impl<R: TextureResolver> TextureLoader<R> {
}),
);
buf.copy_buffer_to_image(
- &*staging_buffer.buf,
- &*img.img,
+ &*staging_buffer.buf(),
+ &*img.img(),
Layout::TransferDstOptimal,
once(BufferImageCopy {
buffer_offset: 0,
@@ -602,7 +563,7 @@ impl<R: TextureResolver> TextureLoader<R> {
once(Barrier::Image {
states: (Access::TRANSFER_WRITE, Layout::TransferDstOptimal)
..(Access::empty(), Layout::ShaderReadOnlyOptimal),
- target: &*img.img,
+ target: &*img.img(),
families: None,
range: RESOURCES,
}),
@@ -628,7 +589,10 @@ impl<R: TextureResolver> TextureLoader<R> {
device.destroy_fence(fence);
- staging_buffer.deactivate(device, staging_allocator);
+ {
+ let mut staging_mempool = staging_mempool.write().unwrap();
+ staging_buffer.deactivate_device_pool(device, &mut *staging_mempool);
+ }
Ok(img)
}
@@ -658,8 +622,9 @@ impl<R: TextureResolver> TextureLoader<R> {
device.destroy_fence(assets.0);
// Command buffer will be freed when we reset the command pool
// Destroy staging buffers
+ let mut staging_mempool = self.staging_mempool.write().unwrap();
for buf in staging_bufs.drain(..) {
- buf.deactivate(&mut device, &mut self.staging_allocator);
+ buf.deactivate_device_pool(&mut device, &mut staging_mempool);
}
self.return_channel
@@ -674,7 +639,11 @@ impl<R: TextureResolver> TextureLoader<R> {
}
// Destroy blank image
- read(&*self.blank_image).deactivate(&mut device, &mut *self.tex_allocator);
+ {
+ let mut tex_mempool = self.tex_mempool.write().unwrap();
+ read(&*self.blank_image)
+ .deactivate_with_device_pool(&mut device, &mut *tex_mempool);
+ }
// Destroy fences
@@ -690,7 +659,6 @@ impl<R: TextureResolver> TextureLoader<R> {
debug!("Done deactivating TextureLoader");
TextureLoaderRemains {
- tex_allocator: ManuallyDrop::new(read(&*self.tex_allocator)),
descriptor_allocator: ManuallyDrop::new(read(&*self.descriptor_allocator)),
}
}
@@ -698,7 +666,6 @@ impl<R: TextureResolver> TextureLoader<R> {
}
pub struct TextureLoaderRemains {
- pub tex_allocator: ManuallyDrop<DynamicAllocator>,
pub descriptor_allocator: ManuallyDrop<DescriptorAllocator>,
}
diff --git a/stockton-skeleton/src/texture/mod.rs b/stockton-skeleton/src/texture/mod.rs
index aef1b03..10fbbad 100644
--- a/stockton-skeleton/src/texture/mod.rs
+++ b/stockton-skeleton/src/texture/mod.rs
@@ -6,7 +6,6 @@ mod load;
mod loader;
mod repo;
pub mod resolver;
-mod staging_buffer;
pub use self::block::TexturesBlock;
pub use self::image::LoadableImage;
diff --git a/stockton-skeleton/src/texture/repo.rs b/stockton-skeleton/src/texture/repo.rs
index 341d355..635eebb 100644
--- a/stockton-skeleton/src/texture/repo.rs
+++ b/stockton-skeleton/src/texture/repo.rs
@@ -4,14 +4,15 @@ use super::{
loader::{BlockRef, LoaderRequest, TextureLoader, TextureLoaderRemains, NUM_SIMULTANEOUS_CMDS},
resolver::TextureResolver,
};
-use crate::error::LockPoisoned;
-use crate::queue_negotiator::QueueFamilySelector;
use crate::types::*;
+use crate::{context::RenderingContext, error::LockPoisoned, mem::MappableBlock};
+use crate::{mem::MemoryPool, queue_negotiator::QueueFamilySelector};
use std::{
array::IntoIter,
collections::HashMap,
iter::empty,
+ marker::PhantomData,
mem::ManuallyDrop,
sync::{
mpsc::{channel, Receiver, Sender},
@@ -21,10 +22,7 @@ use std::{
};
use anyhow::{Context, Result};
-use hal::{
- pso::{DescriptorSetLayoutBinding, DescriptorType, ImageDescriptorType, ShaderStageFlags},
- queue::family::QueueFamilyId,
-};
+use hal::pso::{DescriptorSetLayoutBinding, DescriptorType, ImageDescriptorType, ShaderStageFlags};
use log::debug;
/// The number of textures in one 'block'
@@ -32,26 +30,46 @@ use log::debug;
/// Whenever a texture is needed, the whole block its in is loaded.
pub const BLOCK_SIZE: usize = 8;
-pub struct TextureRepo {
+/// An easy way to load [`super::LoadableImage`]s into GPU memory using another thread.
+/// This assumes each texture has a numeric id, and will group them into blocks of `[BLOCK_SIZE]`,
+/// yielding descriptor sets with that many samplers and images.
+/// You only need to supply a [`super::resolver::TextureResolver`] and create one from the main thread.
+/// Then, use [`get_ds_layout`] in your graphics pipeline.
+/// Make sure to call [`process_responses`] every frame.
+/// Then, whenever you draw, use [`attempt_get_descriptor_set`] to see if that texture has finished loading,
+/// or `queue_load` to start loading it ASAP.
+
+pub struct TextureRepo<TP, SP>
+where
+ TP: MemoryPool,
+ SP: MemoryPool,
+ SP::Block: MappableBlock,
+{
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>>>,
+ resp_recv: Receiver<TexturesBlock<TP>>,
+ blocks: HashMap<BlockRef, Option<TexturesBlock<TP>>>,
+ _d: PhantomData<(TP, SP)>,
}
-impl TextureRepo {
- pub fn new<R: 'static + TextureResolver + Send + Sync>(
- device_lock: Arc<RwLock<DeviceT>>,
- family: QueueFamilyId,
- queue: Arc<RwLock<QueueT>>,
- adapter: &Adapter,
+impl<TP, SP> TextureRepo<TP, SP>
+where
+ TP: MemoryPool,
+ SP: MemoryPool,
+ SP::Block: MappableBlock,
+{
+ /// Create a new TextureRepo from the given context.
+ /// Q should most likely be [`TexLoadQueue`]
+ pub fn new<R: 'static + TextureResolver + Send + Sync, Q: QueueFamilySelector>(
+ context: &mut RenderingContext,
config: TextureLoadConfig<R>,
) -> Result<Self> {
// Create Channels
let (req_send, req_recv) = channel();
let (resp_send, resp_recv) = channel();
- let device = device_lock
+ let device = context
+ .device()
.write()
.map_err(|_| LockPoisoned::Device)
.context("Error getting device lock")?;
@@ -91,10 +109,8 @@ impl TextureRepo {
drop(device);
let joiner = {
- let loader = TextureLoader::new(
- adapter,
- device_lock.clone(),
- (family, queue),
+ let loader = <TextureLoader<_, TP, SP>>::new::<Q>(
+ context,
ds_lock.clone(),
(req_recv, resp_send),
config,
@@ -109,9 +125,12 @@ impl TextureRepo {
blocks: HashMap::new(),
req_send,
resp_recv,
+ _d: PhantomData,
})
}
+ /// Get the descriptor layout used for each texture descriptor
+ /// This can be used when creating graphics pipelines.
pub fn get_ds_layout(&self) -> Result<RwLockReadGuard<DescriptorSetLayoutT>> {
self.ds_layout
.read()
@@ -119,6 +138,7 @@ impl TextureRepo {
.context("Error locking descriptor set layout")
}
+ /// Ask for the given block to be loaded, if it's not already.
pub fn queue_load(&mut self, block_id: BlockRef) -> Result<()> {
if self.blocks.contains_key(&block_id) {
return Ok(());
@@ -127,6 +147,7 @@ impl TextureRepo {
self.force_queue_load(block_id)
}
+ /// Ask for the given block to be loaded, even if it already has been.
pub fn force_queue_load(&mut self, block_id: BlockRef) -> Result<()> {
self.req_send
.send(LoaderRequest::Load(block_id))
@@ -137,12 +158,14 @@ impl TextureRepo {
Ok(())
}
+ /// Get the descriptor set for the given block, if it's loaded.
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()))
}
+ /// Process any textures that just finished loading. This should be called every frame.
pub fn process_responses(&mut self) {
let resp_iter: Vec<_> = self.resp_recv.try_iter().collect();
for resp in resp_iter {
@@ -151,7 +174,8 @@ impl TextureRepo {
}
}
- pub fn deactivate(mut self, device_lock: &Arc<RwLock<DeviceT>>) {
+ /// Destroy all vulkan objects. Should be called before dropping.
+ pub fn deactivate(mut self, context: &mut RenderingContext) {
unsafe {
use std::ptr::read;
@@ -162,22 +186,27 @@ impl TextureRepo {
// Process any ones that just got done loading
self.process_responses();
+ let mut tex_allocator = context
+ .existing_memory_pool::<TP>()
+ .unwrap()
+ .write()
+ .unwrap();
+
// Only now can we lock device without deadlocking
- let mut device = device_lock.write().unwrap();
+ let mut device = context.device().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 *tex_allocator,
&mut remains.descriptor_allocator,
);
}
}
- // Dispose of both allocators
- read(&*remains.tex_allocator).dispose();
+ // Dispose of the descriptor allocator
read(&*remains.descriptor_allocator).dispose(&device);
// Deactivate DS Layout
@@ -190,6 +219,7 @@ impl TextureRepo {
}
}
+/// The queue to use when loading textures
pub struct TexLoadQueue;
impl QueueFamilySelector for TexLoadQueue {
diff --git a/stockton-skeleton/src/texture/staging_buffer.rs b/stockton-skeleton/src/texture/staging_buffer.rs
deleted file mode 100644
index 8d2ae17..0000000
--- a/stockton-skeleton/src/texture/staging_buffer.rs
+++ /dev/null
@@ -1,59 +0,0 @@
-#![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));
- }
- }
-}