diff options
author | tcmal <me@aria.rip> | 2024-08-25 17:44:22 +0100 |
---|---|---|
committer | tcmal <me@aria.rip> | 2024-08-25 17:44:22 +0100 |
commit | c48b54f3fb7bbe9046915eb99eca02fa84dc55c9 (patch) | |
tree | 752831451d2bd3a658485df724a01ae39e80fae3 /stockton-render | |
parent | b437109ebf4da243fd643f0a31546d0d0155b0a4 (diff) |
feat(render): multithreaded texture loading
also a bunch of supporting changes
Diffstat (limited to 'stockton-render')
26 files changed, 1535 insertions, 1282 deletions
diff --git a/stockton-render/Cargo.toml b/stockton-render/Cargo.toml index 2840015..5982a22 100644 --- a/stockton-render/Cargo.toml +++ b/stockton-render/Cargo.toml @@ -18,6 +18,7 @@ image = "0.23.11" legion = { version = "^0.3" } egui = "^0.2" rendy-memory = "0.5.2" +rendy-descriptor = "0.5.1" [features] default = ["vulkan"] diff --git a/stockton-render/src/culling.rs b/stockton-render/src/culling.rs index 8ee2877..9b5844c 100644 --- a/stockton-render/src/culling.rs +++ b/stockton-render/src/culling.rs @@ -19,12 +19,12 @@ #![allow(dead_code)] use stockton_levels::prelude::*; -use stockton_levels::traits::tree::{BSPNode, BSPNodeValue}; +use stockton_levels::traits::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<X: CoordSystem, T: MinBSPFeatures<X>>(pos: Vector3, file: &T) -> Vec<u32> { +pub fn get_visible_faces<X: CoordSystem, T: MinBspFeatures<X>>(pos: Vector3, file: &T) -> Vec<u32> { let vis_cluster = get_cluster_id(pos, file); let mut visible = Vec::with_capacity(file.faces_len() as usize); @@ -43,16 +43,16 @@ pub fn get_visible_faces<X: CoordSystem, T: MinBSPFeatures<X>>(pos: Vector3, fil visible } -pub fn walk_bsp_tree<X: CoordSystem, T: MinBSPFeatures<X>>( - node: &BSPNode, +pub fn walk_bsp_tree<X: CoordSystem, T: MinBspFeatures<X>>( + node: &BspNode, vis_cluster: u32, visible_faces: &mut Vec<u32>, file: &T, ) { - if let BSPNodeValue::Children(front, back) = &node.value { + 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 { + } else if let BspNodeValue::Leaf(leaf) = &node.value { if (leaf.cluster_id & 0x80000000) != 0 { // Negative means invalid leaf return; @@ -66,9 +66,9 @@ pub fn walk_bsp_tree<X: CoordSystem, T: MinBSPFeatures<X>>( } /// Get the viscluster pos lies in -fn get_cluster_id<X: CoordSystem, T: MinBSPFeatures<X>>(pos: Vector3, file: &T) -> u32 { +fn get_cluster_id<X: CoordSystem, T: MinBspFeatures<X>>(pos: Vector3, file: &T) -> u32 { let mut node = file.get_bsp_root(); - while let BSPNodeValue::Children(front, back) = &node.value { + while let BspNodeValue::Children(front, back) = &node.value { let plane = file.get_plane(node.plane_idx); let dist = plane.normal.dot(&pos) - plane.dist; @@ -79,7 +79,7 @@ fn get_cluster_id<X: CoordSystem, T: MinBSPFeatures<X>>(pos: Vector3, file: &T) } } - if let BSPNodeValue::Leaf(leaf) = &node.value { + 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/camera.rs b/stockton-render/src/draw/camera.rs index aa5efac..15692ac 100644 --- a/stockton-render/src/draw/camera.rs +++ b/stockton-render/src/draw/camera.rs @@ -21,6 +21,7 @@ use legion::maybe_changed; use nalgebra_glm::look_at_lh; use nalgebra_glm::perspective_lh_zo; +use stockton_levels::prelude::{MinBspFeatures, VulkanSystem}; use crate::Renderer; use stockton_types::components::{CameraSettings, Transform}; @@ -40,10 +41,10 @@ fn euler_to_direction(euler: &Vector3) -> Vector3 { #[system(for_each)] #[filter(maybe_changed::<Transform>() | maybe_changed::<CameraSettings>())] -pub fn calc_vp_matrix( +pub fn calc_vp_matrix<M: 'static + MinBspFeatures<VulkanSystem>>( transform: &Transform, settings: &CameraSettings, - #[resource] renderer: &mut Renderer<'static>, + #[resource] renderer: &mut Renderer<'static, M>, ) { let ratio = renderer.context.target_chain.properties.extent.width as f32 / renderer.context.target_chain.properties.extent.height as f32; @@ -54,7 +55,7 @@ pub fn calc_vp_matrix( let view_matrix = look_at_lh( &transform.position, &(transform.position + direction), - &Vector3::new(0.0, 1.0, 0.0), // TODO + &Vector3::new(0.0, 1.0, 0.0), ); // Converts camera space to screen space diff --git a/stockton-render/src/draw/context.rs b/stockton-render/src/draw/context.rs index 139c59a..b65e078 100644 --- a/stockton-render/src/draw/context.rs +++ b/stockton-render/src/draw/context.rs @@ -19,7 +19,7 @@ //! In the end, this takes in a depth-sorted list of faces and a map file and renders them. //! You'll need something else to actually find/sort the faces though. -use std::{mem::ManuallyDrop, ops::Deref}; +use std::{mem::ManuallyDrop, pin::Pin}; use arrayvec::ArrayVec; use hal::{pool::CommandPoolCreateFlags, prelude::*}; @@ -30,27 +30,32 @@ use winit::window::Window; use super::{ buffer::ModifiableBuffer, - draw_buffers::{DrawBuffers, UVPoint}, + draw_buffers::{DrawBuffers, UvPoint}, pipeline::CompletePipeline, render::do_render, target::{SwapchainProperties, TargetChain}, - texture::TextureStore, - ui::{do_render as do_render_ui, ensure_textures as ensure_textures_ui, UIPipeline, UIPoint}, + texture::{resolver::BasicFsResolver, TextureRepo}, + ui::{ + do_render as do_render_ui, ensure_textures as ensure_textures_ui, UiPipeline, UiPoint, + UiTextures, + }, utils::find_memory_type_id, }; -use crate::{error, types::*, window::UIState}; +use crate::{error, types::*, window::UiState}; use stockton_levels::prelude::*; /// 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<'a> { +pub struct RenderingContext<'a, M: 'static + MinBspFeatures<VulkanSystem>> { + pub map: Pin<Box<M>>, + // Parents for most of these things /// Vulkan Instance instance: ManuallyDrop<back::Instance>, /// Device we're using - device: ManuallyDrop<Device>, + device: Pin<Box<Device>>, /// Adapter we're using adapter: Adapter, @@ -66,7 +71,7 @@ pub struct RenderingContext<'a> { pipeline: ManuallyDrop<CompletePipeline>, /// 2D Graphics pipeline and associated objects - ui_pipeline: ManuallyDrop<UIPipeline>, + ui_pipeline: ManuallyDrop<UiPipeline>, // Command pool and buffers /// The command pool used for our buffers @@ -75,17 +80,17 @@ pub struct RenderingContext<'a> { /// The queue group our buffers belong to queue_group: QueueGroup, - /// Texture store - texture_store: ManuallyDrop<TextureStore>, + /// Main Texture repo + tex_repo: ManuallyDrop<TextureRepo<'a>>, - /// Texture store for UI - ui_texture_store: ManuallyDrop<TextureStore>, + /// UI Texture repo + ui_tex_repo: ManuallyDrop<TextureRepo<'a>>, /// Buffers used for drawing - draw_buffers: ManuallyDrop<DrawBuffers<'a, UVPoint>>, + draw_buffers: ManuallyDrop<DrawBuffers<'a, UvPoint>>, /// Buffers used for drawing the UI - ui_draw_buffers: ManuallyDrop<DrawBuffers<'a, UIPoint>>, + ui_draw_buffers: ManuallyDrop<DrawBuffers<'a, UiPoint>>, /// Memory allocator used for any sort of textures / maps /// Guaranteed suitable for 2D RGBA images with `Optimal` tiling and `Usage::Sampled` @@ -97,9 +102,11 @@ pub struct RenderingContext<'a> { pub(crate) pixels_per_point: f32, } -impl<'a> RenderingContext<'a> { +impl<'a, M: 'static + MinBspFeatures<VulkanSystem>> RenderingContext<'a, M> { /// Create a new RenderingContext for the given window. - pub fn new<T: HasTextures>(window: &Window, file: &T) -> Result<Self, error::CreationError> { + pub fn new(window: &Window, map: M) -> Result<Self, error::CreationError> { + let map = Box::pin(map); + // Create surface let (instance, mut surface, mut adapters) = unsafe { use hal::Instance; @@ -115,10 +122,10 @@ impl<'a> RenderingContext<'a> { }; // TODO: Properly figure out which adapter to use - let mut adapter = adapters.remove(0); + let adapter = adapters.remove(0); // Device & Queue group - let (mut device, mut queue_group) = { + let (mut device, queue_group) = { let family = adapter .queue_families .iter() @@ -134,7 +141,7 @@ impl<'a> RenderingContext<'a> { .unwrap() }; - (gpu.device, gpu.queue_groups.pop().unwrap()) + (Box::pin(gpu.device), gpu.queue_groups.pop().unwrap()) }; // Figure out what our swapchain will look like @@ -159,7 +166,7 @@ impl<'a> RenderingContext<'a> { let ui_draw_buffers = DrawBuffers::new(&mut device, &adapter)?; // Memory allocators - let mut texture_allocator = unsafe { + let texture_allocator = unsafe { use hal::{ format::Format, image::{Kind, Tiling, Usage, ViewCapabilities}, @@ -204,31 +211,32 @@ impl<'a> RenderingContext<'a> { ) }; - // Texture store - let texture_store = TextureStore::new( - &mut device, - &mut adapter, - &mut texture_allocator, - &mut queue_group.queues[0], - &mut cmd_pool, - file, - )?; + // Texture repos + let long_device_pointer = unsafe { &mut *(&mut *device as *mut Device) }; + let long_texs_pointer: &'static M = unsafe { &*(&*map as *const M) }; - // Texture store for UI elements - let ui_texture_store = TextureStore::new_empty( - &mut device, - &mut adapter, - &mut texture_allocator, - &mut queue_group.queues[0], - &mut cmd_pool, - 1, // TODO - )?; + let tex_repo = TextureRepo::new( + long_device_pointer, + &adapter, + long_texs_pointer, + BasicFsResolver::new(std::path::Path::new(".")), + ) + .unwrap(); // TODO + let long_device_pointer = unsafe { &mut *(&mut *device as *mut Device) }; + + let ui_tex_repo = TextureRepo::new( + long_device_pointer, + &adapter, + &UiTextures, + BasicFsResolver::new(std::path::Path::new(".")), + ) + .unwrap(); // TODO let mut descriptor_set_layouts: ArrayVec<[_; 2]> = ArrayVec::new(); - descriptor_set_layouts.push(texture_store.descriptor_set_layout.deref()); + descriptor_set_layouts.push(tex_repo.get_ds_layout()); let mut ui_descriptor_set_layouts: ArrayVec<[_; 2]> = ArrayVec::new(); - ui_descriptor_set_layouts.push(ui_texture_store.descriptor_set_layout.deref()); + ui_descriptor_set_layouts.push(tex_repo.get_ds_layout()); // Graphics pipeline let pipeline = CompletePipeline::new( @@ -239,7 +247,7 @@ impl<'a> RenderingContext<'a> { )?; // UI pipeline - let ui_pipeline = UIPipeline::new( + let ui_pipeline = UiPipeline::new( &mut device, swapchain_properties.extent, &swapchain_properties, @@ -260,10 +268,11 @@ impl<'a> RenderingContext<'a> { .map_err(error::CreationError::TargetChainCreationError)?; Ok(RenderingContext { + map, instance: ManuallyDrop::new(instance), surface: ManuallyDrop::new(surface), - device: ManuallyDrop::new(device), + device, adapter, queue_group, @@ -273,13 +282,12 @@ impl<'a> RenderingContext<'a> { pipeline: ManuallyDrop::new(pipeline), ui_pipeline: ManuallyDrop::new(ui_pipeline), - texture_store: ManuallyDrop::new(texture_store), + tex_repo: ManuallyDrop::new(tex_repo), + ui_tex_repo: ManuallyDrop::new(ui_tex_repo), draw_buffers: ManuallyDrop::new(draw_buffers), ui_draw_buffers: ManuallyDrop::new(ui_draw_buffers), - ui_texture_store: ManuallyDrop::new(ui_texture_store), - texture_allocator: ManuallyDrop::new(texture_allocator), vp_matrix: Mat4::identity(), @@ -304,7 +312,7 @@ impl<'a> RenderingContext<'a> { ManuallyDrop::into_inner(read(&self.pipeline)).deactivate(&mut self.device); self.pipeline = ManuallyDrop::new({ let mut descriptor_set_layouts: ArrayVec<[_; 2]> = ArrayVec::new(); - descriptor_set_layouts.push(self.texture_store.descriptor_set_layout.deref()); + descriptor_set_layouts.push(self.tex_repo.get_ds_layout()); CompletePipeline::new( &mut self.device, @@ -319,9 +327,9 @@ impl<'a> RenderingContext<'a> { ManuallyDrop::into_inner(read(&self.ui_pipeline)).deactivate(&mut self.device); self.ui_pipeline = ManuallyDrop::new({ let mut descriptor_set_layouts: ArrayVec<[_; 1]> = ArrayVec::new(); - descriptor_set_layouts.push(self.ui_texture_store.descriptor_set_layout.deref()); + descriptor_set_layouts.push(self.ui_tex_repo.get_ds_layout()); - UIPipeline::new( + UiPipeline::new( &mut self.device, properties.extent, &properties, @@ -349,15 +357,10 @@ impl<'a> RenderingContext<'a> { } /// Draw all vertices in the buffer - pub fn draw_vertices<M: MinBSPFeatures<VulkanSystem>>( - &mut self, - file: &M, - ui: &mut UIState, - faces: &[u32], - ) -> Result<(), &'static str> { + pub fn draw_vertices(&mut self, ui: &mut UiState, faces: &[u32]) -> Result<(), &'static str> { // Ensure UI texture(s) are loaded ensure_textures_ui( - &mut self.ui_texture_store, + &mut self.ui_tex_repo, ui, &mut self.device, &mut self.adapter, @@ -366,6 +369,9 @@ impl<'a> RenderingContext<'a> { &mut self.cmd_pool, ); + // Get any textures that just finished loading + self.tex_repo.process_responses(); + // 3D Pass let cmd_buffer = self.target_chain.prep_next_target( &mut self.device, @@ -376,9 +382,9 @@ impl<'a> RenderingContext<'a> { do_render( cmd_buffer, &mut self.draw_buffers, - &self.texture_store, + &mut self.tex_repo, &self.pipeline.pipeline_layout, - file, + &*self.map, faces, ); @@ -390,7 +396,7 @@ impl<'a> RenderingContext<'a> { cmd_buffer, &self.ui_pipeline.pipeline_layout, &mut self.ui_draw_buffers, - &mut self.ui_texture_store, + &mut self.ui_tex_repo, ui, ); @@ -427,7 +433,7 @@ impl<'a> RenderingContext<'a> { } } -impl<'a> core::ops::Drop for RenderingContext<'a> { +impl<'a, M: MinBspFeatures<VulkanSystem>> core::ops::Drop for RenderingContext<'a, M> { fn drop(&mut self) { self.device.wait_idle().unwrap(); @@ -436,10 +442,8 @@ impl<'a> core::ops::Drop for RenderingContext<'a> { ManuallyDrop::into_inner(read(&self.draw_buffers)).deactivate(&mut self.device); ManuallyDrop::into_inner(read(&self.ui_draw_buffers)).deactivate(&mut self.device); - ManuallyDrop::into_inner(read(&self.texture_store)) - .deactivate(&mut self.device, &mut self.texture_allocator); - ManuallyDrop::into_inner(read(&self.ui_texture_store)) - .deactivate(&mut self.device, &mut self.texture_allocator); + ManuallyDrop::into_inner(read(&self.tex_repo)).deactivate(&mut self.device); + ManuallyDrop::into_inner(read(&self.ui_tex_repo)).deactivate(&mut self.device); ManuallyDrop::into_inner(read(&self.texture_allocator)).dispose(); @@ -454,8 +458,6 @@ impl<'a> core::ops::Drop for RenderingContext<'a> { self.instance .destroy_surface(ManuallyDrop::into_inner(read(&self.surface))); - - ManuallyDrop::drop(&mut self.device); } } } diff --git a/stockton-render/src/draw/depth_buffer.rs b/stockton-render/src/draw/depth_buffer.rs new file mode 100644 index 0000000..14b4d30 --- /dev/null +++ b/stockton-render/src/draw/depth_buffer.rs @@ -0,0 +1,298 @@ +use crate::draw::buffer::create_buffer; +use gfx_hal::{format::Aspects, memory::Properties, queue::Submission, MemoryTypeId}; +use hal::{ + buffer::Usage as BufUsage, + format::{Format, Swizzle}, + image::{SubresourceRange, Usage, ViewKind}, + memory, +}; +use std::convert::TryInto; + +use crate::types::*; +use hal::prelude::*; +use std::mem::ManuallyDrop; + +use super::texture::{LoadableImage, PIXEL_SIZE}; + +/// Holds an image that's loaded into GPU memory dedicated only to that image, bypassing the memory allocator. +pub struct DedicatedLoadedImage { + /// The GPU Image handle + image: ManuallyDrop<Image>, + + /// The full view of the image + pub image_view: ManuallyDrop<ImageView>, + + /// The memory backing the image + memory: ManuallyDrop<Memory>, +} + +impl DedicatedLoadedImage { + pub fn new( + device: &mut Device, + adapter: &Adapter, + format: Format, + usage: Usage, + resources: SubresourceRange, + width: usize, + height: usize, + ) -> Result<DedicatedLoadedImage, &'static str> { + let (memory, image_ref) = { + // Round up the size to align properly + let initial_row_size = PIXEL_SIZE * width; + let limits = adapter.physical_device.limits(); + let row_alignment_mask = limits.optimal_buffer_copy_pitch_alignment as u32 - 1; + + let row_size = + ((initial_row_size as u32 + row_alignment_mask) & !row_alignment_mask) as usize; + debug_assert!(row_size as usize >= initial_row_size); + + // Make the image + let mut image_ref = unsafe { + use hal::image::{Kind, Tiling, ViewCapabilities}; + + device.create_image( + Kind::D2(width as u32, height as u32, 1, 1), + 1, + format, + Tiling::Optimal, + usage, + ViewCapabilities::empty(), + ) + } + .map_err(|_| "Couldn't create image")?; + + // Allocate memory + + // Allocate memory + let memory = unsafe { + let requirements = device.get_image_requirements(&image_ref); + + let memory_type_id = adapter + .physical_device + .memory_properties() + .memory_types + .iter() + .enumerate() + .find(|&(id, memory_type)| { + requirements.type_mask & (1 << id) != 0 + && memory_type.properties.contains(Properties::DEVICE_LOCAL) + }) + .map(|(id, _)| MemoryTypeId(id)) + .ok_or("Couldn't find a memory type for image memory")?; + + let memory = device + .allocate_memory(memory_type_id, requirements.size) + .map_err(|_| "Couldn't allocate image memory")?; + + device + .bind_image_memory(&memory, 0, &mut image_ref) + .map_err(|_| "Couldn't bind memory to image")?; + + Ok(memory) + }?; + + Ok((memory, image_ref)) + }?; + + // Create ImageView and sampler + let image_view = unsafe { + device.create_image_view(&image_ref, ViewKind::D2, format, Swizzle::NO, resources) + } + .map_err(|_| "Couldn't create the image view!")?; + + Ok(DedicatedLoadedImage { + image: ManuallyDrop::new(image_ref), + image_view: ManuallyDrop::new(image_view), + memory: ManuallyDrop::new(memory), + }) + } + + /// Load the given image + pub fn load<T: LoadableImage>( + &mut self, + img: T, + device: &mut Device, + adapter: &Adapter, + command_queue: &mut CommandQueue, + command_pool: &mut CommandPool, + ) -> Result<(), &'static str> { + let initial_row_size = PIXEL_SIZE * img.width() as usize; + let limits = adapter.physical_device.limits(); + let row_alignment_mask = limits.optimal_buffer_copy_pitch_alignment as u32 - 1; + + let row_size = + ((initial_row_size as u32 + row_alignment_mask) & !row_alignment_mask) as usize; + let total_size = (row_size * (img.height() as usize)) as u64; + debug_assert!(row_size as usize >= initial_row_size); + + // Make a staging buffer + let (staging_buffer, staging_memory) = create_buffer( + device, + adapter, + BufUsage::TRANSFER_SRC, + memory::Properties::CPU_VISIBLE | memory::Properties::COHERENT, + total_size, + ) + .map_err(|_| "Couldn't create staging buffer")?; + + // Copy everything into it + unsafe { + let mapped_memory: *mut u8 = std::mem::transmute( + device + .map_memory(&staging_memory, 0..total_size) + .map_err(|_| "Couldn't map buffer memory")?, + ); + + for y in 0..img.height() as usize { + let dest_base: isize = (y * row_size).try_into().unwrap(); + img.copy_row(y as u32, mapped_memory.offset(dest_base)); + } + + device.unmap_memory(&staging_memory); + } + + // Copy from staging to image memory + let buf = unsafe { + use hal::command::{BufferImageCopy, CommandBufferFlags}; + use hal::image::{Access, Extent, Layout, Offset, SubresourceLayers}; + use hal::memory::Barrier; + use hal::pso::PipelineStage; + + // Get a command buffer + let mut buf = command_pool.allocate_one(hal::command::Level::Primary); + buf.begin_primary(CommandBufferFlags::ONE_TIME_SUBMIT); + + // Setup the layout of our image for copying + let image_barrier = Barrier::Image { + states: (Access::empty(), Layout::Undefined) + ..(Access::TRANSFER_WRITE, Layout::TransferDstOptimal), + target: &(*self.image), + families: None, + range: SubresourceRange { + aspects: Aspects::COLOR, + levels: 0..1, + layers: 0..1, + }, + }; + buf.pipeline_barrier( + PipelineStage::TOP_OF_PIPE..PipelineStage::TRANSFER, + memory::Dependencies::empty(), + &[image_barrier], + ); + + // Copy from buffer to image + buf.copy_buffer_to_image( + &staging_buffer, + &(*self.image), + Layout::TransferDstOptimal, + &[BufferImageCopy { + buffer_offset: 0, + buffer_width: (row_size / PIXEL_SIZE) as u32, + buffer_height: img.height(), + image_layers: SubresourceLayers { + aspects: Aspects::COLOR, + level: 0, + layers: 0..1, + }, + image_offset: Offset { x: 0, y: 0, z: 0 }, + image_extent: Extent { + width: img.width(), + height: img.height(), + depth: 1, + }, + }], + ); + + // Setup the layout of our image for shaders + let image_barrier = Barrier::Image { + states: (Access::TRANSFER_WRITE, Layout::TransferDstOptimal) + ..(Access::SHADER_READ, Layout::ShaderReadOnlyOptimal), + target: &(*self.image), + families: None, + range: SubresourceRange { + aspects: Aspects::COLOR, + levels: 0..1, + layers: 0..1, + }, + }; + + buf.pipeline_barrier( + PipelineStage::TRANSFER..PipelineStage::FRAGMENT_SHADER, + memory::Dependencies::empty(), + &[image_barrier], + ); + + buf.finish(); + + buf + }; + + // Submit our commands and wait for them to finish + unsafe { + let setup_finished = device.create_fence(false).unwrap(); + command_queue.submit::<_, _, Semaphore, _, _>( + Submission { + command_buffers: &[&buf], + wait_semaphores: std::iter::empty::<_>(), + signal_semaphores: std::iter::empty::<_>(), + }, + Some(&setup_finished), + ); + + device + .wait_for_fence(&setup_finished, core::u64::MAX) + .unwrap(); + device.destroy_fence(setup_finished); + }; + + // Clean up temp resources + unsafe { + command_pool.free(std::iter::once(buf)); + + device.free_memory(staging_memory); + device.destroy_buffer(staging_buffer); + } + + Ok(()) + } + + /// Load the given image into a new buffer + pub fn load_into_new<T: LoadableImage>( + img: T, + device: &mut Device, + adapter: &Adapter, + command_queue: &mut CommandQueue, + command_pool: &mut CommandPool, + format: Format, + usage: Usage, + ) -> Result<DedicatedLoadedImage, &'static str> { + let mut loaded_image = Self::new( + device, + adapter, + format, + usage | Usage::TRANSFER_DST, + SubresourceRange { + aspects: Aspects::COLOR, + levels: 0..1, + layers: 0..1, + }, + img.width() as usize, + img.height() as usize, + )?; + loaded_image.load(img, device, adapter, command_queue, command_pool)?; + + Ok(loaded_image) + } + + /// Properly frees/destroys all the objects in this struct + /// Dropping without doing this is a bad idea + pub fn deactivate(self, device: &mut Device) { + unsafe { + use core::ptr::read; + + device.destroy_image_view(ManuallyDrop::into_inner(read(&self.image_view))); + device.destroy_image(ManuallyDrop::into_inner(read(&self.image))); + device.free_memory(ManuallyDrop::into_inner(read(&self.memory))); + } + } +} diff --git a/stockton-render/src/draw/draw_buffers.rs b/stockton-render/src/draw/draw_buffers.rs index 02625ad..72029ba 100644 --- a/stockton-render/src/draw/draw_buffers.rs +++ b/stockton-render/src/draw/draw_buffers.rs @@ -22,7 +22,7 @@ use stockton_types::{Vector2, Vector3}; /// Represents a point of a triangle, including UV and texture information. #[derive(Debug, Clone, Copy)] -pub struct UVPoint(pub Vector3, pub i32, pub Vector2); +pub struct UvPoint(pub Vector3, pub i32, pub Vector2); /// Initial size of vertex buffer. TODO: Way of overriding this pub const INITIAL_VERT_SIZE: u64 = 3 * 3000; diff --git a/stockton-render/src/draw/macros.rs b/stockton-render/src/draw/macros.rs index e1ee515..1dd081d 100644 --- a/stockton-render/src/draw/macros.rs +++ b/stockton-render/src/draw/macros.rs @@ -28,9 +28,11 @@ /// ); /// ``` /// See the hal::pso::Format enum for possible types +#[allow(clippy::vec_init_then_push)] macro_rules! pipeline_vb_attributes { // Special case for single item ( $binding:expr, $firstSize:expr; $firstType:ident ) => ({ + #![allow(clippy::vec_init_then_push)] vec![ AttributeDesc { location: 0, diff --git a/stockton-render/src/draw/mod.rs b/stockton-render/src/draw/mod.rs index 27e41cf..05e3b2b 100644 --- a/stockton-render/src/draw/mod.rs +++ b/stockton-render/src/draw/mod.rs @@ -24,6 +24,7 @@ mod macros; mod buffer; mod camera; mod context; +mod depth_buffer; mod draw_buffers; mod pipeline; mod render; @@ -33,4 +34,4 @@ mod utils; pub use self::camera::calc_vp_matrix_system; pub use self::context::RenderingContext; -pub use self::draw_buffers::UVPoint; +pub use self::draw_buffers::UvPoint; diff --git a/stockton-render/src/draw/render.rs b/stockton-render/src/draw/render.rs index 093e257..b713a8d 100644 --- a/stockton-render/src/draw/render.rs +++ b/stockton-render/src/draw/render.rs @@ -17,8 +17,7 @@ use crate::draw::draw_buffers::INITIAL_INDEX_SIZE; use crate::draw::draw_buffers::INITIAL_VERT_SIZE; -use crate::draw::texture::TextureStore; -use crate::draw::UVPoint; +use crate::draw::UvPoint; use arrayvec::ArrayVec; use faces::FaceType; use hal::prelude::*; @@ -29,10 +28,32 @@ use stockton_types::Vector2; use crate::draw::draw_buffers::DrawBuffers; use crate::types::*; -pub fn do_render<M: MinBSPFeatures<VulkanSystem>>( +use super::texture::TextureRepo; + +fn draw_or_queue( + current_chunk: usize, + tex_repo: &mut TextureRepo, + cmd_buffer: &mut CommandBuffer, + pipeline_layout: &PipelineLayout, + chunk_start: u32, + curr_idx_idx: u32, +) { + if let Some(ds) = tex_repo.attempt_get_descriptor_set(current_chunk) { + let mut descriptor_sets: ArrayVec<[_; 1]> = ArrayVec::new(); + descriptor_sets.push(ds); + unsafe { + cmd_buffer.bind_graphics_descriptor_sets(pipeline_layout, 0, descriptor_sets, &[]); + cmd_buffer.draw_indexed(chunk_start * 3..(curr_idx_idx * 3) + 1, 0, 0..1); + } + } else { + tex_repo.queue_load(current_chunk).unwrap() + } +} + +pub fn do_render<M: MinBspFeatures<VulkanSystem>>( cmd_buffer: &mut CommandBuffer, - draw_buffers: &mut DrawBuffers<UVPoint>, - texture_store: &TextureStore, + draw_buffers: &mut DrawBuffers<UvPoint>, + tex_repo: &mut TextureRepo, pipeline_layout: &PipelineLayout, file: &M, faces: &[u32], @@ -46,17 +67,15 @@ pub fn do_render<M: MinBSPFeatures<VulkanSystem>>( for face in faces.iter().map(|idx| file.get_face(*idx)) { if current_chunk != face.texture_idx as usize / 8 { - // Last index was last of group, so draw it all. - let mut descriptor_sets: ArrayVec<[_; 1]> = ArrayVec::new(); - descriptor_sets.push(texture_store.get_chunk_descriptor_set(current_chunk)); - unsafe { - cmd_buffer.bind_graphics_descriptor_sets(pipeline_layout, 0, descriptor_sets, &[]); - cmd_buffer.draw_indexed( - chunk_start as u32 * 3..(curr_idx_idx as u32 * 3) + 1, - 0, - 0..1, - ); - } + // Last index was last of group, so draw it all if textures are loaded. + draw_or_queue( + current_chunk, + tex_repo, + cmd_buffer, + pipeline_layout, + chunk_start as u32, + curr_idx_idx as u32, + ); // Next group of same-chunked faces starts here. chunk_start = curr_idx_idx; @@ -74,7 +93,7 @@ pub fn do_render<M: MinBSPFeatures<VulkanSystem>>( let vert = &file.resolve_meshvert(idx2 as u32, base); let uv = Vector2::new(vert.tex.u[0], vert.tex.v[0]); - let uvp = UVPoint(vert.position, face.texture_idx.try_into().unwrap(), uv); + let uvp = UvPoint(vert.position, face.texture_idx.try_into().unwrap(), uv); draw_buffers.vertex_buffer[curr_vert_idx] = uvp; curr_vert_idx += 1; @@ -104,14 +123,12 @@ pub fn do_render<M: MinBSPFeatures<VulkanSystem>>( } // Draw the final group of chunks - let mut descriptor_sets: ArrayVec<[_; 1]> = ArrayVec::new(); - descriptor_sets.push(texture_store.get_chunk_descriptor_set(current_chunk)); - unsafe { - cmd_buffer.bind_graphics_descriptor_sets(&pipeline_layout, 0, descriptor_sets, &[]); - cmd_buffer.draw_indexed( - chunk_start as u32 * 3..(curr_idx_idx as u32 * 3) + 1, - 0, - 0..1, - ); - } + draw_or_queue( + current_chunk, + tex_repo, + cmd_buffer, + pipeline_layout, + chunk_start as u32, + curr_idx_idx as u32, + ); } diff --git a/stockton-render/src/draw/target.rs b/stockton-render/src/draw/target.rs index 2183a90..d7c070e 100644 --- a/stockton-render/src/draw/target.rs +++ b/stockton-render/src/draw/target.rs @@ -32,10 +32,10 @@ use na::Mat4; use super::{ buffer::ModifiableBuffer, - draw_buffers::{DrawBuffers, UVPoint}, + depth_buffer::DedicatedLoadedImage, + draw_buffers::{DrawBuffers, UvPoint}, pipeline::CompletePipeline, - texture::image::DedicatedLoadedImage, - ui::{UIPipeline, UIPoint}, + ui::{UiPipeline, UiPoint}, }; use crate::types::*; @@ -170,7 +170,7 @@ impl TargetChain { adapter: &Adapter, surface: &mut Surface, pipeline: &CompletePipeline, - ui_pipeline: &UIPipeline, + ui_pipeline: &UiPipeline, cmd_pool: &mut CommandPool, properties: SwapchainProperties, old_swapchain: Option<Swapchain>, @@ -288,7 +288,7 @@ impl TargetChain { pub fn prep_next_target<'a>( &'a mut self, device: &mut Device, - draw_buffers: &mut DrawBuffers<UVPoint>, + draw_buffers: &mut DrawBuffers<UvPoint>, pipeline: &CompletePipeline, vp: &Mat4, ) -> Result<&'a mut crate::types::CommandBuffer, &'static str> { @@ -386,8 +386,8 @@ impl TargetChain { pub fn target_2d_pass<'a>( &'a mut self, - draw_buffers: &mut DrawBuffers<UIPoint>, - pipeline: &UIPipeline, + draw_buffers: &mut DrawBuffers<UiPoint>, + pipeline: &UiPipeline, ) -> Result<&'a mut CommandBuffer, &'static str> { let target = &mut self.targets[self.last_image as usize]; @@ -487,7 +487,6 @@ impl TargetChain { .map_err(|_| "FrameError::PresentError")?; }; - // TODO Ok(()) } } diff --git a/stockton-render/src/draw/texture/block.rs b/stockton-render/src/draw/texture/block.rs new file mode 100644 index 0000000..7735f5c --- /dev/null +++ b/stockton-render/src/draw/texture/block.rs @@ -0,0 +1,63 @@ +use super::{loader::BlockRef, repo::BLOCK_SIZE}; +use crate::types::*; + +use arrayvec::ArrayVec; +use hal::prelude::*; +use rendy_memory::{Allocator, Block}; +use std::{iter::once, mem::ManuallyDrop}; + +pub struct TexturesBlock<B: Block<back::Backend>> { + pub id: BlockRef, + pub descriptor_set: ManuallyDrop<RDescriptorSet>, + pub imgs: ArrayVec<[LoadedImage<B>; BLOCK_SIZE]>, +} + +impl<B: Block<back::Backend>> TexturesBlock<B> { + pub fn deactivate<T: Allocator<back::Backend, Block = B>>( + mut self, + device: &mut Device, + tex_alloc: &mut T, + desc_alloc: &mut DescriptorAllocator, + ) { + unsafe { + use std::ptr::read; + + // Descriptor set + desc_alloc.free(once(read(&*self.descriptor_set))); + + // Images + self.imgs + .drain(..) + .map(|x| x.deactivate(device, tex_alloc)) + .for_each(|_| {}); + } + } +} + +pub struct LoadedImage<B: Block<back::Backend>> { + pub mem: ManuallyDrop<B>, + pub img: ManuallyDrop<Image>, + pub img_view: ManuallyDrop<ImageView>, + pub sampler: ManuallyDrop<Sampler>, + pub row_size: usize, + pub height: u32, + pub width: u32, +} + +impl<B: Block<back::Backend>> LoadedImage<B> { + pub fn deactivate<T: Allocator<back::Backend, Block = B>>( + self, + device: &mut Device, + alloc: &mut T, + ) { + unsafe { + use std::ptr::read; + + device.destroy_image_view(read(&*self.img_view)); + device.destroy_image(read(&*self.img)); + device.destroy_sampler(read(&*self.sampler)); + + alloc.free(device, read(&*self.mem)); + } + } +} diff --git a/stockton-render/src/draw/texture/chunk.rs b/stockton-render/src/draw/texture/chunk.rs deleted file mode 100644 index 0cb6737..0000000 --- a/stockton-render/src/draw/texture/chunk.rs +++ /dev/null @@ -1,207 +0,0 @@ -/* - * Copyright (C) Oscar Shrimpton 2020 - * - * This program is free software: you can redistribute it and/or modify it - * under the terms of the GNU General Public License as published by the Free - * Software Foundation, either version 3 of the License, or (at your option) - * any later version. - * - * This program is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for - * more details. - * - * You should have received a copy of the GNU General Public License along - * with this program. If not, see <http://www.gnu.org/licenses/>. - */ - -//! A chunk of textures is an array of textures, the size of which is known at compile time. -//! This reduces the number of times we need to re-bind our descriptor sets - -use crate::draw::texture::image::LoadableImage; -use hal::prelude::*; -use image::{Rgba, RgbaImage}; - -use core::mem::replace; -use std::ops::Deref; - -use crate::{error, types::*}; - -use super::image::SampledImage; -use super::resolver::TextureResolver; -use log::debug; -use std::iter::Iterator; -use stockton_levels::traits::textures::Texture; - -/// The size of a chunk. Needs to match up with the fragment shader -pub const CHUNK_SIZE: usize = 8; - -/// An array of textures -pub struct TextureChunk { - pub(crate) descriptor_set: DescriptorSet, - sampled_images: Vec<SampledImage>, -} - -impl TextureChunk { - /// Create a new empty texture chunk - pub fn new_empty( - device: &mut Device, - adapter: &mut Adapter, - allocator: &mut DynamicAllocator, - command_queue: &mut CommandQueue, - command_pool: &mut CommandPool, - descriptor_set: DescriptorSet, - ) -> Result<TextureChunk, error::CreationError> { - let mut store = TextureChunk { - descriptor_set, - sampled_images: Vec::with_capacity(CHUNK_SIZE), - }; - - for i in 0..CHUNK_SIZE { - debug!("Putting a placeholder in slot {}", i); - store - .put_texture( - RgbaImage::from_pixel(1, 1, Rgba([0, 0, 0, 1])), - i, - device, - adapter, - allocator, - command_queue, - command_pool, - ) - .unwrap(); - } - - Ok(store) - } - - /// Create a new texture chunk and load in the textures specified by `range` from `file` using `resolver` - /// Can error if the descriptor pool is too small or if a texture isn't found - pub fn new<'a, I, R: TextureResolver<T>, T: LoadableImage>( - device: &mut Device, - adapter: &mut Adapter, - allocator: &mut DynamicAllocator, - command_queue: &mut CommandQueue, - command_pool: &mut CommandPool, - descriptor_set: DescriptorSet, - textures: I, - resolver: &mut R, - ) -> Result<TextureChunk, error::CreationError> - where - I: 'a + Iterator<Item = &'a Texture>, - { - let mut store = TextureChunk { - descriptor_set, - sampled_images: Vec::with_capacity(CHUNK_SIZE), - }; - - let mut local_idx = 0; - - debug!("Created descriptor set"); - for tex in textures { - if let Some(img) = resolver.resolve(tex) { - store - .put_texture( - img, - local_idx, - device, - adapter, - allocator, - command_queue, - command_pool, - ) - .unwrap(); - } else { - // Texture not found. For now, tear everything down. - store.deactivate(device, allocator); - - return Err(error::CreationError::BadDataError); - } - - local_idx += 1; - } - - // Pad out the end if needed - while local_idx < CHUNK_SIZE { - debug!("Putting a placeholder in slot {}", local_idx); - store - .put_texture( - RgbaImage::from_pixel(1, 1, Rgba([0, 0, 0, 1])), - local_idx, - device, - adapter, - allocator, - command_queue, - command_pool, - ) - .unwrap(); - - local_idx += 1; - } - - Ok(store) - } - - pub fn put_texture<T: LoadableImage>( - &mut self, - image: T, - idx: usize, - device: &mut Device, - adapter: &mut Adapter, - allocator: &mut DynamicAllocator, - command_queue: &mut CommandQueue, - command_pool: &mut CommandPool, - ) -> Result<(), &'static str> { - // Load the image - let texture = SampledImage::load_into_new( - image, - device, - adapter, - allocator, - command_queue, - command_pool, - hal::format::Format::Rgba8Srgb, // TODO - hal::image::Usage::empty(), - )?; - - // Write it to the descriptor set - unsafe { - use hal::image::Layout; - use hal::pso::{Descriptor, DescriptorSetWrite}; - - device.write_descriptor_sets(vec![ - DescriptorSetWrite { - set: &self.descriptor_set, - binding: 0, - array_offset: idx, - descriptors: Some(Descriptor::Image( - texture.image.image_view.deref(), - Layout::ShaderReadOnlyOptimal, - )), - }, - DescriptorSetWrite { - set: &self.descriptor_set, - binding: 1, - array_offset: idx, - descriptors: Some(Descriptor::Sampler(texture.sampler.deref())), - }, - ]); - }; - - // Store it so we can safely deactivate it when we need to - // Deactivate the old image if we need to - if idx < self.sampled_images.len() { - replace(&mut self.sampled_images[idx], texture).deactivate(device, allocator); - } else { - self.sampled_images.push(texture); - } - - Ok(()) - } - - pub fn deactivate(mut self, device: &mut Device, allocator: &mut DynamicAllocator) { - for img in self.sampled_images.drain(..) { - img.deactivate(device, allocator); - } - } -} diff --git a/stockton-render/src/draw/texture/image.rs b/stockton-render/src/draw/texture/image.rs index 7cf84a7..aea1cb0 100644 --- a/stockton-render/src/draw/texture/image.rs +++ b/stockton-render/src/draw/texture/image.rs @@ -15,34 +15,19 @@ * with this program. If not, see <http://www.gnu.org/licenses/>. */ -use core::{ - mem::{size_of, ManuallyDrop}, - ptr::copy_nonoverlapping, -}; -use hal::{ - buffer::Usage as BufUsage, - format::{Aspects, Format, Swizzle}, - image::{SubresourceRange, Usage as ImgUsage, ViewKind}, - memory::{Dependencies as MemDependencies, Properties as MemProperties}, - prelude::*, - queue::Submission, - MemoryTypeId, -}; -use image::RgbaImage; -use rendy_memory::{Allocator, Block}; -use std::{convert::TryInto, iter::once}; +use super::PIXEL_SIZE; -use crate::draw::buffer::create_buffer; -use crate::types::*; +use core::ptr::copy_nonoverlapping; +use std::convert::TryInto; -/// The size of each pixel in an image -const PIXEL_SIZE: usize = size_of::<u8>() * 4; +use image::RgbaImage; /// An object that can be loaded as an image into GPU memory pub trait LoadableImage { fn width(&self) -> u32; fn height(&self) -> u32; fn copy_row(&self, y: u32, ptr: *mut u8); + unsafe fn copy_into(&self, ptr: *mut u8, row_size: usize); } impl LoadableImage for RgbaImage { @@ -63,617 +48,11 @@ impl LoadableImage for RgbaImage { copy_nonoverlapping(row.as_ptr(), ptr, row.len()); } } -} - -/// Holds an image that's loaded into GPU memory and can be sampled from -pub struct LoadedImage { - /// The GPU Image handle - image: ManuallyDrop<Image>, - - /// The full view of the image - pub image_view: ManuallyDrop<ImageView>, - - /// The memory backing the image - memory: ManuallyDrop<DynamicBlock>, -} - -pub fn create_image_view( - device: &mut Device, - adapter: &Adapter, - allocator: &mut DynamicAllocator, - format: Format, - usage: ImgUsage, - width: usize, - height: usize, -) -> Result<(DynamicBlock, Image), &'static str> { - // Round up the size to align properly - let initial_row_size = PIXEL_SIZE * width; - let limits = adapter.physical_device.limits(); - let row_alignment_mask = limits.optimal_buffer_copy_pitch_alignment as u32 - 1; - - let row_size = ((initial_row_size as u32 + row_alignment_mask) & !row_alignment_mask) as usize; - debug_assert!(row_size as usize >= initial_row_size); - - // Make the image - let mut image_ref = unsafe { - use hal::image::{Kind, Tiling, ViewCapabilities}; - - device.create_image( - Kind::D2(width as u32, height as u32, 1, 1), - 1, - format, - Tiling::Optimal, - usage, - ViewCapabilities::empty(), - ) - } - .map_err(|_| "Couldn't create image")?; - - // Allocate memory - let (block, _) = unsafe { - let requirements = device.get_image_requirements(&image_ref); - - allocator.alloc(device, requirements.size, requirements.alignment) - } - .map_err(|_| "Out of memory")?; - - unsafe { - device - .bind_image_memory(&block.memory(), block.range().start, &mut image_ref) - .map_err(|_| "Couldn't bind memory to image")?; - } - - Ok((block, image_ref)) -} - -impl LoadedImage { - pub fn new( - device: &mut Device, - adapter: &Adapter, - allocator: &mut DynamicAllocator, - format: Format, - usage: ImgUsage, - resources: SubresourceRange, - width: usize, - height: usize, - ) -> Result<LoadedImage, &'static str> { - let (memory, image_ref) = - create_image_view(device, adapter, allocator, format, usage, width, height)?; - - // Create ImageView and sampler - let image_view = unsafe { - device.create_image_view(&image_ref, ViewKind::D2, format, Swizzle::NO, resources) - } - .map_err(|_| "Couldn't create the image view!")?; - - Ok(LoadedImage { - image: ManuallyDrop::new(image_ref), - image_view: ManuallyDrop::new(image_view), - memory: ManuallyDrop::new(memory), - }) - } - - /// Load the given image - pub fn load<T: LoadableImage>( - &mut self, - img: T, - device: &mut Device, - adapter: &Adapter, - command_queue: &mut CommandQueue, - command_pool: &mut CommandPool, - ) -> Result<(), &'static str> { - let initial_row_size = PIXEL_SIZE * img.width() as usize; - let limits = adapter.physical_device.limits(); - let row_alignment_mask = limits.optimal_buffer_copy_pitch_alignment as u32 - 1; - - let row_size = - ((initial_row_size as u32 + row_alignment_mask) & !row_alignment_mask) as usize; - let total_size = (row_size * (img.height() as usize)) as u64; - debug_assert!(row_size as usize >= initial_row_size); - - // Make a staging buffer - let (staging_buffer, staging_memory) = create_buffer( - device, - adapter, - BufUsage::TRANSFER_SRC, - MemProperties::CPU_VISIBLE | MemProperties::COHERENT, - total_size, - ) - .map_err(|_| "Couldn't create staging buffer")?; - - // Copy everything into it - unsafe { - let mapped_memory: *mut u8 = std::mem::transmute( - device - .map_memory(&staging_memory, 0..total_size) - .map_err(|_| "Couldn't map buffer memory")?, - ); - - for y in 0..img.height() as usize { - let dest_base: isize = (y * row_size).try_into().unwrap(); - img.copy_row(y as u32, mapped_memory.offset(dest_base)); - } - - device.unmap_memory(&staging_memory); - } - - // Copy from staging to image memory - let buf = unsafe { - use hal::command::{BufferImageCopy, CommandBufferFlags}; - use hal::image::{Access, Extent, Layout, Offset, SubresourceLayers}; - use hal::memory::Barrier; - use hal::pso::PipelineStage; - - // Get a command buffer - let mut buf = command_pool.allocate_one(hal::command::Level::Primary); - buf.begin_primary(CommandBufferFlags::ONE_TIME_SUBMIT); - - // Setup the layout of our image for copying - let image_barrier = Barrier::Image { - states: (Access::empty(), Layout::Undefined) - ..(Access::TRANSFER_WRITE, Layout::TransferDstOptimal), - target: &(*self.image), - families: None, - range: SubresourceRange { - aspects: Aspects::COLOR, - levels: 0..1, - layers: 0..1, - }, - }; - buf.pipeline_barrier( - PipelineStage::TOP_OF_PIPE..PipelineStage::TRANSFER, - MemDependencies::empty(), - &[image_barrier], - ); - - // Copy from buffer to image - buf.copy_buffer_to_image( - &staging_buffer, - &(*self.image), - Layout::TransferDstOptimal, - &[BufferImageCopy { - buffer_offset: 0, - buffer_width: (row_size / PIXEL_SIZE) as u32, - buffer_height: img.height(), - image_layers: SubresourceLayers { - aspects: Aspects::COLOR, - level: 0, - layers: 0..1, - }, - image_offset: Offset { x: 0, y: 0, z: 0 }, - image_extent: Extent { - width: img.width(), - height: img.height(), - depth: 1, - }, - }], - ); - - // Setup the layout of our image for shaders - let image_barrier = Barrier::Image { - states: (Access::TRANSFER_WRITE, Layout::TransferDstOptimal) - ..(Access::SHADER_READ, Layout::ShaderReadOnlyOptimal), - target: &(*self.image), - families: None, - range: SubresourceRange { - aspects: Aspects::COLOR, - levels: 0..1, - layers: 0..1, - }, - }; - - buf.pipeline_barrier( - PipelineStage::TRANSFER..PipelineStage::FRAGMENT_SHADER, - MemDependencies::empty(), - &[image_barrier], - ); - - buf.finish(); - - buf - }; - - // Submit our commands and wait for them to finish - unsafe { - let setup_finished = device.create_fence(false).unwrap(); - command_queue.submit::<_, _, Semaphore, _, _>( - Submission { - command_buffers: &[&buf], - wait_semaphores: std::iter::empty::<_>(), - signal_semaphores: std::iter::empty::<_>(), - }, - Some(&setup_finished), - ); - - device - .wait_for_fence(&setup_finished, core::u64::MAX) - .unwrap(); - device.destroy_fence(setup_finished); - }; - - // Clean up temp resources - unsafe { - command_pool.free(once(buf)); - - device.free_memory(staging_memory); - device.destroy_buffer(staging_buffer); - } - - Ok(()) - } - - /// Properly frees/destroys all the objects in this struct - /// Dropping without doing this is a bad idea - pub fn deactivate(self, device: &mut Device, allocator: &mut DynamicAllocator) { - unsafe { - use core::ptr::read; - - device.destroy_image_view(ManuallyDrop::into_inner(read(&self.image_view))); - device.destroy_image(ManuallyDrop::into_inner(read(&self.image))); - allocator.free(device, ManuallyDrop::into_inner(read(&self.memory))); - } - } -} - -pub struct SampledImage { - pub image: ManuallyDrop<LoadedImage>, - pub sampler: ManuallyDrop<Sampler>, -} - -impl SampledImage { - pub fn new( - device: &mut Device, - adapter: &Adapter, - allocator: &mut DynamicAllocator, - format: Format, - usage: ImgUsage, - width: usize, - height: usize, - ) -> Result<SampledImage, &'static str> { - let image = LoadedImage::new( - device, - adapter, - allocator, - format, - usage | ImgUsage::SAMPLED, - SubresourceRange { - aspects: Aspects::COLOR, - levels: 0..1, - layers: 0..1, - }, - width, - height, - )?; - - let sampler = unsafe { - use hal::image::{Filter, SamplerDesc, WrapMode}; - - device.create_sampler(&SamplerDesc::new(Filter::Nearest, WrapMode::Tile)) - } - .map_err(|_| "Couldn't create the sampler!")?; - - Ok(SampledImage { - image: ManuallyDrop::new(image), - sampler: ManuallyDrop::new(sampler), - }) - } - - pub fn load_into_new<T: LoadableImage>( - img: T, - device: &mut Device, - adapter: &Adapter, - allocator: &mut DynamicAllocator, - command_queue: &mut CommandQueue, - command_pool: &mut CommandPool, - format: Format, - usage: ImgUsage, - ) -> Result<SampledImage, &'static str> { - let mut sampled_image = SampledImage::new( - device, - adapter, - allocator, - format, - usage | ImgUsage::TRANSFER_DST, - img.width() as usize, - img.height() as usize, - )?; - sampled_image - .image - .load(img, device, adapter, command_queue, command_pool)?; - - Ok(sampled_image) - } - - pub fn deactivate(self, device: &mut Device, allocator: &mut DynamicAllocator) { - unsafe { - use core::ptr::read; - - device.destroy_sampler(ManuallyDrop::into_inner(read(&self.sampler))); - - ManuallyDrop::into_inner(read(&self.image)).deactivate(device, allocator); - } - } -} - -/// Holds an image that's loaded into GPU memory dedicated only to that image, bypassing the memory allocator. -pub struct DedicatedLoadedImage { - /// The GPU Image handle - image: ManuallyDrop<Image>, - - /// The full view of the image - pub image_view: ManuallyDrop<ImageView>, - - /// The memory backing the image - memory: ManuallyDrop<Memory>, -} - -impl DedicatedLoadedImage { - pub fn new( - device: &mut Device, - adapter: &Adapter, - format: Format, - usage: ImgUsage, - resources: SubresourceRange, - width: usize, - height: usize, - ) -> Result<DedicatedLoadedImage, &'static str> { - let (memory, image_ref) = { - // Round up the size to align properly - let initial_row_size = PIXEL_SIZE * width; - let limits = adapter.physical_device.limits(); - let row_alignment_mask = limits.optimal_buffer_copy_pitch_alignment as u32 - 1; - - let row_size = - ((initial_row_size as u32 + row_alignment_mask) & !row_alignment_mask) as usize; - debug_assert!(row_size as usize >= initial_row_size); - - // Make the image - let mut image_ref = unsafe { - use hal::image::{Kind, Tiling, ViewCapabilities}; - - device.create_image( - Kind::D2(width as u32, height as u32, 1, 1), - 1, - format, - Tiling::Optimal, - usage, - ViewCapabilities::empty(), - ) - } - .map_err(|_| "Couldn't create image")?; - - // Allocate memory - - // Allocate memory - let memory = unsafe { - let requirements = device.get_image_requirements(&image_ref); - - let memory_type_id = adapter - .physical_device - .memory_properties() - .memory_types - .iter() - .enumerate() - .find(|&(id, memory_type)| { - requirements.type_mask & (1 << id) != 0 - && memory_type.properties.contains(MemProperties::DEVICE_LOCAL) - }) - .map(|(id, _)| MemoryTypeId(id)) - .ok_or("Couldn't find a memory type for image memory")?; - - let memory = device - .allocate_memory(memory_type_id, requirements.size) - .map_err(|_| "Couldn't allocate image memory")?; - - device - .bind_image_memory(&memory, 0, &mut image_ref) - .map_err(|_| "Couldn't bind memory to image")?; - - Ok(memory) - }?; - - Ok((memory, image_ref)) - }?; - - // Create ImageView and sampler - let image_view = unsafe { - device.create_image_view(&image_ref, ViewKind::D2, format, Swizzle::NO, resources) - } - .map_err(|_| "Couldn't create the image view!")?; - - Ok(DedicatedLoadedImage { - image: ManuallyDrop::new(image_ref), - image_view: ManuallyDrop::new(image_view), - memory: ManuallyDrop::new(memory), - }) - } - - /// Load the given image - pub fn load<T: LoadableImage>( - &mut self, - img: T, - device: &mut Device, - adapter: &Adapter, - command_queue: &mut CommandQueue, - command_pool: &mut CommandPool, - ) -> Result<(), &'static str> { - let initial_row_size = PIXEL_SIZE * img.width() as usize; - let limits = adapter.physical_device.limits(); - let row_alignment_mask = limits.optimal_buffer_copy_pitch_alignment as u32 - 1; - - let row_size = - ((initial_row_size as u32 + row_alignment_mask) & !row_alignment_mask) as usize; - let total_size = (row_size * (img.height() as usize)) as u64; - debug_assert!(row_size as usize >= initial_row_size); - - // Make a staging buffer - let (staging_buffer, staging_memory) = create_buffer( - device, - adapter, - BufUsage::TRANSFER_SRC, - MemProperties::CPU_VISIBLE | MemProperties::COHERENT, - total_size, - ) - .map_err(|_| "Couldn't create staging buffer")?; - - // Copy everything into it - unsafe { - let mapped_memory: *mut u8 = std::mem::transmute( - device - .map_memory(&staging_memory, 0..total_size) - .map_err(|_| "Couldn't map buffer memory")?, - ); - - for y in 0..img.height() as usize { - let dest_base: isize = (y * row_size).try_into().unwrap(); - img.copy_row(y as u32, mapped_memory.offset(dest_base)); - } - - device.unmap_memory(&staging_memory); - } - - // Copy from staging to image memory - let buf = unsafe { - use hal::command::{BufferImageCopy, CommandBufferFlags}; - use hal::image::{Access, Extent, Layout, Offset, SubresourceLayers}; - use hal::memory::Barrier; - use hal::pso::PipelineStage; - - // Get a command buffer - let mut buf = command_pool.allocate_one(hal::command::Level::Primary); - buf.begin_primary(CommandBufferFlags::ONE_TIME_SUBMIT); - - // Setup the layout of our image for copying - let image_barrier = Barrier::Image { - states: (Access::empty(), Layout::Undefined) - ..(Access::TRANSFER_WRITE, Layout::TransferDstOptimal), - target: &(*self.image), - families: None, - range: SubresourceRange { - aspects: Aspects::COLOR, - levels: 0..1, - layers: 0..1, - }, - }; - buf.pipeline_barrier( - PipelineStage::TOP_OF_PIPE..PipelineStage::TRANSFER, - MemDependencies::empty(), - &[image_barrier], - ); - - // Copy from buffer to image - buf.copy_buffer_to_image( - &staging_buffer, - &(*self.image), - Layout::TransferDstOptimal, - &[BufferImageCopy { - buffer_offset: 0, - buffer_width: (row_size / PIXEL_SIZE) as u32, - buffer_height: img.height(), - image_layers: SubresourceLayers { - aspects: Aspects::COLOR, - level: 0, - layers: 0..1, - }, - image_offset: Offset { x: 0, y: 0, z: 0 }, - image_extent: Extent { - width: img.width(), - height: img.height(), - depth: 1, - }, - }], - ); - - // Setup the layout of our image for shaders - let image_barrier = Barrier::Image { - states: (Access::TRANSFER_WRITE, Layout::TransferDstOptimal) - ..(Access::SHADER_READ, Layout::ShaderReadOnlyOptimal), - target: &(*self.image), - families: None, - range: SubresourceRange { - aspects: Aspects::COLOR, - levels: 0..1, - layers: 0..1, - }, - }; - - buf.pipeline_barrier( - PipelineStage::TRANSFER..PipelineStage::FRAGMENT_SHADER, - MemDependencies::empty(), - &[image_barrier], - ); - - buf.finish(); - - buf - }; - - // Submit our commands and wait for them to finish - unsafe { - let setup_finished = device.create_fence(false).unwrap(); - command_queue.submit::<_, _, Semaphore, _, _>( - Submission { - command_buffers: &[&buf], - wait_semaphores: std::iter::empty::<_>(), - signal_semaphores: std::iter::empty::<_>(), - }, - Some(&setup_finished), - ); - - device - .wait_for_fence(&setup_finished, core::u64::MAX) - .unwrap(); - device.destroy_fence(setup_finished); - }; - - // Clean up temp resources - unsafe { - command_pool.free(once(buf)); - - device.free_memory(staging_memory); - device.destroy_buffer(staging_buffer); - } - - Ok(()) - } - - /// Load the given image into a new buffer - pub fn load_into_new<T: LoadableImage>( - img: T, - device: &mut Device, - adapter: &Adapter, - command_queue: &mut CommandQueue, - command_pool: &mut CommandPool, - format: Format, - usage: ImgUsage, - ) -> Result<DedicatedLoadedImage, &'static str> { - let mut loaded_image = Self::new( - device, - adapter, - format, - usage | ImgUsage::TRANSFER_DST, - SubresourceRange { - aspects: Aspects::COLOR, - levels: 0..1, - layers: 0..1, - }, - img.width() as usize, - img.height() as usize, - )?; - loaded_image.load(img, device, adapter, command_queue, command_pool)?; - - Ok(loaded_image) - } - - /// Properly frees/destroys all the objects in this struct - /// Dropping without doing this is a bad idea - pub fn deactivate(self, device: &mut Device) { - unsafe { - use core::ptr::read; - device.destroy_image_view(ManuallyDrop::into_inner(read(&self.image_view))); - device.destroy_image(ManuallyDrop::into_inner(read(&self.image))); - device.free_memory(ManuallyDrop::into_inner(read(&self.memory))); + unsafe fn copy_into(&self, ptr: *mut u8, row_size: usize) { + for y in 0..self.height() as usize { + let dest_base: isize = (y * row_size).try_into().unwrap(); + self.copy_row(y as u32, ptr.offset(dest_base)); } } } diff --git a/stockton-render/src/draw/texture/load.rs b/stockton-render/src/draw/texture/load.rs new file mode 100644 index 0000000..011ae29 --- /dev/null +++ b/stockton-render/src/draw/texture/load.rs @@ -0,0 +1,352 @@ +use super::{ + block::LoadedImage, block::TexturesBlock, loader::TextureLoader, repo::BLOCK_SIZE, + resolver::TextureResolver, staging_buffer::StagingBuffer, LoadableImage, PIXEL_SIZE, +}; +use crate::types::*; +use stockton_levels::prelude::*; + +use arrayvec::ArrayVec; +use hal::{ + command::{BufferImageCopy, CommandBufferFlags}, + format::{Aspects, Format, Swizzle}, + image::{ + Extent, Filter, Layout, Offset, SamplerDesc, SubresourceLayers, SubresourceRange, + Usage as ImgUsage, ViewKind, WrapMode, + }, + memory::{Barrier, Dependencies}, + prelude::*, + pso::{Descriptor, DescriptorSetWrite, PipelineStage, ShaderStageFlags}, + queue::Submission, +}; +use rendy_descriptor::{DescriptorRanges, DescriptorSetLayoutBinding, DescriptorType}; +use rendy_memory::{Allocator, Block}; +use std::mem::ManuallyDrop; + +pub struct QueuedLoad<B: Block<back::Backend>> { + pub fence: Fence, + pub buf: CommandBuffer, + pub block: TexturesBlock<B>, + pub staging_bufs: ArrayVec<[StagingBuffer; BLOCK_SIZE]>, +} + +impl<B: Block<back::Backend>> QueuedLoad<B> { + pub fn dissolve( + self, + ) -> ( + (Fence, CommandBuffer), + ArrayVec<[StagingBuffer; BLOCK_SIZE]>, + TexturesBlock<B>, + ) { + ((self.fence, self.buf), self.staging_bufs, self.block) + } +} + +impl<'a, T: HasTextures, R: TextureResolver<I>, I: LoadableImage> TextureLoader<'a, T, R, I> { + const FORMAT: Format = Format::Rgba8Srgb; + const RESOURCES: SubresourceRange = SubresourceRange { + aspects: Aspects::COLOR, + levels: 0..1, + layers: 0..1, + }; + const LAYERS: SubresourceLayers = SubresourceLayers { + aspects: Aspects::COLOR, + level: 0, + layers: 0..1, + }; + + pub(crate) unsafe fn attempt_queue_load( + &mut self, + block_ref: usize, + ) -> Option<QueuedLoad<DynamicBlock>> { + // Get assets to use + let (fence, mut buf) = self.buffers.pop_front()?; + + // Create descriptor set + let descriptor_set = self.create_descriptor_set(); + + // Get a command buffer + buf.begin_primary(CommandBufferFlags::ONE_TIME_SUBMIT); + + let mut copy_cmds: ArrayVec<[_; BLOCK_SIZE]> = ArrayVec::new(); + let mut imgs: ArrayVec<[_; BLOCK_SIZE]> = ArrayVec::new(); + let mut staging_bufs: ArrayVec<[_; BLOCK_SIZE]> = ArrayVec::new(); + + // For each texture in block + for tex_idx in (block_ref * BLOCK_SIZE)..(block_ref + 1) * BLOCK_SIZE { + // Get texture and Resolve image + let tex = self.textures.get_texture(tex_idx as u32); + if tex.is_none() { + break; // Past the end + // TODO: We should actually write blank descriptors + } + let tex = tex.unwrap(); + + let img_data = self.resolver.resolve(tex).expect("Invalid texture"); + + // Calculate buffer size + let (row_size, total_size) = + tex_size_info(&img_data, self.optimal_buffer_copy_pitch_alignment); + + // Create staging buffer + let mut staging_buffer = StagingBuffer::new( + &mut self.device, + &mut self.staging_allocator, + total_size as u64, + self.staging_memory_type, + ) + .expect("Couldn't create staging buffer"); + + // Write to staging buffer + let mapped_memory = staging_buffer + .map_memory(&mut self.device) + .expect("Error mapping staged memory"); + + img_data.copy_into(mapped_memory, row_size); + + staging_buffer.unmap_memory(&mut self.device); + + // Create image + let (img_mem, img) = create_image_view( + &mut self.device, + &mut *self.tex_allocator, + Self::FORMAT, + ImgUsage::SAMPLED, + &img_data, + ) + .unwrap(); + + // Create image view + let img_view = self + .device + .create_image_view( + &img, + ViewKind::D2, + Self::FORMAT, + Swizzle::NO, + Self::RESOURCES, + ) + .expect("Error creating image view"); + + // Queue copy from buffer to image + copy_cmds.push(BufferImageCopy { + buffer_offset: 0, + buffer_width: (row_size / super::PIXEL_SIZE) as u32, + buffer_height: img_data.height(), + image_layers: Self::LAYERS, + image_offset: Offset { x: 0, y: 0, z: 0 }, + image_extent: Extent { + width: img_data.width(), + height: img_data.height(), + depth: 1, + }, + }); + + // Create sampler + let sampler = self + .device + .create_sampler(&SamplerDesc::new(Filter::Nearest, WrapMode::Tile)) + .expect("Failed to create sampler"); + + // Write to descriptor set + { + self.device.write_descriptor_sets(vec![ + DescriptorSetWrite { + set: descriptor_set.raw(), + binding: 0, + array_offset: tex_idx % BLOCK_SIZE, + descriptors: Some(Descriptor::Image( + &img_view, + Layout::ShaderReadOnlyOptimal, + )), + }, + DescriptorSetWrite { + set: descriptor_set.raw(), + binding: 1, + array_offset: tex_idx % BLOCK_SIZE, + descriptors: Some(Descriptor::Sampler(&sampler)), + }, + ]); + } + + imgs.push(LoadedImage { + mem: ManuallyDrop::new(img_mem), + img: ManuallyDrop::new(img), + img_view: ManuallyDrop::new(img_view), + sampler: ManuallyDrop::new(sampler), + row_size, + height: img_data.height(), + width: img_data.width(), + }); + + staging_bufs.push(staging_buffer); + } + + // Add start pipeline barriers + for li in imgs.iter() { + use hal::image::Access; + + buf.pipeline_barrier( + PipelineStage::TOP_OF_PIPE..PipelineStage::TRANSFER, + Dependencies::empty(), + &[Barrier::Image { + states: (Access::empty(), Layout::Undefined) + ..(Access::TRANSFER_WRITE, Layout::TransferDstOptimal), + target: &*li.img, + families: None, + range: SubresourceRange { + aspects: Aspects::COLOR, + levels: 0..1, + layers: 0..1, + }, + }], + ); + } + + // Record copy commands + for (li, sb) in imgs.iter().zip(staging_bufs.iter()) { + buf.copy_buffer_to_image( + &*sb.buf, + &*li.img, + Layout::TransferDstOptimal, + &[BufferImageCopy { + buffer_offset: 0, + buffer_width: (li.row_size / super::PIXEL_SIZE) as u32, + buffer_height: li.height, + image_layers: SubresourceLayers { + aspects: Aspects::COLOR, + level: 0, + layers: 0..1, + }, + image_offset: Offset { x: 0, y: 0, z: 0 }, + image_extent: gfx_hal::image::Extent { + width: li.width, + height: li.height, + depth: 1, + }, + }], + ); + } + for li in imgs.iter() { + use hal::image::Access; + + buf.pipeline_barrier( + PipelineStage::TOP_OF_PIPE..PipelineStage::TRANSFER, + Dependencies::empty(), + &[Barrier::Image { + states: (Access::TRANSFER_WRITE, Layout::TransferDstOptimal) + ..(Access::SHADER_READ, Layout::ShaderReadOnlyOptimal), + target: &*li.img, + families: None, + range: Self::RESOURCES, + }], + ); + } + + buf.finish(); + + // Submit command buffer + self.gpu.queue_groups[self.cmd_queue_idx].queues[0].submit::<_, _, Semaphore, _, _>( + Submission { + command_buffers: &[&buf], + signal_semaphores: std::iter::empty(), + wait_semaphores: std::iter::empty(), + }, + Some(&fence), + ); + + Some(QueuedLoad { + staging_bufs, + fence, + buf, + block: TexturesBlock { + id: block_ref, + imgs, + descriptor_set: ManuallyDrop::new(descriptor_set), + }, + }) + } + + pub(crate) unsafe fn create_descriptor_set(&mut self) -> RDescriptorSet { + let mut v: ArrayVec<[RDescriptorSet; 1]> = ArrayVec::new(); + self.descriptor_allocator + .allocate( + self.device, + &*self.ds_layout, + DescriptorRanges::from_bindings(&[ + DescriptorSetLayoutBinding { + binding: 0, + ty: DescriptorType::SampledImage, + count: BLOCK_SIZE, + stage_flags: ShaderStageFlags::FRAGMENT, + immutable_samplers: false, + }, + DescriptorSetLayoutBinding { + binding: 1, + ty: DescriptorType::Sampler, + count: BLOCK_SIZE, + stage_flags: ShaderStageFlags::FRAGMENT, + immutable_samplers: false, + }, + ]), + 1, + &mut v, + ) + .unwrap(); + + v.pop().unwrap() + } +} + +pub fn tex_size_info<T: LoadableImage>(img: &T, obcpa: hal::buffer::Offset) -> (usize, usize) { + let initial_row_size = PIXEL_SIZE * img.width() as usize; + let row_alignment_mask = obcpa as u32 - 1; + + let row_size = ((initial_row_size as u32 + row_alignment_mask) & !row_alignment_mask) as usize; + let total_size = (row_size * (img.height() as usize)) as u64; + debug_assert!(row_size as usize >= initial_row_size); + + (row_size, total_size as usize) +} + +fn create_image_view<T, I>( + device: &mut Device, + allocator: &mut T, + format: Format, + usage: ImgUsage, + img: &I, +) -> Result<(T::Block, Image), &'static str> +where + T: Allocator<back::Backend>, + I: LoadableImage, +{ + // Make the image + let mut image_ref = unsafe { + use hal::image::{Kind, Tiling, ViewCapabilities}; + + device.create_image( + Kind::D2(img.width(), img.height(), 1, 1), + 1, + format, + Tiling::Optimal, + usage, + ViewCapabilities::empty(), + ) + } + .map_err(|_| "Couldn't create image")?; + + // Allocate memory + let (block, _) = unsafe { + let requirements = device.get_image_requirements(&image_ref); + + allocator.alloc(device, requirements.size, requirements.alignment) + } + .map_err(|_| "Out of memory")?; + + unsafe { + device + .bind_image_memory(&block.memory(), block.range().start, &mut image_ref) + .map_err(|_| "Couldn't bind memory to image")?; + } + + Ok((block, image_ref)) +} diff --git a/stockton-render/src/draw/texture/loader.rs b/stockton-render/src/draw/texture/loader.rs index 0cfe0c3..33025a6 100644 --- a/stockton-render/src/draw/texture/loader.rs +++ b/stockton-render/src/draw/texture/loader.rs @@ -15,284 +15,357 @@ * with this program. If not, see <http://www.gnu.org/licenses/>. */ -//! Deals with loading textures into GPU memory +//! Manages the loading/unloading of textures -use super::chunk::TextureChunk; -use crate::draw::texture::chunk::CHUNK_SIZE; -use crate::draw::texture::image::LoadableImage; -use crate::draw::texture::resolver::BasicFSResolver; -use core::mem::ManuallyDrop; -use std::path::Path; +use super::{block::TexturesBlock, load::QueuedLoad, resolver::TextureResolver, LoadableImage}; +use crate::{draw::utils::find_memory_type_id, types::*}; -use log::debug; +use std::{ + collections::VecDeque, + marker::PhantomData, + mem::ManuallyDrop, + sync::mpsc::{Receiver, Sender}, + thread::sleep, + time::Duration, +}; -use hal::prelude::*; +use arrayvec::ArrayVec; +use hal::{ + format::Format, memory::Properties as MemProps, prelude::*, queue::family::QueueFamilyId, + MemoryTypeId, +}; +use log::*; +use rendy_memory::DynamicConfig; +use stockton_levels::prelude::HasTextures; -use stockton_levels::prelude::*; +/// The number of command buffers to have in flight simultaneously. +pub const NUM_SIMULTANEOUS_CMDS: usize = 2; -use crate::error; -use crate::types::*; +/// A reference to a texture of the current map +pub type BlockRef = usize; -/// Stores all loaded textures in GPU memory. -/// When rendering, the descriptor sets are bound to the buffer -/// The descriptor set layout should have the same count of textures as this does. -/// All descriptors will be properly initialised images. -pub struct TextureStore { - descriptor_pool: ManuallyDrop<DescriptorPool>, - pub(crate) descriptor_set_layout: ManuallyDrop<DescriptorSetLayout>, - chunks: Box<[TextureChunk]>, +/// Manages the loading/unloading of textures +/// This is expected to load the textures, then send the loaded blocks back +pub struct TextureLoader<'a, T, R, I> { + /// Handle to the device we're using + pub(crate) device: &'a mut Device, + + /// Blocks for which commands have been queued and are done loading once the fence is triggered. + pub(crate) commands_queued: ArrayVec<[QueuedLoad<DynamicBlock>; NUM_SIMULTANEOUS_CMDS]>, + + /// The command buffers used and a fence to go with them + pub(crate) buffers: VecDeque<(Fence, CommandBuffer)>, + + /// The command pool buffers were allocated from + pub(crate) pool: ManuallyDrop<CommandPool>, + + /// The GPU we're submitting to + pub(crate) gpu: ManuallyDrop<Gpu>, + + /// The index of the command queue being used + pub(crate) cmd_queue_idx: usize, + + /// The memory allocator being used for textures + pub(crate) tex_allocator: ManuallyDrop<DynamicAllocator>, + + /// The memory allocator for staging memory + pub(crate) staging_allocator: ManuallyDrop<DynamicAllocator>, + + /// Allocator for descriptor sets + pub(crate) descriptor_allocator: ManuallyDrop<DescriptorAllocator>, + + pub(crate) ds_layout: &'a DescriptorSetLayout, + + /// Type ID for staging memory + pub(crate) staging_memory_type: MemoryTypeId, + + /// From adapter, used for determining alignment + pub(crate) optimal_buffer_copy_pitch_alignment: hal::buffer::Offset, + + /// The textures lump to get info from + pub(crate) textures: &'a T, + + /// The resolver which gets image data for a given texture. + pub(crate) resolver: R, + + /// The channel requests come in. + /// Requests should reference a texture **block**, for example textures 8..16 is block 1. + pub(crate) request_channel: Receiver<LoaderRequest>, + + /// The channel blocks are returned to. + pub(crate) return_channel: Sender<TexturesBlock<DynamicBlock>>, + + pub(crate) _li: PhantomData<I>, } -impl TextureStore { - pub fn new_empty( - device: &mut Device, - adapter: &mut Adapter, - allocator: &mut DynamicAllocator, - command_queue: &mut CommandQueue, - command_pool: &mut CommandPool, - size: usize, - ) -> Result<TextureStore, error::CreationError> { - // Figure out how many textures in this file / how many chunks needed - let num_chunks = { - let mut x = size / CHUNK_SIZE; - if size % CHUNK_SIZE != 0 { - x += 1; - } - x - }; - let rounded_size = num_chunks * CHUNK_SIZE; - - // Descriptor pool, where we get our sets from - let mut descriptor_pool = unsafe { - use hal::pso::{DescriptorPoolCreateFlags, DescriptorRangeDesc, DescriptorType}; - - device - .create_descriptor_pool( - num_chunks, - &[ - DescriptorRangeDesc { - ty: DescriptorType::SampledImage, - count: rounded_size, - }, - DescriptorRangeDesc { - ty: DescriptorType::Sampler, - count: rounded_size, - }, - ], - DescriptorPoolCreateFlags::empty(), - ) - .map_err(|e| { - println!("{:?}", e); - error::CreationError::OutOfMemoryError - })? - }; +impl<'a, T: HasTextures, R: TextureResolver<I>, I: LoadableImage> TextureLoader<'a, T, R, I> { + pub fn loop_forever(mut self) -> Result<TextureLoaderRemains, &'static str> { + debug!("TextureLoader starting main loop"); + let mut res = Ok(()); + while res.is_ok() { + res = self.main(); + sleep(Duration::from_secs(0)); + } - // Layout of our descriptor sets - let descriptor_set_layout = unsafe { - use hal::pso::{DescriptorSetLayoutBinding, DescriptorType, ShaderStageFlags}; - - device.create_descriptor_set_layout( - &[ - DescriptorSetLayoutBinding { - binding: 0, - ty: DescriptorType::SampledImage, - count: CHUNK_SIZE, - stage_flags: ShaderStageFlags::FRAGMENT, - immutable_samplers: false, - }, - DescriptorSetLayoutBinding { - binding: 1, - ty: DescriptorType::Sampler, - count: CHUNK_SIZE, - stage_flags: ShaderStageFlags::FRAGMENT, - immutable_samplers: false, - }, - ], - &[], - ) + match res { + Err(r) => match r { + LoopEndReason::Graceful => { + debug!("Starting to deactivate TextureLoader"); + + Ok(self.deactivate()) + } + LoopEndReason::Error(r) => Err(r), + }, + Ok(_) => Err(""), } - .map_err(|_| error::CreationError::OutOfMemoryError)?; - - log::debug!("texture ds layout: {:?}", descriptor_set_layout); - - // Create texture chunks - debug!("Starting to load textures..."); - let mut chunks = Vec::with_capacity(num_chunks); - for i in 0..num_chunks { - debug!("Chunk {} / {}", i + 1, num_chunks); - - let descriptor_set = unsafe { - descriptor_pool - .allocate_set(&descriptor_set_layout) - .map_err(|_| error::CreationError::OutOfMemoryError)? - }; - chunks.push(TextureChunk::new_empty( - device, - adapter, - allocator, - command_queue, - command_pool, - descriptor_set, - )?); + } + fn main(&mut self) -> Result<(), LoopEndReason> { + // Check for blocks that are finished, then send them back + let mut i = 0; + while i < self.commands_queued.len() { + let signalled = unsafe { self.device.get_fence_status(&self.commands_queued[i].fence) } + .map_err(|_| LoopEndReason::Error("Device lost by TextureManager"))?; + + if signalled { + let (assets, staging_bufs, block) = self.commands_queued.remove(i).dissolve(); + debug!("Done loading texture block {:?}", block.id); + + // Destroy staging buffers + staging_bufs + .into_iter() + .map(|x| x.deactivate(self.device, &mut self.staging_allocator)) + .for_each(|_| {}); + + self.buffers.push_back(assets); + self.return_channel.send(block).unwrap(); + } else { + i += 1; + } } - debug!("All textures loaded."); + // Check for messages to start loading blocks + let req_iter: Vec<_> = self.request_channel.try_iter().collect(); + for to_load in req_iter { + match to_load { + LoaderRequest::Load(to_load) => { + // Attempt to load given block + if let Some(queued_load) = unsafe { self.attempt_queue_load(to_load) } { + self.commands_queued.push(queued_load); + } + } + LoaderRequest::End => return Err(LoopEndReason::Graceful), + } + } - Ok(TextureStore { - descriptor_pool: ManuallyDrop::new(descriptor_pool), - descriptor_set_layout: ManuallyDrop::new(descriptor_set_layout), - chunks: chunks.into_boxed_slice(), - }) + Ok(()) } - /// Create a new texture store for the given file, loading all textures from it. - pub fn new<T: HasTextures>( - device: &mut Device, - adapter: &mut Adapter, - allocator: &mut DynamicAllocator, - command_queue: &mut CommandQueue, - command_pool: &mut CommandPool, - file: &T, - ) -> Result<TextureStore, error::CreationError> { - // Figure out how many textures in this file / how many chunks needed - let size = file.textures_iter().count(); - let num_chunks = { - let mut x = size / CHUNK_SIZE; - if size % CHUNK_SIZE != 0 { - x += 1; - } - x - }; - let rounded_size = num_chunks * CHUNK_SIZE; - - // Descriptor pool, where we get our sets from - let mut descriptor_pool = unsafe { - use hal::pso::{DescriptorPoolCreateFlags, DescriptorRangeDesc, DescriptorType}; - - device - .create_descriptor_pool( - num_chunks, - &[ - DescriptorRangeDesc { - ty: DescriptorType::SampledImage, - count: rounded_size, - }, - DescriptorRangeDesc { - ty: DescriptorType::Sampler, - count: rounded_size, - }, - ], - DescriptorPoolCreateFlags::empty(), + pub fn new( + device: &'a mut Device, + adapter: &Adapter, + family: QueueFamilyId, + gpu: Gpu, + ds_layout: &'a DescriptorSetLayout, + request_channel: Receiver<LoaderRequest>, + return_channel: Sender<TexturesBlock<DynamicBlock>>, + texs: &'a T, + resolver: R, + ) -> Result<Self, &'static str> { + // Pool + let mut pool = unsafe { + use hal::pool::CommandPoolCreateFlags; + + device.create_command_pool(family, CommandPoolCreateFlags::RESET_INDIVIDUAL) + } + .map_err(|_| "Couldn't create command pool")?; + + let type_mask = unsafe { + use hal::image::{Kind, Tiling, Usage, ViewCapabilities}; + + // We create an empty image with the same format as used for textures + // this is to get the type_mask required, which will stay the same for + // all colour images of the same tiling. (certain memory flags excluded). + + // Size and alignment don't necessarily stay the same, so we're forced to + // guess at the alignment for our allocator. + + // TODO: Way to tune these options + let img = device + .create_image( + Kind::D2(16, 16, 1, 1), + 1, + Format::Rgba8Srgb, + Tiling::Optimal, + Usage::SAMPLED, + ViewCapabilities::empty(), ) - .map_err(|e| { - println!("{:?}", e); - error::CreationError::OutOfMemoryError - })? + .map_err(|_| "Couldn't make image to get memory requirements")?; + + let type_mask = device.get_image_requirements(&img).type_mask; + + device.destroy_image(img); + + type_mask }; - // Layout of our descriptor sets - let descriptor_set_layout = unsafe { - use hal::pso::{DescriptorSetLayoutBinding, DescriptorType, ShaderStageFlags}; - - device.create_descriptor_set_layout( - &[ - DescriptorSetLayoutBinding { - binding: 0, - ty: DescriptorType::SampledImage, - count: CHUNK_SIZE, - stage_flags: ShaderStageFlags::FRAGMENT, - immutable_samplers: false, - }, - DescriptorSetLayoutBinding { - binding: 1, - ty: DescriptorType::Sampler, - count: CHUNK_SIZE, - stage_flags: ShaderStageFlags::FRAGMENT, - immutable_samplers: false, + // Tex Allocator + let tex_allocator = { + let props = MemProps::DEVICE_LOCAL; + + DynamicAllocator::new( + find_memory_type_id(&adapter, type_mask, props) + .ok_or("Couldn't find memory type supporting image")?, + props, + DynamicConfig { + block_size_granularity: 4 * 32 * 32, // 32x32 image + max_chunk_size: u64::pow(2, 63), + min_device_allocation: 4 * 32 * 32, + }, + ) + }; + + let (staging_memory_type, staging_allocator) = { + let props = MemProps::CPU_VISIBLE | MemProps::COHERENT; + let t = find_memory_type_id(&adapter, type_mask, props) + .ok_or("Couldn't find memory type supporting image")?; + ( + t, + DynamicAllocator::new( + t, + props, + DynamicConfig { + block_size_granularity: 4 * 32 * 32, // 32x32 image + max_chunk_size: u64::pow(2, 63), + min_device_allocation: 4 * 32 * 32, }, - ], - &[], + ), ) - } - .map_err(|_| error::CreationError::OutOfMemoryError)?; - - // TODO: Proper way to set up resolver - let mut resolver = BasicFSResolver::new(Path::new(".")); - - // Create texture chunks - debug!("Starting to load textures..."); - let mut chunks = Vec::with_capacity(num_chunks); - for i in 0..num_chunks { - debug!("Chunk {} / {}", i + 1, num_chunks); - - let descriptor_set = unsafe { - descriptor_pool - .allocate_set(&descriptor_set_layout) - .map_err(|_| error::CreationError::OutOfMemoryError)? - }; - chunks.push(TextureChunk::new( - device, - adapter, - allocator, - command_queue, - command_pool, - descriptor_set, - file.textures_iter().skip(i * CHUNK_SIZE).take(CHUNK_SIZE), - &mut resolver, - )?); - } + }; + + let buffers = { + let mut data = VecDeque::with_capacity(NUM_SIMULTANEOUS_CMDS); + + for _ in 0..NUM_SIMULTANEOUS_CMDS { + unsafe { + data.push_back(( + device + .create_fence(false) + .map_err(|_| "Couldn't create fence")?, + pool.allocate_one(hal::command::Level::Primary), + )); + }; + } + + data + }; - debug!("All textures loaded."); + let cmd_queue_idx = gpu + .queue_groups + .iter() + .position(|x| x.family == family) + .unwrap(); - Ok(TextureStore { - descriptor_pool: ManuallyDrop::new(descriptor_pool), - descriptor_set_layout: ManuallyDrop::new(descriptor_set_layout), - chunks: chunks.into_boxed_slice(), + Ok(TextureLoader { + device, + commands_queued: ArrayVec::new(), + buffers, + pool: ManuallyDrop::new(pool), + gpu: ManuallyDrop::new(gpu), + cmd_queue_idx, + ds_layout, + + tex_allocator: ManuallyDrop::new(tex_allocator), + staging_allocator: ManuallyDrop::new(staging_allocator), + descriptor_allocator: ManuallyDrop::new(DescriptorAllocator::new()), + + staging_memory_type, + optimal_buffer_copy_pitch_alignment: adapter + .physical_device + .limits() + .optimal_buffer_copy_pitch_alignment, + + request_channel, + return_channel, + textures: texs, + resolver, + _li: PhantomData::default(), }) } - /// Call this before dropping - pub fn deactivate(mut self, device: &mut Device, allocator: &mut DynamicAllocator) { - unsafe { - use core::ptr::read; + /// Safely destroy all the vulkan stuff in this instance + /// Note that this returns the memory allocators, from which should be freed any TextureBlocks + /// All in-progress things are sent to return_channel. + fn deactivate(mut self) -> TextureLoaderRemains { + use std::ptr::read; - for chunk in self.chunks.into_vec().drain(..) { - chunk.deactivate(device, allocator); + unsafe { + // Wait for any currently queued loads to be done + while self.commands_queued.len() > 0 { + let mut i = 0; + while i < self.commands_queued.len() { + let signalled = self + .device + .get_fence_status(&self.commands_queued[i].fence) + .expect("Device lost by TextureManager"); + + if signalled { + // Destroy finished ones + let (assets, mut staging_bufs, block) = + self.commands_queued.remove(i).dissolve(); + + self.device.destroy_fence(assets.0); + // Command buffer will be freed when we reset the command pool + staging_bufs + .drain(..) + .map(|x| x.deactivate(self.device, &mut self.staging_allocator)) + .for_each(|_| {}); + + self.return_channel + .send(block) + .expect("Sending through return channel failed"); + } else { + i += 1; + } + } + + sleep(Duration::from_secs(0)); } - self.descriptor_pool.reset(); - device.destroy_descriptor_set_layout(ManuallyDrop::into_inner(read( - &self.descriptor_set_layout, - ))); - device.destroy_descriptor_pool(ManuallyDrop::into_inner(read(&self.descriptor_pool))); + // Destroy fences + let vec: Vec<_> = self.buffers.drain(..).collect(); + + vec.into_iter() + .map(|(f, _)| self.device.destroy_fence(f)) + .for_each(|_| {}); + + // Free command pool + self.pool.reset(true); + self.device.destroy_command_pool(read(&*self.pool)); + + debug!("Done deactivating TextureLoader"); + + TextureLoaderRemains { + tex_allocator: ManuallyDrop::new(read(&*self.tex_allocator)), + descriptor_allocator: ManuallyDrop::new(read(&*self.descriptor_allocator)), + } } } +} - /// Get the descriptor set for a given chunk - pub fn get_chunk_descriptor_set(&self, idx: usize) -> &DescriptorSet { - &self.chunks[idx].descriptor_set - } +pub struct TextureLoaderRemains { + pub tex_allocator: ManuallyDrop<DynamicAllocator>, + pub descriptor_allocator: ManuallyDrop<DescriptorAllocator>, +} - pub fn put_texture<T: LoadableImage>( - &mut self, - idx: usize, - img: T, - device: &mut Device, - adapter: &mut Adapter, - allocator: &mut DynamicAllocator, - command_queue: &mut CommandQueue, - command_pool: &mut CommandPool, - ) -> Result<(), &'static str> { - // TODO: Resizing, etc? - let chunk = &mut self.chunks[idx / CHUNK_SIZE]; - chunk.put_texture( - img, - idx % CHUNK_SIZE, - device, - adapter, - allocator, - command_queue, - command_pool, - ) - } +enum LoopEndReason { + Graceful, + Error(&'static str), +} + +pub enum LoaderRequest { + /// Load the given block + Load(BlockRef), + + /// Stop looping and deactivate + End, } diff --git a/stockton-render/src/draw/texture/mod.rs b/stockton-render/src/draw/texture/mod.rs index 3878472..09896ab 100644 --- a/stockton-render/src/draw/texture/mod.rs +++ b/stockton-render/src/draw/texture/mod.rs @@ -17,14 +17,18 @@ //! Everything related to loading textures into GPU memory -// Since this is in the process of being rewritten, we ignore this for now -#![allow(clippy::too_many_arguments)] - -mod chunk; -pub mod image; -pub mod loader; -mod resolver; +mod block; +mod image; +mod load; +mod loader; +mod repo; +pub mod resolver; +mod staging_buffer; +pub use self::block::TexturesBlock; pub use self::image::LoadableImage; -pub use self::image::{LoadedImage, SampledImage}; -pub use self::loader::TextureStore; +pub use self::loader::BlockRef; +pub use self::repo::TextureRepo; + +/// The size of each pixel in an image +pub const PIXEL_SIZE: usize = std::mem::size_of::<u8>() * 4; diff --git a/stockton-render/src/draw/texture/repo.rs b/stockton-render/src/draw/texture/repo.rs new file mode 100644 index 0000000..7bc8fb5 --- /dev/null +++ b/stockton-render/src/draw/texture/repo.rs @@ -0,0 +1,192 @@ +use stockton_levels::prelude::HasTextures; + +use super::{ + block::TexturesBlock, + loader::{BlockRef, LoaderRequest, TextureLoader, TextureLoaderRemains, NUM_SIMULTANEOUS_CMDS}, + resolver::TextureResolver, + LoadableImage, +}; +use crate::types::*; + +use std::{ + collections::HashMap, + marker::PhantomData, + mem::ManuallyDrop, + pin::Pin, + sync::mpsc::{channel, Receiver, Sender}, + thread::JoinHandle, +}; + +use hal::{ + prelude::*, + pso::{DescriptorSetLayoutBinding, DescriptorType, ShaderStageFlags}, + Features, +}; +use log::debug; + +/// The number of textures in one 'block' +/// The textures of the loaded file are divided into blocks of this size. +/// Whenever a texture is needed, the whole block its in is loaded. +pub const BLOCK_SIZE: usize = 8; + +pub struct TextureRepo<'a> { + joiner: ManuallyDrop<JoinHandle<Result<TextureLoaderRemains, &'static str>>>, + ds_layout: Pin<Box<DescriptorSetLayout>>, + req_send: Sender<LoaderRequest>, + resp_recv: Receiver<TexturesBlock<DynamicBlock>>, + blocks: HashMap<BlockRef, Option<TexturesBlock<DynamicBlock>>>, + + _a: PhantomData<&'a ()>, +} + +impl<'a> TextureRepo<'a> { + pub fn new< + T: HasTextures + Send + Sync, + R: 'static + TextureResolver<I> + Send + Sync, + I: 'static + LoadableImage + Send, + >( + device: &'static mut Device, + adapter: &Adapter, + texs: &'static T, + resolver: R, + ) -> Result<Self, &'static str> { + let (req_send, req_recv) = channel(); + let (resp_send, resp_recv) = channel(); + let family = adapter + .queue_families + .iter() + .find(|family| { + family.queue_type().supports_transfer() + && family.max_queues() >= NUM_SIMULTANEOUS_CMDS + }) + .unwrap(); + let gpu = unsafe { + adapter + .physical_device + .open(&[(family, &[1.0])], Features::empty()) + .unwrap() + }; + + let mut ds_layout = Box::pin( + unsafe { + device.create_descriptor_set_layout( + &[ + DescriptorSetLayoutBinding { + binding: 0, + ty: DescriptorType::SampledImage, + count: BLOCK_SIZE, + stage_flags: ShaderStageFlags::FRAGMENT, + immutable_samplers: false, + }, + DescriptorSetLayoutBinding { + binding: 1, + ty: DescriptorType::Sampler, + count: BLOCK_SIZE, + stage_flags: ShaderStageFlags::FRAGMENT, + immutable_samplers: false, + }, + ], + &[], + ) + } + .map_err(|_| "Couldn't create descriptor set layout")?, + ); + + let long_ds_pointer: &'static DescriptorSetLayout = + unsafe { &mut *(&mut *ds_layout as *mut DescriptorSetLayout) }; + + let joiner = { + let loader = TextureLoader::new( + device, + adapter, + family.id(), + gpu, + long_ds_pointer, + req_recv, + resp_send, + texs, + resolver, + )?; + + std::thread::spawn(move || loader.loop_forever()) + }; + + Ok(TextureRepo { + joiner: ManuallyDrop::new(joiner), + ds_layout, + blocks: HashMap::new(), + req_send, + resp_recv, + _a: PhantomData::default(), + }) + } + + pub fn get_ds_layout(&self) -> &DescriptorSetLayout { + &*self.ds_layout + } + + pub fn queue_load(&mut self, block_id: BlockRef) -> Result<(), &'static str> { + if self.blocks.contains_key(&block_id) { + return Ok(()); + } + + self.force_queue_load(block_id) + } + + pub fn force_queue_load(&mut self, block_id: BlockRef) -> Result<(), &'static str> { + self.req_send + .send(LoaderRequest::Load(block_id)) + .map_err(|_| "Couldn't send load request")?; + + self.blocks.insert(block_id, None); + + Ok(()) + } + + pub fn attempt_get_descriptor_set(&mut self, block_id: BlockRef) -> Option<&DescriptorSet> { + self.blocks + .get(&block_id) + .and_then(|opt| opt.as_ref().map(|z| z.descriptor_set.raw())) + } + + pub fn process_responses(&mut self) { + let resp_iter: Vec<_> = self.resp_recv.try_iter().collect(); + for resp in resp_iter { + debug!("Got block {:?} back from loader", resp.id); + self.blocks.insert(resp.id, Some(resp)); + } + } + + pub fn deactivate(mut self, device: &mut Device) { + unsafe { + use std::ptr::read; + + // Join the loader thread + self.req_send.send(LoaderRequest::End).unwrap(); + let mut remains = read(&*self.joiner).join().unwrap().unwrap(); + + // Process any ones that just got done loading + self.process_responses(); + + // Return all the texture memory and descriptors. + for (i, v) in self.blocks.drain() { + debug!("Deactivating blockref {:?}", i); + if let Some(block) = v { + block.deactivate( + device, + &mut *remains.tex_allocator, + &mut remains.descriptor_allocator, + ); + } + } + + debug!("Deactivated all blocks"); + + // Dispose of both allocators + read(&*remains.tex_allocator).dispose(); + read(&*remains.descriptor_allocator).dispose(device); + + debug!("Disposed of allocators"); + } + } +} diff --git a/stockton-render/src/draw/texture/resolver.rs b/stockton-render/src/draw/texture/resolver.rs index f33f0a1..a0df7d8 100644 --- a/stockton-render/src/draw/texture/resolver.rs +++ b/stockton-render/src/draw/texture/resolver.rs @@ -20,10 +20,10 @@ use crate::draw::texture::image::LoadableImage; use stockton_levels::traits::textures::Texture; -use image::{io::Reader, RgbaImage}; - use std::path::Path; +use image::{io::Reader, RgbaImage}; + /// An object that can be used to resolve a texture from a BSP File pub trait TextureResolver<T: LoadableImage> { /// Get the given texture, or None if it's corrupt/not there. @@ -31,17 +31,17 @@ pub trait TextureResolver<T: LoadableImage> { } /// A basic filesystem resolver which expects no file extension and guesses the image format -pub struct BasicFSResolver<'a> { +pub struct BasicFsResolver<'a> { path: &'a Path, } -impl<'a> BasicFSResolver<'a> { - pub fn new(path: &'a Path) -> BasicFSResolver<'a> { - BasicFSResolver { path } +impl<'a> BasicFsResolver<'a> { + pub fn new(path: &'a Path) -> BasicFsResolver<'a> { + BasicFsResolver { path } } } -impl<'a> TextureResolver<RgbaImage> for BasicFSResolver<'a> { +impl<'a> TextureResolver<RgbaImage> for BasicFsResolver<'a> { fn resolve(&mut self, tex: &Texture) -> Option<RgbaImage> { let path = self.path.join(&tex.name); diff --git a/stockton-render/src/draw/texture/staging_buffer.rs b/stockton-render/src/draw/texture/staging_buffer.rs new file mode 100644 index 0000000..d1897ad --- /dev/null +++ b/stockton-render/src/draw/texture/staging_buffer.rs @@ -0,0 +1,56 @@ +use crate::types::*; + +use std::mem::ManuallyDrop; + +use hal::{device::MapError, prelude::*, MemoryTypeId}; +use rendy_memory::{Allocator, Block}; + +pub struct StagingBuffer { + pub buf: ManuallyDrop<Buffer>, + pub mem: ManuallyDrop<DynamicBlock>, +} + +impl StagingBuffer { + const USAGE: hal::buffer::Usage = hal::buffer::Usage::TRANSFER_SRC; + + pub fn new( + device: &mut Device, + alloc: &mut DynamicAllocator, + size: u64, + _memory_type_id: MemoryTypeId, + ) -> Result<StagingBuffer, &'static str> { + let mut buffer = unsafe { device.create_buffer(size, Self::USAGE) } + .map_err(|_| "Couldn't create staging buffer")?; + + let requirements = unsafe { device.get_buffer_requirements(&buffer) }; + + let (memory, _) = alloc + .alloc(device, requirements.size, requirements.alignment) + .map_err(|_| "Couldn't allocate staging memory")?; + + unsafe { device.bind_buffer_memory(memory.memory(), 0, &mut buffer) } + .map_err(|_| "Couldn't bind staging memory to buffer")?; + + Ok(StagingBuffer { + buf: ManuallyDrop::new(buffer), + mem: ManuallyDrop::new(memory), + }) + } + + pub unsafe fn map_memory(&mut self, device: &mut Device) -> Result<*mut u8, MapError> { + device.map_memory(self.mem.memory(), self.mem.range()) + } + pub unsafe fn unmap_memory(&mut self, device: &mut Device) { + device.unmap_memory(self.mem.memory()); // TODO: What if the same Memory is mapped in multiple places? + } + + pub fn deactivate(self, device: &mut Device, alloc: &mut DynamicAllocator) { + unsafe { + use std::ptr::read; + // Destroy buffer + device.destroy_buffer(read(&*self.buf)); + // Free memory + alloc.free(device, read(&*self.mem)); + } + } +} diff --git a/stockton-render/src/draw/ui/mod.rs b/stockton-render/src/draw/ui/mod.rs index bcf5f38..4caec7a 100644 --- a/stockton-render/src/draw/ui/mod.rs +++ b/stockton-render/src/draw/ui/mod.rs @@ -21,10 +21,10 @@ pub mod pipeline; pub mod render; pub mod texture; -pub use pipeline::UIPipeline; +pub use pipeline::UiPipeline; pub use render::do_render; use stockton_types::Vector2; -pub use texture::ensure_textures; +pub use texture::{ensure_textures, UiTextures}; #[derive(Debug)] -pub struct UIPoint(pub Vector2, pub Vector2, pub Srgba); +pub struct UiPoint(pub Vector2, pub Vector2, pub Srgba); diff --git a/stockton-render/src/draw/ui/pipeline.rs b/stockton-render/src/draw/ui/pipeline.rs index f608173..1261ea7 100644 --- a/stockton-render/src/draw/ui/pipeline.rs +++ b/stockton-render/src/draw/ui/pipeline.rs @@ -37,7 +37,7 @@ use crate::error; use crate::types::*; /// A complete 2D graphics pipeline and associated resources -pub struct UIPipeline { +pub struct UiPipeline { /// Our main render pass pub(crate) renderpass: ManuallyDrop<RenderPass>, @@ -54,7 +54,7 @@ pub struct UIPipeline { pub(crate) fs_module: ManuallyDrop<ShaderModule>, } -impl UIPipeline { +impl UiPipeline { pub fn new<T>( device: &mut Device, extent: hal::image::Extent, @@ -271,7 +271,7 @@ impl UIPipeline { let pipeline = unsafe { device.create_graphics_pipeline(&pipeline_desc, None) } .map_err(error::CreationError::PipelineError)?; - Ok(UIPipeline { + Ok(UiPipeline { renderpass: ManuallyDrop::new(renderpass), pipeline_layout: ManuallyDrop::new(layout), pipeline: ManuallyDrop::new(pipeline), diff --git a/stockton-render/src/draw/ui/render.rs b/stockton-render/src/draw/ui/render.rs index 02135e5..77231b0 100644 --- a/stockton-render/src/draw/ui/render.rs +++ b/stockton-render/src/draw/ui/render.rs @@ -15,24 +15,24 @@ * with this program. If not, see <http://www.gnu.org/licenses/>. */ -use crate::draw::texture::TextureStore; +use crate::draw::texture::TextureRepo; use arrayvec::ArrayVec; use hal::prelude::*; use hal::pso::ShaderStageFlags; -use super::UIPoint; +use super::UiPoint; use crate::draw::draw_buffers::DrawBuffers; use crate::types::*; -use crate::UIState; +use crate::UiState; use std::convert::TryInto; use stockton_types::Vector2; pub fn do_render( cmd_buffer: &mut CommandBuffer, pipeline_layout: &PipelineLayout, - draw_buffers: &mut DrawBuffers<UIPoint>, - texture_store: &mut TextureStore, - ui: &mut UIState, + draw_buffers: &mut DrawBuffers<UiPoint>, + tex_repo: &mut TextureRepo, + ui: &mut UiState, ) { // TODO: Actual UI Rendering let (_out, paint) = ui.end_frame(); @@ -57,7 +57,7 @@ pub fn do_render( ); } for (i, vertex) in tris.vertices.iter().enumerate() { - draw_buffers.vertex_buffer[i] = UIPoint( + draw_buffers.vertex_buffer[i] = UiPoint( Vector2::new(vertex.pos.x, vertex.pos.y), Vector2::new(vertex.uv.x, vertex.uv.y), vertex.color, @@ -65,13 +65,17 @@ pub fn do_render( } // TODO: *Properly* deal with textures - let mut descriptor_sets: ArrayVec<[_; 1]> = ArrayVec::new(); - descriptor_sets.push(texture_store.get_chunk_descriptor_set(0)); + if let Some(ds) = tex_repo.attempt_get_descriptor_set(0) { + let mut descriptor_sets: ArrayVec<[_; 1]> = ArrayVec::new(); + descriptor_sets.push(ds); - unsafe { - cmd_buffer.bind_graphics_descriptor_sets(pipeline_layout, 0, descriptor_sets, &[]); - // Call draw - cmd_buffer.draw_indexed(0..tris.indices.len() as u32, 0, 0..1); + unsafe { + cmd_buffer.bind_graphics_descriptor_sets(pipeline_layout, 0, descriptor_sets, &[]); + // Call draw + cmd_buffer.draw_indexed(0..tris.indices.len() as u32, 0, 0..1); + } + } else { + // tex_repo.queue_load(0); } } } diff --git a/stockton-render/src/draw/ui/texture.rs b/stockton-render/src/draw/ui/texture.rs index 98688de..439c3d7 100755 --- a/stockton-render/src/draw/ui/texture.rs +++ b/stockton-render/src/draw/ui/texture.rs @@ -14,10 +14,25 @@ * You should have received a copy of the GNU General Public License along * with this program. If not, see <http://www.gnu.org/licenses/>. */ -use crate::draw::texture::{LoadableImage, TextureStore}; +use crate::draw::texture::{LoadableImage, TextureRepo}; use crate::types::*; -use crate::UIState; +use crate::UiState; use egui::Texture; +use stockton_levels::{prelude::HasTextures, traits::textures::Texture as LTexture}; + +pub struct UiTextures; + +impl HasTextures for UiTextures { + type TexturesIter<'a> = std::slice::Iter<'a, LTexture>; + + fn textures_iter(&self) -> Self::TexturesIter<'_> { + (&[]).iter() + } + + fn get_texture(&self, _idx: u32) -> Option<&stockton_levels::prelude::textures::Texture> { + None + } +} impl LoadableImage for &Texture { fn width(&self) -> u32 { @@ -38,31 +53,25 @@ impl LoadableImage for &Texture { } } } + + unsafe fn copy_into(&self, _ptr: *mut u8, _row_size: usize) { + todo!() + } } pub fn ensure_textures( - texture_store: &mut TextureStore, - ui: &mut UIState, - device: &mut Device, - adapter: &mut Adapter, - allocator: &mut DynamicAllocator, - command_queue: &mut CommandQueue, - command_pool: &mut CommandPool, + _tex_repo: &mut TextureRepo, + ui: &mut UiState, + _device: &mut Device, + _adapter: &mut Adapter, + _allocator: &mut DynamicAllocator, + _command_queue: &mut CommandQueue, + _command_pool: &mut CommandPool, ) { let tex = ui.ctx.texture(); if tex.version != ui.last_tex_ver { - texture_store - .put_texture( - 0, - &*tex, - device, - adapter, - allocator, - command_queue, - command_pool, - ) - .unwrap(); // TODO + // tex_repo.force_queue_load(0).unwrap(); // TODO ui.last_tex_ver = tex.version; } } diff --git a/stockton-render/src/lib.rs b/stockton-render/src/lib.rs index fbbb329..98ee817 100644 --- a/stockton-render/src/lib.rs +++ b/stockton-render/src/lib.rs @@ -14,7 +14,8 @@ * You should have received a copy of the GNU General Public License along * with this program. If not, see <http://www.gnu.org/licenses/>. */ - +#![allow(incomplete_features)] +#![feature(generic_associated_types)] #[cfg(feature = "vulkan")] extern crate gfx_backend_vulkan as back; extern crate gfx_hal as hal; @@ -37,7 +38,7 @@ use legion::IntoQuery; use std::sync::mpsc::{Receiver, Sender}; use std::sync::Arc; use std::sync::RwLock; -pub use window::{UIState, WindowEvent}; +pub use window::{UiState, WindowEvent}; use stockton_levels::prelude::*; use stockton_types::components::{CameraSettings, Transform}; @@ -49,9 +50,9 @@ 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<'a> { +pub struct Renderer<'a, M: 'static + MinBspFeatures<VulkanSystem>> { /// All the vulkan stuff - pub(crate) context: RenderingContext<'a>, + pub(crate) context: RenderingContext<'a, M>, /// For getting events from the winit event loop pub window_events: Receiver<WindowEvent>, @@ -60,12 +61,9 @@ pub struct Renderer<'a> { pub update_control_flow: Arc<RwLock<ControlFlow>>, } -impl<'a> Renderer<'a> { +impl<'a, M: 'static + MinBspFeatures<VulkanSystem>> Renderer<'a, M> { /// Create a new Renderer. - pub fn new<T: MinBSPFeatures<VulkanSystem>>( - window: &Window, - file: &T, - ) -> (Self, Sender<WindowEvent>) { + pub fn new(window: &Window, file: M) -> (Self, Sender<WindowEvent>) { let (tx, rx) = channel(); let update_control_flow = Arc::new(RwLock::new(ControlFlow::Poll)); @@ -80,16 +78,16 @@ impl<'a> Renderer<'a> { } /// Render a single frame of the given map. - fn render<T: MinBSPFeatures<VulkanSystem>>(&mut self, map: &T, ui: &mut UIState, pos: Vector3) { + fn render(&mut self, ui: &mut UiState, pos: Vector3) { // Get visible faces - let faces = get_visible_faces(pos, map); + let faces = get_visible_faces(pos, &*self.context.map); // Then draw them - if self.context.draw_vertices(map, ui, &faces).is_err() { + if self.context.draw_vertices(ui, &faces).is_err() { unsafe { self.context.handle_surface_change().unwrap() }; // If it fails twice, then error - self.context.draw_vertices(map, ui, &faces).unwrap(); + self.context.draw_vertices(ui, &faces).unwrap(); } } @@ -102,14 +100,13 @@ impl<'a> Renderer<'a> { #[system] #[read_component(Transform)] #[read_component(CameraSettings)] -pub fn do_render<T: 'static + MinBSPFeatures<VulkanSystem>>( - #[resource] renderer: &mut Renderer<'static>, - #[resource] ui: &mut UIState, - #[resource] map: &T, +pub fn do_render<T: 'static + MinBspFeatures<VulkanSystem>>( + #[resource] renderer: &mut Renderer<'static, T>, + #[resource] ui: &mut UiState, world: &SubWorld, ) { let mut query = <(&Transform, &CameraSettings)>::query(); for (transform, _) in query.iter(world) { - renderer.render(map, ui, transform.position); + renderer.render(ui, transform.position); } } diff --git a/stockton-render/src/types.rs b/stockton-render/src/types.rs index cf0b025..c461fde 100644 --- a/stockton-render/src/types.rs +++ b/stockton-render/src/types.rs @@ -18,6 +18,7 @@ //! Convenience module to reference types that are stored in the backend's enum pub type Device = <back::Backend as hal::Backend>::Device; +pub type Gpu = hal::adapter::Gpu<back::Backend>; pub type Buffer = <back::Backend as hal::Backend>::Buffer; pub type Memory = <back::Backend as hal::Backend>::Memory; pub type Swapchain = <back::Backend as hal::Backend>::Swapchain; @@ -28,7 +29,6 @@ pub type CommandPool = <back::Backend as hal::Backend>::CommandPool; pub type CommandBuffer = <back::Backend as hal::Backend>::CommandBuffer; pub type CommandQueue = <back::Backend as hal::Backend>::CommandQueue; pub type DescriptorSetLayout = <back::Backend as hal::Backend>::DescriptorSetLayout; -pub type DescriptorPool = <back::Backend as hal::Backend>::DescriptorPool; pub type DescriptorSet = <back::Backend as hal::Backend>::DescriptorSet; pub type PipelineLayout = <back::Backend as hal::Backend>::PipelineLayout; pub type GraphicsPipeline = <back::Backend as hal::Backend>::GraphicsPipeline; @@ -42,5 +42,8 @@ pub type RenderPass = <back::Backend as hal::Backend>::RenderPass; pub type Adapter = hal::adapter::Adapter<back::Backend>; pub type QueueGroup = hal::queue::QueueGroup<back::Backend>; +pub type DescriptorAllocator = rendy_descriptor::DescriptorAllocator<back::Backend>; pub type DynamicAllocator = rendy_memory::DynamicAllocator<back::Backend>; pub type DynamicBlock = rendy_memory::DynamicBlock<back::Backend>; + +pub type RDescriptorSet = rendy_descriptor::DescriptorSet<back::Backend>; diff --git a/stockton-render/src/window.rs b/stockton-render/src/window.rs index e6bd5b0..4a1628d 100644 --- a/stockton-render/src/window.rs +++ b/stockton-render/src/window.rs @@ -20,6 +20,7 @@ use egui::Context; use legion::systems::Runnable; use log::debug; use std::sync::Arc; +use stockton_levels::prelude::{MinBspFeatures, VulkanSystem}; use egui::{Output, PaintJobs, Pos2, RawInput, Ui}; @@ -86,7 +87,7 @@ impl WindowEvent { } } -pub struct UIState { +pub struct UiState { pub(crate) ctx: Arc<Context>, pub(crate) raw_input: RawInput, ui: Option<Ui>, @@ -94,7 +95,7 @@ pub struct UIState { pub(crate) last_tex_ver: u64, } -impl UIState { +impl UiState { pub fn ui(&mut self) -> &mut Ui { if self.ui.is_none() { self.ui = Some(self.begin_frame()); @@ -144,8 +145,8 @@ impl UIState { } } - pub fn new(renderer: &Renderer) -> Self { - let mut state = UIState { + pub fn new<T: MinBspFeatures<VulkanSystem>>(renderer: &Renderer<T>) -> Self { + let mut state = UiState { ctx: Context::new(), raw_input: RawInput::default(), ui: None, @@ -162,11 +163,14 @@ impl UIState { #[system] /// A system to process the window events sent to renderer by the winit event loop. -pub fn _process_window_events<T: 'static + InputManager>( - #[resource] renderer: &mut Renderer<'static>, +pub fn _process_window_events< + T: 'static + InputManager, + M: 'static + MinBspFeatures<VulkanSystem>, +>( + #[resource] renderer: &mut Renderer<'static, M>, #[resource] manager: &mut T, #[resource] mouse: &mut Mouse, - #[resource] ui_state: &mut UIState, + #[resource] ui_state: &mut UiState, #[state] actions_buf: &mut Vec<KBAction>, ) { let mut actions_buf_cursor = 0; @@ -220,6 +224,9 @@ pub fn _process_window_events<T: 'static + InputManager>( manager.handle_frame(&actions_buf[0..actions_buf_cursor]); } -pub fn process_window_events_system<T: 'static + InputManager>() -> impl Runnable { - _process_window_events_system::<T>(Vec::with_capacity(4)) +pub fn process_window_events_system< + T: 'static + InputManager, + M: 'static + MinBspFeatures<VulkanSystem>, +>() -> impl Runnable { + _process_window_events_system::<T, M>(Vec::with_capacity(4)) } |