tirbofish/dropbear · commit
aa5d3a33e6536dd92db9cbd864b25709f078faba
fix: bug with padding of MaterialUniform
Signature present but could not be verified.
Unverified
@@ -1,6 +1,6 @@ use crate::graphics::SharedGraphicsContext; use crate::model::Model; -use crate::texture::Texture; +use crate::texture::{Texture, TextureBuilder}; use crate::utils::ResourceReference; use parking_lot::RwLock; use std::collections::HashMap; @@ -219,15 +219,12 @@ impl AssetRegistry { return handle; } - let texture = Texture::from_bytes_verbose_mipmapped_with_format( - graphics, - &rgba, - Some((1, 1)), - None, - None, - format, - Some(label.as_str()), - ); + let texture = TextureBuilder::new(&graphics.device) + .with_raw_pixels(graphics.clone(), &rgba) + .size(1, 1) + .format(format) + .label(label.as_str()) + .build(); self.add_texture_with_label(label, texture) } @@ -1,8 +1,9 @@ use crate::asset::{AssetRegistry, Handle}; use crate::buffer::UniformBuffer; +use crate::texture::TextureBuilder; use crate::{ graphics::SharedGraphicsContext, - texture::{Texture, TextureBuilder, TextureWrapMode}, + texture::{Texture, TextureWrapMode}, utils::ResourceReference, }; use gltf::image::{Format, Source}; @@ -160,89 +161,81 @@ pub enum ChannelValues { } impl Material { - pub fn new( + fn build_bind_group( registry: &mut AssetRegistry, - graphics: Arc<SharedGraphicsContext>, - name: impl Into<String>, + graphics: &Arc<SharedGraphicsContext>, + tint_buffer: &UniformBuffer<MaterialUniform>, 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 { - puffin::profile_function!(); - let name = name.into(); - - let uv_tiling = [1.0, 1.0]; - let uniform = MaterialUniform { - base_colour: tint, - emissive: [0.0, 0.0, 0.0], - emissive_strength: 1.0, - metallic: 1.0, - roughness: 1.0, - normal_scale: 1.0, - occlusion_strength: 1.0, - alpha_cutoff: 0.5, - uv_tiling, - 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, - }; + ) -> wgpu::BindGroup { + let d = registry + .get_texture(diffuse_texture) + .expect("diffuse texture handle missing from registry"); - let d = registry.get_texture(diffuse_texture).unwrap(); - let n = if let Some(normal) = normal_texture { - registry.get_texture(normal).unwrap() + registry + .get_texture(normal) + .expect("normal texture handle missing from registry") } 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() + registry + .get_texture(flat_normal_texture) + .expect("flat normal fallback texture missing from registry") }; - let e = if let Some(e) = emissive_texture { - registry.get_texture(e).unwrap() + let e = if let Some(emissive) = emissive_texture { + registry + .get_texture(emissive) + .expect("emissive texture handle missing from registry") } else { - let e = registry.solid_texture_rgba8( + let emissive = registry.solid_texture_rgba8( graphics.clone(), [0, 0, 0, 255], Some(Texture::TEXTURE_FORMAT_BASE), ); - registry.get_texture(e).unwrap() + registry + .get_texture(emissive) + .expect("emissive fallback texture missing from registry") }; - let mr = if let Some(m) = metallic_roughness_texture { - registry.get_texture(m).unwrap() + let mr = if let Some(metallic_roughness) = metallic_roughness_texture { + registry + .get_texture(metallic_roughness) + .expect("metallic roughness texture handle missing from registry") } else { - let metallic_roughness_texture = registry.solid_texture_rgba8( + let metallic_roughness = registry.solid_texture_rgba8( graphics.clone(), [0, 128, 0, 255], Some(Texture::TEXTURE_FORMAT_BASE), ); - registry.get_texture(metallic_roughness_texture).unwrap() + registry + .get_texture(metallic_roughness) + .expect("metallic roughness fallback texture missing from registry") }; - let o = if let Some(o) = occlusion_texture { - registry.get_texture(o).unwrap() + let o = if let Some(occlusion) = occlusion_texture { + registry + .get_texture(occlusion) + .expect("occlusion texture handle missing from registry") } else { - let o = registry.solid_texture_rgba8( + let occlusion = registry.solid_texture_rgba8( graphics.clone(), [255, 255, 255, 255], Some(Texture::TEXTURE_FORMAT_BASE), ); - registry.get_texture(o).unwrap() + registry + .get_texture(occlusion) + .expect("occlusion fallback texture missing from registry") }; - let tint_buffer = UniformBuffer::new(&graphics.device, "material_tint_uniform"); - tint_buffer.write(&graphics.queue, &uniform); - - let bind_group = graphics.device.create_bind_group(&wgpu::BindGroupDescriptor { + graphics.device.create_bind_group(&wgpu::BindGroupDescriptor { label: Some("material bind group"), layout: &graphics.layouts.material_bind_layout, entries: &[ @@ -276,15 +269,11 @@ impl Material { }, wgpu::BindGroupEntry { binding: 7, - resource: wgpu::BindingResource::TextureView( - &mr.view, - ), + resource: wgpu::BindingResource::TextureView(&mr.view), }, wgpu::BindGroupEntry { binding: 8, - resource: wgpu::BindingResource::Sampler( - &mr.sampler, - ), + resource: wgpu::BindingResource::Sampler(&mr.sampler), }, wgpu::BindGroupEntry { binding: 9, @@ -295,7 +284,55 @@ impl Material { resource: wgpu::BindingResource::Sampler(&o.sampler), }, ], - }); + }) + } + + pub fn new( + registry: &mut AssetRegistry, + graphics: Arc<SharedGraphicsContext>, + name: impl Into<String>, + 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 { + puffin::profile_function!(); + let name = name.into(); + + let uv_tiling = [1.0, 1.0]; + let uniform = MaterialUniform { + base_colour: tint, + emissive: [0.0, 0.0, 0.0], + emissive_strength: 1.0, + metallic: 1.0, + roughness: 1.0, + normal_scale: 1.0, + occlusion_strength: 1.0, + alpha_cutoff: 0.5, + uv_tiling, + 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, + _pad: 0, + }; + + let tint_buffer = UniformBuffer::new(&graphics.device, "material_tint_uniform"); + tint_buffer.write(&graphics.queue, &uniform); + let bind_group = Self::build_bind_group( + registry, + &graphics, + &tint_buffer, + diffuse_texture, + normal_texture, + emissive_texture, + metallic_roughness_texture, + occlusion_texture, + ); Self { name, @@ -337,10 +374,28 @@ impl Material { has_occlusion_texture: self.occlusion_texture.is_some() as u32, pad: 0, has_normal_texture: self.normal_texture.is_some() as u32, + _pad: 0, }; self.tint_buffer.write(&graphics.queue, &uniform); } + + pub fn rebuild_bind_group( + &mut self, + registry: &mut AssetRegistry, + graphics: &Arc<SharedGraphicsContext>, + ) { + self.bind_group = Self::build_bind_group( + registry, + graphics, + &self.tint_buffer, + self.diffuse_texture, + self.normal_texture, + self.emissive_texture, + self.metallic_roughness_texture, + self.occlusion_texture, + ); + } } #[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize, Archive, rkyv::Serialize, rkyv::Deserialize)] @@ -1139,20 +1194,16 @@ impl Model { let material_name = processed.name; 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) .with_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) + .sampler(tex.sampler) .label(material_name.as_str()); - if let Some(ref mime) = tex.mime_type { - builder = builder.mime_type(mime.as_str()); + if let Some(mime) = tex.mime_type { + builder = builder.mime_type(&mime); } + builder.build() }; @@ -1665,6 +1716,7 @@ pub struct MaterialUniform { pub normal_scale: f32, pub occlusion_strength: f32, pub alpha_cutoff: f32, + pub _pad: u32, pub uv_tiling: [f32; 2], pub has_normal_texture: u32, pub has_emissive_texture: u32, @@ -1,5 +1,3 @@ -// Main shaders for standard objects. - struct Globals { num_lights: u32, ambient_strength: f32, @@ -187,7 +185,7 @@ fn vs_main( u_morph_info.uses_morph != 0u, ); let world_position = model_matrix * skin_matrix * vec4<f32>(morphed_pos, 1.0); - + let skin_normal = (skin_matrix * vec4<f32>(model.normal, 0.0)).xyz; let skin_tangent = (skin_matrix * vec4<f32>(model.tangent.xyz, 0.0)).xyz; @@ -278,9 +276,10 @@ fn apply_normal_map( world_normal_in: vec3<f32>, world_tangent_in: vec3<f32>, world_bitangent_in: vec3<f32>, - normal_sample_rgb: vec3<f32>, + normal_sample_raw: vec3<f32>, + normal_scale: f32, ) -> vec3<f32> { - let normal_ts = normalize(normal_sample_rgb * 2.0 - vec3<f32>(1.0)); + let unpacked = normalize((normal_sample_raw * 2.0 - 1.0) * vec3<f32>(normal_scale, normal_scale, 1.0)); let n = normalize(world_normal_in); var t = normalize(world_tangent_in); @@ -291,7 +290,11 @@ fn apply_normal_map( let b = cross(n, t) * handedness; let tbn = mat3x3<f32>(t, b, n); - return normalize(tbn * normal_ts); + return normalize(tbn * unpacked); +} + +fn fresnel_schlick(cos_theta: f32, f0: vec3<f32>) -> vec3<f32> { + return f0 + (vec3<f32>(1.0) - f0) * pow(clamp(1.0 - cos_theta, 0.0, 1.0), 5.0); } @fragment @@ -307,13 +310,13 @@ fn s_fs_main(in: VertexOutput) -> @location(0) vec4<f32> { var world_normal: vec3<f32>; if (u_material.has_normal_texture != 0u) { - let object_normal = textureSample(t_normal, s_normal, uv).xyz; - let scaled_normal = normalize((object_normal * 2.0 - 1.0) * vec3<f32>(u_material.normal_scale, u_material.normal_scale, 1.0)); + let normal_sample_raw = textureSample(t_normal, s_normal, uv).xyz; world_normal = apply_normal_map( in.world_normal, in.world_tangent, in.world_bitangent, - scaled_normal, + normal_sample_raw, + u_material.normal_scale, ); } else { world_normal = normalize(in.world_normal); @@ -340,6 +343,7 @@ fn s_fs_main(in: VertexOutput) -> @location(0) vec4<f32> { } let view_dir = normalize(u_camera.view_pos.xyz - in.world_position); + let ambient = vec3<f32>(1.0) * u_globals.ambient_strength * base_colour.rgb * occlusion; var final_color = ambient; @@ -356,14 +360,16 @@ fn s_fs_main(in: VertexOutput) -> @location(0) vec4<f32> { } } - final_color *= occlusion; - let world_reflect = reflect(-view_dir, world_normal); let reflection = textureSample(env_map, env_sampler, world_reflect).rgb; - let shininess = (1.0 - roughness) * metallic; - final_color += reflection * shininess; + let n_dot_v = max(dot(world_normal, view_dir), 0.0); + let f0 = mix(vec3<f32>(0.04), base_colour.rgb, metallic); + let fresnel = fresnel_schlick(n_dot_v, f0); + let reflection_weight = fresnel * (1.0 - roughness) * mix(vec3<f32>(1.0), base_colour.rgb, metallic); + final_color += reflection * reflection_weight; final_color += emissive; + // return vec4<f32>(uv.x, uv.y, 0.0, 1.0); return vec4<f32>(final_color, base_colour.a); } @@ -2,11 +2,11 @@ use std::sync::Arc; use crate::asset::AssetRegistry; use crate::graphics::SharedGraphicsContext; -use crate::utils::{ResourceReference, ToPotentialString}; +use crate::utils::{ResourceReference}; use image::{DynamicImage, GenericImageView, RgbaImage}; use rkyv::Archive; use serde::{Deserialize, Serialize}; -use wgpu::{TextureAspect, TextureFormat, TextureUsages, TextureViewDescriptor, TextureViewDimension}; +use wgpu::{SamplerDescriptor, TextureAspect, TextureFormat, TextureUsages, TextureViewDescriptor, TextureViewDimension}; use crate::multisampling::{AntiAliasingMode}; /// Describes a texture, like an image of some sort. Can be a normal texture on a model or a viewport or depth texture. @@ -162,9 +162,9 @@ impl<'a> TextureBuilder<'a> { mip_level_count: 1, auto_mip: false, mag_filter: wgpu::FilterMode::Linear, - min_filter: wgpu::FilterMode::Nearest, - mipmap_filter: wgpu::FilterMode::Nearest, - wrap_mode: TextureWrapMode::Clamp, + min_filter: wgpu::FilterMode::Linear, + mipmap_filter: wgpu::FilterMode::Linear, + wrap_mode: TextureWrapMode::Repeat, compare: None, lod_min_clamp: 0.0, lod_max_clamp: 32.0, @@ -247,6 +247,16 @@ impl<'a> TextureBuilder<'a> { self } + pub fn sampler(mut self, sampler: SamplerDescriptor) -> Self { + self.wrap_mode = sampler.address_mode_u.into(); + self.mag_filter = sampler.mag_filter; + self.min_filter = sampler.min_filter; + self.compare = sampler.compare; + self.lod_max_clamp = sampler.lod_max_clamp; + self.lod_min_clamp = sampler.lod_min_clamp; + self + } + pub fn with_bytes(mut self, graphics: Arc<SharedGraphicsContext>, bytes: &'a [u8]) -> Self { self.graphics = Some(graphics); let hash = AssetRegistry::hash_bytes(bytes); @@ -615,366 +625,6 @@ 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(crate) fn from_bytes_verbose_mipmapped_with_format( - graphics: Arc<SharedGraphicsContext>, - bytes: &[u8], - dimensions: Option<(u32, u32)>, - view_descriptor: Option<wgpu::TextureViewDescriptor>, - sampler: Option<wgpu::SamplerDescriptor>, - format: wgpu::TextureFormat, - label: Option<&str>, - ) -> Self { - puffin::profile_function!(label.unwrap_or("")); - let texture = Self::from_bytes_verbose_with_format( - graphics.clone(), - bytes, - dimensions, - Some(format), - view_descriptor, - sampler, - label, - ); - - if let Err(err) = - graphics - .mipmapper - .compute_mipmaps(&graphics.device, &graphics.queue, &texture) - { - log_once::warn_once!("Failed to generate mipmaps: {}", err); - } - - texture - } - - fn from_bytes_verbose_with_format( - graphics: Arc<SharedGraphicsContext>, - bytes: &[u8], - dimensions: Option<(u32, u32)>, - format_override: Option<wgpu::TextureFormat>, - view_descriptor: Option<wgpu::TextureViewDescriptor>, - sampler: Option<wgpu::SamplerDescriptor>, - label: Option<&str>, - ) -> Self { - puffin::profile_function!(label.unwrap_or("")); - if let Some(l) = label { - log::debug!("Loading texture: {l}"); - } - - let hash = AssetRegistry::hash_bytes(bytes); - - let (diffuse_rgba, dimensions) = { - puffin::profile_scope!("load from memory image"); - match image::load_from_memory(bytes) { - Ok(image) => { - let rgba = image.to_rgba8().into_raw(); - let dims = dimensions.unwrap_or_else(|| image.dimensions()); - (rgba, dims) - } - Err(err) => { - if let Some(dims) = 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.", - 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.", - 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 = size.width.min(size.height).ilog2() + 1; - log::debug!("Mip level count [{:?}]: {}", label, mip_level_count); - - let texture_format = format_override.unwrap_or(Texture::TEXTURE_FORMAT); - let texture = graphics.device.create_texture(&wgpu::TextureDescriptor { - label: Some(format!("{:?} diffuse blit texture", label).as_str()), - size, - mip_level_count, - sample_count: 1, - dimension: wgpu::TextureDimension::D2, - format: texture_format, - usage: wgpu::TextureUsages::TEXTURE_BINDING - | wgpu::TextureUsages::RENDER_ATTACHMENT - | wgpu::TextureUsages::COPY_DST - | wgpu::TextureUsages::COPY_SRC, - view_formats: &[], - }); - - let unpadded_bytes_per_row = 4 * 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); - debug_assert!(diffuse_rgba.len() >= (unpadded_bytes_per_row * size.height) as usize); - - if padded_bytes_per_row == unpadded_bytes_per_row { - puffin::profile_scope!("write to texture"); - graphics.queue.write_texture( - wgpu::TexelCopyTextureInfo { - texture: &texture, - mip_level: 0, - origin: wgpu::Origin3d::ZERO, - aspect: wgpu::TextureAspect::All, - }, - &diffuse_rgba, - wgpu::TexelCopyBufferLayout { - offset: 0, - bytes_per_row: Some(unpadded_bytes_per_row), - rows_per_image: Some(size.height), - }, - size, - ); - } else { - puffin::profile_scope!("write to texture"); - 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(&diffuse_rgba[src_start..src_start + src_stride]); - } - - graphics.queue.write_texture( - wgpu::TexelCopyTextureInfo { - texture: &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, - ); - } - - let sampler_desc = if let Some(sampler) = sampler { - sampler - } else { - wgpu::SamplerDescriptor { - address_mode_u: wgpu::AddressMode::ClampToEdge, - address_mode_v: wgpu::AddressMode::ClampToEdge, - address_mode_w: wgpu::AddressMode::ClampToEdge, - mag_filter: wgpu::FilterMode::Linear, - min_filter: wgpu::FilterMode::Nearest, - mipmap_filter: wgpu::FilterMode::Linear, - ..Default::default() - } - }; - - let sampler = graphics.device.create_sampler(&sampler_desc); - - let view = texture.create_view(&view_descriptor.unwrap_or_default()); - - Self { - label: label.to_potential_string(), - texture, - sampler, - size, - view, - hash: Some(hash), - reference: Some(ResourceReference::from_bytes(bytes)), - } - } - - pub fn from_raw_pixels_mipmapped_with_format( - graphics: Arc<SharedGraphicsContext>, - pixels: &[u8], - dimensions: (u32, u32), - format: wgpu::TextureFormat, - sampler: Option<wgpu::SamplerDescriptor>, - label: Option<&str>, - _mime_type: Option<&str>, - ) -> Self { - puffin::profile_function!(label.unwrap_or("")); - if let Some(l) = label { - log::debug!("Loading texture: {l}"); - } - - let hash = AssetRegistry::hash_bytes(pixels); - - let bytes_per_pixel = match format.block_copy_size(None) { - Some(value) => value, - None => { - log::error!( - "Texture [{:?}] has unsupported format {:?}; falling back to 1x1 magenta.", - label, - format - ); - return Self::from_bytes_verbose_mipmapped_with_format( - graphics, - &[255, 0, 255, 255], - Some((1, 1)), - None, - sampler, - Texture::TEXTURE_FORMAT, - label, - ); - } - }; - - 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 { - log::error!( - "Texture [{:?}] byte length {} does not match expected {} for {:?} ({}x{}). Falling back to 1x1 magenta.", - label, - pixels.len(), - expected_len, - format, - dimensions.0, - dimensions.1 - ); - return Self::from_bytes_verbose_mipmapped_with_format( - graphics, - &[255, 0, 255, 255], - Some((1, 1)), - None, - sampler, - Texture::TEXTURE_FORMAT, - label, - ); - } - - let size = wgpu::Extent3d { - width: dimensions.0, - height: dimensions.1, - depth_or_array_layers: 1, - }; - - let mip_level_count = size.width.min(size.height).ilog2() + 1; - log::debug!("Mip level count [{:?}]: {}", label, mip_level_count); - - let texture = graphics.device.create_texture(&wgpu::TextureDescriptor { - label: Some(format!("{:?} raw texture", label).as_str()), - size, - mip_level_count, - sample_count: 1, - dimension: wgpu::TextureDimension::D2, - format, - usage: wgpu::TextureUsages::TEXTURE_BINDING - | wgpu::TextureUsages::RENDER_ATTACHMENT - | wgpu::TextureUsages::COPY_DST - | wgpu::TextureUsages::COPY_SRC, - view_formats: &[], - }); - - 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 { - puffin::profile_scope!("write to texture"); - graphics.queue.write_texture( - wgpu::TexelCopyTextureInfo { - texture: &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 { - puffin::profile_scope!("write to texture"); - 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]); - } - - graphics.queue.write_texture( - wgpu::TexelCopyTextureInfo { - texture: &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, - ); - } - - let sampler_desc = sampler.unwrap_or(wgpu::SamplerDescriptor { - address_mode_u: wgpu::AddressMode::ClampToEdge, - address_mode_v: wgpu::AddressMode::ClampToEdge, - address_mode_w: wgpu::AddressMode::ClampToEdge, - mag_filter: wgpu::FilterMode::Linear, - min_filter: wgpu::FilterMode::Nearest, - mipmap_filter: wgpu::FilterMode::Linear, - ..Default::default() - }); - - let sampler = graphics.device.create_sampler(&sampler_desc); - let view = texture.create_view(&wgpu::TextureViewDescriptor::default()); - - let texture = Self { - label: label.to_potential_string(), - texture, - sampler, - size, - view, - hash: Some(hash), - reference: Some(ResourceReference::from_bytes(pixels)), - }; - - if let Err(err) = - graphics - .mipmapper - .compute_mipmaps(&graphics.device, &graphics.queue, &texture) - { - log_once::warn_once!("Failed to generate mipmaps: {}", err); - } - - texture - } } #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize, Archive, rkyv::Serialize, rkyv::Deserialize)] @@ -998,6 +648,17 @@ impl Into<wgpu::AddressMode> for TextureWrapMode { } } +impl Into<TextureWrapMode> for wgpu::AddressMode { + fn into(self) -> TextureWrapMode { + match self { + wgpu::AddressMode::ClampToEdge => TextureWrapMode::Clamp, + wgpu::AddressMode::Repeat => TextureWrapMode::Repeat, + wgpu::AddressMode::MirrorRepeat => TextureWrapMode::Repeat, + wgpu::AddressMode::ClampToBorder => TextureWrapMode::Clamp, + } + } +} + pub struct DropbearEngineLogo; impl DropbearEngineLogo { @@ -678,6 +678,12 @@ impl Component for MeshRenderer { if let Some(tex) = get_tex_handle(&m.metallic_roughness_texture).await { mat.metallic_roughness_texture = Some(tex?); } + + { + let mut registry = ASSET_REGISTRY.write(); + mat.rebuild_bind_group(&mut registry, &graphics); + } + mat.sync_uniform(&graphics); } } @@ -1398,6 +1404,10 @@ impl InspectableComponent for MeshRenderer { material.uv_tiling = default.uv_tiling; material.texture_tag = default.texture_tag.clone(); material.wrap_mode = default.wrap_mode; + { + let mut registry = ASSET_REGISTRY.write(); + material.rebuild_bind_group(&mut registry, &graphics); + } material.sync_uniform(&graphics); } } @@ -1730,6 +1740,11 @@ impl InspectableComponent for MeshRenderer { ) { if let Some(tex) = new_diffuse { material.diffuse_texture = tex; + { + let mut registry = ASSET_REGISTRY.write(); + material.rebuild_bind_group(&mut registry, &graphics); + } + material.sync_uniform(&graphics); } } @@ -1743,6 +1758,11 @@ impl InspectableComponent for MeshRenderer { ) { if let Some(tex) = new_normal { material.normal_texture = Some(tex); + { + let mut registry = ASSET_REGISTRY.write(); + material.rebuild_bind_group(&mut registry, &graphics); + } + material.sync_uniform(&graphics); } } @@ -1755,6 +1775,11 @@ impl InspectableComponent for MeshRenderer { default_emissive, ) { material.emissive_texture = new_emissive; + { + let mut registry = ASSET_REGISTRY.write(); + material.rebuild_bind_group(&mut registry, &graphics); + } + material.sync_uniform(&graphics); } if let Some(new_mr) = texture_combo( @@ -1766,6 +1791,11 @@ impl InspectableComponent for MeshRenderer { default_mr, ) { material.metallic_roughness_texture = new_mr; + { + let mut registry = ASSET_REGISTRY.write(); + material.rebuild_bind_group(&mut registry, &graphics); + } + material.sync_uniform(&graphics); } if let Some(new_occ) = texture_combo( @@ -1777,6 +1807,11 @@ impl InspectableComponent for MeshRenderer { default_occ, ) { material.occlusion_texture = new_occ; + { + let mut registry = ASSET_REGISTRY.write(); + material.rebuild_bind_group(&mut registry, &graphics); + } + material.sync_uniform(&graphics); } }); } @@ -195,6 +195,8 @@ impl ProjectConfig { e ); + log::error!("{}", msg); + let should_recover = rfd::MessageDialog::new() .set_title("Scene loading error") .set_description(&msg) @@ -480,7 +480,7 @@ impl Scene for Editor { globals.write(&graphics.queue); } - let mut static_batches: HashMap<u64, Vec<InstanceRaw>> = HashMap::new(); + let mut static_batches: HashMap<u64, Vec<(Entity, InstanceRaw)>> = HashMap::new(); let mut animated_instances: Vec< (Entity, u64, InstanceRaw, wgpu::Buffer, wgpu::Buffer, wgpu::Buffer, u32), > = Vec::new(); @@ -503,7 +503,10 @@ impl Scene for Editor { let has_skinning = !animation.skinning_matrices.is_empty(); let has_morph_weights = !animation.morph_weights.is_empty(); if !has_skinning && !has_morph_weights { - static_batches.entry(handle.id).or_default().push(instance); + static_batches + .entry(handle.id) + .or_default() + .push((entity, instance)); continue; } @@ -518,12 +521,18 @@ impl Scene for Editor { } else if !has_skinning { let Some(default_skinning_buffer) = self.default_skinning_buffer.as_ref() else { - static_batches.entry(handle.id).or_default().push(instance); + static_batches + .entry(handle.id) + .or_default() + .push((entity, instance)); continue; }; default_skinning_buffer.clone() } else { - static_batches.entry(handle.id).or_default().push(instance); + static_batches + .entry(handle.id) + .or_default() + .push((entity, instance)); continue; }; let Some(morph_weights_buffer) = animation @@ -531,7 +540,10 @@ impl Scene for Editor { .as_ref() .map(|buffer| buffer.buffer().clone()) else { - static_batches.entry(handle.id).or_default().push(instance); + static_batches + .entry(handle.id) + .or_default() + .push((entity, instance)); continue; }; let Some(morph_info_buffer) = animation @@ -539,7 +551,10 @@ impl Scene for Editor { .as_ref() .map(|buffer| buffer.buffer().clone()) else { - static_batches.entry(handle.id).or_default().push(instance); + static_batches + .entry(handle.id) + .or_default() + .push((entity, instance)); continue; }; @@ -553,20 +568,29 @@ impl Scene for Editor { animation.morph_weight_count, )); } else { - static_batches.entry(handle.id).or_default().push(instance); + static_batches + .entry(handle.id) + .or_default() + .push((entity, instance)); } } } let registry = ASSET_REGISTRY.read(); let mut prepared_models = Vec::new(); - for (handle, instances) in static_batches { + for (handle, batched_instances) in static_batches { puffin::profile_scope!("preparing models"); let Some(model) = registry.get_model(Handle::new(handle)) else { log_once::error_once!("Missing model handle {} in registry", handle); continue; }; + let entity = batched_instances.first().map(|(entity, _)| *entity); + let instances: Vec<InstanceRaw> = batched_instances + .into_iter() + .map(|(_, instance)| instance) + .collect(); + let instance_buffer = self.instance_buffer_cache.entry(handle).or_insert_with(|| { ResizableBuffer::new( &graphics.device, @@ -577,7 +601,7 @@ impl Scene for Editor { }); instance_buffer.write(&graphics.device, &graphics.queue, &instances); - prepared_models.push((model, handle, instances.len() as u32)); + prepared_models.push((model, handle, instances.len() as u32, entity)); } if let Some(light_pipeline) = &self.light_cube_pipeline { @@ -640,7 +664,13 @@ impl Scene for Editor { // static models if let Some(_) = &self.light_cube_pipeline { puffin::profile_scope!("model render pass"); - for (model, handle, instance_count) in prepared_models { + for (model, handle, instance_count, entity) in prepared_models { + let Some(entity) = entity else { + continue; + }; + let Ok(renderer) = self.world.get::<&MeshRenderer>(entity) else { + continue; + }; let default_skinning_buffer = self .default_skinning_buffer .as_ref() @@ -740,6 +770,12 @@ impl Scene for Editor { .write_buffer(default_morph_info_buffer, 0, bytemuck::bytes_of(&info)); let material = &model.materials[mesh.material]; + let material = if let Some(mat) = renderer.material_snapshot.get(&material.name) { + mat + } else { + log_once::warn_once!("Unable to locate MeshRenderer's material_snapshot for that specific material"); + material + }; render_pass.draw_mesh_instanced( mesh, material, @@ -765,6 +801,9 @@ impl Scene for Editor { morph_weight_count, ) in animated_instances { + let Ok(renderer) = self.world.get::<&MeshRenderer>(entity) else { + continue; + }; puffin::profile_scope!("rendering animated model", format!("{:?}", entity)); let Some(model) = registry.get_model(Handle::new(handle)) else { log_once::error_once!("Missing model handle {} in registry", handle); @@ -848,6 +887,12 @@ impl Scene for Editor { .write_buffer(&morph_info_buffer, 0, bytemuck::bytes_of(&info)); let material = &model.materials[mesh.material]; + let material = if let Some(mat) = renderer.material_snapshot.get(&material.name) { + mat + } else { + log_once::warn_once!("Unable to locate MeshRenderer's material_snapshot for that specific material"); + material + }; render_pass.draw_mesh_instanced( mesh, material,