From 6ab13f2d0cb345795f761181a06777ade61ff09c Mon Sep 17 00:00:00 2001 From: tcmal Date: Sun, 25 Aug 2024 17:44:23 +0100 Subject: refactor(all): separate rendering from framework stockton-passes is mostly just a stand-in until this is properly separated --- stockton-render/Cargo.toml | 2 - stockton-render/src/buffers/dedicated_image.rs | 134 ++++ stockton-render/src/buffers/draw_buffers.rs | 43 ++ stockton-render/src/buffers/mod.rs | 63 ++ stockton-render/src/buffers/staged.rs | 142 ++++ stockton-render/src/builders/mod.rs | 7 + stockton-render/src/builders/pipeline.rs | 288 +++++++++ stockton-render/src/builders/renderpass.rs | 75 +++ stockton-render/src/builders/shader.rs | 35 + stockton-render/src/context.rs | 275 ++++++++ stockton-render/src/culling.rs | 70 -- .../src/draw/buffers/dedicated_image.rs | 134 ---- stockton-render/src/draw/buffers/draw_buffers.rs | 43 -- stockton-render/src/draw/buffers/mod.rs | 63 -- stockton-render/src/draw/buffers/staged.rs | 142 ---- stockton-render/src/draw/builders/mod.rs | 3 - stockton-render/src/draw/builders/pipeline.rs | 288 --------- stockton-render/src/draw/builders/renderpass.rs | 75 --- stockton-render/src/draw/builders/shader.rs | 35 - stockton-render/src/draw/camera.rs | 61 -- stockton-render/src/draw/context.rs | 266 -------- stockton-render/src/draw/data/stockton.frag | 15 - stockton-render/src/draw/data/stockton.vert | 23 - stockton-render/src/draw/draw_passes/cons.rs | 74 --- stockton-render/src/draw/draw_passes/level.rs | 478 -------------- stockton-render/src/draw/draw_passes/mod.rs | 52 -- stockton-render/src/draw/draw_passes/ui.rs | 348 ---------- stockton-render/src/draw/draw_passes/util.rs | 41 -- stockton-render/src/draw/mod.rs | 17 - stockton-render/src/draw/queue_negotiator.rs | 135 ---- stockton-render/src/draw/target.rs | 392 ------------ stockton-render/src/draw/texture/block.rs | 62 -- stockton-render/src/draw/texture/image.rs | 41 -- stockton-render/src/draw/texture/load.rs | 191 ------ stockton-render/src/draw/texture/loader.rs | 712 --------------------- stockton-render/src/draw/texture/mod.rs | 18 - stockton-render/src/draw/texture/repo.rs | 201 ------ stockton-render/src/draw/texture/resolver.rs | 55 -- stockton-render/src/draw/texture/staging_buffer.rs | 59 -- stockton-render/src/draw/ui/data/stockton.frag | 15 - stockton-render/src/draw/ui/data/stockton.vert | 37 -- stockton-render/src/draw/ui/mod.rs | 52 -- stockton-render/src/draw/utils.rs | 19 - stockton-render/src/draw_passes/cons.rs | 66 ++ stockton-render/src/draw_passes/mod.rs | 47 ++ stockton-render/src/draw_passes/util.rs | 41 ++ stockton-render/src/lib.rs | 91 +-- stockton-render/src/queue_negotiator.rs | 135 ++++ stockton-render/src/systems.rs | 2 - stockton-render/src/target.rs | 397 ++++++++++++ stockton-render/src/texture/block.rs | 62 ++ stockton-render/src/texture/image.rs | 41 ++ stockton-render/src/texture/load.rs | 191 ++++++ stockton-render/src/texture/loader.rs | 712 +++++++++++++++++++++ stockton-render/src/texture/mod.rs | 18 + stockton-render/src/texture/repo.rs | 201 ++++++ stockton-render/src/texture/resolver.rs | 55 ++ stockton-render/src/texture/staging_buffer.rs | 59 ++ stockton-render/src/utils.rs | 19 + stockton-render/src/window.rs | 239 ------- 60 files changed, 3152 insertions(+), 4505 deletions(-) create mode 100644 stockton-render/src/buffers/dedicated_image.rs create mode 100644 stockton-render/src/buffers/draw_buffers.rs create mode 100644 stockton-render/src/buffers/mod.rs create mode 100644 stockton-render/src/buffers/staged.rs create mode 100644 stockton-render/src/builders/mod.rs create mode 100644 stockton-render/src/builders/pipeline.rs create mode 100644 stockton-render/src/builders/renderpass.rs create mode 100644 stockton-render/src/builders/shader.rs create mode 100644 stockton-render/src/context.rs delete mode 100644 stockton-render/src/culling.rs delete mode 100644 stockton-render/src/draw/buffers/dedicated_image.rs delete mode 100644 stockton-render/src/draw/buffers/draw_buffers.rs delete mode 100644 stockton-render/src/draw/buffers/mod.rs delete mode 100644 stockton-render/src/draw/buffers/staged.rs delete mode 100644 stockton-render/src/draw/builders/mod.rs delete mode 100644 stockton-render/src/draw/builders/pipeline.rs delete mode 100644 stockton-render/src/draw/builders/renderpass.rs delete mode 100644 stockton-render/src/draw/builders/shader.rs delete mode 100644 stockton-render/src/draw/camera.rs delete mode 100644 stockton-render/src/draw/context.rs delete mode 100644 stockton-render/src/draw/data/stockton.frag delete mode 100644 stockton-render/src/draw/data/stockton.vert delete mode 100644 stockton-render/src/draw/draw_passes/cons.rs delete mode 100644 stockton-render/src/draw/draw_passes/level.rs delete mode 100644 stockton-render/src/draw/draw_passes/mod.rs delete mode 100644 stockton-render/src/draw/draw_passes/ui.rs delete mode 100644 stockton-render/src/draw/draw_passes/util.rs delete mode 100644 stockton-render/src/draw/mod.rs delete mode 100644 stockton-render/src/draw/queue_negotiator.rs delete mode 100644 stockton-render/src/draw/target.rs delete mode 100644 stockton-render/src/draw/texture/block.rs delete mode 100644 stockton-render/src/draw/texture/image.rs delete mode 100644 stockton-render/src/draw/texture/load.rs delete mode 100644 stockton-render/src/draw/texture/loader.rs delete mode 100644 stockton-render/src/draw/texture/mod.rs delete mode 100644 stockton-render/src/draw/texture/repo.rs delete mode 100644 stockton-render/src/draw/texture/resolver.rs delete mode 100644 stockton-render/src/draw/texture/staging_buffer.rs delete mode 100644 stockton-render/src/draw/ui/data/stockton.frag delete mode 100644 stockton-render/src/draw/ui/data/stockton.vert delete mode 100644 stockton-render/src/draw/ui/mod.rs delete mode 100644 stockton-render/src/draw/utils.rs create mode 100644 stockton-render/src/draw_passes/cons.rs create mode 100644 stockton-render/src/draw_passes/mod.rs create mode 100644 stockton-render/src/draw_passes/util.rs create mode 100644 stockton-render/src/queue_negotiator.rs delete mode 100644 stockton-render/src/systems.rs create mode 100644 stockton-render/src/target.rs create mode 100644 stockton-render/src/texture/block.rs create mode 100644 stockton-render/src/texture/image.rs create mode 100644 stockton-render/src/texture/load.rs create mode 100644 stockton-render/src/texture/loader.rs create mode 100644 stockton-render/src/texture/mod.rs create mode 100644 stockton-render/src/texture/repo.rs create mode 100644 stockton-render/src/texture/resolver.rs create mode 100644 stockton-render/src/texture/staging_buffer.rs create mode 100644 stockton-render/src/utils.rs delete mode 100644 stockton-render/src/window.rs (limited to 'stockton-render') diff --git a/stockton-render/Cargo.toml b/stockton-render/Cargo.toml index 7f93b3b..cb056b4 100644 --- a/stockton-render/Cargo.toml +++ b/stockton-render/Cargo.toml @@ -16,8 +16,6 @@ shaderc = "^0.7" log = "0.4.0" image = "0.23.11" legion = { version = "^0.3" } -egui = "^0.12" -epaint = "^0.12" rendy-memory = { path = "../rendy-memory" } rendy-descriptor = { path = "../rendy-descriptor" } anyhow = "1.0.40" diff --git a/stockton-render/src/buffers/dedicated_image.rs b/stockton-render/src/buffers/dedicated_image.rs new file mode 100644 index 0000000..bf49a38 --- /dev/null +++ b/stockton-render/src/buffers/dedicated_image.rs @@ -0,0 +1,134 @@ +//! A dedicated image. Used for depth buffers. + +use crate::texture::PIXEL_SIZE; +use crate::types::*; + +use std::mem::ManuallyDrop; + +use anyhow::{Context, Result}; +use hal::{ + format::{Format, Swizzle}, + image::{SubresourceRange, Usage, Usage as ImgUsage, ViewKind}, + memory, + memory::Properties, + MemoryTypeId, +}; +use thiserror::Error; + +/// 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, + + /// The full view of the image + pub image_view: ManuallyDrop, + + /// The memory backing the image + memory: ManuallyDrop, +} + +#[derive(Debug, Error)] +pub enum ImageLoadError { + #[error("No suitable memory type for image memory")] + NoMemoryTypes, +} + +impl DedicatedLoadedImage { + pub fn new( + device: &mut DeviceT, + adapter: &Adapter, + format: Format, + usage: Usage, + resources: SubresourceRange, + width: usize, + height: usize, + ) -> Result { + let (memory, image_ref) = { + // Round up the size to align properly + let initial_row_size = PIXEL_SIZE * width; + let limits = adapter.physical_device.properties().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, + memory::SparseFlags::empty(), + ViewCapabilities::empty(), + ) + } + .context("Error creating image")?; + + // 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(Properties::DEVICE_LOCAL) + }) + .map(|(id, _)| MemoryTypeId(id)) + .ok_or(ImageLoadError::NoMemoryTypes)?; + + let memory = device + .allocate_memory(memory_type_id, requirements.size) + .context("Error allocating memory for image")?; + + device + .bind_image_memory(&memory, 0, &mut image_ref) + .context("Error binding memory to image")?; + + memory + }; + + (memory, image_ref) + }; + + // Create ImageView and sampler + let image_view = unsafe { + device.create_image_view( + &image_ref, + ViewKind::D2, + format, + Swizzle::NO, + ImgUsage::DEPTH_STENCIL_ATTACHMENT, + resources, + ) + } + .context("Error creating image view")?; + + Ok(DedicatedLoadedImage { + image: ManuallyDrop::new(image_ref), + image_view: ManuallyDrop::new(image_view), + memory: ManuallyDrop::new(memory), + }) + } + + /// Properly frees/destroys all the objects in this struct + /// Dropping without doing this is a bad idea + pub fn deactivate(self, device: &mut DeviceT) { + 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))); + } + } +} diff --git a/stockton-render/src/buffers/draw_buffers.rs b/stockton-render/src/buffers/draw_buffers.rs new file mode 100644 index 0000000..5baec92 --- /dev/null +++ b/stockton-render/src/buffers/draw_buffers.rs @@ -0,0 +1,43 @@ +//! A vertex and index buffer set for drawing + +use super::StagedBuffer; +use crate::types::*; + +use anyhow::{Context, Result}; +use hal::buffer::Usage; +use std::mem::ManuallyDrop; + +/// Initial size of vertex buffer. TODO: Way of overriding this +pub const INITIAL_VERT_SIZE: u64 = 3 * 3000; + +/// Initial size of index buffer. TODO: Way of overriding this +pub const INITIAL_INDEX_SIZE: u64 = 3000; + +/// The buffers used for drawing, ie index and vertex buffer +pub struct DrawBuffers<'a, T: Sized> { + pub vertex_buffer: ManuallyDrop>, + pub index_buffer: ManuallyDrop>, +} + +impl<'a, T> DrawBuffers<'a, T> { + pub fn new(device: &mut DeviceT, adapter: &Adapter) -> Result> { + let vert = StagedBuffer::new(device, adapter, Usage::VERTEX, INITIAL_VERT_SIZE) + .context("Error creating vertex buffer")?; + let index = StagedBuffer::new(device, adapter, Usage::INDEX, INITIAL_INDEX_SIZE) + .context("Error creating index buffer")?; + + Ok(DrawBuffers { + vertex_buffer: ManuallyDrop::new(vert), + index_buffer: ManuallyDrop::new(index), + }) + } + + pub fn deactivate(self, device: &mut DeviceT) { + unsafe { + use core::ptr::read; + + ManuallyDrop::into_inner(read(&self.vertex_buffer)).deactivate(device); + ManuallyDrop::into_inner(read(&self.index_buffer)).deactivate(device); + } + } +} diff --git a/stockton-render/src/buffers/mod.rs b/stockton-render/src/buffers/mod.rs new file mode 100644 index 0000000..82ca62a --- /dev/null +++ b/stockton-render/src/buffers/mod.rs @@ -0,0 +1,63 @@ +//! All sorts of buffers + +use std::ops::IndexMut; + +use crate::{error::EnvironmentError, types::*}; + +use anyhow::{Context, Result}; +use hal::{ + buffer::Usage, + memory::{Properties, SparseFlags}, + MemoryTypeId, +}; + +mod dedicated_image; +mod draw_buffers; +mod staged; + +pub use dedicated_image::*; +pub use draw_buffers::*; +pub use staged::*; + +/// Create a buffer of the given specifications, allocating more device memory. +// TODO: Use a different memory allocator? +pub(crate) fn create_buffer( + device: &mut DeviceT, + adapter: &Adapter, + usage: Usage, + properties: Properties, + size: u64, +) -> Result<(BufferT, MemoryT)> { + let mut buffer = unsafe { device.create_buffer(size, usage, SparseFlags::empty()) } + .context("Error creating buffer")?; + + let requirements = unsafe { device.get_buffer_requirements(&buffer) }; + 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(properties) + }) + .map(|(id, _)| MemoryTypeId(id)) + .ok_or(EnvironmentError::NoMemoryTypes)?; + + let memory = unsafe { device.allocate_memory(memory_type_id, requirements.size) } + .context("Error allocating memory")?; + + unsafe { device.bind_buffer_memory(&memory, 0, &mut buffer) } + .context("Error binding memory to buffer")?; + + Ok((buffer, memory)) +} + +/// A buffer that can be modified by the CPU +pub trait ModifiableBuffer: IndexMut { + /// Get a handle to the underlying GPU buffer + fn get_buffer(&mut self) -> &BufferT; + + /// Record the command(s) required to commit changes to this buffer to the given command buffer. + fn record_commit_cmds<'a>(&'a mut self, cmd_buffer: &mut CommandBufferT) -> Result<()>; +} diff --git a/stockton-render/src/buffers/staged.rs b/stockton-render/src/buffers/staged.rs new file mode 100644 index 0000000..71b5204 --- /dev/null +++ b/stockton-render/src/buffers/staged.rs @@ -0,0 +1,142 @@ +//! A buffer that can be written to by the CPU using staging memory + +use super::{create_buffer, ModifiableBuffer}; +use crate::types::*; + +use core::mem::{size_of, ManuallyDrop}; +use std::{ + convert::TryInto, + ops::{Index, IndexMut}, +}; + +use anyhow::{Context, Result}; +use hal::{ + buffer::Usage, + command::BufferCopy, + memory::{Properties, Segment}, +}; + +/// A GPU buffer that is written to using a staging buffer +pub struct StagedBuffer<'a, T: Sized> { + /// CPU-visible buffer + staged_buffer: ManuallyDrop, + + /// CPU-visible memory + staged_memory: ManuallyDrop, + + /// GPU Buffer + buffer: ManuallyDrop, + + /// GPU Memory + memory: ManuallyDrop, + + /// Where staged buffer is mapped in CPU memory + staged_mapped_memory: &'a mut [T], + + /// The highest index in the buffer that's been written to. + pub highest_used: usize, +} + +impl<'a, T: Sized> StagedBuffer<'a, T> { + /// size is the size in T + pub fn new(device: &mut DeviceT, adapter: &Adapter, usage: Usage, size: u64) -> Result { + // Convert size to bytes + let size_bytes = size * size_of::() as u64; + + // Get CPU-visible buffer + let (staged_buffer, mut staged_memory) = create_buffer( + device, + adapter, + Usage::TRANSFER_SRC, + Properties::CPU_VISIBLE, + size_bytes, + ) + .context("Error creating staging buffer")?; + + // Get GPU Buffer + let (buffer, memory) = create_buffer( + device, + adapter, + Usage::TRANSFER_DST | usage, + Properties::DEVICE_LOCAL | Properties::COHERENT, + size_bytes, + ) + .context("Error creating GPU buffer")?; + + // Map it somewhere and get a slice to that memory + let staged_mapped_memory = unsafe { + let ptr = device + .map_memory( + &mut staged_memory, + Segment { + offset: 0, + size: Some(size_bytes), + }, + ) + .context("Error mapping staged memory")?; + + std::slice::from_raw_parts_mut(ptr as *mut T, size.try_into()?) + }; + + Ok(StagedBuffer { + staged_buffer: ManuallyDrop::new(staged_buffer), + staged_memory: ManuallyDrop::new(staged_memory), + buffer: ManuallyDrop::new(buffer), + memory: ManuallyDrop::new(memory), + staged_mapped_memory, + highest_used: 0, + }) + } + + /// Call this before dropping + pub(crate) fn deactivate(mut self, device: &mut DeviceT) { + unsafe { + device.unmap_memory(&mut self.staged_memory); + + device.free_memory(ManuallyDrop::take(&mut self.staged_memory)); + device.destroy_buffer(ManuallyDrop::take(&mut self.staged_buffer)); + + device.free_memory(ManuallyDrop::take(&mut self.memory)); + device.destroy_buffer(ManuallyDrop::take(&mut self.buffer)); + }; + } +} + +impl<'a, T: Sized> ModifiableBuffer for StagedBuffer<'a, T> { + fn get_buffer(&mut self) -> &BufferT { + &self.buffer + } + + fn record_commit_cmds(&mut self, buf: &mut CommandBufferT) -> Result<()> { + unsafe { + buf.copy_buffer( + &self.staged_buffer, + &self.buffer, + std::iter::once(BufferCopy { + src: 0, + dst: 0, + size: ((self.highest_used + 1) * size_of::()) as u64, + }), + ); + } + + Ok(()) + } +} + +impl<'a, T: Sized> Index for StagedBuffer<'a, T> { + type Output = T; + + fn index(&self, index: usize) -> &Self::Output { + &self.staged_mapped_memory[index] + } +} + +impl<'a, T: Sized> IndexMut for StagedBuffer<'a, T> { + fn index_mut(&mut self, index: usize) -> &mut Self::Output { + if index > self.highest_used { + self.highest_used = index; + } + &mut self.staged_mapped_memory[index] + } +} diff --git a/stockton-render/src/builders/mod.rs b/stockton-render/src/builders/mod.rs new file mode 100644 index 0000000..97b47a0 --- /dev/null +++ b/stockton-render/src/builders/mod.rs @@ -0,0 +1,7 @@ +mod pipeline; +mod renderpass; +mod shader; + +pub use pipeline::*; +pub use renderpass::*; +pub use shader::*; diff --git a/stockton-render/src/builders/pipeline.rs b/stockton-render/src/builders/pipeline.rs new file mode 100644 index 0000000..25a4dc6 --- /dev/null +++ b/stockton-render/src/builders/pipeline.rs @@ -0,0 +1,288 @@ +use super::{renderpass::RenderpassSpec, shader::ShaderDesc}; +use crate::{error::EnvironmentError, target::SwapchainProperties, types::*}; + +use std::{mem::ManuallyDrop, ops::Range}; + +use anyhow::{Context, Result}; +use hal::{ + format::Format, + pso::{ + AttributeDesc, BakedStates, BasePipeline, BlendDesc, BufferIndex, DepthStencilDesc, + ElemStride, Element, GraphicsPipelineDesc, InputAssemblerDesc, PipelineCreationFlags, + PrimitiveAssemblerDesc, Rasterizer, Rect, ShaderStageFlags, VertexBufferDesc, + VertexInputRate, Viewport, + }, +}; +use shaderc::Compiler; + +pub struct VertexBufferSpec { + pub attributes: Vec, + pub rate: VertexInputRate, +} + +impl VertexBufferSpec { + pub fn as_attribute_desc(&self, binding: BufferIndex) -> Vec { + let mut v = Vec::with_capacity(self.attributes.len()); + let mut offset = 0; + for (idx, format) in self.attributes.iter().enumerate() { + v.push(AttributeDesc { + location: idx as u32, + binding, + element: Element { + offset, + format: *format, + }, + }); + offset += get_size(*format); + } + + v + } + pub fn stride(&self) -> ElemStride { + self.attributes.iter().fold(0, |x, f| x + get_size(*f)) + } +} + +fn get_size(f: Format) -> u32 { + match f { + Format::Rgb32Sfloat => 4 * 3, + Format::R32Sint => 4, + Format::Rg32Sfloat => 4 * 2, + Format::Rgba32Sfloat => 4 * 4, + _ => unimplemented!("dont know size of format {:?}", f), + } +} + +#[derive(Debug, Clone)] +pub struct VertexPrimitiveAssemblerSpec { + buffers: Vec, + attributes: Vec, + input_assembler: InputAssemblerDesc, +} + +impl VertexPrimitiveAssemblerSpec { + pub fn with_buffer(&mut self, bd: VertexBufferSpec) -> &mut Self { + let idx = self.buffers.len() as u32; + self.buffers.push(VertexBufferDesc { + binding: idx, + stride: bd.stride(), + rate: bd.rate, + }); + + self.attributes.extend(bd.as_attribute_desc(idx)); + + self + } + + pub fn with_buffers(iad: InputAssemblerDesc, mut bds: Vec) -> Self { + let mut this = VertexPrimitiveAssemblerSpec { + buffers: vec![], + attributes: vec![], + input_assembler: iad, + }; + + for bd in bds.drain(..) { + this.with_buffer(bd); + } + + this + } +} + +#[derive(Builder, Debug)] +#[builder(public)] +pub struct PipelineSpec { + rasterizer: Rasterizer, + depth_stencil: DepthStencilDesc, + blender: BlendDesc, + primitive_assembler: VertexPrimitiveAssemblerSpec, + + shader_vertex: ShaderDesc, + #[builder(setter(strip_option))] + shader_fragment: Option, + #[builder(setter(strip_option), default)] + shader_geom: Option, + #[builder(setter(strip_option), default)] + shader_tesselation: Option<(ShaderDesc, ShaderDesc)>, + + push_constants: Vec<(ShaderStageFlags, Range)>, + + #[builder(default = "false")] + dynamic_viewport: bool, + #[builder(default = "false")] + dynamic_scissor: bool, + + renderpass: RenderpassSpec, +} + +impl PipelineSpec { + pub fn build<'b, T: Iterator + std::fmt::Debug>( + self, + device: &mut DeviceT, + extent: hal::image::Extent, + _swapchain_properties: &SwapchainProperties, + set_layouts: T, + ) -> Result { + // Renderpass + let renderpass = self.renderpass.build_renderpass(device)?; + + // Subpass + let subpass = hal::pass::Subpass { + index: 0, + main_pass: &renderpass, + }; + + let mut compiler = Compiler::new().ok_or(EnvironmentError::NoShaderC)?; + let (vs_module, fs_module, gm_module, ts_module) = { + ( + self.shader_vertex.compile(&mut compiler, device)?, + self.shader_fragment + .as_ref() + .map(|x| x.compile(&mut compiler, device)) + .transpose()?, + self.shader_geom + .as_ref() + .map(|x| x.compile(&mut compiler, device)) + .transpose()?, + self.shader_tesselation + .as_ref() + .map::, _>(|(a, b)| { + Ok(( + a.compile(&mut compiler, device)?, + b.compile(&mut compiler, device)?, + )) + }) + .transpose()?, + ) + }; + + // Safety: *_module is always populated when shader_* is, so this is safe + let (vs_entry, fs_entry, gm_entry, ts_entry) = ( + self.shader_vertex.as_entry(&vs_module), + self.shader_fragment + .as_ref() + .map(|x| x.as_entry(fs_module.as_ref().unwrap())), + self.shader_geom + .as_ref() + .map(|x| x.as_entry(gm_module.as_ref().unwrap())), + self.shader_tesselation.as_ref().map(|(a, b)| { + ( + a.as_entry(&ts_module.as_ref().unwrap().0), + b.as_entry(&ts_module.as_ref().unwrap().1), + ) + }), + ); + + // Pipeline layout + let layout = unsafe { + device.create_pipeline_layout(set_layouts.into_iter(), self.push_constants.into_iter()) + } + .context("Error creating pipeline layout")?; + + // Baked states + let baked_states = BakedStates { + viewport: match self.dynamic_viewport { + true => None, + false => Some(Viewport { + rect: extent.rect(), + depth: (0.0..1.0), + }), + }, + scissor: match self.dynamic_scissor { + true => None, + false => Some(extent.rect()), + }, + blend_constants: None, + depth_bounds: None, + }; + + // Primitive assembler + let primitive_assembler = PrimitiveAssemblerDesc::Vertex { + buffers: self.primitive_assembler.buffers.as_slice(), + attributes: self.primitive_assembler.attributes.as_slice(), + input_assembler: self.primitive_assembler.input_assembler, + vertex: vs_entry, + tessellation: ts_entry, + geometry: gm_entry, + }; + + // Pipeline description + let pipeline_desc = GraphicsPipelineDesc { + label: Some("stockton"), + rasterizer: self.rasterizer, + fragment: fs_entry, + blender: self.blender, + depth_stencil: self.depth_stencil, + multisampling: None, + baked_states, + layout: &layout, + subpass, + flags: PipelineCreationFlags::empty(), + parent: BasePipeline::None, + primitive_assembler, + }; + + // Pipeline + let pipeline = unsafe { device.create_graphics_pipeline(&pipeline_desc, None) } + .context("Error creating graphics pipeline")?; + + Ok(CompletePipeline { + renderpass: ManuallyDrop::new(renderpass), + pipeline_layout: ManuallyDrop::new(layout), + pipeline: ManuallyDrop::new(pipeline), + vs_module: ManuallyDrop::new(vs_module), + fs_module, + gm_module, + ts_module, + render_area: extent.rect(), + }) + } +} + +pub struct CompletePipeline { + /// Our main render pass + pub renderpass: ManuallyDrop, + + /// The layout of our main graphics pipeline + pub pipeline_layout: ManuallyDrop, + + /// Our main graphics pipeline + pub pipeline: ManuallyDrop, + + /// The vertex shader module + pub vs_module: ManuallyDrop, + + /// The fragment shader module + pub fs_module: Option, + pub gm_module: Option, + pub ts_module: Option<(ShaderModuleT, ShaderModuleT)>, + + pub render_area: Rect, +} + +impl CompletePipeline { + /// Deactivate vulkan resources. Use before dropping + pub fn deactivate(mut self, device: &mut DeviceT) { + unsafe { + use core::ptr::read; + + device.destroy_render_pass(ManuallyDrop::into_inner(read(&self.renderpass))); + + device.destroy_shader_module(ManuallyDrop::into_inner(read(&self.vs_module))); + if let Some(x) = self.fs_module.take() { + device.destroy_shader_module(x) + } + if let Some(x) = self.gm_module.take() { + device.destroy_shader_module(x) + } + self.ts_module.take().map(|(a, b)| { + device.destroy_shader_module(a); + device.destroy_shader_module(b); + }); + + device.destroy_graphics_pipeline(ManuallyDrop::into_inner(read(&self.pipeline))); + + device.destroy_pipeline_layout(ManuallyDrop::into_inner(read(&self.pipeline_layout))); + } + } +} diff --git a/stockton-render/src/builders/renderpass.rs b/stockton-render/src/builders/renderpass.rs new file mode 100644 index 0000000..43f0eb2 --- /dev/null +++ b/stockton-render/src/builders/renderpass.rs @@ -0,0 +1,75 @@ +use crate::types::*; + +use std::iter::{empty, once}; + +use anyhow::Result; +use hal::pass::{Attachment, AttachmentRef, SubpassDesc}; + +#[derive(Debug, Clone)] +pub struct RenderpassSpec { + pub colors: Vec, + pub depth: Option, + pub inputs: Vec, + pub resolves: Vec, + pub preserves: Vec, +} + +impl RenderpassSpec { + pub fn build_renderpass(self, device: &mut DeviceT) -> Result { + let mut next_offset = 0; + + let colors: Vec = self + .colors + .iter() + .enumerate() + .map(|(i, a)| (next_offset + i, a.layouts.end)) + .collect(); + next_offset = colors.len(); + + let depth_stencil = self.depth.as_ref().map(|x| (next_offset, x.layouts.end)); + if depth_stencil.is_some() { + next_offset += 1; + } + + let inputs: Vec = self + .inputs + .iter() + .enumerate() + .map(|(i, a)| (next_offset + i, a.layouts.end)) + .collect(); + next_offset += inputs.len(); + + let resolves: Vec = self + .resolves + .iter() + .enumerate() + .map(|(i, a)| (next_offset + i, a.layouts.end)) + .collect(); + next_offset += resolves.len(); + + let preserves: Vec = self + .preserves + .iter() + .enumerate() + .map(|(i, _a)| next_offset + i) + .collect(); + + let sp_desc = SubpassDesc { + colors: colors.as_slice(), + depth_stencil: depth_stencil.as_ref(), + inputs: inputs.as_slice(), + resolves: resolves.as_slice(), + preserves: preserves.as_slice(), + }; + + let all_attachments = self + .colors + .into_iter() + .chain(self.depth.into_iter()) + .chain(self.inputs.into_iter()) + .chain(self.resolves.into_iter()) + .chain(self.preserves.into_iter()); + + Ok(unsafe { device.create_render_pass(all_attachments, once(sp_desc), empty())? }) + } +} diff --git a/stockton-render/src/builders/shader.rs b/stockton-render/src/builders/shader.rs new file mode 100644 index 0000000..fde185d --- /dev/null +++ b/stockton-render/src/builders/shader.rs @@ -0,0 +1,35 @@ +use crate::types::*; + +use anyhow::{Context, Result}; +use hal::pso::Specialization; +use shaderc::{Compiler, ShaderKind}; + +#[derive(Debug, Clone)] +pub struct ShaderDesc { + pub source: String, + pub entry: String, + pub kind: ShaderKind, +} + +impl ShaderDesc { + pub fn compile(&self, compiler: &mut Compiler, device: &mut DeviceT) -> Result { + let artifact = compiler + .compile_into_spirv(&self.source, self.kind, "shader", &self.entry, None) + .context("Shader compilation failed")?; + + // Make into shader module + Ok(unsafe { + device + .create_shader_module(artifact.as_binary()) + .context("Shader module creation failed")? + }) + } + + pub fn as_entry<'a>(&'a self, module: &'a ShaderModuleT) -> EntryPoint<'a> { + EntryPoint { + entry: &self.entry, + module, + specialization: Specialization::default(), + } + } +} diff --git a/stockton-render/src/context.rs b/stockton-render/src/context.rs new file mode 100644 index 0000000..3985c47 --- /dev/null +++ b/stockton-render/src/context.rs @@ -0,0 +1,275 @@ +//! Deals with all the Vulkan/HAL details. +//! This relies on draw passes for the actual drawing logic. + +use std::{ + mem::ManuallyDrop, + ptr::read, + sync::{Arc, RwLock}, +}; + +use anyhow::{Context, Result}; +use hal::pool::CommandPoolCreateFlags; +use log::debug; + +use winit::window::Window; + +use super::{ + draw_passes::{DrawPass, IntoDrawPass}, + queue_negotiator::{DrawQueue, QueueNegotiator}, + target::{SwapchainProperties, TargetChain}, +}; +use crate::{ + error::{EnvironmentError, LockPoisoned}, + types::*, +}; + +use stockton_types::Session; + +/// Contains all the hal related stuff. +/// In the end, this takes in a depth-sorted list of faces and a map file and renders them. +// TODO: Settings for clear colour, buffer sizes, etc +pub struct RenderingContext { + // Parents for most of these things + /// Vulkan Instance + instance: ManuallyDrop, + + /// Device we're using + device: Arc>, + + /// Adapter we're using + adapter: Adapter, + + /// Swapchain and stuff + target_chain: ManuallyDrop, + + // Command pool and buffers + /// The command pool used for our buffers + cmd_pool: ManuallyDrop, + + queue_negotiator: QueueNegotiator, + + /// The queue to use for drawing + queue: Arc>, + + pixels_per_point: f32, +} + +impl RenderingContext { + /// Create a new RenderingContext for the given window. + pub fn new, DP: DrawPass>(window: &Window) -> Result { + // Create surface + let (instance, surface, mut adapters) = unsafe { + let instance = + back::Instance::create("stockton", 1).context("Error creating vulkan instance")?; + let surface = instance + .create_surface(window) + .context("Error creating surface")?; + let adapters = instance.enumerate_adapters(); + + (instance, surface, adapters) + }; + + // TODO: Properly figure out which adapter to use + let adapter = adapters.remove(0); + + // Queue Negotiator + let mut queue_families_specs = Vec::new(); + let (mut queue_negotiator, surface) = { + let dq: DrawQueue = DrawQueue { surface }; + + let mut qn = QueueNegotiator::new(); + + // Draw Queue + qn.find(&adapter, &dq) + .context("Couldn't find draw queue family")?; + queue_families_specs.push( + qn.family_spec::(&adapter.queue_families, 1) + .context("Couldn't find draw queue family")?, + ); + + // Auxiliary queues for DP + queue_families_specs.extend( + IDP::find_aux_queues(&adapter, &mut qn) + .context("Level pass couldn't populate queue negotiator")?, + ); + + (qn, dq.surface) + }; + + // Device & Queue groups + let (device_lock, queue_groups) = { + // TODO: This sucks, but hal is restrictive on how we can pass this specific argument. + let queue_families_specs_real: Vec<_> = queue_families_specs + .iter() + .map(|(qf, ns)| (*qf, ns.as_slice())) + .collect(); + + let gpu = unsafe { + adapter + .physical_device + .open(queue_families_specs_real.as_slice(), hal::Features::empty()) + .context("Error opening logical device")? + }; + + (Arc::new(RwLock::new(gpu.device)), gpu.queue_groups) + }; + + queue_negotiator.set_queue_groups(queue_groups); + + // Figure out what our swapchain will look like + let swapchain_properties = SwapchainProperties::find_best(&adapter, &surface) + .context("Error getting properties for swapchain")?; + + // Lock device + let mut device = device_lock + .write() + .map_err(|_| LockPoisoned::Device) + .context("Error getting device lock")?; + + debug!("Detected swapchain properties: {:?}", swapchain_properties); + + // Command pool + let mut cmd_pool = unsafe { + device.create_command_pool( + queue_negotiator + .family::() + .ok_or(EnvironmentError::NoSuitableFamilies)?, + CommandPoolCreateFlags::RESET_INDIVIDUAL, + ) + } + .context("Error creating draw command pool")?; + + // Swapchain and associated resources + let target_chain = TargetChain::new( + &mut device, + &adapter, + surface, + &mut cmd_pool, + swapchain_properties, + ) + .context("Error creating target chain")?; + + // Unlock device + drop(device); + + let queue = queue_negotiator + .get_queue::() + .ok_or(EnvironmentError::NoQueues) + .context("Error getting draw queue")?; + + Ok(RenderingContext { + instance: ManuallyDrop::new(instance), + + device: device_lock, + adapter, + + queue_negotiator, + queue, + + target_chain: ManuallyDrop::new(target_chain), + cmd_pool: ManuallyDrop::new(cmd_pool), + + // pixels_per_point: window.scale_factor() as f32, + pixels_per_point: window.scale_factor() as f32, + }) + } + + /// If this function fails the whole context is probably dead + /// # Safety + /// The context must not be used while this is being called + pub unsafe fn handle_surface_change(&mut self) -> Result<()> { + let mut device = self + .device + .write() + .map_err(|_| LockPoisoned::Device) + .context("Error getting device lock")?; + + device + .wait_idle() + .context("Error waiting for device to become idle")?; + + let surface = ManuallyDrop::into_inner(read(&self.target_chain)) + .deactivate_with_recyling(&mut device, &mut self.cmd_pool); + + let properties = SwapchainProperties::find_best(&self.adapter, &surface) + .context("Error finding best swapchain properties")?; + + self.target_chain = ManuallyDrop::new( + TargetChain::new( + &mut device, + &self.adapter, + surface, + &mut self.cmd_pool, + properties, + ) + .context("Error creating target chain")?, + ); + Ok(()) + } + + /// Draw onto the next frame of the swapchain + pub fn draw_next_frame(&mut self, session: &Session, dp: &mut DP) -> Result<()> { + let mut device = self + .device + .write() + .map_err(|_| LockPoisoned::Device) + .context("Error getting device lock")?; + let mut queue = self + .queue + .write() + .map_err(|_| LockPoisoned::Queue) + .context("Error getting draw queue lock")?; + + // Level draw pass + self.target_chain + .do_draw_with(&mut device, &mut queue, dp, session) + .context("Error preparing next target")?; + + Ok(()) + } + + /// Get a reference to the rendering context's pixels per point. + pub fn pixels_per_point(&self) -> f32 { + self.pixels_per_point + } + + /// Get a reference to the rendering context's device. + pub fn device(&self) -> &Arc> { + &self.device + } + + /// Get a reference to the rendering context's target chain. + pub fn target_chain(&self) -> &TargetChain { + &self.target_chain + } + + /// Get a reference to the rendering context's adapter. + pub fn adapter(&self) -> &Adapter { + &self.adapter + } + + /// Get a mutable reference to the rendering context's queue negotiator. + pub fn queue_negotiator_mut(&mut self) -> &mut QueueNegotiator { + &mut self.queue_negotiator + } +} + +impl core::ops::Drop for RenderingContext { + fn drop(&mut self) { + { + self.device.write().unwrap().wait_idle().unwrap(); + } + + unsafe { + let mut device = self.device.write().unwrap(); + + ManuallyDrop::into_inner(read(&self.target_chain)).deactivate( + &mut self.instance, + &mut device, + &mut self.cmd_pool, + ); + + device.destroy_command_pool(ManuallyDrop::into_inner(read(&self.cmd_pool))); + } + } +} diff --git a/stockton-render/src/culling.rs b/stockton-render/src/culling.rs deleted file mode 100644 index c10605f..0000000 --- a/stockton-render/src/culling.rs +++ /dev/null @@ -1,70 +0,0 @@ -//! Functions for figuring out what to render -#![allow(dead_code)] - -use stockton_levels::prelude::*; -use stockton_levels::parts::tree::{BspNode, BspNodeValue}; -use stockton_types::Vector3; - -/// Get the visible faces according to visdata and frustum culling -// TODO: Write this. For now, just render all faces -pub fn get_visible_faces>(pos: Vector3, file: &T) -> Vec { - let vis_cluster = get_cluster_id(pos, file); - - let mut visible = Vec::with_capacity(file.faces_len() as usize); - if (vis_cluster & 0x80000000) != 0 { - // Negative = Invalid camera position - // For now just render everything - for face_idx in 0..file.faces_len() { - visible.push(face_idx); - } - - return visible; - } - - walk_bsp_tree(file.get_bsp_root(), vis_cluster, &mut visible, file); - - visible -} - -pub fn walk_bsp_tree>( - node: &BspNode, - vis_cluster: u32, - visible_faces: &mut Vec, - file: &T, -) { - if let BspNodeValue::Children(front, back) = &node.value { - walk_bsp_tree(back, vis_cluster, visible_faces, file); - walk_bsp_tree(front, vis_cluster, visible_faces, file); - } else if let BspNodeValue::Leaf(leaf) = &node.value { - if (leaf.cluster_id & 0x80000000) != 0 { - // Negative means invalid leaf - return; - } else if file.cluster_visible_from(vis_cluster, leaf.cluster_id) { - for face_idx in leaf.faces_idx.iter() { - // TODO: Culling or something - visible_faces.push(*face_idx); - } - } - } -} - -/// Get the viscluster pos lies in -fn get_cluster_id>(pos: Vector3, file: &T) -> u32 { - let mut node = file.get_bsp_root(); - while let BspNodeValue::Children(front, back) = &node.value { - let plane = file.get_plane(node.plane_idx); - let dist = plane.normal.dot(&pos) - plane.dist; - - if dist >= 0.0 { - node = front; - } else { - node = back; - } - } - - if let BspNodeValue::Leaf(leaf) = &node.value { - leaf.cluster_id - } else { - panic!("should have had a leaf but didn't"); - } -} diff --git a/stockton-render/src/draw/buffers/dedicated_image.rs b/stockton-render/src/draw/buffers/dedicated_image.rs deleted file mode 100644 index 878d304..0000000 --- a/stockton-render/src/draw/buffers/dedicated_image.rs +++ /dev/null @@ -1,134 +0,0 @@ -//! A dedicated image. Used for depth buffers. - -use crate::draw::texture::PIXEL_SIZE; -use crate::types::*; - -use std::mem::ManuallyDrop; - -use anyhow::{Context, Result}; -use hal::{ - format::{Format, Swizzle}, - image::{SubresourceRange, Usage, Usage as ImgUsage, ViewKind}, - memory, - memory::Properties, - MemoryTypeId, -}; -use thiserror::Error; - -/// 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, - - /// The full view of the image - pub image_view: ManuallyDrop, - - /// The memory backing the image - memory: ManuallyDrop, -} - -#[derive(Debug, Error)] -pub enum ImageLoadError { - #[error("No suitable memory type for image memory")] - NoMemoryTypes, -} - -impl DedicatedLoadedImage { - pub fn new( - device: &mut DeviceT, - adapter: &Adapter, - format: Format, - usage: Usage, - resources: SubresourceRange, - width: usize, - height: usize, - ) -> Result { - let (memory, image_ref) = { - // Round up the size to align properly - let initial_row_size = PIXEL_SIZE * width; - let limits = adapter.physical_device.properties().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, - memory::SparseFlags::empty(), - ViewCapabilities::empty(), - ) - } - .context("Error creating image")?; - - // 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(Properties::DEVICE_LOCAL) - }) - .map(|(id, _)| MemoryTypeId(id)) - .ok_or(ImageLoadError::NoMemoryTypes)?; - - let memory = device - .allocate_memory(memory_type_id, requirements.size) - .context("Error allocating memory for image")?; - - device - .bind_image_memory(&memory, 0, &mut image_ref) - .context("Error binding memory to image")?; - - memory - }; - - (memory, image_ref) - }; - - // Create ImageView and sampler - let image_view = unsafe { - device.create_image_view( - &image_ref, - ViewKind::D2, - format, - Swizzle::NO, - ImgUsage::DEPTH_STENCIL_ATTACHMENT, - resources, - ) - } - .context("Error creating image view")?; - - Ok(DedicatedLoadedImage { - image: ManuallyDrop::new(image_ref), - image_view: ManuallyDrop::new(image_view), - memory: ManuallyDrop::new(memory), - }) - } - - /// Properly frees/destroys all the objects in this struct - /// Dropping without doing this is a bad idea - pub fn deactivate(self, device: &mut DeviceT) { - 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))); - } - } -} diff --git a/stockton-render/src/draw/buffers/draw_buffers.rs b/stockton-render/src/draw/buffers/draw_buffers.rs deleted file mode 100644 index 5baec92..0000000 --- a/stockton-render/src/draw/buffers/draw_buffers.rs +++ /dev/null @@ -1,43 +0,0 @@ -//! A vertex and index buffer set for drawing - -use super::StagedBuffer; -use crate::types::*; - -use anyhow::{Context, Result}; -use hal::buffer::Usage; -use std::mem::ManuallyDrop; - -/// Initial size of vertex buffer. TODO: Way of overriding this -pub const INITIAL_VERT_SIZE: u64 = 3 * 3000; - -/// Initial size of index buffer. TODO: Way of overriding this -pub const INITIAL_INDEX_SIZE: u64 = 3000; - -/// The buffers used for drawing, ie index and vertex buffer -pub struct DrawBuffers<'a, T: Sized> { - pub vertex_buffer: ManuallyDrop>, - pub index_buffer: ManuallyDrop>, -} - -impl<'a, T> DrawBuffers<'a, T> { - pub fn new(device: &mut DeviceT, adapter: &Adapter) -> Result> { - let vert = StagedBuffer::new(device, adapter, Usage::VERTEX, INITIAL_VERT_SIZE) - .context("Error creating vertex buffer")?; - let index = StagedBuffer::new(device, adapter, Usage::INDEX, INITIAL_INDEX_SIZE) - .context("Error creating index buffer")?; - - Ok(DrawBuffers { - vertex_buffer: ManuallyDrop::new(vert), - index_buffer: ManuallyDrop::new(index), - }) - } - - pub fn deactivate(self, device: &mut DeviceT) { - unsafe { - use core::ptr::read; - - ManuallyDrop::into_inner(read(&self.vertex_buffer)).deactivate(device); - ManuallyDrop::into_inner(read(&self.index_buffer)).deactivate(device); - } - } -} diff --git a/stockton-render/src/draw/buffers/mod.rs b/stockton-render/src/draw/buffers/mod.rs deleted file mode 100644 index dc7df65..0000000 --- a/stockton-render/src/draw/buffers/mod.rs +++ /dev/null @@ -1,63 +0,0 @@ -//! All sorts of buffers - -use std::ops::IndexMut; - -use crate::{error::EnvironmentError, types::*}; - -use anyhow::{Context, Result}; -use hal::{ - buffer::Usage, - memory::{Properties, SparseFlags}, - MemoryTypeId, -}; - -pub mod dedicated_image; -pub mod draw_buffers; -pub mod staged; - -pub use dedicated_image::DedicatedLoadedImage; -pub use draw_buffers::DrawBuffers; -pub use staged::StagedBuffer; - -/// Create a buffer of the given specifications, allocating more device memory. -// TODO: Use a different memory allocator? -pub(crate) fn create_buffer( - device: &mut DeviceT, - adapter: &Adapter, - usage: Usage, - properties: Properties, - size: u64, -) -> Result<(BufferT, MemoryT)> { - let mut buffer = unsafe { device.create_buffer(size, usage, SparseFlags::empty()) } - .context("Error creating buffer")?; - - let requirements = unsafe { device.get_buffer_requirements(&buffer) }; - 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(properties) - }) - .map(|(id, _)| MemoryTypeId(id)) - .ok_or(EnvironmentError::NoMemoryTypes)?; - - let memory = unsafe { device.allocate_memory(memory_type_id, requirements.size) } - .context("Error allocating memory")?; - - unsafe { device.bind_buffer_memory(&memory, 0, &mut buffer) } - .context("Error binding memory to buffer")?; - - Ok((buffer, memory)) -} - -/// A buffer that can be modified by the CPU -pub trait ModifiableBuffer: IndexMut { - /// Get a handle to the underlying GPU buffer - fn get_buffer(&mut self) -> &BufferT; - - /// Record the command(s) required to commit changes to this buffer to the given command buffer. - fn record_commit_cmds<'a>(&'a mut self, cmd_buffer: &mut CommandBufferT) -> Result<()>; -} diff --git a/stockton-render/src/draw/buffers/staged.rs b/stockton-render/src/draw/buffers/staged.rs deleted file mode 100644 index 71b5204..0000000 --- a/stockton-render/src/draw/buffers/staged.rs +++ /dev/null @@ -1,142 +0,0 @@ -//! A buffer that can be written to by the CPU using staging memory - -use super::{create_buffer, ModifiableBuffer}; -use crate::types::*; - -use core::mem::{size_of, ManuallyDrop}; -use std::{ - convert::TryInto, - ops::{Index, IndexMut}, -}; - -use anyhow::{Context, Result}; -use hal::{ - buffer::Usage, - command::BufferCopy, - memory::{Properties, Segment}, -}; - -/// A GPU buffer that is written to using a staging buffer -pub struct StagedBuffer<'a, T: Sized> { - /// CPU-visible buffer - staged_buffer: ManuallyDrop, - - /// CPU-visible memory - staged_memory: ManuallyDrop, - - /// GPU Buffer - buffer: ManuallyDrop, - - /// GPU Memory - memory: ManuallyDrop, - - /// Where staged buffer is mapped in CPU memory - staged_mapped_memory: &'a mut [T], - - /// The highest index in the buffer that's been written to. - pub highest_used: usize, -} - -impl<'a, T: Sized> StagedBuffer<'a, T> { - /// size is the size in T - pub fn new(device: &mut DeviceT, adapter: &Adapter, usage: Usage, size: u64) -> Result { - // Convert size to bytes - let size_bytes = size * size_of::() as u64; - - // Get CPU-visible buffer - let (staged_buffer, mut staged_memory) = create_buffer( - device, - adapter, - Usage::TRANSFER_SRC, - Properties::CPU_VISIBLE, - size_bytes, - ) - .context("Error creating staging buffer")?; - - // Get GPU Buffer - let (buffer, memory) = create_buffer( - device, - adapter, - Usage::TRANSFER_DST | usage, - Properties::DEVICE_LOCAL | Properties::COHERENT, - size_bytes, - ) - .context("Error creating GPU buffer")?; - - // Map it somewhere and get a slice to that memory - let staged_mapped_memory = unsafe { - let ptr = device - .map_memory( - &mut staged_memory, - Segment { - offset: 0, - size: Some(size_bytes), - }, - ) - .context("Error mapping staged memory")?; - - std::slice::from_raw_parts_mut(ptr as *mut T, size.try_into()?) - }; - - Ok(StagedBuffer { - staged_buffer: ManuallyDrop::new(staged_buffer), - staged_memory: ManuallyDrop::new(staged_memory), - buffer: ManuallyDrop::new(buffer), - memory: ManuallyDrop::new(memory), - staged_mapped_memory, - highest_used: 0, - }) - } - - /// Call this before dropping - pub(crate) fn deactivate(mut self, device: &mut DeviceT) { - unsafe { - device.unmap_memory(&mut self.staged_memory); - - device.free_memory(ManuallyDrop::take(&mut self.staged_memory)); - device.destroy_buffer(ManuallyDrop::take(&mut self.staged_buffer)); - - device.free_memory(ManuallyDrop::take(&mut self.memory)); - device.destroy_buffer(ManuallyDrop::take(&mut self.buffer)); - }; - } -} - -impl<'a, T: Sized> ModifiableBuffer for StagedBuffer<'a, T> { - fn get_buffer(&mut self) -> &BufferT { - &self.buffer - } - - fn record_commit_cmds(&mut self, buf: &mut CommandBufferT) -> Result<()> { - unsafe { - buf.copy_buffer( - &self.staged_buffer, - &self.buffer, - std::iter::once(BufferCopy { - src: 0, - dst: 0, - size: ((self.highest_used + 1) * size_of::()) as u64, - }), - ); - } - - Ok(()) - } -} - -impl<'a, T: Sized> Index for StagedBuffer<'a, T> { - type Output = T; - - fn index(&self, index: usize) -> &Self::Output { - &self.staged_mapped_memory[index] - } -} - -impl<'a, T: Sized> IndexMut for StagedBuffer<'a, T> { - fn index_mut(&mut self, index: usize) -> &mut Self::Output { - if index > self.highest_used { - self.highest_used = index; - } - &mut self.staged_mapped_memory[index] - } -} diff --git a/stockton-render/src/draw/builders/mod.rs b/stockton-render/src/draw/builders/mod.rs deleted file mode 100644 index 002c09f..0000000 --- a/stockton-render/src/draw/builders/mod.rs +++ /dev/null @@ -1,3 +0,0 @@ -pub mod pipeline; -pub mod renderpass; -pub mod shader; diff --git a/stockton-render/src/draw/builders/pipeline.rs b/stockton-render/src/draw/builders/pipeline.rs deleted file mode 100644 index 0c06774..0000000 --- a/stockton-render/src/draw/builders/pipeline.rs +++ /dev/null @@ -1,288 +0,0 @@ -use super::{renderpass::RenderpassSpec, shader::ShaderDesc}; -use crate::{draw::target::SwapchainProperties, error::EnvironmentError, types::*}; - -use std::{mem::ManuallyDrop, ops::Range}; - -use anyhow::{Context, Result}; -use hal::{ - format::Format, - pso::{ - AttributeDesc, BakedStates, BasePipeline, BlendDesc, BufferIndex, DepthStencilDesc, - ElemStride, Element, GraphicsPipelineDesc, InputAssemblerDesc, PipelineCreationFlags, - PrimitiveAssemblerDesc, Rasterizer, Rect, ShaderStageFlags, VertexBufferDesc, - VertexInputRate, Viewport, - }, -}; -use shaderc::Compiler; - -pub struct VertexBufferSpec { - pub attributes: Vec, - pub rate: VertexInputRate, -} - -impl VertexBufferSpec { - pub fn as_attribute_desc(&self, binding: BufferIndex) -> Vec { - let mut v = Vec::with_capacity(self.attributes.len()); - let mut offset = 0; - for (idx, format) in self.attributes.iter().enumerate() { - v.push(AttributeDesc { - location: idx as u32, - binding, - element: Element { - offset, - format: *format, - }, - }); - offset += get_size(*format); - } - - v - } - pub fn stride(&self) -> ElemStride { - self.attributes.iter().fold(0, |x, f| x + get_size(*f)) - } -} - -fn get_size(f: Format) -> u32 { - match f { - Format::Rgb32Sfloat => 4 * 3, - Format::R32Sint => 4, - Format::Rg32Sfloat => 4 * 2, - Format::Rgba32Sfloat => 4 * 4, - _ => unimplemented!("dont know size of format {:?}", f), - } -} - -#[derive(Debug, Clone)] -pub struct VertexPrimitiveAssemblerSpec { - buffers: Vec, - attributes: Vec, - input_assembler: InputAssemblerDesc, -} - -impl VertexPrimitiveAssemblerSpec { - pub fn with_buffer(&mut self, bd: VertexBufferSpec) -> &mut Self { - let idx = self.buffers.len() as u32; - self.buffers.push(VertexBufferDesc { - binding: idx, - stride: bd.stride(), - rate: bd.rate, - }); - - self.attributes.extend(bd.as_attribute_desc(idx)); - - self - } - - pub fn with_buffers(iad: InputAssemblerDesc, mut bds: Vec) -> Self { - let mut this = VertexPrimitiveAssemblerSpec { - buffers: vec![], - attributes: vec![], - input_assembler: iad, - }; - - for bd in bds.drain(..) { - this.with_buffer(bd); - } - - this - } -} - -#[derive(Builder, Debug)] -#[builder(public)] -pub struct PipelineSpec { - rasterizer: Rasterizer, - depth_stencil: DepthStencilDesc, - blender: BlendDesc, - primitive_assembler: VertexPrimitiveAssemblerSpec, - - shader_vertex: ShaderDesc, - #[builder(setter(strip_option))] - shader_fragment: Option, - #[builder(setter(strip_option), default)] - shader_geom: Option, - #[builder(setter(strip_option), default)] - shader_tesselation: Option<(ShaderDesc, ShaderDesc)>, - - push_constants: Vec<(ShaderStageFlags, Range)>, - - #[builder(default = "false")] - dynamic_viewport: bool, - #[builder(default = "false")] - dynamic_scissor: bool, - - renderpass: RenderpassSpec, -} - -impl PipelineSpec { - pub fn build<'b, T: Iterator + std::fmt::Debug>( - self, - device: &mut DeviceT, - extent: hal::image::Extent, - _swapchain_properties: &SwapchainProperties, - set_layouts: T, - ) -> Result { - // Renderpass - let renderpass = self.renderpass.build_renderpass(device)?; - - // Subpass - let subpass = hal::pass::Subpass { - index: 0, - main_pass: &renderpass, - }; - - let mut compiler = Compiler::new().ok_or(EnvironmentError::NoShaderC)?; - let (vs_module, fs_module, gm_module, ts_module) = { - ( - self.shader_vertex.compile(&mut compiler, device)?, - self.shader_fragment - .as_ref() - .map(|x| x.compile(&mut compiler, device)) - .transpose()?, - self.shader_geom - .as_ref() - .map(|x| x.compile(&mut compiler, device)) - .transpose()?, - self.shader_tesselation - .as_ref() - .map::, _>(|(a, b)| { - Ok(( - a.compile(&mut compiler, device)?, - b.compile(&mut compiler, device)?, - )) - }) - .transpose()?, - ) - }; - - // Safety: *_module is always populated when shader_* is, so this is safe - let (vs_entry, fs_entry, gm_entry, ts_entry) = ( - self.shader_vertex.as_entry(&vs_module), - self.shader_fragment - .as_ref() - .map(|x| x.as_entry(fs_module.as_ref().unwrap())), - self.shader_geom - .as_ref() - .map(|x| x.as_entry(gm_module.as_ref().unwrap())), - self.shader_tesselation.as_ref().map(|(a, b)| { - ( - a.as_entry(&ts_module.as_ref().unwrap().0), - b.as_entry(&ts_module.as_ref().unwrap().1), - ) - }), - ); - - // Pipeline layout - let layout = unsafe { - device.create_pipeline_layout(set_layouts.into_iter(), self.push_constants.into_iter()) - } - .context("Error creating pipeline layout")?; - - // Baked states - let baked_states = BakedStates { - viewport: match self.dynamic_viewport { - true => None, - false => Some(Viewport { - rect: extent.rect(), - depth: (0.0..1.0), - }), - }, - scissor: match self.dynamic_scissor { - true => None, - false => Some(extent.rect()), - }, - blend_constants: None, - depth_bounds: None, - }; - - // Primitive assembler - let primitive_assembler = PrimitiveAssemblerDesc::Vertex { - buffers: self.primitive_assembler.buffers.as_slice(), - attributes: self.primitive_assembler.attributes.as_slice(), - input_assembler: self.primitive_assembler.input_assembler, - vertex: vs_entry, - tessellation: ts_entry, - geometry: gm_entry, - }; - - // Pipeline description - let pipeline_desc = GraphicsPipelineDesc { - label: Some("stockton"), - rasterizer: self.rasterizer, - fragment: fs_entry, - blender: self.blender, - depth_stencil: self.depth_stencil, - multisampling: None, - baked_states, - layout: &layout, - subpass, - flags: PipelineCreationFlags::empty(), - parent: BasePipeline::None, - primitive_assembler, - }; - - // Pipeline - let pipeline = unsafe { device.create_graphics_pipeline(&pipeline_desc, None) } - .context("Error creating graphics pipeline")?; - - Ok(CompletePipeline { - renderpass: ManuallyDrop::new(renderpass), - pipeline_layout: ManuallyDrop::new(layout), - pipeline: ManuallyDrop::new(pipeline), - vs_module: ManuallyDrop::new(vs_module), - fs_module, - gm_module, - ts_module, - render_area: extent.rect(), - }) - } -} - -pub struct CompletePipeline { - /// Our main render pass - pub(crate) renderpass: ManuallyDrop, - - /// The layout of our main graphics pipeline - pub(crate) pipeline_layout: ManuallyDrop, - - /// Our main graphics pipeline - pub(crate) pipeline: ManuallyDrop, - - /// The vertex shader module - pub(crate) vs_module: ManuallyDrop, - - /// The fragment shader module - pub(crate) fs_module: Option, - pub(crate) gm_module: Option, - pub(crate) ts_module: Option<(ShaderModuleT, ShaderModuleT)>, - - pub(crate) render_area: Rect, -} - -impl CompletePipeline { - /// Deactivate vulkan resources. Use before dropping - pub fn deactivate(mut self, device: &mut DeviceT) { - unsafe { - use core::ptr::read; - - device.destroy_render_pass(ManuallyDrop::into_inner(read(&self.renderpass))); - - device.destroy_shader_module(ManuallyDrop::into_inner(read(&self.vs_module))); - if let Some(x) = self.fs_module.take() { - device.destroy_shader_module(x) - } - if let Some(x) = self.gm_module.take() { - device.destroy_shader_module(x) - } - self.ts_module.take().map(|(a, b)| { - device.destroy_shader_module(a); - device.destroy_shader_module(b); - }); - - device.destroy_graphics_pipeline(ManuallyDrop::into_inner(read(&self.pipeline))); - - device.destroy_pipeline_layout(ManuallyDrop::into_inner(read(&self.pipeline_layout))); - } - } -} diff --git a/stockton-render/src/draw/builders/renderpass.rs b/stockton-render/src/draw/builders/renderpass.rs deleted file mode 100644 index 43f0eb2..0000000 --- a/stockton-render/src/draw/builders/renderpass.rs +++ /dev/null @@ -1,75 +0,0 @@ -use crate::types::*; - -use std::iter::{empty, once}; - -use anyhow::Result; -use hal::pass::{Attachment, AttachmentRef, SubpassDesc}; - -#[derive(Debug, Clone)] -pub struct RenderpassSpec { - pub colors: Vec, - pub depth: Option, - pub inputs: Vec, - pub resolves: Vec, - pub preserves: Vec, -} - -impl RenderpassSpec { - pub fn build_renderpass(self, device: &mut DeviceT) -> Result { - let mut next_offset = 0; - - let colors: Vec = self - .colors - .iter() - .enumerate() - .map(|(i, a)| (next_offset + i, a.layouts.end)) - .collect(); - next_offset = colors.len(); - - let depth_stencil = self.depth.as_ref().map(|x| (next_offset, x.layouts.end)); - if depth_stencil.is_some() { - next_offset += 1; - } - - let inputs: Vec = self - .inputs - .iter() - .enumerate() - .map(|(i, a)| (next_offset + i, a.layouts.end)) - .collect(); - next_offset += inputs.len(); - - let resolves: Vec = self - .resolves - .iter() - .enumerate() - .map(|(i, a)| (next_offset + i, a.layouts.end)) - .collect(); - next_offset += resolves.len(); - - let preserves: Vec = self - .preserves - .iter() - .enumerate() - .map(|(i, _a)| next_offset + i) - .collect(); - - let sp_desc = SubpassDesc { - colors: colors.as_slice(), - depth_stencil: depth_stencil.as_ref(), - inputs: inputs.as_slice(), - resolves: resolves.as_slice(), - preserves: preserves.as_slice(), - }; - - let all_attachments = self - .colors - .into_iter() - .chain(self.depth.into_iter()) - .chain(self.inputs.into_iter()) - .chain(self.resolves.into_iter()) - .chain(self.preserves.into_iter()); - - Ok(unsafe { device.create_render_pass(all_attachments, once(sp_desc), empty())? }) - } -} diff --git a/stockton-render/src/draw/builders/shader.rs b/stockton-render/src/draw/builders/shader.rs deleted file mode 100644 index fde185d..0000000 --- a/stockton-render/src/draw/builders/shader.rs +++ /dev/null @@ -1,35 +0,0 @@ -use crate::types::*; - -use anyhow::{Context, Result}; -use hal::pso::Specialization; -use shaderc::{Compiler, ShaderKind}; - -#[derive(Debug, Clone)] -pub struct ShaderDesc { - pub source: String, - pub entry: String, - pub kind: ShaderKind, -} - -impl ShaderDesc { - pub fn compile(&self, compiler: &mut Compiler, device: &mut DeviceT) -> Result { - let artifact = compiler - .compile_into_spirv(&self.source, self.kind, "shader", &self.entry, None) - .context("Shader compilation failed")?; - - // Make into shader module - Ok(unsafe { - device - .create_shader_module(artifact.as_binary()) - .context("Shader module creation failed")? - }) - } - - pub fn as_entry<'a>(&'a self, module: &'a ShaderModuleT) -> EntryPoint<'a> { - EntryPoint { - entry: &self.entry, - module, - specialization: Specialization::default(), - } - } -} diff --git a/stockton-render/src/draw/camera.rs b/stockton-render/src/draw/camera.rs deleted file mode 100644 index 62784de..0000000 --- a/stockton-render/src/draw/camera.rs +++ /dev/null @@ -1,61 +0,0 @@ -//! Things related to converting 3D world space to 2D screen space - -use legion::maybe_changed; -use na::{look_at_lh, perspective_lh_zo}; -use stockton_types::{ - components::{CameraSettings, CameraVPMatrix, Transform}, - Vector3, -}; - -use crate::Renderer; - -use super::DrawPass; - -fn euler_to_direction(euler: &Vector3) -> Vector3 { - let pitch = euler.x; - let yaw = euler.y; - let _roll = euler.z; // TODO: Support camera roll - - Vector3::new( - yaw.sin() * pitch.cos(), - pitch.sin(), - yaw.cos() * pitch.cos(), - ) -} - -#[system(for_each)] -#[filter(maybe_changed::() | maybe_changed::())] -pub fn calc_vp_matrix( - transform: &Transform, - settings: &CameraSettings, - matrix: &mut CameraVPMatrix, - #[resource] renderer: &Renderer, -) { - // Get look direction from euler angles - let direction = euler_to_direction(&transform.rotation); - - // Converts world space to camera space - let view_matrix = look_at_lh( - &transform.position, - &(transform.position + direction), - &Vector3::new(0.0, 1.0, 0.0), - ); - - // Converts camera space to screen space - let projection_matrix = { - let mut temp = perspective_lh_zo( - renderer.get_aspect_ratio(), - settings.fov, - settings.near, - settings.far, - ); - - // Vulkan's co-ord system is different from OpenGLs - temp[(1, 1)] *= -1.0; - - temp - }; - - // Chain them together into a single matrix - matrix.vp_matrix = projection_matrix * view_matrix; -} diff --git a/stockton-render/src/draw/context.rs b/stockton-render/src/draw/context.rs deleted file mode 100644 index 65dcff6..0000000 --- a/stockton-render/src/draw/context.rs +++ /dev/null @@ -1,266 +0,0 @@ -//! Deals with all the Vulkan/HAL details. -//! This relies on draw passes for the actual drawing logic. - -use std::{ - mem::ManuallyDrop, - ptr::read, - sync::{Arc, RwLock}, -}; - -use anyhow::{Context, Result}; -use hal::pool::CommandPoolCreateFlags; -use log::debug; - -use winit::window::Window; - -use super::{ - draw_passes::{DrawPass, IntoDrawPass}, - queue_negotiator::{DrawQueue, QueueNegotiator}, - target::{SwapchainProperties, TargetChain}, -}; -use crate::{ - error::{EnvironmentError, LockPoisoned}, - types::*, -}; - -use stockton_types::Session; - -/// Contains all the hal related stuff. -/// In the end, this takes in a depth-sorted list of faces and a map file and renders them. -// TODO: Settings for clear colour, buffer sizes, etc -pub struct RenderingContext { - // Parents for most of these things - /// Vulkan Instance - instance: ManuallyDrop, - - /// Device we're using - device: Arc>, - - /// Adapter we're using - adapter: Adapter, - - /// Swapchain and stuff - pub(crate) target_chain: ManuallyDrop, - - // Command pool and buffers - /// The command pool used for our buffers - cmd_pool: ManuallyDrop, - - /// The queue to use for drawing - queue: Arc>, - - /// Deals with drawing logic, and holds any data required for drawing. - draw_pass: ManuallyDrop, - - pub(crate) pixels_per_point: f32, -} - -impl RenderingContext { - /// Create a new RenderingContext for the given window. - pub fn new>( - window: &Window, - session: &Session, - idp: IDP, - ) -> Result { - // Create surface - let (instance, surface, mut adapters) = unsafe { - let instance = - back::Instance::create("stockton", 1).context("Error creating vulkan instance")?; - let surface = instance - .create_surface(window) - .context("Error creating surface")?; - let adapters = instance.enumerate_adapters(); - - (instance, surface, adapters) - }; - - // TODO: Properly figure out which adapter to use - let adapter = adapters.remove(0); - - // Queue Negotiator - let mut queue_families_specs = Vec::new(); - let (mut queue_negotiator, surface) = { - let dq: DrawQueue = DrawQueue { surface }; - - let mut qn = QueueNegotiator::new(); - - // Draw Queue - qn.find(&adapter, &dq) - .context("Couldn't find draw queue family")?; - queue_families_specs.push( - qn.family_spec::(&adapter.queue_families, 1) - .context("Couldn't find draw queue family")?, - ); - - // Auxiliary queues for DP - queue_families_specs.extend( - IDP::find_aux_queues(&adapter, &mut qn) - .context("Level pass couldn't populate queue negotiator")?, - ); - - (qn, dq.surface) - }; - - // Device & Queue groups - let (device_lock, queue_groups) = { - // TODO: This sucks, but hal is restrictive on how we can pass this specific argument. - let queue_families_specs_real: Vec<_> = queue_families_specs - .iter() - .map(|(qf, ns)| (*qf, ns.as_slice())) - .collect(); - - let gpu = unsafe { - adapter - .physical_device - .open(queue_families_specs_real.as_slice(), hal::Features::empty()) - .context("Error opening logical device")? - }; - - (Arc::new(RwLock::new(gpu.device)), gpu.queue_groups) - }; - - queue_negotiator.set_queue_groups(queue_groups); - - // Figure out what our swapchain will look like - let swapchain_properties = SwapchainProperties::find_best(&adapter, &surface) - .context("Error getting properties for swapchain")?; - - // Draw pass - let dp = idp - .init( - session, - &adapter, - device_lock.clone(), - &mut queue_negotiator, - &swapchain_properties, - ) - .context("Error initialising draw pass")?; - - // Lock device - let mut device = device_lock - .write() - .map_err(|_| LockPoisoned::Device) - .context("Error getting device lock")?; - - debug!("Detected swapchain properties: {:?}", swapchain_properties); - - // Command pool - let mut cmd_pool = unsafe { - device.create_command_pool( - queue_negotiator - .family::() - .ok_or(EnvironmentError::NoSuitableFamilies)?, - CommandPoolCreateFlags::RESET_INDIVIDUAL, - ) - } - .context("Error creating draw command pool")?; - - // Swapchain and associated resources - let target_chain = TargetChain::new( - &mut device, - &adapter, - surface, - &mut cmd_pool, - swapchain_properties, - ) - .context("Error creating target chain")?; - - // Unlock device - drop(device); - - Ok(RenderingContext { - instance: ManuallyDrop::new(instance), - - device: device_lock, - adapter, - - queue: queue_negotiator - .get_queue::() - .ok_or(EnvironmentError::NoQueues) - .context("Error getting draw queue")?, - - draw_pass: ManuallyDrop::new(dp), - target_chain: ManuallyDrop::new(target_chain), - cmd_pool: ManuallyDrop::new(cmd_pool), - - // pixels_per_point: window.scale_factor() as f32, - pixels_per_point: window.scale_factor() as f32, - }) - } - - /// If this function fails the whole context is probably dead - /// # Safety - /// The context must not be used while this is being called - pub unsafe fn handle_surface_change(&mut self) -> Result<()> { - let mut device = self - .device - .write() - .map_err(|_| LockPoisoned::Device) - .context("Error getting device lock")?; - - device - .wait_idle() - .context("Error waiting for device to become idle")?; - - let surface = ManuallyDrop::into_inner(read(&self.target_chain)) - .deactivate_with_recyling(&mut device, &mut self.cmd_pool); - - let properties = SwapchainProperties::find_best(&self.adapter, &surface) - .context("Error finding best swapchain properties")?; - - // TODO: Notify draw passes - - self.target_chain = ManuallyDrop::new( - TargetChain::new( - &mut device, - &self.adapter, - surface, - &mut self.cmd_pool, - properties, - ) - .context("Error creating target chain")?, - ); - Ok(()) - } - - /// Draw onto the next frame of the swapchain - pub fn draw_next_frame(&mut self, session: &Session) -> Result<()> { - let mut device = self - .device - .write() - .map_err(|_| LockPoisoned::Device) - .context("Error getting device lock")?; - let mut queue = self - .queue - .write() - .map_err(|_| LockPoisoned::Queue) - .context("Error getting draw queue lock")?; - - // Level draw pass - self.target_chain - .do_draw_with(&mut device, &mut queue, &mut *self.draw_pass, session) - .context("Error preparing next target")?; - - Ok(()) - } -} - -impl core::ops::Drop for RenderingContext { - fn drop(&mut self) { - { - self.device.write().unwrap().wait_idle().unwrap(); - } - - unsafe { - let mut device = self.device.write().unwrap(); - - ManuallyDrop::into_inner(read(&self.target_chain)).deactivate( - &mut self.instance, - &mut device, - &mut self.cmd_pool, - ); - - device.destroy_command_pool(ManuallyDrop::into_inner(read(&self.cmd_pool))); - } - } -} diff --git a/stockton-render/src/draw/data/stockton.frag b/stockton-render/src/draw/data/stockton.frag deleted file mode 100644 index 336d9fe..0000000 --- a/stockton-render/src/draw/data/stockton.frag +++ /dev/null @@ -1,15 +0,0 @@ -#version 450 - -// DescriptorSet 0 = Textures -layout(set = 0, binding = 0) uniform texture2D tex[8]; -layout(set = 0, binding = 1) uniform sampler samp[8]; - -layout (location = 1) in vec2 frag_uv; -layout (location = 2) in flat int frag_tex; - -layout (location = 0) out vec4 color; - -void main() -{ - color = texture(sampler2D(tex[frag_tex % 8], samp[frag_tex % 8]), frag_uv); -} \ No newline at end of file diff --git a/stockton-render/src/draw/data/stockton.vert b/stockton-render/src/draw/data/stockton.vert deleted file mode 100644 index aaee1a5..0000000 --- a/stockton-render/src/draw/data/stockton.vert +++ /dev/null @@ -1,23 +0,0 @@ -#version 450 - -// DescriptorSet 0 = Matrices -layout (push_constant) uniform PushConsts { - mat4 vp; -} push; - -layout (location = 0) in vec3 position; -layout (location = 1) in int tex; -layout (location = 2) in vec2 uv; - -out gl_PerVertex { - vec4 gl_Position; -}; -layout (location = 1) out vec2 frag_uv; -layout (location = 2) out flat int frag_tex; - -void main() -{ - gl_Position = push.vp * vec4(position, 1.0); - frag_uv = uv; - frag_tex = tex; -} \ No newline at end of file diff --git a/stockton-render/src/draw/draw_passes/cons.rs b/stockton-render/src/draw/draw_passes/cons.rs deleted file mode 100644 index 3209806..0000000 --- a/stockton-render/src/draw/draw_passes/cons.rs +++ /dev/null @@ -1,74 +0,0 @@ -//! Code for using multiple draw passes in place of just one -//! Note that this can be extended to an arbitrary amount of draw passes. - -use std::sync::{Arc, RwLock}; - -use super::{DrawPass, IntoDrawPass}; -use crate::types::*; -use stockton_types::Session; - -use anyhow::Result; - -/// One draw pass, then another. -pub struct ConsDrawPass { - pub a: A, - pub b: B, -} - -impl DrawPass for ConsDrawPass { - fn queue_draw( - &mut self, - session: &Session, - img_view: &ImageViewT, - cmd_buffer: &mut CommandBufferT, - ) -> Result<()> { - self.a.queue_draw(session, img_view, cmd_buffer)?; - self.b.queue_draw(session, img_view, cmd_buffer)?; - - Ok(()) - } - - fn deactivate(self, device: &mut Arc>) -> Result<()> { - self.a.deactivate(device)?; - self.b.deactivate(device) - } -} - -impl, IB: IntoDrawPass> - IntoDrawPass> for (IA, IB) -{ - fn init( - self, - session: &Session, - adapter: &Adapter, - device: Arc>, - queue_negotiator: &mut crate::draw::queue_negotiator::QueueNegotiator, - swapchain_properties: &crate::draw::target::SwapchainProperties, - ) -> Result> { - Ok(ConsDrawPass { - a: self.0.init( - session, - adapter, - device.clone(), - queue_negotiator, - swapchain_properties, - )?, - b: self.1.init( - session, - adapter, - device, - queue_negotiator, - swapchain_properties, - )?, - }) - } - - fn find_aux_queues<'a>( - adapter: &'a Adapter, - queue_negotiator: &mut crate::draw::queue_negotiator::QueueNegotiator, - ) -> Result)>> { - let mut v = IA::find_aux_queues(adapter, queue_negotiator)?; - v.extend(IB::find_aux_queues(adapter, queue_negotiator)?); - Ok(v) - } -} diff --git a/stockton-render/src/draw/draw_passes/level.rs b/stockton-render/src/draw/draw_passes/level.rs deleted file mode 100644 index 682c775..0000000 --- a/stockton-render/src/draw/draw_passes/level.rs +++ /dev/null @@ -1,478 +0,0 @@ -//! Minimal code for drawing any level, based on traits from stockton-levels - -use super::{util::TargetSpecificResources, DrawPass, IntoDrawPass}; -use crate::{ - draw::{ - buffers::{ - draw_buffers::{DrawBuffers, INITIAL_INDEX_SIZE, INITIAL_VERT_SIZE}, - DedicatedLoadedImage, ModifiableBuffer, - }, - builders::{ - pipeline::{ - CompletePipeline, PipelineSpecBuilder, VertexBufferSpec, - VertexPrimitiveAssemblerSpec, - }, - renderpass::RenderpassSpec, - shader::ShaderDesc, - }, - queue_negotiator::QueueNegotiator, - target::SwapchainProperties, - texture::{resolver::TextureResolver, TexLoadQueue, TextureLoadConfig, TextureRepo}, - }, - error::{EnvironmentError, LevelError, LockPoisoned}, - types::*, -}; -use hal::{ - buffer::SubRange, - command::{ClearColor, ClearDepthStencil, ClearValue, RenderAttachmentInfo, SubpassContents}, - format::{Aspects, Format}, - image::{ - Filter, FramebufferAttachment, Layout, SubresourceRange, Usage, ViewCapabilities, WrapMode, - }, - pass::{Attachment, AttachmentLoadOp, AttachmentOps, AttachmentStoreOp}, - pso::{ - BlendDesc, BlendOp, BlendState, ColorBlendDesc, ColorMask, Comparison, DepthStencilDesc, - DepthTest, Face, Factor, FrontFace, InputAssemblerDesc, LogicOp, PolygonMode, Primitive, - Rasterizer, ShaderStageFlags, State, VertexInputRate, - }, -}; -use legion::{Entity, IntoQuery}; -use shaderc::ShaderKind; -use stockton_levels::{ - features::MinRenderFeatures, - parts::{data::Geometry, IsFace}, -}; -use stockton_types::{ - components::{CameraSettings, CameraVPMatrix, Transform}, - *, -}; - -use std::{ - array::IntoIter, - convert::TryInto, - iter::{empty, once}, - marker::PhantomData, - sync::{Arc, RwLock}, -}; - -use anyhow::{Context, Result}; - -/// The Vertexes that go to the shader -#[derive(Debug, Clone, Copy)] -struct UvPoint(pub Vector3, pub i32, pub Vector2); - -/// Draw a level -pub struct LevelDrawPass<'a, M> { - pipeline: CompletePipeline, - repo: TextureRepo, - active_camera: Entity, - draw_buffers: DrawBuffers<'a, UvPoint>, - - framebuffers: TargetSpecificResources, - depth_buffers: TargetSpecificResources, - - _d: PhantomData, -} - -impl<'a, M> DrawPass for LevelDrawPass<'a, M> -where - M: for<'b> MinRenderFeatures<'b> + 'static, -{ - fn queue_draw( - &mut self, - session: &Session, - img_view: &ImageViewT, - cmd_buffer: &mut crate::types::CommandBufferT, - ) -> anyhow::Result<()> { - // We might have loaded more textures - self.repo.process_responses(); - - // Make sure we update the vertex buffers after they're written to, but before they're read from. - self.draw_buffers - .vertex_buffer - .record_commit_cmds(cmd_buffer)?; - self.draw_buffers - .index_buffer - .record_commit_cmds(cmd_buffer)?; - - // Get level & camera - let mut query = <(&Transform, &CameraSettings, &CameraVPMatrix)>::query(); - let (camera_transform, camera_settings, camera_vp) = query - .get(&session.world, self.active_camera) - .context("Couldn't find camera components")?; - let map_lock: Arc> = session.resources.get::>>().unwrap().clone(); - let map = map_lock.read().map_err(|_| LockPoisoned::Map)?; - - // Get framebuffer and depth buffer - let fb = self.framebuffers.get_next(); - let db = self.depth_buffers.get_next(); - - unsafe { - cmd_buffer.begin_render_pass( - &self.pipeline.renderpass, - fb, - self.pipeline.render_area, - vec![ - RenderAttachmentInfo { - image_view: img_view, - clear_value: ClearValue { - color: ClearColor { - float32: [0.0, 0.0, 0.0, 1.0], - }, - }, - }, - RenderAttachmentInfo { - image_view: &*db.image_view, - clear_value: ClearValue { - depth_stencil: ClearDepthStencil { - depth: 1.0, - stencil: 0, - }, - }, - }, - ] - .into_iter(), - SubpassContents::Inline, - ); - cmd_buffer.bind_graphics_pipeline(&self.pipeline.pipeline); - - // VP Matrix - let vp = &*(camera_vp.vp_matrix.data.as_slice() as *const [f32] as *const [u32]); - - cmd_buffer.push_graphics_constants( - &self.pipeline.pipeline_layout, - ShaderStageFlags::VERTEX, - 0, - vp, - ); - - // Bind buffers - cmd_buffer.bind_vertex_buffers( - 0, - once(( - self.draw_buffers.vertex_buffer.get_buffer(), - SubRange { - offset: 0, - size: None, - }, - )), - ); - cmd_buffer.bind_index_buffer( - self.draw_buffers.index_buffer.get_buffer(), - SubRange { - offset: 0, - size: None, - }, - hal::IndexType::U16, - ); - } - - // Get visible faces - let mut faces = map.get_visible(camera_transform, camera_settings); - - // Iterate over faces, copying them in and drawing groups that use the same texture chunk all at once. - let face = faces.next(); - if let Some(face) = face { - let mut face = map.get_face(face).ok_or(LevelError::BadReference)?; - let mut current_chunk = face.texture_idx(&map) as usize / 8; - let mut chunk_start = 0; - - let mut curr_vert_idx: usize = 0; - let mut curr_idx_idx: usize = 0; - loop { - if current_chunk != face.texture_idx(&map) as usize / 8 { - // Last index was last of group, so draw it all if textures are loaded. - draw_or_queue( - current_chunk, - &mut self.repo, - cmd_buffer, - &*self.pipeline.pipeline_layout, - chunk_start as u32, - curr_idx_idx as u32, - )?; - - // Next group of same-chunked faces starts here. - chunk_start = curr_idx_idx; - current_chunk = face.texture_idx(&map) as usize / 8; - } - - match face.geometry(&map) { - Geometry::Vertices(v1, v2, v3) => { - for v in &[v1, v2, v3] { - let uvp = - UvPoint(v.position, face.texture_idx(&map).try_into()?, v.tex); - - self.draw_buffers.vertex_buffer[curr_vert_idx] = uvp; - curr_vert_idx += 1; - } - self.draw_buffers.index_buffer[curr_idx_idx] = ( - curr_vert_idx as u16 - 3, - curr_vert_idx as u16 - 2, - curr_vert_idx as u16 - 1, - ); - curr_idx_idx += 1; - } - } - - if curr_vert_idx >= INITIAL_VERT_SIZE.try_into()? - || curr_idx_idx >= INITIAL_INDEX_SIZE.try_into()? - { - println!("out of vertex buffer space!"); - break; - } - - match faces.next() { - Some(x) => face = map.get_face(x).ok_or(LevelError::BadReference)?, - None => break, - }; - } - - // Draw the final group of chunks - draw_or_queue( - current_chunk, - &mut self.repo, - cmd_buffer, - &*self.pipeline.pipeline_layout, - chunk_start as u32, - curr_idx_idx as u32, - )?; - } - - unsafe { - cmd_buffer.end_render_pass(); - } - - Ok(()) - } - - fn deactivate(self, device_lock: &mut Arc>) -> Result<()> { - unsafe { - let mut device = device_lock.write().map_err(|_| LockPoisoned::Device)?; - self.pipeline.deactivate(&mut device); - self.draw_buffers.deactivate(&mut device); - for fb in self.framebuffers.dissolve() { - device.destroy_framebuffer(fb); - } - for db in self.depth_buffers.dissolve() { - db.deactivate(&mut device); - } - } - self.repo.deactivate(device_lock); - - Ok(()) - } -} - -pub struct LevelDrawPassConfig { - pub active_camera: Entity, - pub tex_resolver: R, -} - -impl<'a, M, R> IntoDrawPass> for LevelDrawPassConfig -where - M: for<'b> MinRenderFeatures<'b> + 'static, - R: TextureResolver + Send + Sync + 'static, -{ - fn init( - self, - _session: &Session, - adapter: &Adapter, - device_lock: Arc>, - queue_negotiator: &mut QueueNegotiator, - swapchain_properties: &SwapchainProperties, - ) -> Result> { - let spec = PipelineSpecBuilder::default() - .rasterizer(Rasterizer { - polygon_mode: PolygonMode::Fill, - cull_face: Face::BACK, - front_face: FrontFace::CounterClockwise, - depth_clamping: false, - depth_bias: None, - conservative: true, - line_width: State::Static(1.0), - }) - .depth_stencil(DepthStencilDesc { - depth: Some(DepthTest { - fun: Comparison::Less, - write: true, - }), - depth_bounds: false, - stencil: None, - }) - .blender(BlendDesc { - logic_op: Some(LogicOp::Copy), - targets: vec![ColorBlendDesc { - mask: ColorMask::ALL, - blend: Some(BlendState { - color: BlendOp::Add { - src: Factor::One, - dst: Factor::Zero, - }, - alpha: BlendOp::Add { - src: Factor::One, - dst: Factor::Zero, - }, - }), - }], - }) - .primitive_assembler(VertexPrimitiveAssemblerSpec::with_buffers( - InputAssemblerDesc::new(Primitive::TriangleList), - vec![VertexBufferSpec { - attributes: vec![Format::Rgb32Sfloat, Format::R32Sint, Format::Rg32Sfloat], - rate: VertexInputRate::Vertex, - }], - )) - .shader_vertex(ShaderDesc { - source: include_str!("../data/stockton.vert").to_string(), - entry: "main".to_string(), - kind: ShaderKind::Vertex, - }) - .shader_fragment(ShaderDesc { - source: include_str!("../data/stockton.frag").to_string(), - entry: "main".to_string(), - kind: ShaderKind::Fragment, - }) - .push_constants(vec![(ShaderStageFlags::VERTEX, 0..64)]) - .renderpass(RenderpassSpec { - colors: vec![Attachment { - format: Some(swapchain_properties.format), - samples: 1, - ops: AttachmentOps::new(AttachmentLoadOp::Clear, AttachmentStoreOp::Store), - stencil_ops: AttachmentOps::new( - AttachmentLoadOp::Clear, - AttachmentStoreOp::DontCare, - ), - layouts: Layout::ColorAttachmentOptimal..Layout::ColorAttachmentOptimal, - }], - depth: Some(Attachment { - format: Some(swapchain_properties.depth_format), - samples: 1, - ops: AttachmentOps::new(AttachmentLoadOp::Clear, AttachmentStoreOp::DontCare), - stencil_ops: AttachmentOps::new( - AttachmentLoadOp::DontCare, - AttachmentStoreOp::DontCare, - ), - layouts: Layout::Undefined..Layout::DepthStencilAttachmentOptimal, - }), - inputs: vec![], - resolves: vec![], - preserves: vec![], - }) - .build() - .context("Error building pipeline")?; - - let repo = TextureRepo::new( - device_lock.clone(), - queue_negotiator - .family::() - .ok_or(EnvironmentError::NoSuitableFamilies) - .context("Error finding texture queue")?, - queue_negotiator - .get_queue::() - .ok_or(EnvironmentError::NoQueues) - .context("Error finding texture queue")?, - adapter, - TextureLoadConfig { - resolver: self.tex_resolver, - filter: Filter::Linear, - wrap_mode: WrapMode::Tile, - }, - ) - .context("Error creating texture repo")?; - - let (draw_buffers, pipeline, framebuffers, depth_buffers) = { - let mut device = device_lock.write().map_err(|_| LockPoisoned::Device)?; - let draw_buffers = - DrawBuffers::new(&mut device, adapter).context("Error creating draw buffers")?; - let pipeline = spec - .build( - &mut device, - swapchain_properties.extent, - swapchain_properties, - once(&*repo.get_ds_layout()?), - ) - .context("Error building pipeline")?; - - let fat = swapchain_properties.framebuffer_attachment(); - let dat = FramebufferAttachment { - usage: Usage::DEPTH_STENCIL_ATTACHMENT, - format: swapchain_properties.depth_format, - view_caps: ViewCapabilities::empty(), - }; - let framebuffers = TargetSpecificResources::new( - || unsafe { - Ok(device.create_framebuffer( - &pipeline.renderpass, - IntoIter::new([fat.clone(), dat.clone()]), - swapchain_properties.extent, - )?) - }, - swapchain_properties.image_count as usize, - )?; - let depth_buffers = TargetSpecificResources::new( - || { - DedicatedLoadedImage::new( - &mut device, - adapter, - swapchain_properties.depth_format, - Usage::DEPTH_STENCIL_ATTACHMENT, - SubresourceRange { - aspects: Aspects::DEPTH, - level_start: 0, - level_count: Some(1), - layer_start: 0, - layer_count: Some(1), - }, - swapchain_properties.extent.width as usize, - swapchain_properties.extent.height as usize, - ) - .context("Error creating depth buffer") - }, - swapchain_properties.image_count as usize, - )?; - - (draw_buffers, pipeline, framebuffers, depth_buffers) - }; - - Ok(LevelDrawPass { - pipeline, - repo, - draw_buffers, - active_camera: self.active_camera, - _d: PhantomData, - framebuffers, - depth_buffers, - }) - } - - fn find_aux_queues<'c>( - adapter: &'c Adapter, - queue_negotiator: &mut QueueNegotiator, - ) -> Result)>> { - queue_negotiator.find(adapter, &TexLoadQueue)?; - - Ok(vec![queue_negotiator - .family_spec::(&adapter.queue_families, 1) - .ok_or(EnvironmentError::NoSuitableFamilies)?]) - } -} - -fn draw_or_queue( - current_chunk: usize, - tex_repo: &mut TextureRepo, - cmd_buffer: &mut CommandBufferT, - pipeline_layout: &PipelineLayoutT, - chunk_start: u32, - curr_idx_idx: u32, -) -> Result<()> { - if let Some(ds) = tex_repo.attempt_get_descriptor_set(current_chunk) { - unsafe { - cmd_buffer.bind_graphics_descriptor_sets(pipeline_layout, 0, once(ds), empty()); - cmd_buffer.draw_indexed(chunk_start * 3..(curr_idx_idx * 3) + 1, 0, 0..1); - } - } else { - tex_repo.queue_load(current_chunk)? - } - - Ok(()) -} diff --git a/stockton-render/src/draw/draw_passes/mod.rs b/stockton-render/src/draw/draw_passes/mod.rs deleted file mode 100644 index 8d1d59b..0000000 --- a/stockton-render/src/draw/draw_passes/mod.rs +++ /dev/null @@ -1,52 +0,0 @@ -//! Traits and common draw passes. -use super::{queue_negotiator::QueueNegotiator, target::SwapchainProperties}; -use crate::types::*; -use stockton_types::Session; - -use std::sync::{Arc, RwLock}; - -use anyhow::Result; - -mod cons; -mod level; -mod ui; -pub mod util; - -pub use cons::ConsDrawPass; -pub use level::{LevelDrawPass, LevelDrawPassConfig}; -pub use ui::UiDrawPass; - -/// One of several 'passes' that draw on each frame. -pub trait DrawPass { - /// Queue any necessary draw commands to cmd_buffer - /// This should assume the command buffer isn't in the middle of a renderpass, and should leave it as such. - fn queue_draw( - &mut self, - session: &Session, - img_view: &ImageViewT, - cmd_buffer: &mut CommandBufferT, - ) -> Result<()>; - - fn deactivate(self, device: &mut Arc>) -> Result<()>; -} - -/// A type that can be made into a specific draw pass type. -/// This allows extra data to be used in initialisation without the Renderer needing to worry about it. -pub trait IntoDrawPass { - fn init( - self, - session: &Session, - adapter: &Adapter, - device: Arc>, - queue_negotiator: &mut QueueNegotiator, - swapchain_properties: &SwapchainProperties, - ) -> Result; - - /// This function should ask the queue negotatior to find families for any auxilary operations this draw pass needs to perform - /// For example, .find(&TexLoadQueue) - /// It should return then call .family_spec for each queue type negotiated and return the results. - fn find_aux_queues<'a>( - adapter: &'a Adapter, - queue_negotiator: &mut QueueNegotiator, - ) -> Result)>>; -} diff --git a/stockton-render/src/draw/draw_passes/ui.rs b/stockton-render/src/draw/draw_passes/ui.rs deleted file mode 100644 index 5e6c68e..0000000 --- a/stockton-render/src/draw/draw_passes/ui.rs +++ /dev/null @@ -1,348 +0,0 @@ -//! Minimal code for drawing any level, based on traits from stockton-levels - -use super::{util::TargetSpecificResources, DrawPass, IntoDrawPass}; -use crate::{ - draw::{ - buffers::{draw_buffers::DrawBuffers, ModifiableBuffer}, - builders::{ - pipeline::{ - CompletePipeline, PipelineSpecBuilder, VertexBufferSpec, - VertexPrimitiveAssemblerSpec, - }, - renderpass::RenderpassSpec, - shader::ShaderDesc, - }, - queue_negotiator::QueueNegotiator, - target::SwapchainProperties, - texture::{TexLoadQueue, TextureLoadConfig, TextureRepo}, - ui::UiTextures, - }, - error::{EnvironmentError, LockPoisoned}, - types::*, - UiState, -}; -use egui::{ClippedMesh, TextureId}; -use hal::{ - buffer::SubRange, - command::{ClearColor, ClearValue, RenderAttachmentInfo, SubpassContents}, - format::Format, - image::Layout, - pass::{Attachment, AttachmentLoadOp, AttachmentOps, AttachmentStoreOp}, - pso::{ - BlendDesc, BlendOp, BlendState, ColorBlendDesc, ColorMask, DepthStencilDesc, Face, Factor, - FrontFace, InputAssemblerDesc, LogicOp, PolygonMode, Primitive, Rasterizer, Rect, - ShaderStageFlags, State, VertexInputRate, - }, -}; -use shaderc::ShaderKind; -use stockton_types::{Session, Vector2}; - -use std::{ - array::IntoIter, - convert::TryInto, - iter::{empty, once}, - sync::{Arc, RwLock}, -}; - -use anyhow::{anyhow, Context, Result}; - -#[derive(Debug)] -pub struct UiPoint(pub Vector2, pub Vector2, pub [f32; 4]); - -/// Draw a Ui object -pub struct UiDrawPass<'a> { - pipeline: CompletePipeline, - repo: TextureRepo, - draw_buffers: DrawBuffers<'a, UiPoint>, - - framebuffers: TargetSpecificResources, -} - -impl<'a> DrawPass for UiDrawPass<'a> { - fn queue_draw( - &mut self, - session: &Session, - img_view: &ImageViewT, - cmd_buffer: &mut crate::types::CommandBufferT, - ) -> anyhow::Result<()> { - // We might have loaded more textures - self.repo.process_responses(); - - // Make sure we update the vertex buffers after they're written to, but before they're read from. - self.draw_buffers - .vertex_buffer - .record_commit_cmds(cmd_buffer)?; - self.draw_buffers - .index_buffer - .record_commit_cmds(cmd_buffer)?; - - // Get level & camera - let ui: &mut UiState = &mut session.resources.get_mut::().unwrap(); - - // Get framebuffer and depth buffer - let fb = self.framebuffers.get_next(); - unsafe { - cmd_buffer.begin_render_pass( - &self.pipeline.renderpass, - fb, - self.pipeline.render_area, - vec![RenderAttachmentInfo { - image_view: img_view, - clear_value: ClearValue { - color: ClearColor { - float32: [0.0, 0.0, 0.0, 1.0], - }, - }, - }] - .into_iter(), - SubpassContents::Inline, - ); - cmd_buffer.bind_graphics_pipeline(&self.pipeline.pipeline); - - // Bind buffers - cmd_buffer.bind_vertex_buffers( - 0, - once(( - self.draw_buffers.vertex_buffer.get_buffer(), - SubRange { - offset: 0, - size: None, - }, - )), - ); - cmd_buffer.bind_index_buffer( - self.draw_buffers.index_buffer.get_buffer(), - SubRange { - offset: 0, - size: None, - }, - hal::IndexType::U16, - ); - } - - let (_out, shapes) = ui.end_frame(); - let screen = ui.dimensions().ok_or(anyhow!("UI not set up properly."))?; - let shapes = ui.ctx().tessellate(shapes); - - for ClippedMesh(rect, tris) in shapes.iter() { - assert!(tris.texture_id == TextureId::Egui); - - // Copy triangles/indicies - for i in (0..tris.indices.len()).step_by(3) { - self.draw_buffers.index_buffer[i / 3] = ( - tris.indices[i].try_into()?, - tris.indices[i + 1].try_into()?, - tris.indices[i + 2].try_into()?, - ); - } - for (i, vertex) in tris.vertices.iter().enumerate() { - self.draw_buffers.vertex_buffer[i] = UiPoint( - Vector2::new(vertex.pos.x, vertex.pos.y), - Vector2::new(vertex.uv.x, vertex.uv.y), - [ - vertex.color.r() as f32 / 255.0, - vertex.color.g() as f32 / 255.0, - vertex.color.b() as f32 / 255.0, - vertex.color.a() as f32 / 255.0, - ], - ); - } - // TODO: *Properly* deal with textures - if let Some(ds) = self.repo.attempt_get_descriptor_set(0) { - unsafe { - cmd_buffer.push_graphics_constants( - &self.pipeline.pipeline_layout, - ShaderStageFlags::VERTEX, - 0, - &[screen.x.to_bits(), screen.y.to_bits()], - ); - - cmd_buffer.set_scissors( - 0, - IntoIter::new([Rect { - x: rect.min.x as i16, - y: rect.min.y as i16, - w: rect.width() as i16, - h: rect.height() as i16, - }]), - ); - cmd_buffer.bind_graphics_descriptor_sets( - &self.pipeline.pipeline_layout, - 0, - IntoIter::new([ds]), - empty(), - ); - // Call draw - cmd_buffer.draw_indexed(0..tris.indices.len() as u32, 0, 0..1); - } - } else { - self.repo.queue_load(0)?; - } - } - - unsafe { - cmd_buffer.end_render_pass(); - } - - Ok(()) - } - - fn deactivate(self, device_lock: &mut Arc>) -> Result<()> { - unsafe { - let mut device = device_lock.write().map_err(|_| LockPoisoned::Device)?; - self.pipeline.deactivate(&mut device); - self.draw_buffers.deactivate(&mut device); - for fb in self.framebuffers.dissolve() { - device.destroy_framebuffer(fb); - } - } - self.repo.deactivate(device_lock); - - Ok(()) - } -} - -impl<'a> IntoDrawPass> for () { - fn init( - self, - session: &Session, - adapter: &Adapter, - device_lock: Arc>, - queue_negotiator: &mut QueueNegotiator, - swapchain_properties: &SwapchainProperties, - ) -> Result> { - let spec = PipelineSpecBuilder::default() - .rasterizer(Rasterizer { - polygon_mode: PolygonMode::Fill, - cull_face: Face::NONE, - front_face: FrontFace::CounterClockwise, - depth_clamping: false, - depth_bias: None, - conservative: true, - line_width: State::Static(1.0), - }) - .depth_stencil(DepthStencilDesc { - depth: None, - depth_bounds: false, - stencil: None, - }) - .blender(BlendDesc { - logic_op: Some(LogicOp::Copy), - targets: vec![ColorBlendDesc { - mask: ColorMask::ALL, - blend: Some(BlendState { - color: BlendOp::Add { - src: Factor::SrcAlpha, - dst: Factor::OneMinusSrcAlpha, - }, - alpha: BlendOp::Add { - src: Factor::SrcAlpha, - dst: Factor::OneMinusSrcAlpha, - }, - }), - }], - }) - .primitive_assembler(VertexPrimitiveAssemblerSpec::with_buffers( - InputAssemblerDesc::new(Primitive::TriangleList), - vec![VertexBufferSpec { - attributes: vec![Format::Rg32Sfloat, Format::Rg32Sfloat, Format::Rgba32Sfloat], - rate: VertexInputRate::Vertex, - }], - )) - .shader_vertex(ShaderDesc { - source: include_str!("../ui/data/stockton.vert").to_string(), - entry: "main".to_string(), - kind: ShaderKind::Vertex, - }) - .shader_fragment(ShaderDesc { - source: include_str!("../ui/data/stockton.frag").to_string(), - entry: "main".to_string(), - kind: ShaderKind::Fragment, - }) - .push_constants(vec![(ShaderStageFlags::VERTEX, 0..8)]) - .renderpass(RenderpassSpec { - colors: vec![Attachment { - format: Some(swapchain_properties.format), - samples: 1, - ops: AttachmentOps::new(AttachmentLoadOp::Load, AttachmentStoreOp::Store), - stencil_ops: AttachmentOps::new( - AttachmentLoadOp::DontCare, - AttachmentStoreOp::DontCare, - ), - layouts: Layout::ColorAttachmentOptimal..Layout::ColorAttachmentOptimal, - }], - depth: None, - inputs: vec![], - resolves: vec![], - preserves: vec![], - }) - .dynamic_scissor(true) - .build() - .context("Error building pipeline")?; - - let ui: &mut UiState = &mut session.resources.get_mut::().unwrap(); - let repo = TextureRepo::new( - device_lock.clone(), - queue_negotiator - .family::() - .ok_or(EnvironmentError::NoSuitableFamilies) - .context("Error finding texture queue")?, - queue_negotiator - .get_queue::() - .ok_or(EnvironmentError::NoQueues) - .context("Error finding texture queue")?, - adapter, - TextureLoadConfig { - resolver: UiTextures::new(ui.ctx().clone()), - filter: hal::image::Filter::Linear, - wrap_mode: hal::image::WrapMode::Clamp, - }, - ) - .context("Error creating texture repo")?; - - let (draw_buffers, pipeline, framebuffers) = { - let mut device = device_lock.write().map_err(|_| LockPoisoned::Device)?; - let draw_buffers = - DrawBuffers::new(&mut device, adapter).context("Error creating draw buffers")?; - let pipeline = spec - .build( - &mut device, - swapchain_properties.extent, - swapchain_properties, - once(&*repo.get_ds_layout()?), - ) - .context("Error building pipeline")?; - - let fat = swapchain_properties.framebuffer_attachment(); - let framebuffers = TargetSpecificResources::new( - || unsafe { - Ok(device.create_framebuffer( - &pipeline.renderpass, - IntoIter::new([fat.clone()]), - swapchain_properties.extent, - )?) - }, - swapchain_properties.image_count as usize, - )?; - (draw_buffers, pipeline, framebuffers) - }; - - Ok(UiDrawPass { - pipeline, - repo, - draw_buffers, - framebuffers, - }) - } - - fn find_aux_queues<'c>( - adapter: &'c Adapter, - queue_negotiator: &mut QueueNegotiator, - ) -> Result)>> { - queue_negotiator.find(adapter, &TexLoadQueue)?; - - Ok(vec![queue_negotiator - .family_spec::(&adapter.queue_families, 1) - .ok_or(EnvironmentError::NoSuitableFamilies)?]) - } -} diff --git a/stockton-render/src/draw/draw_passes/util.rs b/stockton-render/src/draw/draw_passes/util.rs deleted file mode 100644 index 5a4eb1a..0000000 --- a/stockton-render/src/draw/draw_passes/util.rs +++ /dev/null @@ -1,41 +0,0 @@ -//! Utility structs & functions - -use anyhow::Result; - -/// Keeps a given resource for each swapchain image -pub struct TargetSpecificResources { - elements: Vec, - next_idx: usize, -} - -impl TargetSpecificResources { - /// Create a new set of resources, given a function to generate them and the count - /// In most cases, count should be swapchain_properties.image_count - pub fn new(mut generator: F, count: usize) -> Result - where - F: FnMut() -> Result, - { - let mut elements = Vec::with_capacity(count); - for _ in 0..count { - elements.push(generator()?); - } - - Ok(TargetSpecificResources { - elements, - next_idx: 0, - }) - } - - /// Get the next resource, wrapping around if necessary. - pub fn get_next<'a>(&'a mut self) -> &'a T { - let el = &self.elements[self.next_idx]; - self.next_idx = (self.next_idx + 1) % self.elements.len(); - el - } - - /// Dissolve the resource set, returning an iterator over each item. - /// In most cases, each item will need deactivated. - pub fn dissolve(self) -> impl Iterator { - self.elements.into_iter() - } -} diff --git a/stockton-render/src/draw/mod.rs b/stockton-render/src/draw/mod.rs deleted file mode 100644 index 4ba38cd..0000000 --- a/stockton-render/src/draw/mod.rs +++ /dev/null @@ -1,17 +0,0 @@ -//! Given 3D points and some camera information, renders to the screen. - -pub mod target; - -mod buffers; -mod builders; -pub mod camera; -mod context; -pub mod draw_passes; -mod queue_negotiator; -pub mod texture; -mod ui; -mod utils; - -pub use self::context::RenderingContext; - -pub use self::draw_passes::*; diff --git a/stockton-render/src/draw/queue_negotiator.rs b/stockton-render/src/draw/queue_negotiator.rs deleted file mode 100644 index 65c7aa4..0000000 --- a/stockton-render/src/draw/queue_negotiator.rs +++ /dev/null @@ -1,135 +0,0 @@ -use crate::{error::EnvironmentError, types::*}; - -use anyhow::{Error, Result}; -use hal::queue::family::QueueFamilyId; -use std::{ - any::TypeId, - collections::HashMap, - sync::{Arc, RwLock}, -}; - -/// Used to find appropriate queue families and share queues from them as needed. -pub struct QueueNegotiator { - family_ids: HashMap, - already_allocated: HashMap>>, usize)>, - all: Vec, -} - -/// Can be used to select a specific queue family -pub trait QueueFamilySelector: 'static { - /// Check if the given family is suitable - fn is_suitable(&self, family: &QueueFamilyT) -> bool; -} - -impl QueueNegotiator { - pub fn new() -> Self { - QueueNegotiator { - family_ids: HashMap::new(), - already_allocated: HashMap::new(), - all: vec![], - } - } - - pub fn find(&mut self, adapter: &Adapter, filter: &T) -> Result<()> { - if self.family_ids.contains_key(&TypeId::of::()) { - return Ok(()); - } - - let candidates: Vec<&QueueFamilyT> = adapter - .queue_families - .iter() - .filter(|x| filter.is_suitable(*x)) - .collect(); - - if candidates.is_empty() { - return Err(Error::new(EnvironmentError::NoSuitableFamilies)); - } - - // Prefer using unique families - let family = match candidates - .iter() - .find(|x| !self.family_ids.values().any(|y| *y == x.id())) - { - Some(x) => *x, - None => candidates[0], - }; - - self.family_ids.insert(TypeId::of::(), family.id()); - - Ok(()) - } - - pub fn set_queue_groups(&mut self, queue_groups: Vec) { - self.all = queue_groups - } - - pub fn get_queue(&mut self) -> Option>> { - let tid = TypeId::of::(); - let family_id = self.family_ids.get(&tid)?; - log::debug!("{:?}", self.all); - log::debug!("{:?}", self.already_allocated); - match self - .all - .iter() - .position(|x| !x.queues.is_empty() && x.family == *family_id) - { - Some(idx) => { - // At least one remaining queue - let queue = self.all[idx].queues.pop().unwrap(); - let queue = Arc::new(RwLock::new(queue)); - - self.add_to_allocated::(queue.clone()); - - Some(queue) - } - None => match self.already_allocated.get_mut(&tid) { - Some((queues, next_share)) => { - let queue = (&queues[*next_share]).clone(); - - *next_share += 1; - - Some(queue) - } - None => None, - }, - } - } - - pub fn family(&self) -> Option { - self.family_ids.get(&TypeId::of::()).cloned() - } - - fn add_to_allocated(&mut self, queue: Arc>) { - let tid = TypeId::of::(); - match self.already_allocated.get_mut(&tid) { - None => { - self.already_allocated.insert(tid, (vec![queue], 0)); - } - Some(x) => { - x.0.push(queue); - } - } - } - - pub fn family_spec<'a, T: QueueFamilySelector>( - &self, - queue_families: &'a Vec, - count: usize, - ) -> Option<(&'a QueueFamilyT, Vec)> { - let qf_id = self.family::()?; - - let qf = queue_families.iter().find(|x| x.id() == qf_id)?; - let v = vec![1.0; count]; - - Some((qf, v)) - } -} - -pub struct DrawQueue { - pub surface: SurfaceT, -} -impl QueueFamilySelector for DrawQueue { - fn is_suitable(&self, family: &QueueFamilyT) -> bool { - self.surface.supports_queue_family(family) && family.queue_type().supports_graphics() - } -} diff --git a/stockton-render/src/draw/target.rs b/stockton-render/src/draw/target.rs deleted file mode 100644 index 3861192..0000000 --- a/stockton-render/src/draw/target.rs +++ /dev/null @@ -1,392 +0,0 @@ -//! Resources needed for drawing on the screen, including sync objects - -use std::{ - borrow::Borrow, - iter::{empty, once}, - mem::ManuallyDrop, -}; - -use hal::{ - command::CommandBufferFlags, - format::{Aspects, ChannelType, Format, ImageFeature}, - image::{ - Access, Extent, FramebufferAttachment, Layout, SubresourceRange, Usage as ImgUsage, - ViewCapabilities, - }, - memory::{Barrier, Dependencies}, - pso::{PipelineStage, Viewport}, - window::{CompositeAlphaMode, Extent2D, PresentMode, SwapchainConfig}, -}; - -use super::draw_passes::DrawPass; -use crate::{error::EnvironmentError, types::*}; -use anyhow::{Context, Result}; -use stockton_types::Session; - -#[derive(Debug, Clone)] -pub struct SwapchainProperties { - pub format: Format, - pub depth_format: Format, - pub present_mode: PresentMode, - pub composite_alpha_mode: CompositeAlphaMode, - pub viewport: Viewport, - pub extent: Extent, - pub image_count: u32, -} - -impl SwapchainProperties { - pub fn find_best( - adapter: &Adapter, - surface: &SurfaceT, - ) -> Result { - let caps = surface.capabilities(&adapter.physical_device); - let formats = surface.supported_formats(&adapter.physical_device); - - // Find which settings we'll actually use based on preset preferences - let format = match formats { - Some(formats) => formats - .iter() - .find(|format| format.base_format().1 == ChannelType::Srgb) - .copied() - .ok_or(EnvironmentError::ColorFormat), - None => Ok(Format::Rgba8Srgb), - }?; - - let depth_format = *[ - Format::D32SfloatS8Uint, - Format::D24UnormS8Uint, - Format::D32Sfloat, - ] - .iter() - .find(|format| { - format.is_depth() - && adapter - .physical_device - .format_properties(Some(**format)) - .optimal_tiling - .contains(ImageFeature::DEPTH_STENCIL_ATTACHMENT) - }) - .ok_or(EnvironmentError::DepthFormat)?; - - let present_mode = [ - PresentMode::MAILBOX, - PresentMode::FIFO, - PresentMode::RELAXED, - PresentMode::IMMEDIATE, - ] - .iter() - .cloned() - .find(|pm| caps.present_modes.contains(*pm)) - .ok_or(EnvironmentError::PresentMode)?; - - let composite_alpha_mode = [ - CompositeAlphaMode::OPAQUE, - CompositeAlphaMode::INHERIT, - CompositeAlphaMode::PREMULTIPLIED, - CompositeAlphaMode::POSTMULTIPLIED, - ] - .iter() - .cloned() - .find(|ca| caps.composite_alpha_modes.contains(*ca)) - .ok_or(EnvironmentError::CompositeAlphaMode)?; - - let extent = caps.extents.end().to_extent(); // Size - let viewport = Viewport { - rect: extent.rect(), - depth: 0.0..1.0, - }; - - Ok(SwapchainProperties { - format, - depth_format, - present_mode, - composite_alpha_mode, - extent, - viewport, - image_count: if present_mode == PresentMode::MAILBOX { - ((*caps.image_count.end()) - 1).min((*caps.image_count.start()).max(3)) - } else { - ((*caps.image_count.end()) - 1).min((*caps.image_count.start()).max(2)) - }, - }) - } - - pub fn framebuffer_attachment(&self) -> FramebufferAttachment { - FramebufferAttachment { - usage: ImgUsage::COLOR_ATTACHMENT, - format: self.format, - view_caps: ViewCapabilities::empty(), - } - } -} - -pub struct TargetChain { - /// Surface we're targeting - pub surface: ManuallyDrop, - pub properties: SwapchainProperties, - - /// Resources tied to each target frame in the swapchain - pub targets: Box<[TargetResources]>, - - /// Sync objects used in drawing - /// These are seperated from the targets because we don't necessarily always match up indexes - pub sync_objects: Box<[SyncObjects]>, - - /// The last set of sync objects used - last_syncs: usize, - - /// Last image index of the swapchain drawn to - last_image: u32, -} - -impl TargetChain { - pub fn new( - device: &mut DeviceT, - adapter: &Adapter, - mut surface: SurfaceT, - cmd_pool: &mut CommandPoolT, - properties: SwapchainProperties, - ) -> Result { - let caps = surface.capabilities(&adapter.physical_device); - - // Number of frames to pre-render - let image_count = if properties.present_mode == PresentMode::MAILBOX { - ((*caps.image_count.end()) - 1).min((*caps.image_count.start()).max(3)) - } else { - ((*caps.image_count.end()) - 1).min((*caps.image_count.start()).max(2)) - }; - - // Swap config - let swap_config = SwapchainConfig { - present_mode: properties.present_mode, - composite_alpha_mode: properties.composite_alpha_mode, - format: properties.format, - extent: Extent2D { - width: properties.extent.width, - height: properties.extent.height, - }, - image_count, - image_layers: 1, - image_usage: ImgUsage::COLOR_ATTACHMENT, - }; - - let _fat = swap_config.framebuffer_attachment(); - let mut targets: Vec = - Vec::with_capacity(swap_config.image_count as usize); - let mut sync_objects: Vec = - Vec::with_capacity(swap_config.image_count as usize); - - for _ in 0..swap_config.image_count { - targets.push( - TargetResources::new(device, cmd_pool, &properties) - .context("Error creating target resources")?, - ); - - sync_objects.push(SyncObjects::new(device).context("Error creating sync objects")?); - } - - // Configure Swapchain - unsafe { - surface - .configure_swapchain(device, swap_config) - .context("Error configuring swapchain")?; - } - - Ok(TargetChain { - surface: ManuallyDrop::new(surface), - targets: targets.into_boxed_slice(), - sync_objects: sync_objects.into_boxed_slice(), - properties, - last_syncs: (image_count - 1) as usize, // This means the next one to be used is index 0 - last_image: 0, - }) - } - - pub fn deactivate( - self, - instance: &mut InstanceT, - device: &mut DeviceT, - cmd_pool: &mut CommandPoolT, - ) { - let surface = self.deactivate_with_recyling(device, cmd_pool); - - unsafe { - instance.destroy_surface(surface); - } - } - - pub fn deactivate_with_recyling( - mut self, - device: &mut DeviceT, - cmd_pool: &mut CommandPoolT, - ) -> SurfaceT { - use core::ptr::read; - unsafe { - for i in 0..self.targets.len() { - read(&self.targets[i]).deactivate(device, cmd_pool); - } - - for i in 0..self.sync_objects.len() { - read(&self.sync_objects[i]).deactivate(device); - } - - self.surface.unconfigure_swapchain(device); - } - - unsafe { ManuallyDrop::into_inner(read(&self.surface)) } - } - - pub fn do_draw_with<'a, DP: DrawPass>( - &'a mut self, - device: &mut DeviceT, - command_queue: &mut QueueT, - dp: &mut DP, - session: &Session, - ) -> Result<()> { - self.last_syncs = (self.last_syncs + 1) % self.sync_objects.len(); - self.last_image = (self.last_image + 1) % self.targets.len() as u32; - - let syncs = &mut self.sync_objects[self.last_syncs]; - let target = &mut self.targets[self.last_image as usize]; - - // Get the image - let (img, _) = unsafe { - self.surface - .acquire_image(core::u64::MAX) - .context("Error getting image from swapchain")? - }; - - // Make sure whatever was last using this has finished - unsafe { - device - .wait_for_fence(&syncs.present_complete, core::u64::MAX) - .context("Error waiting for present_complete")?; - device - .reset_fence(&mut syncs.present_complete) - .context("Error resetting present_complete fence")?; - }; - - // Record commands - unsafe { - target.cmd_buffer.begin_primary(CommandBufferFlags::empty()); - - target.cmd_buffer.pipeline_barrier( - PipelineStage::TOP_OF_PIPE..PipelineStage::TOP_OF_PIPE, - Dependencies::empty(), - once(Barrier::Image { - states: (Access::empty(), Layout::Undefined) - ..(Access::empty(), Layout::ColorAttachmentOptimal), - target: img.borrow(), - range: SubresourceRange { - aspects: Aspects::COLOR, - level_start: 0, - level_count: Some(1), - layer_start: 0, - layer_count: Some(1), - }, - families: None, - }), - ); - - dp.queue_draw(session, img.borrow(), &mut target.cmd_buffer) - .context("Error in draw pass")?; - - target.cmd_buffer.pipeline_barrier( - PipelineStage::BOTTOM_OF_PIPE..PipelineStage::BOTTOM_OF_PIPE, - Dependencies::empty(), - once(Barrier::Image { - states: (Access::empty(), Layout::ColorAttachmentOptimal) - ..(Access::empty(), Layout::Present), - target: img.borrow(), - range: SubresourceRange { - aspects: Aspects::COLOR, - level_start: 0, - level_count: Some(1), - layer_start: 0, - layer_count: Some(1), - }, - families: None, - }), - ); - - target.cmd_buffer.finish(); - } - - // Submit it - unsafe { - command_queue.submit( - once(&*target.cmd_buffer), - empty(), - once(&*syncs.render_complete), - Some(&mut syncs.present_complete), - ); - command_queue - .present(&mut self.surface, img, Some(&mut *syncs.render_complete)) - .context("Error presenting to surface")?; - }; - - Ok(()) - } -} - -/// Resources for a single target frame, including sync objects -pub struct TargetResources { - /// Command buffer to use when drawing - pub cmd_buffer: ManuallyDrop, -} - -impl TargetResources { - pub fn new( - _device: &mut DeviceT, - cmd_pool: &mut CommandPoolT, - _properties: &SwapchainProperties, - ) -> Result { - // Command Buffer - let cmd_buffer = unsafe { cmd_pool.allocate_one(hal::command::Level::Primary) }; - - Ok(TargetResources { - cmd_buffer: ManuallyDrop::new(cmd_buffer), - }) - } - - pub fn deactivate(self, _device: &mut DeviceT, cmd_pool: &mut CommandPoolT) { - use core::ptr::read; - unsafe { - cmd_pool.free(once(ManuallyDrop::into_inner(read(&self.cmd_buffer)))); - } - } -} - -pub struct SyncObjects { - /// Triggered when rendering is done - pub render_complete: ManuallyDrop, - - /// Triggered when the image is on screen - pub present_complete: ManuallyDrop, -} - -impl SyncObjects { - pub fn new(device: &mut DeviceT) -> Result { - // Sync objects - let render_complete = device - .create_semaphore() - .context("Error creating render_complete semaphore")?; - let present_complete = device - .create_fence(true) - .context("Error creating present_complete fence")?; - - Ok(SyncObjects { - render_complete: ManuallyDrop::new(render_complete), - present_complete: ManuallyDrop::new(present_complete), - }) - } - - pub fn deactivate(self, device: &mut DeviceT) { - use core::ptr::read; - - unsafe { - device.destroy_semaphore(ManuallyDrop::into_inner(read(&self.render_complete))); - device.destroy_fence(ManuallyDrop::into_inner(read(&self.present_complete))); - } - } -} diff --git a/stockton-render/src/draw/texture/block.rs b/stockton-render/src/draw/texture/block.rs deleted file mode 100644 index 5ac3a94..0000000 --- a/stockton-render/src/draw/texture/block.rs +++ /dev/null @@ -1,62 +0,0 @@ -use super::{loader::BlockRef, repo::BLOCK_SIZE}; -use crate::types::*; - -use arrayvec::ArrayVec; -use rendy_memory::{Allocator, Block}; -use std::{iter::once, mem::ManuallyDrop}; - -pub struct TexturesBlock> { - pub id: BlockRef, - pub descriptor_set: ManuallyDrop, - pub imgs: ArrayVec<[LoadedImage; BLOCK_SIZE]>, -} - -impl> TexturesBlock { - pub fn deactivate>( - mut self, - device: &mut DeviceT, - tex_alloc: &mut T, - desc_alloc: &mut DescriptorAllocator, - ) { - unsafe { - use std::ptr::read; - - // Descriptor set - desc_alloc.free(once(read(&*self.descriptor_set))); - - // Images - self.imgs - .drain(..) - .map(|x| x.deactivate(device, tex_alloc)) - .for_each(|_| {}); - } - } -} - -pub struct LoadedImage> { - pub mem: ManuallyDrop, - pub img: ManuallyDrop, - pub img_view: ManuallyDrop, - pub sampler: ManuallyDrop, - pub row_size: usize, - pub height: u32, - pub width: u32, -} - -impl> LoadedImage { - pub fn deactivate>( - 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-render/src/draw/texture/image.rs b/stockton-render/src/draw/texture/image.rs deleted file mode 100644 index 0e272e9..0000000 --- a/stockton-render/src/draw/texture/image.rs +++ /dev/null @@ -1,41 +0,0 @@ -use super::PIXEL_SIZE; - -use core::ptr::copy_nonoverlapping; -use std::convert::TryInto; - -use image::RgbaImage; - -/// An object that can be loaded as an image into GPU memory -pub trait LoadableImage { - fn width(&self) -> u32; - fn height(&self) -> u32; - fn copy_row(&self, y: u32, ptr: *mut u8); - unsafe fn copy_into(&self, ptr: *mut u8, row_size: usize); -} - -impl LoadableImage for RgbaImage { - fn width(&self) -> u32 { - self.width() - } - - fn height(&self) -> u32 { - self.height() - } - - fn copy_row(&self, y: u32, ptr: *mut u8) { - let row_size_bytes = self.width() as usize * PIXEL_SIZE; - let raw: &Vec = self.as_raw(); - let row = &raw[y as usize * row_size_bytes..(y as usize + 1) * row_size_bytes]; - - unsafe { - copy_nonoverlapping(row.as_ptr(), ptr, row.len()); - } - } - - 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 deleted file mode 100644 index 1f33ad5..0000000 --- a/stockton-render/src/draw/texture/load.rs +++ /dev/null @@ -1,191 +0,0 @@ -use super::{ - block::LoadedImage, block::TexturesBlock, repo::BLOCK_SIZE, resolver::TextureResolver, - staging_buffer::StagingBuffer, LoadableImage, PIXEL_SIZE, -}; -use crate::types::*; - -use anyhow::{Context, Result}; -use arrayvec::ArrayVec; -use hal::{ - format::{Aspects, Format, Swizzle}, - image::{ - Filter, SamplerDesc, SubresourceLayers, SubresourceRange, Usage as ImgUsage, ViewKind, - WrapMode, - }, - memory::SparseFlags, - MemoryTypeId, -}; -use rendy_memory::{Allocator, Block}; -use std::mem::ManuallyDrop; -use thiserror::Error; - -#[derive(Error, Debug)] -pub enum TextureLoadError { - #[error("No available resources")] - NoResources, -} - -pub const FORMAT: Format = Format::Rgba8Srgb; -pub const RESOURCES: SubresourceRange = SubresourceRange { - aspects: Aspects::COLOR, - level_start: 0, - level_count: Some(1), - layer_start: 0, - layer_count: Some(1), -}; -pub const LAYERS: SubresourceLayers = SubresourceLayers { - aspects: Aspects::COLOR, - level: 0, - layers: 0..1, -}; - -pub struct TextureLoadConfig { - pub resolver: R, - pub filter: Filter, - pub wrap_mode: WrapMode, -} - -pub struct QueuedLoad> { - pub fence: FenceT, - pub buf: CommandBufferT, - pub block: TexturesBlock, - pub staging_bufs: ArrayVec<[StagingBuffer; BLOCK_SIZE]>, -} - -impl> QueuedLoad { - pub fn dissolve( - self, - ) -> ( - (FenceT, CommandBufferT), - ArrayVec<[StagingBuffer; BLOCK_SIZE]>, - TexturesBlock, - ) { - ((self.fence, self.buf), self.staging_bufs, self.block) - } -} - -pub fn tex_size_info(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( - device: &mut DeviceT, - allocator: &mut T, - format: Format, - usage: ImgUsage, - img: &I, -) -> Result<(T::Block, ImageT)> -where - T: Allocator, - I: LoadableImage, -{ - // Make the image - let mut image_ref = unsafe { - use hal::image::{Kind, Tiling, ViewCapabilities}; - - device.create_image( - Kind::D2(img.width(), img.height(), 1, 1), - 1, - format, - Tiling::Optimal, - usage, - SparseFlags::empty(), - ViewCapabilities::empty(), - ) - } - .context("Error creating image")?; - - // Allocate memory - let (block, _) = unsafe { - let requirements = device.get_image_requirements(&image_ref); - - allocator.alloc(device, requirements.size, requirements.alignment) - } - .context("Error allocating memory")?; - - unsafe { - device - .bind_image_memory(block.memory(), block.range().start, &mut image_ref) - .context("Error binding memory to image")?; - } - - Ok((block, image_ref)) -} - -pub unsafe fn load_image( - device: &mut DeviceT, - staging_allocator: &mut DynamicAllocator, - tex_allocator: &mut DynamicAllocator, - staging_memory_type: MemoryTypeId, - obcpa: u64, - img_data: I, - config: &TextureLoadConfig, -) -> Result<(StagingBuffer, LoadedImage)> { - // Calculate buffer size - let (row_size, total_size) = tex_size_info(&img_data, obcpa); - - // Create staging buffer - let mut staging_buffer = StagingBuffer::new( - device, - staging_allocator, - total_size as u64, - staging_memory_type, - ) - .context("Error creating staging buffer")?; - - // Write to staging buffer - let mapped_memory = staging_buffer - .map_memory(device) - .context("Error mapping staged memory")?; - - img_data.copy_into(mapped_memory, row_size); - - staging_buffer.unmap_memory(device); - - // Create image - let (img_mem, img) = create_image_view( - device, - tex_allocator, - FORMAT, - ImgUsage::SAMPLED | ImgUsage::TRANSFER_DST, - &img_data, - ) - .context("Error creating image")?; - - // Create image view - let img_view = device - .create_image_view( - &img, - ViewKind::D2, - FORMAT, - Swizzle::NO, - ImgUsage::SAMPLED | ImgUsage::TRANSFER_DST, - RESOURCES, - ) - .context("Error creating image view")?; - - // Create sampler - let sampler = device - .create_sampler(&SamplerDesc::new(config.filter, config.wrap_mode)) - .context("Error creating sampler")?; - - Ok(( - staging_buffer, - LoadedImage { - mem: ManuallyDrop::new(img_mem), - img: ManuallyDrop::new(img), - img_view: ManuallyDrop::new(img_view), - sampler: ManuallyDrop::new(sampler), - row_size, - height: img_data.height(), - width: img_data.width(), - }, - )) -} diff --git a/stockton-render/src/draw/texture/loader.rs b/stockton-render/src/draw/texture/loader.rs deleted file mode 100644 index e6c19db..0000000 --- a/stockton-render/src/draw/texture/loader.rs +++ /dev/null @@ -1,712 +0,0 @@ -//! Manages the loading/unloading of textures - -use super::{ - block::{LoadedImage, TexturesBlock}, - load::{load_image, QueuedLoad, TextureLoadConfig, TextureLoadError, LAYERS, RESOURCES}, - repo::BLOCK_SIZE, - resolver::TextureResolver, - PIXEL_SIZE, -}; -use crate::{draw::utils::find_memory_type_id, error::LockPoisoned, types::*}; - -use std::{ - array::IntoIter, - collections::VecDeque, - iter::{empty, once}, - mem::{drop, ManuallyDrop}, - sync::{ - mpsc::{Receiver, Sender}, - Arc, RwLock, - }, - thread::sleep, - time::Duration, -}; - -use anyhow::{Context, Result}; -use arrayvec::ArrayVec; -use hal::{ - command::{BufferImageCopy, CommandBufferFlags}, - format::{Aspects, Format}, - image::{Access, Extent, Layout, Offset, SubresourceLayers, SubresourceRange}, - memory::{Barrier, Dependencies, Properties as MemProps, SparseFlags}, - pso::{Descriptor, DescriptorSetWrite, ImageDescriptorType, PipelineStage, ShaderStageFlags}, - queue::family::QueueFamilyId, - MemoryTypeId, -}; -use image::{Rgba, RgbaImage}; -use log::*; -use rendy_descriptor::{DescriptorRanges, DescriptorSetLayoutBinding, DescriptorType}; -use rendy_memory::DynamicConfig; -use thiserror::Error; - -/// The number of command buffers to have in flight simultaneously. -pub const NUM_SIMULTANEOUS_CMDS: usize = 2; - -/// A reference to a texture of the current map -pub type BlockRef = usize; - -/// Manages the loading/unloading of textures -/// This is expected to load the textures, then send the loaded blocks back -pub struct TextureLoader { - /// Blocks for which commands have been queued and are done loading once the fence is triggered. - commands_queued: ArrayVec<[QueuedLoad; NUM_SIMULTANEOUS_CMDS]>, - - /// The command buffers used and a fence to go with them - buffers: VecDeque<(FenceT, CommandBufferT)>, - - /// The command pool buffers were allocated from - pool: ManuallyDrop, - - /// The GPU we're submitting to - device: Arc>, - - /// The command queue being used - queue: Arc>, - - /// The memory allocator being used for textures - tex_allocator: ManuallyDrop, - - /// The memory allocator for staging memory - staging_allocator: ManuallyDrop, - - /// Allocator for descriptor sets - descriptor_allocator: ManuallyDrop, - - ds_layout: Arc>, - - /// Type ID for staging memory - staging_memory_type: MemoryTypeId, - - /// From adapter, used for determining alignment - optimal_buffer_copy_pitch_alignment: hal::buffer::Offset, - - /// Configuration for how to find and load textures - config: TextureLoadConfig, - - /// The channel requests come in. - /// Requests should reference a texture **block**, for example textures 8..16 is block 1. - request_channel: Receiver, - - /// The channel blocks are returned to. - return_channel: Sender>, - - /// A filler image for descriptors that aren't needed but still need to be written to - blank_image: ManuallyDrop>, -} - -#[derive(Error, Debug)] -pub enum TextureLoaderError { - #[error("Couldn't find a suitable memory type")] - NoMemoryTypes, -} - -impl TextureLoader { - pub fn loop_until_exit(mut self) -> Result { - debug!("TextureLoader starting main loop"); - let mut res = Ok(false); - while res.is_ok() { - res = self.main(); - if let Ok(true) = res { - break; - } - - sleep(Duration::from_secs(0)); - } - - match res { - Ok(true) => { - debug!("Starting to deactivate TextureLoader"); - - Ok(self.deactivate()) - } - Err(r) => Err(r.context("Error in TextureLoader loop")), - _ => unreachable!(), - } - } - fn main(&mut self) -> Result { - let mut device = self - .device - .write() - .map_err(|_| LockPoisoned::Device) - .context("Error getting device lock")?; - // Check for blocks that are finished, then send them back - let mut i = 0; - while i < self.commands_queued.len() { - let signalled = unsafe { device.get_fence_status(&self.commands_queued[i].fence) } - .context("Error checking fence status")?; - - if signalled { - let (assets, mut staging_bufs, block) = self.commands_queued.remove(i).dissolve(); - debug!("Load finished for texture block {:?}", block.id); - - // Destroy staging buffers - for buf in staging_bufs.drain(..) { - buf.deactivate(&mut device, &mut self.staging_allocator); - } - - self.buffers.push_back(assets); - self.return_channel - .send(block) - .context("Error returning texture block")?; - } else { - i += 1; - } - } - - drop(device); - - // Check for messages to start loading blocks - let req_iter: Vec<_> = self.request_channel.try_iter().collect(); - for to_load in req_iter { - match to_load { - LoaderRequest::Load(to_load) => { - // Attempt to load given block - debug!("Attempting to queue load for texture block {:?}", to_load); - - let result = unsafe { self.attempt_queue_load(to_load) }; - match result { - Ok(queued_load) => self.commands_queued.push(queued_load), - Err(x) => match x.downcast_ref::() { - Some(TextureLoadError::NoResources) => { - debug!("No resources, trying again later"); - } - _ => return Err(x).context("Error queuing texture load"), - }, - } - } - LoaderRequest::End => return Ok(true), - } - } - - Ok(false) - } - - pub fn new( - adapter: &Adapter, - device_lock: Arc>, - family: QueueFamilyId, - queue_lock: Arc>, - ds_layout: Arc>, - request_channel: Receiver, - return_channel: Sender>, - config: TextureLoadConfig, - ) -> Result { - let mut device = device_lock - .write() - .map_err(|_| LockPoisoned::Device) - .context("Error getting device lock")?; - let device_props = adapter.physical_device.properties(); - - let type_mask = unsafe { - use hal::image::{Kind, Tiling, Usage, ViewCapabilities}; - - // We create an empty image with the same format as used for textures - // this is to get the type_mask required, which will stay the same for - // all colour images of the same tiling. (certain memory flags excluded). - - // Size and alignment don't necessarily stay the same, so we're forced to - // guess at the alignment for our allocator. - - // TODO: Way to tune these options - let img = device - .create_image( - Kind::D2(16, 16, 1, 1), - 1, - Format::Rgba8Srgb, - Tiling::Optimal, - Usage::SAMPLED, - SparseFlags::empty(), - ViewCapabilities::empty(), - ) - .context("Error creating test image to get buffer settings")?; - - let type_mask = device.get_image_requirements(&img).type_mask; - - device.destroy_image(img); - - type_mask - }; - - debug!("Using type mask {:?}", type_mask); - - // Tex Allocator - let mut tex_allocator = { - let props = MemProps::DEVICE_LOCAL; - - DynamicAllocator::new( - find_memory_type_id(adapter, type_mask, props) - .ok_or(TextureLoaderError::NoMemoryTypes) - .context("Couldn't create tex memory allocator")?, - props, - DynamicConfig { - block_size_granularity: 4 * 32 * 32, // 32x32 image - max_chunk_size: u64::pow(2, 63), - min_device_allocation: 4 * 32 * 32, - }, - device_props.limits.non_coherent_atom_size as u64, - ) - }; - - let (staging_memory_type, mut staging_allocator) = { - let props = MemProps::CPU_VISIBLE | MemProps::COHERENT; - let t = find_memory_type_id(adapter, u32::MAX, props) - .ok_or(TextureLoaderError::NoMemoryTypes) - .context("Couldn't create staging memory allocator")?; - ( - t, - DynamicAllocator::new( - t, - props, - DynamicConfig { - block_size_granularity: 4 * 32 * 32, // 32x32 image - max_chunk_size: u64::pow(2, 63), - min_device_allocation: 4 * 32 * 32, - }, - device_props.limits.non_coherent_atom_size as u64, - ), - ) - }; - - // Pool - let mut pool = unsafe { - use hal::pool::CommandPoolCreateFlags; - - device.create_command_pool(family, CommandPoolCreateFlags::RESET_INDIVIDUAL) - } - .context("Error creating command pool")?; - - // Command buffers and fences - debug!("Creating resources..."); - let mut buffers = { - let mut data = VecDeque::with_capacity(NUM_SIMULTANEOUS_CMDS); - - for _ in 0..NUM_SIMULTANEOUS_CMDS { - unsafe { - data.push_back(( - device.create_fence(false).context("Error creating fence")?, - pool.allocate_one(hal::command::Level::Primary), - )); - }; - } - - data - }; - - let optimal_buffer_copy_pitch_alignment = - device_props.limits.optimal_buffer_copy_pitch_alignment; - - let blank_image = unsafe { - Self::get_blank_image( - &mut device, - &mut buffers[0].1, - &queue_lock, - &mut staging_allocator, - &mut tex_allocator, - staging_memory_type, - optimal_buffer_copy_pitch_alignment, - &config, - ) - } - .context("Error creating blank image")?; - - drop(device); - - Ok(TextureLoader { - commands_queued: ArrayVec::new(), - buffers, - pool: ManuallyDrop::new(pool), - device: device_lock, - queue: queue_lock, - ds_layout, - - tex_allocator: ManuallyDrop::new(tex_allocator), - staging_allocator: ManuallyDrop::new(staging_allocator), - descriptor_allocator: ManuallyDrop::new(DescriptorAllocator::new()), - - staging_memory_type, - optimal_buffer_copy_pitch_alignment, - - request_channel, - return_channel, - config, - blank_image: ManuallyDrop::new(blank_image), - }) - } - - unsafe fn attempt_queue_load(&mut self, block_ref: usize) -> Result> { - let mut device = self - .device - .write() - .map_err(|_| LockPoisoned::Device) - .context("Error getting device lock")?; - - // Get assets to use - let (mut fence, mut buf) = self - .buffers - .pop_front() - .ok_or(TextureLoadError::NoResources) - .context("Error getting resources to use")?; - - // Create descriptor set - let mut descriptor_set = { - let mut v: ArrayVec<[RDescriptorSet; 1]> = ArrayVec::new(); - self.descriptor_allocator - .allocate( - &device, - &*self - .ds_layout - .read() - .map_err(|_| LockPoisoned::Other) - .context("Error reading descriptor set layout")?, - DescriptorRanges::from_bindings(&[ - DescriptorSetLayoutBinding { - binding: 0, - ty: DescriptorType::Image { - ty: ImageDescriptorType::Sampled { - with_sampler: false, - }, - }, - count: BLOCK_SIZE, - stage_flags: ShaderStageFlags::FRAGMENT, - immutable_samplers: false, - }, - DescriptorSetLayoutBinding { - binding: 1, - ty: DescriptorType::Sampler, - count: BLOCK_SIZE, - stage_flags: ShaderStageFlags::FRAGMENT, - immutable_samplers: false, - }, - ]), - 1, - &mut v, - ) - .context("Error creating descriptor set")?; - - v.pop().unwrap() - }; - - // Get a command buffer - buf.begin_primary(CommandBufferFlags::ONE_TIME_SUBMIT); - - let mut imgs: ArrayVec<[_; BLOCK_SIZE]> = ArrayVec::new(); - let mut staging_bufs: ArrayVec<[_; BLOCK_SIZE]> = ArrayVec::new(); - - // For each texture in block - for tex_idx in (block_ref * BLOCK_SIZE)..(block_ref + 1) * BLOCK_SIZE { - // Resolve texture - let img_data = self.config.resolver.resolve(tex_idx as u32); - if img_data.is_none() { - // Write a blank descriptor - device.write_descriptor_set(DescriptorSetWrite { - set: descriptor_set.raw_mut(), - binding: 0, - array_offset: tex_idx % BLOCK_SIZE, - descriptors: once(Descriptor::Image( - &*self.blank_image.img_view, - Layout::ShaderReadOnlyOptimal, - )), - }); - device.write_descriptor_set(DescriptorSetWrite { - set: descriptor_set.raw_mut(), - binding: 1, - array_offset: tex_idx % BLOCK_SIZE, - descriptors: once(Descriptor::Sampler(&*self.blank_image.sampler)), - }); - - continue; - } - - let img_data = img_data.unwrap(); - - let array_offset = tex_idx % BLOCK_SIZE; - - let (staging_buffer, img) = load_image( - &mut device, - &mut self.staging_allocator, - &mut self.tex_allocator, - self.staging_memory_type, - self.optimal_buffer_copy_pitch_alignment, - img_data, - &self.config, - )?; - - // Write to descriptor set - { - device.write_descriptor_set(DescriptorSetWrite { - set: descriptor_set.raw_mut(), - binding: 0, - array_offset, - descriptors: once(Descriptor::Image( - &*img.img_view, - Layout::ShaderReadOnlyOptimal, - )), - }); - device.write_descriptor_set(DescriptorSetWrite { - set: descriptor_set.raw_mut(), - binding: 1, - array_offset, - descriptors: once(Descriptor::Sampler(&*img.sampler)), - }); - } - - imgs.push(img); - - staging_bufs.push(staging_buffer); - } - - // Add start pipeline barrier - buf.pipeline_barrier( - PipelineStage::TOP_OF_PIPE..PipelineStage::TRANSFER, - Dependencies::empty(), - imgs.iter().map(|li| Barrier::Image { - states: (Access::empty(), Layout::Undefined) - ..(Access::TRANSFER_WRITE, Layout::TransferDstOptimal), - target: &*li.img, - families: None, - range: SubresourceRange { - aspects: Aspects::COLOR, - level_start: 0, - level_count: None, - layer_start: 0, - layer_count: None, - }, - }), - ); - - // Record copy commands - for (li, sb) in imgs.iter().zip(staging_bufs.iter()) { - buf.copy_buffer_to_image( - &*sb.buf, - &*li.img, - Layout::TransferDstOptimal, - once(BufferImageCopy { - buffer_offset: 0, - buffer_width: (li.row_size / super::PIXEL_SIZE) as u32, - buffer_height: li.height, - image_layers: SubresourceLayers { - aspects: Aspects::COLOR, - level: 0, - layers: 0..1, - }, - image_offset: Offset { x: 0, y: 0, z: 0 }, - image_extent: gfx_hal::image::Extent { - width: li.width, - height: li.height, - depth: 1, - }, - }), - ); - } - buf.pipeline_barrier( - PipelineStage::TRANSFER..PipelineStage::BOTTOM_OF_PIPE, - Dependencies::empty(), - imgs.iter().map(|li| Barrier::Image { - states: (Access::TRANSFER_WRITE, Layout::TransferDstOptimal) - ..(Access::empty(), Layout::ShaderReadOnlyOptimal), - target: &*li.img, - families: None, - range: RESOURCES, - }), - ); - - buf.finish(); - - // Submit command buffer - { - let mut queue = self.queue.write().map_err(|_| LockPoisoned::Queue)?; - - queue.submit(IntoIter::new([&buf]), empty(), empty(), Some(&mut fence)); - } - - Ok(QueuedLoad { - staging_bufs, - fence, - buf, - block: TexturesBlock { - id: block_ref, - imgs, - descriptor_set: ManuallyDrop::new(descriptor_set), - }, - }) - } - - unsafe fn get_blank_image( - device: &mut DeviceT, - buf: &mut CommandBufferT, - queue_lock: &Arc>, - staging_allocator: &mut DynamicAllocator, - tex_allocator: &mut DynamicAllocator, - staging_memory_type: MemoryTypeId, - obcpa: u64, - config: &TextureLoadConfig, - ) -> Result> { - let img_data = RgbaImage::from_pixel(1, 1, Rgba([255, 0, 255, 255])); - - let height = img_data.height(); - let width = img_data.width(); - let row_alignment_mask = obcpa as u32 - 1; - let initial_row_size = PIXEL_SIZE * img_data.width() as usize; - let row_size = - ((initial_row_size as u32 + row_alignment_mask) & !row_alignment_mask) as usize; - - let (staging_buffer, img) = load_image( - device, - staging_allocator, - tex_allocator, - staging_memory_type, - obcpa, - img_data, - config, - )?; - - buf.begin_primary(CommandBufferFlags::ONE_TIME_SUBMIT); - - buf.pipeline_barrier( - PipelineStage::TOP_OF_PIPE..PipelineStage::TRANSFER, - Dependencies::empty(), - once(Barrier::Image { - states: (Access::empty(), Layout::Undefined) - ..(Access::TRANSFER_WRITE, Layout::TransferDstOptimal), - target: &*img.img, - families: None, - range: SubresourceRange { - aspects: Aspects::COLOR, - level_start: 0, - level_count: None, - layer_start: 0, - layer_count: None, - }, - }), - ); - buf.copy_buffer_to_image( - &*staging_buffer.buf, - &*img.img, - Layout::TransferDstOptimal, - once(BufferImageCopy { - buffer_offset: 0, - buffer_width: (row_size / super::PIXEL_SIZE) as u32, - buffer_height: height, - image_layers: LAYERS, - image_offset: Offset { x: 0, y: 0, z: 0 }, - image_extent: Extent { - width, - height, - depth: 1, - }, - }), - ); - - buf.pipeline_barrier( - PipelineStage::TRANSFER..PipelineStage::BOTTOM_OF_PIPE, - Dependencies::empty(), - once(Barrier::Image { - states: (Access::TRANSFER_WRITE, Layout::TransferDstOptimal) - ..(Access::empty(), Layout::ShaderReadOnlyOptimal), - target: &*img.img, - families: None, - range: RESOURCES, - }), - ); - buf.finish(); - - let mut fence = device.create_fence(false).context("Error creating fence")?; - - { - let mut queue = queue_lock.write().map_err(|_| LockPoisoned::Queue)?; - - queue.submit( - IntoIter::new([buf as &CommandBufferT]), - empty(), - empty(), - Some(&mut fence), - ); - } - - device - .wait_for_fence(&fence, std::u64::MAX) - .context("Error waiting for copy")?; - - device.destroy_fence(fence); - - staging_buffer.deactivate(device, staging_allocator); - - Ok(img) - } - - /// Safely destroy all the vulkan stuff in this instance - /// Note that this returns the memory allocators, from which should be freed any TextureBlocks - /// All in-progress things are sent to return_channel. - fn deactivate(mut self) -> TextureLoaderRemains { - use std::ptr::read; - - let mut device = self.device.write().unwrap(); - - unsafe { - // Wait for any currently queued loads to be done - while self.commands_queued.len() > 0 { - let mut i = 0; - while i < self.commands_queued.len() { - let signalled = device - .get_fence_status(&self.commands_queued[i].fence) - .expect("Device lost by TextureManager"); - - if signalled { - // Destroy finished ones - let (assets, mut staging_bufs, block) = - self.commands_queued.remove(i).dissolve(); - - device.destroy_fence(assets.0); - // Command buffer will be freed when we reset the command pool - // Destroy staging buffers - for buf in staging_bufs.drain(..) { - buf.deactivate(&mut device, &mut self.staging_allocator); - } - - self.return_channel - .send(block) - .expect("Sending through return channel failed"); - } else { - i += 1; - } - } - - sleep(Duration::from_secs(0)); - } - - // Destroy blank image - read(&*self.blank_image).deactivate(&mut device, &mut *self.tex_allocator); - - // Destroy fences - - self.buffers - .drain(..) - .map(|(f, _)| device.destroy_fence(f)) - .for_each(|_| {}); - - // Free command pool - self.pool.reset(true); - device.destroy_command_pool(read(&*self.pool)); - - debug!("Done deactivating TextureLoader"); - - TextureLoaderRemains { - tex_allocator: ManuallyDrop::new(read(&*self.tex_allocator)), - descriptor_allocator: ManuallyDrop::new(read(&*self.descriptor_allocator)), - } - } - } -} - -pub struct TextureLoaderRemains { - pub tex_allocator: ManuallyDrop, - pub descriptor_allocator: ManuallyDrop, -} - -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 deleted file mode 100644 index aef1b03..0000000 --- a/stockton-render/src/draw/texture/mod.rs +++ /dev/null @@ -1,18 +0,0 @@ -//! Everything related to loading textures into GPU memory - -mod block; -mod image; -mod load; -mod loader; -mod repo; -pub mod resolver; -mod staging_buffer; - -pub use self::block::TexturesBlock; -pub use self::image::LoadableImage; -pub use self::load::TextureLoadConfig; -pub use self::loader::BlockRef; -pub use self::repo::{TexLoadQueue, TextureRepo}; - -/// The size of each pixel in an image -pub const PIXEL_SIZE: usize = std::mem::size_of::() * 4; diff --git a/stockton-render/src/draw/texture/repo.rs b/stockton-render/src/draw/texture/repo.rs deleted file mode 100644 index e427eef..0000000 --- a/stockton-render/src/draw/texture/repo.rs +++ /dev/null @@ -1,201 +0,0 @@ -use super::{ - block::TexturesBlock, - load::TextureLoadConfig, - loader::{BlockRef, LoaderRequest, TextureLoader, TextureLoaderRemains, NUM_SIMULTANEOUS_CMDS}, - resolver::TextureResolver, -}; -use crate::draw::queue_negotiator::QueueFamilySelector; -use crate::error::LockPoisoned; -use crate::types::*; - -use std::{ - array::IntoIter, - collections::HashMap, - iter::empty, - mem::ManuallyDrop, - sync::{ - mpsc::{channel, Receiver, Sender}, - Arc, RwLock, RwLockReadGuard, - }, - thread::JoinHandle, -}; - -use anyhow::{Context, Result}; -use hal::{ - pso::{DescriptorSetLayoutBinding, DescriptorType, ImageDescriptorType, ShaderStageFlags}, - queue::family::QueueFamilyId, -}; -use log::debug; - -/// The number of textures in one 'block' -/// The textures of the loaded file are divided into blocks of this size. -/// Whenever a texture is needed, the whole block its in is loaded. -pub const BLOCK_SIZE: usize = 8; - -pub struct TextureRepo { - joiner: ManuallyDrop>>, - ds_layout: Arc>, - req_send: Sender, - resp_recv: Receiver>, - blocks: HashMap>>, -} - -impl TextureRepo { - pub fn new( - device_lock: Arc>, - family: QueueFamilyId, - queue: Arc>, - adapter: &Adapter, - config: TextureLoadConfig, - ) -> Result { - // Create Channels - let (req_send, req_recv) = channel(); - let (resp_send, resp_recv) = channel(); - let device = device_lock - .write() - .map_err(|_| LockPoisoned::Device) - .context("Error getting device lock")?; - - // Create descriptor set layout - let ds_lock = Arc::new(RwLock::new( - unsafe { - device.create_descriptor_set_layout( - IntoIter::new([ - DescriptorSetLayoutBinding { - binding: 0, - ty: DescriptorType::Image { - ty: ImageDescriptorType::Sampled { - with_sampler: false, - }, - }, - count: BLOCK_SIZE, - stage_flags: ShaderStageFlags::FRAGMENT, - immutable_samplers: false, - }, - DescriptorSetLayoutBinding { - binding: 1, - ty: DescriptorType::Sampler, - count: BLOCK_SIZE, - stage_flags: ShaderStageFlags::FRAGMENT, - immutable_samplers: false, - }, - ]), - empty(), - ) - } - .context("Error creating descriptor set layout")?, - )); - - debug!("Created descriptor set layout {:?}", ds_lock); - - drop(device); - - let joiner = { - let loader = TextureLoader::new( - adapter, - device_lock.clone(), - family, - queue, - ds_lock.clone(), - req_recv, - resp_send, - config, - )?; - - std::thread::spawn(move || loader.loop_until_exit()) - }; - - Ok(TextureRepo { - joiner: ManuallyDrop::new(joiner), - ds_layout: ds_lock, - blocks: HashMap::new(), - req_send, - resp_recv, - }) - } - - pub fn get_ds_layout(&self) -> Result> { - self.ds_layout - .read() - .map_err(|_| LockPoisoned::Other) - .context("Error locking descriptor set layout") - } - - pub fn queue_load(&mut self, block_id: BlockRef) -> Result<()> { - if self.blocks.contains_key(&block_id) { - return Ok(()); - } - - self.force_queue_load(block_id) - } - - pub fn force_queue_load(&mut self, block_id: BlockRef) -> Result<()> { - self.req_send - .send(LoaderRequest::Load(block_id)) - .context("Error queuing texture block load")?; - - self.blocks.insert(block_id, None); - - Ok(()) - } - - pub fn attempt_get_descriptor_set(&mut self, block_id: BlockRef) -> Option<&DescriptorSetT> { - self.blocks - .get(&block_id) - .and_then(|opt| opt.as_ref().map(|z| z.descriptor_set.raw())) - } - - pub fn process_responses(&mut self) { - let resp_iter: Vec<_> = self.resp_recv.try_iter().collect(); - for resp in resp_iter { - debug!("Got block {:?} back from loader", resp.id); - self.blocks.insert(resp.id, Some(resp)); - } - } - - pub fn deactivate(mut self, device_lock: &mut Arc>) { - unsafe { - use std::ptr::read; - - // Join the loader thread - self.req_send.send(LoaderRequest::End).unwrap(); - let mut remains = read(&*self.joiner).join().unwrap().unwrap(); - - // Process any ones that just got done loading - self.process_responses(); - - // Only now can we lock device without deadlocking - let mut device = device_lock.write().unwrap(); - - // Return all the texture memory and descriptors. - for (_, v) in self.blocks.drain() { - if let Some(block) = v { - block.deactivate( - &mut device, - &mut *remains.tex_allocator, - &mut remains.descriptor_allocator, - ); - } - } - - // Dispose of both allocators - read(&*remains.tex_allocator).dispose(); - read(&*remains.descriptor_allocator).dispose(&device); - - // Deactivate DS Layout - let ds_layout = Arc::try_unwrap(self.ds_layout) - .unwrap() - .into_inner() - .unwrap(); - device.destroy_descriptor_set_layout(ds_layout); - } - } -} - -pub struct TexLoadQueue; - -impl QueueFamilySelector for TexLoadQueue { - fn is_suitable(&self, family: &QueueFamilyT) -> bool { - family.queue_type().supports_transfer() && family.max_queues() >= NUM_SIMULTANEOUS_CMDS - } -} diff --git a/stockton-render/src/draw/texture/resolver.rs b/stockton-render/src/draw/texture/resolver.rs deleted file mode 100644 index 4b61c41..0000000 --- a/stockton-render/src/draw/texture/resolver.rs +++ /dev/null @@ -1,55 +0,0 @@ -//! Resolves a texture in a BSP File to an image - -use crate::draw::texture::image::LoadableImage; -use stockton_levels::{parts::IsTexture, prelude::HasTextures}; - -use std::{ - path::Path, - sync::{Arc, RwLock}, -}; - -use image::{io::Reader, RgbaImage}; - -/// An object that can be used to resolve a texture from a BSP File -pub trait TextureResolver { - type Image: LoadableImage; - - /// Get the given texture, or None if it's corrupt/not there. - fn resolve(&mut self, texture_id: u32) -> Option; -} - -/// A basic filesystem resolver which gets the texture name from any HasTextures Object. -pub struct FsResolver<'a, T: HasTextures> { - path: &'a Path, - map_lock: Arc>, -} - -impl<'a, T: HasTextures> FsResolver<'a, T> { - pub fn new(path: &'a Path, map_lock: Arc>) -> Self { - FsResolver { path, map_lock } - } -} - -impl<'a, T: HasTextures> TextureResolver for FsResolver<'a, T> { - type Image = RgbaImage; - - fn resolve(&mut self, tex: u32) -> Option { - let map = self.map_lock.read().unwrap(); - let tex = map.get_texture(tex)?; - let path = self.path.join(&tex.name()); - - // drop(tex); - // drop(map); - - if let Ok(file) = Reader::open(path) { - if let Ok(guessed) = file.with_guessed_format() { - if let Ok(decoded) = guessed.decode() { - return Some(decoded.into_rgba8()); - } - } - } - - log::warn!("Couldn't resolve texture {:?}", tex.name()); - None - } -} diff --git a/stockton-render/src/draw/texture/staging_buffer.rs b/stockton-render/src/draw/texture/staging_buffer.rs deleted file mode 100644 index 8d2ae17..0000000 --- a/stockton-render/src/draw/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, - pub mem: ManuallyDrop, -} - -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 { - 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)); - } - } -} diff --git a/stockton-render/src/draw/ui/data/stockton.frag b/stockton-render/src/draw/ui/data/stockton.frag deleted file mode 100644 index c30c99e..0000000 --- a/stockton-render/src/draw/ui/data/stockton.frag +++ /dev/null @@ -1,15 +0,0 @@ -#version 450 -#extension GL_ARB_separate_shader_objects : enable - -// DescriptorSet 0 = Textures -layout(set = 0, binding = 0) uniform texture2D tex[8]; -layout(set = 0, binding = 1) uniform sampler samp[8]; - -layout (location = 1) in vec2 frag_uv; -layout (location = 2) in vec4 frag_col; - -layout (location = 0) out vec4 color; - -void main() { - color = texture(sampler2D(tex[0], samp[0]), frag_uv) * frag_col; -} \ No newline at end of file diff --git a/stockton-render/src/draw/ui/data/stockton.vert b/stockton-render/src/draw/ui/data/stockton.vert deleted file mode 100644 index 8912e96..0000000 --- a/stockton-render/src/draw/ui/data/stockton.vert +++ /dev/null @@ -1,37 +0,0 @@ -#version 450 - -layout (push_constant) uniform PushConsts { - vec2 screen_size; -} push; - -layout (location = 0) in vec2 pos; -layout (location = 1) in vec2 uv; -layout (location = 2) in vec4 col; - -out gl_PerVertex { - vec4 gl_Position; -}; -layout (location = 1) out vec2 frag_uv; -layout (location = 2) out vec4 frag_col; - -vec3 linear_from_srgb(vec3 srgb) { - bvec3 cutoff = lessThan(srgb, vec3(10.31475)); - vec3 lower = srgb / vec3(3294.6); - vec3 higher = pow((srgb + vec3(14.025)) / vec3(269.025), vec3(2.4)); - return mix(higher, lower, cutoff); -} - -vec4 linear_from_srgba(vec4 srgba) { - return vec4(linear_from_srgb(srgba.rgb * 255.0), srgba.a); -} - -void main() { - gl_Position = vec4( - 2.0 * pos.x / push.screen_size.x - 1.0, - 2.0 * pos.y / push.screen_size.y - 1.0, - 0.0, - 1.0 - ); - frag_uv = uv; - frag_col = linear_from_srgba(col); -} diff --git a/stockton-render/src/draw/ui/mod.rs b/stockton-render/src/draw/ui/mod.rs deleted file mode 100644 index 1b52753..0000000 --- a/stockton-render/src/draw/ui/mod.rs +++ /dev/null @@ -1,52 +0,0 @@ -use crate::draw::texture::{resolver::TextureResolver, LoadableImage}; -use egui::{CtxRef, Texture}; -use std::{convert::TryInto, sync::Arc}; - -pub struct UiTextures { - ctx: CtxRef, -} - -impl TextureResolver for UiTextures { - type Image = Arc; - fn resolve(&mut self, tex: u32) -> Option { - if tex == 0 { - Some(self.ctx.texture()) - } else { - None - } - } -} - -impl UiTextures { - pub fn new(ctx: CtxRef) -> Self { - UiTextures { ctx } - } -} - -impl LoadableImage for Arc { - fn width(&self) -> u32 { - self.width as u32 - } - fn height(&self) -> u32 { - self.height as u32 - } - fn copy_row(&self, y: u32, ptr: *mut u8) { - let row_size = self.width(); - let pixels = &self.pixels[(y * row_size) as usize..((y + 1) * row_size) as usize]; - - for (i, x) in pixels.iter().enumerate() { - unsafe { - *ptr.offset(i as isize * 4) = 255; - *ptr.offset((i as isize * 4) + 1) = 255; - *ptr.offset((i as isize * 4) + 2) = 255; - *ptr.offset((i as isize * 4) + 3) = *x; - } - } - } - - unsafe fn copy_into(&self, ptr: *mut u8, row_size: usize) { - for y in 0..self.height() { - self.copy_row(y, ptr.offset((row_size * y as usize).try_into().unwrap())); - } - } -} diff --git a/stockton-render/src/draw/utils.rs b/stockton-render/src/draw/utils.rs deleted file mode 100644 index 2ab984b..0000000 --- a/stockton-render/src/draw/utils.rs +++ /dev/null @@ -1,19 +0,0 @@ -use crate::types::*; -use hal::{memory::Properties as MemProperties, prelude::*, MemoryTypeId}; - -pub fn find_memory_type_id( - adapter: &Adapter, - type_mask: u32, - props: MemProperties, -) -> Option { - adapter - .physical_device - .memory_properties() - .memory_types - .iter() - .enumerate() - .find(|&(id, memory_type)| { - type_mask & (1 << id) != 0 && memory_type.properties.contains(props) - }) - .map(|(id, _)| MemoryTypeId(id)) -} diff --git a/stockton-render/src/draw_passes/cons.rs b/stockton-render/src/draw_passes/cons.rs new file mode 100644 index 0000000..ad94b1c --- /dev/null +++ b/stockton-render/src/draw_passes/cons.rs @@ -0,0 +1,66 @@ +//! Code for using multiple draw passes in place of just one +//! Note that this can be extended to an arbitrary amount of draw passes. + +use super::{DrawPass, IntoDrawPass}; +use crate::{context::RenderingContext, queue_negotiator::QueueNegotiator, types::*}; +use stockton_types::Session; + +use anyhow::Result; + +/// One draw pass, then another. +pub struct ConsDrawPass { + pub a: A, + pub b: B, +} + +impl DrawPass for ConsDrawPass { + fn queue_draw( + &mut self, + session: &Session, + img_view: &ImageViewT, + cmd_buffer: &mut CommandBufferT, + ) -> Result<()> { + self.a.queue_draw(session, img_view, cmd_buffer)?; + self.b.queue_draw(session, img_view, cmd_buffer)?; + + Ok(()) + } + + fn deactivate(self, context: &mut RenderingContext) -> Result<()> { + self.a.deactivate(context)?; + self.b.deactivate(context) + } + + fn handle_surface_change( + &mut self, + session: &Session, + context: &mut RenderingContext, + ) -> Result<()> { + self.a.handle_surface_change(session, context)?; + self.b.handle_surface_change(session, context) + } +} + +impl, IB: IntoDrawPass> + IntoDrawPass> for (IA, IB) +{ + fn init( + self, + session: &mut Session, + context: &mut RenderingContext, + ) -> Result> { + Ok(ConsDrawPass { + a: self.0.init(session, context)?, + b: self.1.init(session, context)?, + }) + } + + fn find_aux_queues<'a>( + adapter: &'a Adapter, + queue_negotiator: &mut QueueNegotiator, + ) -> Result)>> { + let mut v = IA::find_aux_queues(adapter, queue_negotiator)?; + v.extend(IB::find_aux_queues(adapter, queue_negotiator)?); + Ok(v) + } +} diff --git a/stockton-render/src/draw_passes/mod.rs b/stockton-render/src/draw_passes/mod.rs new file mode 100644 index 0000000..a0dbba5 --- /dev/null +++ b/stockton-render/src/draw_passes/mod.rs @@ -0,0 +1,47 @@ +//! Traits and common draw passes. +use super::{queue_negotiator::QueueNegotiator, RenderingContext}; +use crate::types::*; +use stockton_types::Session; + +use anyhow::Result; + +mod cons; +pub mod util; + +pub use cons::ConsDrawPass; + +/// One of several 'passes' that draw on each frame. +pub trait DrawPass { + /// Queue any necessary draw commands to cmd_buffer + /// This should assume the command buffer isn't in the middle of a renderpass, and should leave it as such. + fn queue_draw( + &mut self, + session: &Session, + img_view: &ImageViewT, + cmd_buffer: &mut CommandBufferT, + ) -> Result<()>; + + /// Called just after the surface changes (probably a resize). + fn handle_surface_change( + &mut self, + session: &Session, + context: &mut RenderingContext, + ) -> Result<()>; + + /// Deactivate any vulkan parts that need to be deactivated + fn deactivate(self, context: &mut RenderingContext) -> Result<()>; +} + +/// A type that can be made into a specific draw pass type. +/// This allows extra data to be used in initialisation without the Renderer needing to worry about it. +pub trait IntoDrawPass { + fn init(self, session: &mut Session, context: &mut RenderingContext) -> Result; + + /// This function should ask the queue negotatior to find families for any auxilary operations this draw pass needs to perform + /// For example, .find(&TexLoadQueue) + /// It should return then call .family_spec for each queue type negotiated and return the results. + fn find_aux_queues<'a>( + adapter: &'a Adapter, + queue_negotiator: &mut QueueNegotiator, + ) -> Result)>>; +} diff --git a/stockton-render/src/draw_passes/util.rs b/stockton-render/src/draw_passes/util.rs new file mode 100644 index 0000000..5a4eb1a --- /dev/null +++ b/stockton-render/src/draw_passes/util.rs @@ -0,0 +1,41 @@ +//! Utility structs & functions + +use anyhow::Result; + +/// Keeps a given resource for each swapchain image +pub struct TargetSpecificResources { + elements: Vec, + next_idx: usize, +} + +impl TargetSpecificResources { + /// Create a new set of resources, given a function to generate them and the count + /// In most cases, count should be swapchain_properties.image_count + pub fn new(mut generator: F, count: usize) -> Result + where + F: FnMut() -> Result, + { + let mut elements = Vec::with_capacity(count); + for _ in 0..count { + elements.push(generator()?); + } + + Ok(TargetSpecificResources { + elements, + next_idx: 0, + }) + } + + /// Get the next resource, wrapping around if necessary. + pub fn get_next<'a>(&'a mut self) -> &'a T { + let el = &self.elements[self.next_idx]; + self.next_idx = (self.next_idx + 1) % self.elements.len(); + el + } + + /// Dissolve the resource set, returning an iterator over each item. + /// In most cases, each item will need deactivated. + pub fn dissolve(self) -> impl Iterator { + self.elements.into_iter() + } +} diff --git a/stockton-render/src/lib.rs b/stockton-render/src/lib.rs index 37acf27..03f6d53 100644 --- a/stockton-render/src/lib.rs +++ b/stockton-render/src/lib.rs @@ -6,86 +6,87 @@ extern crate nalgebra_glm as na; #[macro_use] extern crate derive_builder; -#[macro_use] -extern crate legion; - -pub mod draw; +pub mod buffers; +pub mod builders; +pub mod context; +pub mod draw_passes; pub mod error; -pub mod systems; -mod types; -pub mod window; - -use draw::{ - draw_passes::{DrawPass, IntoDrawPass}, - RenderingContext, -}; +pub mod queue_negotiator; +mod target; +pub mod texture; +pub mod types; +pub mod utils; -use std::sync::mpsc::{Receiver, Sender}; -use std::sync::Arc; -use std::sync::RwLock; -pub use window::{UiState, WindowEvent}; +use context::RenderingContext; +use draw_passes::{DrawPass, IntoDrawPass}; -use anyhow::Result; +use anyhow::{Context, Result}; use stockton_types::Session; -use winit::event_loop::ControlFlow; use winit::window::Window; -use std::sync::mpsc::channel; - /// Renders a world to a window when you tell it to. /// Also takes ownership of the window and channels window events to be processed outside winit's event loop. pub struct Renderer { /// All the vulkan stuff - pub(crate) context: RenderingContext, - - /// For getting events from the winit event loop - pub window_events: Receiver, + context: RenderingContext, - /// For updating the control flow of the winit event loop - pub update_control_flow: Arc>, + /// The draw pass we're using + draw_pass: DP, } impl Renderer { /// Create a new Renderer. pub fn new>( window: &Window, - session: &Session, + session: &mut Session, idp: IDP, - ) -> Result<(Self, Sender)> { - let (tx, rx) = channel(); - let update_control_flow = Arc::new(RwLock::new(ControlFlow::Poll)); - - Ok(( - Renderer { - context: RenderingContext::new(window, session, idp)?, - window_events: rx, - update_control_flow, - }, - tx, - )) + ) -> Result { + let mut context = RenderingContext::new::(window)?; + + // Draw pass + let draw_pass = idp + .init(session, &mut context) + .context("Error initialising draw pass")?; + + Ok(Renderer { context, draw_pass }) } /// Render a single frame of the given session. pub fn render(&mut self, session: &Session) -> Result<()> { // Try to draw - if self.context.draw_next_frame(session).is_err() { + if self + .context + .draw_next_frame(session, &mut self.draw_pass) + .is_err() + { // Probably the surface changed - unsafe { self.context.handle_surface_change()? }; + self.handle_surface_change(session)?; // If it fails twice, then error - self.context.draw_next_frame(session)?; + self.context.draw_next_frame(session, &mut self.draw_pass)?; } Ok(()) } pub fn get_aspect_ratio(&self) -> f32 { - let e = self.context.target_chain.properties.extent; + let e = self.context.target_chain().properties().extent; e.width as f32 / e.height as f32 } - fn resize(&mut self) -> Result<()> { - unsafe { self.context.handle_surface_change() } + pub fn handle_surface_change(&mut self, session: &Session) -> Result<()> { + unsafe { + self.context.handle_surface_change()?; + self.draw_pass + .handle_surface_change(session, &mut self.context)?; + } + + Ok(()) + } + + /// Get a reference to the renderer's context. + pub fn context(&self) -> &RenderingContext { + &self.context } } diff --git a/stockton-render/src/queue_negotiator.rs b/stockton-render/src/queue_negotiator.rs new file mode 100644 index 0000000..65c7aa4 --- /dev/null +++ b/stockton-render/src/queue_negotiator.rs @@ -0,0 +1,135 @@ +use crate::{error::EnvironmentError, types::*}; + +use anyhow::{Error, Result}; +use hal::queue::family::QueueFamilyId; +use std::{ + any::TypeId, + collections::HashMap, + sync::{Arc, RwLock}, +}; + +/// Used to find appropriate queue families and share queues from them as needed. +pub struct QueueNegotiator { + family_ids: HashMap, + already_allocated: HashMap>>, usize)>, + all: Vec, +} + +/// Can be used to select a specific queue family +pub trait QueueFamilySelector: 'static { + /// Check if the given family is suitable + fn is_suitable(&self, family: &QueueFamilyT) -> bool; +} + +impl QueueNegotiator { + pub fn new() -> Self { + QueueNegotiator { + family_ids: HashMap::new(), + already_allocated: HashMap::new(), + all: vec![], + } + } + + pub fn find(&mut self, adapter: &Adapter, filter: &T) -> Result<()> { + if self.family_ids.contains_key(&TypeId::of::()) { + return Ok(()); + } + + let candidates: Vec<&QueueFamilyT> = adapter + .queue_families + .iter() + .filter(|x| filter.is_suitable(*x)) + .collect(); + + if candidates.is_empty() { + return Err(Error::new(EnvironmentError::NoSuitableFamilies)); + } + + // Prefer using unique families + let family = match candidates + .iter() + .find(|x| !self.family_ids.values().any(|y| *y == x.id())) + { + Some(x) => *x, + None => candidates[0], + }; + + self.family_ids.insert(TypeId::of::(), family.id()); + + Ok(()) + } + + pub fn set_queue_groups(&mut self, queue_groups: Vec) { + self.all = queue_groups + } + + pub fn get_queue(&mut self) -> Option>> { + let tid = TypeId::of::(); + let family_id = self.family_ids.get(&tid)?; + log::debug!("{:?}", self.all); + log::debug!("{:?}", self.already_allocated); + match self + .all + .iter() + .position(|x| !x.queues.is_empty() && x.family == *family_id) + { + Some(idx) => { + // At least one remaining queue + let queue = self.all[idx].queues.pop().unwrap(); + let queue = Arc::new(RwLock::new(queue)); + + self.add_to_allocated::(queue.clone()); + + Some(queue) + } + None => match self.already_allocated.get_mut(&tid) { + Some((queues, next_share)) => { + let queue = (&queues[*next_share]).clone(); + + *next_share += 1; + + Some(queue) + } + None => None, + }, + } + } + + pub fn family(&self) -> Option { + self.family_ids.get(&TypeId::of::()).cloned() + } + + fn add_to_allocated(&mut self, queue: Arc>) { + let tid = TypeId::of::(); + match self.already_allocated.get_mut(&tid) { + None => { + self.already_allocated.insert(tid, (vec![queue], 0)); + } + Some(x) => { + x.0.push(queue); + } + } + } + + pub fn family_spec<'a, T: QueueFamilySelector>( + &self, + queue_families: &'a Vec, + count: usize, + ) -> Option<(&'a QueueFamilyT, Vec)> { + let qf_id = self.family::()?; + + let qf = queue_families.iter().find(|x| x.id() == qf_id)?; + let v = vec![1.0; count]; + + Some((qf, v)) + } +} + +pub struct DrawQueue { + pub surface: SurfaceT, +} +impl QueueFamilySelector for DrawQueue { + fn is_suitable(&self, family: &QueueFamilyT) -> bool { + self.surface.supports_queue_family(family) && family.queue_type().supports_graphics() + } +} diff --git a/stockton-render/src/systems.rs b/stockton-render/src/systems.rs deleted file mode 100644 index 5f86c29..0000000 --- a/stockton-render/src/systems.rs +++ /dev/null @@ -1,2 +0,0 @@ -pub use crate::draw::camera::calc_vp_matrix_system; -pub use crate::window::process_window_events_system; diff --git a/stockton-render/src/target.rs b/stockton-render/src/target.rs new file mode 100644 index 0000000..d0d2380 --- /dev/null +++ b/stockton-render/src/target.rs @@ -0,0 +1,397 @@ +//! Resources needed for drawing on the screen, including sync objects + +use std::{ + borrow::Borrow, + iter::{empty, once}, + mem::ManuallyDrop, +}; + +use hal::{ + command::CommandBufferFlags, + format::{Aspects, ChannelType, Format, ImageFeature}, + image::{ + Access, Extent, FramebufferAttachment, Layout, SubresourceRange, Usage as ImgUsage, + ViewCapabilities, + }, + memory::{Barrier, Dependencies}, + pso::{PipelineStage, Viewport}, + window::{CompositeAlphaMode, Extent2D, PresentMode, SwapchainConfig}, +}; + +use super::draw_passes::DrawPass; +use crate::{error::EnvironmentError, types::*}; +use anyhow::{Context, Result}; +use stockton_types::Session; + +#[derive(Debug, Clone)] +pub struct SwapchainProperties { + pub format: Format, + pub depth_format: Format, + pub present_mode: PresentMode, + pub composite_alpha_mode: CompositeAlphaMode, + pub viewport: Viewport, + pub extent: Extent, + pub image_count: u32, +} + +impl SwapchainProperties { + pub fn find_best( + adapter: &Adapter, + surface: &SurfaceT, + ) -> Result { + let caps = surface.capabilities(&adapter.physical_device); + let formats = surface.supported_formats(&adapter.physical_device); + + // Find which settings we'll actually use based on preset preferences + let format = match formats { + Some(formats) => formats + .iter() + .find(|format| format.base_format().1 == ChannelType::Srgb) + .copied() + .ok_or(EnvironmentError::ColorFormat), + None => Ok(Format::Rgba8Srgb), + }?; + + let depth_format = *[ + Format::D32SfloatS8Uint, + Format::D24UnormS8Uint, + Format::D32Sfloat, + ] + .iter() + .find(|format| { + format.is_depth() + && adapter + .physical_device + .format_properties(Some(**format)) + .optimal_tiling + .contains(ImageFeature::DEPTH_STENCIL_ATTACHMENT) + }) + .ok_or(EnvironmentError::DepthFormat)?; + + let present_mode = [ + PresentMode::MAILBOX, + PresentMode::FIFO, + PresentMode::RELAXED, + PresentMode::IMMEDIATE, + ] + .iter() + .cloned() + .find(|pm| caps.present_modes.contains(*pm)) + .ok_or(EnvironmentError::PresentMode)?; + + let composite_alpha_mode = [ + CompositeAlphaMode::OPAQUE, + CompositeAlphaMode::INHERIT, + CompositeAlphaMode::PREMULTIPLIED, + CompositeAlphaMode::POSTMULTIPLIED, + ] + .iter() + .cloned() + .find(|ca| caps.composite_alpha_modes.contains(*ca)) + .ok_or(EnvironmentError::CompositeAlphaMode)?; + + let extent = caps.extents.end().to_extent(); // Size + let viewport = Viewport { + rect: extent.rect(), + depth: 0.0..1.0, + }; + + Ok(SwapchainProperties { + format, + depth_format, + present_mode, + composite_alpha_mode, + extent, + viewport, + image_count: if present_mode == PresentMode::MAILBOX { + ((*caps.image_count.end()) - 1).min((*caps.image_count.start()).max(3)) + } else { + ((*caps.image_count.end()) - 1).min((*caps.image_count.start()).max(2)) + }, + }) + } + + pub fn framebuffer_attachment(&self) -> FramebufferAttachment { + FramebufferAttachment { + usage: ImgUsage::COLOR_ATTACHMENT, + format: self.format, + view_caps: ViewCapabilities::empty(), + } + } +} + +pub struct TargetChain { + /// Surface we're targeting + surface: ManuallyDrop, + properties: SwapchainProperties, + + /// Resources tied to each target frame in the swapchain + targets: Box<[TargetResources]>, + + /// Sync objects used in drawing + /// These are seperated from the targets because we don't necessarily always match up indexes + sync_objects: Box<[SyncObjects]>, + + /// The last set of sync objects used + last_syncs: usize, + + /// Last image index of the swapchain drawn to + last_image: u32, +} + +impl TargetChain { + pub fn new( + device: &mut DeviceT, + adapter: &Adapter, + mut surface: SurfaceT, + cmd_pool: &mut CommandPoolT, + properties: SwapchainProperties, + ) -> Result { + let caps = surface.capabilities(&adapter.physical_device); + + // Number of frames to pre-render + let image_count = if properties.present_mode == PresentMode::MAILBOX { + ((*caps.image_count.end()) - 1).min((*caps.image_count.start()).max(3)) + } else { + ((*caps.image_count.end()) - 1).min((*caps.image_count.start()).max(2)) + }; + + // Swap config + let swap_config = SwapchainConfig { + present_mode: properties.present_mode, + composite_alpha_mode: properties.composite_alpha_mode, + format: properties.format, + extent: Extent2D { + width: properties.extent.width, + height: properties.extent.height, + }, + image_count, + image_layers: 1, + image_usage: ImgUsage::COLOR_ATTACHMENT, + }; + + let _fat = swap_config.framebuffer_attachment(); + let mut targets: Vec = + Vec::with_capacity(swap_config.image_count as usize); + let mut sync_objects: Vec = + Vec::with_capacity(swap_config.image_count as usize); + + for _ in 0..swap_config.image_count { + targets.push( + TargetResources::new(device, cmd_pool, &properties) + .context("Error creating target resources")?, + ); + + sync_objects.push(SyncObjects::new(device).context("Error creating sync objects")?); + } + + // Configure Swapchain + unsafe { + surface + .configure_swapchain(device, swap_config) + .context("Error configuring swapchain")?; + } + + Ok(TargetChain { + surface: ManuallyDrop::new(surface), + targets: targets.into_boxed_slice(), + sync_objects: sync_objects.into_boxed_slice(), + properties, + last_syncs: (image_count - 1) as usize, // This means the next one to be used is index 0 + last_image: 0, + }) + } + + pub fn deactivate( + self, + instance: &mut InstanceT, + device: &mut DeviceT, + cmd_pool: &mut CommandPoolT, + ) { + let surface = self.deactivate_with_recyling(device, cmd_pool); + + unsafe { + instance.destroy_surface(surface); + } + } + + pub fn deactivate_with_recyling( + mut self, + device: &mut DeviceT, + cmd_pool: &mut CommandPoolT, + ) -> SurfaceT { + use core::ptr::read; + unsafe { + for i in 0..self.targets.len() { + read(&self.targets[i]).deactivate(device, cmd_pool); + } + + for i in 0..self.sync_objects.len() { + read(&self.sync_objects[i]).deactivate(device); + } + + self.surface.unconfigure_swapchain(device); + } + + unsafe { ManuallyDrop::into_inner(read(&self.surface)) } + } + + pub fn do_draw_with<'a, DP: DrawPass>( + &'a mut self, + device: &mut DeviceT, + command_queue: &mut QueueT, + dp: &mut DP, + session: &Session, + ) -> Result<()> { + self.last_syncs = (self.last_syncs + 1) % self.sync_objects.len(); + self.last_image = (self.last_image + 1) % self.targets.len() as u32; + + let syncs = &mut self.sync_objects[self.last_syncs]; + let target = &mut self.targets[self.last_image as usize]; + + // Get the image + let (img, _) = unsafe { + self.surface + .acquire_image(core::u64::MAX) + .context("Error getting image from swapchain")? + }; + + // Make sure whatever was last using this has finished + unsafe { + device + .wait_for_fence(&syncs.present_complete, core::u64::MAX) + .context("Error waiting for present_complete")?; + device + .reset_fence(&mut syncs.present_complete) + .context("Error resetting present_complete fence")?; + }; + + // Record commands + unsafe { + target.cmd_buffer.begin_primary(CommandBufferFlags::empty()); + + target.cmd_buffer.pipeline_barrier( + PipelineStage::TOP_OF_PIPE..PipelineStage::TOP_OF_PIPE, + Dependencies::empty(), + once(Barrier::Image { + states: (Access::empty(), Layout::Undefined) + ..(Access::empty(), Layout::ColorAttachmentOptimal), + target: img.borrow(), + range: SubresourceRange { + aspects: Aspects::COLOR, + level_start: 0, + level_count: Some(1), + layer_start: 0, + layer_count: Some(1), + }, + families: None, + }), + ); + + dp.queue_draw(session, img.borrow(), &mut target.cmd_buffer) + .context("Error in draw pass")?; + + target.cmd_buffer.pipeline_barrier( + PipelineStage::BOTTOM_OF_PIPE..PipelineStage::BOTTOM_OF_PIPE, + Dependencies::empty(), + once(Barrier::Image { + states: (Access::empty(), Layout::ColorAttachmentOptimal) + ..(Access::empty(), Layout::Present), + target: img.borrow(), + range: SubresourceRange { + aspects: Aspects::COLOR, + level_start: 0, + level_count: Some(1), + layer_start: 0, + layer_count: Some(1), + }, + families: None, + }), + ); + + target.cmd_buffer.finish(); + } + + // Submit it + unsafe { + command_queue.submit( + once(&*target.cmd_buffer), + empty(), + once(&*syncs.render_complete), + Some(&mut syncs.present_complete), + ); + command_queue + .present(&mut self.surface, img, Some(&mut *syncs.render_complete)) + .context("Error presenting to surface")?; + }; + + Ok(()) + } + + /// Get a reference to the target chain's properties. + pub fn properties(&self) -> &SwapchainProperties { + &self.properties + } +} + +/// Resources for a single target frame, including sync objects +pub struct TargetResources { + /// Command buffer to use when drawing + pub cmd_buffer: ManuallyDrop, +} + +impl TargetResources { + pub fn new( + _device: &mut DeviceT, + cmd_pool: &mut CommandPoolT, + _properties: &SwapchainProperties, + ) -> Result { + // Command Buffer + let cmd_buffer = unsafe { cmd_pool.allocate_one(hal::command::Level::Primary) }; + + Ok(TargetResources { + cmd_buffer: ManuallyDrop::new(cmd_buffer), + }) + } + + pub fn deactivate(self, _device: &mut DeviceT, cmd_pool: &mut CommandPoolT) { + use core::ptr::read; + unsafe { + cmd_pool.free(once(ManuallyDrop::into_inner(read(&self.cmd_buffer)))); + } + } +} + +pub struct SyncObjects { + /// Triggered when rendering is done + pub render_complete: ManuallyDrop, + + /// Triggered when the image is on screen + pub present_complete: ManuallyDrop, +} + +impl SyncObjects { + pub fn new(device: &mut DeviceT) -> Result { + // Sync objects + let render_complete = device + .create_semaphore() + .context("Error creating render_complete semaphore")?; + let present_complete = device + .create_fence(true) + .context("Error creating present_complete fence")?; + + Ok(SyncObjects { + render_complete: ManuallyDrop::new(render_complete), + present_complete: ManuallyDrop::new(present_complete), + }) + } + + pub fn deactivate(self, device: &mut DeviceT) { + use core::ptr::read; + + unsafe { + device.destroy_semaphore(ManuallyDrop::into_inner(read(&self.render_complete))); + device.destroy_fence(ManuallyDrop::into_inner(read(&self.present_complete))); + } + } +} diff --git a/stockton-render/src/texture/block.rs b/stockton-render/src/texture/block.rs new file mode 100644 index 0000000..5ac3a94 --- /dev/null +++ b/stockton-render/src/texture/block.rs @@ -0,0 +1,62 @@ +use super::{loader::BlockRef, repo::BLOCK_SIZE}; +use crate::types::*; + +use arrayvec::ArrayVec; +use rendy_memory::{Allocator, Block}; +use std::{iter::once, mem::ManuallyDrop}; + +pub struct TexturesBlock> { + pub id: BlockRef, + pub descriptor_set: ManuallyDrop, + pub imgs: ArrayVec<[LoadedImage; BLOCK_SIZE]>, +} + +impl> TexturesBlock { + pub fn deactivate>( + mut self, + device: &mut DeviceT, + tex_alloc: &mut T, + desc_alloc: &mut DescriptorAllocator, + ) { + unsafe { + use std::ptr::read; + + // Descriptor set + desc_alloc.free(once(read(&*self.descriptor_set))); + + // Images + self.imgs + .drain(..) + .map(|x| x.deactivate(device, tex_alloc)) + .for_each(|_| {}); + } + } +} + +pub struct LoadedImage> { + pub mem: ManuallyDrop, + pub img: ManuallyDrop, + pub img_view: ManuallyDrop, + pub sampler: ManuallyDrop, + pub row_size: usize, + pub height: u32, + pub width: u32, +} + +impl> LoadedImage { + pub fn deactivate>( + 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-render/src/texture/image.rs b/stockton-render/src/texture/image.rs new file mode 100644 index 0000000..0e272e9 --- /dev/null +++ b/stockton-render/src/texture/image.rs @@ -0,0 +1,41 @@ +use super::PIXEL_SIZE; + +use core::ptr::copy_nonoverlapping; +use std::convert::TryInto; + +use image::RgbaImage; + +/// An object that can be loaded as an image into GPU memory +pub trait LoadableImage { + fn width(&self) -> u32; + fn height(&self) -> u32; + fn copy_row(&self, y: u32, ptr: *mut u8); + unsafe fn copy_into(&self, ptr: *mut u8, row_size: usize); +} + +impl LoadableImage for RgbaImage { + fn width(&self) -> u32 { + self.width() + } + + fn height(&self) -> u32 { + self.height() + } + + fn copy_row(&self, y: u32, ptr: *mut u8) { + let row_size_bytes = self.width() as usize * PIXEL_SIZE; + let raw: &Vec = self.as_raw(); + let row = &raw[y as usize * row_size_bytes..(y as usize + 1) * row_size_bytes]; + + unsafe { + copy_nonoverlapping(row.as_ptr(), ptr, row.len()); + } + } + + 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/texture/load.rs b/stockton-render/src/texture/load.rs new file mode 100644 index 0000000..1f33ad5 --- /dev/null +++ b/stockton-render/src/texture/load.rs @@ -0,0 +1,191 @@ +use super::{ + block::LoadedImage, block::TexturesBlock, repo::BLOCK_SIZE, resolver::TextureResolver, + staging_buffer::StagingBuffer, LoadableImage, PIXEL_SIZE, +}; +use crate::types::*; + +use anyhow::{Context, Result}; +use arrayvec::ArrayVec; +use hal::{ + format::{Aspects, Format, Swizzle}, + image::{ + Filter, SamplerDesc, SubresourceLayers, SubresourceRange, Usage as ImgUsage, ViewKind, + WrapMode, + }, + memory::SparseFlags, + MemoryTypeId, +}; +use rendy_memory::{Allocator, Block}; +use std::mem::ManuallyDrop; +use thiserror::Error; + +#[derive(Error, Debug)] +pub enum TextureLoadError { + #[error("No available resources")] + NoResources, +} + +pub const FORMAT: Format = Format::Rgba8Srgb; +pub const RESOURCES: SubresourceRange = SubresourceRange { + aspects: Aspects::COLOR, + level_start: 0, + level_count: Some(1), + layer_start: 0, + layer_count: Some(1), +}; +pub const LAYERS: SubresourceLayers = SubresourceLayers { + aspects: Aspects::COLOR, + level: 0, + layers: 0..1, +}; + +pub struct TextureLoadConfig { + pub resolver: R, + pub filter: Filter, + pub wrap_mode: WrapMode, +} + +pub struct QueuedLoad> { + pub fence: FenceT, + pub buf: CommandBufferT, + pub block: TexturesBlock, + pub staging_bufs: ArrayVec<[StagingBuffer; BLOCK_SIZE]>, +} + +impl> QueuedLoad { + pub fn dissolve( + self, + ) -> ( + (FenceT, CommandBufferT), + ArrayVec<[StagingBuffer; BLOCK_SIZE]>, + TexturesBlock, + ) { + ((self.fence, self.buf), self.staging_bufs, self.block) + } +} + +pub fn tex_size_info(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( + device: &mut DeviceT, + allocator: &mut T, + format: Format, + usage: ImgUsage, + img: &I, +) -> Result<(T::Block, ImageT)> +where + T: Allocator, + I: LoadableImage, +{ + // Make the image + let mut image_ref = unsafe { + use hal::image::{Kind, Tiling, ViewCapabilities}; + + device.create_image( + Kind::D2(img.width(), img.height(), 1, 1), + 1, + format, + Tiling::Optimal, + usage, + SparseFlags::empty(), + ViewCapabilities::empty(), + ) + } + .context("Error creating image")?; + + // Allocate memory + let (block, _) = unsafe { + let requirements = device.get_image_requirements(&image_ref); + + allocator.alloc(device, requirements.size, requirements.alignment) + } + .context("Error allocating memory")?; + + unsafe { + device + .bind_image_memory(block.memory(), block.range().start, &mut image_ref) + .context("Error binding memory to image")?; + } + + Ok((block, image_ref)) +} + +pub unsafe fn load_image( + device: &mut DeviceT, + staging_allocator: &mut DynamicAllocator, + tex_allocator: &mut DynamicAllocator, + staging_memory_type: MemoryTypeId, + obcpa: u64, + img_data: I, + config: &TextureLoadConfig, +) -> Result<(StagingBuffer, LoadedImage)> { + // Calculate buffer size + let (row_size, total_size) = tex_size_info(&img_data, obcpa); + + // Create staging buffer + let mut staging_buffer = StagingBuffer::new( + device, + staging_allocator, + total_size as u64, + staging_memory_type, + ) + .context("Error creating staging buffer")?; + + // Write to staging buffer + let mapped_memory = staging_buffer + .map_memory(device) + .context("Error mapping staged memory")?; + + img_data.copy_into(mapped_memory, row_size); + + staging_buffer.unmap_memory(device); + + // Create image + let (img_mem, img) = create_image_view( + device, + tex_allocator, + FORMAT, + ImgUsage::SAMPLED | ImgUsage::TRANSFER_DST, + &img_data, + ) + .context("Error creating image")?; + + // Create image view + let img_view = device + .create_image_view( + &img, + ViewKind::D2, + FORMAT, + Swizzle::NO, + ImgUsage::SAMPLED | ImgUsage::TRANSFER_DST, + RESOURCES, + ) + .context("Error creating image view")?; + + // Create sampler + let sampler = device + .create_sampler(&SamplerDesc::new(config.filter, config.wrap_mode)) + .context("Error creating sampler")?; + + Ok(( + staging_buffer, + LoadedImage { + mem: ManuallyDrop::new(img_mem), + img: ManuallyDrop::new(img), + img_view: ManuallyDrop::new(img_view), + sampler: ManuallyDrop::new(sampler), + row_size, + height: img_data.height(), + width: img_data.width(), + }, + )) +} diff --git a/stockton-render/src/texture/loader.rs b/stockton-render/src/texture/loader.rs new file mode 100644 index 0000000..f9c643c --- /dev/null +++ b/stockton-render/src/texture/loader.rs @@ -0,0 +1,712 @@ +//! Manages the loading/unloading of textures + +use super::{ + block::{LoadedImage, TexturesBlock}, + load::{load_image, QueuedLoad, TextureLoadConfig, TextureLoadError, LAYERS, RESOURCES}, + repo::BLOCK_SIZE, + resolver::TextureResolver, + PIXEL_SIZE, +}; +use crate::{error::LockPoisoned, types::*, utils::find_memory_type_id}; + +use std::{ + array::IntoIter, + collections::VecDeque, + iter::{empty, once}, + mem::{drop, ManuallyDrop}, + sync::{ + mpsc::{Receiver, Sender}, + Arc, RwLock, + }, + thread::sleep, + time::Duration, +}; + +use anyhow::{Context, Result}; +use arrayvec::ArrayVec; +use hal::{ + command::{BufferImageCopy, CommandBufferFlags}, + format::{Aspects, Format}, + image::{Access, Extent, Layout, Offset, SubresourceLayers, SubresourceRange}, + memory::{Barrier, Dependencies, Properties as MemProps, SparseFlags}, + pso::{Descriptor, DescriptorSetWrite, ImageDescriptorType, PipelineStage, ShaderStageFlags}, + queue::family::QueueFamilyId, + MemoryTypeId, +}; +use image::{Rgba, RgbaImage}; +use log::*; +use rendy_descriptor::{DescriptorRanges, DescriptorSetLayoutBinding, DescriptorType}; +use rendy_memory::DynamicConfig; +use thiserror::Error; + +/// The number of command buffers to have in flight simultaneously. +pub const NUM_SIMULTANEOUS_CMDS: usize = 2; + +/// A reference to a texture of the current map +pub type BlockRef = usize; + +/// Manages the loading/unloading of textures +/// This is expected to load the textures, then send the loaded blocks back +pub struct TextureLoader { + /// Blocks for which commands have been queued and are done loading once the fence is triggered. + commands_queued: ArrayVec<[QueuedLoad; NUM_SIMULTANEOUS_CMDS]>, + + /// The command buffers used and a fence to go with them + buffers: VecDeque<(FenceT, CommandBufferT)>, + + /// The command pool buffers were allocated from + pool: ManuallyDrop, + + /// The GPU we're submitting to + device: Arc>, + + /// The command queue being used + queue: Arc>, + + /// The memory allocator being used for textures + tex_allocator: ManuallyDrop, + + /// The memory allocator for staging memory + staging_allocator: ManuallyDrop, + + /// Allocator for descriptor sets + descriptor_allocator: ManuallyDrop, + + ds_layout: Arc>, + + /// Type ID for staging memory + staging_memory_type: MemoryTypeId, + + /// From adapter, used for determining alignment + optimal_buffer_copy_pitch_alignment: hal::buffer::Offset, + + /// Configuration for how to find and load textures + config: TextureLoadConfig, + + /// The channel requests come in. + /// Requests should reference a texture **block**, for example textures 8..16 is block 1. + request_channel: Receiver, + + /// The channel blocks are returned to. + return_channel: Sender>, + + /// A filler image for descriptors that aren't needed but still need to be written to + blank_image: ManuallyDrop>, +} + +#[derive(Error, Debug)] +pub enum TextureLoaderError { + #[error("Couldn't find a suitable memory type")] + NoMemoryTypes, +} + +impl TextureLoader { + pub fn loop_until_exit(mut self) -> Result { + debug!("TextureLoader starting main loop"); + let mut res = Ok(false); + while res.is_ok() { + res = self.main(); + if let Ok(true) = res { + break; + } + + sleep(Duration::from_secs(0)); + } + + match res { + Ok(true) => { + debug!("Starting to deactivate TextureLoader"); + + Ok(self.deactivate()) + } + Err(r) => Err(r.context("Error in TextureLoader loop")), + _ => unreachable!(), + } + } + fn main(&mut self) -> Result { + let mut device = self + .device + .write() + .map_err(|_| LockPoisoned::Device) + .context("Error getting device lock")?; + // Check for blocks that are finished, then send them back + let mut i = 0; + while i < self.commands_queued.len() { + let signalled = unsafe { device.get_fence_status(&self.commands_queued[i].fence) } + .context("Error checking fence status")?; + + if signalled { + let (assets, mut staging_bufs, block) = self.commands_queued.remove(i).dissolve(); + debug!("Load finished for texture block {:?}", block.id); + + // Destroy staging buffers + for buf in staging_bufs.drain(..) { + buf.deactivate(&mut device, &mut self.staging_allocator); + } + + self.buffers.push_back(assets); + self.return_channel + .send(block) + .context("Error returning texture block")?; + } else { + i += 1; + } + } + + drop(device); + + // Check for messages to start loading blocks + let req_iter: Vec<_> = self.request_channel.try_iter().collect(); + for to_load in req_iter { + match to_load { + LoaderRequest::Load(to_load) => { + // Attempt to load given block + debug!("Attempting to queue load for texture block {:?}", to_load); + + let result = unsafe { self.attempt_queue_load(to_load) }; + match result { + Ok(queued_load) => self.commands_queued.push(queued_load), + Err(x) => match x.downcast_ref::() { + Some(TextureLoadError::NoResources) => { + debug!("No resources, trying again later"); + } + _ => return Err(x).context("Error queuing texture load"), + }, + } + } + LoaderRequest::End => return Ok(true), + } + } + + Ok(false) + } + + pub fn new( + adapter: &Adapter, + device_lock: Arc>, + family: QueueFamilyId, + queue_lock: Arc>, + ds_layout: Arc>, + request_channel: Receiver, + return_channel: Sender>, + config: TextureLoadConfig, + ) -> Result { + let mut device = device_lock + .write() + .map_err(|_| LockPoisoned::Device) + .context("Error getting device lock")?; + let device_props = adapter.physical_device.properties(); + + let type_mask = unsafe { + use hal::image::{Kind, Tiling, Usage, ViewCapabilities}; + + // We create an empty image with the same format as used for textures + // this is to get the type_mask required, which will stay the same for + // all colour images of the same tiling. (certain memory flags excluded). + + // Size and alignment don't necessarily stay the same, so we're forced to + // guess at the alignment for our allocator. + + // TODO: Way to tune these options + let img = device + .create_image( + Kind::D2(16, 16, 1, 1), + 1, + Format::Rgba8Srgb, + Tiling::Optimal, + Usage::SAMPLED, + SparseFlags::empty(), + ViewCapabilities::empty(), + ) + .context("Error creating test image to get buffer settings")?; + + let type_mask = device.get_image_requirements(&img).type_mask; + + device.destroy_image(img); + + type_mask + }; + + debug!("Using type mask {:?}", type_mask); + + // Tex Allocator + let mut tex_allocator = { + let props = MemProps::DEVICE_LOCAL; + + DynamicAllocator::new( + find_memory_type_id(adapter, type_mask, props) + .ok_or(TextureLoaderError::NoMemoryTypes) + .context("Couldn't create tex memory allocator")?, + props, + DynamicConfig { + block_size_granularity: 4 * 32 * 32, // 32x32 image + max_chunk_size: u64::pow(2, 63), + min_device_allocation: 4 * 32 * 32, + }, + device_props.limits.non_coherent_atom_size as u64, + ) + }; + + let (staging_memory_type, mut staging_allocator) = { + let props = MemProps::CPU_VISIBLE | MemProps::COHERENT; + let t = find_memory_type_id(adapter, u32::MAX, props) + .ok_or(TextureLoaderError::NoMemoryTypes) + .context("Couldn't create staging memory allocator")?; + ( + t, + DynamicAllocator::new( + t, + props, + DynamicConfig { + block_size_granularity: 4 * 32 * 32, // 32x32 image + max_chunk_size: u64::pow(2, 63), + min_device_allocation: 4 * 32 * 32, + }, + device_props.limits.non_coherent_atom_size as u64, + ), + ) + }; + + // Pool + let mut pool = unsafe { + use hal::pool::CommandPoolCreateFlags; + + device.create_command_pool(family, CommandPoolCreateFlags::RESET_INDIVIDUAL) + } + .context("Error creating command pool")?; + + // Command buffers and fences + debug!("Creating resources..."); + let mut buffers = { + let mut data = VecDeque::with_capacity(NUM_SIMULTANEOUS_CMDS); + + for _ in 0..NUM_SIMULTANEOUS_CMDS { + unsafe { + data.push_back(( + device.create_fence(false).context("Error creating fence")?, + pool.allocate_one(hal::command::Level::Primary), + )); + }; + } + + data + }; + + let optimal_buffer_copy_pitch_alignment = + device_props.limits.optimal_buffer_copy_pitch_alignment; + + let blank_image = unsafe { + Self::get_blank_image( + &mut device, + &mut buffers[0].1, + &queue_lock, + &mut staging_allocator, + &mut tex_allocator, + staging_memory_type, + optimal_buffer_copy_pitch_alignment, + &config, + ) + } + .context("Error creating blank image")?; + + drop(device); + + Ok(TextureLoader { + commands_queued: ArrayVec::new(), + buffers, + pool: ManuallyDrop::new(pool), + device: device_lock, + queue: queue_lock, + ds_layout, + + tex_allocator: ManuallyDrop::new(tex_allocator), + staging_allocator: ManuallyDrop::new(staging_allocator), + descriptor_allocator: ManuallyDrop::new(DescriptorAllocator::new()), + + staging_memory_type, + optimal_buffer_copy_pitch_alignment, + + request_channel, + return_channel, + config, + blank_image: ManuallyDrop::new(blank_image), + }) + } + + unsafe fn attempt_queue_load(&mut self, block_ref: usize) -> Result> { + let mut device = self + .device + .write() + .map_err(|_| LockPoisoned::Device) + .context("Error getting device lock")?; + + // Get assets to use + let (mut fence, mut buf) = self + .buffers + .pop_front() + .ok_or(TextureLoadError::NoResources) + .context("Error getting resources to use")?; + + // Create descriptor set + let mut descriptor_set = { + let mut v: ArrayVec<[RDescriptorSet; 1]> = ArrayVec::new(); + self.descriptor_allocator + .allocate( + &device, + &*self + .ds_layout + .read() + .map_err(|_| LockPoisoned::Other) + .context("Error reading descriptor set layout")?, + DescriptorRanges::from_bindings(&[ + DescriptorSetLayoutBinding { + binding: 0, + ty: DescriptorType::Image { + ty: ImageDescriptorType::Sampled { + with_sampler: false, + }, + }, + count: BLOCK_SIZE, + stage_flags: ShaderStageFlags::FRAGMENT, + immutable_samplers: false, + }, + DescriptorSetLayoutBinding { + binding: 1, + ty: DescriptorType::Sampler, + count: BLOCK_SIZE, + stage_flags: ShaderStageFlags::FRAGMENT, + immutable_samplers: false, + }, + ]), + 1, + &mut v, + ) + .context("Error creating descriptor set")?; + + v.pop().unwrap() + }; + + // Get a command buffer + buf.begin_primary(CommandBufferFlags::ONE_TIME_SUBMIT); + + let mut imgs: ArrayVec<[_; BLOCK_SIZE]> = ArrayVec::new(); + let mut staging_bufs: ArrayVec<[_; BLOCK_SIZE]> = ArrayVec::new(); + + // For each texture in block + for tex_idx in (block_ref * BLOCK_SIZE)..(block_ref + 1) * BLOCK_SIZE { + // Resolve texture + let img_data = self.config.resolver.resolve(tex_idx as u32); + if img_data.is_none() { + // Write a blank descriptor + device.write_descriptor_set(DescriptorSetWrite { + set: descriptor_set.raw_mut(), + binding: 0, + array_offset: tex_idx % BLOCK_SIZE, + descriptors: once(Descriptor::Image( + &*self.blank_image.img_view, + Layout::ShaderReadOnlyOptimal, + )), + }); + device.write_descriptor_set(DescriptorSetWrite { + set: descriptor_set.raw_mut(), + binding: 1, + array_offset: tex_idx % BLOCK_SIZE, + descriptors: once(Descriptor::Sampler(&*self.blank_image.sampler)), + }); + + continue; + } + + let img_data = img_data.unwrap(); + + let array_offset = tex_idx % BLOCK_SIZE; + + let (staging_buffer, img) = load_image( + &mut device, + &mut self.staging_allocator, + &mut self.tex_allocator, + self.staging_memory_type, + self.optimal_buffer_copy_pitch_alignment, + img_data, + &self.config, + )?; + + // Write to descriptor set + { + device.write_descriptor_set(DescriptorSetWrite { + set: descriptor_set.raw_mut(), + binding: 0, + array_offset, + descriptors: once(Descriptor::Image( + &*img.img_view, + Layout::ShaderReadOnlyOptimal, + )), + }); + device.write_descriptor_set(DescriptorSetWrite { + set: descriptor_set.raw_mut(), + binding: 1, + array_offset, + descriptors: once(Descriptor::Sampler(&*img.sampler)), + }); + } + + imgs.push(img); + + staging_bufs.push(staging_buffer); + } + + // Add start pipeline barrier + buf.pipeline_barrier( + PipelineStage::TOP_OF_PIPE..PipelineStage::TRANSFER, + Dependencies::empty(), + imgs.iter().map(|li| Barrier::Image { + states: (Access::empty(), Layout::Undefined) + ..(Access::TRANSFER_WRITE, Layout::TransferDstOptimal), + target: &*li.img, + families: None, + range: SubresourceRange { + aspects: Aspects::COLOR, + level_start: 0, + level_count: None, + layer_start: 0, + layer_count: None, + }, + }), + ); + + // Record copy commands + for (li, sb) in imgs.iter().zip(staging_bufs.iter()) { + buf.copy_buffer_to_image( + &*sb.buf, + &*li.img, + Layout::TransferDstOptimal, + once(BufferImageCopy { + buffer_offset: 0, + buffer_width: (li.row_size / super::PIXEL_SIZE) as u32, + buffer_height: li.height, + image_layers: SubresourceLayers { + aspects: Aspects::COLOR, + level: 0, + layers: 0..1, + }, + image_offset: Offset { x: 0, y: 0, z: 0 }, + image_extent: gfx_hal::image::Extent { + width: li.width, + height: li.height, + depth: 1, + }, + }), + ); + } + buf.pipeline_barrier( + PipelineStage::TRANSFER..PipelineStage::BOTTOM_OF_PIPE, + Dependencies::empty(), + imgs.iter().map(|li| Barrier::Image { + states: (Access::TRANSFER_WRITE, Layout::TransferDstOptimal) + ..(Access::empty(), Layout::ShaderReadOnlyOptimal), + target: &*li.img, + families: None, + range: RESOURCES, + }), + ); + + buf.finish(); + + // Submit command buffer + { + let mut queue = self.queue.write().map_err(|_| LockPoisoned::Queue)?; + + queue.submit(IntoIter::new([&buf]), empty(), empty(), Some(&mut fence)); + } + + Ok(QueuedLoad { + staging_bufs, + fence, + buf, + block: TexturesBlock { + id: block_ref, + imgs, + descriptor_set: ManuallyDrop::new(descriptor_set), + }, + }) + } + + unsafe fn get_blank_image( + device: &mut DeviceT, + buf: &mut CommandBufferT, + queue_lock: &Arc>, + staging_allocator: &mut DynamicAllocator, + tex_allocator: &mut DynamicAllocator, + staging_memory_type: MemoryTypeId, + obcpa: u64, + config: &TextureLoadConfig, + ) -> Result> { + let img_data = RgbaImage::from_pixel(1, 1, Rgba([255, 0, 255, 255])); + + let height = img_data.height(); + let width = img_data.width(); + let row_alignment_mask = obcpa as u32 - 1; + let initial_row_size = PIXEL_SIZE * img_data.width() as usize; + let row_size = + ((initial_row_size as u32 + row_alignment_mask) & !row_alignment_mask) as usize; + + let (staging_buffer, img) = load_image( + device, + staging_allocator, + tex_allocator, + staging_memory_type, + obcpa, + img_data, + config, + )?; + + buf.begin_primary(CommandBufferFlags::ONE_TIME_SUBMIT); + + buf.pipeline_barrier( + PipelineStage::TOP_OF_PIPE..PipelineStage::TRANSFER, + Dependencies::empty(), + once(Barrier::Image { + states: (Access::empty(), Layout::Undefined) + ..(Access::TRANSFER_WRITE, Layout::TransferDstOptimal), + target: &*img.img, + families: None, + range: SubresourceRange { + aspects: Aspects::COLOR, + level_start: 0, + level_count: None, + layer_start: 0, + layer_count: None, + }, + }), + ); + buf.copy_buffer_to_image( + &*staging_buffer.buf, + &*img.img, + Layout::TransferDstOptimal, + once(BufferImageCopy { + buffer_offset: 0, + buffer_width: (row_size / super::PIXEL_SIZE) as u32, + buffer_height: height, + image_layers: LAYERS, + image_offset: Offset { x: 0, y: 0, z: 0 }, + image_extent: Extent { + width, + height, + depth: 1, + }, + }), + ); + + buf.pipeline_barrier( + PipelineStage::TRANSFER..PipelineStage::BOTTOM_OF_PIPE, + Dependencies::empty(), + once(Barrier::Image { + states: (Access::TRANSFER_WRITE, Layout::TransferDstOptimal) + ..(Access::empty(), Layout::ShaderReadOnlyOptimal), + target: &*img.img, + families: None, + range: RESOURCES, + }), + ); + buf.finish(); + + let mut fence = device.create_fence(false).context("Error creating fence")?; + + { + let mut queue = queue_lock.write().map_err(|_| LockPoisoned::Queue)?; + + queue.submit( + IntoIter::new([buf as &CommandBufferT]), + empty(), + empty(), + Some(&mut fence), + ); + } + + device + .wait_for_fence(&fence, std::u64::MAX) + .context("Error waiting for copy")?; + + device.destroy_fence(fence); + + staging_buffer.deactivate(device, staging_allocator); + + Ok(img) + } + + /// Safely destroy all the vulkan stuff in this instance + /// Note that this returns the memory allocators, from which should be freed any TextureBlocks + /// All in-progress things are sent to return_channel. + fn deactivate(mut self) -> TextureLoaderRemains { + use std::ptr::read; + + let mut device = self.device.write().unwrap(); + + unsafe { + // Wait for any currently queued loads to be done + while self.commands_queued.len() > 0 { + let mut i = 0; + while i < self.commands_queued.len() { + let signalled = device + .get_fence_status(&self.commands_queued[i].fence) + .expect("Device lost by TextureManager"); + + if signalled { + // Destroy finished ones + let (assets, mut staging_bufs, block) = + self.commands_queued.remove(i).dissolve(); + + device.destroy_fence(assets.0); + // Command buffer will be freed when we reset the command pool + // Destroy staging buffers + for buf in staging_bufs.drain(..) { + buf.deactivate(&mut device, &mut self.staging_allocator); + } + + self.return_channel + .send(block) + .expect("Sending through return channel failed"); + } else { + i += 1; + } + } + + sleep(Duration::from_secs(0)); + } + + // Destroy blank image + read(&*self.blank_image).deactivate(&mut device, &mut *self.tex_allocator); + + // Destroy fences + + self.buffers + .drain(..) + .map(|(f, _)| device.destroy_fence(f)) + .for_each(|_| {}); + + // Free command pool + self.pool.reset(true); + device.destroy_command_pool(read(&*self.pool)); + + debug!("Done deactivating TextureLoader"); + + TextureLoaderRemains { + tex_allocator: ManuallyDrop::new(read(&*self.tex_allocator)), + descriptor_allocator: ManuallyDrop::new(read(&*self.descriptor_allocator)), + } + } + } +} + +pub struct TextureLoaderRemains { + pub tex_allocator: ManuallyDrop, + pub descriptor_allocator: ManuallyDrop, +} + +pub enum LoaderRequest { + /// Load the given block + Load(BlockRef), + + /// Stop looping and deactivate + End, +} diff --git a/stockton-render/src/texture/mod.rs b/stockton-render/src/texture/mod.rs new file mode 100644 index 0000000..aef1b03 --- /dev/null +++ b/stockton-render/src/texture/mod.rs @@ -0,0 +1,18 @@ +//! Everything related to loading textures into GPU memory + +mod block; +mod image; +mod load; +mod loader; +mod repo; +pub mod resolver; +mod staging_buffer; + +pub use self::block::TexturesBlock; +pub use self::image::LoadableImage; +pub use self::load::TextureLoadConfig; +pub use self::loader::BlockRef; +pub use self::repo::{TexLoadQueue, TextureRepo}; + +/// The size of each pixel in an image +pub const PIXEL_SIZE: usize = std::mem::size_of::() * 4; diff --git a/stockton-render/src/texture/repo.rs b/stockton-render/src/texture/repo.rs new file mode 100644 index 0000000..e29625b --- /dev/null +++ b/stockton-render/src/texture/repo.rs @@ -0,0 +1,201 @@ +use super::{ + block::TexturesBlock, + load::TextureLoadConfig, + loader::{BlockRef, LoaderRequest, TextureLoader, TextureLoaderRemains, NUM_SIMULTANEOUS_CMDS}, + resolver::TextureResolver, +}; +use crate::error::LockPoisoned; +use crate::queue_negotiator::QueueFamilySelector; +use crate::types::*; + +use std::{ + array::IntoIter, + collections::HashMap, + iter::empty, + mem::ManuallyDrop, + sync::{ + mpsc::{channel, Receiver, Sender}, + Arc, RwLock, RwLockReadGuard, + }, + thread::JoinHandle, +}; + +use anyhow::{Context, Result}; +use hal::{ + pso::{DescriptorSetLayoutBinding, DescriptorType, ImageDescriptorType, ShaderStageFlags}, + queue::family::QueueFamilyId, +}; +use log::debug; + +/// The number of textures in one 'block' +/// The textures of the loaded file are divided into blocks of this size. +/// Whenever a texture is needed, the whole block its in is loaded. +pub const BLOCK_SIZE: usize = 8; + +pub struct TextureRepo { + joiner: ManuallyDrop>>, + ds_layout: Arc>, + req_send: Sender, + resp_recv: Receiver>, + blocks: HashMap>>, +} + +impl TextureRepo { + pub fn new( + device_lock: Arc>, + family: QueueFamilyId, + queue: Arc>, + adapter: &Adapter, + config: TextureLoadConfig, + ) -> Result { + // Create Channels + let (req_send, req_recv) = channel(); + let (resp_send, resp_recv) = channel(); + let device = device_lock + .write() + .map_err(|_| LockPoisoned::Device) + .context("Error getting device lock")?; + + // Create descriptor set layout + let ds_lock = Arc::new(RwLock::new( + unsafe { + device.create_descriptor_set_layout( + IntoIter::new([ + DescriptorSetLayoutBinding { + binding: 0, + ty: DescriptorType::Image { + ty: ImageDescriptorType::Sampled { + with_sampler: false, + }, + }, + count: BLOCK_SIZE, + stage_flags: ShaderStageFlags::FRAGMENT, + immutable_samplers: false, + }, + DescriptorSetLayoutBinding { + binding: 1, + ty: DescriptorType::Sampler, + count: BLOCK_SIZE, + stage_flags: ShaderStageFlags::FRAGMENT, + immutable_samplers: false, + }, + ]), + empty(), + ) + } + .context("Error creating descriptor set layout")?, + )); + + debug!("Created descriptor set layout {:?}", ds_lock); + + drop(device); + + let joiner = { + let loader = TextureLoader::new( + adapter, + device_lock.clone(), + family, + queue, + ds_lock.clone(), + req_recv, + resp_send, + config, + )?; + + std::thread::spawn(move || loader.loop_until_exit()) + }; + + Ok(TextureRepo { + joiner: ManuallyDrop::new(joiner), + ds_layout: ds_lock, + blocks: HashMap::new(), + req_send, + resp_recv, + }) + } + + pub fn get_ds_layout(&self) -> Result> { + self.ds_layout + .read() + .map_err(|_| LockPoisoned::Other) + .context("Error locking descriptor set layout") + } + + pub fn queue_load(&mut self, block_id: BlockRef) -> Result<()> { + if self.blocks.contains_key(&block_id) { + return Ok(()); + } + + self.force_queue_load(block_id) + } + + pub fn force_queue_load(&mut self, block_id: BlockRef) -> Result<()> { + self.req_send + .send(LoaderRequest::Load(block_id)) + .context("Error queuing texture block load")?; + + self.blocks.insert(block_id, None); + + Ok(()) + } + + pub fn attempt_get_descriptor_set(&mut self, block_id: BlockRef) -> Option<&DescriptorSetT> { + self.blocks + .get(&block_id) + .and_then(|opt| opt.as_ref().map(|z| z.descriptor_set.raw())) + } + + pub fn process_responses(&mut self) { + let resp_iter: Vec<_> = self.resp_recv.try_iter().collect(); + for resp in resp_iter { + debug!("Got block {:?} back from loader", resp.id); + self.blocks.insert(resp.id, Some(resp)); + } + } + + pub fn deactivate(mut self, device_lock: &Arc>) { + unsafe { + use std::ptr::read; + + // Join the loader thread + self.req_send.send(LoaderRequest::End).unwrap(); + let mut remains = read(&*self.joiner).join().unwrap().unwrap(); + + // Process any ones that just got done loading + self.process_responses(); + + // Only now can we lock device without deadlocking + let mut device = device_lock.write().unwrap(); + + // Return all the texture memory and descriptors. + for (_, v) in self.blocks.drain() { + if let Some(block) = v { + block.deactivate( + &mut device, + &mut *remains.tex_allocator, + &mut remains.descriptor_allocator, + ); + } + } + + // Dispose of both allocators + read(&*remains.tex_allocator).dispose(); + read(&*remains.descriptor_allocator).dispose(&device); + + // Deactivate DS Layout + let ds_layout = Arc::try_unwrap(self.ds_layout) + .unwrap() + .into_inner() + .unwrap(); + device.destroy_descriptor_set_layout(ds_layout); + } + } +} + +pub struct TexLoadQueue; + +impl QueueFamilySelector for TexLoadQueue { + fn is_suitable(&self, family: &QueueFamilyT) -> bool { + family.queue_type().supports_transfer() && family.max_queues() >= NUM_SIMULTANEOUS_CMDS + } +} diff --git a/stockton-render/src/texture/resolver.rs b/stockton-render/src/texture/resolver.rs new file mode 100644 index 0000000..f66b724 --- /dev/null +++ b/stockton-render/src/texture/resolver.rs @@ -0,0 +1,55 @@ +//! Resolves a texture in a BSP File to an image + +use crate::texture::image::LoadableImage; +use stockton_levels::{parts::IsTexture, prelude::HasTextures}; + +use std::{ + path::Path, + sync::{Arc, RwLock}, +}; + +use image::{io::Reader, RgbaImage}; + +/// An object that can be used to resolve a texture from a BSP File +pub trait TextureResolver { + type Image: LoadableImage; + + /// Get the given texture, or None if it's corrupt/not there. + fn resolve(&mut self, texture_id: u32) -> Option; +} + +/// A basic filesystem resolver which gets the texture name from any HasTextures Object. +pub struct FsResolver<'a, T: HasTextures> { + path: &'a Path, + map_lock: Arc>, +} + +impl<'a, T: HasTextures> FsResolver<'a, T> { + pub fn new(path: &'a Path, map_lock: Arc>) -> Self { + FsResolver { path, map_lock } + } +} + +impl<'a, T: HasTextures> TextureResolver for FsResolver<'a, T> { + type Image = RgbaImage; + + fn resolve(&mut self, tex: u32) -> Option { + let map = self.map_lock.read().unwrap(); + let tex = map.get_texture(tex)?; + let path = self.path.join(&tex.name()); + + // drop(tex); + // drop(map); + + if let Ok(file) = Reader::open(path) { + if let Ok(guessed) = file.with_guessed_format() { + if let Ok(decoded) = guessed.decode() { + return Some(decoded.into_rgba8()); + } + } + } + + log::warn!("Couldn't resolve texture {:?}", tex.name()); + None + } +} diff --git a/stockton-render/src/texture/staging_buffer.rs b/stockton-render/src/texture/staging_buffer.rs new file mode 100644 index 0000000..8d2ae17 --- /dev/null +++ b/stockton-render/src/texture/staging_buffer.rs @@ -0,0 +1,59 @@ +#![allow(mutable_transmutes)] +use crate::types::*; + +use std::mem::ManuallyDrop; + +use anyhow::{Context, Result}; +use hal::{device::MapError, memory::SparseFlags, MemoryTypeId}; +use rendy_memory::{Allocator, Block}; + +pub struct StagingBuffer { + pub buf: ManuallyDrop, + pub mem: ManuallyDrop, +} + +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 { + 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)); + } + } +} diff --git a/stockton-render/src/utils.rs b/stockton-render/src/utils.rs new file mode 100644 index 0000000..152ba10 --- /dev/null +++ b/stockton-render/src/utils.rs @@ -0,0 +1,19 @@ +use crate::types::*; +use hal::{memory::Properties as MemProperties, MemoryTypeId}; + +pub fn find_memory_type_id( + adapter: &Adapter, + type_mask: u32, + props: MemProperties, +) -> Option { + adapter + .physical_device + .memory_properties() + .memory_types + .iter() + .enumerate() + .find(|&(id, memory_type)| { + type_mask & (1 << id) != 0 && memory_type.properties.contains(props) + }) + .map(|(id, _)| MemoryTypeId(id)) +} diff --git a/stockton-render/src/window.rs b/stockton-render/src/window.rs deleted file mode 100644 index 87f3182..0000000 --- a/stockton-render/src/window.rs +++ /dev/null @@ -1,239 +0,0 @@ -use crate::{error::full_error_display, DrawPass, Renderer}; -use egui::{Modifiers, Rect, Vec2}; -use legion::systems::Runnable; -use log::debug; - -use egui::{CtxRef, Event, Output, Pos2, RawInput}; -use epaint::ClippedShape; -use log::error; -use stockton_input::{Action as KBAction, InputManager, Mouse}; - -use winit::event::{ - ElementState, Event as WinitEvent, MouseButton, WindowEvent as WinitWindowEvent, -}; -use winit::event_loop::ControlFlow; - -#[derive(Debug, Clone, Copy)] -pub enum WindowEvent { - SizeChanged(u32, u32), - CloseRequested, - KeyboardAction(KBAction), - MouseAction(KBAction), - MouseMoved(f32, f32), - MouseLeft, -} - -impl WindowEvent { - pub fn from(winit_event: &WinitEvent<()>) -> Option { - // TODO - match winit_event { - WinitEvent::WindowEvent { event, .. } => match event { - WinitWindowEvent::CloseRequested => Some(WindowEvent::CloseRequested), - WinitWindowEvent::Resized(size) => { - Some(WindowEvent::SizeChanged(size.width, size.height)) - } - WinitWindowEvent::KeyboardInput { input, .. } => match input.state { - ElementState::Pressed => Some(WindowEvent::KeyboardAction(KBAction::KeyPress( - input.scancode, - ))), - ElementState::Released => Some(WindowEvent::KeyboardAction( - KBAction::KeyRelease(input.scancode), - )), - }, - WinitWindowEvent::CursorMoved { position, .. } => Some(WindowEvent::MouseMoved( - position.x as f32, - position.y as f32, - )), - WinitWindowEvent::CursorLeft { .. } => Some(WindowEvent::MouseLeft), - WinitWindowEvent::MouseInput { button, state, .. } => { - let mb: stockton_input::MouseButton = match button { - MouseButton::Left => stockton_input::MouseButton::Left, - MouseButton::Right => stockton_input::MouseButton::Right, - MouseButton::Middle => stockton_input::MouseButton::Middle, - MouseButton::Other(x) => stockton_input::MouseButton::Other(*x), - }; - - match state { - ElementState::Pressed => { - Some(WindowEvent::MouseAction(KBAction::MousePress(mb))) - } - ElementState::Released => { - Some(WindowEvent::MouseAction(KBAction::MouseRelease(mb))) - } - } - } - _ => None, - }, - _ => None, - } - } -} - -pub struct UiState { - ctx: CtxRef, - raw_input: RawInput, - frame_active: bool, - - modifiers: Modifiers, - pointer_pos: Pos2, -} - -impl UiState { - pub fn new() -> Self { - UiState { - ctx: CtxRef::default(), - raw_input: RawInput::default(), - frame_active: false, - modifiers: Default::default(), - pointer_pos: Pos2::new(0.0, 0.0), - } - } - - pub fn populate_initial_state<'a, T: DrawPass>(&mut self, renderer: &Renderer) { - let props = &renderer.context.target_chain.properties; - self.set_dimensions(props.extent.width, props.extent.height); - self.set_pixels_per_point(Some(renderer.context.pixels_per_point)); - debug!("{:?}", self.raw_input); - } - - #[inline] - pub fn ctx(&mut self) -> &CtxRef { - if !self.frame_active { - self.begin_frame() - } - &self.ctx - } - - #[inline] - fn begin_frame(&mut self) { - #[allow(deprecated)] - let new_raw_input = RawInput { - scroll_delta: Vec2::new(0.0, 0.0), - zoom_delta: 0.0, - screen_size: self.raw_input.screen_size, - screen_rect: self.raw_input.screen_rect, - pixels_per_point: self.raw_input.pixels_per_point, - time: self.raw_input.time, - predicted_dt: self.raw_input.predicted_dt, - modifiers: self.modifiers, - events: Vec::new(), - }; - self.ctx.begin_frame(self.raw_input.take()); - self.raw_input = new_raw_input; - self.frame_active = true; - } - - #[inline] - pub(crate) fn end_frame(&mut self) -> (Output, Vec) { - self.frame_active = false; - self.ctx.end_frame() - } - - #[inline] - pub fn dimensions(&self) -> Option { - Some(self.raw_input.screen_rect?.size()) - } - - fn set_mouse_pos(&mut self, x: f32, y: f32) { - self.raw_input - .events - .push(Event::PointerMoved(Pos2::new(x, y))); - - self.pointer_pos = Pos2::new(x, y); - } - - fn set_mouse_left(&mut self) { - self.raw_input.events.push(Event::PointerGone); - } - - fn set_dimensions(&mut self, w: u32, h: u32) { - self.raw_input.screen_rect = - Some(Rect::from_x_y_ranges(0.0..=(w as f32), 0.0..=(h as f32))); - } - fn set_pixels_per_point(&mut self, ppp: Option) { - debug!("Using {:?} pixels per point", ppp); - self.raw_input.pixels_per_point = ppp; - } - - fn handle_action(&mut self, action: KBAction) { - // TODO - match action { - KBAction::MousePress(stockton_input::MouseButton::Left) => { - self.raw_input.events.push(Event::PointerButton { - pos: self.pointer_pos, - button: egui::PointerButton::Primary, - pressed: true, - modifiers: self.modifiers, - }); - } - _ => (), - } - } -} - -#[system] -/// A system to process the window events sent to renderer by the winit event loop. -pub fn _process_window_events( - #[resource] renderer: &mut Renderer, - #[resource] manager: &mut T, - #[resource] mouse: &mut Mouse, - #[resource] ui_state: &mut UiState, - #[state] actions_buf: &mut Vec, -) { - let mut actions_buf_cursor = 0; - let mut mouse_delta = mouse.abs; - - while let Ok(event) = renderer.window_events.try_recv() { - match event { - WindowEvent::SizeChanged(w, h) => { - if let Err(err) = renderer.resize() { - error!("{}", full_error_display(err)); - }; - ui_state.set_dimensions(w, h); - } - WindowEvent::CloseRequested => { - let mut flow = renderer.update_control_flow.write().unwrap(); - // TODO: Let everything know this is our last frame - *flow = ControlFlow::Exit; - } - WindowEvent::KeyboardAction(action) => { - if actions_buf_cursor >= actions_buf.len() { - actions_buf.push(action); - } else { - actions_buf[actions_buf_cursor] = action; - } - actions_buf_cursor += 1; - - ui_state.handle_action(action); - } - WindowEvent::MouseMoved(x, y) => { - mouse_delta.x = x; - mouse_delta.y = y; - - ui_state.set_mouse_pos(x, y); - } - WindowEvent::MouseLeft => { - ui_state.set_mouse_left(); - } - WindowEvent::MouseAction(action) => { - if actions_buf_cursor >= actions_buf.len() { - actions_buf.push(action); - } else { - actions_buf[actions_buf_cursor] = action; - } - actions_buf_cursor += 1; - - ui_state.handle_action(action); - } - }; - } - - mouse.handle_frame(mouse_delta); - - manager.handle_frame(&actions_buf[0..actions_buf_cursor]); -} - -pub fn process_window_events_system( -) -> impl Runnable { - _process_window_events_system::(Vec::with_capacity(4)) -} -- cgit v1.2.3