diff --git a/.cargo/config.toml b/.cargo/config.toml index 6a86f80d..c7b9923d 100644 --- a/.cargo/config.toml +++ b/.cargo/config.toml @@ -1,5 +1,7 @@ [alias] xtask = "run --package xtask --" +shaders = "xtask compile-shaders" +linkage = "xtask generati-linkage" [build] rustflags = ["--cfg=web_sys_unstable_apis"] diff --git a/crates/example/src/lib.rs b/crates/example/src/lib.rs index 39b8023a..bc80cbdc 100644 --- a/crates/example/src/lib.rs +++ b/crates/example/src/lib.rs @@ -144,8 +144,7 @@ impl App { .with_background_color(DARK_BLUE_BG_COLOR) .with_bloom_mix_strength(0.5) .with_bloom_filter_radius(4.0) - .with_msaa_sample_count(4) - .with_debug_overlay(true); + .with_msaa_sample_count(4); let camera = stage.new_value(Camera::default()); let sunlight = stage.new_value(DirectionalLight { direction: Vec3::NEG_Y, diff --git a/crates/renderling/src/cull/cpu.rs b/crates/renderling/src/cull/cpu.rs index b789efa8..2c032568 100644 --- a/crates/renderling/src/cull/cpu.rs +++ b/crates/renderling/src/cull/cpu.rs @@ -9,7 +9,7 @@ use crabslab::{Array, Slab}; use glam::UVec2; use snafu::{OptionExt, Snafu}; -use crate::texture::Texture; +use crate::{bindgroup::ManagedBindGroup, texture::Texture}; use super::DepthPyramidDescriptor; @@ -40,11 +40,14 @@ impl From for CullingError { /// Computes frustum and occlusion culling on the GPU. pub struct ComputeCulling { pipeline: wgpu::ComputePipeline, - bindgroup_layout: wgpu::BindGroupLayout, - bindgroup: wgpu::BindGroup, + pyramid_slab_buffer: SlabBuffer, stage_slab_buffer: SlabBuffer, indirect_slab_buffer: SlabBuffer, + + bindgroup_layout: wgpu::BindGroupLayout, + bindgroup: ManagedBindGroup, + pub(crate) compute_depth_pyramid: ComputeDepthPyramid, } @@ -86,10 +89,9 @@ impl ComputeCulling { pub fn new( runtime: impl AsRef, - size: UVec2, - sample_count: u32, stage_slab_buffer: &SlabBuffer, indirect_slab_buffer: &SlabBuffer, + depth_texture: &Texture, ) -> Self { let runtime = runtime.as_ref(); let device = &runtime.device; @@ -143,7 +145,7 @@ impl ComputeCulling { compilation_options: wgpu::PipelineCompilationOptions::default(), cache: None, }); - let compute_depth_pyramid = ComputeDepthPyramid::new(runtime, size, sample_count); + let compute_depth_pyramid = ComputeDepthPyramid::new(runtime, depth_texture); let pyramid_slab_buffer = compute_depth_pyramid .compute_copy_depth .pyramid_slab_buffer @@ -151,14 +153,14 @@ impl ComputeCulling { let bindgroup = Self::new_bindgroup( stage_slab_buffer, &pyramid_slab_buffer, - &indirect_slab_buffer, + indirect_slab_buffer, &bindgroup_layout, device, ); Self { pipeline, bindgroup_layout, - bindgroup, + bindgroup: ManagedBindGroup::new(bindgroup), compute_depth_pyramid, pyramid_slab_buffer, stage_slab_buffer: stage_slab_buffer.clone(), @@ -171,21 +173,31 @@ impl ComputeCulling { } pub fn run(&mut self, indirect_draw_count: u32, depth_texture: &Texture) { + log::trace!( + "indirect_draw_count: {indirect_draw_count}, sample_count: {}", + depth_texture.texture.sample_count() + ); // Compute the depth pyramid from last frame's depth buffer self.compute_depth_pyramid.run(depth_texture); - let should_recreate_bindgroup = self.stage_slab_buffer.synchronize() - || self.indirect_slab_buffer.synchronize() - || self.pyramid_slab_buffer.synchronize(); - if should_recreate_bindgroup { - self.bindgroup = Self::new_bindgroup( + let stage_slab_invalid = self.stage_slab_buffer.synchronize(); + let indirect_slab_invalid = self.indirect_slab_buffer.synchronize(); + let pyramid_slab_invalid = self.pyramid_slab_buffer.synchronize(); + let should_recreate_bindgroup = + stage_slab_invalid || indirect_slab_invalid || pyramid_slab_invalid; + log::trace!("stage_slab_invalid: {stage_slab_invalid}"); + log::trace!("indirect_slab_invalid: {indirect_slab_invalid}"); + log::trace!("pyramid_slab_invalid: {pyramid_slab_invalid}"); + let bindgroup = self.bindgroup.get(should_recreate_bindgroup, || { + log::debug!("recreating compute-culling bindgroup"); + Self::new_bindgroup( &self.stage_slab_buffer, &self.pyramid_slab_buffer, &self.indirect_slab_buffer, &self.bindgroup_layout, self.compute_depth_pyramid.depth_pyramid.slab.device(), - ); - } + ) + }); let runtime = self.runtime(); let mut encoder = runtime .device @@ -196,7 +208,7 @@ impl ComputeCulling { timestamp_writes: None, }); compute_pass.set_pipeline(&self.pipeline); - compute_pass.set_bind_group(0, Some(&self.bindgroup), &[]); + compute_pass.set_bind_group(0, Some(bindgroup.as_ref()), &[]); compute_pass.dispatch_workgroups(indirect_draw_count / 32 + 1, 1, 1); } runtime.queue.submit(Some(encoder.finish())); @@ -309,7 +321,7 @@ struct ComputeCopyDepth { bindgroup_layout: wgpu::BindGroupLayout, sample_count: u32, pyramid_slab_buffer: SlabBuffer, - bindgroup: Option, + bindgroup: ManagedBindGroup, } impl ComputeCopyDepth { @@ -411,14 +423,21 @@ impl ComputeCopyDepth { }) } - pub fn new(sample_count: u32, depth_pyramid: &DepthPyramid) -> Self { + pub fn new(depth_pyramid: &DepthPyramid, depth_texture: &Texture) -> Self { let device = depth_pyramid.slab.device(); + let sample_count = depth_texture.texture.sample_count(); let bindgroup_layout = Self::create_bindgroup_layout(device, sample_count); let pipeline = Self::create_pipeline(device, &bindgroup_layout, sample_count > 1); let pyramid_slab_buffer = depth_pyramid.slab.upkeep(); + let buffer = Self::create_bindgroup( + device, + &bindgroup_layout, + &pyramid_slab_buffer, + &depth_texture.view, + ); Self { pipeline, - bindgroup: None, + bindgroup: ManagedBindGroup::new(buffer), bindgroup_layout, pyramid_slab_buffer, sample_count, @@ -458,17 +477,15 @@ impl ComputeCopyDepth { let _ = pyramid.slab.upkeep(); let should_recreate_bindgroup = self.pyramid_slab_buffer.synchronize() || sample_count_mismatch || size_changed; - if should_recreate_bindgroup { - self.bindgroup = Some(Self::create_bindgroup( - pyramid.slab.device(), + let bindgroup = self.bindgroup.get(should_recreate_bindgroup, || { + Self::create_bindgroup( + &runtime.device, &self.bindgroup_layout, &self.pyramid_slab_buffer, &depth_texture.view, - )); - } + ) + }); - // UNWRAP: safe because we just set it above^ - let bindgroup = self.bindgroup.as_ref().unwrap(); let mut encoder = runtime .device .create_command_encoder(&wgpu::CommandEncoderDescriptor { label: Self::LABEL }); @@ -478,7 +495,7 @@ impl ComputeCopyDepth { ..Default::default() }); compute_pass.set_pipeline(&self.pipeline); - compute_pass.set_bind_group(0, Some(bindgroup), &[]); + compute_pass.set_bind_group(0, Some(bindgroup.as_ref()), &[]); let x = size.x / 32 + 1; let y = size.y / 32 + 1; let z = 1; @@ -579,7 +596,7 @@ impl ComputeDownsampleDepth { } for i in 1..pyramid.mip_data.len() { - log::trace!("downsampling to mip {i}"); + log::trace!("downsampling to mip {i}..{}", pyramid.mip_data.len()); // Update the mip_level we're operating on. let size = pyramid.desc.modify(|desc| { desc.mip_level = i as u32; @@ -615,7 +632,6 @@ impl ComputeDownsampleDepth { /// Computes occlusion culling on the GPU. pub struct ComputeDepthPyramid { - sample_count: u32, pub(crate) depth_pyramid: DepthPyramid, compute_copy_depth: ComputeCopyDepth, compute_downsample_depth: ComputeDownsampleDepth, @@ -624,16 +640,15 @@ pub struct ComputeDepthPyramid { impl ComputeDepthPyramid { const _LABEL: Option<&'static str> = Some("compute-depth-pyramid"); - pub fn new(runtime: impl AsRef, size: UVec2, sample_count: u32) -> Self { + pub fn new(runtime: impl AsRef, depth_texture: &Texture) -> Self { let runtime = runtime.as_ref(); - let depth_pyramid = DepthPyramid::new(runtime, size); - let compute_copy_depth = ComputeCopyDepth::new(sample_count, &depth_pyramid); + let depth_pyramid = DepthPyramid::new(runtime, depth_texture.size()); + let compute_copy_depth = ComputeCopyDepth::new(&depth_pyramid, depth_texture); let compute_downsample_depth = ComputeDownsampleDepth::new(&depth_pyramid); Self { depth_pyramid, compute_copy_depth, compute_downsample_depth, - sample_count, } } diff --git a/crates/renderling/src/draw/cpu.rs b/crates/renderling/src/draw/cpu.rs index e68857b2..8b2c5d33 100644 --- a/crates/renderling/src/draw/cpu.rs +++ b/crates/renderling/src/draw/cpu.rs @@ -53,19 +53,21 @@ pub struct IndirectDraws { impl IndirectDraws { fn new( runtime: impl AsRef, - size: UVec2, - sample_count: u32, stage_slab_buffer: &SlabBuffer, + depth_texture: &Texture, ) -> Self { let runtime = runtime.as_ref(); - let indirect_slab = SlabAllocator::new(runtime, wgpu::BufferUsages::INDIRECT); + let indirect_slab = SlabAllocator::new_with_label( + runtime, + wgpu::BufferUsages::INDIRECT, + Some("indirect-slab"), + ); Self { compute_culling: ComputeCulling::new( runtime, - size, - sample_count, stage_slab_buffer, &indirect_slab.upkeep(), + depth_texture, ), slab: indirect_slab, draws: vec![], @@ -91,7 +93,10 @@ impl IndirectDraws { self.invalidate(); // Pre-upkeep to reclaim resources - this is necessary because // the draw buffer has to be contiguous (it can't start with a bunch of trash) - self.slab.upkeep(); + let indirect_buffer = self.slab.upkeep(); + if indirect_buffer.is_new_this_upkeep() { + log::warn!("new indirect buffer"); + } self.draws = internal_renderlets .iter() .map(|ir: &InternalRenderlet| { @@ -173,9 +178,8 @@ impl DrawCalls { pub fn new( ctx: &Context, use_compute_culling: bool, - size: UVec2, - sample_count: u32, stage_slab_buffer: &SlabBuffer, + depth_texture: &Texture, ) -> Self { let can_use_multi_draw_indirect = ctx.get_adapter().features().contains( wgpu::Features::INDIRECT_FIRST_INSTANCE | wgpu::Features::MULTI_DRAW_INDIRECT, @@ -194,9 +198,8 @@ impl DrawCalls { log::debug!("Using indirect drawing method and compute culling"); DrawingStrategy::Indirect(IndirectDraws::new( ctx, - size, - sample_count, stage_slab_buffer, + depth_texture, )) } else { log::debug!("Using direct drawing method"); diff --git a/crates/renderling/src/skybox/cpu.rs b/crates/renderling/src/skybox/cpu.rs index f8c59f17..ae73750f 100644 --- a/crates/renderling/src/skybox/cpu.rs +++ b/crates/renderling/src/skybox/cpu.rs @@ -191,7 +191,8 @@ impl Skybox { let runtime = runtime.as_ref(); log::trace!("creating skybox"); - let slab = SlabAllocator::new(runtime, wgpu::BufferUsages::VERTEX); + let slab = + SlabAllocator::new_with_label(runtime, wgpu::BufferUsages::VERTEX, Some("skybox-slab")); let proj = Mat4::perspective_rh(std::f32::consts::FRAC_PI_2, 1.0, 0.1, 10.0); let camera = slab.new_value(Camera::default().with_projection(proj)); diff --git a/crates/renderling/src/stage/cpu.rs b/crates/renderling/src/stage/cpu.rs index 154a498a..69e137e1 100644 --- a/crates/renderling/src/stage/cpu.rs +++ b/crates/renderling/src/stage/cpu.rs @@ -186,7 +186,8 @@ impl Stage { let resolution @ UVec2 { x: w, y: h } = ctx.get_size(); let atlas_size = *ctx.atlas_size.read().unwrap(); let atlas = Atlas::new(ctx, atlas_size).unwrap(); - let mngr = SlabAllocator::new(runtime, wgpu::BufferUsages::empty()); + let mngr = + SlabAllocator::new_with_label(runtime, wgpu::BufferUsages::empty(), Some("stage-slab")); let pbr_config = mngr.new_value(PbrConfig { atlas_size: UVec2::new(atlas_size.width, atlas_size.height), resolution, @@ -200,12 +201,7 @@ impl Stage { h, multisample_count, ))); - let depth_texture = Arc::new(RwLock::new(Texture::create_depth_texture( - device, - w, - h, - multisample_count, - ))); + let depth_texture = Texture::create_depth_texture(device, w, h, multisample_count); let msaa_render_target = Default::default(); // UNWRAP: safe because no other references at this point (created above^) let bloom = Bloom::new(ctx, &hdr_texture.read().unwrap()); @@ -221,10 +217,10 @@ impl Stage { draw_calls: Arc::new(RwLock::new(DrawCalls::new( ctx, true, - UVec2::new(w, h), - multisample_count, &mngr.upkeep(), + &depth_texture, ))), + depth_texture: Arc::new(RwLock::new(depth_texture)), buffers_bindgroup: ManagedBindGroup::new(crate::linkage::slab_bindgroup( device, &stage_slab_buffer, @@ -234,7 +230,6 @@ impl Stage { mngr, pbr_config, lights: Arc::new(RwLock::new(lights)), - stage_pipeline: Arc::new(RwLock::new(stage_pipeline)), atlas, skybox: Arc::new(RwLock::new(Skybox::empty(runtime))), @@ -245,11 +240,9 @@ impl Stage { tonemapping, has_bloom: AtomicBool::from(true).into(), textures_bindgroup: Default::default(), - debug_overlay: DebugOverlay::new(device, ctx.get_render_target().format()), has_debug_overlay: Arc::new(false.into()), hdr_texture, - depth_texture, msaa_render_target, msaa_sample_count: Arc::new(multisample_count.into()), clear_color_attachments: Arc::new(true.into()), diff --git a/crates/renderling/src/stage/gltf_support.rs b/crates/renderling/src/stage/gltf_support.rs index 5c977428..5dc7250e 100644 --- a/crates/renderling/src/stage/gltf_support.rs +++ b/crates/renderling/src/stage/gltf_support.rs @@ -1473,7 +1473,9 @@ mod test { #[test] fn camera_position_sanity() { - // Test that the camera has the expected translation + // Test that the camera has the expected translation, + // taking into account that the gltf files may have been + // saved with Y up, or with Z up let ctx = Context::headless(100, 100); let mut stage = ctx.new_stage(); let doc = stage @@ -1485,10 +1487,11 @@ mod test { ) .unwrap(); let camera_a = doc.cameras.first().unwrap(); - assert!( - Vec3::new(14.699949, 4.958309, 12.676651).distance(camera_a.get_camera().position()) - <= 10e-6 - ); + + let eq = |p: Vec3| p.distance(camera_a.get_camera().position()) <= 10e-6; + let either_y_up_or_z_up = eq(Vec3::new(14.699949, 4.958309, 12.676651)) + || eq(Vec3::new(14.699949, -12.676651, 4.958309)); + assert!(either_y_up_or_z_up); let doc = stage .load_gltf_document_from_path( @@ -1499,9 +1502,17 @@ mod test { ) .unwrap(); let camera_b = doc.cameras.first().unwrap(); - assert_eq!( + + let eq = |a: Vec3, b: Vec3| { + let c = Vec3::new(b.x, -b.z, b.y); + println!("a: {a}"); + println!("b: {b}"); + println!("c: {c}"); + a.distance(b) <= 10e-6 || c.distance(c) <= 10e-6 + }; + assert!(eq( camera_a.get_camera().position(), camera_b.get_camera().position() - ); + )); } } diff --git a/crates/renderling/src/texture.rs b/crates/renderling/src/texture.rs index 06a0d16a..552886df 100644 --- a/crates/renderling/src/texture.rs +++ b/crates/renderling/src/texture.rs @@ -68,6 +68,10 @@ impl Texture { self.texture.height() } + pub fn size(&self) -> UVec2 { + UVec2::new(self.width(), self.height()) + } + /// Create a cubemap texture from 6 faces. pub fn new_cubemap_texture( runtime: impl AsRef, diff --git a/crates/renderling/src/tonemapping/cpu.rs b/crates/renderling/src/tonemapping/cpu.rs index 59cbd42b..1e86612d 100644 --- a/crates/renderling/src/tonemapping/cpu.rs +++ b/crates/renderling/src/tonemapping/cpu.rs @@ -100,7 +100,11 @@ impl Tonemapping { frame_texture_format: wgpu::TextureFormat, hdr_texture: &Texture, ) -> Self { - let slab = SlabAllocator::new(runtime, wgpu::BufferUsages::empty()); + let slab = SlabAllocator::new_with_label( + runtime, + wgpu::BufferUsages::empty(), + Some("tonemapping-slab"), + ); let config = slab.new_value(TonemapConstants::default()); let label = Some("tonemapping");