Skip to main content

viewport_lib/plugin_api/
cull.rs

1//! GPU-driven culling service published to plugins.
2//!
3//! A plugin that submits its own instanced geometry can run the lib's
4//! frustum-cull compute against its instance AABB buffer to get a compacted
5//! visibility list and a `DrawIndexedIndirect` entry suitable for
6//! `draw_indexed_indirect`.
7//!
8//! [`CullSubmission`] carries the buffers for a multi-batch cull: one big
9//! AABB list, one [`BatchMeta`] entry per batch, one atomic counter slot per
10//! batch, and one indirect-draw entry per batch. Plugins with many meshes
11//! pack them all into one submission so the renderer dispatches the compute
12//! pass once for the whole group.
13//!
14//! For the common one-mesh-N-instances case, see
15//! [`crate::renderer::ViewportRenderer::submit_cull_single_mesh`]. It takes
16//! the per-mesh draw parameters directly and fills the lib's scratch meta +
17//! counter buffers for you.
18//!
19//! Submit via [`crate::renderer::ViewportRenderer::submit_cull`] or
20//! [`crate::renderer::ViewportRenderer::submit_cull_shadow`].
21
22pub use crate::resources::BatchMeta;
23
24/// Per-instance world-space bounding box for GPU culling.
25///
26/// Plugin code builds an `array<InstanceAabb>` storage buffer, one entry per
27/// drawable instance, and passes it via [`CullSubmission::instance_aabbs`].
28///
29/// Layout matches the cull shader's contract (32 bytes per entry).
30/// `batch_index` selects which [`BatchMeta`] entry the instance belongs to;
31/// for a single-batch submission set it to 0. `cast_shadows` is honoured
32/// when [`CullSubmission::shadow_pass`] is `true` or the submission goes
33/// through `submit_cull_shadow`.
34#[repr(C)]
35#[derive(Copy, Clone, bytemuck::Pod, bytemuck::Zeroable, Debug)]
36pub struct InstanceAabb {
37    /// World-space minimum corner.
38    pub min: [f32; 3],
39    /// Index into the batch_meta buffer that this instance belongs to.
40    pub batch_index: u32,
41    /// World-space maximum corner.
42    pub max: [f32; 3],
43    /// `1` if the instance participates in shadow casting, `0` to skip it
44    /// during shadow cull dispatches. Ignored on non-shadow submissions.
45    pub cast_shadows: u32,
46}
47
48const _: () = assert!(std::mem::size_of::<InstanceAabb>() == 32);
49
50/// Inputs to one cull dispatch.
51///
52/// Buffer requirements:
53///
54/// - `instance_aabbs`: `STORAGE`. `instance_count` x [`InstanceAabb`].
55/// - `batch_meta`: `STORAGE`. `batch_count` x [`BatchMeta`].
56/// - `counter`: `STORAGE`. `batch_count` x `u32`, used as atomic counters.
57///   The renderer zeroes this slot before the dispatch.
58/// - `visible_out`: `STORAGE`. Holds up to `instance_count` x `u32`, the
59///   compacted list of visible instance indices written by the cull pass.
60/// - `indirect_out`: `STORAGE | INDIRECT`. `batch_count` x
61///   `DrawIndexedIndirect` (20 bytes per entry). The static fields
62///   (`index_count`, `first_index`, `base_vertex`, `first_instance`) come
63///   from the matching [`BatchMeta`] entry; `instance_count` is overwritten
64///   by the cull pass.
65///
66/// After the encoder runs, draw each batch with
67/// `pass.draw_indexed_indirect(indirect_out, batch_idx * 20)` using
68/// `visible_out` as the per-instance lookup buffer.
69pub struct CullSubmission<'a> {
70    /// Storage buffer of [`InstanceAabb`] entries.
71    pub instance_aabbs: &'a wgpu::Buffer,
72    /// Number of valid entries in `instance_aabbs` (not the buffer capacity).
73    pub instance_count: u32,
74    /// Storage buffer of [`BatchMeta`] entries, one per batch.
75    pub batch_meta: &'a wgpu::Buffer,
76    /// Number of valid entries in `batch_meta`.
77    pub batch_count: u32,
78    /// Storage buffer used for per-batch atomic counters. Must hold at
79    /// least `batch_count` u32 slots. The cull pass zeroes the counters at
80    /// the end of its dispatch so the same buffer can be reused across
81    /// cascades or frames.
82    pub counter: &'a wgpu::Buffer,
83    /// Storage buffer that receives the compacted list of visible instance
84    /// indices. Sized for at least `instance_count` u32 slots.
85    pub visible_out: &'a wgpu::Buffer,
86    /// Storage / indirect buffer that receives one `DrawIndexedIndirect`
87    /// entry per batch (20 bytes each).
88    pub indirect_out: &'a wgpu::Buffer,
89    /// `true` to run the shadow-cull variant on the main camera pass: the
90    /// cull shader honours `InstanceAabb::cast_shadows` and skips entries
91    /// with the flag clear. `submit_cull_shadow` forces this on regardless
92    /// of the value here.
93    pub shadow_pass: bool,
94}
95
96/// Draw parameters for a one-mesh-N-instances submission.
97///
98/// Passed to [`crate::renderer::ViewportRenderer::submit_cull_single_mesh`]
99/// when a plugin only has one mesh to cull. The renderer fills its scratch
100/// `BatchMeta` and counter slots from these fields, so the plugin does not
101/// have to allocate either buffer.
102#[derive(Copy, Clone, Debug)]
103pub struct SingleMeshDraw {
104    /// Mesh index count for one instance.
105    pub index_count: u32,
106    /// First index offset (typically 0).
107    pub first_index: u32,
108    /// Vertex offset (typically 0).
109    pub base_vertex: i32,
110    /// First-instance value written into the indirect entry (typically 0).
111    pub first_instance: u32,
112}