tirbofish/dropbear · diff
100 utah teapots with 30 fps, not good. todo: optimise code. jarvis, run github actions
Signature present but could not be verified.
Unverified
@@ -31,6 +31,7 @@ gilrs = "0.11" git2 = "0.20" glam = { version = "0.30", features = ["serde"] } hecs = { version = "0.10", features = ["serde"] } +lazy_static = "1.5" log = "0.4" log-once = "0.4" model_to_image = "0.1.1" @@ -33,6 +33,7 @@ winit.workspace = true hecs.workspace = true once_cell.workspace = true parking_lot.workspace = true +lazy_static.workspace = true [target.'cfg(not(target_os = "android"))'.dependencies] rfd.workspace = true @@ -1,15 +1,11 @@ -use std::collections::HashMap; use std::path::PathBuf; use glam::{DMat4, DQuat, DVec3, Mat4}; -use once_cell::sync::Lazy; -use parking_lot::RwLock; use serde::{Deserialize, Serialize}; use wgpu::{util::DeviceExt, BindGroup, Buffer}; use crate::{ graphics::{Graphics, Instance}, model::Model }; -use crate::model::Mesh; #[derive(Debug, Clone, Deserialize, Serialize, Copy, PartialEq)] pub struct Transform { @@ -61,10 +57,7 @@ impl Transform { #[derive(Default)] pub struct AdoptedEntity { pub model: Option<Model>, - pub uniform: ModelUniform, - pub uniform_buffer: Option<Buffer>, - pub uniform_bind_group: Option<BindGroup>, - #[allow(unused)] + pub previous_matrix: DMat4, pub instance: Instance, pub instance_buffer: Option<Buffer>, } @@ -88,67 +81,35 @@ impl AdoptedEntity { } pub fn adopt(graphics: &Graphics, model: Model, label: Option<&str>) -> Self { - let uniform = ModelUniform::new(); - let uniform_buffer = graphics.create_uniform(uniform, Some("Entity Model Uniform")); - - let model_layout = graphics.create_model_uniform_bind_group_layout(); - let uniform_bind_group = - graphics - .state - .device - .create_bind_group(&wgpu::BindGroupDescriptor { - label: Some("model_uniform_bind_group"), - layout: &model_layout, - entries: &[wgpu::BindGroupEntry { - binding: 0, - resource: uniform_buffer.as_entire_binding(), - }], - }); - - let instance = Instance::new(DVec3::ONE, DQuat::IDENTITY, DVec3::ONE); - - let instance_buffer = - graphics - .state - .device - .create_buffer_init(&wgpu::util::BufferInitDescriptor { - label: match label { - Some(_) => label, - None => Some("instance buffer"), - }, - contents: bytemuck::cast_slice(&[instance.to_raw()]), - usage: wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST, - }); - log::debug!("Successfully adopted Model"); + let instance = Instance::new(DVec3::ZERO, DQuat::IDENTITY, DVec3::ONE); + let initial_matrix = DMat4::IDENTITY; // Default; update in new() if transform provided + let instance_raw = instance.to_raw(); + let instance_buffer = graphics.state.device.create_buffer_init(&wgpu::util::BufferInitDescriptor { + label: match label { + Some(l) => Some(l), + None => Some("instance buffer"), + }, + contents: bytemuck::cast_slice(&[instance_raw]), + usage: wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST, + }); + Self { model: Some(model), - uniform, - uniform_buffer: Some(uniform_buffer), - uniform_bind_group: Some(uniform_bind_group), instance, instance_buffer: Some(instance_buffer), + previous_matrix: initial_matrix, } } pub fn update(&mut self, graphics: &Graphics, transform: &Transform) { - self.uniform.model = transform.matrix().as_mat4().to_cols_array_2d(); - - if let Some(buffer) = &self.uniform_buffer { - graphics - .state - .queue - .write_buffer(buffer, 0, bytemuck::cast_slice(&[self.uniform])); - } - - self.instance = Instance::from_matrix(transform.matrix()); - - if let Some(instance_buffer) = &self.instance_buffer { + let current_matrix = transform.matrix(); + if self.previous_matrix != current_matrix { + self.instance = Instance::from_matrix(current_matrix); let instance_raw = self.instance.to_raw(); - graphics.state.queue.write_buffer( - instance_buffer, - 0, - bytemuck::cast_slice(&[instance_raw]), - ); + if let Some(buffer) = &self.instance_buffer { + graphics.state.queue.write_buffer(buffer, 0, bytemuck::cast_slice(&[instance_raw])); + } + self.previous_matrix = current_matrix; } } @@ -1,53 +1,21 @@ use std::{mem, ops::Range, path::PathBuf}; - +use std::collections::HashMap; use russimp_ng::{ Vector3D, material::{DataContent, TextureType}, scene::{PostProcess, Scene}, }; use wgpu::{BufferAddress, VertexAttribute, VertexBufferLayout, util::DeviceExt}; - +use lazy_static::lazy_static; +use parking_lot::Mutex; use crate::graphics::{Graphics, NO_MODEL, Texture}; use crate::utils::ResourceReference; pub const GREY_TEXTURE_BYTES: &'static [u8] = include_bytes!("../../resources/grey.png"); -pub trait Vertex { - fn desc() -> VertexBufferLayout<'static>; -} - -#[repr(C)] -#[derive(Copy, Clone, Debug, bytemuck::Pod, bytemuck::Zeroable)] -pub struct ModelVertex { - pub position: [f32; 3], - pub tex_coords: [f32; 2], - pub normal: [f32; 3], -} - -impl Vertex for ModelVertex { - fn desc() -> wgpu::VertexBufferLayout<'static> { - VertexBufferLayout { - array_stride: mem::size_of::<ModelVertex>() as BufferAddress, - step_mode: wgpu::VertexStepMode::Vertex, - attributes: &[ - VertexAttribute { - format: wgpu::VertexFormat::Float32x3, - offset: 0, - shader_location: 0, - }, - wgpu::VertexAttribute { - offset: mem::size_of::<[f32; 3]>() as wgpu::BufferAddress, - shader_location: 1, - format: wgpu::VertexFormat::Float32x2, - }, - wgpu::VertexAttribute { - offset: mem::size_of::<[f32; 5]>() as wgpu::BufferAddress, - shader_location: 2, - format: wgpu::VertexFormat::Float32x3, - }, - ], - } - } +lazy_static! { + static ref MODEL_CACHE: Mutex<HashMap<String, Model>> = Mutex::new(HashMap::new()); + static ref MEMORY_MODEL_CACHE: Mutex<HashMap<String, Model>> = Mutex::new(HashMap::new()); } #[derive(Clone)] @@ -80,6 +48,14 @@ impl Model { buffer: Vec<u8>, label: Option<&str>, ) -> anyhow::Result<Model> { + let cache_key = label.unwrap_or("default").to_string(); + + // Check memory cache first + if let Some(cached_model) = MEMORY_MODEL_CACHE.lock().get(&cache_key) { + log::debug!("Model loaded from memory cache: {:?}", cache_key); + return Ok(cached_model.clone()); + } + log::debug!("Loading from memory"); let res_ref = ResourceReference::from_bytes(buffer.clone()); @@ -93,15 +69,18 @@ impl Model { "obj", ) { Ok(v) => v, - Err(_) => Scene::from_buffer( - NO_MODEL, - vec![ - PostProcess::Triangulate, - PostProcess::FlipUVs, - PostProcess::GenerateNormals, - ], - "glb", - )?, + Err(_) => { + log::error!("Unable to load {:?} from memory", label); + Scene::from_buffer( + NO_MODEL, + vec![ + PostProcess::Triangulate, + PostProcess::FlipUVs, + PostProcess::GenerateNormals, + ], + "glb", + )? + }, }; let mut materials = Vec::new(); @@ -204,7 +183,7 @@ impl Model { }); } log::debug!("Successfully loaded model [{:?}]", label); - Ok(Model { + let model = Model { meshes, materials, label: if let Some(l) = label { @@ -213,7 +192,11 @@ impl Model { String::from("No named model") }, path: res_ref, - }) + }; + + MEMORY_MODEL_CACHE.lock().insert(cache_key, model.clone()); + log::debug!("Model cached from memory: {:?}", label); + Ok(model) } pub fn load( @@ -224,6 +207,13 @@ impl Model { let file_name = path.file_name(); log::debug!("Loading model [{:?}]", file_name); + let path_str = path.to_string_lossy().to_string(); + + if let Some(cached_model) = MODEL_CACHE.lock().get(&path_str) { + log::debug!("Model loaded from cache: {:?}", path_str); + return Ok(cached_model.clone()); + } + let scene = match Scene::from_file( path.to_str().unwrap(), vec![ @@ -233,15 +223,18 @@ impl Model { ], ) { Ok(v) => v, - Err(_) => Scene::from_buffer( - NO_MODEL, - vec![ - PostProcess::Triangulate, - PostProcess::FlipUVs, - PostProcess::GenerateNormals, - ], - "glb", - )?, + Err(_) => { + log::error!("Unable to locate model [{}]", path.display()); + Scene::from_buffer( + NO_MODEL, + vec![ + PostProcess::Triangulate, + PostProcess::FlipUVs, + PostProcess::GenerateNormals, + ], + "glb", + )? + }, }; let mut materials = Vec::new(); @@ -344,7 +337,7 @@ impl Model { }); } log::debug!("Successfully loaded model [{:?}]", file_name); - Ok(Model { + let model = Model { meshes, materials, label: if let Some(l) = label { @@ -353,7 +346,11 @@ impl Model { String::from(file_name.unwrap().to_str().unwrap().split(".").into_iter().next().unwrap()) }, path: ResourceReference::from_path(path).unwrap(), - }) + }; + + MODEL_CACHE.lock().insert(path_str, model.clone()); + log::debug!("Model cached and loaded: {:?}", file_name); + Ok(model) } } @@ -504,3 +501,41 @@ where } } } + +pub trait Vertex { + fn desc() -> VertexBufferLayout<'static>; +} + +#[repr(C)] +#[derive(Copy, Clone, Debug, bytemuck::Pod, bytemuck::Zeroable)] +pub struct ModelVertex { + pub position: [f32; 3], + pub tex_coords: [f32; 2], + pub normal: [f32; 3], +} + +impl Vertex for ModelVertex { + fn desc() -> wgpu::VertexBufferLayout<'static> { + VertexBufferLayout { + array_stride: mem::size_of::<ModelVertex>() as BufferAddress, + step_mode: wgpu::VertexStepMode::Vertex, + attributes: &[ + VertexAttribute { + format: wgpu::VertexFormat::Float32x3, + offset: 0, + shader_location: 0, + }, + wgpu::VertexAttribute { + offset: mem::size_of::<[f32; 3]>() as wgpu::BufferAddress, + shader_location: 1, + format: wgpu::VertexFormat::Float32x2, + }, + wgpu::VertexAttribute { + offset: mem::size_of::<[f32; 5]>() as wgpu::BufferAddress, + shader_location: 2, + format: wgpu::VertexFormat::Float32x3, + }, + ], + } + } +} @@ -103,10 +103,8 @@ impl ResourceReference { /// Otherwise, it uses project_path + resource_path directly. pub fn to_project_path(&self, project_path: impl AsRef<Path>) -> PathBuf { let path = project_path.as_ref(); - match path.parent() { - Some(parent) => parent.join("resources").join(self.resource_ref_path.as_str()), - None => path.join("resources").join(self.resource_ref_path.as_str()), - } + log::debug!("Parent path: {}", path.display()); + path.join("resources").join(self.resource_ref_path.as_str()) } /// Creates a PathBuf that points to the resource relative to the executable directory. @@ -41,6 +41,7 @@ winit = { workspace = true, optional = true } clap = { workspace = true, optional = true } walkdir = { workspace = true, optional = true } zip = { workspace = true, optional = true } +bytemuck = "1.23.2" [target.'cfg(not(target_os = "android"))'.dependencies] rfd = { workspace = true, optional = true } @@ -93,7 +94,7 @@ data-only = [ "winit", "hecs", "ron", - "git2", +# "git2", "log-once", "once_cell", "egui", @@ -5,7 +5,10 @@ use dropbear_engine::{ use log; use parking_lot::Mutex; use wgpu::Color; +use wgpu::util::DeviceExt; use winit::{event_loop::ActiveEventLoop, keyboard::KeyCode}; +use dropbear_engine::graphics::InstanceRaw; +use dropbear_engine::model::Model; use super::*; use crate::{ utils::PendingSpawn, @@ -598,10 +601,11 @@ impl Scene for Editor { let mut entity_query = self.world.query::<(&AdoptedEntity, &Transform)>(); { let mut render_pass = graphics.clear_colour(color); + if let Some(light_pipeline) = &self.light_manager.pipeline { render_pass.set_pipeline(light_pipeline); for (_, (light, component)) in light_query.iter() { - if component.visible { + if component.enabled { render_pass.set_vertex_buffer(1, light.instance_buffer.as_ref().unwrap().slice(..)); render_pass.draw_light_model( light.model(), @@ -612,12 +616,33 @@ impl Scene for Editor { } } - render_pass.set_pipeline(pipeline); + let mut model_batches: HashMap<*const Model, Vec<InstanceRaw>> = HashMap::new(); for (_, (entity, _)) in entity_query.iter() { - render_pass.set_vertex_buffer(1, entity.instance_buffer.as_ref().unwrap().slice(..)); - // render_pass.set_bind_group(2, entity.uniform_bind_group.as_ref().unwrap(), &[]); - render_pass.draw_model(entity.model(), camera.bind_group(), self.light_manager.bind_group()); + let model_ptr = entity.model() as *const Model; + let instance_raw = entity.instance.to_raw(); // Use instance directly + model_batches.entry(model_ptr).or_insert(Vec::new()).push(instance_raw); + } + + render_pass.set_pipeline(pipeline); + + for (model_ptr, instances) in model_batches { + let model = unsafe { &*model_ptr }; + + // Create a temporary instance buffer for this batch + let instance_buffer = graphics.state.device.create_buffer_init(&wgpu::util::BufferInitDescriptor { + label: Some("Batched Instance Buffer"), + contents: bytemuck::cast_slice(&instances), + usage: wgpu::BufferUsages::VERTEX, + }); + + render_pass.set_vertex_buffer(1, instance_buffer.slice(..)); + render_pass.draw_model_instanced( + model, + 0..instances.len() as u32, + camera.bind_group(), + self.light_manager.bind_group(), + ); } } } @@ -733,13 +733,15 @@ impl SceneConfig { ); world.clear(); - let project_config = if cfg!(feature = "data-only") { + let project_config = if !cfg!(feature = "data-only") { if let Ok(cfg) = PROJECT.read() { cfg.project_path.clone() } else { + log::warn!("Unable to retrieve a lock from the PROJECT config"); PathBuf::new() } } else { + log::warn!("Feature is data only, no need for project config"); PathBuf::new() }; @@ -748,9 +750,14 @@ impl SceneConfig { for entity_config in &self.entities { log::debug!("Loading entity: {}", entity_config.label); let model_path = if !cfg!(feature = "data-only") { - entity_config.model_path.to_project_path(project_config.clone()) + log::debug!("Project Config location: {:?}", project_config.display()); + let path = entity_config.model_path.to_project_path(project_config.clone()); + log::debug!("Loading from project: {}", path.display()); + path } else { - entity_config.model_path.to_executable_path()? + let path = entity_config.model_path.to_executable_path()?; + log::debug!("Loading from executable ref: {}", path.display()); + path }; let adopted = AdoptedEntity::new(