From bb51647dae7483a204723b0afac732580ac8b493 Mon Sep 17 00:00:00 2001 From: Schell Carl Scivally Date: Sat, 21 Sep 2024 08:43:58 +1200 Subject: [PATCH] feature: actually use multi-draw-indirect (#129) * feature: actually use multi-draw-indirect * devlog * pre-upkeep on the indirect slab --- DEVLOG.md | 10 + crates/renderling-ui/src/lib.rs | 8 +- crates/renderling/src/lib.rs | 5 +- .../src/linkage/stage-renderlet_vertex.spv | Bin 39272 -> 39476 bytes crates/renderling/src/stage.rs | 14 ++ crates/renderling/src/stage/cpu.rs | 175 +++++++++++++----- 6 files changed, 157 insertions(+), 55 deletions(-) diff --git a/DEVLOG.md b/DEVLOG.md index eaffcb66..53ddf1e9 100644 --- a/DEVLOG.md +++ b/DEVLOG.md @@ -17,6 +17,16 @@ My private stuff. Pay no attention to the man behind the curtain. I'm trying to decide what the next step is - either I can tackle frustum culling, light tiling or shadow mapping. +In the meantime I finally got around to using +[`RenderPass::multi_draw_indirect`](https://docs.rs/wgpu/22.1.0/wgpu/struct.RenderPass.html#method.multi_draw_indirect) +for my draw calls, when available. + +The only hitch was that `Renderlets` that have been marked invisible were still getting drawn. +This is because previously we checked `is_visible` on the CPU and simply didn't send that draw +call, but now that it's hosted in a buffer we check in the vertex shader itself, and if +`is_visible == false` we move the vertex outside of the clipping frustum. It's sub-optimal but +it's dead easy and I think it's probably a good trade-off. This feature is meant to be used to +save time on draw calls, which it will, even if we have to discard some triangles. ## Thu Sep 19, 2024 diff --git a/crates/renderling-ui/src/lib.rs b/crates/renderling-ui/src/lib.rs index fc20ef42..505e9f96 100644 --- a/crates/renderling-ui/src/lib.rs +++ b/crates/renderling-ui/src/lib.rs @@ -349,13 +349,7 @@ mod test { #[ctor::ctor] fn init_logging() { - let _ = env_logger::builder() - .is_test(true) - .filter_level(log::LevelFilter::Warn) - .filter_module("renderling", log::LevelFilter::Trace) - .filter_module("renderling_ui", log::LevelFilter::Trace) - .filter_module("crabslab", log::LevelFilter::Debug) - .try_init(); + let _ = env_logger::builder().is_test(true).try_init(); } pub struct Colors(std::iter::Cycle>); diff --git a/crates/renderling/src/lib.rs b/crates/renderling/src/lib.rs index c978c270..5534d35a 100644 --- a/crates/renderling/src/lib.rs +++ b/crates/renderling/src/lib.rs @@ -212,10 +212,7 @@ mod test { #[ctor::ctor] fn init_logging() { - let _ = env_logger::builder() - .is_test(true) - .filter_level(log::LevelFilter::Warn) - .try_init(); + let _ = env_logger::builder().is_test(true).try_init(); } #[test] diff --git a/crates/renderling/src/linkage/stage-renderlet_vertex.spv b/crates/renderling/src/linkage/stage-renderlet_vertex.spv index dd3b28ab55147163ae65483e63561450f400a63e..f865eb10508d45fbbbb17fe7e5db3003b3166b88 100644 GIT binary patch literal 39476 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 HBIEx7|FvMt diff --git a/crates/renderling/src/stage.rs b/crates/renderling/src/stage.rs index 0cc0dbd0..48253dbd 100644 --- a/crates/renderling/src/stage.rs +++ b/crates/renderling/src/stage.rs @@ -31,6 +31,15 @@ mod gltf_support; #[cfg(all(feature = "gltf", not(target_arch = "spirv")))] pub use gltf_support::*; +/// Argument buffer layout for draw_indirect commands. +#[derive(Clone, Copy, Default, SlabItem)] +pub struct DrawIndirectArgs { + pub vertex_count: u32, + pub instance_count: u32, + pub first_vertex: u32, + pub first_instance: u32, +} + /// A vertex skin. /// /// For more info on vertex skinning, see @@ -270,6 +279,11 @@ pub fn renderlet_vertex( #[spirv(position)] out_clip_pos: &mut Vec4, ) { let renderlet = slab.read_unchecked(renderlet_id); + if !renderlet.visible { + // put it outside the clipping frustum + *out_clip_pos = Vec4::new(10.0, 10.0, 10.0, 1.0); + return; + } *out_camera = renderlet.camera_id; *out_material = renderlet.material_id; diff --git a/crates/renderling/src/stage/cpu.rs b/crates/renderling/src/stage/cpu.rs index 886defcd..67a3b0d0 100644 --- a/crates/renderling/src/stage/cpu.rs +++ b/crates/renderling/src/stage/cpu.rs @@ -50,11 +50,33 @@ impl From for StageError { /// to update the GPU pub const RENDERLET_STRONG_COUNT_LOWER_BOUND: usize = 2; +struct IndirectDraws { + slab: SlabAllocator, + draws: Vec>, +} + +impl From<&Hybrid> for DrawIndirectArgs { + fn from(h: &Hybrid) -> Self { + let r = h.get(); + DrawIndirectArgs { + vertex_count: if r.indices_array.is_null() { + r.vertices_array.len() as u32 + } else { + r.indices_array.len() as u32 + }, + instance_count: 1, + first_vertex: 0, + first_instance: h.id().into(), + } + } +} + /// Provides a way to communicate with the stage about how you'd like your /// objects drawn. -pub(crate) enum StageDrawStrategy { - // TODO: Add `Indirect` to `StageDrawStrategy` which uses `RenderPass::multi_draw_indirect` - Direct(Vec>), +#[derive(Default)] +pub(crate) struct StageDraws { + hybrids: Vec>, + indirect: Option, } fn create_msaa_textureview( @@ -182,7 +204,7 @@ pub struct Stage { pub(crate) buffers_bindgroup: Arc>>>, pub(crate) textures_bindgroup: Arc>>>, - pub(crate) draws: Arc>, + pub(crate) draws: Arc>, } impl Deref for Stage { @@ -229,6 +251,22 @@ impl Stage { ctx.get_render_target().format().add_srgb_suffix(), &bloom.get_mix_texture(), ); + let draws = StageDraws { + hybrids: vec![], + indirect: { + if ctx.get_adapter().features().contains( + wgpu::Features::INDIRECT_FIRST_INSTANCE | wgpu::Features::MULTI_DRAW_INDIRECT, + ) { + log::info!("creating stage to use Renderpass::multi_draw_indirect"); + Some(IndirectDraws { + slab: SlabAllocator::default(), + draws: vec![], + }) + } else { + None + } + }, + }; Self { mngr, @@ -249,7 +287,7 @@ impl Stage { has_bloom: AtomicBool::from(true).into(), buffers_bindgroup: Default::default(), textures_bindgroup: Default::default(), - draws: Arc::new(RwLock::new(StageDrawStrategy::Direct(vec![]))), + draws: Arc::new(RwLock::new(draws)), hdr_texture, depth_texture, msaa_render_target, @@ -651,10 +689,13 @@ impl Stage { pub fn add_renderlet(&self, renderlet: &Hybrid) { // UNWRAP: if we can't acquire the lock we want to panic. let mut draws = self.draws.write().unwrap(); - match draws.deref_mut() { - StageDrawStrategy::Direct(units) => { - units.push(renderlet.clone()); - } + let StageDraws { hybrids, indirect } = draws.deref_mut(); + hybrids.push(renderlet.clone()); + if let Some(IndirectDraws { slab, draws }) = indirect.as_mut() { + draws.push( + slab.new_value(DrawIndirectArgs::from(renderlet)) + .into_gpu_only(), + ); } } @@ -663,10 +704,10 @@ impl Stage { pub fn remove_renderlet(&self, renderlet: &Hybrid) { let id = renderlet.id(); let mut draws = self.draws.write().unwrap(); - match draws.deref_mut() { - StageDrawStrategy::Direct(units) => { - units.retain(|hybrid| hybrid.id() != id); - } + let StageDraws { hybrids, indirect } = draws.deref_mut(); + hybrids.retain(|hybrid| hybrid.id() != id); + if let Some(IndirectDraws { slab: _, draws }) = indirect.as_mut() { + *draws = vec![]; } } @@ -674,9 +715,11 @@ impl Stage { pub fn get_renderlets(&self) -> Vec> { // UNWRAP: if we can't acquire the lock we want to panic. let draws = self.draws.read().unwrap(); - match draws.deref() { - StageDrawStrategy::Direct(units) => units.clone(), - } + let StageDraws { + hybrids, + indirect: _, + } = draws.deref(); + hybrids.clone() } /// Re-order the renderlets. @@ -687,22 +730,28 @@ impl Stage { /// renderlets will be drawn _before_ the ordered ones, in no particular /// order. pub fn reorder_renderlets(&self, order: impl IntoIterator>) { + log::trace!("reordering renderlets"); // UNWRAP: panic on purpose - let mut renderlets = self.draws.write().unwrap(); - match renderlets.deref_mut() { - StageDrawStrategy::Direct(rs) => { - let mut ordered = vec![]; - let mut m = - FxHashMap::from_iter(std::mem::take(rs).into_iter().map(|r| (r.id(), r))); - for id in order.into_iter() { - if let Some(renderlet) = m.remove(&id) { - ordered.push(renderlet); - } - } - rs.extend(m.into_values()); - rs.extend(ordered); + let mut guard = self.draws.write().unwrap(); + let StageDraws { + ref mut hybrids, + indirect, + } = guard.deref_mut(); + let mut ordered = vec![]; + let mut m = FxHashMap::from_iter(std::mem::take(hybrids).into_iter().map(|r| (r.id(), r))); + for id in order.into_iter() { + if let Some(renderlet) = m.remove(&id) { + ordered.push(renderlet); } } + hybrids.extend(m.into_values()); + hybrids.extend(ordered); + if let Some(indirect) = indirect.as_mut() { + indirect.draws.drain(..); + // for (hybrid, draw) in hybrids.iter().zip(indirect.draws.iter_mut()) { + // draw.set(hybrid.into()); + // } + } } /// Returns a clone of the current depth texture. @@ -727,15 +776,46 @@ impl Stage { NestedTransform::new(&self.mngr) } - fn tick_internal(&self) -> Arc { - { + fn tick_internal(&self) -> (Arc, Option>) { + let maybe_indirect_draw_buffer = { + let mut redraw_args = false; let mut draw_guard = self.draws.write().unwrap(); - match draw_guard.deref_mut() { - StageDrawStrategy::Direct(draws) => { - draws.retain(|d| d.strong_count() > RENDERLET_STRONG_COUNT_LOWER_BOUND); + let StageDraws { hybrids, indirect } = draw_guard.deref_mut(); + hybrids.retain(|d| { + if d.strong_count() > RENDERLET_STRONG_COUNT_LOWER_BOUND { + true + } else { + redraw_args = true; + false + } + }); + if let Some(IndirectDraws { slab, draws }) = indirect.as_mut() { + if redraw_args || draws.len() != hybrids.len() { + // 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) + let _ = slab.upkeep(( + &self.device, + &self.queue, + Some("indirect draw pre upkeep"), + wgpu::BufferUsages::INDIRECT, + )); + *draws = hybrids + .iter() + .map(|h: &Hybrid| { + slab.new_value(DrawIndirectArgs::from(h)).into_gpu_only() + }) + .collect(); } + Some(slab.get_updated_buffer(( + &self.device, + &self.queue, + Some("indirect draw upkeep"), + wgpu::BufferUsages::INDIRECT, + ))) + } else { + None } - } + }; if let Some(new_slab_buffer) = self.mngr.upkeep(( &self.device, &self.queue, @@ -745,11 +825,11 @@ impl Stage { // invalidate our bindgroups, etc let _ = self.skybox_bindgroup.lock().unwrap().take(); let _ = self.buffers_bindgroup.lock().unwrap().take(); - new_slab_buffer + (new_slab_buffer, maybe_indirect_draw_buffer) } else { // UNWRAP: safe because we called `SlabManager::upkeep` above^, which ensures // the buffer exists - self.mngr.get_buffer().unwrap() + (self.mngr.get_buffer().unwrap(), maybe_indirect_draw_buffer) } } @@ -768,7 +848,7 @@ impl Stage { let label = Some("stage render"); // UNWRAP: POP let background_color = *self.background_color.read().unwrap(); - let slab_buffer = self.tick_internal(); + let (slab_buffer, maybe_indirect_draw_buffer) = self.tick_internal(); // UNWRAP: POP let pipeline = self.stage_pipeline.read().unwrap(); // UNWRAP: POP @@ -831,12 +911,20 @@ impl Stage { }), ..Default::default() }); + render_pass.set_pipeline(&pipeline); render_pass.set_bind_group(0, &slab_buffers_bindgroup, &[]); render_pass.set_bind_group(1, &textures_bindgroup, &[]); - match draws.deref() { - StageDrawStrategy::Direct(units) => { - for hybrid in units { + let num_draw_calls = draws.hybrids.len(); + if num_draw_calls > 0 { + if let Some(indirect_draw_buffer) = maybe_indirect_draw_buffer { + render_pass.multi_draw_indirect( + &indirect_draw_buffer, + 0, + draws.hybrids.len() as u32, + ); + } else { + for hybrid in draws.hybrids.iter() { let rlet = hybrid.get(); if rlet.visible { let vertex_range = if rlet.indices_array.is_null() { @@ -848,13 +936,12 @@ impl Stage { let instance_range = id.inner()..id.inner() + 1; log::trace!( "drawing vertices {vertex_range:?} and instances \ - {instance_range:?}" + {instance_range:?}" ); render_pass.draw(vertex_range, instance_range); } } - } /* render_pass.multi_draw_indirect(&indirect_buffer, 0, - * stage.number_of_indirect_draws()); */ + } } if let Some((pipeline, bindgroup)) = may_skybox_pipeline_and_bindgroup.as_ref() {