tirbofish/dropbear · commit
51bd99fb844aa18e8e4fceb86572a8f2cfa714bb
asset server
Signature present but could not be verified.
Unverified
@@ -60,6 +60,7 @@ libloading = "0.8" indexmap = "2.11" sha2 = "0.10" wesl = "0.2" +dashmap = "6.1" [workspace.dependencies.image] version = "0.25" @@ -39,6 +39,7 @@ backtrace.workspace = true os_info.workspace = true rustc_version_runtime.workspace = true ron.workspace = true +dashmap.workspace = true [target.'cfg(not(target_os = "android"))'.dependencies] rfd.workspace = true @@ -0,0 +1,74 @@ +use std::sync::Arc; + +use dashmap::DashMap; + +use crate::model::{Material, MaterialComponent, Mesh, MeshComponent}; + +pub type Handle = u64; + +pub struct AssetCache { + materials: DashMap<MaterialComponent, Arc<Material>>, + meshes: DashMap<MeshComponent, Arc<Mesh>>, +} + +impl AssetCache { + pub fn new() -> Self { + Self { + materials: DashMap::new(), + meshes: DashMap::new(), + } + } + + /// Fetches the material based off the handle. + /// + /// If it doesn't exist, it will run the loader as a function and store the mesh from there. + pub fn get_or_load_material<F>(&self, handle: MaterialComponent, loader: F) -> anyhow::Result<Arc<Material>> + where + F: FnOnce() -> anyhow::Result<Material>, + { + if let Some(existing) = self.materials.get(&handle) { + return Ok(existing.clone()); + } + + let material = Arc::new(loader()?); + + match self.materials.entry(handle) { + dashmap::mapref::entry::Entry::Occupied(entry) => Ok(entry.get().clone()), + dashmap::mapref::entry::Entry::Vacant(entry) => { + entry.insert(material.clone()); + Ok(material) + } + } + } + + /// Fetches the mesh based off the handle. + /// + /// If it doesn't exist, it will run the loader as a function and store the mesh from there. + pub fn get_or_load_mesh<F>(&self, handle: MeshComponent, loader: F) -> anyhow::Result<Arc<Mesh>> + where + F: FnOnce() -> anyhow::Result<Mesh>, + { + log::debug!("Searching for mesh {:?}", handle); + if let Some(existing) = self.meshes.get(&handle) { + log::debug!("Found existing mesh"); + return Ok(existing.clone()); + } + + let mesh = Arc::new(loader()?); + log::debug!("Created new mesh"); + + match self.meshes.entry(handle) { + dashmap::mapref::entry::Entry::Occupied(entry) => Ok(entry.get().clone()), + dashmap::mapref::entry::Entry::Vacant(entry) => { + entry.insert(mesh.clone()); + Ok(mesh) + } + } + } + + pub fn clear_everything(&mut self) { + self.materials.clear(); + self.meshes.clear(); + log::debug!("Cleared everything in the asset cache"); + } +} @@ -1,15 +1,14 @@ -use futures::executor::block_on; use glam::{DMat4, DQuat, DVec3, Mat4}; use serde::{Deserialize, Serialize}; use std::{ - path::{Path, PathBuf}, + path::Path, sync::Arc, }; use wgpu::{Buffer, util::DeviceExt}; use crate::{ graphics::{Instance, SharedGraphicsContext}, - model::{LazyModel, LazyType, Model}, + model::Model, }; /// A type that represents a position, rotation and scale of an entity @@ -78,55 +77,6 @@ impl Transform { } } -/// Creates a new adopted entity in a lazy method. It fetches the data first (which can be done on a separate -/// thread). After, the [`LazyAdoptedEntity::poke()`] function can be called to convert the Lazy to a Real adopted entity. -#[derive(Default)] -pub struct LazyAdoptedEntity { - lazy_model: LazyModel, - #[allow(dead_code)] - label: String, -} - -impl LazyAdoptedEntity { - /// Create a LazyAdoptedEntity from a file path (can be run on background thread) - pub async fn from_file(path: &PathBuf, label: Option<&str>) -> anyhow::Result<Self> { - let buffer = std::fs::read(path)?; - Self::from_memory(buffer, label).await - } - - /// Create a LazyAdoptedEntity from memory buffer (can be run on background thread) - pub async fn from_memory( - buffer: impl AsRef<[u8]>, - label: Option<&str>, - ) -> anyhow::Result<Self> { - let lazy_model = Model::lazy_load(buffer, label).await?; - let label_str = label.unwrap_or("LazyAdoptedEntity").to_string(); - - Ok(Self { - lazy_model, - label: label_str, - }) - } - - /// Create a LazyAdoptedEntity from an existing LazyModel - pub fn from_lazy_model(lazy_model: LazyModel, label: Option<&str>) -> Self { - let label_str = label.unwrap_or("LazyAdoptedEntity").to_string(); - Self { - lazy_model, - label: label_str, - } - } -} - -impl LazyType for LazyAdoptedEntity { - type T = AdoptedEntity; - - fn poke(self, graphics: Arc<SharedGraphicsContext>) -> anyhow::Result<Self::T> { - let model = self.lazy_model.poke(graphics.clone())?; - Ok(block_on(AdoptedEntity::adopt(graphics, model))) - } -} - #[derive(Clone)] pub struct AdoptedEntity { pub model: Arc<Model>, @@ -145,11 +95,11 @@ impl AdoptedEntity { label: Option<&str>, ) -> anyhow::Result<Self> { let path = path.as_ref().to_path_buf(); - let model = Model::load(graphics.clone(), &path, label).await?; - Ok(Self::adopt(graphics, model).await) + let model_handle = Model::load(graphics.clone(), &path, label).await?; + Ok(Self::adopt(graphics, model_handle.get())) } - pub async fn adopt(graphics: Arc<SharedGraphicsContext>, model: Model) -> Self { + pub fn adopt(graphics: Arc<SharedGraphicsContext>, model: Arc<Model>) -> Self { let label = model.label.clone(); let instance = Instance::new(DVec3::ZERO, DQuat::IDENTITY, DVec3::ONE); let initial_matrix = DMat4::IDENTITY; // Default; update in new() if transform provided @@ -164,7 +114,7 @@ impl AdoptedEntity { }); Self { - model: Arc::new(model), + model, instance, instance_buffer: Some(instance_buffer), previous_matrix: initial_matrix, @@ -1,3 +1,4 @@ +pub mod asset; pub mod attenuation; pub mod buffer; pub mod camera; @@ -8,7 +8,6 @@ use wgpu::{ use crate::attenuation::{Attenuation, RANGE_50}; use crate::graphics::SharedGraphicsContext; -use crate::model::{LazyModel, LazyType}; use crate::shader::Shader; use crate::{ camera::Camera, @@ -200,104 +199,6 @@ impl LightComponent { } } -pub struct LazyLight { - light_component: LightComponent, - transform: Transform, - label: Option<String>, - cube_lazy_model: Option<LazyModel>, -} - -impl LazyType for LazyLight { - type T = Light; - - fn poke(self, graphics: Arc<SharedGraphicsContext>) -> anyhow::Result<Self::T> { - let label_str = self.label.clone().unwrap_or_else(|| "Light".to_string()); - - let forward = DVec3::new(0.0, 0.0, -1.0); - let direction = self.transform.rotation * forward; - - let uniform = LightUniform { - position: dvec3_to_uniform_array(self.transform.position), - direction: dvec3_direction_to_uniform_array( - direction, - self.light_component.outer_cutoff_angle, - ), - colour: dvec3_colour_to_uniform_array( - self.light_component.colour * self.light_component.intensity as f64, - self.light_component.light_type, - ), - constant: self.light_component.attenuation.constant, - linear: self.light_component.attenuation.linear, - quadratic: self.light_component.attenuation.quadratic, - cutoff: f32::cos(self.light_component.cutoff_angle.to_radians()), - }; - - let cube_model = Arc::new(if let Some(lazy_model) = self.cube_lazy_model { - lazy_model.poke(graphics.clone())? - } else { - anyhow::bail!( - "The light cube LazyModel has not been initialised yet. Use Light::new(/** params */).preload_cube_model() to preload it (which is required)" - ); - }); - - let buffer = graphics.create_uniform(uniform, self.label.as_deref()); - - let layout = graphics - .device - .create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { - entries: &[wgpu::BindGroupLayoutEntry { - binding: 0, - visibility: wgpu::ShaderStages::VERTEX | wgpu::ShaderStages::FRAGMENT, - ty: wgpu::BindingType::Buffer { - ty: wgpu::BufferBindingType::Uniform, - has_dynamic_offset: false, - min_binding_size: None, - }, - count: None, - }], - label: self.label.as_deref(), - }); - - let bind_group = graphics - .device - .create_bind_group(&wgpu::BindGroupDescriptor { - layout: &layout, - entries: &[wgpu::BindGroupEntry { - binding: 0, - resource: buffer.as_entire_binding(), - }], - label: self.label.as_deref(), - }); - - let instance = Instance::new( - self.transform.position, - self.transform.rotation, - DVec3::new(0.25, 0.25, 0.25), - ); - - let instance_buffer = - graphics - .device - .create_buffer_init(&wgpu::util::BufferInitDescriptor { - label: self.label.as_deref().or(Some("instance buffer")), - contents: bytemuck::cast_slice(&[instance.to_raw()]), - usage: wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST, - }); - - log::debug!("Created new light [{}]", label_str); - - Ok(Light { - uniform, - cube_model, - label: label_str, - buffer: Some(buffer), - layout: Some(layout), - bind_group: Some(bind_group), - instance_buffer: Some(instance_buffer), - }) - } -} - #[derive(Clone)] pub struct Light { pub uniform: LightUniform, @@ -310,28 +211,6 @@ pub struct Light { } impl Light { - pub async fn lazy_new( - light_component: LightComponent, - transform: Transform, - label: Option<&str>, - ) -> anyhow::Result<LazyLight> { - let mut result = LazyLight { - light_component, - transform, - label: label.map(|s| s.to_string()), - cube_lazy_model: None, - }; - if result.cube_lazy_model.is_none() { - let lazy_model = Model::lazy_load( - include_bytes!("../../resources/models/cube.glb").to_vec(), - result.label.as_deref(), - ) - .await?; - result.cube_lazy_model = Some(lazy_model); - } - Ok(result) - } - pub async fn new( graphics: Arc<SharedGraphicsContext>, light: LightComponent, @@ -356,15 +235,14 @@ impl Light { log::trace!("Created new light uniform"); - let cube_model = Arc::new( - Model::load_from_memory( - graphics.clone(), - include_bytes!("../../resources/models/cube.glb").to_vec(), - label, - ) - .await - .unwrap(), - ); + let cube_model = Model::load_from_memory( + graphics.clone(), + include_bytes!("../../resources/models/cube.glb").to_vec(), + label, + ) + .await + .expect("failed to load light cube model") + .get(); let label_str = label.unwrap_or("Light").to_string(); @@ -3,11 +3,6 @@ use crate::utils::ResourceReference; use image::GenericImageView; use lazy_static::lazy_static; use parking_lot::Mutex; -// use russimp_ng::{ -// Vector3D, -// material::{DataContent, TextureType}, -// scene::{PostProcess, Scene}, -// }; use rayon::prelude::*; use std::collections::HashMap; use std::hash::{DefaultHasher, Hash, Hasher}; @@ -19,12 +14,18 @@ use wgpu::{BufferAddress, VertexAttribute, VertexBufferLayout, util::DeviceExt}; pub const GREY_TEXTURE_BYTES: &[u8] = include_bytes!("../../resources/textures/grey.png"); lazy_static! { - pub static ref MODEL_CACHE: Mutex<HashMap<String, Model>> = Mutex::new(HashMap::new()); + pub static ref MODEL_CACHE: Mutex<HashMap<String, Arc<Model>>> = Mutex::new(HashMap::new()); } #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub struct ModelId(pub u64); +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub struct MaterialComponent(pub u64); + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub struct MeshComponent(pub u64); + #[derive(Clone)] pub struct Model { pub id: ModelId, @@ -35,13 +36,42 @@ pub struct Model { } #[derive(Clone)] +pub struct LoadedModel { + inner: Arc<Model>, +} + +impl LoadedModel { + pub fn new(inner: Arc<Model>) -> Self { + Self { inner } + } + + /// Returns the unique identifier of the underlying model asset. + pub fn id(&self) -> ModelId { + self.inner.id + } + + /// Provides shared access to the underlying model. + pub fn get(&self) -> Arc<Model> { + Arc::clone(&self.inner) + } +} + +impl std::ops::Deref for LoadedModel { + type Target = Model; + + fn deref(&self) -> &Self::Target { + &self.inner + } +} + +#[derive(Clone)] pub struct Material { pub name: String, pub diffuse_texture: Texture, pub bind_group: wgpu::BindGroup, } -#[derive(Clone, Hash)] +#[derive(Clone)] pub struct Mesh { pub name: String, pub vertex_buffer: wgpu::Buffer, @@ -50,311 +80,12 @@ pub struct Mesh { pub material: usize, } -#[derive(Default, Clone)] -pub struct ParsedModelData { - pub label: String, - pub path: ResourceReference, - pub mesh_data: Vec<ParsedMeshData>, - pub material_data: Vec<ParsedMaterialData>, -} - -#[derive(Default, Clone)] -pub struct ParsedMeshData { - pub name: String, - pub vertices: Vec<ModelVertex>, - pub indices: Vec<u32>, - pub material_index: usize, -} - -#[derive(Default, Clone)] -pub struct ParsedMaterialData { - pub name: String, - pub rgba_data: Vec<u8>, - pub dimensions: (u32, u32), -} - -/// A type that is used to lazily load an object/struct. -/// -/// It allows for separate threading loading. -pub trait LazyType { - /// The data type of the lazy item - /// - /// For example, a [`LazyModel`] would have a [`Model`] - type T; - - /// A function used to load the wgpu graphics items. - /// - /// This can be ran after the initial thread loading is completed. - /// # Parameters - /// * [`Arc<SharedGraphicsContext>`] - The graphics context. It can be shared between threads - /// if required. - /// - /// # Returns - /// * [`Self::T`] - The data type of the item - fn poke(self, graphics: Arc<SharedGraphicsContext>) -> anyhow::Result<Self::T>; -} - -/// Loads the model into memory but graphics functions are defined after the creation -/// of the model -#[derive(Default)] -pub struct LazyModel { - parsed_data: ParsedModelData, -} - -impl LazyType for LazyModel { - type T = Model; - - fn poke(self, graphics: Arc<SharedGraphicsContext>) -> anyhow::Result<Self::T> { - let start = Instant::now(); - - let mut hasher = DefaultHasher::new(); - - let cache_key = self.parsed_data.label.clone(); - if let Some(cached_model) = MODEL_CACHE.lock().get(&cache_key) { - log::debug!("Model loaded from cache during poke: {:?}", cache_key); - return Ok(cached_model.clone()); - } - - log::debug!( - "Creating GPU resources for model: {:?}", - self.parsed_data.label - ); - - let mut materials = Vec::new(); - for material_data in &self.parsed_data.material_data { - let texture_start = Instant::now(); - - let diffuse_texture = Texture::from_rgba_buffer( - graphics.clone(), - &material_data.rgba_data, - material_data.dimensions, - ); - - let bind_group = diffuse_texture.bind_group().to_owned(); - - materials.push(Material { - name: material_data.name.clone(), - diffuse_texture, - bind_group, - }); - - log::debug!( - "Created GPU texture for material '{}' in {:?}", - material_data.name, - texture_start.elapsed() - ); - } - - let mut meshes = Vec::new(); - for mesh_data in &self.parsed_data.mesh_data { - for v in &mesh_data.vertices { - let _ = v.position.iter().map(|v| (*v as i32).hash(&mut hasher)); - let _ = v.tex_coords.iter().map(|v| (*v as i32).hash(&mut hasher)); - let _ = v.normal.iter().map(|v| (*v as i32).hash(&mut hasher)); - } - mesh_data.indices.hash(&mut hasher); - - let buffer_start = Instant::now(); - - let vertex_buffer = - graphics - .clone() - .device - .create_buffer_init(&wgpu::util::BufferInitDescriptor { - label: Some(&format!("{} Vertex Buffer", mesh_data.name)), - contents: bytemuck::cast_slice(&mesh_data.vertices), - usage: wgpu::BufferUsages::VERTEX, - }); - - let index_buffer = - graphics - .clone() - .device - .create_buffer_init(&wgpu::util::BufferInitDescriptor { - label: Some(&format!("{} Index Buffer", mesh_data.name)), - contents: bytemuck::cast_slice(&mesh_data.indices), - usage: wgpu::BufferUsages::INDEX, - }); - - meshes.push(Mesh { - name: mesh_data.name.clone(), - vertex_buffer, - index_buffer, - num_elements: mesh_data.indices.len() as u32, - material: mesh_data.material_index, - }); - - log::debug!( - "Created GPU buffers for mesh '{}' in {:?}", - mesh_data.name, - buffer_start.elapsed() - ); - } - - let model = Model { - meshes, - materials, - label: self.parsed_data.label.clone(), - path: self.parsed_data.path.clone(), - id: ModelId(hasher.finish()), - }; - - MODEL_CACHE.lock().insert(cache_key, model.clone()); - log::debug!( - "Model GPU resource creation completed in {:?}", - start.elapsed() - ); - - Ok(model) - } -} - impl Model { - /// Creates a [`LazyModel`]. - pub async fn lazy_load( - buffer: impl AsRef<[u8]>, - label: Option<&str>, - ) -> anyhow::Result<LazyModel> { - let start = Instant::now(); - let label_str = label.unwrap_or("default"); - - log::debug!("Starting lazy load for model: {:?}", label_str); - - let res_ref = ResourceReference::from_bytes(buffer.as_ref()); - let (gltf, buffers, _images) = gltf::import_slice(buffer.as_ref())?; - - let mut texture_data = Vec::new(); - for material in gltf.materials() { - log::debug!("Processing material: {:?}", material.name()); - let material_name = material.name().unwrap_or("Unnamed Material").to_string(); - - let image_data = - if let Some(pbr) = material.pbr_metallic_roughness().base_color_texture() { - let texture_info = pbr.texture(); - let image = texture_info.source(); - match image.source() { - gltf::image::Source::View { view, mime_type: _ } => { - let buffer_data = &buffers[view.buffer().index()]; - let start = view.offset(); - let end = start + view.length(); - buffer_data[start..end].to_vec() - } - gltf::image::Source::Uri { uri, mime_type: _ } => { - log::warn!("External URI textures not supported: {}", uri); - GREY_TEXTURE_BYTES.to_vec() - } - } - } else { - GREY_TEXTURE_BYTES.to_vec() - }; - - texture_data.push((material_name, image_data)); - } - - if texture_data.is_empty() { - texture_data.push(("Default".to_string(), GREY_TEXTURE_BYTES.to_vec())); - } - - let parallel_start = Instant::now(); - let processed_materials: Vec<_> = texture_data - .into_par_iter() - .map(|(material_name, image_data)| { - let material_start = Instant::now(); - - let diffuse_image = - image::load_from_memory(&image_data).expect("Failed to load image from memory"); - let diffuse_rgba = diffuse_image.to_rgba8(); - let dimensions = diffuse_image.dimensions(); - - log::debug!( - "Processed material '{}' in {:?}", - material_name, - material_start.elapsed() - ); - - ParsedMaterialData { - name: material_name, - rgba_data: diffuse_rgba.into_raw(), - dimensions, - } - }) - .collect(); - - log::debug!( - "Parallel material processing took: {:?}", - parallel_start.elapsed() - ); - - let mut mesh_data = Vec::new(); - for mesh in gltf.meshes() { - log::debug!("Processing mesh: {:?}", mesh.name()); - for primitive in mesh.primitives() { - let reader = primitive.reader(|buffer| Some(&buffers[buffer.index()])); - - let positions: Vec<[f32; 3]> = reader - .read_positions() - .ok_or_else(|| anyhow::anyhow!("Mesh missing positions"))? - .collect(); - - let normals: Vec<[f32; 3]> = reader - .read_normals() - .map(|iter| iter.collect()) - .unwrap_or_else(|| vec![[0.0, 1.0, 0.0]; positions.len()]); - - let tex_coords: Vec<[f32; 2]> = reader - .read_tex_coords(0) - .map(|iter| iter.into_f32().collect()) - .unwrap_or_else(|| vec![[0.0, 0.0]; positions.len()]); - - let vertices: Vec<ModelVertex> = positions - .iter() - .zip(normals.iter()) - .zip(tex_coords.iter()) - .map(|((pos, norm), tex)| ModelVertex { - position: *pos, - normal: *norm, - tex_coords: *tex, - }) - .collect(); - - let indices: Vec<u32> = reader - .read_indices() - .ok_or_else(|| anyhow::anyhow!("Mesh missing indices"))? - .into_u32() - .collect(); - - let material_index = primitive.material().index().unwrap_or(0); - - mesh_data.push(ParsedMeshData { - name: mesh.name().unwrap_or("Unnamed Mesh").to_string(), - vertices, - indices, - material_index, - }); - } - } - - let parsed_data = ParsedModelData { - label: label_str.to_string(), - path: res_ref, - mesh_data, - material_data: processed_materials, - }; - - log::debug!( - "Lazy load completed for model: {:?} in {:?}", - label_str, - start.elapsed() - ); - - Ok(LazyModel { parsed_data }) - } - pub async fn load_from_memory( graphics: Arc<SharedGraphicsContext>, buffer: impl AsRef<[u8]>, label: Option<&str>, - ) -> anyhow::Result<Model> { + ) -> anyhow::Result<LoadedModel> { let start = Instant::now(); let mut hasher = DefaultHasher::new(); @@ -362,7 +93,7 @@ impl Model { if let Some(cached_model) = MODEL_CACHE.lock().get(&cache_key) { log::debug!("Model loaded from memory cache: {:?}", cache_key); - return Ok(cached_model.clone()); + return Ok(LoadedModel::new(cached_model.clone())); } log::trace!( @@ -532,27 +263,29 @@ impl Model { } log::debug!("Successfully loaded model [{:?}]", label); - let model = Model { + let model = Arc::new(Model { meshes, materials, label: label.unwrap_or("No named model").to_string(), path: res_ref, id: ModelId(hasher.finish()), - }; + }); - MODEL_CACHE.lock().insert(cache_key, model.clone()); + MODEL_CACHE + .lock() + .insert(cache_key.clone(), Arc::clone(&model)); log::trace!("==================== DONE ===================="); log::debug!("Model cached from memory: {:?}", label); log::debug!("Took {:?} to load model: {:?}", start.elapsed(), label); log::trace!("=============================================="); - Ok(model) + Ok(LoadedModel::new(model)) } pub async fn load( graphics: Arc<SharedGraphicsContext>, path: &PathBuf, label: Option<&str>, - ) -> anyhow::Result<Model> { + ) -> anyhow::Result<LoadedModel> { let file_name = path.file_name(); log::debug!("Loading model [{:?}]", file_name); @@ -561,19 +294,34 @@ impl Model { log::debug!("Checking if model exists in cache"); if let Some(cached_model) = MODEL_CACHE.lock().get(&path_str) { log::debug!("Model loaded from cache: {:?}", path_str); - return Ok(cached_model.clone()); + return Ok(LoadedModel::new(cached_model.clone())); } log::debug!("Model does not exist in cache, loading memory..."); log::debug!("Path of model: {}", path.display()); let buffer = std::fs::read(path)?; - let mut model = Self::load_from_memory(graphics, buffer, label).await?; - model.path = ResourceReference::from_path(path)?; + let loaded = Self::load_from_memory(graphics, buffer, label).await?; + + let mut model_clone: Model = (*loaded).clone(); + if let Ok(reference) = ResourceReference::from_path(path) { + model_clone.path = reference; + } + if let Some(custom_label) = label { + model_clone.label = custom_label.to_string(); + } + + let updated = Arc::new(model_clone); + { + let mut cache = MODEL_CACHE.lock(); + cache.insert(path_str, Arc::clone(&updated)); + if let Some(custom_label) = label { + cache.insert(custom_label.to_string(), Arc::clone(&updated)); + } + } - MODEL_CACHE.lock().insert(path_str, model.clone()); log::debug!("Model cached and loaded: {:?}", file_name); - Ok(model) + Ok(LoadedModel::new(updated)) } } @@ -1,9 +1,7 @@ use crate::entity::AdoptedEntity; use crate::graphics::{SharedGraphicsContext, Texture}; -use crate::model::{LazyType, MODEL_CACHE, Material, Mesh, Model, ModelId, ModelVertex}; +use crate::model::{MODEL_CACHE, Material, Mesh, Model, ModelId, ModelVertex}; use crate::utils::{ResourceReference, ResourceReferenceType}; -use futures::executor::block_on; -use image::GenericImageView; use std::hash::{DefaultHasher, Hash, Hasher}; /// A straight plane (and some components). Thats it. /// @@ -12,104 +10,6 @@ use std::hash::{DefaultHasher, Hash, Hasher}; use std::sync::Arc; use wgpu::{AddressMode, util::DeviceExt}; -/// Lazily creates a new Plane. This can only be accessed through the Default trait (which you shouldn't use), -/// or the [`PlaneBuilder::lazy_build()`] (also taken from [`PlaneBuilder::new()`]). -#[derive(Default)] -pub struct LazyPlaneBuilder { - rgba_data: Vec<u8>, - dimensions: (u32, u32), - width: f32, - height: f32, - tiles_x: u32, - tiles_z: u32, - label: Option<String>, -} - -impl LazyType for LazyPlaneBuilder { - type T = AdoptedEntity; - - fn poke(self, graphics: Arc<SharedGraphicsContext>) -> anyhow::Result<Self::T> { - let mut hasher = DefaultHasher::new(); - - let mut vertices = Vec::new(); - let mut indices = Vec::new(); - - for z in 0..=1 { - for x in 0..=1 { - let position = [ - (x as f32 - 0.5) * self.width, - 0.0, - (z as f32 - 0.5) * self.height, - ]; - let normal = [0.0, 1.0, 0.0]; - let tex_coords = [ - x as f32 * self.tiles_x as f32, - z as f32 * self.tiles_z as f32, - ]; - - let _ = position.iter().map(|v| (*v as i32).hash(&mut hasher)); - let _ = normal.iter().map(|v| (*v as i32).hash(&mut hasher)); - let _ = tex_coords.iter().map(|v| (*v as i32).hash(&mut hasher)); - - vertices.push(ModelVertex { - position, - tex_coords, - normal, - }); - } - } - - indices.extend_from_slice(&[0, 2, 1, 1, 2, 3]); - indices.hash(&mut hasher); - - let vertex_buffer = graphics - .device - .create_buffer_init(&wgpu::util::BufferInitDescriptor { - label: Some(&format!("{:?} Vertex Buffer", self.label.as_deref())), - contents: bytemuck::cast_slice(&vertices), - usage: wgpu::BufferUsages::VERTEX, - }); - let index_buffer = graphics - .device - .create_buffer_init(&wgpu::util::BufferInitDescriptor { - label: Some(&format!("{:?} Index Buffer", self.label.as_deref())), - contents: bytemuck::cast_slice(&indices), - usage: wgpu::BufferUsages::INDEX, - }); - - let mesh = Mesh { - name: "plane".to_string(), - vertex_buffer, - index_buffer, - num_elements: indices.len() as u32, - material: 0, - }; - - let diffuse_texture = Texture::new_with_sampler_with_rgba_buffer( - graphics.clone(), - &self.rgba_data, - self.dimensions, - AddressMode::Repeat, - ); - let bind_group = diffuse_texture.bind_group().clone(); - let material = Material { - name: "plane_material".to_string(), - diffuse_texture, - bind_group, - }; - - let model = Model { - label: self.label.as_deref().unwrap_or("Plane").to_string(), - path: ResourceReference::from_reference(ResourceReferenceType::Plane), - meshes: vec![mesh], - materials: vec![material], - id: ModelId(hasher.finish()), - }; - - Ok(block_on(AdoptedEntity::adopt(graphics, model))) - } -} - /// Creates a plane in the form of an AdoptedEntity. pub struct PlaneBuilder { width: f32, @@ -146,31 +46,6 @@ impl PlaneBuilder { self } - pub async fn lazy_build( - mut self, - texture_bytes: &[u8], - label: Option<&str>, - ) -> anyhow::Result<LazyPlaneBuilder> { - if self.tiles_x == 0 && self.tiles_z == 0 { - self.tiles_x = self.width as u32; - self.tiles_z = self.height as u32; - } - - let img = image::load_from_memory(texture_bytes)?; - let rgba = img.to_rgba8(); - let dimensions = img.dimensions(); - - Ok(LazyPlaneBuilder { - rgba_data: rgba.into_raw(), - dimensions, - width: self.width, - height: self.height, - tiles_x: self.tiles_x, - tiles_z: self.tiles_z, - label: label.map(|s| s.to_string()), - }) - } - pub async fn build( mut self, graphics: Arc<SharedGraphicsContext>, @@ -222,12 +97,10 @@ impl PlaneBuilder { let hash = hasher.finish(); - let model = if let Some(cached_model) = MODEL_CACHE.lock().get(&label.clone()) { - log::debug!("Model loaded from cache: {:?}", label.clone()); - Some(cached_model.clone()) - } else { - None - }; + if let Some(cached_model) = MODEL_CACHE.lock().get(&label) { + log::debug!("Model loaded from cache: {:?}", label); + return Ok(AdoptedEntity::adopt(graphics, Arc::clone(cached_model))); + } let vertex_buffer = graphics .device @@ -261,20 +134,18 @@ impl PlaneBuilder { bind_group, }; - let model = if let Some(m) = model { - m - } else { - let m = Model { - label: label.to_string(), - path: ResourceReference::from_reference(ResourceReferenceType::Plane), - meshes: vec![mesh], - materials: vec![material], - id: ModelId(hash), - }; - MODEL_CACHE.lock().insert(label, m.clone()); - m - }; + let model = Arc::new(Model { + label: label.clone(), + path: ResourceReference::from_reference(ResourceReferenceType::Plane), + meshes: vec![mesh], + materials: vec![material], + id: ModelId(hash), + }); + + MODEL_CACHE + .lock() + .insert(label, Arc::clone(&model)); - Ok(AdoptedEntity::adopt(graphics, model).await) + Ok(AdoptedEntity::adopt(graphics, model)) } } @@ -1088,7 +1088,7 @@ impl SceneConfig { Some(&entity_config.label), ) .await?; - let adopted = AdoptedEntity::adopt(graphics.clone(), model).await; + let adopted = AdoptedEntity::adopt(graphics.clone(), model.get()); let transform = entity_config.transform; @@ -55,7 +55,7 @@ impl PendingSpawnController for Editor { Some(&asset_name), ) .await?; - Ok(AdoptedEntity::adopt(graphics_clone, model).await) + Ok(AdoptedEntity::adopt(graphics_clone, model.get())) } ResourceReferenceType::Plane => { let get_float = |key: &str| -> anyhow::Result<f32> { @@ -1,3 +1 @@ -fn main() { - -} +fn main() {} @@ -1,4 +1,2 @@ #[tokio::main] -async fn main() { - -} +async fn main() {}