renderling/draw/
cpu.rs

1//! CPU-only side of renderling/draw.rs
2
3use 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
18/// Issues indirect draw calls.
19///
20/// Issues draw calls and performs culling.
21pub 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    /// Read the images from the hierarchical z-buffer used for occlusion
60    /// culling.
61    ///
62    /// This is primarily for testing.
63    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        // This is obviously incomplete, but that's ok because
75        // the rest of this struct is filled out on the GPU during
76        // culling.
77        DrawIndirectArgs {
78            vertex_count: 0,
79            instance_count: 0,
80            first_vertex: 0,
81            first_instance: id.inner(),
82        }
83    }
84}
85
86/// The drawing method used to send geometry to the GPU.
87///
88/// This is one of either:
89/// * Indirect drawing - standard drawing method that includes compute culling.
90/// * Direct drawing - fallback drawing method for web targets.
91///   Does not include compute culling, as the MULTI_DRAW_INDIRECT
92///   `wgpu` feature is required and not available on web.
93pub(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
103/// Used to determine which objects are drawn and maintains the
104/// list of all [`Primitive`]s.
105pub struct DrawCalls {
106    /// Internal representation of all staged renderlets.
107    renderlets: Vec<Primitive>,
108    pub(crate) drawing_strategy: DrawingStrategy,
109}
110
111impl DrawCalls {
112    /// Create a new [`DrawCalls`].
113    ///
114    /// `use_compute_culling` can be used to set whether frustum culling is used
115    /// as a GPU compute step before drawing. This is a native-only option.
116    ///
117    /// ## Note
118    /// A [`Context`] is required because `DrawCalls` needs to query for the set of available driver
119    /// features.
120    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    /// Returns whether compute culling is available.
157    pub fn get_compute_culling_available(&self) -> bool {
158        matches!(
159            &self.drawing_strategy,
160            DrawingStrategy { indirect: Some(_) }
161        )
162    }
163
164    /// Add a renderlet to the drawing queue.
165    ///
166    /// Returns the number of draw calls in the queue.
167    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    /// Erase the given renderlet from the internal list of renderlets to be
177    /// drawn each frame.
178    ///
179    /// Returns the number of draw calls remaining in the queue.
180    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    /// Sort draw calls using a function compairing [`Primitive`]s.
192    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    /// Returns the number of draw calls (direct or indirect) that will be
200    /// made during pre-rendering (compute culling) and rendering.
201    pub fn draw_count(&self) -> usize {
202        self.renderlets.len()
203    }
204
205    /// Perform pre-draw steps like frustum and occlusion culling, if available.
206    ///
207    /// Returns the indirect draw buffer.
208    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        // Only do compute culling if there are things we need to draw, otherwise
214        // `wgpu` will err with something like:
215        // "Buffer with 'indirect draw upkeep' label binding size is zero"
216        if num_draw_calls > 0 {
217            log::trace!("num_draw_calls: {num_draw_calls}");
218            // TODO: Cull on GPU even when `multidraw_indirect` is unavailable.
219            //
220            // We can do this without multidraw by running GPU culling and then
221            // copying `indirect_buffer` back to the CPU.
222            if let Some(indirect) = &mut self.drawing_strategy.indirect {
223                if indirect.draws.len() != self.renderlets.len() {
224                    indirect.invalidate();
225                    // Pre-upkeep to reclaim resources - this is necessary because
226                    // the draw buffer has to be contiguous (it can't start with a bunch of trash)
227                    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    /// Draw into the given `RenderPass` by directly calling each draw.
257    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            // UNWRAP: panic on purpose
263            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    /// Draw into the given `RenderPass`.
272    ///
273    /// This method draws using the indirect draw buffer, if possible, otherwise
274    /// it falls back to `draw_direct`.
275    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}