diff options
author | tcmal <me@aria.rip> | 2024-08-25 17:44:24 +0100 |
---|---|---|
committer | tcmal <me@aria.rip> | 2024-08-25 17:44:24 +0100 |
commit | b86e97f67a07368877bd18501aebcbe80cf93118 (patch) | |
tree | 97b72b4339da6400b481170eab11fd89ab5dfa80 /stockton-skeleton/src/buffers/image.rs | |
parent | e1cc0e9a9d191bcd3a634be46fd3555d430b07a8 (diff) |
feat(skeleton): add memory pools
added stock memory pools behind a feature gate
refactored buffers to use them and to have better APIs.
moved some util code out of builders::pipeline
updated stockton-render for the changes
deactivation is a WIP
breaks UI drawing, fix WIP
Diffstat (limited to 'stockton-skeleton/src/buffers/image.rs')
-rw-r--r-- | stockton-skeleton/src/buffers/image.rs | 342 |
1 files changed, 342 insertions, 0 deletions
diff --git a/stockton-skeleton/src/buffers/image.rs b/stockton-skeleton/src/buffers/image.rs new file mode 100644 index 0000000..34a0a37 --- /dev/null +++ b/stockton-skeleton/src/buffers/image.rs @@ -0,0 +1,342 @@ +//! An image with memory bound to it and an image view into its entirety. +//! This is useful for most types of images. +//! ```rust +//! # use anyhow::Result; +//! # use crate::{mem::DrawAttachments, context::RenderingContext}; +//! fn create_depth_buffer( +//! context: &mut RenderingContext, +//! ) -> Result<BoundImageView<DrawAttachments>> { +//! BoundImageView::from_context( +//! context, +//! &ImageSpec { +//! width: 10, +//! height: 10, +//! format: Format::D32Sfloat, +//! usage: Usage::DEPTH_STENCIL_ATTACHMENT, +//! }, +//! ) +//! } +/// ``` +use std::mem::ManuallyDrop; + +use crate::{ + context::RenderingContext, + error::LockPoisoned, + mem::{Block, MemoryPool}, + types::*, + utils::get_pixel_size, +}; +use anyhow::{Context, Result}; +use hal::{ + format::{Aspects, Format, Swizzle}, + image::{SamplerDesc, SubresourceRange, Usage, ViewKind}, + memory::SparseFlags, +}; + +pub const COLOR_RESOURCES: SubresourceRange = SubresourceRange { + aspects: Aspects::COLOR, + level_start: 0, + level_count: Some(1), + layer_start: 0, + layer_count: Some(1), +}; + +pub const DEPTH_RESOURCES: SubresourceRange = SubresourceRange { + aspects: Aspects::DEPTH, + level_start: 0, + level_count: Some(1), + layer_start: 0, + layer_count: Some(1), +}; + +/// An image with memory bound to it and an image view into its entirety +/// Memory is allocated from the memory pool P, see [`crate::mem`] +pub struct BoundImageView<P: MemoryPool> { + mem: ManuallyDrop<P::Block>, + img: ManuallyDrop<ImageT>, + img_view: ManuallyDrop<ImageViewT>, + unpadded_row_size: u32, + row_size: u32, + height: u32, +} + +impl<P: MemoryPool> BoundImageView<P> { + /// Create an uninitialised image using memory from the specified pool + pub fn from_context(context: &mut RenderingContext, spec: &ImageSpec) -> Result<Self> { + // Ensure the memory pool exists before we get a reference to it + context + .ensure_memory_pool::<P>() + .context("Error creating memory pool requested for BoundImageView")?; + let mut allocator = context + .existing_memory_pool::<P>() + .unwrap() + .write() + .map_err(|_| LockPoisoned::MemoryPool)?; + + let mut device = context.device().write().map_err(|_| LockPoisoned::Device)?; + let row_alignment_mask = context + .physical_device_properties() + .limits + .optimal_buffer_copy_pitch_alignment as u32 + - 1; + Self::from_device_allocator(&mut device, &mut allocator, row_alignment_mask, spec) + } + + /// Create an uninitialised image using memory from the specified pool, but using a much less convenient signature. + /// Use this when you don't have access to the full context. + pub fn from_device_allocator( + device: &mut DeviceT, + pool: &mut P, + row_alignment_mask: u32, + spec: &ImageSpec, + ) -> Result<Self> { + // Calculate buffer size & alignment + let initial_row_size = get_pixel_size(spec.format) * spec.width; + let row_size = (initial_row_size + row_alignment_mask) & !row_alignment_mask; + debug_assert!(row_size >= initial_row_size); + + unsafe { + use hal::image::{Kind, Tiling, ViewCapabilities}; + + // Create the image + let mut img = device + .create_image( + Kind::D2(spec.width, spec.height, 1, 1), + 1, + spec.format, + Tiling::Optimal, + spec.usage, + SparseFlags::empty(), + ViewCapabilities::empty(), + ) + .context("Error creating image")?; + + // Get memory requirements + let requirements = device.get_image_requirements(&img); + + // Allocate memory + let (mem, _) = pool + .alloc(&device, requirements.size, requirements.alignment) + .context("Error allocating memory")?; + + // Bind memory + device + .bind_image_memory(mem.memory(), mem.range().start, &mut img) + .context("Error binding memory to image")?; + + // Create image view + let img_view = device + .create_image_view( + &img, + ViewKind::D2, + spec.format, + Swizzle::NO, + spec.usage, + spec.resources.clone(), + ) + .context("Error creating image view")?; + + Ok(Self { + mem: ManuallyDrop::new(mem), + img: ManuallyDrop::new(img), + img_view: ManuallyDrop::new(img_view), + row_size, + height: spec.height, + unpadded_row_size: spec.width, + }) + } + } + + /// Destroy all vulkan objects. Must be called before dropping. + pub fn deactivate_with_context(self, context: &mut RenderingContext) { + let mut device = context + .device() + .write() + .map_err(|_| LockPoisoned::Device) + .unwrap(); + let mut pool = context + .existing_memory_pool::<P>() + .unwrap() + .write() + .unwrap(); + + self.deactivate_with_device_pool(&mut device, &mut pool); + } + + /// Destroy all vulkan objects. Must be called before dropping. + pub fn deactivate_with_device_pool(self, device: &mut DeviceT, pool: &mut P) { + use std::ptr::read; + unsafe { + device.destroy_image_view(read(&*self.img_view)); + device.destroy_image(read(&*self.img)); + pool.free(&device, read(&*self.mem)); + } + } + + /// Get a reference to the bound image. + pub fn img(&self) -> &ImageT { + &*self.img + } + + /// Get a reference to the view of the bound image. + pub fn img_view(&self) -> &ImageViewT { + &*self.img_view + } + + /// Get a reference to the memory used by the bound image. + pub fn mem(&self) -> &<P as MemoryPool>::Block { + &*self.mem + } + + /// Get the bound image view's row size. + pub fn row_size(&self) -> u32 { + self.row_size + } + + /// Get the bound image view's height. + pub fn height(&self) -> u32 { + self.height + } + + /// Get the bound image view's unpadded row size. + pub fn unpadded_row_size(&self) -> u32 { + self.unpadded_row_size + } +} + +/// A [`self::BoundImageView`] and accompanying sampler. +pub struct SampledImage<P: MemoryPool> { + bound_image: ManuallyDrop<BoundImageView<P>>, + sampler: ManuallyDrop<SamplerT>, +} + +impl<P: MemoryPool> SampledImage<P> { + /// Create an uninitialised image using memory from the specified pool + pub fn from_context( + context: &mut RenderingContext, + spec: &ImageSpec, + sampler_desc: &SamplerDesc, + ) -> Result<Self> { + // Ensure the memory pool exists before we get a reference to it + context + .ensure_memory_pool::<P>() + .context("Error creating memory pool requested for BoundImageView")?; + let mut allocator = context + .existing_memory_pool::<P>() + .unwrap() + .write() + .map_err(|_| LockPoisoned::MemoryPool)?; + + let mut device = context.device().write().map_err(|_| LockPoisoned::Device)?; + let row_alignment_mask = context + .physical_device_properties() + .limits + .optimal_buffer_copy_pitch_alignment as u32 + - 1; + + Self::from_device_allocator( + &mut device, + &mut allocator, + row_alignment_mask, + spec, + sampler_desc, + ) + } + + /// Create an uninitialised image and sampler using memory from the specified pool, but using a much less convenient signature. + /// Use this when you don't have access to the full context. + pub fn from_device_allocator( + device: &mut DeviceT, + pool: &mut P, + row_alignment_mask: u32, + spec: &ImageSpec, + sampler_desc: &SamplerDesc, + ) -> Result<Self> { + let sampler = unsafe { device.create_sampler(sampler_desc) }?; + + Ok(SampledImage { + bound_image: ManuallyDrop::new(BoundImageView::from_device_allocator( + device, + pool, + row_alignment_mask, + spec, + )?), + sampler: ManuallyDrop::new(sampler), + }) + } + + /// Destroy all vulkan objects. Must be called before dropping. + pub fn deactivate_with_context(self, context: &mut RenderingContext) { + let mut device = context + .device() + .write() + .map_err(|_| LockPoisoned::Device) + .unwrap(); + let mut pool = context + .existing_memory_pool::<P>() + .unwrap() + .write() + .unwrap(); + + self.deactivate_with_device_pool(&mut device, &mut pool); + } + + /// Destroy all vulkan objects. Must be called before dropping. + pub fn deactivate_with_device_pool(self, device: &mut DeviceT, pool: &mut P) { + unsafe { + use std::ptr::read; + read(&*self.bound_image).deactivate_with_device_pool(device, pool); + device.destroy_sampler(read(&*self.sampler)); + } + } + + /// Get a reference to the bound image object. + pub fn bound_image(&self) -> &BoundImageView<P> { + &self.bound_image + } + + /// Get a reference to the bound image. + pub fn img(&self) -> &ImageT { + self.bound_image.img() + } + + /// Get a reference to the view of the bound image. + pub fn img_view(&self) -> &ImageViewT { + self.bound_image.img_view() + } + + /// Get the bound image view's row size. + pub fn row_size(&self) -> u32 { + self.bound_image.row_size() + } + + /// Get the bound image view's unpadded row size. + pub fn unpadded_row_size(&self) -> u32 { + self.bound_image.unpadded_row_size() + } + + /// Get the bound image view's height. + pub fn height(&self) -> u32 { + self.bound_image.height() + } + + /// Get a reference to the memory used by the bound image. + pub fn mem(&self) -> &<P as MemoryPool>::Block { + self.bound_image.mem() + } + + /// Get a reference to the sampler. + pub fn sampler(&self) -> &SamplerT { + &self.sampler + } +} + +/// Information needed to create an image. +#[derive(Debug, Clone)] +pub struct ImageSpec { + pub width: u32, + pub height: u32, + pub format: Format, + pub usage: Usage, + pub resources: SubresourceRange, +} |