diff --git a/Cargo.lock b/Cargo.lock index 7a09e3c..4e6e011 100755 --- a/Cargo.lock +++ b/Cargo.lock @@ -1294,6 +1294,7 @@ dependencies = [ "tracing-appender", "tracing-log", "tracing-subscriber", + "uuid", "wgpu", "winit", ] @@ -1552,7 +1553,7 @@ checksum = "e63899ad0da84ce718c14936262a41cee2c79c981fc0a0e7c7beb47d5a07e8c1" dependencies = [ "num-integer", "num-traits", - "rand", + "rand 0.4.6", "rustc-serialize", ] @@ -1867,6 +1868,12 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "ppv-lite86" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" + [[package]] name = "proc-easy" version = "0.3.0" @@ -1934,6 +1941,27 @@ dependencies = [ "winapi", ] +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha", + "rand_core 0.6.4", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core 0.6.4", +] + [[package]] name = "rand_core" version = "0.3.1" @@ -1949,6 +1977,15 @@ version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c33a3c44ca05fa6f1807d8e6743f3824e8509beca625669633be0acbdf509dc" +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom", +] + [[package]] name = "range-alloc" version = "0.1.3" @@ -2483,11 +2520,12 @@ checksum = "daf8dba3b7eb870caf1ddeed7bc9d2a049f3cfdfae7cb521b087cc33ae4c49da" [[package]] name = "uuid" -version = "1.4.1" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "79daa5ed5740825c40b389c5e50312b9c86df53fccd33f281df655642b43869d" +checksum = "88ad59a7560b41a70d191093a945f0b87bc1deeda46fb237479708a1d6b6cdfc" dependencies = [ "getrandom", + "rand 0.8.5", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 4f755b4..1aaf3ca 100755 --- a/Cargo.toml +++ b/Cargo.toml @@ -36,3 +36,4 @@ aligned-vec = "0.5.0" tracing-appender = "0.2.2" stopwatch = "0.0.7" petgraph = "0.6.4" +uuid = { version = "1.5.0", features = ["v4", "fast-rng"] } \ No newline at end of file diff --git a/examples/testbed/Cargo.lock b/examples/testbed/Cargo.lock index 3893931..f7dfad3 100644 --- a/examples/testbed/Cargo.lock +++ b/examples/testbed/Cargo.lock @@ -1333,6 +1333,7 @@ dependencies = [ "tracing-appender", "tracing-log", "tracing-subscriber", + "uuid", "wgpu", "winit", ] @@ -1581,7 +1582,7 @@ checksum = "e63899ad0da84ce718c14936262a41cee2c79c981fc0a0e7c7beb47d5a07e8c1" dependencies = [ "num-integer", "num-traits", - "rand", + "rand 0.4.6", "rustc-serialize", ] @@ -1877,6 +1878,12 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "ppv-lite86" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" + [[package]] name = "proc-easy" version = "0.3.0" @@ -1944,6 +1951,27 @@ dependencies = [ "winapi", ] +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha", + "rand_core 0.6.4", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core 0.6.4", +] + [[package]] name = "rand_core" version = "0.3.1" @@ -1959,6 +1987,15 @@ version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c33a3c44ca05fa6f1807d8e6743f3824e8509beca625669633be0acbdf509dc" +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom", +] + [[package]] name = "range-alloc" version = "0.1.3" @@ -2508,11 +2545,12 @@ checksum = "daf8dba3b7eb870caf1ddeed7bc9d2a049f3cfdfae7cb521b087cc33ae4c49da" [[package]] name = "uuid" -version = "1.4.1" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "79daa5ed5740825c40b389c5e50312b9c86df53fccd33f281df655642b43869d" +checksum = "88ad59a7560b41a70d191093a945f0b87bc1deeda46fb237479708a1d6b6cdfc" dependencies = [ "getrandom", + "rand 0.8.5", ] [[package]] diff --git a/examples/testbed/assets/AntiqueCamera.glb b/examples/testbed/assets/AntiqueCamera.glb new file mode 100644 index 0000000..a45f134 Binary files /dev/null and b/examples/testbed/assets/AntiqueCamera.glb differ diff --git a/examples/testbed/assets/cube-texture-bin.glb b/examples/testbed/assets/cube-texture-bin.glb new file mode 100644 index 0000000..9a3965b Binary files /dev/null and b/examples/testbed/assets/cube-texture-bin.glb differ diff --git a/examples/testbed/assets/texture-sep/texture-sep.bin b/examples/testbed/assets/texture-sep/texture-sep.bin new file mode 100644 index 0000000..9322438 Binary files /dev/null and b/examples/testbed/assets/texture-sep/texture-sep.bin differ diff --git a/examples/testbed/assets/texture-sep/texture-sep.gltf b/examples/testbed/assets/texture-sep/texture-sep.gltf new file mode 100644 index 0000000..8e9d366 --- /dev/null +++ b/examples/testbed/assets/texture-sep/texture-sep.gltf @@ -0,0 +1,137 @@ +{ + "asset":{ + "generator":"Khronos glTF Blender I/O v3.6.6", + "version":"2.0" + }, + "scene":0, + "scenes":[ + { + "name":"Scene", + "nodes":[ + 0 + ] + } + ], + "nodes":[ + { + "mesh":0, + "name":"Cube" + } + ], + "materials":[ + { + "doubleSided":true, + "name":"Material", + "pbrMetallicRoughness":{ + "baseColorTexture":{ + "index":0 + }, + "metallicFactor":0, + "roughnessFactor":0.5 + } + } + ], + "meshes":[ + { + "name":"Cube", + "primitives":[ + { + "attributes":{ + "POSITION":0, + "TEXCOORD_0":1, + "NORMAL":2 + }, + "indices":3, + "material":0 + } + ] + } + ], + "textures":[ + { + "sampler":0, + "source":0 + } + ], + "images":[ + { + "mimeType":"image/png", + "name":"uvgrid", + "uri":"uvgrid.png" + } + ], + "accessors":[ + { + "bufferView":0, + "componentType":5126, + "count":24, + "max":[ + 1, + 1, + 1 + ], + "min":[ + -1, + -1, + -1 + ], + "type":"VEC3" + }, + { + "bufferView":1, + "componentType":5126, + "count":24, + "type":"VEC2" + }, + { + "bufferView":2, + "componentType":5126, + "count":24, + "type":"VEC3" + }, + { + "bufferView":3, + "componentType":5123, + "count":36, + "type":"SCALAR" + } + ], + "bufferViews":[ + { + "buffer":0, + "byteLength":288, + "byteOffset":0, + "target":34962 + }, + { + "buffer":0, + "byteLength":192, + "byteOffset":288, + "target":34962 + }, + { + "buffer":0, + "byteLength":288, + "byteOffset":480, + "target":34962 + }, + { + "buffer":0, + "byteLength":72, + "byteOffset":768, + "target":34963 + } + ], + "samplers":[ + { + "magFilter":9729, + "minFilter":9987 + } + ], + "buffers":[ + { + "byteLength":840, + "uri":"texture-sep.bin" + } + ] +} diff --git a/examples/testbed/assets/texture-sep/uvgrid.png b/examples/testbed/assets/texture-sep/uvgrid.png new file mode 100644 index 0000000..06aad7c Binary files /dev/null and b/examples/testbed/assets/texture-sep/uvgrid.png differ diff --git a/examples/testbed/src/main.rs b/examples/testbed/src/main.rs index 16ef3ac..4a74aa7 100644 --- a/examples/testbed/src/main.rs +++ b/examples/testbed/src/main.rs @@ -75,35 +75,18 @@ async fn main() { let mut resman = world.get_resource_mut::().unwrap(); let diffuse_texture = resman.request::("assets/happy-tree.png").unwrap(); - //let cube_model = resman.request::("assets/cube-embedded.gltf").unwrap(); - let cube_model = resman.request::("assets/cube-texture-embedded.gltf").unwrap(); + let antique_camera_model = resman.request::("assets/AntiqueCamera.glb").unwrap(); + let cube_model = resman.request::("assets/texture-sep/texture-sep.gltf").unwrap(); drop(resman); - /* world.spawn((MeshComponent::new( - Mesh { - vertices: VERTICES.to_vec(), - indices: Some(INDICES.to_vec()) - }, Material { - shader_id: 0, - texture: diffuse_texture.clone() - }), - TransformComponent::from(Transform::from_xyz(0.005, 0.0, -2.0)), - )); - - world.spawn((MeshComponent::new( - Mesh { - vertices: VERTICES.to_vec(), - indices: Some(INDICES.to_vec()) - }, Material { - shader_id: 0, - texture: diffuse_texture - }), - TransformComponent::from(Transform::from_xyz(0.005, 0.7, -0.5)), + /* world.spawn(( + ModelComponent(cube_model.clone()), + TransformComponent::from(Transform::from_xyz(3.0, 0.5, -2.2)), )); */ world.spawn(( - ModelComponent(cube_model), - TransformComponent::from(Transform::from_xyz(0.005, 0.5, -2.2)), + ModelComponent(antique_camera_model), + TransformComponent::from(Transform::from_xyz(0.0, -5.0, -10.0)), )); let mut camera = CameraComponent::new_3d(); @@ -115,7 +98,6 @@ async fn main() { Ok(()) }; - //world.insert_resource(fps_counter::FPSCounter::new()); let fps_system = |world: &mut World| -> anyhow::Result<()> { let mut counter: RefMut = world.get_resource_mut().unwrap(); diff --git a/lyra-resource/src/loader/model.rs b/lyra-resource/src/loader/model.rs index e2d2b6d..daae8e4 100644 --- a/lyra-resource/src/loader/model.rs +++ b/lyra-resource/src/loader/model.rs @@ -27,6 +27,17 @@ impl From for LoaderError { } } +pub(crate) struct GltfLoadContext<'a> { + pub resource_manager: &'a mut ResourceManager, + pub gltf: &'a gltf::Gltf, + /// Path to the gltf + pub gltf_path: &'a str, + /// The path to the directory that the gltf is contained in. + pub gltf_parent_path: &'a str, + /// List of buffers in the gltf + pub buffers: &'a Vec>, +} + #[derive(Default)] pub struct ModelLoader; @@ -59,7 +70,10 @@ impl ModelLoader { // read the positions if let Some(pos) = reader.read_positions() { - debug!("Mesh mode is {:?}", prim.mode()); + if prim.mode() != gltf::mesh::Mode::Triangles { + todo!("Load position primitives that aren't triangles"); // TODO + } + let pos: Vec = pos.map(|t| t.into()).collect(); new_mesh.add_attribute(MeshVertexAttribute::Position, VertexAttributeData::Vec3(pos)); } @@ -114,7 +128,7 @@ impl ModelLoader { impl ResourceLoader for ModelLoader { fn extensions(&self) -> &[&str] { &[ - "gltf" + "gltf", "glb" ] } @@ -132,11 +146,27 @@ impl ResourceLoader for ModelLoader { parent_path.pop(); let parent_path = parent_path.display().to_string(); + /* let (document, buffers, images) = gltf::import(&path)?; + let buffers: Vec> = buffers.into_iter().map(|b| b.0).collect(); + + let scene = document.scenes().next().unwrap(); + + let materials: Vec = document.materials() + .map(|mat| Material::from_gltf(resource_manager, &parent_path, mat)).collect(); + + let meshes: Vec = scene.nodes() + .map(|node| self.process_node(&buffers, &materials, node)) + .flatten().collect(); */ + let gltf = gltf::Gltf::open(path)?; + let mut use_bin = false; let buffers: Vec> = gltf.buffers().map(|b| match b.source() { - gltf::buffer::Source::Bin => gltf.blob.as_deref().map(|v| v.to_vec()) - .ok_or(ModelLoaderError::MissingBin(path.to_string())), + gltf::buffer::Source::Bin => { + use_bin = true; + gltf.blob.as_deref().map(|v| v.to_vec()) + .ok_or(ModelLoaderError::MissingBin(path.to_string())) + }, gltf::buffer::Source::Uri(uri) => util::gltf_read_buffer_uri(&parent_path, uri) .map_err(|e| ModelLoaderError::UriDecodingError(e)), }).flatten().collect(); @@ -144,13 +174,21 @@ impl ResourceLoader for ModelLoader { // TODO: Read in multiple scenes let scene = gltf.scenes().next().unwrap(); - // Load the materials + let mut context = GltfLoadContext { + resource_manager, + gltf: &gltf, + gltf_path: path, + gltf_parent_path: &parent_path, + buffers: &buffers, + }; + let materials: Vec = gltf.materials() - .map(|mat| Material::from_gltf(resource_manager, &parent_path, mat)).collect(); + .map(|mat| Material::from_gltf(&mut context, mat)).collect(); let meshes: Vec = scene.nodes() .map(|node| self.process_node(&buffers, &materials, node)) .flatten().collect(); + debug!("Loaded {} meshes, and {} materials from '{}'", meshes.len(), materials.len(), path); Ok(Arc::new(Resource::with_data(path, Model::new(meshes)))) } diff --git a/lyra-resource/src/material.rs b/lyra-resource/src/material.rs index 252b2e1..7294486 100644 --- a/lyra-resource/src/material.rs +++ b/lyra-resource/src/material.rs @@ -1,6 +1,6 @@ use std::{fs::File, io::{BufReader, Read}, collections::hash_map::DefaultHasher, hash::{Hash, Hasher}}; -use crate::{Texture, ResHandle, ResourceManager, util}; +use crate::{Texture, ResHandle, ResourceManager, util, loader::model::GltfLoadContext}; /// PBR metallic roughness #[derive(Clone, Debug, Default)] @@ -159,10 +159,11 @@ impl Material { } } - fn read_source(resource_manager: &mut ResourceManager, gltf_rel_path: &str, src: gltf::image::Source) -> Result, util::UriReadError> { + fn read_source(context: &mut GltfLoadContext, src: gltf::image::Source) -> Result, util::UriReadError> { + let gltf_rel_path = context.gltf_parent_path; // TODO: Don't copy sources match src { - gltf::image::Source::View { view, mime_type } => { + gltf::image::Source::View { view, mime_type: _ } => { let buf = view.buffer(); let src = buf.source(); @@ -170,7 +171,12 @@ impl Material { let len = view.length(); match src { - gltf::buffer::Source::Bin => todo!("Read material source from gltf Bin"), + gltf::buffer::Source::Bin => { + let mut b = context.gltf.blob.clone().unwrap(); + b.drain(0..offset); + b.truncate(len); + Ok(b) + }, gltf::buffer::Source::Uri(uri) => { util::gltf_read_buffer_uri(gltf_rel_path, uri) .map(|mut buf| { @@ -181,23 +187,23 @@ impl Material { } } }, - gltf::image::Source::Uri { uri, mime_type } => { + gltf::image::Source::Uri { uri, mime_type: _ } => { util::gltf_read_buffer_uri(gltf_rel_path, uri) }, } } - fn load_texture(resource_manager: &mut ResourceManager, gltf_rel_path: &str, texture_info: gltf::texture::Info<'_>) -> ResHandle { + fn load_texture(context: &mut GltfLoadContext, texture_info: gltf::texture::Info<'_>) -> ResHandle { // TODO: texture_info.tex_coord() let tex = texture_info.texture(); let img = tex.source(); let src = img.source(); - let buf = Material::read_source(resource_manager, gltf_rel_path, src).unwrap(); + let buf = Material::read_source(context, src).unwrap(); let buflen = buf.len(); let mime_type = infer::get(&buf).expect("Failed to get file type").mime_type(); - resource_manager.load_bytes::(&uuid::Uuid::new_v4().to_string(), mime_type, + context.resource_manager.load_bytes::(&uuid::Uuid::new_v4().to_string(), mime_type, buf, 0, buflen).unwrap() } @@ -205,21 +211,17 @@ impl Material { /// /// `gltf_rel_path`: The relative path of the gltf file, /// e.g. gltf model path is "resource/models/player.gltf", the relative path would be "resource/models/" - pub fn from_gltf(resource_manager: &mut ResourceManager, gltf_rel_path: &str, gltf_mat: gltf::Material) -> Self { + pub(crate) fn from_gltf(context: &mut GltfLoadContext, gltf_mat: gltf::Material) -> Self { let pbr_rough = gltf_mat.pbr_metallic_roughness(); let base_color = pbr_rough.base_color_factor().into(); let metallic = pbr_rough.metallic_factor(); let roughness = pbr_rough.roughness_factor(); let base_color_texture = pbr_rough.base_color_texture() - .map(|info| Material::load_texture(resource_manager, gltf_rel_path, info)); + .map(|info| Material::load_texture(context, info)); let metallic_roughness_texture = pbr_rough.metallic_roughness_texture() - .map(|info| Material::load_texture(resource_manager, gltf_rel_path, info)); - - /* let base_color_texture = if let Some(base_tex_info) = pbr_rough.base_color_texture() { - Some(Material::load_texture(resource_manager, gltf_rel_path, base_tex_info)) - } else { None }; */ + .map(|info| Material::load_texture(context, info)); Material { name: gltf_mat.name() diff --git a/lyra-resource/src/model.rs b/lyra-resource/src/model.rs index 9484232..76ccfc6 100644 --- a/lyra-resource/src/model.rs +++ b/lyra-resource/src/model.rs @@ -77,13 +77,25 @@ pub enum MeshVertexAttribute { Other(String), } -#[derive(Clone, Default, edict::Component)] +#[derive(Clone, edict::Component)] pub struct Mesh { + pub uuid: uuid::Uuid, pub attributes: HashMap, pub indices: Option, material: Option, } +impl Default for Mesh { + fn default() -> Self { + Self { + uuid: uuid::Uuid::new_v4(), + attributes: Default::default(), + indices: Default::default(), + material: Default::default() + } + } +} + impl Mesh { pub fn add_attribute(&mut self, attribute: MeshVertexAttribute, data: VertexAttributeData) { self.attributes.insert(attribute, data); diff --git a/lyra-resource/src/util.rs b/lyra-resource/src/util.rs index e7202df..1061d31 100644 --- a/lyra-resource/src/util.rs +++ b/lyra-resource/src/util.rs @@ -19,20 +19,23 @@ pub enum UriReadError { /// /// * `containing_path`: The path of the containing folder of the buffers "parent", /// the parent being where this buffer is defined in, -/// i.e. parent="resources/models/player.gltf", containing="reousrce/models" +/// i.e. parent="resources/models/player.gltf", containing="resource/models" pub(crate) fn gltf_read_buffer_uri(containing_path: &str, uri: &str) -> Result, UriReadError> { - let uri = uri.strip_prefix("data").ok_or(UriReadError::None)?; - let (mime, data) = uri.split_once(",").ok_or(UriReadError::None)?; - - let (_mime, is_base64) = match mime.strip_suffix(";base64") { - Some(mime) => (mime, true), - None => (mime, false), - }; - - if is_base64 { - base64::engine::general_purpose::STANDARD.decode(data).map_err(|e| UriReadError::Base64Decode(e)) + if let Some((mime, data)) = uri.strip_prefix("data") + .and_then(|uri| uri.split_once(",")) { + let (_mime, is_base64) = match mime.strip_suffix(";base64") { + Some(mime) => (mime, true), + None => (mime, false), + }; + + if is_base64 { + base64::engine::general_purpose::STANDARD.decode(data) + .map_err(|e| UriReadError::Base64Decode(e)) + } else { + Ok(data.as_bytes().to_vec()) + } } else { - let full_path = format!("{containing_path}/{data}"); + let full_path = format!("{containing_path}/{uri}"); std::fs::read(&full_path).map_err(|e| UriReadError::IoError(e)) } } \ No newline at end of file diff --git a/lyra-resource/test_files/gltf/texture-sep/Green.png b/lyra-resource/test_files/gltf/texture-sep/Green.png deleted file mode 100644 index fd1076e..0000000 Binary files a/lyra-resource/test_files/gltf/texture-sep/Green.png and /dev/null differ diff --git a/rust-toolchain b/rust-toolchain new file mode 100644 index 0000000..07ade69 --- /dev/null +++ b/rust-toolchain @@ -0,0 +1 @@ +nightly \ No newline at end of file diff --git a/src/game.rs b/src/game.rs index e719846..c76cfde 100755 --- a/src/game.rs +++ b/src/game.rs @@ -297,7 +297,7 @@ impl Game { .with(fmt::layer().with_writer(stdout_layer)) .with(filter::Targets::new() .with_target("lyra_engine", Level::TRACE) - .with_target("wgpu_core", Level::INFO) + .with_target("wgpu_core", Level::WARN) .with_default(Level::DEBUG)) .init(); diff --git a/src/lib.rs b/src/lib.rs index 4c01741..e45bd7b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,3 +1,5 @@ +#![feature(hash_extract_if)] + pub mod game; pub mod render; pub mod input_event; diff --git a/src/render/render_job.rs b/src/render/render_job.rs index a7000aa..319329f 100755 --- a/src/render/render_job.rs +++ b/src/render/render_job.rs @@ -2,48 +2,23 @@ use edict::EntityId; use crate::math::Transform; -//use super::mesh::Mesh; -use lyra_resource::Mesh; - pub struct RenderJob { - mesh: Mesh, - entity: EntityId, + pub entity: EntityId, + pub shader_id: u64, + pub mesh_buffer_id: uuid::Uuid, - transform: Transform, - last_transform: Option, // TODO: render interpolation + pub transform: Transform, + pub last_transform: Option, // TODO: render interpolation } impl RenderJob { - pub fn new(mesh: Mesh, entity: EntityId, transform: Transform, last_transform: Option) -> Self { + pub fn new(entity: EntityId, shader_id: u64, mesh_buffer_id: uuid::Uuid, transform: Transform, last_transform: Option) -> Self { Self { - mesh, entity, + shader_id, + mesh_buffer_id, transform, last_transform, } } - - pub fn mesh(&self)-> &Mesh { - &self.mesh - } - - pub fn entity(&self)-> EntityId { - self.entity - } - - pub fn transform(&self)-> &Transform { - &self.transform - } - - pub fn set_transform(&mut self, transform: Transform){ - self.transform = transform; - } - - pub fn last_transform(&self)-> Option<&Transform> { - self.last_transform.as_ref() - } - - pub fn set_last_transform(&mut self, last_transform: Transform){ - self.last_transform = Some(last_transform); - } } \ No newline at end of file diff --git a/src/render/renderer.rs b/src/render/renderer.rs index 345a303..05844a8 100755 --- a/src/render/renderer.rs +++ b/src/render/renderer.rs @@ -17,6 +17,7 @@ use crate::ecs::components::camera::CameraComponent; use crate::ecs::components::mesh::MeshComponent; use crate::ecs::components::model::ModelComponent; use crate::ecs::components::transform::TransformComponent; +use crate::math::Transform; use super::camera::RenderCamera; use super::desc_buf_lay::DescVertexBufferLayout; @@ -68,13 +69,15 @@ struct TransformBuffers { impl TransformBuffers { /// Update an entity's buffer with the new transform. Will panic if the entity isn't stored - fn update_entity(&mut self, queue: &wgpu::Queue, limits: &Limits, entity: EntityId, transform: glam::Mat4) { + fn update_entity(&mut self, queue: &wgpu::Queue, limits: &Limits, entity: EntityId, transform: glam::Mat4) -> TransformBufferIndices { let indices = self.not_updated.remove(&entity) + .or_else(|| self.just_updated.remove(&entity)) .expect("Use 'insert_entity' for new entities"); self.just_updated.insert(entity, indices); let (_, buffer, _) = self.buffer_bindgroups.get(indices.buffer_index).unwrap(); queue.write_buffer(buffer, indices.transform_index as u64 * limits.min_uniform_buffer_offset_alignment as u64, bytemuck::bytes_of(&transform)); + indices } /// Insert a new entity into the buffer, returns where it was stored. @@ -104,6 +107,22 @@ impl TransformBuffers { indices } + /// Update or insert an entities transform + fn update_or_insert(&mut self, queue: &wgpu::Queue, limits: &Limits, entity: EntityId, transform_fn: TFn) -> TransformBufferIndices + where TFn: Fn() -> glam::Mat4 + { + if self.contains(entity) { + self.update_entity(queue, limits, entity, transform_fn()) + } else { + self.insert_entity(queue, limits, entity, transform_fn()) + } + } + + /// Returns true if the entity's transform is stored (does not mean its up-to-date). + fn contains(&self, entity: EntityId) -> bool { + self.not_updated.contains_key(&entity) || self.just_updated.contains_key(&entity) + } + /// Collect the dead entities, mark entities and not updated for next updates. fn tick(&mut self) { // take the dead entities, these were ones that were not updated this tick @@ -111,7 +130,6 @@ impl TransformBuffers { .map(|t| t.clone()).collect(); self.dead_indices = dead; - // swap just_updated into not_updated self.not_updated = self.just_updated.clone(); self.just_updated.clear(); } @@ -143,7 +161,8 @@ pub struct BasicRenderer { pub render_pipelines: HashMap>, pub render_jobs: VecDeque, - buffer_storage: HashMap, // TODO: clean up left over buffers from deleted entities/components + mesh_buffers: HashMap, // TODO: clean up left over buffers from deleted entities/components + entity_meshes: HashMap, transform_buffers: TransformBuffers, transform_bind_group_layout: BindGroupLayout, @@ -205,7 +224,7 @@ impl BasicRenderer { false => surface_caps.present_modes[0] }; */ - println!("present mode: {:?}", present_mode); + debug!("present mode: {:?}", present_mode); let surface_format = surface_caps.formats.iter() .copied() @@ -380,7 +399,8 @@ impl BasicRenderer { }, render_pipelines: HashMap::new(), render_jobs: VecDeque::new(), - buffer_storage: HashMap::new(), + mesh_buffers: HashMap::new(), + entity_meshes: HashMap::new(), render_limits, transform_buffers, @@ -405,17 +425,16 @@ impl BasicRenderer { s } - fn update_mesh_buffers(&mut self, entity: EntityId, mesh: &Mesh) { - if let Some(buffers) = self.buffer_storage.get_mut(&entity) { + fn update_mesh_buffers(&mut self, _entity: EntityId, mesh: &Mesh) { + if let Some(buffers) = self.mesh_buffers.get_mut(&mesh.uuid) { // check if the buffer sizes dont match. If they dont, completely remake the buffers let vertices = mesh.position().unwrap(); if buffers.buffer_vertex.count() != vertices.len() { - drop(buffers); - debug!("Recreating buffers for mesh"); + debug!("Recreating buffers for mesh {}", mesh.uuid.to_string()); let (vert, idx) = self.create_vertex_index_buffers(mesh); // have to re-get buffers because of borrow checker - let buffers = self.buffer_storage.get_mut(&entity).unwrap(); + let buffers = self.mesh_buffers.get_mut(&mesh.uuid).unwrap(); buffers.buffer_indices = idx; buffers.buffer_vertex = vert; @@ -448,13 +467,11 @@ impl BasicRenderer { let tex_coords: Vec = mesh.tex_coords().cloned() .unwrap_or_else(|| vec![glam::Vec2::new(0.0, 0.0); positions.len()]); - debug!("Pos count: {}, tex coords count: {}", positions.len(), tex_coords.len()); assert!(positions.len() == tex_coords.len()); let vertex_inputs: Vec = std::iter::zip(positions, tex_coords.into_iter()) .map(|(v, t)| Vertex::new(v.clone(), t)) .collect(); - println!("Vertex inputs: {:?}", vertex_inputs.as_slice()); let vertex_buffer = self.device.create_buffer_init( &wgpu::util::BufferInitDescriptor { @@ -565,6 +582,28 @@ impl BasicRenderer { buffers.next_indices = indices; indices } + + /// Processes the mesh for the renderer, storing and creating buffers as needed. Returns true if a new mesh was processed. + fn process_mesh(&mut self, entity: EntityId, transform: Transform, mesh: &Mesh) -> bool { + let indices = self.transform_buffers.update_or_insert(&self.queue, &self.render_limits, + entity, || transform.calculate_mat4()); + + if self.mesh_buffers.contains_key(&mesh.uuid) { + false + } else { + // check if the transform buffers need to be expanded + if self.transform_buffers.should_expand() { + self.expand_transform_buffers(); + } + + // create the mesh's buffers + let buffers = self.create_mesh_buffers(mesh, indices); + self.mesh_buffers.insert(mesh.uuid, buffers); + self.entity_meshes.insert(entity, mesh.uuid); + + true + } + } } impl Renderer for BasicRenderer { @@ -574,37 +613,20 @@ impl Renderer for BasicRenderer { let mut alive_entities = HashSet::new(); for (entity, model, model_epoch, transform) in main_world.query::<(Entities, &ModelComponent, EpochOf, &TransformComponent)>().iter() { - let model = model.data.as_ref().unwrap().as_ref(); - let model_mesh = model.meshes.first().unwrap(); - - // Create the render job and push it to the queue - let job = RenderJob::new(model_mesh.clone(), entity, transform.transform, None); - self.render_jobs.push_back(job); - alive_entities.insert(entity); - if self.buffer_storage.get(&entity).is_none() { - // check if the transform buffers need to be expanded - if self.transform_buffers.should_expand() { - self.expand_transform_buffers(); + let model = model.data.as_ref().unwrap().as_ref(); + + for mesh in model.meshes.iter() { + if !self.process_mesh(entity, transform.transform, mesh) { + if model_epoch == last_epoch { + self.update_mesh_buffers(entity, mesh); + } } - // insert transform into buffers - let indices = self.transform_buffers.insert_entity(&self.queue, &self.render_limits, - entity, transform.transform.calculate_mat4()); - - // create the mesh's buffers - let buffers = self.create_mesh_buffers(model_mesh, indices); - self.buffer_storage.insert(entity, buffers); - } else { - // update entity transforms - self.transform_buffers.update_entity(&self.queue, &self.render_limits, - entity, transform.transform.calculate_mat4()); - - // if the model was updated, update its buffers - if model_epoch == last_epoch { - self.update_mesh_buffers(entity, model_mesh); - } + let shader = mesh.material().shader_uuid.unwrap_or(0); + let job = RenderJob::new(entity, shader, mesh.uuid, transform.transform, None); + self.render_jobs.push_back(job); } } @@ -617,8 +639,12 @@ impl Renderer for BasicRenderer { // when buffer storage length does not match the amount of iterated entities, // remove all dead entities, and their buffers, if they weren't iterated over - if self.buffer_storage.len() != alive_entities.len() { - self.buffer_storage.retain(|e, _| alive_entities.contains(e)); + if self.mesh_buffers.len() != alive_entities.len() { + let removed_entities: Vec = self.entity_meshes + .extract_if(|e, _| !alive_entities.contains(e)) + .map(|(_, v)| v) + .collect(); + self.mesh_buffers.retain(|u, _| !removed_entities.contains(u)); } if let Some(camera) = main_world.query_mut::<(&mut CameraComponent,)>().into_iter().next() { @@ -663,13 +689,12 @@ impl Renderer for BasicRenderer { // Pop off jobs from the queue as they're being processed while let Some(job) = self.render_jobs.pop_front() { - if let Some(pipeline) = self.render_pipelines.get(&job.mesh().material().shader_uuid.unwrap_or(0)) { + if let Some(pipeline) = self.render_pipelines.get(&job.shader_id) { // specify to use this pipeline render_pass.set_pipeline(pipeline.get_wgpu_pipeline()); // get the mesh (containing vertices) and the buffers from storage - let mesh = job.mesh(); - let buffers = self.buffer_storage.get(&job.entity()).unwrap(); + let buffers = self.mesh_buffers.get(&job.mesh_buffer_id).unwrap(); // Bind the optional texture if let Some(tex) = buffers.texture_bindgroup.as_ref() { @@ -695,13 +720,11 @@ impl Renderer for BasicRenderer { render_pass.set_index_buffer(indices.buffer().slice(..), idx_type.clone()); render_pass.draw_indexed(0..indices_len, 0, 0..1); } else { - let vertices = mesh.position().unwrap(); + let vertex_count = buffers.buffer_vertex.count(); render_pass.set_vertex_buffer(buffers.buffer_vertex.slot(), buffers.buffer_vertex.buffer().slice(..)); - render_pass.draw(0..vertices.len() as u32, 0..1); + render_pass.draw(0..vertex_count as u32, 0..1); } - } else { - warn!("Failure to find RenderPipeline with shader id of '{}'!", job.mesh().material().shader_uuid.unwrap_or(0)); } } }