1use craballoc::{
4 prelude::{Gpu, SlabAllocator, WgpuRuntime},
5 slab::SlabBuffer,
6};
7use crabslab::Id;
8
9use crate::{
10 context::Context,
11 cull::{ComputeCulling, CullingError},
12 primitive::{shader::PrimitiveDescriptor, Primitive},
13 texture::Texture,
14};
15
16use super::DrawIndirectArgs;
17
18pub struct IndirectDraws {
22 pub(crate) slab: SlabAllocator<WgpuRuntime>,
23 pub(crate) draws: Vec<Gpu<DrawIndirectArgs>>,
24 pub(crate) compute_culling: ComputeCulling,
25}
26
27impl IndirectDraws {
28 fn new(
29 runtime: impl AsRef<WgpuRuntime>,
30 stage_slab_buffer: &SlabBuffer<wgpu::Buffer>,
31 depth_texture: &Texture,
32 ) -> Self {
33 let runtime = runtime.as_ref();
34 let indirect_slab =
35 SlabAllocator::new(runtime, "indirect-slab", wgpu::BufferUsages::INDIRECT);
36 Self {
37 compute_culling: ComputeCulling::new(
38 runtime,
39 stage_slab_buffer,
40 &indirect_slab.commit(),
41 depth_texture,
42 ),
43 slab: indirect_slab,
44 draws: vec![],
45 }
46 }
47
48 pub(crate) fn slab_allocator(&self) -> &SlabAllocator<WgpuRuntime> {
49 &self.slab
50 }
51
52 fn invalidate(&mut self) {
53 if !self.draws.is_empty() {
54 log::trace!("draining indirect draws after invalidation");
55 let _ = self.draws.drain(..);
56 }
57 }
58
59 pub async fn read_hzb_images(&self) -> Result<Vec<image::GrayImage>, CullingError> {
64 self.compute_culling
65 .compute_depth_pyramid
66 .depth_pyramid
67 .read_images()
68 .await
69 }
70}
71
72impl From<Id<PrimitiveDescriptor>> for DrawIndirectArgs {
73 fn from(id: Id<PrimitiveDescriptor>) -> Self {
74 DrawIndirectArgs {
78 vertex_count: 0,
79 instance_count: 0,
80 first_vertex: 0,
81 first_instance: id.inner(),
82 }
83 }
84}
85
86pub(crate) struct DrawingStrategy {
94 indirect: Option<IndirectDraws>,
95}
96
97impl DrawingStrategy {
98 pub(crate) fn as_indirect(&self) -> Option<&IndirectDraws> {
99 self.indirect.as_ref()
100 }
101}
102
103pub struct DrawCalls {
106 renderlets: Vec<Primitive>,
108 pub(crate) drawing_strategy: DrawingStrategy,
109}
110
111impl DrawCalls {
112 pub fn new(
121 ctx: &Context,
122 use_compute_culling: bool,
123 stage_slab_buffer: &SlabBuffer<wgpu::Buffer>,
124 depth_texture: &Texture,
125 ) -> Self {
126 let supported_features = ctx.get_adapter().features();
127 log::trace!("supported features: {supported_features:#?}");
128 let can_use_multi_draw_indirect = ctx.get_adapter().features().contains(
129 wgpu::Features::INDIRECT_FIRST_INSTANCE | wgpu::Features::MULTI_DRAW_INDIRECT,
130 );
131 if use_compute_culling && !can_use_multi_draw_indirect {
132 log::warn!(
133 "`use_compute_culling` is `true`, but the MULTI_DRAW_INDIRECT feature is not \
134 available. No compute culling will occur."
135 )
136 }
137 let can_use_compute_culling = use_compute_culling && can_use_multi_draw_indirect;
138 Self {
139 renderlets: vec![],
140 drawing_strategy: DrawingStrategy {
141 indirect: if can_use_compute_culling {
142 log::debug!("Using indirect drawing method and compute culling");
143 Some(IndirectDraws::new(ctx, stage_slab_buffer, depth_texture))
144 } else {
145 log::debug!("Using direct drawing method");
146 None
147 },
148 },
149 }
150 }
151
152 pub(crate) fn drawing_strategy(&self) -> &DrawingStrategy {
153 &self.drawing_strategy
154 }
155
156 pub fn get_compute_culling_available(&self) -> bool {
158 matches!(
159 &self.drawing_strategy,
160 DrawingStrategy { indirect: Some(_) }
161 )
162 }
163
164 pub fn add_primitive(&mut self, renderlet: &Primitive) -> usize {
168 log::trace!("adding renderlet {:?}", renderlet.id());
169 if let Some(indirect) = &mut self.drawing_strategy.indirect {
170 indirect.invalidate();
171 }
172 self.renderlets.push(renderlet.clone());
173 self.renderlets.len()
174 }
175
176 pub fn remove_primitive(&mut self, renderlet: &Primitive) -> usize {
181 let id = renderlet.id();
182 self.renderlets.retain(|ir| ir.descriptor.id() != id);
183
184 if let Some(indirect) = &mut self.drawing_strategy.indirect {
185 indirect.invalidate();
186 }
187
188 self.renderlets.len()
189 }
190
191 pub fn sort_primitives(&mut self, f: impl Fn(&Primitive, &Primitive) -> std::cmp::Ordering) {
193 self.renderlets.sort_by(f);
194 if let Some(indirect) = &mut self.drawing_strategy.indirect {
195 indirect.invalidate();
196 }
197 }
198
199 pub fn draw_count(&self) -> usize {
202 self.renderlets.len()
203 }
204
205 pub fn pre_draw(
209 &mut self,
210 depth_texture: &Texture,
211 ) -> Result<Option<SlabBuffer<wgpu::Buffer>>, CullingError> {
212 let num_draw_calls = self.draw_count();
213 if num_draw_calls > 0 {
217 log::trace!("num_draw_calls: {num_draw_calls}");
218 if let Some(indirect) = &mut self.drawing_strategy.indirect {
223 if indirect.draws.len() != self.renderlets.len() {
224 indirect.invalidate();
225 let indirect_buffer = indirect.slab.commit();
228 if indirect_buffer.is_new_this_commit() {
229 log::warn!("new indirect buffer");
230 }
231 indirect.draws = self
232 .renderlets
233 .iter()
234 .map(|r| {
235 indirect
236 .slab
237 .new_value(DrawIndirectArgs::from(r.descriptor.id()))
238 .into_gpu_only()
239 })
240 .collect();
241 }
242 let indirect_buffer = indirect.slab.commit();
243 log::trace!("performing culling on {num_draw_calls} renderlets");
244 indirect
245 .compute_culling
246 .run(num_draw_calls as u32, depth_texture);
247 Ok(Some(indirect_buffer))
248 } else {
249 Ok(None)
250 }
251 } else {
252 Ok(None)
253 }
254 }
255
256 pub fn draw_direct(&self, render_pass: &mut wgpu::RenderPass) {
258 if self.renderlets.is_empty() {
259 log::warn!("no internal renderlets, nothing to draw");
260 }
261 for ir in self.renderlets.iter() {
262 let desc = ir.descriptor.get();
264 let vertex_range = 0..desc.get_vertex_count();
265 let id = ir.descriptor.id();
266 let instance_range = id.inner()..id.inner() + 1;
267 render_pass.draw(vertex_range, instance_range);
268 }
269 }
270
271 pub fn draw(&self, render_pass: &mut wgpu::RenderPass) {
276 let num_draw_calls = self.draw_count();
277 if num_draw_calls > 0 {
278 if let Some(indirect) = &self.drawing_strategy.indirect {
279 log::trace!("drawing {num_draw_calls} renderlets using indirect");
280 if let Some(indirect_buffer) = indirect.slab.get_buffer() {
281 render_pass.multi_draw_indirect(&indirect_buffer, 0, num_draw_calls as u32);
282 } else {
283 log::warn!(
284 "could not get the indirect buffer - was `DrawCall::upkeep` called?"
285 );
286 }
287 } else {
288 log::trace!("drawing {num_draw_calls} renderlets using direct");
289 self.draw_direct(render_pass);
290 }
291 } else {
292 log::warn!("zero draw calls");
293 }
294 }
295}