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