tirbofish/dropbear · diff
refactor: created new TextureBuilder struct to clean up the mess that was before.
refactor: made AssetViewer use Arc<T> instead of references.
fix: on colour change, it constantly recreates the texture.
Signature present but could not be verified.
Unverified
@@ -11,6 +11,7 @@ readme.workspace = true [dependencies] dropbear_future-queue = { path = "../dropbear_future-queue" } dropbear-macro = { path = "../dropbear-macro" } +dropbear-utils = { path = "../dropbear-utils" } slank = { path = "../slank", features = ["download-slang", "use-wgpu"] } anyhow.workspace = true @@ -66,10 +66,10 @@ impl<T> Handle<T> { } pub struct AssetRegistry { - textures: HashMap<u64, Texture>, + textures: HashMap<u64, Arc<Texture>>, texture_labels: HashMap<String, Handle<Texture>>, - models: HashMap<u64, Model>, + models: HashMap<u64, Arc<Model>>, model_labels: HashMap<String, Handle<Model>>, } @@ -113,13 +113,6 @@ impl AssetRegistry { hasher.finish() } - /// A convenient helper function for hashing a byte slice of data. - pub(crate) fn hash_contents<T: Hash>(data: T) -> u64 { - let mut hasher = DefaultHasher::new(); - data.hash(&mut hasher); - hasher.finish() - } - /// Checks if the asset registry contains a handle with the given hash. /// /// It will check all different types, so it does not point out specifically where. @@ -146,7 +139,7 @@ impl AssetRegistry { .hash .map(|v| Handle::new(v)) .unwrap_or_else(|| Handle::NULL); - self.textures.entry(handle.id).or_insert(texture); + self.textures.entry(handle.id).or_insert(Arc::new(texture)); handle } @@ -162,6 +155,14 @@ impl AssetRegistry { handle } + pub fn update_texture( + &mut self, + handle: Handle<Texture>, + new_texture: Texture, + ) -> Option<Arc<Texture>> { + self.textures.insert(handle.id, Arc::new(new_texture)) + } + /// Maps a label to an existing texture handle. pub fn label_texture(&mut self, label: impl Into<String>, handle: Handle<Texture>) { self.texture_labels.insert(label.into(), handle.clone()); @@ -174,20 +175,15 @@ impl AssetRegistry { self.texture_labels.remove(label); } - /// Updates the asset server by inserting the texture provided at the location of the handle, - /// and removing the old texture (by returning it back to you). - pub fn update_texture(&mut self, handle: Handle<Texture>, texture: Texture) -> Option<Texture> { - self.textures.insert(handle.id, texture) + pub fn get_texture(&self, handle: Handle<Texture>) -> Option<Arc<Texture>> { + self.textures.get(&handle.id).cloned() } - pub fn get_texture(&self, handle: Handle<Texture>) -> Option<&Texture> { - self.textures.get(&handle.id) - } - - pub fn get_texture_by_label(&self, label: &str) -> Option<&Texture> { + pub fn get_texture_by_label(&self, label: &str) -> Option<Arc<Texture>> { self.texture_labels .get(label) .and_then(|handle| self.textures.get(&handle.id)) + .cloned() } pub fn get_texture_handle_from_label(&self, label: &str) -> Option<Handle<Texture>> { @@ -199,10 +195,10 @@ impl AssetRegistry { } pub fn grey_texture(&mut self, graphics: Arc<SharedGraphicsContext>) -> Handle<Texture> { - self.solid_texture_rgba8_with_format( + self.solid_texture_rgba8( graphics, [128, 128, 128, 255], - Texture::TEXTURE_FORMAT_BASE.add_srgb_suffix(), + Some(Texture::TEXTURE_FORMAT_BASE.add_srgb_suffix()), ) } @@ -210,27 +206,18 @@ impl AssetRegistry { &mut self, graphics: Arc<SharedGraphicsContext>, rgba: [u8; 4], + format: Option<wgpu::TextureFormat>, ) -> Handle<Texture> { - self.solid_texture_rgba8_with_format(graphics, rgba, Texture::TEXTURE_FORMAT) - } + let format = format.unwrap_or(Texture::TEXTURE_FORMAT); - pub fn solid_texture_rgba8_with_format( - &mut self, - graphics: Arc<SharedGraphicsContext>, - rgba: [u8; 4], - format: wgpu::TextureFormat, - ) -> Handle<Texture> { let format_tag = format!("{:?}", format); - let handle = Handle::new(Self::hash_contents((rgba, format_tag.as_str()))); - - if self.contains_hash(handle.id) { - return handle; - } - let label = format!( "Solid texture [{}, {}, {}, {}] {}", rgba[0], rgba[1], rgba[2], rgba[3], format_tag, ); + if let Some(handle) = self.get_texture_handle_from_label(label.as_str()) { + return handle; + } let texture = Texture::from_bytes_verbose_mipmapped_with_format( graphics, @@ -260,7 +247,7 @@ impl AssetRegistry { impl AssetRegistry { pub fn add_model(&mut self, model: Model) -> Handle<Model> { let handle = Handle::new(model.hash); - self.models.entry(handle.id).or_insert(model); + self.models.entry(handle.id).or_insert(Arc::new(model)); handle } @@ -278,7 +265,7 @@ impl AssetRegistry { self.model_labels.insert(label.into(), handle.clone()); } - pub fn update_model(&mut self, handle: Handle<Model>, model: Model) -> Option<Model> { + pub fn update_model(&mut self, handle: Handle<Model>, model: Model) -> Option<Arc<Model>> { if let Some(existing) = self.models.get(&handle.id) { if existing.label.eq_ignore_ascii_case("light cube") { log::warn!("Attempted to update protected model '{}'", existing.label); @@ -286,21 +273,18 @@ impl AssetRegistry { } } - self.models.insert(handle.id, model) - } - - pub fn get_model(&self, handle: Handle<Model>) -> Option<&Model> { - self.models.get(&handle.id) + self.models.insert(handle.id, Arc::new(model)) } - pub fn get_model_mut(&mut self, handle: Handle<Model>) -> Option<&mut Model> { - self.models.get_mut(&handle.id) + pub fn get_model(&self, handle: Handle<Model>) -> Option<Arc<Model>> { + self.models.get(&handle.id).cloned() } - pub fn get_model_by_label(&self, label: &str) -> Option<&Model> { + pub fn get_model_by_label(&self, label: &str) -> Option<Arc<Model>> { self.model_labels .get(label) .and_then(|handle| self.models.get(&handle.id)) + .cloned() } pub fn get_model_handle_from_label(&self, label: &str) -> Option<Handle<Model>> { @@ -117,7 +117,7 @@ impl BillboardPipeline { { let mut registry = ASSET_REGISTRY.write(); - let handle = registry.solid_texture_rgba8(graphics.clone(), [0, 0, 0, 0]); // make transparent for now + let handle = registry.solid_texture_rgba8(graphics.clone(), [0, 0, 0, 0], None); // make transparent for now let _ = registry.get_texture(handle); } @@ -392,26 +392,20 @@ impl MeshRenderer { if let Some(model) = registry.get_model(self.handle) { for material in &model.materials { - if material.diffuse_texture.hash == Some(texture.id) { + if material.diffuse_texture == texture { return true; } - if material.normal_texture.hash == Some(texture.id) { + if material.normal_texture == Some(texture) { return true; } - if let Some(emissive) = &material.emissive_texture { - if emissive.hash == Some(texture.id) { - return true; - } + if material.emissive_texture == Some(texture) { + return true; } - if let Some(mr) = &material.metallic_roughness_texture { - if mr.hash == Some(texture.id) { - return true; - } + if material.metallic_roughness_texture == Some(texture) { + return true; } - if let Some(occ) = &material.occlusion_texture { - if occ.hash == Some(texture.id) { - return true; - } + if material.occlusion_texture == Some(texture) { + return true; } } } @@ -24,8 +24,8 @@ pub struct SharedGraphicsContext { pub surface_config: Arc<RwLock<SurfaceConfiguration>>, pub instance: Arc<wgpu::Instance>, pub window: Arc<Window>, - pub viewport_texture: texture::Texture, - pub depth_texture: texture::Texture, + pub viewport_texture: Arc<texture::Texture>, + pub depth_texture: Arc<texture::Texture>, pub egui_renderer: Arc<Mutex<EguiRenderer>>, pub texture_id: Arc<TextureId>, pub future_queue: Arc<FutureQueue>, @@ -69,7 +69,7 @@ use winit::{ use crate::egui_renderer::EguiRenderer; use crate::graphics::{CommandEncoder, SharedGraphicsContext}; use crate::mipmap::MipMapper; -use crate::texture::Texture; +use crate::texture::{Texture, TextureBuilder}; use crate::pipelines::hdr::HdrPipeline; use crate::scene::Scene; @@ -375,8 +375,8 @@ pub struct State { pub config: Arc<RwLock<SurfaceConfiguration>>, pub is_surface_configured: bool, pub egui_renderer: Arc<Mutex<EguiRenderer>>, - pub depth_texture: Texture, - pub viewport_texture: Texture, + pub depth_texture: Arc<Texture>, + pub viewport_texture: Arc<Texture>, pub texture_id: Arc<TextureId>, pub future_queue: Arc<FutureQueue>, pub mipmapper: Arc<MipMapper>, @@ -529,8 +529,15 @@ Hardware: surface.configure(&device, &config); } - let depth_texture = Texture::depth_texture(&config, &device, antialiasing, Some("depth texture")); - let viewport_texture = Texture::viewport(&config, &device, Some("viewport texture")); + let depth_texture = Arc::new(TextureBuilder::new(&device) + .depth(&config, antialiasing) + .label("depth texture") + .build()); + + let viewport_texture = Arc::new(TextureBuilder::new(&device) + .viewport(&config) + .label("viewport texture") + .build()); let mipmapper = Arc::new(MipMapper::new(&device)); @@ -598,10 +605,18 @@ Hardware: self.hdr.write().resize(&self.device, width, height, Some(*self.antialiasing.read())); } - self.depth_texture = - Texture::depth_texture(&self.config.read(), &self.device, *self.antialiasing.read(), Some("depth texture")); - self.viewport_texture = - Texture::viewport(&self.config.read(), &self.device, Some("viewport texture")); + let depth_texture = TextureBuilder::new(&self.device) + .depth(&self.config.read(), *self.antialiasing.read()) + .label("depth texture") + .build(); + + let viewport_texture = TextureBuilder::new(&self.device) + .viewport(&self.config.read()) + .label("viewport texture") + .build(); + + self.depth_texture = Arc::new(depth_texture); + self.viewport_texture = Arc::new(viewport_texture); self.egui_renderer .lock() .renderer() @@ -621,8 +636,11 @@ Hardware: *self.antialiasing.write() = antialiasing; let config = self.config.read().clone(); - self.depth_texture = - Texture::depth_texture(&config, &self.device, antialiasing, Some("depth texture")); + let depth_texture = TextureBuilder::new(&self.device) + .depth(&config, antialiasing) + .label("depth texture") + .build(); + self.depth_texture = Arc::new(depth_texture); self.hdr .write() .resize(&self.device, config.width, config.height, Some(antialiasing)); @@ -645,8 +663,18 @@ Hardware: config.width = width; config.height = height; - self.depth_texture = Texture::depth_texture(&config, &self.device, *self.antialiasing.read(), Some("depth texture")); - self.viewport_texture = Texture::viewport(&config, &self.device, Some("viewport texture")); + let depth_texture = TextureBuilder::new(&self.device) + .depth(&config, *self.antialiasing.read()) + .label("depth texture") + .build(); + + let viewport_texture = TextureBuilder::new(&self.device) + .viewport(&config) + .label("viewport texture") + .build(); + + self.depth_texture = Arc::new(depth_texture); + self.viewport_texture = Arc::new(viewport_texture); self.hdr.write().resize(&self.device, width, height, Some(*self.antialiasing.read())); self.egui_renderer .lock() @@ -2,7 +2,7 @@ use crate::asset::{AssetRegistry, Handle}; use crate::buffer::UniformBuffer; use crate::{ graphics::SharedGraphicsContext, - texture::{Texture, TextureWrapMode}, + texture::{Texture, TextureBuilder, TextureWrapMode}, utils::ResourceReference, }; use gltf::image::{Format, Source}; @@ -47,11 +47,11 @@ pub struct Mesh { #[derive(Clone)] pub struct Material { pub name: String, - pub diffuse_texture: Texture, - pub normal_texture: Texture, - pub emissive_texture: Option<Texture>, - pub metallic_roughness_texture: Option<Texture>, - pub occlusion_texture: Option<Texture>, + pub diffuse_texture: Handle<Texture>, + pub normal_texture: Option<Handle<Texture>>, + pub emissive_texture: Option<Handle<Texture>>, + pub metallic_roughness_texture: Option<Handle<Texture>>, + pub occlusion_texture: Option<Handle<Texture>>, pub tint: [f32; 4], pub emissive_factor: [f32; 3], pub metallic_factor: f32, @@ -66,7 +66,6 @@ pub struct Material { pub bind_group: wgpu::BindGroup, pub texture_tag: Option<String>, pub wrap_mode: TextureWrapMode, - pub has_normal_texture: bool, } #[derive(Clone, Copy, Eq, PartialEq, Debug, Serialize, Deserialize, Default)] @@ -161,17 +160,14 @@ pub enum ChannelValues { impl Material { pub fn new( + registry: &mut AssetRegistry, graphics: Arc<SharedGraphicsContext>, name: impl Into<String>, - diffuse_texture: Texture, - normal_texture: Texture, - emissive_texture: Option<Texture>, - metallic_roughness_texture: Option<Texture>, - occlusion_texture: Option<Texture>, - emissive_texture_bound: Texture, - metallic_roughness_texture_bound: Texture, - occlusion_texture_bound: Texture, - has_normal_texture: bool, + diffuse_texture: Handle<Texture>, + normal_texture: Option<Handle<Texture>>, + emissive_texture: Option<Handle<Texture>>, + metallic_roughness_texture: Option<Handle<Texture>>, + occlusion_texture: Option<Handle<Texture>>, tint: [f32; 4], texture_tag: Option<String>, ) -> Self { @@ -189,13 +185,59 @@ impl Material { occlusion_strength: 1.0, alpha_cutoff: 0.5, uv_tiling, - has_normal_texture: has_normal_texture as u32, + has_normal_texture: normal_texture.is_some() as u32, has_emissive_texture: emissive_texture.is_some() as u32, has_metallic_texture: metallic_roughness_texture.is_some() as u32, has_occlusion_texture: occlusion_texture.is_some() as u32, pad: 0, }; + let d = registry.get_texture(diffuse_texture).unwrap(); + + let n = if let Some(normal) = normal_texture { + registry.get_texture(normal).unwrap() + } else { + let flat_normal_texture = registry.solid_texture_rgba8( + graphics.clone(), + [128, 128, 255, 255], + Some(Texture::TEXTURE_FORMAT_BASE), + ); + registry.get_texture(flat_normal_texture).unwrap() + }; + + let e = if let Some(e) = emissive_texture { + registry.get_texture(e).unwrap() + } else { + let e = registry.solid_texture_rgba8( + graphics.clone(), + [0, 0, 0, 255], + Some(Texture::TEXTURE_FORMAT_BASE), + ); + registry.get_texture(e).unwrap() + }; + + let mr = if let Some(m) = metallic_roughness_texture { + registry.get_texture(m).unwrap() + } else { + let metallic_roughness_texture = registry.solid_texture_rgba8( + graphics.clone(), + [0, 128, 0, 255], + Some(Texture::TEXTURE_FORMAT_BASE), + ); + registry.get_texture(metallic_roughness_texture).unwrap() + }; + + let o = if let Some(o) = occlusion_texture { + registry.get_texture(o).unwrap() + } else { + let o = registry.solid_texture_rgba8( + graphics.clone(), + [255, 255, 255, 255], + Some(Texture::TEXTURE_FORMAT_BASE), + ); + registry.get_texture(o).unwrap() + }; + let tint_buffer = UniformBuffer::new(&graphics.device, "material_tint_uniform"); tint_buffer.write(&graphics.queue, &uniform); @@ -209,47 +251,47 @@ impl Material { }, wgpu::BindGroupEntry { binding: 1, - resource: wgpu::BindingResource::TextureView(&diffuse_texture.view), + resource: wgpu::BindingResource::TextureView(&d.view), }, wgpu::BindGroupEntry { binding: 2, - resource: wgpu::BindingResource::Sampler(&diffuse_texture.sampler), + resource: wgpu::BindingResource::Sampler(&d.sampler), }, wgpu::BindGroupEntry { binding: 3, - resource: wgpu::BindingResource::TextureView(&normal_texture.view), + resource: wgpu::BindingResource::TextureView(&n.view), }, wgpu::BindGroupEntry { binding: 4, - resource: wgpu::BindingResource::Sampler(&normal_texture.sampler), + resource: wgpu::BindingResource::Sampler(&n.sampler), }, wgpu::BindGroupEntry { binding: 5, - resource: wgpu::BindingResource::TextureView(&emissive_texture_bound.view), + resource: wgpu::BindingResource::TextureView(&e.view), }, wgpu::BindGroupEntry { binding: 6, - resource: wgpu::BindingResource::Sampler(&emissive_texture_bound.sampler), + resource: wgpu::BindingResource::Sampler(&e.sampler), }, wgpu::BindGroupEntry { binding: 7, resource: wgpu::BindingResource::TextureView( - &metallic_roughness_texture_bound.view, + &mr.view, ), }, wgpu::BindGroupEntry { binding: 8, resource: wgpu::BindingResource::Sampler( - &metallic_roughness_texture_bound.sampler, + &mr.sampler, ), }, wgpu::BindGroupEntry { binding: 9, - resource: wgpu::BindingResource::TextureView(&occlusion_texture_bound.view), + resource: wgpu::BindingResource::TextureView(&o.view), }, wgpu::BindGroupEntry { binding: 10, - resource: wgpu::BindingResource::Sampler(&occlusion_texture_bound.sampler), + resource: wgpu::BindingResource::Sampler(&o.sampler), }, ], }); @@ -275,7 +317,6 @@ impl Material { emissive_texture, metallic_roughness_texture, occlusion_texture, - has_normal_texture, } } @@ -290,11 +331,11 @@ impl Material { occlusion_strength: self.occlusion_strength, alpha_cutoff: self.alpha_cutoff.unwrap_or(0.5), uv_tiling: self.uv_tiling, - has_normal_texture: self.has_normal_texture as u32, has_emissive_texture: self.emissive_texture.is_some() as u32, has_metallic_texture: self.metallic_roughness_texture.is_some() as u32, has_occlusion_texture: self.occlusion_texture.is_some() as u32, pad: 0, + has_normal_texture: self.normal_texture.is_some() as u32, }; self.tint_buffer.write(&graphics.queue, &uniform); @@ -1077,157 +1118,71 @@ impl Model { let mut materials = Vec::new(); - let white_srgb_texture = registry.solid_texture_rgba8_with_format( - graphics.clone(), - [255, 255, 255, 255], - Texture::TEXTURE_FORMAT_BASE.add_srgb_suffix(), - ); - let black_srgb_texture = registry.solid_texture_rgba8_with_format( - graphics.clone(), - [0, 0, 0, 255], - Texture::TEXTURE_FORMAT_BASE.add_srgb_suffix(), - ); - let white_linear_texture = registry.solid_texture_rgba8_with_format( - graphics.clone(), - [255, 255, 255, 255], - Texture::TEXTURE_FORMAT_BASE, - ); - let green_linear_texture = registry.solid_texture_rgba8_with_format( - graphics.clone(), - [0, 255, 0, 255], - Texture::TEXTURE_FORMAT_BASE, - ); - let flat_normal_texture = registry.solid_texture_rgba8_with_format( - graphics.clone(), - [128, 128, 255, 255], - Texture::TEXTURE_FORMAT_BASE, - ); - for processed in processed_textures { puffin::profile_scope!("creating material"); let material_name = processed.name; - let processed_diffuse = processed.diffuse; - let processed_normal = processed.normal; - let processed_emissive = processed.emissive; - let processed_metallic_roughness = processed.metallic_roughness; - let processed_occlusion = processed.occlusion; - let diffuse_texture = if let Some(diffuse) = processed_diffuse { - let format = diffuse.format.add_srgb_suffix(); - Texture::from_raw_pixels_mipmapped_with_format( - graphics.clone(), - &diffuse.pixels, - diffuse.dimensions, - format, - Some(diffuse.sampler), - Some(material_name.as_str()), - diffuse.mime_type.as_deref(), - ) - } else if let Some(white) = registry.get_texture(white_srgb_texture) { - (*white).clone() - } else { - anyhow::bail!( - "Unable to find processed diffuse or fetch fallback texture for model {:?}", - label - ); + + let build_texture = |tex: ProcessedTexture, format: wgpu::TextureFormat| -> Texture { + let wrap = match tex.sampler.address_mode_u { + wgpu::AddressMode::Repeat | wgpu::AddressMode::MirrorRepeat => TextureWrapMode::Repeat, + _ => TextureWrapMode::Clamp, + }; + let mut builder = TextureBuilder::new(&graphics.device) + .from_raw_pixels(graphics.clone(), tex.pixels.as_slice()) + .size(tex.dimensions.0, tex.dimensions.1) + .format(format) + .wrap_mode(wrap) + .mag_filter(tex.sampler.mag_filter) + .label(material_name.as_str()); + if let Some(ref mime) = tex.mime_type { + builder = builder.mime_type(mime.as_str()); + } + builder.build() }; - let has_normal_texture = processed_normal.is_some(); - let normal_texture = if let Some(normal) = processed_normal { - Texture::from_raw_pixels_mipmapped_with_format( + let diffuse_handle = if let Some(diffuse) = processed.diffuse { + let format = diffuse.format.add_srgb_suffix(); + registry.add_texture(build_texture(diffuse, format)) + } else { + registry.solid_texture_rgba8( graphics.clone(), - &normal.pixels, - normal.dimensions, - normal.format, - Some(normal.sampler), - Some(material_name.as_str()), - normal.mime_type.as_deref(), + [255, 255, 255, 255], + Some(Texture::TEXTURE_FORMAT_BASE.add_srgb_suffix()), ) - } else if let Some(tex) = registry.get_texture(flat_normal_texture) { - (*tex).clone() - } else { - anyhow::bail!( - "Unable to find processed normal or fetch fallback texture for model {:?}", - label - ); }; - let emissive_texture = processed_emissive.map(|emissive| { - let format = emissive.format.add_srgb_suffix(); - Texture::from_raw_pixels_mipmapped_with_format( - graphics.clone(), - &emissive.pixels, - emissive.dimensions, - format, - Some(emissive.sampler), - Some(material_name.as_str()), - emissive.mime_type.as_deref(), - ) + let normal_handle = processed.normal.map(|n| { + let format = n.format; + registry.add_texture(build_texture(n, format)) }); - let metallic_roughness_texture = processed_metallic_roughness.map(|metallic| { - Texture::from_raw_pixels_mipmapped_with_format( - graphics.clone(), - &metallic.pixels, - metallic.dimensions, - metallic.format, - Some(metallic.sampler), - Some(material_name.as_str()), - metallic.mime_type.as_deref(), - ) + + let emissive_handle = processed.emissive.map(|e| { + let format = e.format.add_srgb_suffix(); + registry.add_texture(build_texture(e, format)) }); - let occlusion_texture = processed_occlusion.map(|occlusion| { - Texture::from_raw_pixels_mipmapped_with_format( - graphics.clone(), - &occlusion.pixels, - occlusion.dimensions, - occlusion.format, - Some(occlusion.sampler), - Some(material_name.as_str()), - occlusion.mime_type.as_deref(), - ) + + let metallic_roughness_handle = processed.metallic_roughness.map(|mr| { + let format = mr.format; + registry.add_texture(build_texture(mr, format)) + }); + + let occlusion_handle = processed.occlusion.map(|occ| { + let format = occ.format; + registry.add_texture(build_texture(occ, format)) }); - let emissive_texture_bound = emissive_texture - .clone() - .or_else(|| registry.get_texture(black_srgb_texture).cloned()) - .ok_or_else(|| { - anyhow::anyhow!( - "Unable to resolve emissive fallback texture for model {:?}", - label - ) - })?; - let metallic_roughness_texture_bound = metallic_roughness_texture - .clone() - .or_else(|| registry.get_texture(green_linear_texture).cloned()) - .ok_or_else(|| { - anyhow::anyhow!( - "Unable to resolve metallic fallback texture for model {:?}", - label - ) - })?; - let occlusion_texture_bound = occlusion_texture - .clone() - .or_else(|| registry.get_texture(white_linear_texture).cloned()) - .ok_or_else(|| { - anyhow::anyhow!( - "Unable to resolve occlusion fallback texture for model {:?}", - label - ) - })?; let texture_tag = Some(material_name.clone()); let mut material = Material::new( + &mut *registry, graphics.clone(), material_name, - diffuse_texture, - normal_texture, - emissive_texture.clone(), - metallic_roughness_texture.clone(), - occlusion_texture.clone(), - emissive_texture_bound, - metallic_roughness_texture_bound, - occlusion_texture_bound, - has_normal_texture, + diffuse_handle, + normal_handle, + emissive_handle, + metallic_roughness_handle, + occlusion_handle, processed.tint, texture_tag, ); @@ -1240,9 +1195,6 @@ impl Model { material.double_sided = processed.double_sided; material.occlusion_strength = processed.occlusion_strength; material.normal_scale = processed.normal_scale; - material.emissive_texture = emissive_texture; - material.metallic_roughness_texture = metallic_roughness_texture; - material.occlusion_texture = occlusion_texture; material.sync_uniform(&graphics); materials.push(material); @@ -1311,24 +1263,7 @@ impl Model { }); } - if let Some(resref) = optional_resref.clone() { - for material in &mut materials { - material.diffuse_texture.reference = Some(resref.clone()); - material.normal_texture.reference = Some(resref.clone()); - if let Some(texture) = material.emissive_texture.as_mut() { - texture.reference = Some(resref.clone()); - } - - if let Some(texture) = material.metallic_roughness_texture.as_mut() { - texture.reference = Some(resref.clone()); - } - - if let Some(texture) = material.occlusion_texture.as_mut() { - texture.reference = Some(resref.clone()); - } - } - } log::debug!("Successfully loaded model [{:?}]", label); @@ -1707,7 +1642,7 @@ impl Hash for ModelVertex { #[repr(C)] #[derive(Copy, Clone, Debug, bytemuck::Pod, bytemuck::Zeroable)] pub struct MaterialUniform { - pub base_colour: [f32; 4], + pub base_colour: [f32; 4], // colour pub emissive: [f32; 3], pub emissive_strength: f32, pub metallic: f32, @@ -1,5 +1,5 @@ use crate::pipelines::create_render_pipeline; -use crate::texture::Texture; +use crate::texture::{Texture, TextureBuilder}; use wgpu::Operations; pub struct HdrPipeline { @@ -28,29 +28,25 @@ impl HdrPipeline { // features to be enabled for rendering. let format = wgpu::TextureFormat::Rgba16Float; - let texture = Texture::create_2d_texture( - device, - width, - height, - format, - wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::RENDER_ATTACHMENT, - wgpu::FilterMode::Nearest, - None, - Some("Hdr::texture"), - ); + let texture = TextureBuilder::new(device) + .size(width, height) + .format(format) + .usage(wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::RENDER_ATTACHMENT) + .mag_filter(wgpu::FilterMode::Nearest) + .label("Hdr::texture") + .build(); let msaa_texture = match antialiasing { crate::multisampling::AntiAliasingMode::None => None, - _ => Some(Texture::create_2d_texture( - device, - width, - height, - format, - wgpu::TextureUsages::RENDER_ATTACHMENT, - wgpu::FilterMode::Nearest, - Some(antialiasing), - Some("Hdr::msaa_texture"), - )), + _ => Some(TextureBuilder::new(device) + .size(width, height) + .format(format) + .usage(wgpu::TextureUsages::RENDER_ATTACHMENT) + .mag_filter(wgpu::FilterMode::Nearest) + .label("Hdr::texture") + .antialiasing(antialiasing) + .build() + ), }; let layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { @@ -129,28 +125,25 @@ impl HdrPipeline { pub fn resize(&mut self, device: &wgpu::Device, width: u32, height: u32, antialiasing: Option<crate::multisampling::AntiAliasingMode>) { self.antialiasing = antialiasing.unwrap_or(self.antialiasing); - self.texture = Texture::create_2d_texture( - device, - width, - height, - self.format, - wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::RENDER_ATTACHMENT, - wgpu::FilterMode::Nearest, - None, - Some("Hdr::texture"), - ); + self.texture = TextureBuilder::new(device) + .size(width, height) + .format(self.format) + .usage(wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::RENDER_ATTACHMENT) + .mag_filter(wgpu::FilterMode::Nearest) + .label("Hdr::texture") + .build(); + self.msaa_texture = match self.antialiasing { crate::multisampling::AntiAliasingMode::None => None, - _ => Some(Texture::create_2d_texture( - device, - width, - height, - self.format, - wgpu::TextureUsages::RENDER_ATTACHMENT, - wgpu::FilterMode::Nearest, - Some(self.antialiasing), - Some("Hdr::msaa_texture"), - )), + _ => Some(TextureBuilder::new(device) + .size(width, height) + .format(self.format) + .usage(wgpu::TextureUsages::RENDER_ATTACHMENT) + .mag_filter(wgpu::FilterMode::Nearest) + .label("Hdr::texture") + .antialiasing(self.antialiasing) + .build() + ), }; self.bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor { label: Some("Hdr::bind_group"), @@ -5,7 +5,6 @@ use crate::asset::{AssetRegistry, Handle}; use crate::graphics::SharedGraphicsContext; use crate::model::ModelVertex; use crate::model::{Material, Mesh, Model}; -use crate::texture::Texture; use crate::utils::ResourceReference; use parking_lot::RwLock; use serde::{Deserialize, Serialize}; @@ -38,8 +37,6 @@ impl ProcedurallyGeneratedObject { hash: Option<u64>, registry: Arc<RwLock<AssetRegistry>>, ) -> Model { - let mut _rguard = registry.write(); - let hash = if let Some(hash) = hash { hash } else { @@ -86,65 +83,22 @@ impl ProcedurallyGeneratedObject { }; let material = material.unwrap_or_else(|| { - let white_srgb_texture = _rguard.solid_texture_rgba8_with_format( - graphics.clone(), - [255, 255, 255, 255], - Texture::TEXTURE_FORMAT_BASE.add_srgb_suffix(), - ); - let black_srgb_texture = _rguard.solid_texture_rgba8_with_format( - graphics.clone(), - [0, 0, 0, 255], - Texture::TEXTURE_FORMAT_BASE.add_srgb_suffix(), - ); - let white_linear_texture = _rguard.solid_texture_rgba8_with_format( + let mut _rguard = registry.write(); + let white_srgb_texture = _rguard.solid_texture_rgba8( graphics.clone(), [255, 255, 255, 255], - Texture::TEXTURE_FORMAT_BASE, - ); - let green_linear_texture = _rguard.solid_texture_rgba8_with_format( - graphics.clone(), - [0, 255, 0, 255], - Texture::TEXTURE_FORMAT_BASE, - ); - let flat_normal_texture = _rguard.solid_texture_rgba8_with_format( - graphics.clone(), - [128, 128, 255, 255], - Texture::TEXTURE_FORMAT_BASE, + None, ); - let white_srgb = _rguard - .get_texture(white_srgb_texture) - .cloned() - .expect("Missing procedural white srgb texture"); - let black_srgb = _rguard - .get_texture(black_srgb_texture) - .cloned() - .expect("Missing procedural black srgb texture"); - let white_linear = _rguard - .get_texture(white_linear_texture) - .cloned() - .expect("Missing procedural white linear texture"); - let green_linear = _rguard - .get_texture(green_linear_texture) - .cloned() - .expect("Missing procedural green linear texture"); - let flat_normal = _rguard - .get_texture(flat_normal_texture) - .cloned() - .expect("Missing procedural flat normal texture"); - Material::new( + &mut _rguard, graphics.clone(), "procedural_material", - white_srgb, - flat_normal, + white_srgb_texture, + None, None, None, None, - black_srgb, - green_linear, - white_linear, - false, [1.0, 1.0, 1.0, 1.0], Some("procedural_material".to_string()), ) @@ -1,6 +1,6 @@ use crate::graphics::SharedGraphicsContext; use crate::pipelines::{create_render_pipeline_ex}; -use crate::texture::Texture; +use crate::texture::{Texture, TextureBuilder}; use image::codecs::hdr::HdrDecoder; use std::io::Cursor; use std::sync::Arc; @@ -176,16 +176,12 @@ impl HdrLoader { }) .collect::<Vec<_>>(); - let src = Texture::create_2d_texture( - device, - meta.width, - meta.height, - loader.texture_format, - wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST, - wgpu::FilterMode::Linear, - None, - None, - ); + let src = TextureBuilder::new(&device) + .size(meta.width, meta.height) + .format(loader.texture_format) + .usage(wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST) + .mag_filter(wgpu::FilterMode::Linear) + .build(); queue.write_texture( wgpu::TexelCopyTextureInfo { @@ -1,4 +1,4 @@ -use std::{fs, path::PathBuf, sync::Arc}; +use std::sync::Arc; use crate::asset::AssetRegistry; use crate::graphics::SharedGraphicsContext; @@ -7,7 +7,6 @@ use image::GenericImageView; use serde::{Deserialize, Serialize}; use crate::multisampling::{AntiAliasingMode}; -#[derive(Clone)] /// Describes a texture, like an image of some sort. Can be a normal texture on a model or a viewport or depth texture. pub struct Texture { pub label: Option<String>, @@ -17,243 +16,496 @@ pub struct Texture { pub view: wgpu::TextureView, pub hash: Option<u64>, pub reference: Option<ResourceReference>, - pub mime_type: Option<String>, } -impl Texture { - /// Describes the depth format for all Texture related functions in WGPU to use. Makes life easier - pub const DEPTH_FORMAT: wgpu::TextureFormat = wgpu::TextureFormat::Depth32Float; - pub const TEXTURE_FORMAT_BASE: wgpu::TextureFormat = wgpu::TextureFormat::Rgba8Unorm; - pub const TEXTURE_FORMAT: wgpu::TextureFormat = wgpu::TextureFormat::Rgba8UnormSrgb; +pub struct TextureBuilder<'a> { + device: &'a wgpu::Device, + graphics: Option<Arc<SharedGraphicsContext>>, - pub fn create_2d_texture( - device: &wgpu::Device, - width: u32, - height: u32, - format: wgpu::TextureFormat, - usage: wgpu::TextureUsages, - mag_filter: wgpu::FilterMode, - antialiasing: Option<AntiAliasingMode>, - label: Option<&str>, - ) -> Self { - puffin::profile_function!(label.unwrap_or("create 2d texture")); - let size = wgpu::Extent3d { - width, - height, - depth_or_array_layers: 1, - }; + width: u32, + height: u32, + depth_or_array_layers: u32, - Self::create_texture( - device, - label, - size, - format, - usage, - wgpu::TextureDimension::D2, - mag_filter, - antialiasing, - ) - } + format: wgpu::TextureFormat, + usage: wgpu::TextureUsages, + dimension: wgpu::TextureDimension, + sample_count: u32, + mip_level_count: u32, + auto_mip: bool, - pub fn create_texture( - device: &wgpu::Device, - label: Option<&str>, - size: wgpu::Extent3d, - format: wgpu::TextureFormat, - usage: wgpu::TextureUsages, - dimension: wgpu::TextureDimension, - mag_filter: wgpu::FilterMode, - antialiasing: Option<AntiAliasingMode>, - ) -> Self { - puffin::profile_function!(label.unwrap_or("create texture")); - let texture = device.create_texture(&wgpu::TextureDescriptor { - label, - size, - mip_level_count: 1, - sample_count: antialiasing.unwrap_or_default().into(), - dimension, - format, - usage, - view_formats: &[], - }); + mag_filter: wgpu::FilterMode, + min_filter: wgpu::FilterMode, + mipmap_filter: wgpu::FilterMode, + wrap_mode: TextureWrapMode, + compare: Option<wgpu::CompareFunction>, + lod_min_clamp: f32, + lod_max_clamp: f32, - let view = texture.create_view(&wgpu::TextureViewDescriptor::default()); - let sampler = device.create_sampler(&wgpu::SamplerDescriptor { - address_mode_u: wgpu::AddressMode::ClampToEdge, - address_mode_v: wgpu::AddressMode::ClampToEdge, - address_mode_w: wgpu::AddressMode::ClampToEdge, - mag_filter, - min_filter: wgpu::FilterMode::Nearest, - mipmap_filter: wgpu::FilterMode::Nearest, - ..Default::default() - }); + view_descriptor: Option<wgpu::TextureViewDescriptor<'a>>, - Self { - label: label.and_then(|v| Some(v.to_string())), - texture, - view, - sampler, - size, - hash: None, - reference: None, - mime_type: None, - } - } + label: Option<&'a str>, + mime_type: Option<String>, - /// Creates a new depth texture. This is an internal function. - pub fn depth_texture( - config: &wgpu::SurfaceConfiguration, - device: &wgpu::Device, - antialiasing: AntiAliasingMode, - label: Option<&str>, - ) -> Self { - puffin::profile_function!(label.unwrap_or("depth texture")); - let size = wgpu::Extent3d { - width: config.width.max(1), - height: config.height.max(1), - depth_or_array_layers: 1, - }; + source: TextureSource<'a>, +} - let desc = wgpu::TextureDescriptor { - label, - size, - mip_level_count: 1, // leave me alone - sample_count: antialiasing.into(), - dimension: wgpu::TextureDimension::D2, - format: Self::DEPTH_FORMAT, - usage: wgpu::TextureUsages::RENDER_ATTACHMENT | wgpu::TextureUsages::TEXTURE_BINDING, - view_formats: &[], - }; - let texture = device.create_texture(&desc); +enum TextureSource<'a> { + Empty, + Bytes(&'a [u8]), + RawPixels(&'a [u8]), + #[allow(dead_code)] + SurfaceConfig(&'a wgpu::SurfaceConfiguration), + #[allow(dead_code)] + Depth(&'a wgpu::SurfaceConfiguration), +} - let sampler = device.create_sampler(&wgpu::SamplerDescriptor { - address_mode_u: wgpu::AddressMode::ClampToEdge, - address_mode_v: wgpu::AddressMode::ClampToEdge, - address_mode_w: wgpu::AddressMode::ClampToEdge, +impl<'a> TextureBuilder<'a> { + pub fn new(device: &'a wgpu::Device) -> Self { + Self { + device, + graphics: None, + width: 1, + height: 1, + depth_or_array_layers: 1, + format: Texture::TEXTURE_FORMAT, + usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST, + dimension: wgpu::TextureDimension::D2, + sample_count: 1, + mip_level_count: 1, + auto_mip: false, mag_filter: wgpu::FilterMode::Linear, - min_filter: wgpu::FilterMode::Linear, + min_filter: wgpu::FilterMode::Nearest, mipmap_filter: wgpu::FilterMode::Nearest, - compare: Some(wgpu::CompareFunction::LessEqual), + wrap_mode: TextureWrapMode::Clamp, + compare: None, lod_min_clamp: 0.0, - lod_max_clamp: 100.0, - ..Default::default() - }); - let view = texture.create_view(&wgpu::TextureViewDescriptor::default()); - - Self { - texture, - sampler, - size, - view, - label: label.to_potential_string(), - hash: None, - reference: None, + lod_max_clamp: 32.0, + view_descriptor: None, + label: None, mime_type: None, + source: TextureSource::Empty, } } - /// Creates a viewport texture. - /// - /// This is an internal function. - pub fn viewport( - config: &wgpu::SurfaceConfiguration, - device: &wgpu::Device, - label: Option<&str>, - ) -> Self { - puffin::profile_function!(label.unwrap_or("viewport texture")); - let size = wgpu::Extent3d { - width: config.width.max(1), - height: config.height.max(1), - depth_or_array_layers: 1, - }; + pub fn size(mut self, width: u32, height: u32) -> Self { + self.width = width; + self.height = height; + self + } - let desc = wgpu::TextureDescriptor { - label, - size, - mip_level_count: 1, // leave me alone - sample_count: 1, - dimension: wgpu::TextureDimension::D2, - format: config.format.add_srgb_suffix(), - usage: wgpu::TextureUsages::RENDER_ATTACHMENT | wgpu::TextureUsages::TEXTURE_BINDING, - view_formats: &[], - }; + pub fn size_from_config(mut self, config: &'a wgpu::SurfaceConfiguration) -> Self { + self.width = config.width.max(1); + self.height = config.height.max(1); + self + } - let texture = device.create_texture(&desc); - let sampler = device.create_sampler(&wgpu::SamplerDescriptor::default()); - let view = texture.create_view(&wgpu::TextureViewDescriptor::default()); + pub fn format(mut self, format: wgpu::TextureFormat) -> Self { + self.format = format; + self + } - Self { - label: label.to_potential_string(), - texture, - sampler, - size, - view, - hash: None, - reference: None, - mime_type: None, + pub fn usage(mut self, usage: wgpu::TextureUsages) -> Self { + self.usage = usage; + self + } + + /// preset: depth_texture() + pub fn depth(mut self, config: &'a wgpu::SurfaceConfiguration, antialiasing: AntiAliasingMode) -> Self { + self.source = TextureSource::Depth(config); + self.width = config.width.max(1); + self.height = config.height.max(1); + self.format = Texture::DEPTH_FORMAT; + self.usage = wgpu::TextureUsages::RENDER_ATTACHMENT | wgpu::TextureUsages::TEXTURE_BINDING; + self.sample_count = antialiasing.into(); + self.mag_filter = wgpu::FilterMode::Linear; + self.min_filter = wgpu::FilterMode::Linear; + self.mipmap_filter = wgpu::FilterMode::Nearest; + self.compare = Some(wgpu::CompareFunction::LessEqual); + self.lod_min_clamp = 0.0; + self.lod_max_clamp = 100.0; + self + } + + /// preset: viewport() + pub fn viewport(mut self, config: &'a wgpu::SurfaceConfiguration) -> Self { + self.source = TextureSource::SurfaceConfig(config); + self.width = config.width.max(1); + self.height = config.height.max(1); + self.format = config.format.add_srgb_suffix(); + self.usage = wgpu::TextureUsages::RENDER_ATTACHMENT | wgpu::TextureUsages::TEXTURE_BINDING; + self.mag_filter = wgpu::FilterMode::Linear; + self.min_filter = wgpu::FilterMode::Linear; + self.mipmap_filter = wgpu::FilterMode::Nearest; + self + } + + pub fn render_target(mut self) -> Self { + self.usage = wgpu::TextureUsages::RENDER_ATTACHMENT | wgpu::TextureUsages::TEXTURE_BINDING; + self + } + + pub fn mag_filter(mut self, filter: wgpu::FilterMode) -> Self { + self.mag_filter = filter; + self + } + + pub fn wrap_mode(mut self, wrap: TextureWrapMode) -> Self { + self.wrap_mode = wrap; + self + } + + pub fn antialiasing(mut self, aa: AntiAliasingMode) -> Self { + self.sample_count = aa.into(); + self + } + + pub fn from_bytes(mut self, graphics: Arc<SharedGraphicsContext>, bytes: &'a [u8]) -> Self { + self.graphics = Some(graphics); + self.source = TextureSource::Bytes(bytes); + self.auto_mip = true; + self.mipmap_filter = wgpu::FilterMode::Linear; + self.usage = wgpu::TextureUsages::TEXTURE_BINDING + | wgpu::TextureUsages::RENDER_ATTACHMENT + | wgpu::TextureUsages::COPY_DST + | wgpu::TextureUsages::COPY_SRC; + self + } + + pub fn from_raw_pixels(mut self, graphics: Arc<SharedGraphicsContext>, pixels: &'a [u8]) -> Self { + self.graphics = Some(graphics); + self.source = TextureSource::RawPixels(pixels); + self.auto_mip = true; + self.mipmap_filter = wgpu::FilterMode::Linear; + self.usage = wgpu::TextureUsages::TEXTURE_BINDING + | wgpu::TextureUsages::RENDER_ATTACHMENT + | wgpu::TextureUsages::COPY_DST + | wgpu::TextureUsages::COPY_SRC; + self + } + + pub fn with_auto_mip(mut self) -> Self { + self.auto_mip = true; + self + } + + pub fn label(mut self, label: &'a str) -> Self { + self.label = Some(label); + self + } + + pub fn mime_type(mut self, mime: &str) -> Self { + self.mime_type = Some(mime.to_string()); + self + } + + pub fn view_descriptor(mut self, desc: wgpu::TextureViewDescriptor<'a>) -> Self { + self.view_descriptor = Some(desc); + self + } + + pub fn build(self) -> Texture { + puffin::profile_function!(self.label.unwrap_or("TextureBuilder::build")); + + match &self.source { + TextureSource::Bytes(bytes) => { + let graphics = self + .graphics + .as_ref() + .expect("from_bytes() requires graphics context"); + let hash = AssetRegistry::hash_bytes(bytes); + let requested_dimensions = Some((self.width, self.height)).filter(|&d| d != (1, 1)); + + let (rgba, dimensions) = match image::load_from_memory(bytes) { + Ok(image) => { + let rgba = image.to_rgba8().into_raw(); + let dims = requested_dimensions.unwrap_or_else(|| image.dimensions()); + (rgba, dims) + } + Err(err) => { + if let Some(dims) = requested_dimensions { + let expected_len = (dims.0 as usize) + .saturating_mul(dims.1 as usize) + .saturating_mul(4); + if bytes.len() == expected_len { + (bytes.to_vec(), dims) + } else { + log::error!( + "Texture [{:?}] decode failed ({:?}); expected {} bytes for raw RGBA ({}x{}), got {}. Falling back.", + self.label, + err, + expected_len, + dims.0, + dims.1, + bytes.len() + ); + (vec![255, 0, 255, 255], (1, 1)) + } + } else { + log::error!( + "Texture [{:?}] decode failed ({:?}) and no dimensions were provided; falling back to 1x1 magenta.", + self.label, + err + ); + (vec![255, 0, 255, 255], (1, 1)) + } + } + }; + + let size = wgpu::Extent3d { + width: dimensions.0, + height: dimensions.1, + depth_or_array_layers: 1, + }; + + let mip_level_count = self.compute_mip_level_count(size); + let texture = self.create_texture(&graphics.device, size, self.format, mip_level_count); + Self::upload_level0(&graphics.queue, &texture, size, &rgba, 4); + self.finish_uploaded_texture( + &graphics, + texture, + size, + hash, + ResourceReference::from_bytes(bytes), + ) + } + TextureSource::RawPixels(pixels) => { + let graphics = self + .graphics + .as_ref() + .expect("from_raw_pixels() requires graphics context"); + let hash = AssetRegistry::hash_bytes(pixels); + + let (format, pixels, dimensions) = match self.format.block_copy_size(None) { + Some(bytes_per_pixel) => { + let dimensions = (self.width, self.height); + let expected_len = (dimensions.0 as usize) + .saturating_mul(dimensions.1 as usize) + .saturating_mul(bytes_per_pixel as usize); + + if pixels.len() == expected_len { + (self.format, pixels.to_vec(), dimensions) + } else { + log::error!( + "Texture [{:?}] byte length {} does not match expected {} for {:?} ({}x{}). Falling back to 1x1 magenta.", + self.label, + pixels.len(), + expected_len, + self.format, + dimensions.0, + dimensions.1 + ); + (Texture::TEXTURE_FORMAT, vec![255, 0, 255, 255], (1, 1)) + } + } + None => { + log::error!( + "Texture [{:?}] has unsupported format {:?}; falling back to 1x1 magenta.", + self.label, + self.format + ); + (Texture::TEXTURE_FORMAT, vec![255, 0, 255, 255], (1, 1)) + } + }; + + let size = wgpu::Extent3d { + width: dimensions.0, + height: dimensions.1, + depth_or_array_layers: 1, + }; + + let bytes_per_pixel = format + .block_copy_size(None) + .expect("fallback format must have a valid block size"); + let mip_level_count = self.compute_mip_level_count(size); + let texture = self.create_texture(&graphics.device, size, format, mip_level_count); + Self::upload_level0(&graphics.queue, &texture, size, &pixels, bytes_per_pixel); + self.finish_uploaded_texture( + &graphics, + texture, + size, + hash, + ResourceReference::from_bytes(pixels.as_slice()), + ) + } + _ => { + let size = wgpu::Extent3d { + width: self.width, + height: self.height, + depth_or_array_layers: self.depth_or_array_layers, + }; + + let texture = self.device.create_texture(&wgpu::TextureDescriptor { + label: self.label, + size, + mip_level_count: self.mip_level_count, + sample_count: self.sample_count, + dimension: self.dimension, + format: self.format, + usage: self.usage, + view_formats: &[], + }); + + let view = texture.create_view( + &self.view_descriptor.clone().unwrap_or_default() + ); + let sampler = self.device.create_sampler(&self.build_sampler_desc()); + + Texture { + label: self.label.map(|s| s.to_string()), + texture, + view, + sampler, + size, + hash: None, + reference: None, + } + } } } - /// Loads the texture from a file. - pub async fn from_file( - graphics: Arc<SharedGraphicsContext>, - path: &PathBuf, - label: Option<&str>, - ) -> anyhow::Result<Self> { - puffin::profile_function!(label.unwrap_or("")); - let data = fs::read(path)?; - let mut result = Self::from_bytes(graphics.clone(), &data, label); - result.reference = Some(ResourceReference::from_path(path)?); - Ok(result) + fn compute_mip_level_count(&self, size: wgpu::Extent3d) -> u32 { + if self.auto_mip { + size.width.min(size.height).ilog2() + 1 + } else { + self.mip_level_count + } } - /// Loads the texture from bytes. - /// - /// If you want more customisability in the texture being generated, you can use [Self::from_bytes_verbose] - pub fn from_bytes( - graphics: Arc<SharedGraphicsContext>, - bytes: &[u8], - label: Option<&str>, - ) -> Self { - puffin::profile_function!(label.unwrap_or("")); - Self::from_bytes_verbose_mipmapped(graphics, bytes, None, None, None, label) + fn create_texture( + &self, + device: &wgpu::Device, + size: wgpu::Extent3d, + format: wgpu::TextureFormat, + mip_level_count: u32, + ) -> wgpu::Texture { + device.create_texture(&wgpu::TextureDescriptor { + label: self.label, + size, + mip_level_count, + sample_count: self.sample_count, + dimension: self.dimension, + format, + usage: self.usage, + view_formats: &[], + }) } - /// Loads the texture from bytes and generates mipmaps on the GPU. - /// - /// This is the recommended constructor for any sampled texture used for rendering. - pub fn from_bytes_verbose_mipmapped( - graphics: Arc<SharedGraphicsContext>, - bytes: &[u8], - dimensions: Option<(u32, u32)>, - view_descriptor: Option<wgpu::TextureViewDescriptor>, - sampler: Option<wgpu::SamplerDescriptor>, - label: Option<&str>, - ) -> Self { - puffin::profile_function!(label.unwrap_or("")); - let texture = Self::from_bytes_verbose_with_format( - graphics.clone(), - bytes, - dimensions, - None, - view_descriptor, + fn upload_level0( + queue: &wgpu::Queue, + texture: &wgpu::Texture, + size: wgpu::Extent3d, + pixels: &[u8], + bytes_per_pixel: u32, + ) { + let unpadded_bytes_per_row = bytes_per_pixel * size.width; + let padded_bytes_per_row = (unpadded_bytes_per_row + wgpu::COPY_BYTES_PER_ROW_ALIGNMENT + - 1) + & !(wgpu::COPY_BYTES_PER_ROW_ALIGNMENT - 1); + + if padded_bytes_per_row == unpadded_bytes_per_row { + queue.write_texture( + wgpu::TexelCopyTextureInfo { + texture, + mip_level: 0, + origin: wgpu::Origin3d::ZERO, + aspect: wgpu::TextureAspect::All, + }, + pixels, + wgpu::TexelCopyBufferLayout { + offset: 0, + bytes_per_row: Some(unpadded_bytes_per_row), + rows_per_image: Some(size.height), + }, + size, + ); + } else { + let mut padded = vec![0u8; (padded_bytes_per_row * size.height) as usize]; + let src_stride = unpadded_bytes_per_row as usize; + let dst_stride = padded_bytes_per_row as usize; + for row in 0..size.height as usize { + let src_start = row * src_stride; + let dst_start = row * dst_stride; + padded[dst_start..dst_start + src_stride] + .copy_from_slice(&pixels[src_start..src_start + src_stride]); + } + + queue.write_texture( + wgpu::TexelCopyTextureInfo { + texture, + mip_level: 0, + origin: wgpu::Origin3d::ZERO, + aspect: wgpu::TextureAspect::All, + }, + &padded, + wgpu::TexelCopyBufferLayout { + offset: 0, + bytes_per_row: Some(padded_bytes_per_row), + rows_per_image: Some(size.height), + }, + size, + ); + } + } + + fn finish_uploaded_texture( + &self, + graphics: &SharedGraphicsContext, + texture: wgpu::Texture, + size: wgpu::Extent3d, + hash: u64, + reference: ResourceReference, + ) -> Texture { + let sampler_desc = self.build_sampler_desc(); + let view_descriptor = self.view_descriptor.clone().unwrap_or_default(); + let view = texture.create_view(&view_descriptor); + let sampler = graphics.device.create_sampler(&sampler_desc); + + let built = Texture { + label: self.label.map(|s| s.to_string()), + texture, sampler, - label, - ); + size, + view, + hash: Some(hash), + reference: Some(reference), + }; - if let Err(err) = - graphics + if self.auto_mip { + if let Err(err) = graphics .mipmapper - .compute_mipmaps(&graphics.device, &graphics.queue, &texture) - { - log_once::warn_once!("Failed to generate mipmaps: {}", err); + .compute_mipmaps(&graphics.device, &graphics.queue, &built) + { + log_once::warn_once!("Failed to generate mipmaps: {}", err); + } } - texture + built + } + + fn build_sampler_desc(&self) -> wgpu::SamplerDescriptor<'static> { + let addr: wgpu::AddressMode = self.wrap_mode.into(); + wgpu::SamplerDescriptor { + address_mode_u: addr, + address_mode_v: addr, + address_mode_w: addr, + mag_filter: self.mag_filter, + min_filter: self.min_filter, + mipmap_filter: self.mipmap_filter, + compare: self.compare, + lod_min_clamp: self.lod_min_clamp, + lod_max_clamp: self.lod_max_clamp, + ..Default::default() + } } +} + +impl Texture { + pub const DEPTH_FORMAT: wgpu::TextureFormat = wgpu::TextureFormat::Depth32Float; + pub const TEXTURE_FORMAT_BASE: wgpu::TextureFormat = wgpu::TextureFormat::Rgba8Unorm; + pub const TEXTURE_FORMAT: wgpu::TextureFormat = wgpu::TextureFormat::Rgba8UnormSrgb; /// Loads the texture from bytes and generates mipmaps on the GPU using the specified format. - pub fn from_bytes_verbose_mipmapped_with_format( + pub(crate) fn from_bytes_verbose_mipmapped_with_format( graphics: Arc<SharedGraphicsContext>, bytes: &[u8], dimensions: Option<(u32, u32)>, @@ -284,29 +536,6 @@ impl Texture { texture } - /// Loads the texture from bytes, with options for more arguments. - /// - /// Requires more arguments. For a simpler usage, you should use [Self::from_bytes] - pub fn from_bytes_verbose( - graphics: Arc<SharedGraphicsContext>, - bytes: &[u8], - dimensions: Option<(u32, u32)>, - _texture_descriptor: Option<wgpu::TextureDescriptor>, - view_descriptor: Option<wgpu::TextureViewDescriptor>, - sampler: Option<wgpu::SamplerDescriptor>, - label: Option<&str>, - ) -> Self { - Self::from_bytes_verbose_with_format( - graphics, - bytes, - dimensions, - None, - view_descriptor, - sampler, - label, - ) - } - fn from_bytes_verbose_with_format( graphics: Arc<SharedGraphicsContext>, bytes: &[u8], @@ -464,14 +693,9 @@ impl Texture { view, hash: Some(hash), reference: Some(ResourceReference::from_bytes(bytes)), - mime_type: None, } } - fn bytes_per_pixel(format: wgpu::TextureFormat) -> Option<u32> { - format.block_copy_size(None) - } - pub fn from_raw_pixels_mipmapped_with_format( graphics: Arc<SharedGraphicsContext>, pixels: &[u8], @@ -479,7 +703,7 @@ impl Texture { format: wgpu::TextureFormat, sampler: Option<wgpu::SamplerDescriptor>, label: Option<&str>, - mime_type: Option<&str>, + _mime_type: Option<&str>, ) -> Self { puffin::profile_function!(label.unwrap_or("")); if let Some(l) = label { @@ -488,7 +712,7 @@ impl Texture { let hash = AssetRegistry::hash_bytes(pixels); - let bytes_per_pixel = match Self::bytes_per_pixel(format) { + let bytes_per_pixel = match format.block_copy_size(None) { Some(value) => value, None => { log::error!( @@ -627,7 +851,6 @@ impl Texture { view, hash: Some(hash), reference: Some(ResourceReference::from_bytes(pixels)), - mime_type: mime_type.map(|value| value.to_string()), }; if let Err(err) = @@ -640,18 +863,6 @@ impl Texture { texture } - - pub fn sampler_from_wrap(wrap: TextureWrapMode) -> wgpu::SamplerDescriptor<'static> { - wgpu::SamplerDescriptor { - address_mode_u: wrap.into(), - address_mode_v: wrap.into(), - address_mode_w: wrap.into(), - mag_filter: wgpu::FilterMode::Linear, - min_filter: wgpu::FilterMode::Nearest, - mipmap_filter: wgpu::FilterMode::Linear, - ..Default::default() - } - } } #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)] @@ -55,7 +55,7 @@ impl Component for AnimationComponent { return; }; - self.update(dt, model); + self.update(dt, &model); if let Ok(mut entity_transform) = world.get::<&mut EntityTransform>(entity) { if model.skins.is_empty() { @@ -8,7 +8,6 @@ use dropbear_engine::model::{ Animation, AnimationChannel, AnimationInterpolation, ChannelValues, Material, Mesh, ModelVertex, Node, NodeTransform, Skin, }; -use dropbear_engine::texture::Texture; use jni::JNIEnv; use jni::objects::{JObject, JValue}; @@ -103,7 +102,10 @@ impl ToJObject for NMesh { pub struct NMaterial { pub name: String, pub diffuse_texture: u64, - pub normal_texture: u64, + pub normal_texture: Option<u64>, + pub emissive_texture: Option<u64>, + pub metallic_roughness_texture: Option<u64>, + pub occlusion_texture: Option<u64>, pub tint: NVector4, pub emissive_factor: NVector3, pub metallic_factor: f32, @@ -113,9 +115,6 @@ pub struct NMaterial { pub occlusion_strength: f32, pub normal_scale: f32, pub uv_tiling: NVector2, - pub emissive_texture: Option<u64>, - pub metallic_roughness_texture: Option<u64>, - pub occlusion_texture: Option<u64>, } impl ToJObject for NMaterial { @@ -128,11 +127,10 @@ impl ToJObject for NMaterial { .new_string(&self.name) .map_err(|_| DropbearNativeError::JNIFailedToCreateObject)?; let diffuse_texture = new_texture(env, self.diffuse_texture)?; - let normal_texture = new_texture(env, self.normal_texture)?; - let tint = self.tint.to_jobject(env)?; - let emissive_factor = self.emissive_factor.to_jobject(env)?; - let uv_tiling = self.uv_tiling.to_jobject(env)?; - let alpha_cutoff = self.alpha_cutoff.to_jobject(env)?; + let normal_texture = match self.normal_texture { + Some(id) => new_texture(env, id)?, + None => JObject::null(), + }; let emissive_texture = match self.emissive_texture { Some(id) => new_texture(env, id)?, None => JObject::null(), @@ -145,6 +143,10 @@ impl ToJObject for NMaterial { Some(id) => new_texture(env, id)?, None => JObject::null(), }; + let tint = self.tint.to_jobject(env)?; + let emissive_factor = self.emissive_factor.to_jobject(env)?; + let uv_tiling = self.uv_tiling.to_jobject(env)?; + let alpha_cutoff = self.alpha_cutoff.to_jobject(env)?; let args = [ JValue::Object(&name), @@ -466,13 +468,6 @@ fn new_texture<'a>(env: &mut JNIEnv<'a>, texture_id: u64) -> DropbearNativeResul .map_err(|_| DropbearNativeError::JNIFailedToCreateObject) } -fn texture_handle_id(registry: &dropbear_engine::asset::AssetRegistry, texture: &Texture) -> u64 { - texture - .hash - .and_then(|hash| registry.texture_handle_by_hash(hash).map(|h| h.id)) - .unwrap_or(0) -} - fn map_vertex(vertex: &ModelVertex) -> NModelVertex { NModelVertex { position: NVector3::from(vertex.position), @@ -496,13 +491,13 @@ fn map_mesh(mesh: &Mesh) -> NMesh { } fn map_material( - registry: &dropbear_engine::asset::AssetRegistry, + _registry: &dropbear_engine::asset::AssetRegistry, material: &Material, ) -> NMaterial { NMaterial { name: material.name.clone(), - diffuse_texture: texture_handle_id(registry, &material.diffuse_texture), - normal_texture: texture_handle_id(registry, &material.normal_texture), + diffuse_texture: material.diffuse_texture.id, + normal_texture: material.normal_texture.and_then(|v| Some(v.id)), tint: NVector4::from(material.tint), emissive_factor: NVector3::from(material.emissive_factor), metallic_factor: material.metallic_factor, @@ -512,21 +507,13 @@ fn map_material( occlusion_strength: material.occlusion_strength, normal_scale: material.normal_scale, uv_tiling: NVector2::from(material.uv_tiling), - emissive_texture: material - .emissive_texture - .as_ref() - .map(|tex| texture_handle_id(registry, tex)) - .filter(|id| *id != 0), + emissive_texture: material.emissive_texture.and_then(|v| Some(v.id)), metallic_roughness_texture: material .metallic_roughness_texture - .as_ref() - .map(|tex| texture_handle_id(registry, tex)) - .filter(|id| *id != 0), + .and_then(|v| Some(v.id)), occlusion_texture: material .occlusion_texture - .as_ref() - .map(|tex| texture_handle_id(registry, tex)) - .filter(|id| *id != 0), + .and_then(|v| Some(v.id)), } } @@ -4,11 +4,11 @@ use crate::states::{SerializedMaterialCustomisation, SerializedMeshRenderer}; use crate::utils::ResolveReference; use downcast_rs::{Downcast, impl_downcast}; use dropbear_engine::asset::{ASSET_REGISTRY, Handle}; -use dropbear_engine::entity::{EntityTransform, MeshRenderer}; +use dropbear_engine::entity::{EntityTransform, MeshRenderer, Transform}; use dropbear_engine::graphics::SharedGraphicsContext; use dropbear_engine::model::Model; use dropbear_engine::procedural::{ProcObjType, ProcedurallyGeneratedObject}; -use dropbear_engine::texture::Texture; +use dropbear_engine::texture::{Texture, TextureBuilder}; use dropbear_engine::utils::{ResourceReference, ResourceReferenceType}; use egui::{CollapsingHeader, ComboBox, DragValue, Grid, RichText, UiBuilder}; use hecs::{Entity, World}; @@ -579,7 +579,8 @@ impl Component for MeshRenderer { mat.texture_tag = m.texture_tag.clone(); mat.wrap_mode = m.wrap_mode; - let get_tex = async |resource: &Option<ResourceReference>| -> Option<anyhow::Result<Texture>> { + let get_tex_handle = + async |resource: &Option<ResourceReference>| -> Option<anyhow::Result<Handle<Texture>>> { if let Some(dif) = resource { match &dif.ref_type { ResourceReferenceType::None => { @@ -587,12 +588,21 @@ impl Component for MeshRenderer { } ResourceReferenceType::File(_) => { let path = dif.resolve().ok()?; - let tex = Texture::from_file(graphics.clone(), &path, Some(&label)).await; - Some(tex) + let bytes = std::fs::read(&path).ok()?; + let texture = TextureBuilder::new(&graphics.device) + .from_bytes(graphics.clone(), bytes.as_slice()) + .label(label.as_str()) + .build(); + let mut registry = ASSET_REGISTRY.write(); + Some(Ok(registry.add_texture_with_label(label.clone(), texture))) } ResourceReferenceType::Bytes(bytes) => { - let tex = Texture::from_bytes(graphics.clone(), bytes, Some(&label)); - Some(Ok(tex)) + let texture = TextureBuilder::new(&graphics.device) + .from_bytes(graphics.clone(), bytes) + .label(label.as_str()) + .build(); + let mut registry = ASSET_REGISTRY.write(); + Some(Ok(registry.add_texture_with_label(label.clone(), texture))) } ResourceReferenceType::ProcObj(_) => { Some(Err(anyhow::anyhow!("Using a ProcObj as a texture is not valid, for texture with label {}", label))) @@ -603,23 +613,23 @@ impl Component for MeshRenderer { } }; - if let Some(tex) = get_tex(&m.diffuse_texture).await { + if let Some(tex) = get_tex_handle(&m.diffuse_texture).await { mat.diffuse_texture = tex?; } - if let Some(tex) = get_tex(&m.emissive_texture).await { + if let Some(tex) = get_tex_handle(&m.emissive_texture).await { mat.emissive_texture = Some(tex?); } - if let Some(tex) = get_tex(&m.normal_texture).await { - mat.normal_texture = tex?; + if let Some(tex) = get_tex_handle(&m.normal_texture).await { + mat.normal_texture = Some(tex?); } - if let Some(tex) = get_tex(&m.occlusion_texture).await { + if let Some(tex) = get_tex_handle(&m.occlusion_texture).await { mat.occlusion_texture = Some(tex?); } - if let Some(tex) = get_tex(&m.metallic_roughness_texture).await { + if let Some(tex) = get_tex_handle(&m.metallic_roughness_texture).await { mat.metallic_roughness_texture = Some(tex?); } } @@ -635,17 +645,23 @@ impl Component for MeshRenderer { _physics: &mut PhysicsState, entity: Entity, _dt: f32, - _graphics: Arc<SharedGraphicsContext>, + graphics: Arc<SharedGraphicsContext>, ) { if let Ok(transform) = world.query_one::<&EntityTransform>(entity).get() { self.update(&transform.propagate(&world, entity)); + } else { + self.update(&Transform::new()); + } + + for (_, v) in self.material_snapshot.iter() { + v.sync_uniform(&graphics); } } fn save(&self, _world: &World, _entity: Entity) -> Box<dyn SerializedComponent> { let asset = ASSET_REGISTRY.read(); let model = asset.get_model(self.model()); - let (label, handle) = if let Some(model) = model { + let (label, handle) = if let Some(model) = model.as_ref() { (model.label.clone(), model.path.clone()) } else { if !self.model().is_null() { @@ -659,20 +675,57 @@ impl Component for MeshRenderer { let mut texture_override: HashMap<String, SerializedMaterialCustomisation> = HashMap::new(); for (label, mat) in &self.material_snapshot { - let diffuse_texture = mat.diffuse_texture.reference.clone(); - let normal_texture = mat.normal_texture.reference.clone(); - let emissive_texture = mat - .emissive_texture - .as_ref() - .and_then(|t| t.reference.clone()); - let occlusion_texture = mat - .occlusion_texture + let default_material = model .as_ref() - .and_then(|t| t.reference.clone()); - let metallic_roughness_texture = mat - .metallic_roughness_texture - .as_ref() - .and_then(|t| t.reference.clone()); + .and_then(|m| m.materials.iter().find(|default| default.name == *label)); + + let diffuse_texture = if default_material + .map(|default| default.diffuse_texture) + == Some(mat.diffuse_texture) + { + None + } else { + asset + .get_texture(mat.diffuse_texture) + .and_then(|t| t.reference.clone()) + }; + + let normal_texture = if default_material.map(|default| default.normal_texture) + == Some(mat.normal_texture) + { + None + } else { + mat.normal_texture + .and_then(|h| asset.get_texture(h).and_then(|t| t.reference.clone())) + }; + + let emissive_texture = if default_material.map(|default| default.emissive_texture) + == Some(mat.emissive_texture) + { + None + } else { + mat.emissive_texture + .and_then(|h| asset.get_texture(h).and_then(|t| t.reference.clone())) + }; + + let occlusion_texture = if default_material.map(|default| default.occlusion_texture) + == Some(mat.occlusion_texture) + { + None + } else { + mat.occlusion_texture + .and_then(|h| asset.get_texture(h).and_then(|t| t.reference.clone())) + }; + + let metallic_roughness_texture = if default_material + .map(|default| default.metallic_roughness_texture) + == Some(mat.metallic_roughness_texture) + { + None + } else { + mat.metallic_roughness_texture + .and_then(|h| asset.get_texture(h).and_then(|t| t.reference.clone())) + }; texture_override.insert( label.to_string(), @@ -1124,38 +1177,38 @@ impl InspectableComponent for MeshRenderer { let default_textures = { let mut registry = ASSET_REGISTRY.write(); - let white_srgb = registry.solid_texture_rgba8_with_format( + let white_srgb = registry.solid_texture_rgba8( graphics.clone(), [255, 255, 255, 255], - Texture::TEXTURE_FORMAT_BASE.add_srgb_suffix(), + Some(Texture::TEXTURE_FORMAT_BASE.add_srgb_suffix()), ); - let black_srgb = registry.solid_texture_rgba8_with_format( + let black_srgb = registry.solid_texture_rgba8( graphics.clone(), [0, 0, 0, 255], - Texture::TEXTURE_FORMAT_BASE.add_srgb_suffix(), + Some(Texture::TEXTURE_FORMAT_BASE.add_srgb_suffix()), ); - let white_linear = registry.solid_texture_rgba8_with_format( + let white_linear = registry.solid_texture_rgba8( graphics.clone(), [255, 255, 255, 255], - Texture::TEXTURE_FORMAT_BASE, + Some(Texture::TEXTURE_FORMAT_BASE), ); - let green_linear = registry.solid_texture_rgba8_with_format( + let green_linear = registry.solid_texture_rgba8( graphics.clone(), [0, 255, 0, 255], - Texture::TEXTURE_FORMAT_BASE, + Some(Texture::TEXTURE_FORMAT_BASE), ); - let flat_normal = registry.solid_texture_rgba8_with_format( + let flat_normal = registry.solid_texture_rgba8( graphics.clone(), [128, 128, 255, 255], - Texture::TEXTURE_FORMAT_BASE, + Some(Texture::TEXTURE_FORMAT_BASE), ); ( - registry.get_texture(white_srgb).cloned(), - registry.get_texture(flat_normal).cloned(), - registry.get_texture(black_srgb).cloned(), - registry.get_texture(green_linear).cloned(), - registry.get_texture(white_linear).cloned(), + Some(white_srgb), + Some(flat_normal), + Some(black_srgb), + Some(green_linear), + Some(white_linear), ) }; @@ -1179,6 +1232,32 @@ impl InspectableComponent for MeshRenderer { .id_salt(format!("{} {}", material_id, entity.to_bits())) .default_open(true) .show(ui, |ui| { + ui.horizontal(|ui| { + if ui.button("Reset Material").clicked() { + if let Some(default) = default_material.as_ref() { + material.diffuse_texture = default.diffuse_texture; + material.normal_texture = default.normal_texture; + material.emissive_texture = default.emissive_texture; + material.metallic_roughness_texture = + default.metallic_roughness_texture; + material.occlusion_texture = default.occlusion_texture; + material.tint = default.tint; + material.emissive_factor = default.emissive_factor; + material.metallic_factor = default.metallic_factor; + material.roughness_factor = default.roughness_factor; + material.alpha_mode = default.alpha_mode; + material.alpha_cutoff = default.alpha_cutoff; + material.double_sided = default.double_sided; + material.occlusion_strength = default.occlusion_strength; + material.normal_scale = default.normal_scale; + material.uv_tiling = default.uv_tiling; + material.texture_tag = default.texture_tag.clone(); + material.wrap_mode = default.wrap_mode; + material.sync_uniform(&graphics); + } + } + }); + ui.add_space(4.0); ui.label( RichText::new("Colors") @@ -1400,66 +1479,54 @@ impl InspectableComponent for MeshRenderer { ui.add_space(8.0); ui.label(RichText::new("Textures").strong()); let texture_matches = - |current: Option<&Texture>, candidate: Option<&Texture>| { + |current: Option<Handle<Texture>>, candidate: Option<Handle<Texture>>| { match (current, candidate) { (None, None) => true, - (Some(cur), Some(def)) => match (cur.hash, def.hash) { - (Some(a), Some(b)) if a == b => true, - _ => match (&cur.reference, &def.reference) { - (Some(a), Some(b)) if a == b => true, - _ => match (&cur.label, &def.label) { - (Some(a), Some(b)) => a == b, - _ => false, - }, - }, - }, + (Some(cur), Some(def)) => cur == def, _ => false, } }; - let original_label = |original: &Option<Texture>| -> String { - if let Some(texture) = original { + let handle_label = |handle: Handle<Texture>| -> String { + let registry = ASSET_REGISTRY.read(); + if let Some(texture) = registry.get_texture(handle) { if let Some(label) = &texture.label { - label.clone() - } else if let Some(reference) = &texture.reference { - reference - .as_uri() - .map(|uri| { - uri.rsplit('/') - .next() - .filter(|v| !v.is_empty()) - .unwrap_or(uri) - .to_string() - }) - .unwrap_or_else(|| "Original".to_string()) - } else { - "Original".to_string() + return label.clone(); + } + if let Some(reference) = &texture.reference { + if let Some(uri) = reference.as_uri() { + return uri + .rsplit('/') + .next() + .filter(|v| !v.is_empty()) + .unwrap_or(uri) + .to_string(); + } } - } else { - "Original (None)".to_string() } + format!("Texture {:016x}", handle.id) + }; + let original_label = |original: Option<Handle<Texture>>| -> String { + original + .map(handle_label) + .unwrap_or_else(|| "Original (None)".to_string()) }; let texture_combo = |ui: &mut egui::Ui, slot_label: &str, slot_id: &str, - current_texture: Option<&Texture>, - original_texture: Option<Texture>, - default_texture: Option<Texture>| - -> Option<Option<Texture>> { + current_texture: Option<Handle<Texture>>, + original_texture: Option<Handle<Texture>>, + default_texture: Option<Handle<Texture>>| + -> Option<Option<Handle<Texture>>> { let mut updated = None; - let is_original = - texture_matches(current_texture, original_texture.as_ref()); - let is_default = - texture_matches(current_texture, default_texture.as_ref()); + let is_original = texture_matches(current_texture, original_texture); + let is_default = texture_matches(current_texture, default_texture); let selected_text = if is_original { - original_label(&original_texture) + original_label(original_texture) } else if is_default { "Default".to_string() - } else if let Some(texture) = current_texture { - texture - .label - .clone() - .unwrap_or_else(|| "Texture".to_string()) + } else if let Some(handle) = current_texture { + handle_label(handle) } else { "None".to_string() }; @@ -1475,11 +1542,11 @@ impl InspectableComponent for MeshRenderer { if ui .selectable_label( false, - original_label(&original_texture), + original_label(original_texture), ) .clicked() { - updated = Some(original_texture.clone()); + updated = Some(original_texture); } ui.separator(); @@ -1488,7 +1555,7 @@ impl InspectableComponent for MeshRenderer { .selectable_label(false, "Default") .clicked() { - updated = Some(default_texture.clone()); + updated = Some(default_texture); } ui.separator(); @@ -1501,9 +1568,9 @@ impl InspectableComponent for MeshRenderer { if let Some(texture) = ASSET_REGISTRY .read() .get_texture(*handle) - .cloned() { - updated = Some(Some(texture)); + let _ = texture; + updated = Some(Some(*handle)); } } } @@ -1514,17 +1581,17 @@ impl InspectableComponent for MeshRenderer { }; let original_diffuse = - default_material.as_ref().map(|m| m.diffuse_texture.clone()); + default_material.as_ref().map(|m| m.diffuse_texture); let original_normal = - default_material.as_ref().map(|m| m.normal_texture.clone()); + default_material.as_ref().and_then(|m| m.normal_texture); let original_emissive = - default_material.as_ref().and_then(|m| m.emissive_texture.clone()); + default_material.as_ref().and_then(|m| m.emissive_texture); let original_mr = default_material .as_ref() - .and_then(|m| m.metallic_roughness_texture.clone()); + .and_then(|m| m.metallic_roughness_texture); let original_occ = default_material .as_ref() - .and_then(|m| m.occlusion_texture.clone()); + .and_then(|m| m.occlusion_texture); let (default_diffuse, default_normal, default_emissive, default_mr, default_occ) = default_textures.clone(); @@ -1533,7 +1600,7 @@ impl InspectableComponent for MeshRenderer { ui, "Diffuse", "diffuse", - Some(&material.diffuse_texture), + Some(material.diffuse_texture), original_diffuse, default_diffuse, ) { @@ -1546,12 +1613,12 @@ impl InspectableComponent for MeshRenderer { ui, "Normal", "normal", - Some(&material.normal_texture), + material.normal_texture, original_normal, default_normal, ) { if let Some(tex) = new_normal { - material.normal_texture = tex; + material.normal_texture = Some(tex); } } @@ -1559,7 +1626,7 @@ impl InspectableComponent for MeshRenderer { ui, "Emissive", "emissive", - material.emissive_texture.as_ref(), + material.emissive_texture, original_emissive, default_emissive, ) { @@ -1570,7 +1637,7 @@ impl InspectableComponent for MeshRenderer { ui, "Metal/Rough", "metal_rough", - material.metallic_roughness_texture.as_ref(), + material.metallic_roughness_texture, original_mr, default_mr, ) { @@ -1581,7 +1648,7 @@ impl InspectableComponent for MeshRenderer { ui, "Occlusion", "occlusion", - material.occlusion_texture.as_ref(), + material.occlusion_texture, original_occ, default_occ, ) { @@ -1,4 +1,6 @@ pub mod shared { + use std::sync::Arc; + use dropbear_engine::asset::AssetRegistry; use dropbear_engine::entity::MeshRenderer; use dropbear_engine::model::Model; @@ -27,10 +29,10 @@ pub mod shared { .map(|mat| mat.name.clone()) } - pub fn model_for_renderer<'a>( - registry: &'a AssetRegistry, + pub fn model_for_renderer( + registry: &AssetRegistry, renderer: &MeshRenderer, - ) -> Option<&'a Model> { + ) -> Option<Arc<Model>> { registry.get_model(renderer.model()) } } @@ -110,17 +112,15 @@ fn get_all_texture_ids( shared::model_for_renderer(&reader, &renderer).ok_or(DropbearNativeError::AssetNotFound)?; let mut ids = HashSet::new(); - let mut push_handle = |texture: &Texture| { - if let Some(hash) = texture.hash { - if let Some(handle) = reader.texture_handle_by_hash(hash) { - ids.insert(handle.id); - } - } + let mut push_handle = |handle: &Handle<Texture>| { + ids.insert(handle.id); }; for material in &model.materials { push_handle(&material.diffuse_texture); - push_handle(&material.normal_texture); + if let Some(tex) = &material.normal_texture { + push_handle(tex); + } if let Some(tex) = &material.emissive_texture { push_handle(tex); } @@ -154,7 +154,7 @@ fn get_texture( .map_err(|_| DropbearNativeError::NoSuchComponent)?; let model = shared::model_for_renderer(&reader, &renderer).ok_or(DropbearNativeError::AssetNotFound)?; - let idx = match shared::resolve_target_material_index(model, &material_name) { + let idx = match shared::resolve_target_material_index(&model, &material_name) { Some(value) => value, None => return Ok(None), }; @@ -163,11 +163,9 @@ fn get_texture( .get(idx) .ok_or(DropbearNativeError::InvalidArgument)?; - Ok(material + Ok(Some(material .diffuse_texture - .hash - .and_then(|hash| reader.texture_handle_by_hash(hash)) - .map(|handle| handle.id)) + .id)) } #[dropbear_macro::export( @@ -190,20 +188,20 @@ fn set_texture_override( .map_err(|_| DropbearNativeError::NoSuchComponent)?; let model = shared::model_for_renderer(&reader, &renderer).ok_or(DropbearNativeError::AssetNotFound)?; - let _ = shared::resolve_target_material_name(model, &material_name) + let _ = shared::resolve_target_material_name(&model, &material_name) .ok_or(DropbearNativeError::InvalidArgument)?; let handle = Handle::<Texture>::new(texture_handle); - let Some(diff_tex) = reader.get_texture(handle) else { + if reader.get_texture(handle).is_none() { return Err(DropbearNativeError::InvalidHandle); - }; + } let mut renderer = world .get::<&mut MeshRenderer>(entity) .map_err(|_| DropbearNativeError::NoSuchComponent)?; if let Some(v) = renderer.material_snapshot.get_mut(&material_name) { - v.diffuse_texture = diff_tex.clone(); + v.diffuse_texture = handle; } else { return Err(DropbearNativeError::InvalidArgument); } @@ -1,6 +1,6 @@ use dropbear_engine::asset::ASSET_REGISTRY; use dropbear_engine::model::Model; -use dropbear_engine::texture::Texture; +use dropbear_engine::texture::TextureBuilder; use dropbear_engine::{graphics::NO_TEXTURE, utils::ResourceReference}; use egui_ltreeview::{Action, NodeBuilder, TreeViewBuilder}; use eucalyptus_core::states::PROJECT; @@ -1212,7 +1212,7 @@ impl<'a> EditorTabViewer<'a> { graphics.clone(), buffer, Some(reference.clone()), - None, + Some(label.as_str()), ASSET_REGISTRY.clone(), ) .await @@ -1225,10 +1225,6 @@ impl<'a> EditorTabViewer<'a> { }; let mut registry = ASSET_REGISTRY.write(); - if let Some(model) = registry.get_model_mut(handle) { - model.path = reference.clone(); - model.label = label.clone(); - } registry.label_model(label.clone(), handle); Ok::<(), anyhow::Error>(()) @@ -1249,13 +1245,12 @@ impl<'a> EditorTabViewer<'a> { let queue = graphics.future_queue.clone(); queue.push(async move { let path = reference.resolve()?; - let texture = match Texture::from_file(graphics.clone(), &path, Some(&label)).await { - Ok(v) => v, - Err(e) => { - eucalyptus_core::warn!("Unable to load texture {}: {}", reference, e); - return Err(e); - } - }; + let bytes = fs::read(&path)?; + let mut texture = TextureBuilder::new(&graphics.device) + .from_bytes(graphics.clone(), bytes.as_slice()) + .label(label.as_str()) + .build(); + texture.reference = Some(reference.clone()); let mut registry = ASSET_REGISTRY.write(); registry.add_texture_with_label(label.clone(), texture); Ok::<(), anyhow::Error>(()) @@ -579,48 +579,46 @@ impl Scene for Editor { prepared_models.push((model, handle, instances.len() as u32)); } - { - puffin::profile_scope!("light cube render pass"); - let mut render_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor { - label: Some("light cube render pass"), - color_attachments: &[Some(wgpu::RenderPassColorAttachment { - view: hdr.render_view(), - depth_slice: None, - resolve_target: hdr.resolve_target(), - ops: wgpu::Operations { - load: wgpu::LoadOp::Load, - store: wgpu::StoreOp::Store, - }, - })], - depth_stencil_attachment: Some(wgpu::RenderPassDepthStencilAttachment { - view: &graphics.depth_texture.view, - depth_ops: Some(wgpu::Operations { - load: wgpu::LoadOp::Load, - store: wgpu::StoreOp::Store, - }), - stencil_ops: None, - }), - occlusion_query_set: None, - timestamp_writes: None, - }); - if let Some(light_pipeline) = &self.light_cube_pipeline { - render_pass.set_pipeline(light_pipeline.pipeline()); - for light in &lights { - render_pass.set_vertex_buffer(1, light.instance_buffer.buffer().slice(..)); - if !light.component.visible { - continue; - } - - let Some(model) = registry.get_model(light.cube_model) else { - log_once::error_once!( - "Missing light cube model handle {} in registry", - light.cube_model.id - ); - continue; - }; + if let Some(light_pipeline) = &self.light_cube_pipeline { + if let Some(l) = lights.first() + && let Some(model) = registry.get_model(l.cube_model) + { + { + puffin::profile_scope!("light cube pass"); + let mut render_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor { + label: Some("light cube render pass"), + color_attachments: &[Some(wgpu::RenderPassColorAttachment { + view: hdr.render_view(), + depth_slice: None, + resolve_target: hdr.resolve_target(), + ops: wgpu::Operations { + load: wgpu::LoadOp::Load, + store: wgpu::StoreOp::Store, + }, + })], + depth_stencil_attachment: Some(wgpu::RenderPassDepthStencilAttachment { + view: &graphics.depth_texture.view, + depth_ops: Some(wgpu::Operations { + load: wgpu::LoadOp::Load, + store: wgpu::StoreOp::Store, + }), + stencil_ops: None, + }), + occlusion_query_set: None, + timestamp_writes: None, + }); - render_pass.draw_light_model(model, camera_bind_group, &light.bind_group); + render_pass.set_pipeline(light_pipeline.pipeline()); + for light in &lights { + render_pass.set_vertex_buffer(1, light.instance_buffer.buffer().slice(..)); + if !light.component.visible { + continue; + } + render_pass.draw_light_model(&model, camera_bind_group, &light.bind_group); + } } + } else { + log_once::error_once!("Missing light cube model handle in registry",); } } @@ -1,6 +1,7 @@ //! Allows you to a launch play mode as another window. use crossbeam_channel::{Receiver, unbounded}; +use dropbear_engine::billboarding::BillboardPipeline; use dropbear_engine::buffer::ResizableBuffer; use dropbear_engine::camera::Camera; use dropbear_engine::future::{FutureHandle, FutureQueue}; @@ -40,7 +41,6 @@ use std::sync::Arc; use wgpu::SurfaceConfiguration; use wgpu::util::DeviceExt; use winit::window::Fullscreen; -use dropbear_engine::billboarding::BillboardPipeline; mod command; mod input; @@ -248,7 +248,11 @@ impl PlayMode { Ok(result) } - pub fn reload_wgpu(&mut self, graphics: Arc<SharedGraphicsContext>, sky_texture: Option<&Vec<u8>>) { + pub fn reload_wgpu( + &mut self, + graphics: Arc<SharedGraphicsContext>, + sky_texture: Option<&Vec<u8>>, + ) { self.light_cube_pipeline = None; self.main_pipeline = None; self.shader_globals = None; @@ -265,7 +269,11 @@ impl PlayMode { self.load_wgpu_nerdy_stuff(graphics, sky_texture); } - pub fn load_wgpu_nerdy_stuff<'a>(&mut self, graphics: Arc<SharedGraphicsContext>, sky_texture: Option<&Vec<u8>>) { + pub fn load_wgpu_nerdy_stuff<'a>( + &mut self, + graphics: Arc<SharedGraphicsContext>, + sky_texture: Option<&Vec<u8>>, + ) { self.light_cube_pipeline = Some(LightCubePipeline::new(graphics.clone())); self.main_pipeline = Some(MainRenderPipeline::new(graphics.clone())); self.shader_globals = Some(GlobalsUniform::new( @@ -280,31 +288,43 @@ impl PlayMode { if self.default_skinning_buffer.is_none() { let max_skinning_matrices = 256usize; let identity = vec![Mat4::IDENTITY; max_skinning_matrices]; - let skinning_buffer = graphics.device.create_buffer_init(&wgpu::util::BufferInitDescriptor { - label: Some("runtime default skinning buffer"), - contents: bytemuck::cast_slice(&identity), - usage: wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_DST, - }); - - let morph_deltas_buffer = graphics.device.create_buffer_init(&wgpu::util::BufferInitDescriptor { - label: Some("runtime default morph deltas buffer"), - contents: bytemuck::cast_slice(&[0.0f32]), - usage: wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_DST, - }); + let skinning_buffer = + graphics + .device + .create_buffer_init(&wgpu::util::BufferInitDescriptor { + label: Some("runtime default skinning buffer"), + contents: bytemuck::cast_slice(&identity), + usage: wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_DST, + }); + + let morph_deltas_buffer = + graphics + .device + .create_buffer_init(&wgpu::util::BufferInitDescriptor { + label: Some("runtime default morph deltas buffer"), + contents: bytemuck::cast_slice(&[0.0f32]), + usage: wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_DST, + }); let morph_weights = vec![0.0f32; MAX_MORPH_WEIGHTS]; - let morph_weights_buffer = graphics.device.create_buffer_init(&wgpu::util::BufferInitDescriptor { - label: Some("runtime default morph weights buffer"), - contents: bytemuck::cast_slice(&morph_weights), - usage: wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_DST, - }); + let morph_weights_buffer = + graphics + .device + .create_buffer_init(&wgpu::util::BufferInitDescriptor { + label: Some("runtime default morph weights buffer"), + contents: bytemuck::cast_slice(&morph_weights), + usage: wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_DST, + }); let morph_info = dropbear_engine::animation::MorphTargetInfo::default(); - let morph_info_buffer = graphics.device.create_buffer_init(&wgpu::util::BufferInitDescriptor { - label: Some("runtime default morph info buffer"), - contents: bytemuck::bytes_of(&morph_info), - usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST, - }); + let morph_info_buffer = + graphics + .device + .create_buffer_init(&wgpu::util::BufferInitDescriptor { + label: Some("runtime default morph info buffer"), + contents: bytemuck::bytes_of(&morph_info), + usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST, + }); self.default_skinning_buffer = Some(skinning_buffer); self.default_morph_deltas_buffer = Some(morph_deltas_buffer); @@ -330,28 +350,30 @@ impl PlayMode { .as_ref() .expect("Default morph info buffer missing"); - self.default_animation_bind_group = Some(graphics.device.create_bind_group(&wgpu::BindGroupDescriptor { - label: Some("runtime default animation bind group"), - layout: &graphics.layouts.animation_layout, - entries: &[ - wgpu::BindGroupEntry { - binding: 0, - resource: skinning_buffer.as_entire_binding(), - }, - wgpu::BindGroupEntry { - binding: 1, - resource: morph_deltas_buffer.as_entire_binding(), - }, - wgpu::BindGroupEntry { - binding: 2, - resource: morph_weights_buffer.as_entire_binding(), - }, - wgpu::BindGroupEntry { - binding: 3, - resource: morph_info_buffer.as_entire_binding(), - }, - ], - })); + self.default_animation_bind_group = Some(graphics.device.create_bind_group( + &wgpu::BindGroupDescriptor { + label: Some("runtime default animation bind group"), + layout: &graphics.layouts.animation_layout, + entries: &[ + wgpu::BindGroupEntry { + binding: 0, + resource: skinning_buffer.as_entire_binding(), + }, + wgpu::BindGroupEntry { + binding: 1, + resource: morph_deltas_buffer.as_entire_binding(), + }, + wgpu::BindGroupEntry { + binding: 2, + resource: morph_weights_buffer.as_entire_binding(), + }, + wgpu::BindGroupEntry { + binding: 3, + resource: morph_info_buffer.as_entire_binding(), + }, + ], + }, + )); } self.kino = Some(KinoState::new( @@ -379,14 +401,16 @@ impl PlayMode { if let Some(camera_entity) = self.active_camera { if let Ok(camera) = self.world.query_one::<&Camera>(camera_entity).get() { - camera_bind_group = Some(graphics.device.create_bind_group(&wgpu::BindGroupDescriptor { - label: Some("runtime camera bind group"), - layout: &graphics.layouts.camera_bind_group_layout, - entries: &[wgpu::BindGroupEntry { - binding: 0, - resource: camera.buffer().as_entire_binding(), - }], - })); + camera_bind_group = Some(graphics.device.create_bind_group( + &wgpu::BindGroupDescriptor { + label: Some("runtime camera bind group"), + layout: &graphics.layouts.camera_bind_group_layout, + entries: &[wgpu::BindGroupEntry { + binding: 0, + resource: camera.buffer().as_entire_binding(), + }], + }, + )); match sky_texture_result { Ok(sky_texture) => { @@ -29,11 +29,11 @@ use eucalyptus_core::states::{Label, PROJECT}; use eucalyptus_core::ui::HUDComponent; use glam::{DVec3, Mat3, Mat4, Quat, Vec2, Vec3}; use hecs::Entity; +use kino_ui::WidgetTree; +use kino_ui::rendering::KinoRenderTargetId; use std::collections::HashMap; use winit::event::WindowEvent; use winit::event_loop::ActiveEventLoop; -use kino_ui::rendering::KinoRenderTargetId; -use kino_ui::{WidgetTree}; impl Scene for PlayMode { fn load(&mut self, graphics: Arc<SharedGraphicsContext>) { @@ -672,9 +672,15 @@ impl Scene for PlayMode { } let mut static_batches: HashMap<u64, Vec<InstanceRaw>> = HashMap::new(); - let mut animated_instances: Vec< - (Entity, u64, InstanceRaw, wgpu::Buffer, wgpu::Buffer, wgpu::Buffer, u32), - > = Vec::new(); + let mut animated_instances: Vec<( + Entity, + u64, + InstanceRaw, + wgpu::Buffer, + wgpu::Buffer, + wgpu::Buffer, + u32, + )> = Vec::new(); { let mut query = self @@ -769,50 +775,47 @@ impl Scene for PlayMode { } let registry = ASSET_REGISTRY.read(); - { - let mut render_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor { - label: Some("light cube render pass"), - color_attachments: &[Some(wgpu::RenderPassColorAttachment { - view: hdr.render_view(), - depth_slice: None, - resolve_target: hdr.resolve_target(), - ops: wgpu::Operations { - load: wgpu::LoadOp::Load, - store: wgpu::StoreOp::Store, - }, - })], - depth_stencil_attachment: Some(wgpu::RenderPassDepthStencilAttachment { - view: &graphics.depth_texture.view, - depth_ops: Some(wgpu::Operations { - load: wgpu::LoadOp::Load, - store: wgpu::StoreOp::Store, - }), - stencil_ops: None, - }), - occlusion_query_set: None, - timestamp_writes: None, - }); - if let Some(light_pipeline) = &self.light_cube_pipeline { - render_pass.set_pipeline(light_pipeline.pipeline()); - for light in &lights { - render_pass.set_vertex_buffer(1, light.instance_buffer.buffer().slice(..)); - if !light.component.visible { - continue; - } - - let Some(model) = registry.get_model(light.cube_model) else { - log_once::error_once!( - "Missing light cube model handle {} in registry", - light.cube_model.id - ); - continue; - }; + if let Some(light_pipeline) = &self.light_cube_pipeline { + if let Some(l) = lights.first() + && let Some(model) = registry.get_model(l.cube_model) + { + { + let mut render_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor { + label: Some("light cube render pass"), + color_attachments: &[Some(wgpu::RenderPassColorAttachment { + view: hdr.render_view(), + depth_slice: None, + resolve_target: hdr.resolve_target(), + ops: wgpu::Operations { + load: wgpu::LoadOp::Load, + store: wgpu::StoreOp::Store, + }, + })], + depth_stencil_attachment: Some(wgpu::RenderPassDepthStencilAttachment { + view: &graphics.depth_texture.view, + depth_ops: Some(wgpu::Operations { + load: wgpu::LoadOp::Load, + store: wgpu::StoreOp::Store, + }), + stencil_ops: None, + }), + occlusion_query_set: None, + timestamp_writes: None, + }); - render_pass.draw_light_model(model, camera_bind_group, &light.bind_group); + render_pass.set_pipeline(light_pipeline.pipeline()); + for light in &lights { + render_pass.set_vertex_buffer(1, light.instance_buffer.buffer().slice(..)); + if !light.component.visible { + continue; + } + render_pass.draw_light_model(&model, camera_bind_group, &light.bind_group); + } } + } else { + log_once::error_once!("Missing light cube model handle in registry",); } } - let default_skinning_buffer = self .default_skinning_buffer .as_ref() @@ -838,7 +841,7 @@ impl Scene for PlayMode { return; }; log_once::debug_once!("Pipeline ready"); - + for (model, handle, instance_count) in prepared_models { let morph_deltas_buffer = model .morph_deltas_buffer @@ -886,8 +889,7 @@ impl Scene for PlayMode { }); render_pass.set_pipeline(pipeline.pipeline()); if let Some(instance_buffer) = self.instance_buffer_cache.get(&handle) { - render_pass - .set_vertex_buffer(1, instance_buffer.slice(instance_count as usize)); + render_pass.set_vertex_buffer(1, instance_buffer.slice(instance_count as usize)); } else { continue; } @@ -921,9 +923,11 @@ impl Scene for PlayMode { _padding: Default::default(), }; - graphics - .queue - .write_buffer(default_morph_info_buffer, 0, bytemuck::bytes_of(&info)); + graphics.queue.write_buffer( + default_morph_info_buffer, + 0, + bytemuck::bytes_of(&info), + ); let material = &model.materials[mesh.material]; render_pass.draw_mesh_instanced( @@ -975,17 +979,17 @@ impl Scene for PlayMode { .expect("Per-frame bind group not initialised") .clone(); - let instance_buffer = self - .animated_instance_buffers - .entry(entity) - .or_insert_with(|| { - ResizableBuffer::new( - &graphics.device, - 1, - wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST, - "Runtime Animated Instance Buffer", - ) - }); + let instance_buffer = + self.animated_instance_buffers + .entry(entity) + .or_insert_with(|| { + ResizableBuffer::new( + &graphics.device, + 1, + wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST, + "Runtime Animated Instance Buffer", + ) + }); instance_buffer.write(&graphics.device, &graphics.queue, &[instance]); let mut render_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor { @@ -1202,12 +1206,9 @@ impl Scene for PlayMode { { // 1. render billboards and prepare views if let Some(kino) = &mut self.kino { - let mut kino_encoder = CommandEncoder::new(graphics.clone(), Some("kino billboard encoder")); - kino.render_billboard_targets( - &graphics.device, - &graphics.queue, - &mut kino_encoder, - ); + let mut kino_encoder = + CommandEncoder::new(graphics.clone(), Some("kino billboard encoder")); + kino.render_billboard_targets(&graphics.device, &graphics.queue, &mut kino_encoder); if let Err(e) = kino_encoder.submit() { log_once::error_once!("Unable to submit billboard kino pass: {}", e); @@ -1234,8 +1235,7 @@ impl Scene for PlayMode { let mut query = self .world .query::<(Entity, &BillboardComponent, &EntityTransform)>(); - for (entity, billboard, entity_transform) in query.iter() - { + for (entity, billboard, entity_transform) in query.iter() { if !billboard.enabled { continue; } @@ -1274,7 +1274,8 @@ impl Scene for PlayMode { } }; - let transform = Mat4::from_scale_rotation_translation(scale, rotation, position); + let transform = + Mat4::from_scale_rotation_translation(scale, rotation, position); billboards.push((transform, texture_view)); } @@ -308,7 +308,10 @@ typedef struct NVector2 { typedef struct NMaterial { const char* name; uint64_t diffuse_texture; - uint64_t normal_texture; + const uint64_t* normal_texture; + const uint64_t* emissive_texture; + const uint64_t* metallic_roughness_texture; + const uint64_t* occlusion_texture; NVector4 tint; NVector3 emissive_factor; float metallic_factor; @@ -318,9 +321,6 @@ typedef struct NMaterial { float occlusion_strength; float normal_scale; NVector2 uv_tiling; - const uint64_t* emissive_texture; - const uint64_t* metallic_roughness_texture; - const uint64_t* occlusion_texture; } NMaterial; typedef struct NMaterialArray {