kitgit

tirbofish/dropbear · diff

c12f63a · tk

100 utah teapots with 30 fps, not good. todo: optimise code. jarvis, run github actions

Unverified

diff --git a/Cargo.toml b/Cargo.toml
index 7b17222..cde9eb4 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -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"
diff --git a/dropbear-engine/Cargo.toml b/dropbear-engine/Cargo.toml
index 7ffd453..05012b1 100644
--- a/dropbear-engine/Cargo.toml
+++ b/dropbear-engine/Cargo.toml
@@ -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
diff --git a/dropbear-engine/src/entity.rs b/dropbear-engine/src/entity.rs
index b0c481b..668c61c 100644
--- a/dropbear-engine/src/entity.rs
+++ b/dropbear-engine/src/entity.rs
@@ -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;
         }
     }
 
diff --git a/dropbear-engine/src/model.rs b/dropbear-engine/src/model.rs
index 7fb9567..2f6bf06 100644
--- a/dropbear-engine/src/model.rs
+++ b/dropbear-engine/src/model.rs
@@ -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,
+                },
+            ],
+        }
+    }
+}
diff --git a/dropbear-engine/src/utils.rs b/dropbear-engine/src/utils.rs
index 02157d4..9962c99 100644
--- a/dropbear-engine/src/utils.rs
+++ b/dropbear-engine/src/utils.rs
@@ -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.
diff --git a/eucalyptus/Cargo.toml b/eucalyptus/Cargo.toml
index a6b1cac..4f7aa15 100644
--- a/eucalyptus/Cargo.toml
+++ b/eucalyptus/Cargo.toml
@@ -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",
diff --git a/eucalyptus/src/editor/scene.rs b/eucalyptus/src/editor/scene.rs
index fc99ecf..b81e7fc 100644
--- a/eucalyptus/src/editor/scene.rs
+++ b/eucalyptus/src/editor/scene.rs
@@ -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(),
+                        );
                     }
                 }
             }
diff --git a/eucalyptus/src/states.rs b/eucalyptus/src/states.rs
index 6afb118..cd0d633 100644
--- a/eucalyptus/src/states.rs
+++ b/eucalyptus/src/states.rs
@@ -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(