tirbofish/dropbear · commit
69c2f901cb7d5013f36c142f5c35a66e716a42e2
feature: implement reflections, improved model textures.
for some reason, collider wireframe doesnt render and i deadass dont know why.
Signature present but could not be verified.
Unverified
@@ -0,0 +1,124 @@ +use crate::buffer::UniformBuffer; +use crate::graphics::SharedGraphicsContext; +use crate::pipelines::globals::Globals; +use wgpu::{BindGroup, Buffer}; + +/// Bind groups for @group(0) +pub struct SceneGlobalsBindGroup { + pub bind_group: BindGroup, +} + +impl SceneGlobalsBindGroup { + pub fn new( + graphics: &SharedGraphicsContext, + globals_buffer: &UniformBuffer<Globals>, + camera_buffer: &Buffer, + ) -> Self { + let bind_group = graphics + .device + .create_bind_group(&wgpu::BindGroupDescriptor { + label: Some("scene globals+camera bind group"), + layout: &graphics.layouts.scene_globals_bind_group_layout, + entries: &[ + wgpu::BindGroupEntry { + binding: 0, + resource: globals_buffer.buffer().as_entire_binding(), + }, + wgpu::BindGroupEntry { + binding: 1, + resource: camera_buffer.as_entire_binding(), + }, + ], + }); + + Self { bind_group } + } + + pub fn update( + &mut self, + graphics: &SharedGraphicsContext, + globals_buffer: &UniformBuffer<Globals>, + camera_buffer: &Buffer, + ) { + self.bind_group = graphics + .device + .create_bind_group(&wgpu::BindGroupDescriptor { + label: Some("scene globals+camera bind group"), + layout: &graphics.layouts.scene_globals_bind_group_layout, + entries: &[ + wgpu::BindGroupEntry { + binding: 0, + resource: globals_buffer.buffer().as_entire_binding(), + }, + wgpu::BindGroupEntry { + binding: 1, + resource: camera_buffer.as_entire_binding(), + }, + ], + }); + } + + pub fn as_ref(&self) -> &BindGroup { + &self.bind_group + } +} + +/// Bind group for @group(2) +pub struct LightSkinBindGroup { + pub bind_group: BindGroup, +} + +impl LightSkinBindGroup { + pub fn new( + graphics: &SharedGraphicsContext, + light_buffer: &Buffer, + skinning_buffer: &Buffer, + ) -> Self { + let bind_group = graphics + .device + .create_bind_group(&wgpu::BindGroupDescriptor { + label: Some("scene light+skin bind group"), + layout: &graphics.layouts.scene_light_skin_bind_group_layout, + entries: &[ + wgpu::BindGroupEntry { + binding: 0, + resource: light_buffer.as_entire_binding(), + }, + wgpu::BindGroupEntry { + binding: 1, + resource: skinning_buffer.as_entire_binding(), + }, + ], + }); + + Self { bind_group } + } + + pub fn update( + &mut self, + graphics: &SharedGraphicsContext, + light_buffer: &Buffer, + skinning_buffer: &Buffer, + ) { + self.bind_group = graphics + .device + .create_bind_group(&wgpu::BindGroupDescriptor { + label: Some("scene light+skin bind group"), + layout: &graphics.layouts.scene_light_skin_bind_group_layout, + entries: &[ + wgpu::BindGroupEntry { + binding: 0, + resource: light_buffer.as_entire_binding(), + }, + wgpu::BindGroupEntry { + binding: 1, + resource: skinning_buffer.as_entire_binding(), + }, + ], + }); + } + + pub fn as_ref(&self) -> &BindGroup { + &self.bind_group + } +} @@ -1,6 +1,7 @@ pub mod animation; pub mod asset; pub mod attenuation; +pub mod bind_groups; pub mod buffer; pub mod camera; pub mod colour; @@ -5,7 +5,7 @@ use crate::{ texture::{Texture, TextureWrapMode}, utils::ResourceReference, }; -use gltf::image::Format; +use gltf::image::{Format, Source}; use gltf::texture::MinFilter; use parking_lot::RwLock; use puffin::profile_scope; @@ -329,6 +329,7 @@ pub enum AnimationInterpolation { struct GLTFTextureInformation { sampler: wgpu::SamplerDescriptor<'static>, pixels: Vec<u8>, + mime_type: Option<String>, width: u32, height: u32, #[allow(dead_code)] @@ -342,6 +343,11 @@ impl GLTFTextureInformation { puffin::profile_function!(); let sampler = tex.sampler(); + let mime = match tex.source().source() { + Source::View { mime_type, .. } => Some(mime_type.to_string()), + Source::Uri { mime_type, .. } => mime_type.map(|value| value.to_string()), + }; + let mag_filter = match sampler.mag_filter() { Some(gltf::texture::MagFilter::Nearest) => wgpu::FilterMode::Nearest, _ => wgpu::FilterMode::Linear, @@ -408,7 +414,26 @@ impl GLTFTextureInformation { (rgba, wgpu::TextureFormat::Rgba8Unorm) } Format::R8G8B8A8 => (image_data.pixels.clone(), wgpu::TextureFormat::Rgba8Unorm), - _ => panic!("Unsupported format"), + Format::R16 => (image_data.pixels.clone(), wgpu::TextureFormat::R16Unorm), + Format::R16G16 => (image_data.pixels.clone(), wgpu::TextureFormat::Rg16Unorm), + Format::R16G16B16 => { + let mut rgba = Vec::with_capacity(image_data.pixels.len() / 6 * 8); + for chunk in image_data.pixels.chunks(6) { + rgba.extend_from_slice(chunk); + rgba.extend_from_slice(&[255u8, 255u8]); + } + (rgba, wgpu::TextureFormat::Rgba16Unorm) + } + Format::R16G16B16A16 => (image_data.pixels.clone(), wgpu::TextureFormat::Rgba16Unorm), + Format::R32G32B32FLOAT => { + let mut rgba = Vec::with_capacity(image_data.pixels.len() / 12 * 16); + for chunk in image_data.pixels.chunks(12) { + rgba.extend_from_slice(chunk); + rgba.extend_from_slice(&1.0f32.to_ne_bytes()); + } + (rgba, wgpu::TextureFormat::Rgba32Float) + } + Format::R32G32B32A32FLOAT => (image_data.pixels.clone(), wgpu::TextureFormat::Rgba32Float), }; GLTFTextureInformation { @@ -418,6 +443,7 @@ impl GLTFTextureInformation { format, width, height, + mime_type: mime, } } } @@ -458,16 +484,11 @@ struct GLTFMaterialInformation { struct ProcessedMaterialTextures { name: String, - diffuse: Option<(Vec<u8>, (u32, u32))>, - normal: Option<(Vec<u8>, (u32, u32))>, - emissive: Option<(Vec<u8>, (u32, u32))>, - metallic_roughness: Option<(Vec<u8>, (u32, u32))>, - occlusion: Option<(Vec<u8>, (u32, u32))>, - diffuse_sampler: Option<wgpu::SamplerDescriptor<'static>>, - normal_sampler: Option<wgpu::SamplerDescriptor<'static>>, - emissive_sampler: Option<wgpu::SamplerDescriptor<'static>>, - metallic_roughness_sampler: Option<wgpu::SamplerDescriptor<'static>>, - occlusion_sampler: Option<wgpu::SamplerDescriptor<'static>>, + diffuse: Option<ProcessedTexture>, + normal: Option<ProcessedTexture>, + emissive: Option<ProcessedTexture>, + metallic_roughness: Option<ProcessedTexture>, + occlusion: Option<ProcessedTexture>, tint: [f32; 4], emissive_factor: [f32; 3], metallic_factor: f32, @@ -479,6 +500,14 @@ struct ProcessedMaterialTextures { normal_scale: f32, } +struct ProcessedTexture { + pixels: Vec<u8>, + dimensions: (u32, u32), + format: wgpu::TextureFormat, + sampler: wgpu::SamplerDescriptor<'static>, + mime_type: Option<String>, +} + impl Model { fn load_materials( gltf: &gltf::Document, @@ -954,28 +983,23 @@ impl Model { puffin::profile_scope!("processing material textures"); let material_name = material_info.name; - let extract = |info: Option<GLTFTextureInformation>| -> ( - Option<(Vec<u8>, (u32, u32))>, - Option<wgpu::SamplerDescriptor<'static>>, - ) { - if let Some(info) = info { - ( - Some((info.pixels, (info.width, info.height))), - Some(info.sampler), - ) - } else { - (None, None) - } - }; - - let (processed_diffuse, diffuse_sampler) = extract(material_info.diffuse_texture); - let (processed_normal, normal_sampler) = extract(material_info.normal_texture); - let (processed_emissive, emissive_sampler) = - extract(material_info.emissive_texture); - let (processed_metallic_roughness, metallic_roughness_sampler) = + let extract = + |info: Option<GLTFTextureInformation>| -> Option<ProcessedTexture> { + info.map(|info| ProcessedTexture { + pixels: info.pixels, + dimensions: (info.width, info.height), + format: info.format, + sampler: info.sampler, + mime_type: info.mime_type, + }) + }; + + let processed_diffuse = extract(material_info.diffuse_texture); + let processed_normal = extract(material_info.normal_texture); + let processed_emissive = extract(material_info.emissive_texture); + let processed_metallic_roughness = extract(material_info.metallic_roughness_texture); - let (processed_occlusion, occlusion_sampler) = - extract(material_info.occlusion_texture); + let processed_occlusion = extract(material_info.occlusion_texture); let tint = material_info.tint; let emissive_factor = material_info.emissive_factor; @@ -994,11 +1018,6 @@ impl Model { emissive: processed_emissive, metallic_roughness: processed_metallic_roughness, occlusion: processed_occlusion, - diffuse_sampler, - normal_sampler, - emissive_sampler, - metallic_roughness_sampler, - occlusion_sampler, tint, emissive_factor, metallic_factor, @@ -1039,21 +1058,16 @@ impl Model { let processed_emissive = processed.emissive; let processed_metallic_roughness = processed.metallic_roughness; let processed_occlusion = processed.occlusion; - let diffuse_sampler = processed.diffuse_sampler; - let normal_sampler = processed.normal_sampler; - let emissive_sampler = processed.emissive_sampler; - let metallic_roughness_sampler = processed.metallic_roughness_sampler; - let occlusion_sampler = processed.occlusion_sampler; - - let diffuse_texture = if let Some((rgba_data, dimensions)) = processed_diffuse { - Texture::from_bytes_verbose_mipmapped_with_format( + 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(), - &rgba_data, - Some(dimensions), - None, - diffuse_sampler.clone(), - Texture::TEXTURE_FORMAT_BASE.add_srgb_suffix(), + &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() @@ -1065,15 +1079,15 @@ impl Model { }; let has_normal_texture = processed_normal.is_some(); - let normal_texture = if let Some((rgba_data, dimensions)) = processed_normal { - Texture::from_bytes_verbose_mipmapped_with_format( + let normal_texture = if let Some(normal) = processed_normal { + Texture::from_raw_pixels_mipmapped_with_format( graphics.clone(), - &rgba_data, - Some(dimensions), - None, - normal_sampler.clone(), - Texture::TEXTURE_FORMAT_BASE, + &normal.pixels, + normal.dimensions, + normal.format, + Some(normal.sampler), Some(material_name.as_str()), + normal.mime_type.as_deref(), ) } else if let Some(tex) = registry.get_texture(flat_normal_texture) { (*tex).clone() @@ -1084,38 +1098,38 @@ impl Model { ); }; - let emissive_texture = processed_emissive.map(|(rgba_data, dimensions)| { - Texture::from_bytes_verbose_mipmapped_with_format( + let emissive_texture = processed_emissive.map(|emissive| { + let format = emissive.format.add_srgb_suffix(); + Texture::from_raw_pixels_mipmapped_with_format( graphics.clone(), - &rgba_data, - Some(dimensions), - None, - emissive_sampler.clone(), - Texture::TEXTURE_FORMAT_BASE.add_srgb_suffix(), + &emissive.pixels, + emissive.dimensions, + format, + Some(emissive.sampler), Some(material_name.as_str()), + emissive.mime_type.as_deref(), ) }); - let metallic_roughness_texture = - processed_metallic_roughness.map(|(rgba_data, dimensions)| { - Texture::from_bytes_verbose_mipmapped_with_format( - graphics.clone(), - &rgba_data, - Some(dimensions), - None, - metallic_roughness_sampler.clone(), - Texture::TEXTURE_FORMAT_BASE, - Some(material_name.as_str()), - ) - }); - let occlusion_texture = processed_occlusion.map(|(rgba_data, dimensions)| { - Texture::from_bytes_verbose_mipmapped_with_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 occlusion_texture = processed_occlusion.map(|occlusion| { + Texture::from_raw_pixels_mipmapped_with_format( graphics.clone(), - &rgba_data, - Some(dimensions), - None, - occlusion_sampler.clone(), - Texture::TEXTURE_FORMAT_BASE, + &occlusion.pixels, + occlusion.dimensions, + occlusion.format, + Some(occlusion.sampler), Some(material_name.as_str()), + occlusion.mime_type.as_deref(), ) }); @@ -1287,6 +1301,7 @@ pub trait DrawModel<'a> { material: &'a Material, globals_camera_bind_group: &'a wgpu::BindGroup, light_skin_bind_group: &'a wgpu::BindGroup, + environment_bind_group: &'a wgpu::BindGroup, ); fn draw_mesh_instanced( &mut self, @@ -1295,6 +1310,7 @@ pub trait DrawModel<'a> { instances: Range<u32>, globals_camera_bind_group: &'a wgpu::BindGroup, light_skin_bind_group: &'a wgpu::BindGroup, + environment_bind_group: &'a wgpu::BindGroup, ); #[allow(unused)] @@ -1303,6 +1319,7 @@ pub trait DrawModel<'a> { model: &'a Model, globals_camera_bind_group: &'a wgpu::BindGroup, light_skin_bind_group: &'a wgpu::BindGroup, + environment_bind_group: &'a wgpu::BindGroup, ); fn draw_model_instanced( &mut self, @@ -1310,6 +1327,7 @@ pub trait DrawModel<'a> { instances: Range<u32>, globals_camera_bind_group: &'a wgpu::BindGroup, light_skin_bind_group: &'a wgpu::BindGroup, + environment_bind_group: &'a wgpu::BindGroup, ); } @@ -1323,6 +1341,7 @@ where material: &'b Material, globals_camera_bind_group: &'b wgpu::BindGroup, light_skin_bind_group: &'a wgpu::BindGroup, + environment_bind_group: &'a wgpu::BindGroup, ) { self.draw_mesh_instanced( mesh, @@ -1330,6 +1349,7 @@ where 0..1, globals_camera_bind_group, light_skin_bind_group, + environment_bind_group, ); } @@ -1340,12 +1360,14 @@ where instances: Range<u32>, globals_camera_bind_group: &'b wgpu::BindGroup, light_skin_bind_group: &'a wgpu::BindGroup, + environment_bind_group: &'a wgpu::BindGroup, ) { self.set_vertex_buffer(0, mesh.vertex_buffer.slice(..)); self.set_index_buffer(mesh.index_buffer.slice(..), wgpu::IndexFormat::Uint32); self.set_bind_group(0, globals_camera_bind_group, &[]); self.set_bind_group(1, &material.bind_group, &[]); self.set_bind_group(2, light_skin_bind_group, &[]); + self.set_bind_group(3, environment_bind_group, &[]); self.draw_indexed(0..mesh.num_elements, 0, instances); } @@ -1355,12 +1377,14 @@ where model: &'b Model, globals_camera_bind_group: &'b wgpu::BindGroup, light_skin_bind_group: &'a wgpu::BindGroup, + environment_bind_group: &'a wgpu::BindGroup, ) { self.draw_model_instanced( model, 0..1, globals_camera_bind_group, light_skin_bind_group, + environment_bind_group ); } @@ -1370,6 +1394,7 @@ where instances: Range<u32>, globals_camera_bind_group: &'b wgpu::BindGroup, light_skin_bind_group: &'a wgpu::BindGroup, + environment_bind_group: &'a wgpu::BindGroup, ) { for mesh in &model.meshes { let material = &model.materials[mesh.material]; @@ -1379,6 +1404,7 @@ where instances.clone(), globals_camera_bind_group, light_skin_bind_group, + environment_bind_group, ); } } @@ -3,10 +3,6 @@ use std::sync::Arc; use crate::buffer::UniformBuffer; use crate::graphics::SharedGraphicsContext; -/// Mirrors `Globals` in `pipelines/shaders/shader.wgsl` (`@group(4) @binding(0)`). -/// -/// Note: the `_padding` ensures the uniform is 16 bytes, which matches WGSL -/// uniform layout expectations. #[repr(C)] #[derive(Debug, Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)] pub struct Globals { @@ -25,7 +21,6 @@ impl Default for Globals { } } -/// Owns the globals uniform buffer and its bind group. #[derive(Debug, Clone)] pub struct GlobalsUniform { pub data: Globals, @@ -165,4 +165,4 @@ impl HdrPipeline { pass.set_bind_group(0, &self.bind_group, &[]); pass.draw(0..3, 0..1); } -} +} @@ -26,6 +26,7 @@ impl DropbearShaderPipeline for MainRenderPipeline { &graphics.layouts.scene_globals_bind_group_layout, // @group(0) &graphics.layouts.material_bind_layout, // @group(1) &graphics.layouts.scene_light_skin_bind_group_layout, // @group(2) + &graphics.layouts.environment_bind_group_layout, // @group(3) ]; let pipeline_layout = @@ -73,6 +73,11 @@ var<storage, read> s_light_array: array<Light>; @group(2) @binding(1) var<storage, read> s_skinning: array<mat4x4<f32>>; +@group(3) @binding(0) +var env_map: texture_cube<f32>; +@group(3) @binding(1) +var env_sampler: sampler; + struct InstanceInput { @location(8) model_matrix_0: vec4<f32>, @location(9) model_matrix_1: vec4<f32>, @@ -103,6 +108,7 @@ struct VertexOutput { @location(2) world_position: vec3<f32>, @location(3) world_tangent: vec3<f32>, @location(4) world_bitangent: vec3<f32>, + @location(5) world_view_position: vec3<f32>, }; @vertex @@ -156,6 +162,7 @@ fn vs_main( out.world_position = world_position.xyz; out.world_tangent = world_tangent; out.world_bitangent = world_bitangent; + out.world_view_position = u_camera.view_pos.xyz; return out; } @@ -311,6 +318,11 @@ 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; + final_color += emissive; return vec4<f32>(final_color, base_colour.a); @@ -70,6 +70,7 @@ pub struct Texture { pub view: wgpu::TextureView, pub hash: Option<u64>, pub reference: Option<ResourceReference>, + pub mime_type: Option<String>, } impl Texture { @@ -145,6 +146,7 @@ impl Texture { size, hash: None, reference: None, + mime_type: None, } } @@ -195,6 +197,7 @@ impl Texture { label: label.to_potential_string(), hash: None, reference: None, + mime_type: None, } } @@ -236,6 +239,7 @@ impl Texture { view, hash: None, reference: None, + mime_type: None, } } @@ -509,9 +513,183 @@ 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], + 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 Self::bytes_per_pixel(format) { + 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)), + mime_type: mime_type.map(|value| value.to_string()), + }; + + if let Err(err) = + graphics + .mipmapper + .compute_mipmaps(&graphics.device, &graphics.queue, &texture) + { + log_once::warn_once!("Failed to generate mipmaps: {}", err); + } + + texture + } + pub fn sampler_from_wrap(wrap: TextureWrapMode) -> wgpu::SamplerDescriptor<'static> { wgpu::SamplerDescriptor { address_mode_u: wrap.into(), @@ -876,6 +876,7 @@ fn build_jni_return( match #inner_name(#(#call_args),*) { crate::scripting::result::DropbearNativeResult::Ok(()) => (), crate::scripting::result::DropbearNativeResult::Err(e) => { + eprintln!("JNI call failed in {}: {:?}", stringify!(#inner_name), e); let _ = env.throw_new("java/lang/RuntimeException", format!("JNI call failed: {:?}", e)); } } @@ -922,6 +923,7 @@ fn build_jni_return( None => std::ptr::null_mut(), }, crate::scripting::result::DropbearNativeResult::Err(e) => { + eprintln!("JNI call failed in {}: {:?}", stringify!(#inner_name), e); let _ = env.throw_new("java/lang/RuntimeException", format!("JNI call failed: {:?}", e)); std::ptr::null_mut() } @@ -944,6 +946,7 @@ fn build_jni_return( None => std::ptr::null_mut(), }, crate::scripting::result::DropbearNativeResult::Err(e) => { + eprintln!("JNI call failed in {}: {:?}", stringify!(#inner_name), e); let _ = env.throw_new("java/lang/RuntimeException", format!("JNI call failed: {:?}", e)); std::ptr::null_mut() } @@ -965,6 +968,7 @@ fn build_jni_return( None => std::ptr::null_mut(), }, crate::scripting::result::DropbearNativeResult::Err(e) => { + eprintln!("JNI call failed in {}: {:?}", stringify!(#inner_name), e); let _ = env.throw_new("java/lang/RuntimeException", format!("JNI call failed: {:?}", e)); std::ptr::null_mut() } @@ -984,6 +988,7 @@ fn build_jni_return( } }, crate::scripting::result::DropbearNativeResult::Err(e) => { + eprintln!("JNI call failed in {}: {:?}", stringify!(#inner_name), e); let _ = env.throw_new("java/lang/RuntimeException", format!("JNI call failed: {:?}", e)); crate::ffi_error_return!() } @@ -999,6 +1004,7 @@ fn build_jni_return( match #inner_name(#(#call_args),*) { crate::scripting::result::DropbearNativeResult::Ok(val) => #cast, crate::scripting::result::DropbearNativeResult::Err(e) => { + eprintln!("JNI call failed in {}: {:?}", stringify!(#inner_name), e); let _ = env.throw_new("java/lang/RuntimeException", format!("JNI call failed: {:?}", e)); crate::ffi_error_return!() } @@ -1017,6 +1023,7 @@ fn build_jni_return( } }, crate::scripting::result::DropbearNativeResult::Err(e) => { + eprintln!("JNI call failed in {}: {:?}", stringify!(#inner_name), e); let _ = env.throw_new("java/lang/RuntimeException", format!("JNI call failed: {:?}", e)); std::ptr::null_mut() } @@ -12,7 +12,6 @@ use egui::{CollapsingHeader, Ui}; use glam::DVec3; use hecs::{Entity, World}; use serde::{Deserialize, Serialize}; -use std::any::Any; use std::sync::Arc; #[derive(Debug, Clone, Serialize, Deserialize)] @@ -77,7 +76,9 @@ impl Component for Camera { impl InspectableComponent for Camera { fn inspect(&mut self, ui: &mut Ui, _graphics: Arc<SharedGraphicsContext>) { - CollapsingHeader::new("Camera3D").show(ui, |ui| { + CollapsingHeader::new("Camera3D") + .default_open(true) + .show(ui, |ui| { let mut changed = false; ui.horizontal(|ui| { @@ -54,9 +54,9 @@ pub struct ComponentRegistry { /// Describes a handy little future for [`Component::init`], which deals with initialising a component from its serialized form. /// /// Typically thrown in as a return parameter as `-> ComponentInitFuture<'a, Self>` -pub type ComponentInitFuture<'a, T: Component> = std::pin::Pin< +pub type ComponentInitFuture<'a, T> = std::pin::Pin< Box< - dyn std::future::Future<Output = anyhow::Result<T::RequiredComponentTypes>> + dyn std::future::Future<Output = anyhow::Result<<T as Component>::RequiredComponentTypes>> + Send + Sync + 'a, @@ -401,6 +401,8 @@ impl ComponentRegistry { } else { ui.label(format!("{} (no inspector)", desc.type_name)); } + + ui.separator(); } } @@ -468,10 +470,10 @@ pub trait Component: Sync + Send { /// Converts [`Self::SerializedForm`] into a [`Component`] instance that can be added to /// `hecs::EntityBuilder` during scene initialisation. - fn init<'a>( - ser: &'a Self::SerializedForm, + fn init( + ser: &'_ Self::SerializedForm, graphics: Arc<SharedGraphicsContext>, - ) -> ComponentInitFuture<'a, Self>; + ) -> ComponentInitFuture<'_, Self>; /// Called every frame to update the component's state. fn update_component( @@ -510,10 +512,10 @@ impl Component for MeshRenderer { } } - fn init<'a>( - ser: &'a Self::SerializedForm, + fn init( + ser: &'_ Self::SerializedForm, graphics: Arc<SharedGraphicsContext>, - ) -> ComponentInitFuture<'a, Self> { + ) -> ComponentInitFuture<'_, Self> { Box::pin(async move { let import_scale = ser.import_scale.unwrap_or(1.0); @@ -747,7 +749,7 @@ impl InspectableComponent for MeshRenderer { let proc_obj = ProcedurallyGeneratedObject::cuboid(size_vec); if force_new { let mut model = - proc_obj.construct(graphics.clone(), None, None, None, ASSET_REGISTRY.clone()); + { proc_obj.construct(graphics.clone(), None, None, None, ASSET_REGISTRY.clone()) }; let mut hasher = DefaultHasher::new(); model.hash.hash(&mut hasher); if let Ok(duration) = SystemTime::now().duration_since(UNIX_EPOCH) { @@ -785,8 +787,10 @@ impl InspectableComponent for MeshRenderer { model.hash = current_model.id; model.label = existing_label; - let mut asset = ASSET_REGISTRY.write(); - asset.update_model(current_model, model); + { + let mut asset = ASSET_REGISTRY.write(); + asset.update_model(current_model, model); + } } else { let handle = proc_obj.build_model(graphics.clone(), None, None, ASSET_REGISTRY.clone()); @@ -797,26 +801,32 @@ impl InspectableComponent for MeshRenderer { renderer.reset_texture_override(); }; - CollapsingHeader::new("Mesh Renderer").show(ui, |ui| { - let registry = ASSET_REGISTRY.read(); - let current_model = registry.get_model(self.model()); - let model_list = registry.list_models(); - - let model_reference = current_model - .as_ref() - .map(|model| model.path.clone()) - .unwrap_or_default(); - let model_label = current_model - .as_ref() - .map(|model| model.label.clone()) - .unwrap_or_else(|| "None".to_string()); + CollapsingHeader::new("Mesh Renderer") + .default_open(true) + .show(ui, |ui| { + let (model_reference, model_title, model_list) = { + let registry = ASSET_REGISTRY.read(); + let current_model = registry.get_model(self.model()); + let model_list = registry.list_models(); + + let model_reference = current_model + .as_ref() + .map(|model| model.path.clone()) + .unwrap_or_default(); + let model_label = current_model + .as_ref() + .map(|model| model.label.clone()) + .unwrap_or_else(|| "None".to_string()); + + let model_title = match &model_reference.ref_type { + ResourceReferenceType::None => "None".to_string(), + ResourceReferenceType::ProcObj(obj) => match obj.ty { + ProcObjType::Cuboid => "Cuboid".to_string(), + }, + _ => model_label, + }; - let model_title = match &model_reference.ref_type { - ResourceReferenceType::None => "None".to_string(), - ResourceReferenceType::ProcObj(obj) => match obj.ty { - ProcObjType::Cuboid => "Cuboid".to_string(), - }, - _ => model_label, + (model_reference, model_title, model_list) }; ui.vertical(|ui| { @@ -255,7 +255,7 @@ impl ProjectConfig { { match SceneConfig::read_from(&path) { Ok(scene) => { - log::debug!("Loaded scene config: {}", scene.scene_name); + log::debug!("Loaded scene config from file, added to SCENES entry: {}", scene.scene_name); scene_configs.push(scene); } Err(e) => { @@ -44,7 +44,7 @@ impl Display for LogLevel { pub static GLOBAL_TOASTS: Lazy<Mutex<Toasts>> = Lazy::new(|| { Mutex::new( Toasts::new() - .anchor(egui::Align2::RIGHT_BOTTOM, (-10.0, -10.0)) + .anchor(egui::Align2::RIGHT_BOTTOM, (-10.0, -28.0)) .direction(egui::Direction::BottomUp), ) }); @@ -316,7 +316,7 @@ fn move_character( }; if body_type != RigidBodyType::KinematicPositionBased { - return Err(DropbearNativeError::InvalidArgument); + return Ok(()); // soft error, just tell the user } let collider_handles = physics_state @@ -337,7 +337,7 @@ fn move_character( *collider.position() }; - let filter = QueryFilter::default().exclude_rigid_body(*rigid_body_handle); + let filter = QuprintlneryFilter::default().exclude_rigid_body(*rigid_body_handle); let query_pipeline = physics_state.broad_phase.as_query_pipeline( physics_state.narrow_phase.query_dispatcher(), &physics_state.bodies, @@ -4,7 +4,7 @@ pub mod loading; pub mod scripting; use crate::camera::CameraComponent; -use crate::component::{Component, ComponentRegistry, SerializedComponent}; +use crate::component::{ComponentRegistry, SerializedComponent}; use crate::hierarchy::{Children, Parent, SceneHierarchy}; use crate::physics::PhysicsState; use crate::physics::collider::ColliderGroup; @@ -21,7 +21,6 @@ use hecs::{Entity, World}; use once_cell::sync::Lazy; use parking_lot::RwLock; use serde::{Deserialize, Serialize}; -use std::any::Any; use std::borrow::Borrow; use std::collections::HashMap; use std::fmt; @@ -54,12 +54,14 @@ impl Component for EntityTransform { impl InspectableComponent for EntityTransform { fn inspect(&mut self, ui: &mut Ui, _graphics: Arc<SharedGraphicsContext>) { - CollapsingHeader::new("Entity Transform").show(ui, |ui| { - CollapsingHeader::new("Local").show(ui, |ui| { + CollapsingHeader::new("Entity Transform") + .default_open(true) + .show(ui, |ui| { + CollapsingHeader::new("Local").default_open(true).show(ui, |ui| { self.local_mut().inspect(ui); }); ui.add_space(4.0); - CollapsingHeader::new("World").show(ui, |ui| { + CollapsingHeader::new("World").default_open(true).show(ui, |ui| { self.world_mut().inspect(ui); }); }); @@ -55,7 +55,7 @@ impl ComponentNodeSelection { } #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] -struct ComponentNodeKey { +pub(crate) struct ComponentNodeKey { entity_bits: u64, component_type_id: u64, } @@ -92,19 +92,9 @@ pub struct StaticallyKept { context_menu_tab: Option<EditorTab>, pub(crate) is_focused: bool, pub(crate) old_pos: Transform, - pub(crate) scale_locked: bool, - pub(crate) old_label_entity: Option<hecs::Entity>, - pub(crate) label_original: Option<String>, - pub(crate) label_last_edit: Option<Instant>, - - pub(crate) transform_old_entity: Option<hecs::Entity>, - pub(crate) transform_original_transform: Option<Transform>, pub(crate) entity_transform_original: Option<EntityTransform>, - pub(crate) transform_in_progress: bool, - pub(crate) transform_rotation_cache: HashMap<Entity, glam::DVec3>, - pub(crate) dragged_asset: Option<DraggedAsset>, pub(crate) asset_node_assets: HashMap<u64, DraggedAsset>, pub(crate) asset_node_info: HashMap<u64, AssetNodeInfo>, @@ -307,6 +297,7 @@ pub(crate) struct FsEntry { #[derive(Clone, Debug)] pub(crate) struct AssetRenameState { + #[allow(dead_code)] // cbb to refactor just to remove this pub(crate) node_id: u64, pub(crate) original_path: PathBuf, pub(crate) buffer: String, @@ -94,6 +94,7 @@ pub struct Editor { pub(crate) default_skinning_buffer: Option<wgpu::Buffer>, pub(crate) default_skinning_bind_group: Option<wgpu::BindGroup>, pub(crate) light_skin_bind_group: Option<wgpu::BindGroup>, + pub(crate) scene_globals_bind_group: Option<dropbear_engine::bind_groups::SceneGlobalsBindGroup>, pub active_camera: Arc<Mutex<Option<Entity>>>, @@ -284,6 +285,7 @@ impl Editor { default_skinning_buffer: None, default_skinning_bind_group: None, light_skin_bind_group: None, + scene_globals_bind_group: None, }) } @@ -949,7 +951,19 @@ impl Editor { self.signal = Signal::StopPlaying; } } else if ui.button("Play").clicked() { - self.signal = Signal::Play; + // run prechecks to ensure a starting camera exists and stuff + let mut found_starting = false; + for (_, comp) in self.world.query::<(&Camera, &CameraComponent)>().iter() { + if comp.starting_camera { + found_starting = true; + } + } + + if !found_starting { + fatal!("Unable to start play mode: No initial camera set for this scene"); + } else { + self.signal = Signal::Play; + } } ui.menu_button("Export", |ui| { // todo: create a window for better build menu @@ -1148,7 +1162,18 @@ impl Editor { ui.add_enabled_ui(!can_play, |ui| { if ui.button("▶").clicked() { log::debug!("Menu Button Play button pressed"); - self.signal = Signal::Play; + let mut found_starting = false; + for (_, comp) in self.world.query::<(&Camera, &CameraComponent)>().iter() { + if comp.starting_camera { + found_starting = true; + } + } + + if !found_starting { + fatal!("Unable to start play mode: No initial camera set for this scene"); + } else { + self.signal = Signal::Play; + } } }); }); @@ -1,6 +1,7 @@ +use hecs::Entity; use crate::editor::{EditorTabViewer, TABS_GLOBAL}; use dropbear_engine::camera::Camera; -use eucalyptus_core::camera::CameraComponent; +use eucalyptus_core::camera::{CameraComponent, CameraType}; impl<'a> EditorTabViewer<'a> { pub(crate) fn resource_inspector(&mut self, ui: &mut egui::Ui) { @@ -15,12 +16,8 @@ impl<'a> EditorTabViewer<'a> { ui.label(format!("Entity ID: {}", inspect_entity.id())); ui.separator(); - if self - .world - .query_one::<(&Camera, &CameraComponent)>(inspect_entity) - .get() - .is_ok() - { + let mut local_unset_comp = false; + if let Ok((_, comp)) = self.world.query_one::<(&Camera, &CameraComponent)>(inspect_entity).get() { let is_active = self .active_camera .lock() @@ -38,10 +35,38 @@ impl<'a> EditorTabViewer<'a> { let mut active_camera = self.active_camera.lock(); *active_camera = Some(inspect_entity); } + + let mut is_starting = comp.starting_camera; + let is_starting_label = if comp.camera_type == CameraType::Debug { + is_starting = false; + "Cannot set a Debug camera as starting" + } else if is_starting { + "Already set as starting" + } else { + "Set as starting" + }; + + if ui + .add_enabled(!is_starting, egui::Button::new(is_starting_label)) + .clicked() + { + local_unset_comp = true; + } }); ui.separator(); } + if local_unset_comp { + for (e, comp) in self.world.query::<(Entity, &mut CameraComponent)>().iter() { + if e == inspect_entity { + comp.starting_camera = true; + continue; + } + log::debug!("Unset starting camera for entity {:?}", e); + comp.starting_camera = false; + } + } + self.component_registry.inspect_components( self.world, inspect_entity, @@ -502,29 +502,21 @@ impl Scene for Editor { .expect("Default skinning buffer not initialized"); // model rendering + let sky = self.sky_pipeline.as_ref().expect("Sky pipeline must be initialized before rendering models"); + let environment_bind_group = &sky.bind_group; + + let globals = self.shader_globals.as_ref().expect("Shader globals not initialised"); + if self.scene_globals_bind_group.is_none() { + self.scene_globals_bind_group = Some(dropbear_engine::bind_groups::SceneGlobalsBindGroup::new( + &graphics, + &globals.buffer, + camera.buffer(), + )); + } + let scene_globals_bind_group = self.scene_globals_bind_group.as_ref().unwrap(); + if let Some(lcp) = &self.light_cube_pipeline { for (model, handle, instance_count) in prepared_models { - let globals = self - .shader_globals - .as_ref() - .expect("Shader globals not initialised"); - let globals_camera_bind_group = - graphics - .device - .create_bind_group(&wgpu::BindGroupDescriptor { - label: Some("scene globals+camera bind group"), - layout: &graphics.layouts.scene_globals_bind_group_layout, - entries: &[ - wgpu::BindGroupEntry { - binding: 0, - resource: globals.buffer.buffer().as_entire_binding(), - }, - wgpu::BindGroupEntry { - binding: 1, - resource: camera.buffer().as_entire_binding(), - }, - ], - }); let light_skin_bind_group = graphics .device @@ -575,35 +567,14 @@ impl Scene for Editor { render_pass.draw_model_instanced( model, 0..instance_count, - &globals_camera_bind_group, + scene_globals_bind_group.as_ref(), &light_skin_bind_group, + environment_bind_group, ); } } if let Some(lcp) = &self.light_cube_pipeline { - let globals = self - .shader_globals - .as_ref() - .expect("Shader globals not initialised"); - let globals_camera_bind_group = - graphics - .device - .create_bind_group(&wgpu::BindGroupDescriptor { - label: Some("scene globals+camera bind group"), - layout: &graphics.layouts.scene_globals_bind_group_layout, - entries: &[ - wgpu::BindGroupEntry { - binding: 0, - resource: globals.buffer.buffer().as_entire_binding(), - }, - wgpu::BindGroupEntry { - binding: 1, - resource: camera.buffer().as_entire_binding(), - }, - ], - }); - for (handle, instance, skin_buffer) in animated_instances { let Some(model) = registry.get_model(Handle::new(handle)) else { log_once::error_once!("Missing model handle {} in registry", handle); @@ -667,12 +638,43 @@ impl Scene for Editor { render_pass.draw_model_instanced( model, 0..1, - &globals_camera_bind_group, + scene_globals_bind_group.as_ref(), &self.light_skin_bind_group.as_ref().unwrap(), // safe to do so because of check above + environment_bind_group, ); } } + if let Some(sky) = &self.sky_pipeline { + let mut render_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor { + label: Some("sky render pass"), + color_attachments: &[Some(wgpu::RenderPassColorAttachment { + view: hdr.view(), + resolve_target: None, + ops: wgpu::Operations { + load: wgpu::LoadOp::Load, + store: wgpu::StoreOp::Store, + }, + depth_slice: None, + })], + 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, + }), + timestamp_writes: None, + occlusion_query_set: None, + }); + + render_pass.set_pipeline(&sky.pipeline); + render_pass.set_bind_group(0, &camera.bind_group, &[]); + render_pass.set_bind_group(1, &sky.bind_group, &[]); + render_pass.draw(0..3, 0..1); + } + // collider pipeline { let show_hitboxes = self @@ -687,14 +689,9 @@ impl Scene for Editor { }) .unwrap_or(false); - log_once::debug_once!( - "show_hitboxes = {}, current_scene_name = {:?}", - show_hitboxes, - self.current_scene_name - ); - if show_hitboxes { if let Some(collider_pipeline) = &self.collider_wireframe_pipeline { + log_once::debug_once!("Found collider wireframe pipeline"); let mut render_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor { label: Some("collider wireframe render pass"), color_attachments: &[Some(wgpu::RenderPassColorAttachment { @@ -760,11 +757,6 @@ impl Scene for Editor { }); } } - log_once::debug_once!( - "Collider wireframe: {} entities with colliders, {} total colliders", - entity_count, - collider_count - ); if !instances_by_shape.is_empty() { let total_instances: usize = @@ -814,40 +806,12 @@ impl Scene for Editor { render_pass.draw_indexed(0..geometry.index_count, 0, 0..count as u32); } } + } else { + log_once::warn_once!("No collider pipeline found"); } } } - if let Some(sky) = &self.sky_pipeline { - let mut render_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor { - label: Some("sky render pass"), - color_attachments: &[Some(wgpu::RenderPassColorAttachment { - view: hdr.view(), - resolve_target: None, - ops: wgpu::Operations { - load: wgpu::LoadOp::Load, - store: wgpu::StoreOp::Store, - }, - depth_slice: None, - })], - 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, - }), - timestamp_writes: None, - occlusion_query_set: None, - }); - - render_pass.set_pipeline(&sky.pipeline); - render_pass.set_bind_group(0, &camera.bind_group, &[]); - render_pass.set_bind_group(1, &sky.bind_group, &[]); - render_pass.draw(0..3, 0..1); - } - hdr.process(&mut encoder, &graphics.viewport_texture.view); if let Err(e) = encoder.submit() { @@ -1,39 +1,22 @@ use crate::editor::{AssetClipboard, Editor, EditorState, Signal}; use crate::spawn::{PendingSpawn, push_pending_spawn}; use dropbear_engine::graphics::SharedGraphicsContext; -use dropbear_engine::utils::{EUCA_SCHEME, relative_path_from_euca}; use egui::Align2; use eucalyptus_core::camera::{CameraComponent, CameraType}; use eucalyptus_core::scripting::{BuildStatus, build_jvm}; -use eucalyptus_core::states::{EditorTab, PROJECT}; +use eucalyptus_core::states::{PROJECT}; use eucalyptus_core::{fatal, info, success, success_without_console, warn, warn_without_console}; use std::fs; use std::path::PathBuf; use std::sync::Arc; use winit::keyboard::KeyCode; -fn resolve_editor_path(uri: &str) -> PathBuf { - if uri.starts_with(EUCA_SCHEME) { - let relative = - relative_path_from_euca(uri).unwrap_or_else(|_| uri.trim_start_matches(EUCA_SCHEME)); - let project_path = PROJECT.read().project_path.clone(); - project_path.join("resources").join(relative) - } else { - PathBuf::from(uri) - } -} - pub trait SignalController { fn run_signal(&mut self, graphics: Arc<SharedGraphicsContext>) -> anyhow::Result<()>; } impl SignalController for Editor { fn run_signal(&mut self, graphics: Arc<SharedGraphicsContext>) -> anyhow::Result<()> { - fn is_legacy_internal_cube_uri(uri: &str) -> bool { - let uri = uri.replace('\\', "/"); - uri.ends_with("internal/dropbear/models/cube") - } - let local_signal: Option<Signal> = None; let show = true; @@ -346,8 +329,8 @@ impl SignalController for Editor { self.signal = Signal::None; self.show_build_window = false; self.editor_state = EditorState::Editing; - self.dock_state - .push_to_focused_leaf(EditorTab::ErrorConsole); + // self.dock_state + // .push_to_focused_leaf(EditorTab::ErrorConsole); // getting too problematic } } } @@ -137,6 +137,7 @@ pub struct PlayMode { default_skinning_buffer: Option<wgpu::Buffer>, default_skinning_bind_group: Option<wgpu::BindGroup>, light_skin_bind_group: Option<wgpu::BindGroup>, + scene_globals_bind_group: Option<dropbear_engine::bind_groups::SceneGlobalsBindGroup>, initial_scene: Option<String>, current_scene: Option<String>, @@ -228,6 +229,7 @@ impl PlayMode { default_skinning_buffer: None, default_skinning_bind_group: None, light_skin_bind_group: None, + scene_globals_bind_group: None, }; log::debug!("Created new play mode instance"); @@ -36,11 +36,6 @@ use winit::event_loop::ActiveEventLoop; impl Scene for PlayMode { fn load(&mut self, graphics: Arc<SharedGraphicsContext>) { - // let mut yak = yakui_winit::YakuiWinit::new(&graphics.window); - // yak.set_automatic_viewport(false); - // yak.set_automatic_scale_factor(false); - // self.yakui_winit = Some(yak); - if self.current_scene.is_none() { let initial_scene = if let Some(s) = &self.initial_scene { s.clone() @@ -60,7 +55,7 @@ impl Scene for PlayMode { } } - fn physics_update(&mut self, dt: f32, _graphics: Arc<SharedGraphicsContext>) { + fn physics_update(&muprintlnt self, dt: f32, _graphics: Arc<SharedGraphicsContext>) { if self.scripts_ready { let _ = self .script_manager @@ -542,93 +537,69 @@ impl Scene for PlayMode { })); }); - // overlay - // UI_CONTEXT.with(|yakui_cell| { - // let yak = yakui_cell.borrow(); - // let mut yakui = yak.yakui_state.lock(); - // - // let tex_size = graphics.viewport_texture.size; - // let viewport_size = yakui::geometry::Vec2::new( - // tex_size.width as f32, - // tex_size.height as f32, + // if let Some(kino) = &mut self.kino { + // // #[allow(dead_code)] + // let no_texture = kino.add_texture_from_bytes( + // &graphics.device, + // &graphics.queue, + // "no texture", + // include_bytes!("../../../resources/textures/no-texture.png"), + // 256, + // 256, // ); - // yakui.set_surface_size(viewport_size); - // yakui.set_unscaled_viewport(yakui::geometry::Rect::from_pos_size( - // yakui::geometry::Vec2::ZERO, - // viewport_size, - // )); - // yakui.set_scale_factor(graphics.window.scale_factor() as f32); - // - // yakui.start(); - // - // // eucalyptus_core::ui::poll(); - // - // yakui.finish(); - // }); - if let Some(kino) = &mut self.kino { - // #[allow(dead_code)] - let no_texture = kino.add_texture_from_bytes( - &graphics.device, - &graphics.queue, - "no texture", - include_bytes!("../../../resources/textures/no-texture.png"), - 256, - 256, - ); - - let parent = kino_ui::rect_container( - kino, - Rectangle::new("parent") - .fill(Fill::new([1.0, 1.0, 1.0, 1.0])) - .size(vec2(400.0, 400.0)), - |kino| { - kino.add_widget( - Rectangle::new("rect") - .texture(no_texture) - .size(vec2(128.0, 100.0)) - .border(Border::new([1.0, 0.0, 0.0, 1.0], 3.0)) - .fill(Fill::new([1.0, 1.0, 1.0, 1.0])) - .texture(no_texture) - .build(), - ); - }, - ); + // let parent = kino_ui::rect_container( + // kino, + // Rectangle::new("parent") + // .fill(Fill::new([1.0, 1.0, 1.0, 1.0])) + // .size(vec2(400.0, 400.0)), + // |kino| { + // kino.add_widget( + // Rectangle::new("rect") + // .texture(no_texture) + // .size(vec2(128.0, 100.0)) + // .border(Border::new([1.0, 0.0, 0.0, 1.0], 3.0)) + // .fill(Fill::new([1.0, 1.0, 1.0, 1.0])) + // .texture(no_texture) + // .build(), + // ); + // }, + // ); - kino_ui::label(kino, "Hello World!", |l| { - l.position = vec2( - graphics.viewport_texture.size.width as f32 / 2.0, - graphics.viewport_texture.size.height as f32 / 2.0, - ); - l.metrics.font_size = 30.0; - }); + // kino_ui::label(kino, "Hello World!", |l| { + // l.position = vec2( + // graphics.viewport_texture.size.width as f32 / 2.0, + // graphics.viewport_texture.size.height as f32 / 2.0, + // ); + // l.metrics.font_size = 30.0; + // }); - kino.poll(); + // kino.poll(); - if kino.response(parent).clicked { - println!("Parent clicked!"); - }; + // if kino.response(parent).clicked { + // println!("Parent clicked!"); + // }; - // if kino.response(parent).hovering { - // println!("Parent hovering"); - // }; + // // if kino.response(parent).hovering { + // // println!("Parent hovering"); + // // }; - if kino.response("rect").clicked { - println!("child clicked!"); - }; + // if kino.response("rect").clicked { + // println!("child clicked!"); + // }; - // if kino.response("rect").hovering { - // println!("child hovering"); - // }; + // // if kino.response("rect").hovering { + // // println!("child hovering"); + // // }; - if kino.response("Hello World!").clicked { - println!("text clicked") - } + // if kino.response("Hello World!").clicked { + // println!("text clicked") + // } - if kino.response("Hello World!").hovering { - println!("text hovering"); - }; - } + // if kino.response("Hello World!").hovering { + // println!("text hovering"); + // }; + // } } else { log::warn!("No such camera exists in the world"); } @@ -841,29 +812,21 @@ impl Scene for PlayMode { .expect("Default skinning buffer not initialized"); // model rendering + let sky = self.sky_pipeline.as_ref().expect("Sky pipeline must be initialized before rendering models"); + let environment_bind_group = &sky.bind_group; + + let globals = self.shader_globals.as_ref().expect("Shader globals not initialised"); + if self.scene_globals_bind_group.is_none() { + self.scene_globals_bind_group = Some(dropbear_engine::bind_groups::SceneGlobalsBindGroup::new( + &graphics, + &globals.buffer, + camera.buffer(), + )); + } + let scene_globals_bind_group = self.scene_globals_bind_group.as_ref().unwrap(); + if let Some(lcp) = &self.light_cube_pipeline { for (model, handle, instance_count) in prepared_models { - let globals = self - .shader_globals - .as_ref() - .expect("Shader globals not initialised"); - let globals_camera_bind_group = - graphics - .device - .create_bind_group(&wgpu::BindGroupDescriptor { - label: Some("scene globals+camera bind group"), - layout: &graphics.layouts.scene_globals_bind_group_layout, - entries: &[ - wgpu::BindGroupEntry { - binding: 0, - resource: globals.buffer.buffer().as_entire_binding(), - }, - wgpu::BindGroupEntry { - binding: 1, - resource: camera.buffer().as_entire_binding(), - }, - ], - }); let light_skin_bind_group = graphics .device @@ -914,35 +877,14 @@ impl Scene for PlayMode { render_pass.draw_model_instanced( model, 0..instance_count, - &globals_camera_bind_group, + scene_globals_bind_group.as_ref(), &light_skin_bind_group, + environment_bind_group, ); } } if let Some(lcp) = &self.light_cube_pipeline { - let globals = self - .shader_globals - .as_ref() - .expect("Shader globals not initialised"); - let globals_camera_bind_group = - graphics - .device - .create_bind_group(&wgpu::BindGroupDescriptor { - label: Some("scene globals+camera bind group"), - layout: &graphics.layouts.scene_globals_bind_group_layout, - entries: &[ - wgpu::BindGroupEntry { - binding: 0, - resource: globals.buffer.buffer().as_entire_binding(), - }, - wgpu::BindGroupEntry { - binding: 1, - resource: camera.buffer().as_entire_binding(), - }, - ], - }); - for (handle, instance, skin_buffer) in animated_instances { let Some(model) = registry.get_model(Handle::new(handle)) else { log_once::error_once!("Missing model handle {} in registry", handle); @@ -1006,8 +948,9 @@ impl Scene for PlayMode { render_pass.draw_model_instanced( model, 0..1, - &globals_camera_bind_group, + scene_globals_bind_group.as_ref(), &self.light_skin_bind_group.as_ref().unwrap(), // safe to do so because of check above + environment_bind_group, ); } } @@ -1026,6 +969,66 @@ impl Scene for PlayMode { }) .unwrap_or(false); + // UI_CONTEXT.with(|v| { + // let commands = graphics.yakui_renderer.lock().paint( + // &mut v.borrow().yakui_state.lock(), + // &graphics.device, + // &graphics.queue, + // SurfaceInfo { + // format: Texture::TEXTURE_FORMAT, + // sample_count: 1, + // color_attachment: &graphics.viewport_texture.view, + // resolve_target: None, + // } + // ); + // + // graphics.queue.submit([commands]); + // }); + } + + if let Some(sky) = &self.sky_pipeline { + let mut render_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor { + label: Some("sky render pass"), + color_attachments: &[Some(wgpu::RenderPassColorAttachment { + view: hdr.view(), + resolve_target: None, + ops: wgpu::Operations { + load: wgpu::LoadOp::Load, + store: wgpu::StoreOp::Store, + }, + depth_slice: None, + })], + 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, + }), + timestamp_writes: None, + occlusion_query_set: None, + }); + + render_pass.set_pipeline(&sky.pipeline); + render_pass.set_bind_group(0, &camera.bind_group, &[]); + render_pass.set_bind_group(1, &sky.bind_group, &[]); + render_pass.draw(0..3, 0..1); + } + + { + let show_hitboxes = self + .current_scene + .as_ref() + .and_then(|scene_name| { + let scenes = SCENES.read(); + scenes + .iter() + .find(|scene| &scene.scene_name == scene_name) + .map(|scene| scene.settings.show_hitboxes) + }) + .unwrap_or(false); + if show_hitboxes { if let Some(collider_pipeline) = &self.collider_wireframe_pipeline { let mut render_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor { @@ -1140,52 +1143,6 @@ impl Scene for PlayMode { } } } - - // UI_CONTEXT.with(|v| { - // let commands = graphics.yakui_renderer.lock().paint( - // &mut v.borrow().yakui_state.lock(), - // &graphics.device, - // &graphics.queue, - // SurfaceInfo { - // format: Texture::TEXTURE_FORMAT, - // sample_count: 1, - // color_attachment: &graphics.viewport_texture.view, - // resolve_target: None, - // } - // ); - // - // graphics.queue.submit([commands]); - // }); - } - - if let Some(sky) = &self.sky_pipeline { - let mut render_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor { - label: Some("sky render pass"), - color_attachments: &[Some(wgpu::RenderPassColorAttachment { - view: hdr.view(), - resolve_target: None, - ops: wgpu::Operations { - load: wgpu::LoadOp::Load, - store: wgpu::StoreOp::Store, - }, - depth_slice: None, - })], - 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, - }), - timestamp_writes: None, - occlusion_query_set: None, - }); - - render_pass.set_pipeline(&sky.pipeline); - render_pass.set_bind_group(0, &camera.bind_group, &[]); - render_pass.set_bind_group(1, &sky.bind_group, &[]); - render_pass.draw(0..3, 0..1); } hdr.process(&mut encoder, &graphics.viewport_texture.view);