Skip to main content

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. Does not include
91///   compute culling, as the MULTI_DRAW_INDIRECT `wgpu` feature is required and
92///   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
119    /// of available driver 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,
214        // otherwise `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
226                    // because the draw buffer has to be
227                    // contiguous (it can't start with a bunch of trash)
228                    let indirect_buffer = indirect.slab.commit();
229                    if indirect_buffer.is_new_this_commit() {
230                        log::warn!("new indirect buffer");
231                    }
232                    indirect.draws = self
233                        .renderlets
234                        .iter()
235                        .map(|r| {
236                            indirect
237                                .slab
238                                .new_value(DrawIndirectArgs::from(r.descriptor.id()))
239                                .into_gpu_only()
240                        })
241                        .collect();
242                }
243                let indirect_buffer = indirect.slab.commit();
244                log::trace!("performing culling on {num_draw_calls} renderlets");
245                indirect
246                    .compute_culling
247                    .run(num_draw_calls as u32, depth_texture);
248                Ok(Some(indirect_buffer))
249            } else {
250                Ok(None)
251            }
252        } else {
253            Ok(None)
254        }
255    }
256
257    /// Draw into the given `RenderPass` by directly calling each draw.
258    pub fn draw_direct(&self, render_pass: &mut wgpu::RenderPass) {
259        if self.renderlets.is_empty() {
260            log::warn!("no internal renderlets, nothing to draw");
261        }
262        for ir in self.renderlets.iter() {
263            // UNWRAP: panic on purpose
264            let desc = ir.descriptor.get();
265            let vertex_range = 0..desc.get_vertex_count();
266            let id = ir.descriptor.id();
267            let instance_range = id.inner()..id.inner() + 1;
268            render_pass.draw(vertex_range, instance_range);
269        }
270    }
271
272    /// Draw into the given `RenderPass`.
273    ///
274    /// This method draws using the indirect draw buffer, if possible, otherwise
275    /// it falls back to `draw_direct`.
276    pub fn draw(&self, render_pass: &mut wgpu::RenderPass) {
277        let num_draw_calls = self.draw_count();
278        if num_draw_calls > 0 {
279            if let Some(indirect) = &self.drawing_strategy.indirect {
280                log::trace!("drawing {num_draw_calls} renderlets using indirect");
281                if let Some(indirect_buffer) = indirect.slab.get_buffer() {
282                    render_pass.multi_draw_indirect(&indirect_buffer, 0, num_draw_calls as u32);
283                } else {
284                    log::warn!(
285                        "could not get the indirect buffer - was `DrawCall::upkeep` called?"
286                    );
287                }
288            } else {
289                log::trace!("drawing {num_draw_calls} renderlets using direct");
290                self.draw_direct(render_pass);
291            }
292        } else {
293            log::warn!("zero draw calls");
294        }
295    }
296}