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 | |
parent | b437109ebf4da243fd643f0a31546d0d0155b0a4 (diff) |
feat(render): multithreaded texture loading
also a bunch of supporting changes
49 files changed, 1642 insertions, 1383 deletions
diff --git a/examples/render-bsp/src/main.rs b/examples/render-bsp/src/main.rs index ec39b24..cf6f3f6 100644 --- a/examples/render-bsp/src/main.rs +++ b/examples/render-bsp/src/main.rs @@ -30,10 +30,10 @@ use stockton_contrib::delta_time::*; use stockton_contrib::flycam::*; use stockton_input::{Axis, InputManager, Mouse}; -use stockton_levels::{prelude::*, q3::Q3BSPFile}; +use stockton_levels::{prelude::*, q3::Q3BspFile}; use stockton_render::systems::*; -use stockton_render::{Renderer, UIState, WindowEvent}; +use stockton_render::{Renderer, UiState, WindowEvent}; use stockton_types::components::{CameraSettings, Transform}; use stockton_types::{Session, Vector3}; @@ -63,7 +63,7 @@ impl FlycamInput for MovementInputs { } #[system] -fn hello_world(#[resource] ui: &mut UIState) { +fn hello_world(#[resource] ui: &mut UiState) { let ui = ui.ui(); ui.heading("ABCDEFGHIJKLMNOPQRSTUVWXYZ"); } @@ -88,13 +88,13 @@ fn main() { let data = include_bytes!("../data/newtest.bsp") .to_vec() .into_boxed_slice(); - let bsp: Result<Q3BSPFile<Q3System>, stockton_levels::types::ParseError> = - Q3BSPFile::parse_file(&data); - let bsp: Q3BSPFile<Q3System> = bsp.unwrap(); - let bsp: Q3BSPFile<VulkanSystem> = bsp.swizzle_to(); + let bsp: Result<Q3BspFile<Q3System>, stockton_levels::types::ParseError> = + Q3BspFile::parse_file(&data); + let bsp: Q3BspFile<Q3System> = bsp.unwrap(); + let bsp: Q3BspFile<VulkanSystem> = bsp.swizzle_to(); // Create the renderer - let (renderer, tx) = Renderer::new(&window, &bsp); + let (renderer, tx) = Renderer::new(&window, bsp); let new_control_flow = renderer.update_control_flow.clone(); // Create the input manager @@ -117,9 +117,8 @@ fn main() { // Load everything into the session let mut session = Session::new( move |resources| { - resources.insert(UIState::new(&renderer)); + resources.insert(UiState::new(&renderer)); resources.insert(renderer); - resources.insert(bsp); resources.insert(manager); resources.insert(Timing::default()); resources.insert(Mouse::default()); @@ -127,13 +126,16 @@ fn main() { move |schedule| { schedule .add_system(update_deltatime_system()) - .add_system(process_window_events_system::<MovementInputsManager>()) + .add_system(process_window_events_system::< + MovementInputsManager, + Q3BspFile<VulkanSystem>, + >()) .flush() .add_system(hello_world_system()) .add_system(flycam_move_system::<MovementInputsManager>()) .flush() - .add_system(calc_vp_matrix_system()) - .add_thread_local(do_render_system::<Q3BSPFile<VulkanSystem>>()); + .add_system(calc_vp_matrix_system::<Q3BspFile<VulkanSystem>>()) + .add_thread_local(do_render_system::<Q3BspFile<VulkanSystem>>()); }, ); diff --git a/stockton-levels/src/features.rs b/stockton-levels/src/features.rs index f9e6455..0034831 100644 --- a/stockton-levels/src/features.rs +++ b/stockton-levels/src/features.rs @@ -32,5 +32,5 @@ use crate::coords::CoordSystem; use crate::traits::*; -pub trait MinBSPFeatures<S: CoordSystem>: HasBSPTree<S> + Send + Sync {} -impl<T, S: CoordSystem> MinBSPFeatures<S> for T where T: HasBSPTree<S> + Send + Sync {} +pub trait MinBspFeatures<S: CoordSystem>: HasBspTree<S> + Send + Sync {} +impl<T, S: CoordSystem> MinBspFeatures<S> for T where T: HasBspTree<S> + Send + Sync {} diff --git a/stockton-levels/src/q3/brushes.rs b/stockton-levels/src/q3/brushes.rs index 098967b..585d643 100644 --- a/stockton-levels/src/q3/brushes.rs +++ b/stockton-levels/src/q3/brushes.rs @@ -23,7 +23,7 @@ const BRUSH_SIZE: usize = 4 * 3; /// The size of one brushsize record const SIDE_SIZE: usize = 4 * 2; -use super::Q3BSPFile; +use super::Q3BspFile; use crate::coords::CoordSystem; use crate::helpers::slice_to_i32; use crate::traits::brushes::*; @@ -104,7 +104,7 @@ fn get_sides( Ok(sides.into_boxed_slice()) } -impl<T: CoordSystem> HasBrushes<T> for Q3BSPFile<T> { +impl<T: CoordSystem> HasBrushes<T> for Q3BspFile<T> { type BrushesIter<'a> = std::slice::Iter<'a, Brush>; fn brushes_iter(&self) -> Self::BrushesIter<'_> { diff --git a/stockton-levels/src/q3/effects.rs b/stockton-levels/src/q3/effects.rs index c0aafac..f3786bd 100644 --- a/stockton-levels/src/q3/effects.rs +++ b/stockton-levels/src/q3/effects.rs @@ -17,7 +17,7 @@ use std::str; -use super::Q3BSPFile; +use super::Q3BspFile; use crate::coords::CoordSystem; use crate::helpers::slice_to_u32; use crate::traits::effects::*; @@ -52,7 +52,7 @@ pub fn from_data(data: &[u8], n_brushes: u32) -> Result<Box<[Effect]>> { Ok(effects.into_boxed_slice()) } -impl<T: CoordSystem> HasEffects<T> for Q3BSPFile<T> { +impl<T: CoordSystem> HasEffects<T> for Q3BspFile<T> { type EffectsIter<'a> = std::slice::Iter<'a, Effect>; fn effects_iter(&self) -> Self::EffectsIter<'_> { diff --git a/stockton-levels/src/q3/entities.rs b/stockton-levels/src/q3/entities.rs index cb2a326..9eb0a61 100644 --- a/stockton-levels/src/q3/entities.rs +++ b/stockton-levels/src/q3/entities.rs @@ -18,7 +18,7 @@ use std::collections::HashMap; use std::str; -use super::Q3BSPFile; +use super::Q3BspFile; use crate::coords::CoordSystem; use crate::traits::entities::*; use crate::types::{ParseError, Result}; @@ -103,7 +103,7 @@ pub fn from_data(data: &[u8]) -> Result<Box<[Entity]>> { Ok(entities.into_boxed_slice()) } -impl<T: CoordSystem> HasEntities for Q3BSPFile<T> { +impl<T: CoordSystem> HasEntities for Q3BspFile<T> { type EntitiesIter<'a> = std::slice::Iter<'a, Entity>; fn entities_iter(&self) -> Self::EntitiesIter<'_> { diff --git a/stockton-levels/src/q3/faces.rs b/stockton-levels/src/q3/faces.rs index e28c189..94420a0 100644 --- a/stockton-levels/src/q3/faces.rs +++ b/stockton-levels/src/q3/faces.rs @@ -15,7 +15,7 @@ * with this program. If not, see <http://www.gnu.org/licenses/>. */ -use super::Q3BSPFile; +use super::Q3BspFile; use crate::coords::CoordSystem; use crate::helpers::{slice_to_i32, slice_to_u32, slice_to_vec2ui, slice_to_vec3}; use crate::traits::faces::*; @@ -148,7 +148,7 @@ fn face_from_slice( }) } -impl<T: CoordSystem> HasFaces<T> for Q3BSPFile<T> { +impl<T: CoordSystem> HasFaces<T> for Q3BspFile<T> { type FacesIter<'a> = std::slice::Iter<'a, Face>; fn faces_iter(&self) -> Self::FacesIter<'_> { diff --git a/stockton-levels/src/q3/file.rs b/stockton-levels/src/q3/file.rs index 1440e21..48066f1 100644 --- a/stockton-levels/src/q3/file.rs +++ b/stockton-levels/src/q3/file.rs @@ -36,11 +36,11 @@ use crate::traits::light_vols::LightVol; use crate::traits::models::Model; use crate::traits::planes::Plane; use crate::traits::textures::Texture; -use crate::traits::tree::BSPNode; +use crate::traits::tree::BspNode; use crate::traits::vertices::{MeshVert, Vertex}; /// A parsed Quake 3 BSP File. -pub struct Q3BSPFile<T: CoordSystem> { +pub struct Q3BspFile<T: CoordSystem> { pub(crate) visdata: Box<[BitBox<Local, u8>]>, pub(crate) textures: Box<[Texture]>, pub(crate) entities: Box<[Entity]>, @@ -53,13 +53,13 @@ pub struct Q3BSPFile<T: CoordSystem> { pub(crate) effects: Box<[Effect]>, pub(crate) faces: Box<[Face]>, pub(crate) models: Box<[Model]>, - pub(crate) tree_root: BSPNode, + pub(crate) tree_root: BspNode, _phantom: PhantomData<T>, } -impl Q3BSPFile<Q3System> { +impl Q3BspFile<Q3System> { /// Parse `data` as a quake 3 bsp file. - pub fn parse_file(data: &[u8]) -> Result<Q3BSPFile<Q3System>> { + pub fn parse_file(data: &[u8]) -> Result<Q3BspFile<Q3System>> { let header = Header::from(data)?; let entities = entities::from_data(header.get_lump(&data, 0))?; @@ -101,7 +101,7 @@ impl Q3BSPFile<Q3System> { brushes.len() as u32, )?; - Ok(Q3BSPFile { + Ok(Q3BspFile { visdata, textures, entities, @@ -120,8 +120,8 @@ impl Q3BSPFile<Q3System> { } } -impl<T: CoordSystem> Q3BSPFile<T> { - pub fn swizzle_to<D: CoordSystem>(mut self) -> Q3BSPFile<D> +impl<T: CoordSystem> Q3BspFile<T> { + pub fn swizzle_to<D: CoordSystem>(mut self) -> Q3BspFile<D> where Swizzler: SwizzleFromTo<T, D>, { @@ -144,7 +144,7 @@ impl<T: CoordSystem> Q3BSPFile<T> { } // TODO: Possibly don't need to move? - Q3BSPFile { + Q3BspFile { visdata: self.visdata, textures: self.textures, entities: self.entities, diff --git a/stockton-levels/src/q3/light_maps.rs b/stockton-levels/src/q3/light_maps.rs index 605b7c2..6e07d33 100644 --- a/stockton-levels/src/q3/light_maps.rs +++ b/stockton-levels/src/q3/light_maps.rs @@ -15,10 +15,10 @@ * with this program. If not, see <http://www.gnu.org/licenses/>. */ -use super::Q3BSPFile; +use super::Q3BspFile; use crate::coords::CoordSystem; use crate::traits::light_maps::*; -use crate::types::{ParseError, Result, RGB}; +use crate::types::{ParseError, Result, Rgb}; /// The size of one LightMap const LIGHTMAP_SIZE: usize = 128 * 128 * 3; @@ -33,12 +33,12 @@ pub fn from_data(data: &[u8]) -> Result<Box<[LightMap]>> { let mut maps = Vec::with_capacity(length as usize); for n in 0..length { let raw = &data[n * LIGHTMAP_SIZE..(n + 1) * LIGHTMAP_SIZE]; - let mut map: [[RGB; 128]; 128] = [[RGB::white(); 128]; 128]; + let mut map: [[Rgb; 128]; 128] = [[Rgb::white(); 128]; 128]; for (x, outer) in map.iter_mut().enumerate() { for (y, inner) in outer.iter_mut().enumerate() { let offset = (x * 128 * 3) + (y * 3); - *inner = RGB::from_slice(&raw[offset..offset + 3]); + *inner = Rgb::from_slice(&raw[offset..offset + 3]); } } maps.push(LightMap { map }) @@ -47,7 +47,7 @@ pub fn from_data(data: &[u8]) -> Result<Box<[LightMap]>> { Ok(maps.into_boxed_slice()) } -impl<T: CoordSystem> HasLightMaps for Q3BSPFile<T> { +impl<T: CoordSystem> HasLightMaps for Q3BspFile<T> { type LightMapsIter<'a> = std::slice::Iter<'a, LightMap>; fn lightmaps_iter(&self) -> Self::LightMapsIter<'_> { diff --git a/stockton-levels/src/q3/light_vols.rs b/stockton-levels/src/q3/light_vols.rs index 1741569..932219e 100644 --- a/stockton-levels/src/q3/light_vols.rs +++ b/stockton-levels/src/q3/light_vols.rs @@ -17,10 +17,10 @@ use std::convert::TryInto; -use super::Q3BSPFile; +use super::Q3BspFile; use crate::coords::CoordSystem; use crate::traits::light_vols::*; -use crate::types::{ParseError, Result, RGB}; +use crate::types::{ParseError, Result, Rgb}; const VOL_LENGTH: usize = (3 * 2) + 2; @@ -34,8 +34,8 @@ pub fn from_data(data: &[u8]) -> Result<Box<[LightVol]>> { for n in 0..length { let data = &data[n * VOL_LENGTH..(n + 1) * VOL_LENGTH]; vols.push(LightVol { - ambient: RGB::from_slice(&data[0..3]), - directional: RGB::from_slice(&data[3..6]), + ambient: Rgb::from_slice(&data[0..3]), + directional: Rgb::from_slice(&data[3..6]), dir: data[6..8].try_into().unwrap(), }); } @@ -43,7 +43,7 @@ pub fn from_data(data: &[u8]) -> Result<Box<[LightVol]>> { Ok(vols.into_boxed_slice()) } -impl<T: CoordSystem> HasLightVols for Q3BSPFile<T> { +impl<T: CoordSystem> HasLightVols for Q3BspFile<T> { type LightVolsIter<'a> = std::slice::Iter<'a, LightVol>; fn lightvols_iter(&self) -> Self::LightVolsIter<'_> { diff --git a/stockton-levels/src/q3/mod.rs b/stockton-levels/src/q3/mod.rs index 810ae29..16d1a39 100644 --- a/stockton-levels/src/q3/mod.rs +++ b/stockton-levels/src/q3/mod.rs @@ -32,4 +32,4 @@ mod tree; mod vertices; mod visdata; -pub use self::file::Q3BSPFile; +pub use self::file::Q3BspFile; diff --git a/stockton-levels/src/q3/models.rs b/stockton-levels/src/q3/models.rs index e43a987..a1cc8b4 100644 --- a/stockton-levels/src/q3/models.rs +++ b/stockton-levels/src/q3/models.rs @@ -15,7 +15,7 @@ * with this program. If not, see <http://www.gnu.org/licenses/>. */ -use super::Q3BSPFile; +use super::Q3BspFile; use crate::coords::CoordSystem; use crate::helpers::{slice_to_u32, slice_to_vec3}; use crate::traits::models::*; @@ -69,7 +69,7 @@ pub fn from_data(data: &[u8], n_faces: u32, n_brushes: u32) -> Result<Box<[Model Ok(models.into_boxed_slice()) } -impl<T: CoordSystem> HasModels<T> for Q3BSPFile<T> { +impl<T: CoordSystem> HasModels<T> for Q3BspFile<T> { type ModelsIter<'a> = std::slice::Iter<'a, Model>; fn models_iter(&self) -> Self::ModelsIter<'_> { diff --git a/stockton-levels/src/q3/planes.rs b/stockton-levels/src/q3/planes.rs index 2ded1bf..4bd319f 100644 --- a/stockton-levels/src/q3/planes.rs +++ b/stockton-levels/src/q3/planes.rs @@ -17,7 +17,7 @@ const PLANE_SIZE: usize = (4 * 3) + 4; -use super::Q3BSPFile; +use super::Q3BspFile; use crate::coords::CoordSystem; use crate::helpers::{slice_to_f32, slice_to_vec3}; use crate::traits::planes::*; @@ -44,7 +44,7 @@ pub fn from_data(data: &[u8]) -> Result<Box<[Plane]>> { Ok(planes.into_boxed_slice()) } -impl<T: CoordSystem> HasPlanes<T> for Q3BSPFile<T> { +impl<T: CoordSystem> HasPlanes<T> for Q3BspFile<T> { type PlanesIter<'a> = std::slice::Iter<'a, Plane>; fn planes_iter(&self) -> Self::PlanesIter<'_> { diff --git a/stockton-levels/src/q3/textures.rs b/stockton-levels/src/q3/textures.rs index eaf4bb7..6b845eb 100644 --- a/stockton-levels/src/q3/textures.rs +++ b/stockton-levels/src/q3/textures.rs @@ -17,7 +17,7 @@ use std::str; -use super::Q3BSPFile; +use super::Q3BspFile; use crate::coords::CoordSystem; use crate::helpers::slice_to_u32; use crate::traits::textures::*; @@ -58,15 +58,19 @@ pub fn from_data(lump: &[u8]) -> Result<Box<[Texture]>> { Ok(textures.into_boxed_slice()) } -impl<T: CoordSystem> HasTextures for Q3BSPFile<T> { +impl<T: CoordSystem> HasTextures for Q3BspFile<T> { type TexturesIter<'a> = std::slice::Iter<'a, Texture>; fn textures_iter(&self) -> Self::TexturesIter<'_> { self.textures.iter() } - fn get_texture(&self, idx: u32) -> &Texture { - &self.textures[idx as usize] + fn get_texture(&self, idx: u32) -> Option<&Texture> { + if idx >= self.textures.len() as u32 { + None + } else { + Some(&self.textures[idx as usize]) + } } } diff --git a/stockton-levels/src/q3/tree.rs b/stockton-levels/src/q3/tree.rs index 598f71a..d399c7d 100644 --- a/stockton-levels/src/q3/tree.rs +++ b/stockton-levels/src/q3/tree.rs @@ -17,7 +17,7 @@ //! Parses the BSP tree into a usable format -use super::Q3BSPFile; +use super::Q3BspFile; use crate::coords::CoordSystem; use crate::helpers::{slice_to_i32, slice_to_u32, slice_to_vec3i}; use crate::traits::tree::*; @@ -33,12 +33,12 @@ pub fn from_data( leaf_brushes: &[u8], n_faces: u32, n_brushes: u32, -) -> Result<BSPNode> { +) -> Result<BspNode> { if nodes.len() % NODE_SIZE != 0 || leaves.len() % LEAF_SIZE != 0 { return Err(ParseError::Invalid); } - Ok(compile_node( + compile_node( 0, nodes, leaves, @@ -46,7 +46,7 @@ pub fn from_data( leaf_brushes, n_faces, n_brushes, - )?) + ) } /// Internal function. Visits given node and all its children. Used to recursively build tree. @@ -58,7 +58,7 @@ fn compile_node( leaf_brushes: &[u8], n_faces: u32, n_brushes: u32, -) -> Result<BSPNode> { +) -> Result<BspNode> { if i < 0 { // Leaf. let i = i.abs() - 1; @@ -111,7 +111,7 @@ fn compile_node( brushes.into_boxed_slice() }; - let leaf = BSPLeaf { + let leaf = BspLeaf { cluster_id: slice_to_u32(&raw[0..4]), area: slice_to_i32(&raw[4..8]), // 8..20 = min @@ -120,11 +120,11 @@ fn compile_node( brushes_idx, }; - Ok(BSPNode { + Ok(BspNode { plane_idx: 0, min: slice_to_vec3i(&raw[8..20]), max: slice_to_vec3i(&raw[20..32]), - value: BSPNodeValue::Leaf(leaf), + value: BspNodeValue::Leaf(leaf), }) } else { // Node. @@ -152,17 +152,17 @@ fn compile_node( let min = slice_to_vec3i(&raw[12..24]); let max = slice_to_vec3i(&raw[24..36]); - Ok(BSPNode { + Ok(BspNode { plane_idx, - value: BSPNodeValue::Children(Box::new(child_one), Box::new(child_two)), + value: BspNodeValue::Children(Box::new(child_one), Box::new(child_two)), min, max, }) } } -impl<T: CoordSystem> HasBSPTree<T> for Q3BSPFile<T> { - fn get_bsp_root(&self) -> &BSPNode { +impl<T: CoordSystem> HasBspTree<T> for Q3BspFile<T> { + fn get_bsp_root(&self) -> &BspNode { &self.tree_root } } diff --git a/stockton-levels/src/q3/vertices.rs b/stockton-levels/src/q3/vertices.rs index 107c34e..97cb28f 100644 --- a/stockton-levels/src/q3/vertices.rs +++ b/stockton-levels/src/q3/vertices.rs @@ -17,11 +17,11 @@ use std::convert::TryInto; -use super::Q3BSPFile; +use super::Q3BspFile; use crate::coords::CoordSystem; use crate::helpers::{slice_to_u32, slice_to_vec3}; use crate::traits::vertices::*; -use crate::types::{ParseError, Result, RGBA}; +use crate::types::{ParseError, Result, Rgba}; /// The size of one vertex const VERTEX_SIZE: usize = (4 * 3) + (2 * 2 * 4) + (4 * 3) + 4; @@ -42,7 +42,7 @@ pub fn verts_from_data(data: &[u8]) -> Result<Box<[Vertex]>> { position: slice_to_vec3(&vertex[0..12]), tex: TexCoord::from_bytes(&vertex[12..28].try_into().unwrap()), normal: slice_to_vec3(&vertex[28..40]), - color: RGBA::from_slice(&vertex[40..44]), + color: Rgba::from_slice(&vertex[40..44]), }) } @@ -64,7 +64,7 @@ pub fn meshverts_from_data(data: &[u8]) -> Result<Box<[MeshVert]>> { Ok(meshverts.into_boxed_slice()) } -impl<T: CoordSystem> HasVertices<T> for Q3BSPFile<T> { +impl<T: CoordSystem> HasVertices<T> for Q3BspFile<T> { type VerticesIter<'a> = std::slice::Iter<'a, Vertex>; fn vertices_iter(&self) -> Self::VerticesIter<'_> { @@ -76,7 +76,7 @@ impl<T: CoordSystem> HasVertices<T> for Q3BSPFile<T> { } } -impl<T: CoordSystem> HasMeshVerts<T> for Q3BSPFile<T> { +impl<T: CoordSystem> HasMeshVerts<T> for Q3BspFile<T> { type MeshVertsIter<'a> = std::slice::Iter<'a, MeshVert>; fn meshverts_iter(&self) -> Self::MeshVertsIter<'_> { diff --git a/stockton-levels/src/q3/visdata.rs b/stockton-levels/src/q3/visdata.rs index d4cb645..992a25b 100644 --- a/stockton-levels/src/q3/visdata.rs +++ b/stockton-levels/src/q3/visdata.rs @@ -20,7 +20,7 @@ use bitvec::prelude::*; use std::vec::IntoIter; -use super::file::Q3BSPFile; +use super::file::Q3BspFile; use crate::coords::CoordSystem; use crate::helpers::slice_to_i32; use crate::traits::visdata::*; @@ -49,7 +49,7 @@ pub fn from_data(data: &[u8]) -> Result<Box<[BitBox<Local, u8>]>> { Ok(vecs.into_boxed_slice()) } -impl<T: CoordSystem> HasVisData for Q3BSPFile<T> { +impl<T: CoordSystem> HasVisData for Q3BspFile<T> { type VisibleIterator = IntoIter<ClusterId>; fn all_visible_from(&self, from: ClusterId) -> Self::VisibleIterator { diff --git a/stockton-levels/src/traits/light_maps.rs b/stockton-levels/src/traits/light_maps.rs index 50ec84d..cc7fa70 100644 --- a/stockton-levels/src/traits/light_maps.rs +++ b/stockton-levels/src/traits/light_maps.rs @@ -17,12 +17,12 @@ use std::fmt; -use crate::types::RGB; +use crate::types::Rgb; /// Stores light map textures that help make surface lighting more realistic #[derive(Clone)] pub struct LightMap { - pub map: [[RGB; 128]; 128], + pub map: [[Rgb; 128]; 128], } impl PartialEq for LightMap { diff --git a/stockton-levels/src/traits/light_vols.rs b/stockton-levels/src/traits/light_vols.rs index 48972ef..016ddab 100644 --- a/stockton-levels/src/traits/light_vols.rs +++ b/stockton-levels/src/traits/light_vols.rs @@ -15,12 +15,12 @@ * with this program. If not, see <http://www.gnu.org/licenses/>. */ -use crate::types::RGB; +use crate::types::Rgb; #[derive(Debug, Clone, Copy)] pub struct LightVol { - pub ambient: RGB, - pub directional: RGB, + pub ambient: Rgb, + pub directional: Rgb, pub dir: [u8; 2], } diff --git a/stockton-levels/src/traits/mod.rs b/stockton-levels/src/traits/mod.rs index c65cde8..00d129f 100644 --- a/stockton-levels/src/traits/mod.rs +++ b/stockton-levels/src/traits/mod.rs @@ -39,6 +39,6 @@ pub use self::light_vols::HasLightVols; pub use self::models::HasModels; pub use self::planes::HasPlanes; pub use self::textures::HasTextures; -pub use self::tree::HasBSPTree; +pub use self::tree::HasBspTree; pub use self::vertices::{HasMeshVerts, HasVertices}; pub use self::visdata::HasVisData; diff --git a/stockton-levels/src/traits/textures.rs b/stockton-levels/src/traits/textures.rs index 4477fba..66c120c 100644 --- a/stockton-levels/src/traits/textures.rs +++ b/stockton-levels/src/traits/textures.rs @@ -160,5 +160,5 @@ pub trait HasTextures { type TexturesIter<'a>: Iterator<Item = &'a Texture>; fn textures_iter(&self) -> Self::TexturesIter<'_>; - fn get_texture(&self, idx: u32) -> &Texture; + fn get_texture(&self, idx: u32) -> Option<&Texture>; } diff --git a/stockton-levels/src/traits/tree.rs b/stockton-levels/src/traits/tree.rs index 253ae1b..5ca0d59 100644 --- a/stockton-levels/src/traits/tree.rs +++ b/stockton-levels/src/traits/tree.rs @@ -24,29 +24,29 @@ use na::Vector3; /// A node in a BSP tree. /// Either has two children *or* a leaf entry. #[derive(Debug, Clone)] -pub struct BSPNode { +pub struct BspNode { pub plane_idx: u32, pub min: Vector3<i32>, pub max: Vector3<i32>, - pub value: BSPNodeValue, + pub value: BspNodeValue, } #[derive(Debug, Clone)] -pub enum BSPNodeValue { - Leaf(BSPLeaf), - Children(Box<BSPNode>, Box<BSPNode>), +pub enum BspNodeValue { + Leaf(BspLeaf), + Children(Box<BspNode>, Box<BspNode>), } /// A leaf in a BSP tree. /// Will be under a `BSPNode`, min and max values are stored there. #[derive(Debug, Clone)] -pub struct BSPLeaf { +pub struct BspLeaf { pub cluster_id: u32, pub area: i32, pub faces_idx: Box<[u32]>, pub brushes_idx: Box<[u32]>, } -pub trait HasBSPTree<S: CoordSystem>: HasFaces<S> + HasBrushes<S> + HasVisData { - fn get_bsp_root(&self) -> &BSPNode; +pub trait HasBspTree<S: CoordSystem>: HasFaces<S> + HasBrushes<S> + HasVisData { + fn get_bsp_root(&self) -> &BspNode; } diff --git a/stockton-levels/src/traits/vertices.rs b/stockton-levels/src/traits/vertices.rs index ff23785..44c3000 100644 --- a/stockton-levels/src/traits/vertices.rs +++ b/stockton-levels/src/traits/vertices.rs @@ -17,7 +17,7 @@ use crate::coords::CoordSystem; use crate::helpers::slice_to_f32; -use crate::types::RGBA; +use crate::types::Rgba; use na::Vector3; /// A vertex, used to describe a face. @@ -26,7 +26,7 @@ pub struct Vertex { pub position: Vector3<f32>, pub tex: TexCoord, pub normal: Vector3<f32>, - pub color: RGBA, + pub color: Rgba, } /// Represents a TexCoord. 0 = surface, 1= lightmap. diff --git a/stockton-levels/src/types.rs b/stockton-levels/src/types.rs index fa90398..4af0725 100644 --- a/stockton-levels/src/types.rs +++ b/stockton-levels/src/types.rs @@ -21,17 +21,17 @@ use std::convert::TryInto; /// RGBA Colour (0-255) #[derive(Debug, Clone, Copy, PartialEq)] -pub struct RGBA { +pub struct Rgba { pub r: u8, pub g: u8, pub b: u8, pub a: u8, } -impl RGBA { +impl Rgba { /// Interpret the given bytes as an RGBA colour. - pub fn from_bytes(bytes: [u8; 4]) -> RGBA { - RGBA { + pub fn from_bytes(bytes: [u8; 4]) -> Rgba { + Rgba { r: bytes[0], g: bytes[1], b: bytes[2], @@ -42,23 +42,23 @@ impl RGBA { /// Convert a slice to an RGBA colour /// # Panics /// If slice is not 4 bytes long. - pub fn from_slice(slice: &[u8]) -> RGBA { - RGBA::from_bytes(slice.try_into().unwrap()) + pub fn from_slice(slice: &[u8]) -> Rgba { + Rgba::from_bytes(slice.try_into().unwrap()) } } /// RGB Colour (0-255) #[derive(Debug, Clone, Copy, PartialEq)] -pub struct RGB { +pub struct Rgb { pub r: u8, pub g: u8, pub b: u8, } -impl RGB { +impl Rgb { /// 255, 255, 255 - pub fn white() -> RGB { - RGB { + pub fn white() -> Rgb { + Rgb { r: 255, g: 255, b: 255, @@ -66,8 +66,8 @@ impl RGB { } /// Interpret the given bytes as an RGB colour. - pub fn from_bytes(bytes: [u8; 3]) -> RGB { - RGB { + pub fn from_bytes(bytes: [u8; 3]) -> Rgb { + Rgb { r: bytes[0], g: bytes[1], b: bytes[2], @@ -77,8 +77,8 @@ impl RGB { /// Convert a slice to an RGB colour /// # Panics /// If slice is not 3 bytes long. - pub fn from_slice(slice: &[u8]) -> RGB { - RGB::from_bytes(slice.try_into().unwrap()) + pub fn from_slice(slice: &[u8]) -> Rgb { + Rgb::from_bytes(slice.try_into().unwrap()) } } 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)) } |