bevy_pbr/cluster/
mod.rs

1//! Spatial clustering of objects, currently just point and spot lights.
2
3use core::num::NonZero;
4
5use bevy_core_pipeline::core_3d::Camera3d;
6use bevy_ecs::{
7    component::Component,
8    entity::{Entity, EntityHashMap},
9    query::{With, Without},
10    reflect::ReflectComponent,
11    system::{Commands, Query, Res, Resource},
12    world::{FromWorld, World},
13};
14use bevy_math::{AspectRatio, UVec2, UVec3, UVec4, Vec3Swizzles as _, Vec4};
15use bevy_reflect::{std_traits::ReflectDefault, Reflect};
16use bevy_render::{
17    camera::Camera,
18    render_resource::{
19        BindingResource, BufferBindingType, ShaderSize as _, ShaderType, StorageBuffer,
20        UniformBuffer,
21    },
22    renderer::{RenderDevice, RenderQueue},
23    sync_world::RenderEntity,
24    Extract,
25};
26use bevy_utils::{hashbrown::HashSet, tracing::warn};
27
28pub(crate) use crate::cluster::assign::assign_objects_to_clusters;
29use crate::MeshPipeline;
30
31mod assign;
32
33#[cfg(test)]
34mod test;
35
36// NOTE: this must be kept in sync with the same constants in
37// `mesh_view_types.wgsl`.
38pub const MAX_UNIFORM_BUFFER_CLUSTERABLE_OBJECTS: usize = 204;
39// Make sure that the clusterable object buffer doesn't overflow the maximum
40// size of a UBO on WebGL 2.
41const _: () =
42    assert!(size_of::<GpuClusterableObject>() * MAX_UNIFORM_BUFFER_CLUSTERABLE_OBJECTS <= 16384);
43
44// NOTE: Clustered-forward rendering requires 3 storage buffer bindings so check that
45// at least that many are supported using this constant and SupportedBindingType::from_device()
46pub const CLUSTERED_FORWARD_STORAGE_BUFFER_COUNT: u32 = 3;
47
48// this must match CLUSTER_COUNT_SIZE in pbr.wgsl
49// and must be large enough to contain MAX_UNIFORM_BUFFER_CLUSTERABLE_OBJECTS
50const CLUSTER_COUNT_SIZE: u32 = 9;
51
52const CLUSTER_OFFSET_MASK: u32 = (1 << (32 - (CLUSTER_COUNT_SIZE * 2))) - 1;
53const CLUSTER_COUNT_MASK: u32 = (1 << CLUSTER_COUNT_SIZE) - 1;
54
55// Clustered-forward rendering notes
56// The main initial reference material used was this rather accessible article:
57// http://www.aortiz.me/2018/12/21/CG.html
58// Some inspiration was taken from “Practical Clustered Shading” which is part 2 of:
59// https://efficientshading.com/2015/01/01/real-time-many-light-management-and-shadows-with-clustered-shading/
60// (Also note that Part 3 of the above shows how we could support the shadow mapping for many lights.)
61// The z-slicing method mentioned in the aortiz article is originally from Tiago Sousa's Siggraph 2016 talk about Doom 2016:
62// http://advances.realtimerendering.com/s2016/Siggraph2016_idTech6.pdf
63
64/// Configure the far z-plane mode used for the furthest depth slice for clustered forward
65/// rendering
66#[derive(Debug, Copy, Clone, Reflect)]
67pub enum ClusterFarZMode {
68    /// Calculate the required maximum z-depth based on currently visible
69    /// clusterable objects.  Makes better use of available clusters, speeding
70    /// up GPU lighting operations at the expense of some CPU time and using
71    /// more indices in the clusterable object index lists.
72    MaxClusterableObjectRange,
73    /// Constant max z-depth
74    Constant(f32),
75}
76
77/// Configure the depth-slicing strategy for clustered forward rendering
78#[derive(Debug, Copy, Clone, Reflect)]
79#[reflect(Default)]
80pub struct ClusterZConfig {
81    /// Far `Z` plane of the first depth slice
82    pub first_slice_depth: f32,
83    /// Strategy for how to evaluate the far `Z` plane of the furthest depth slice
84    pub far_z_mode: ClusterFarZMode,
85}
86
87/// Configuration of the clustering strategy for clustered forward rendering
88#[derive(Debug, Copy, Clone, Component, Reflect)]
89#[reflect(Component, Debug, Default)]
90pub enum ClusterConfig {
91    /// Disable cluster calculations for this view
92    None,
93    /// One single cluster. Optimal for low-light complexity scenes or scenes where
94    /// most lights affect the entire scene.
95    Single,
96    /// Explicit `X`, `Y` and `Z` counts (may yield non-square `X/Y` clusters depending on the aspect ratio)
97    XYZ {
98        dimensions: UVec3,
99        z_config: ClusterZConfig,
100        /// Specify if clusters should automatically resize in `X/Y` if there is a risk of exceeding
101        /// the available cluster-object index limit
102        dynamic_resizing: bool,
103    },
104    /// Fixed number of `Z` slices, `X` and `Y` calculated to give square clusters
105    /// with at most total clusters. For top-down games where lights will generally always be within a
106    /// short depth range, it may be useful to use this configuration with 1 or few `Z` slices. This
107    /// would reduce the number of lights per cluster by distributing more clusters in screen space
108    /// `X/Y` which matches how lights are distributed in the scene.
109    FixedZ {
110        total: u32,
111        z_slices: u32,
112        z_config: ClusterZConfig,
113        /// Specify if clusters should automatically resize in `X/Y` if there is a risk of exceeding
114        /// the available clusterable object index limit
115        dynamic_resizing: bool,
116    },
117}
118
119#[derive(Component, Debug, Default)]
120pub struct Clusters {
121    /// Tile size
122    pub(crate) tile_size: UVec2,
123    /// Number of clusters in `X` / `Y` / `Z` in the view frustum
124    pub(crate) dimensions: UVec3,
125    /// Distance to the far plane of the first depth slice. The first depth slice is special
126    /// and explicitly-configured to avoid having unnecessarily many slices close to the camera.
127    pub(crate) near: f32,
128    pub(crate) far: f32,
129    pub(crate) clusterable_objects: Vec<VisibleClusterableObjects>,
130}
131
132#[derive(Clone, Component, Debug, Default)]
133pub struct VisibleClusterableObjects {
134    pub(crate) entities: Vec<Entity>,
135    pub point_light_count: usize,
136    pub spot_light_count: usize,
137}
138
139#[derive(Resource, Default)]
140pub struct GlobalVisibleClusterableObjects {
141    pub(crate) entities: HashSet<Entity>,
142}
143
144#[derive(Resource)]
145pub struct GlobalClusterableObjectMeta {
146    pub gpu_clusterable_objects: GpuClusterableObjects,
147    pub entity_to_index: EntityHashMap<usize>,
148}
149
150#[derive(Copy, Clone, ShaderType, Default, Debug)]
151pub struct GpuClusterableObject {
152    // For point lights: the lower-right 2x2 values of the projection matrix [2][2] [2][3] [3][2] [3][3]
153    // For spot lights: 2 components of the direction (x,z), spot_scale and spot_offset
154    pub(crate) light_custom_data: Vec4,
155    pub(crate) color_inverse_square_range: Vec4,
156    pub(crate) position_radius: Vec4,
157    pub(crate) flags: u32,
158    pub(crate) shadow_depth_bias: f32,
159    pub(crate) shadow_normal_bias: f32,
160    pub(crate) spot_light_tan_angle: f32,
161    pub(crate) soft_shadow_size: f32,
162    pub(crate) shadow_map_near_z: f32,
163    pub(crate) pad_a: f32,
164    pub(crate) pad_b: f32,
165}
166
167pub enum GpuClusterableObjects {
168    Uniform(UniformBuffer<GpuClusterableObjectsUniform>),
169    Storage(StorageBuffer<GpuClusterableObjectsStorage>),
170}
171
172#[derive(ShaderType)]
173pub struct GpuClusterableObjectsUniform {
174    data: Box<[GpuClusterableObject; MAX_UNIFORM_BUFFER_CLUSTERABLE_OBJECTS]>,
175}
176
177#[derive(ShaderType, Default)]
178pub struct GpuClusterableObjectsStorage {
179    #[size(runtime)]
180    data: Vec<GpuClusterableObject>,
181}
182
183#[derive(Component)]
184pub struct ExtractedClusterConfig {
185    /// Special near value for cluster calculations
186    pub(crate) near: f32,
187    pub(crate) far: f32,
188    /// Number of clusters in `X` / `Y` / `Z` in the view frustum
189    pub(crate) dimensions: UVec3,
190}
191
192enum ExtractedClusterableObjectElement {
193    ClusterHeader(u32, u32),
194    ClusterableObjectEntity(Entity),
195}
196
197#[derive(Component)]
198pub struct ExtractedClusterableObjects {
199    data: Vec<ExtractedClusterableObjectElement>,
200}
201
202#[derive(ShaderType)]
203struct GpuClusterOffsetsAndCountsUniform {
204    data: Box<[UVec4; ViewClusterBindings::MAX_UNIFORM_ITEMS]>,
205}
206
207#[derive(ShaderType, Default)]
208struct GpuClusterableObjectIndexListsStorage {
209    #[size(runtime)]
210    data: Vec<u32>,
211}
212
213#[derive(ShaderType, Default)]
214struct GpuClusterOffsetsAndCountsStorage {
215    #[size(runtime)]
216    data: Vec<UVec4>,
217}
218
219enum ViewClusterBuffers {
220    Uniform {
221        // NOTE: UVec4 is because all arrays in Std140 layout have 16-byte alignment
222        clusterable_object_index_lists: UniformBuffer<GpuClusterableObjectIndexListsUniform>,
223        // NOTE: UVec4 is because all arrays in Std140 layout have 16-byte alignment
224        cluster_offsets_and_counts: UniformBuffer<GpuClusterOffsetsAndCountsUniform>,
225    },
226    Storage {
227        clusterable_object_index_lists: StorageBuffer<GpuClusterableObjectIndexListsStorage>,
228        cluster_offsets_and_counts: StorageBuffer<GpuClusterOffsetsAndCountsStorage>,
229    },
230}
231
232#[derive(Component)]
233pub struct ViewClusterBindings {
234    n_indices: usize,
235    n_offsets: usize,
236    buffers: ViewClusterBuffers,
237}
238
239impl Default for ClusterZConfig {
240    fn default() -> Self {
241        Self {
242            first_slice_depth: 5.0,
243            far_z_mode: ClusterFarZMode::MaxClusterableObjectRange,
244        }
245    }
246}
247
248impl Default for ClusterConfig {
249    fn default() -> Self {
250        // 24 depth slices, square clusters with at most 4096 total clusters
251        // use max light distance as clusters max `Z`-depth, first slice extends to 5.0
252        Self::FixedZ {
253            total: 4096,
254            z_slices: 24,
255            z_config: ClusterZConfig::default(),
256            dynamic_resizing: true,
257        }
258    }
259}
260
261impl ClusterConfig {
262    fn dimensions_for_screen_size(&self, screen_size: UVec2) -> UVec3 {
263        match &self {
264            ClusterConfig::None => UVec3::ZERO,
265            ClusterConfig::Single => UVec3::ONE,
266            ClusterConfig::XYZ { dimensions, .. } => *dimensions,
267            ClusterConfig::FixedZ {
268                total, z_slices, ..
269            } => {
270                let aspect_ratio: f32 = AspectRatio::try_from_pixels(screen_size.x, screen_size.y)
271                    .expect("Failed to calculate aspect ratio for Cluster: screen dimensions must be positive, non-zero values")
272                    .ratio();
273                let mut z_slices = *z_slices;
274                if *total < z_slices {
275                    warn!("ClusterConfig has more z-slices than total clusters!");
276                    z_slices = *total;
277                }
278                let per_layer = *total as f32 / z_slices as f32;
279
280                let y = f32::sqrt(per_layer / aspect_ratio);
281
282                let mut x = (y * aspect_ratio) as u32;
283                let mut y = y as u32;
284
285                // check extremes
286                if x == 0 {
287                    x = 1;
288                    y = per_layer as u32;
289                }
290                if y == 0 {
291                    x = per_layer as u32;
292                    y = 1;
293                }
294
295                UVec3::new(x, y, z_slices)
296            }
297        }
298    }
299
300    fn first_slice_depth(&self) -> f32 {
301        match self {
302            ClusterConfig::None | ClusterConfig::Single => 0.0,
303            ClusterConfig::XYZ { z_config, .. } | ClusterConfig::FixedZ { z_config, .. } => {
304                z_config.first_slice_depth
305            }
306        }
307    }
308
309    fn far_z_mode(&self) -> ClusterFarZMode {
310        match self {
311            ClusterConfig::None => ClusterFarZMode::Constant(0.0),
312            ClusterConfig::Single => ClusterFarZMode::MaxClusterableObjectRange,
313            ClusterConfig::XYZ { z_config, .. } | ClusterConfig::FixedZ { z_config, .. } => {
314                z_config.far_z_mode
315            }
316        }
317    }
318
319    fn dynamic_resizing(&self) -> bool {
320        match self {
321            ClusterConfig::None | ClusterConfig::Single => false,
322            ClusterConfig::XYZ {
323                dynamic_resizing, ..
324            }
325            | ClusterConfig::FixedZ {
326                dynamic_resizing, ..
327            } => *dynamic_resizing,
328        }
329    }
330}
331
332impl Clusters {
333    fn update(&mut self, screen_size: UVec2, requested_dimensions: UVec3) {
334        debug_assert!(
335            requested_dimensions.x > 0 && requested_dimensions.y > 0 && requested_dimensions.z > 0
336        );
337
338        let tile_size = (screen_size.as_vec2() / requested_dimensions.xy().as_vec2())
339            .ceil()
340            .as_uvec2()
341            .max(UVec2::ONE);
342        self.tile_size = tile_size;
343        self.dimensions = (screen_size.as_vec2() / tile_size.as_vec2())
344            .ceil()
345            .as_uvec2()
346            .extend(requested_dimensions.z)
347            .max(UVec3::ONE);
348
349        // NOTE: Maximum 4096 clusters due to uniform buffer size constraints
350        debug_assert!(self.dimensions.x * self.dimensions.y * self.dimensions.z <= 4096);
351    }
352    fn clear(&mut self) {
353        self.tile_size = UVec2::ONE;
354        self.dimensions = UVec3::ZERO;
355        self.near = 0.0;
356        self.far = 0.0;
357        self.clusterable_objects.clear();
358    }
359}
360
361pub fn add_clusters(
362    mut commands: Commands,
363    cameras: Query<(Entity, Option<&ClusterConfig>, &Camera), (Without<Clusters>, With<Camera3d>)>,
364) {
365    for (entity, config, camera) in &cameras {
366        if !camera.is_active {
367            continue;
368        }
369
370        let config = config.copied().unwrap_or_default();
371        // actual settings here don't matter - they will be overwritten in
372        // `assign_objects_to_clusters``
373        commands
374            .entity(entity)
375            .insert((Clusters::default(), config));
376    }
377}
378
379impl VisibleClusterableObjects {
380    #[inline]
381    pub fn iter(&self) -> impl DoubleEndedIterator<Item = &Entity> {
382        self.entities.iter()
383    }
384
385    #[inline]
386    pub fn len(&self) -> usize {
387        self.entities.len()
388    }
389
390    #[inline]
391    pub fn is_empty(&self) -> bool {
392        self.entities.is_empty()
393    }
394}
395
396impl GlobalVisibleClusterableObjects {
397    #[inline]
398    pub fn iter(&self) -> impl Iterator<Item = &Entity> {
399        self.entities.iter()
400    }
401
402    #[inline]
403    pub fn contains(&self, entity: Entity) -> bool {
404        self.entities.contains(&entity)
405    }
406}
407
408impl FromWorld for GlobalClusterableObjectMeta {
409    fn from_world(world: &mut World) -> Self {
410        Self::new(
411            world
412                .resource::<RenderDevice>()
413                .get_supported_read_only_binding_type(CLUSTERED_FORWARD_STORAGE_BUFFER_COUNT),
414        )
415    }
416}
417
418impl GlobalClusterableObjectMeta {
419    pub fn new(buffer_binding_type: BufferBindingType) -> Self {
420        Self {
421            gpu_clusterable_objects: GpuClusterableObjects::new(buffer_binding_type),
422            entity_to_index: EntityHashMap::default(),
423        }
424    }
425}
426
427impl GpuClusterableObjects {
428    fn new(buffer_binding_type: BufferBindingType) -> Self {
429        match buffer_binding_type {
430            BufferBindingType::Storage { .. } => Self::storage(),
431            BufferBindingType::Uniform => Self::uniform(),
432        }
433    }
434
435    fn uniform() -> Self {
436        Self::Uniform(UniformBuffer::default())
437    }
438
439    fn storage() -> Self {
440        Self::Storage(StorageBuffer::default())
441    }
442
443    pub(crate) fn set(&mut self, mut clusterable_objects: Vec<GpuClusterableObject>) {
444        match self {
445            GpuClusterableObjects::Uniform(buffer) => {
446                let len = clusterable_objects
447                    .len()
448                    .min(MAX_UNIFORM_BUFFER_CLUSTERABLE_OBJECTS);
449                let src = &clusterable_objects[..len];
450                let dst = &mut buffer.get_mut().data[..len];
451                dst.copy_from_slice(src);
452            }
453            GpuClusterableObjects::Storage(buffer) => {
454                buffer.get_mut().data.clear();
455                buffer.get_mut().data.append(&mut clusterable_objects);
456            }
457        }
458    }
459
460    pub(crate) fn write_buffer(
461        &mut self,
462        render_device: &RenderDevice,
463        render_queue: &RenderQueue,
464    ) {
465        match self {
466            GpuClusterableObjects::Uniform(buffer) => {
467                buffer.write_buffer(render_device, render_queue);
468            }
469            GpuClusterableObjects::Storage(buffer) => {
470                buffer.write_buffer(render_device, render_queue);
471            }
472        }
473    }
474
475    pub fn binding(&self) -> Option<BindingResource> {
476        match self {
477            GpuClusterableObjects::Uniform(buffer) => buffer.binding(),
478            GpuClusterableObjects::Storage(buffer) => buffer.binding(),
479        }
480    }
481
482    pub fn min_size(buffer_binding_type: BufferBindingType) -> NonZero<u64> {
483        match buffer_binding_type {
484            BufferBindingType::Storage { .. } => GpuClusterableObjectsStorage::min_size(),
485            BufferBindingType::Uniform => GpuClusterableObjectsUniform::min_size(),
486        }
487    }
488}
489
490impl Default for GpuClusterableObjectsUniform {
491    fn default() -> Self {
492        Self {
493            data: Box::new(
494                [GpuClusterableObject::default(); MAX_UNIFORM_BUFFER_CLUSTERABLE_OBJECTS],
495            ),
496        }
497    }
498}
499
500pub(crate) struct ClusterableObjectOrderData<'a> {
501    pub(crate) entity: &'a Entity,
502    pub(crate) shadows_enabled: &'a bool,
503    pub(crate) is_volumetric_light: &'a bool,
504    pub(crate) is_spot_light: &'a bool,
505}
506
507#[allow(clippy::too_many_arguments)]
508// Sort clusterable objects by:
509//
510// * point-light vs spot-light, so that we can iterate point lights and spot
511//   lights in contiguous blocks in the fragment shader,
512//
513// * then those with shadows enabled first, so that the index can be used to
514//   render at most `point_light_shadow_maps_count` point light shadows and
515//   `spot_light_shadow_maps_count` spot light shadow maps,
516//
517// * then by entity as a stable key to ensure that a consistent set of
518//   clusterable objects are chosen if the clusterable object count limit is
519//   exceeded.
520pub(crate) fn clusterable_object_order(
521    a: ClusterableObjectOrderData,
522    b: ClusterableObjectOrderData,
523) -> core::cmp::Ordering {
524    a.is_spot_light
525        .cmp(b.is_spot_light) // pointlights before spot lights
526        .then_with(|| b.shadows_enabled.cmp(a.shadows_enabled)) // shadow casters before non-casters
527        .then_with(|| b.is_volumetric_light.cmp(a.is_volumetric_light)) // volumetric lights before non-volumetric lights
528        .then_with(|| a.entity.cmp(b.entity)) // stable
529}
530
531/// Extracts clusters from the main world from the render world.
532pub fn extract_clusters(
533    mut commands: Commands,
534    views: Extract<Query<(RenderEntity, &Clusters, &Camera)>>,
535    mapper: Extract<Query<RenderEntity>>,
536) {
537    for (entity, clusters, camera) in &views {
538        let mut entity_commands = commands
539            .get_entity(entity)
540            .expect("Clusters entity wasn't synced.");
541        if !camera.is_active {
542            entity_commands.remove::<(ExtractedClusterableObjects, ExtractedClusterConfig)>();
543            continue;
544        }
545
546        let num_entities: usize = clusters
547            .clusterable_objects
548            .iter()
549            .map(|l| l.entities.len())
550            .sum();
551        let mut data = Vec::with_capacity(clusters.clusterable_objects.len() + num_entities);
552        for cluster_objects in &clusters.clusterable_objects {
553            data.push(ExtractedClusterableObjectElement::ClusterHeader(
554                cluster_objects.point_light_count as u32,
555                cluster_objects.spot_light_count as u32,
556            ));
557            for clusterable_entity in &cluster_objects.entities {
558                if let Ok(entity) = mapper.get(*clusterable_entity) {
559                    data.push(ExtractedClusterableObjectElement::ClusterableObjectEntity(
560                        entity,
561                    ));
562                }
563            }
564        }
565
566        entity_commands.insert((
567            ExtractedClusterableObjects { data },
568            ExtractedClusterConfig {
569                near: clusters.near,
570                far: clusters.far,
571                dimensions: clusters.dimensions,
572            },
573        ));
574    }
575}
576
577pub fn prepare_clusters(
578    mut commands: Commands,
579    render_device: Res<RenderDevice>,
580    render_queue: Res<RenderQueue>,
581    mesh_pipeline: Res<MeshPipeline>,
582    global_clusterable_object_meta: Res<GlobalClusterableObjectMeta>,
583    views: Query<(Entity, &ExtractedClusterableObjects)>,
584) {
585    let render_device = render_device.into_inner();
586    let supports_storage_buffers = matches!(
587        mesh_pipeline.clustered_forward_buffer_binding_type,
588        BufferBindingType::Storage { .. }
589    );
590    for (entity, extracted_clusters) in &views {
591        let mut view_clusters_bindings =
592            ViewClusterBindings::new(mesh_pipeline.clustered_forward_buffer_binding_type);
593        view_clusters_bindings.clear();
594
595        for record in &extracted_clusters.data {
596            match record {
597                ExtractedClusterableObjectElement::ClusterHeader(
598                    point_light_count,
599                    spot_light_count,
600                ) => {
601                    let offset = view_clusters_bindings.n_indices();
602                    view_clusters_bindings.push_offset_and_counts(
603                        offset,
604                        *point_light_count as usize,
605                        *spot_light_count as usize,
606                    );
607                }
608                ExtractedClusterableObjectElement::ClusterableObjectEntity(entity) => {
609                    if let Some(clusterable_object_index) =
610                        global_clusterable_object_meta.entity_to_index.get(entity)
611                    {
612                        if view_clusters_bindings.n_indices() >= ViewClusterBindings::MAX_INDICES
613                            && !supports_storage_buffers
614                        {
615                            warn!(
616                                "Clusterable object index lists are full! The clusterable \
617                                 objects in the view are present in too many clusters."
618                            );
619                            break;
620                        }
621                        view_clusters_bindings.push_index(*clusterable_object_index);
622                    }
623                }
624            }
625        }
626
627        view_clusters_bindings.write_buffers(render_device, &render_queue);
628
629        commands.entity(entity).insert(view_clusters_bindings);
630    }
631}
632
633impl ViewClusterBindings {
634    pub const MAX_OFFSETS: usize = 16384 / 4;
635    const MAX_UNIFORM_ITEMS: usize = Self::MAX_OFFSETS / 4;
636    pub const MAX_INDICES: usize = 16384;
637
638    pub fn new(buffer_binding_type: BufferBindingType) -> Self {
639        Self {
640            n_indices: 0,
641            n_offsets: 0,
642            buffers: ViewClusterBuffers::new(buffer_binding_type),
643        }
644    }
645
646    pub fn clear(&mut self) {
647        match &mut self.buffers {
648            ViewClusterBuffers::Uniform {
649                clusterable_object_index_lists,
650                cluster_offsets_and_counts,
651            } => {
652                *clusterable_object_index_lists.get_mut().data =
653                    [UVec4::ZERO; Self::MAX_UNIFORM_ITEMS];
654                *cluster_offsets_and_counts.get_mut().data = [UVec4::ZERO; Self::MAX_UNIFORM_ITEMS];
655            }
656            ViewClusterBuffers::Storage {
657                clusterable_object_index_lists,
658                cluster_offsets_and_counts,
659                ..
660            } => {
661                clusterable_object_index_lists.get_mut().data.clear();
662                cluster_offsets_and_counts.get_mut().data.clear();
663            }
664        }
665    }
666
667    pub fn push_offset_and_counts(&mut self, offset: usize, point_count: usize, spot_count: usize) {
668        match &mut self.buffers {
669            ViewClusterBuffers::Uniform {
670                cluster_offsets_and_counts,
671                ..
672            } => {
673                let array_index = self.n_offsets >> 2; // >> 2 is equivalent to / 4
674                if array_index >= Self::MAX_UNIFORM_ITEMS {
675                    warn!("cluster offset and count out of bounds!");
676                    return;
677                }
678                let component = self.n_offsets & ((1 << 2) - 1);
679                let packed = pack_offset_and_counts(offset, point_count, spot_count);
680
681                cluster_offsets_and_counts.get_mut().data[array_index][component] = packed;
682            }
683            ViewClusterBuffers::Storage {
684                cluster_offsets_and_counts,
685                ..
686            } => {
687                cluster_offsets_and_counts.get_mut().data.push(UVec4::new(
688                    offset as u32,
689                    point_count as u32,
690                    spot_count as u32,
691                    0,
692                ));
693            }
694        }
695
696        self.n_offsets += 1;
697    }
698
699    pub fn n_indices(&self) -> usize {
700        self.n_indices
701    }
702
703    pub fn push_index(&mut self, index: usize) {
704        match &mut self.buffers {
705            ViewClusterBuffers::Uniform {
706                clusterable_object_index_lists,
707                ..
708            } => {
709                let array_index = self.n_indices >> 4; // >> 4 is equivalent to / 16
710                let component = (self.n_indices >> 2) & ((1 << 2) - 1);
711                let sub_index = self.n_indices & ((1 << 2) - 1);
712                let index = index as u32;
713
714                clusterable_object_index_lists.get_mut().data[array_index][component] |=
715                    index << (8 * sub_index);
716            }
717            ViewClusterBuffers::Storage {
718                clusterable_object_index_lists,
719                ..
720            } => {
721                clusterable_object_index_lists
722                    .get_mut()
723                    .data
724                    .push(index as u32);
725            }
726        }
727
728        self.n_indices += 1;
729    }
730
731    pub fn write_buffers(&mut self, render_device: &RenderDevice, render_queue: &RenderQueue) {
732        match &mut self.buffers {
733            ViewClusterBuffers::Uniform {
734                clusterable_object_index_lists,
735                cluster_offsets_and_counts,
736            } => {
737                clusterable_object_index_lists.write_buffer(render_device, render_queue);
738                cluster_offsets_and_counts.write_buffer(render_device, render_queue);
739            }
740            ViewClusterBuffers::Storage {
741                clusterable_object_index_lists,
742                cluster_offsets_and_counts,
743            } => {
744                clusterable_object_index_lists.write_buffer(render_device, render_queue);
745                cluster_offsets_and_counts.write_buffer(render_device, render_queue);
746            }
747        }
748    }
749
750    pub fn clusterable_object_index_lists_binding(&self) -> Option<BindingResource> {
751        match &self.buffers {
752            ViewClusterBuffers::Uniform {
753                clusterable_object_index_lists,
754                ..
755            } => clusterable_object_index_lists.binding(),
756            ViewClusterBuffers::Storage {
757                clusterable_object_index_lists,
758                ..
759            } => clusterable_object_index_lists.binding(),
760        }
761    }
762
763    pub fn offsets_and_counts_binding(&self) -> Option<BindingResource> {
764        match &self.buffers {
765            ViewClusterBuffers::Uniform {
766                cluster_offsets_and_counts,
767                ..
768            } => cluster_offsets_and_counts.binding(),
769            ViewClusterBuffers::Storage {
770                cluster_offsets_and_counts,
771                ..
772            } => cluster_offsets_and_counts.binding(),
773        }
774    }
775
776    pub fn min_size_clusterable_object_index_lists(
777        buffer_binding_type: BufferBindingType,
778    ) -> NonZero<u64> {
779        match buffer_binding_type {
780            BufferBindingType::Storage { .. } => GpuClusterableObjectIndexListsStorage::min_size(),
781            BufferBindingType::Uniform => GpuClusterableObjectIndexListsUniform::min_size(),
782        }
783    }
784
785    pub fn min_size_cluster_offsets_and_counts(
786        buffer_binding_type: BufferBindingType,
787    ) -> NonZero<u64> {
788        match buffer_binding_type {
789            BufferBindingType::Storage { .. } => GpuClusterOffsetsAndCountsStorage::min_size(),
790            BufferBindingType::Uniform => GpuClusterOffsetsAndCountsUniform::min_size(),
791        }
792    }
793}
794
795impl ViewClusterBuffers {
796    fn new(buffer_binding_type: BufferBindingType) -> Self {
797        match buffer_binding_type {
798            BufferBindingType::Storage { .. } => Self::storage(),
799            BufferBindingType::Uniform => Self::uniform(),
800        }
801    }
802
803    fn uniform() -> Self {
804        ViewClusterBuffers::Uniform {
805            clusterable_object_index_lists: UniformBuffer::default(),
806            cluster_offsets_and_counts: UniformBuffer::default(),
807        }
808    }
809
810    fn storage() -> Self {
811        ViewClusterBuffers::Storage {
812            clusterable_object_index_lists: StorageBuffer::default(),
813            cluster_offsets_and_counts: StorageBuffer::default(),
814        }
815    }
816}
817
818// NOTE: With uniform buffer max binding size as 16384 bytes
819// that means we can fit 204 clusterable objects in one uniform
820// buffer, which means the count can be at most 204 so it
821// needs 9 bits.
822// The array of indices can also use u8 and that means the
823// offset in to the array of indices needs to be able to address
824// 16384 values. log2(16384) = 14 bits.
825// We use 32 bits to store the offset and counts so
826// we pack the offset into the upper 14 bits of a u32,
827// the point light count into bits 9-17, and the spot light count into bits 0-8.
828//  [ 31     ..     18 | 17      ..      9 | 8       ..     0 ]
829//  [      offset      | point light count | spot light count ]
830// NOTE: This assumes CPU and GPU endianness are the same which is true
831// for all common and tested x86/ARM CPUs and AMD/NVIDIA/Intel/Apple/etc GPUs
832fn pack_offset_and_counts(offset: usize, point_count: usize, spot_count: usize) -> u32 {
833    ((offset as u32 & CLUSTER_OFFSET_MASK) << (CLUSTER_COUNT_SIZE * 2))
834        | (point_count as u32 & CLUSTER_COUNT_MASK) << CLUSTER_COUNT_SIZE
835        | (spot_count as u32 & CLUSTER_COUNT_MASK)
836}
837
838#[derive(ShaderType)]
839struct GpuClusterableObjectIndexListsUniform {
840    data: Box<[UVec4; ViewClusterBindings::MAX_UNIFORM_ITEMS]>,
841}
842
843// NOTE: Assert at compile time that GpuClusterableObjectIndexListsUniform
844// fits within the maximum uniform buffer binding size
845const _: () = assert!(GpuClusterableObjectIndexListsUniform::SHADER_SIZE.get() <= 16384);
846
847impl Default for GpuClusterableObjectIndexListsUniform {
848    fn default() -> Self {
849        Self {
850            data: Box::new([UVec4::ZERO; ViewClusterBindings::MAX_UNIFORM_ITEMS]),
851        }
852    }
853}
854
855impl Default for GpuClusterOffsetsAndCountsUniform {
856    fn default() -> Self {
857        Self {
858            data: Box::new([UVec4::ZERO; ViewClusterBindings::MAX_UNIFORM_ITEMS]),
859        }
860    }
861}