Skip to main content

bevy_pbr/cluster/
mod.rs

1use core::{iter, num::NonZero};
2
3use bevy_camera::Camera;
4use bevy_ecs::{entity::EntityHashMap, prelude::*};
5use bevy_light::{
6    cluster::{
7        ClusterableObjectCounts, ClusterableObjects, Clusters, GlobalClusterGpuSettings,
8        GlobalClusterSettings,
9    },
10    ClusteredDecal, EnvironmentMapLight, IrradianceVolume, PointLight, SpotLight,
11};
12use bevy_math::{uvec4, UVec3, UVec4, Vec4};
13use bevy_render::{
14    render_resource::{
15        BindingResource, BufferBindingType, BufferUsages, DownlevelFlags, RawBufferVec, ShaderSize,
16        ShaderType, StorageBuffer, UniformBuffer,
17    },
18    renderer::{RenderAdapter, RenderDevice, RenderQueue},
19    sync_world::{MainEntity, RenderEntity},
20    Extract,
21};
22use bytemuck::{Pod, Zeroable};
23use tracing::{error, info, trace, warn};
24
25use crate::{MeshPipeline, RenderViewLightProbes};
26
27pub(crate) mod gpu;
28
29// NOTE: this must be kept in sync with the same constants in
30// `mesh_view_types.wgsl`.
31pub const MAX_UNIFORM_BUFFER_CLUSTERABLE_OBJECTS: usize = 204;
32// Make sure that the clusterable object buffer doesn't overflow the maximum
33// size of a UBO on WebGL 2.
34const _: () =
35    assert!(size_of::<GpuClusteredLight>() * MAX_UNIFORM_BUFFER_CLUSTERABLE_OBJECTS <= 16384);
36
37// NOTE: Clustered-forward rendering requires 3 storage buffer bindings so check that
38// at least that many are supported using this constant and SupportedBindingType::from_device()
39pub const CLUSTERED_FORWARD_STORAGE_BUFFER_COUNT: u32 = 3;
40
41// this must match CLUSTER_COUNT_SIZE in pbr.wgsl
42// and must be large enough to contain MAX_UNIFORM_BUFFER_CLUSTERABLE_OBJECTS
43const CLUSTER_COUNT_SIZE: u32 = 9;
44
45const CLUSTER_OFFSET_MASK: u32 = (1 << (32 - (CLUSTER_COUNT_SIZE * 2))) - 1;
46const CLUSTER_COUNT_MASK: u32 = (1 << CLUSTER_COUNT_SIZE) - 1;
47
48/// The initial capacity of the Z slice list.
49///
50/// The application can override this by setting
51/// [`GlobalClusterGpuSettings::initial_z_slice_list_capacity`].
52pub const GPU_CLUSTERING_INITIAL_Z_SLICE_LIST_CAPACITY: usize = 1024;
53
54/// The initial capacity of the clustered object index list.
55///
56/// The application can override this by setting
57/// [`GlobalClusterGpuSettings::initial_index_list_capacity`].
58pub const GPU_CLUSTERING_INITIAL_INDEX_LIST_CAPACITY: usize = 65536;
59
60/// Creates the default [`GlobalClusterSettings`] resource.
61pub(crate) fn make_global_cluster_settings(world: &World) -> GlobalClusterSettings {
62    let device = world.resource::<RenderDevice>();
63    let adapter = world.resource::<RenderAdapter>();
64    let clustered_decals_are_usable =
65        crate::decal::clustered::clustered_decals_are_usable(device, adapter);
66    let supports_storage_buffers = matches!(
67        device.get_supported_read_only_binding_type(CLUSTERED_FORWARD_STORAGE_BUFFER_COUNT),
68        BufferBindingType::Storage { .. }
69    );
70
71    // We need to support compute shaders to use GPU clustering. To deal with
72    // the `WGPU_SETTINGS_PRIO="webgl2"` environment setting, we check the
73    // `RenderDevice` limits in addition to the `RenderAdapter`.
74    //
75    // Some android devices report the capabilities and limits wrong, so we can't rely on them.
76    // See <https://github.com/bevyengine/bevy/issues/23208> for Android issues
77    //
78    // GPU clustering doesn't work properly on iOS simulator. See https://github.com/bevyengine/bevy/issues/23428
79    let gpu_clustering_supported = !(cfg!(target_os = "android") || cfg!(target_abi = "sim"))
80        && adapter
81            .get_downlevel_capabilities()
82            .flags
83            .contains(DownlevelFlags::COMPUTE_SHADERS)
84        && device.limits().max_storage_buffers_per_shader_stage > 0;
85
86    let gpu_clustering = if gpu_clustering_supported {
87        info!("GPU clustering is supported on this device.");
88        Some(GlobalClusterGpuSettings {
89            initial_z_slice_list_capacity: GPU_CLUSTERING_INITIAL_Z_SLICE_LIST_CAPACITY,
90            initial_index_list_capacity: GPU_CLUSTERING_INITIAL_INDEX_LIST_CAPACITY,
91        })
92    } else {
93        info!("GPU clustering isn't supported on this device; falling back to CPU clustering.");
94        None
95    };
96
97    GlobalClusterSettings {
98        supports_storage_buffers,
99        clustered_decals_are_usable,
100        gpu_clustering,
101        max_uniform_buffer_clusterable_objects: MAX_UNIFORM_BUFFER_CLUSTERABLE_OBJECTS,
102        view_cluster_bindings_max_indices: ViewClusterBindings::MAX_INDICES,
103    }
104}
105
106/// The GPU-side structure that stores information about a clustered light
107/// (point or spot).
108///
109/// This is *not* used for other clustered objects, such as light probes.
110#[derive(Copy, Clone, ShaderType, Default, Pod, Zeroable, Debug)]
111#[repr(C)]
112pub struct GpuClusteredLight {
113    // For point lights: the lower-right 2x2 values of the projection matrix [2][2] [2][3] [3][2] [3][3]
114    // For spot lights: 2 components of the direction (x,z), spot_scale and spot_offset
115    pub(crate) light_custom_data: Vec4,
116    pub(crate) color_inverse_square_range: Vec4,
117    pub(crate) position_radius: Vec4,
118    pub(crate) flags: u32,
119    pub(crate) shadow_depth_bias: f32,
120    pub(crate) shadow_normal_bias: f32,
121    pub(crate) spot_light_tan_angle: f32,
122    pub(crate) soft_shadow_size: f32,
123    pub(crate) shadow_map_near_z: f32,
124    /// The decal applied to this light.
125    ///
126    /// Note that this is separate from clustered decals. Clustered decals have
127    /// their own structures and don't use [`GpuClusteredLight`].
128    pub(crate) decal_index: u32,
129    /// The radius of the range that the light affects, used for clustering.
130    pub(crate) range: f32,
131}
132
133/// Contains information about clusterable objects in the scene that's global:
134/// i.e. not specific to any view.
135#[derive(Resource)]
136pub struct GlobalClusterableObjectMeta {
137    /// GPU buffers that hold data about the clustered lights.
138    ///
139    /// This is only for lights. Data about other clusterable objects are stored
140    /// in other buffers.
141    pub gpu_clustered_lights: GpuClusteredLights,
142
143    /// Maps a *render-world* entity to the index in the appropriate list.
144    ///
145    /// Only clusterable objects that have render-world entities are in this
146    /// list! In particular, light probes (reflection probes and irradiance
147    /// volumes) are not.
148    pub entity_to_index: EntityHashMap<usize>,
149}
150
151/// GPU buffers that hold data about the clustered lights.
152///
153/// This is only for lights. Data about other clusterable objects are stored in
154/// other buffers.
155///
156/// This has two variants in order to handle platforms in which storage buffers
157/// aren't available.
158pub struct GpuClusteredLights {
159    data: RawBufferVec<GpuClusteredLight>,
160    is_storage_buffer: bool,
161}
162
163#[derive(Component)]
164pub struct ExtractedClusterConfig {
165    /// Special near value for cluster calculations
166    pub(crate) near: f32,
167    pub(crate) far: f32,
168    /// Number of clusters in `X` / `Y` / `Z` in the view frustum
169    pub(crate) dimensions: UVec3,
170}
171
172impl<'a> From<&'a Clusters> for ExtractedClusterConfig {
173    fn from(clusters: &'a Clusters) -> Self {
174        Self {
175            near: clusters.near,
176            far: clusters.far,
177            dimensions: clusters.dimensions,
178        }
179    }
180}
181
182/// A single command in the stream that [`extract_clusters_for_cpu_clustering`]
183/// produces.
184enum ExtractedClusterableObjectElement {
185    /// Marks the beginning of a new cluster.
186    ClusterHeader(ClusterableObjectCounts),
187    /// Represents a light.
188    ///
189    /// The given entity is the render-world entity.
190    Light(Entity),
191    /// Represents a reflection probe.
192    ///
193    /// The given entity is the main-world entity of the light probe, as light
194    /// probes don't have render world entities.
195    ReflectionProbe(MainEntity),
196    /// Represents an irradiance volume.
197    ///
198    /// The given entity is the main-world entity of the light probe, as light
199    /// probes don't have render world entities.
200    IrradianceVolume(MainEntity),
201    /// Represents a clustered decal.
202    ///
203    /// The given entity is the render-world entity.
204    Decal(Entity),
205}
206
207#[derive(Component)]
208pub struct ExtractedClusterableObjects {
209    data: Vec<ExtractedClusterableObjectElement>,
210}
211
212#[derive(ShaderType)]
213struct GpuClusterOffsetsAndCountsUniform {
214    data: Box<[UVec4; ViewClusterBindings::MAX_UNIFORM_ITEMS]>,
215}
216
217#[derive(ShaderType, Default)]
218struct GpuClusterableObjectIndexListsStorage {
219    #[shader(size(runtime))]
220    data: Vec<u32>,
221}
222
223#[derive(ShaderType, Default)]
224struct GpuClusterOffsetsAndCountsStorage {
225    /// The starting offset, followed by the number of point lights, spot
226    /// lights, reflection probes, and irradiance volumes in each cluster, in
227    /// that order. The remaining fields are filled with zeroes.
228    #[shader(size(runtime))]
229    data: Vec<GpuClusterOffsetAndCounts>,
230}
231
232/// The type we use for the offset and counts for each cluster.
233type GpuClusterOffsetAndCounts = [UVec4; 2];
234
235enum ViewClusterBuffers {
236    Uniform {
237        // NOTE: UVec4 is because all arrays in Std140 layout have 16-byte alignment
238        clusterable_object_index_lists: UniformBuffer<GpuClusterableObjectIndexListsUniform>,
239        // NOTE: UVec4 is because all arrays in Std140 layout have 16-byte alignment
240        cluster_offsets_and_counts: UniformBuffer<GpuClusterOffsetsAndCountsUniform>,
241    },
242    Storage {
243        clusterable_object_index_lists: StorageBuffer<GpuClusterableObjectIndexListsStorage>,
244        cluster_offsets_and_counts: StorageBuffer<GpuClusterOffsetsAndCountsStorage>,
245    },
246}
247
248#[derive(Component)]
249pub struct ViewClusterBindings {
250    n_indices: usize,
251    n_offsets: usize,
252    buffers: ViewClusterBuffers,
253}
254
255pub fn init_global_clusterable_object_meta(
256    mut commands: Commands,
257    render_device: Res<RenderDevice>,
258) {
259    commands.insert_resource(GlobalClusterableObjectMeta::new(
260        render_device.get_supported_read_only_binding_type(CLUSTERED_FORWARD_STORAGE_BUFFER_COUNT),
261    ));
262}
263
264impl GlobalClusterableObjectMeta {
265    pub fn new(buffer_binding_type: BufferBindingType) -> Self {
266        Self {
267            gpu_clustered_lights: GpuClusteredLights::new(buffer_binding_type),
268            entity_to_index: EntityHashMap::default(),
269        }
270    }
271}
272
273impl GpuClusteredLights {
274    fn new(buffer_binding_type: BufferBindingType) -> Self {
275        match buffer_binding_type {
276            BufferBindingType::Storage { .. } => Self::storage(),
277            BufferBindingType::Uniform => Self::uniform(),
278        }
279    }
280
281    fn uniform() -> Self {
282        GpuClusteredLights {
283            data: RawBufferVec::new(BufferUsages::UNIFORM),
284            is_storage_buffer: false,
285        }
286    }
287
288    fn storage() -> Self {
289        GpuClusteredLights {
290            data: RawBufferVec::new(BufferUsages::STORAGE),
291            is_storage_buffer: true,
292        }
293    }
294
295    pub(crate) fn clear(&mut self) {
296        self.data.clear();
297    }
298
299    pub(crate) fn len(&self) -> usize {
300        self.data.len()
301    }
302
303    pub(crate) fn add(&mut self, light: GpuClusteredLight) {
304        if self.is_storage_buffer || self.data.len() < MAX_UNIFORM_BUFFER_CLUSTERABLE_OBJECTS {
305            self.data.push(light);
306        }
307    }
308
309    pub(crate) fn write_buffer(
310        &mut self,
311        render_device: &RenderDevice,
312        render_queue: &RenderQueue,
313    ) {
314        if self.is_storage_buffer {
315            if self.data.is_empty() {
316                self.data.push(GpuClusteredLight::default());
317            }
318        } else {
319            while self.data.len() < MAX_UNIFORM_BUFFER_CLUSTERABLE_OBJECTS {
320                self.data.push(GpuClusteredLight::default());
321            }
322        }
323
324        self.data.write_buffer(render_device, render_queue);
325    }
326
327    pub fn binding(&self) -> Option<BindingResource<'_>> {
328        self.data.binding()
329    }
330
331    pub fn min_size(buffer_binding_type: BufferBindingType) -> NonZero<u64> {
332        match buffer_binding_type {
333            BufferBindingType::Storage { .. } => GpuClusteredLight::min_size(),
334            BufferBindingType::Uniform => NonZero::try_from(
335                u64::from(GpuClusteredLight::min_size())
336                    * MAX_UNIFORM_BUFFER_CLUSTERABLE_OBJECTS as u64,
337            )
338            .unwrap(),
339        }
340    }
341
342    pub fn max_clustered_lights(&self) -> Option<usize> {
343        if self.is_storage_buffer {
344            None
345        } else {
346            Some(MAX_UNIFORM_BUFFER_CLUSTERABLE_OBJECTS)
347        }
348    }
349}
350
351/// A shortcut for testing the type of a clusterable object.
352type ClusterExtractionMapperQueryFlags = (
353    Has<PointLight>,
354    Has<SpotLight>,
355    Has<EnvironmentMapLight>,
356    Has<IrradianceVolume>,
357    Has<ClusteredDecal>,
358);
359/// A shortcut for testing whether an entity is any type of clusterable object.
360type ClusterExtractionMapperQueryFilter = Or<(
361    With<PointLight>,
362    With<SpotLight>,
363    With<EnvironmentMapLight>,
364    With<IrradianceVolume>,
365    With<ClusteredDecal>,
366)>;
367
368/// A run condition that tests whether GPU clustering is enabled.
369///
370/// This is the version for use in extraction systems.
371pub fn gpu_clustering_is_enabled_during_extraction(
372    global_cluster_settings: Extract<Res<GlobalClusterSettings>>,
373) -> bool {
374    global_cluster_settings.gpu_clustering.is_some()
375}
376
377/// A run condition that tests whether GPU clustering is enabled.
378///
379/// This is the version for use in non-extraction systems.
380pub fn gpu_clustering_is_enabled(global_cluster_settings: Res<GlobalClusterSettings>) -> bool {
381    global_cluster_settings.gpu_clustering.is_some()
382}
383
384/// Extracts the clusters that the CPU produced into the render world.
385pub fn extract_clusters_for_cpu_clustering(
386    mut commands: Commands,
387    views: Extract<Query<(RenderEntity, &Clusters, &Camera)>>,
388    mapper: Extract<
389        Query<
390            (Option<&RenderEntity>, ClusterExtractionMapperQueryFlags),
391            ClusterExtractionMapperQueryFilter,
392        >,
393    >,
394    global_cluster_settings: Extract<Res<GlobalClusterSettings>>,
395) {
396    for (entity, clusters, camera) in &views {
397        let mut entity_commands = commands
398            .get_entity(entity)
399            .expect("Clusters entity wasn't synced.");
400        if !camera.is_active {
401            entity_commands.remove::<(ExtractedClusterableObjects, ExtractedClusterConfig)>();
402            continue;
403        }
404
405        let clusterable_objects = match clusters.clusterable_objects {
406            ClusterableObjects::Cpu(ref cpu_clusterable_objects) => cpu_clusterable_objects,
407            ClusterableObjects::Gpu => {
408                error!("Clusterable objects must have been in CPU mode if doing CPU clustering");
409                continue;
410            }
411        };
412
413        let mut data = vec![];
414        for cluster_objects in clusterable_objects {
415            data.push(ExtractedClusterableObjectElement::ClusterHeader(
416                cluster_objects.counts,
417            ));
418            for clusterable_entity in cluster_objects.iter() {
419                let Ok((
420                    maybe_render_entity,
421                    (
422                        is_point_light,
423                        is_spot_light,
424                        is_reflection_probe,
425                        is_irradiance_volume,
426                        is_clustered_decal,
427                    ),
428                )) = mapper.get(*clusterable_entity)
429                else {
430                    error!(
431                        "Couldn't find clustered object {:?} in the main world",
432                        clusterable_entity
433                    );
434                    continue;
435                };
436
437                if let Some(render_entity) = maybe_render_entity {
438                    if is_clustered_decal {
439                        data.push(ExtractedClusterableObjectElement::Decal(**render_entity));
440                    } else if is_point_light || is_spot_light {
441                        data.push(ExtractedClusterableObjectElement::Light(**render_entity));
442                    }
443                }
444                if is_reflection_probe {
445                    data.push(ExtractedClusterableObjectElement::ReflectionProbe(
446                        MainEntity::from(*clusterable_entity),
447                    ));
448                }
449                if is_irradiance_volume {
450                    data.push(ExtractedClusterableObjectElement::IrradianceVolume(
451                        MainEntity::from(*clusterable_entity),
452                    ));
453                }
454            }
455        }
456
457        entity_commands.insert((
458            ExtractedClusterableObjects { data },
459            ExtractedClusterConfig::from(clusters),
460        ));
461    }
462
463    commands.insert_resource(global_cluster_settings.clone());
464}
465
466/// Creates and populates the GPU buffers that store clusters when CPU
467/// clustering is being used.
468pub fn prepare_clusters_for_cpu_clustering(
469    mut commands: Commands,
470    render_device: Res<RenderDevice>,
471    render_queue: Res<RenderQueue>,
472    mesh_pipeline: Res<MeshPipeline>,
473    global_clusterable_object_meta: Res<GlobalClusterableObjectMeta>,
474    views: Query<(
475        Entity,
476        &ExtractedClusterableObjects,
477        Option<&RenderViewLightProbes<EnvironmentMapLight>>,
478        Option<&RenderViewLightProbes<IrradianceVolume>>,
479    )>,
480) {
481    let render_device = render_device.into_inner();
482    let supports_storage_buffers = matches!(
483        mesh_pipeline.clustered_forward_buffer_binding_type,
484        BufferBindingType::Storage { .. }
485    );
486    for (entity, extracted_clusters, maybe_environment_maps, maybe_irradiance_volumes) in &views {
487        let mut view_clusters_bindings =
488            ViewClusterBindings::new(mesh_pipeline.clustered_forward_buffer_binding_type);
489        view_clusters_bindings.clear();
490
491        for record in &extracted_clusters.data {
492            match record {
493                ExtractedClusterableObjectElement::ClusterHeader(counts) => {
494                    let offset = view_clusters_bindings.n_indices();
495                    view_clusters_bindings.push_offset_and_counts(offset, counts);
496                }
497
498                ExtractedClusterableObjectElement::Light(entity)
499                | ExtractedClusterableObjectElement::Decal(entity) => {
500                    if let Some(clusterable_object_index) =
501                        global_clusterable_object_meta.entity_to_index.get(entity)
502                    {
503                        if view_clusters_bindings.n_indices() >= ViewClusterBindings::MAX_INDICES
504                            && !supports_storage_buffers
505                        {
506                            warn!(
507                                "Clusterable object index lists are full! The clusterable \
508                                 objects in the view are present in too many clusters."
509                            );
510                            break;
511                        }
512                        view_clusters_bindings.push_index(*clusterable_object_index);
513                    } else {
514                        // This should never happen. The appropriate systems
515                        // should have populated
516                        // `global_clusterable_object_meta` by now.
517                        error!(
518                            "Clustered light or decal {:?} had no assigned index!",
519                            entity
520                        );
521                        // Things that should never happen won't happen in debug mode.
522                        debug_assert!(false);
523                        view_clusters_bindings.push_dummy_index();
524                    }
525                }
526
527                ExtractedClusterableObjectElement::ReflectionProbe(main_entity) => {
528                    match maybe_environment_maps.and_then(|environment_maps| {
529                        environment_maps
530                            .main_entity_to_render_light_probe_index
531                            .get(main_entity)
532                    }) {
533                        Some(render_light_probe_index) => {
534                            view_clusters_bindings.push_index(*render_light_probe_index as usize);
535                        }
536                        None => {
537                            // This can happen while the reflection probe is loading.
538                            trace!(
539                                "Clustered reflection probe {:?} had no assigned index",
540                                main_entity,
541                            );
542                            view_clusters_bindings.push_dummy_index();
543                        }
544                    }
545                }
546
547                ExtractedClusterableObjectElement::IrradianceVolume(main_entity) => {
548                    match maybe_irradiance_volumes.and_then(|irradiance_volumes| {
549                        irradiance_volumes
550                            .main_entity_to_render_light_probe_index
551                            .get(main_entity)
552                    }) {
553                        Some(render_light_probe_index) => {
554                            view_clusters_bindings.push_index(*render_light_probe_index as usize);
555                        }
556                        None => {
557                            trace!(
558                                "Clustered irradiance volume {:?} had no assigned index",
559                                main_entity
560                            );
561                            view_clusters_bindings.push_dummy_index();
562                        }
563                    }
564                }
565            }
566        }
567
568        view_clusters_bindings.write_buffers(render_device, &render_queue);
569
570        commands.entity(entity).insert(view_clusters_bindings);
571    }
572}
573
574impl ViewClusterBindings {
575    pub const MAX_OFFSETS: usize = 16384 / 4;
576    const MAX_UNIFORM_ITEMS: usize = Self::MAX_OFFSETS / 4;
577    pub const MAX_INDICES: usize = 16384;
578
579    pub fn new(buffer_binding_type: BufferBindingType) -> Self {
580        Self {
581            n_indices: 0,
582            n_offsets: 0,
583            buffers: ViewClusterBuffers::new(buffer_binding_type),
584        }
585    }
586
587    pub fn clear(&mut self) {
588        match &mut self.buffers {
589            ViewClusterBuffers::Uniform {
590                clusterable_object_index_lists,
591                cluster_offsets_and_counts,
592            } => {
593                *clusterable_object_index_lists.get_mut().data =
594                    [UVec4::ZERO; Self::MAX_UNIFORM_ITEMS];
595                *cluster_offsets_and_counts.get_mut().data = [UVec4::ZERO; Self::MAX_UNIFORM_ITEMS];
596            }
597            ViewClusterBuffers::Storage {
598                clusterable_object_index_lists,
599                cluster_offsets_and_counts,
600                ..
601            } => {
602                clusterable_object_index_lists.get_mut().data.clear();
603                cluster_offsets_and_counts.get_mut().data.clear();
604            }
605        }
606    }
607
608    fn push_offset_and_counts(&mut self, offset: usize, counts: &ClusterableObjectCounts) {
609        match &mut self.buffers {
610            ViewClusterBuffers::Uniform {
611                cluster_offsets_and_counts,
612                ..
613            } => {
614                let array_index = self.n_offsets >> 2; // >> 2 is equivalent to / 4
615                if array_index >= Self::MAX_UNIFORM_ITEMS {
616                    warn!("cluster offset and count out of bounds!");
617                    return;
618                }
619                let component = self.n_offsets & ((1 << 2) - 1);
620                let packed =
621                    pack_offset_and_counts(offset, counts.point_lights, counts.spot_lights);
622
623                cluster_offsets_and_counts.get_mut().data[array_index][component] = packed;
624            }
625            ViewClusterBuffers::Storage {
626                cluster_offsets_and_counts,
627                ..
628            } => {
629                cluster_offsets_and_counts.get_mut().data.push([
630                    uvec4(
631                        offset as u32,
632                        counts.point_lights,
633                        counts.spot_lights,
634                        counts.reflection_probes,
635                    ),
636                    uvec4(counts.irradiance_volumes, counts.decals, 0, 0),
637                ]);
638            }
639        }
640
641        self.n_offsets += 1;
642    }
643
644    pub fn n_indices(&self) -> usize {
645        self.n_indices
646    }
647
648    // An internal helper method that pushes a raw clustered object index to the
649    // GPU buffer.
650    fn push_raw_index(&mut self, index: u32) {
651        match &mut self.buffers {
652            ViewClusterBuffers::Uniform {
653                clusterable_object_index_lists,
654                ..
655            } => {
656                let array_index = self.n_indices >> 4; // >> 4 is equivalent to / 16
657                let component = (self.n_indices >> 2) & ((1 << 2) - 1);
658                let sub_index = self.n_indices & ((1 << 2) - 1);
659
660                clusterable_object_index_lists.get_mut().data[array_index][component] |=
661                    index << (8 * sub_index);
662            }
663            ViewClusterBuffers::Storage {
664                clusterable_object_index_lists,
665                ..
666            } => {
667                clusterable_object_index_lists.get_mut().data.push(index);
668            }
669        }
670
671        self.n_indices += 1;
672    }
673
674    /// Pushes the index of a clustered object to the GPU buffer.
675    pub fn push_index(&mut self, index: usize) {
676        self.push_raw_index(index as u32);
677    }
678
679    /// Pushes a placeholder -1 index to the GPU buffer.
680    ///
681    /// This is used when processing reflection probes and irradiance volumes
682    /// that haven't loaded yet.
683    pub fn push_dummy_index(&mut self) {
684        self.push_raw_index(!0);
685    }
686
687    /// Reserves space in the cluster offsets-and-counts list for `clusters`
688    /// clusters.
689    pub fn reserve_clusters(&mut self, clusters: usize) {
690        match &mut self.buffers {
691            ViewClusterBuffers::Uniform { .. } => {
692                error!("`reserve_clusters` should only be called in GPU clustering, which requires a storage buffer");
693            }
694            ViewClusterBuffers::Storage {
695                cluster_offsets_and_counts,
696                ..
697            } => {
698                cluster_offsets_and_counts
699                    .get_mut()
700                    .data
701                    .extend(iter::repeat_n(
702                        GpuClusterOffsetAndCounts::default(),
703                        clusters,
704                    ));
705                self.n_offsets += clusters;
706            }
707        }
708    }
709
710    /// Reserves space in the index lists for `elements` indices.
711    pub fn reserve_indices(&mut self, elements: usize) {
712        match &mut self.buffers {
713            ViewClusterBuffers::Uniform { .. } => {
714                error!("`reserve_indices` should only be called in GPU clustering, which requires a storage buffer");
715            }
716            ViewClusterBuffers::Storage {
717                clusterable_object_index_lists,
718                ..
719            } => {
720                clusterable_object_index_lists
721                    .get_mut()
722                    .data
723                    .extend(iter::repeat_n(0, elements));
724                self.n_indices += elements;
725            }
726        }
727    }
728
729    pub fn write_buffers(&mut self, render_device: &RenderDevice, render_queue: &RenderQueue) {
730        match &mut self.buffers {
731            ViewClusterBuffers::Uniform {
732                clusterable_object_index_lists,
733                cluster_offsets_and_counts,
734            } => {
735                clusterable_object_index_lists.write_buffer(render_device, render_queue);
736                cluster_offsets_and_counts.write_buffer(render_device, render_queue);
737            }
738            ViewClusterBuffers::Storage {
739                clusterable_object_index_lists,
740                cluster_offsets_and_counts,
741            } => {
742                clusterable_object_index_lists.write_buffer(render_device, render_queue);
743                cluster_offsets_and_counts.write_buffer(render_device, render_queue);
744            }
745        }
746    }
747
748    pub fn clusterable_object_index_lists_binding(&self) -> Option<BindingResource<'_>> {
749        match &self.buffers {
750            ViewClusterBuffers::Uniform {
751                clusterable_object_index_lists,
752                ..
753            } => clusterable_object_index_lists.binding(),
754            ViewClusterBuffers::Storage {
755                clusterable_object_index_lists,
756                ..
757            } => clusterable_object_index_lists.binding(),
758        }
759    }
760
761    pub fn offsets_and_counts_binding(&self) -> Option<BindingResource<'_>> {
762        match &self.buffers {
763            ViewClusterBuffers::Uniform {
764                cluster_offsets_and_counts,
765                ..
766            } => cluster_offsets_and_counts.binding(),
767            ViewClusterBuffers::Storage {
768                cluster_offsets_and_counts,
769                ..
770            } => cluster_offsets_and_counts.binding(),
771        }
772    }
773
774    pub fn min_size_clusterable_object_index_lists(
775        buffer_binding_type: BufferBindingType,
776    ) -> NonZero<u64> {
777        match buffer_binding_type {
778            BufferBindingType::Storage { .. } => GpuClusterableObjectIndexListsStorage::min_size(),
779            BufferBindingType::Uniform => GpuClusterableObjectIndexListsUniform::min_size(),
780        }
781    }
782
783    pub fn min_size_cluster_offsets_and_counts(
784        buffer_binding_type: BufferBindingType,
785    ) -> NonZero<u64> {
786        match buffer_binding_type {
787            BufferBindingType::Storage { .. } => GpuClusterOffsetsAndCountsStorage::min_size(),
788            BufferBindingType::Uniform => GpuClusterOffsetsAndCountsUniform::min_size(),
789        }
790    }
791}
792
793impl ViewClusterBuffers {
794    fn new(buffer_binding_type: BufferBindingType) -> Self {
795        match buffer_binding_type {
796            BufferBindingType::Storage { .. } => Self::storage(),
797            BufferBindingType::Uniform => Self::uniform(),
798        }
799    }
800
801    fn uniform() -> Self {
802        ViewClusterBuffers::Uniform {
803            clusterable_object_index_lists: UniformBuffer::default(),
804            cluster_offsets_and_counts: UniformBuffer::default(),
805        }
806    }
807
808    fn storage() -> Self {
809        ViewClusterBuffers::Storage {
810            clusterable_object_index_lists: StorageBuffer::default(),
811            cluster_offsets_and_counts: StorageBuffer::default(),
812        }
813    }
814}
815
816// Compresses the offset and counts of point and spot lights so that they fit in
817// a UBO.
818//
819// This function is only used if storage buffers are unavailable on this
820// platform: typically, on WebGL 2.
821//
822// NOTE: With uniform buffer max binding size as 16384 bytes
823// that means we can fit 204 clusterable objects in one uniform
824// buffer, which means the count can be at most 204 so it
825// needs 9 bits.
826// The array of indices can also use u8 and that means the
827// offset in to the array of indices needs to be able to address
828// 16384 values. log2(16384) = 14 bits.
829// We use 32 bits to store the offset and counts so
830// we pack the offset into the upper 14 bits of a u32,
831// the point light count into bits 9-17, and the spot light count into bits 0-8.
832//  [ 31     ..     18 | 17      ..      9 | 8       ..     0 ]
833//  [      offset      | point light count | spot light count ]
834//
835// NOTE: This assumes CPU and GPU endianness are the same which is true
836// for all common and tested x86/ARM CPUs and AMD/NVIDIA/Intel/Apple/etc GPUs
837//
838// NOTE: On platforms that use this function, we don't cluster light probes, so
839// the number of light probes is irrelevant.
840fn pack_offset_and_counts(offset: usize, point_count: u32, spot_count: u32) -> u32 {
841    ((offset as u32 & CLUSTER_OFFSET_MASK) << (CLUSTER_COUNT_SIZE * 2))
842        | ((point_count & CLUSTER_COUNT_MASK) << CLUSTER_COUNT_SIZE)
843        | (spot_count & CLUSTER_COUNT_MASK)
844}
845
846#[derive(ShaderType)]
847struct GpuClusterableObjectIndexListsUniform {
848    data: Box<[UVec4; ViewClusterBindings::MAX_UNIFORM_ITEMS]>,
849}
850
851// NOTE: Assert at compile time that GpuClusterableObjectIndexListsUniform
852// fits within the maximum uniform buffer binding size
853const _: () = assert!(GpuClusterableObjectIndexListsUniform::SHADER_SIZE.get() <= 16384);
854
855impl Default for GpuClusterableObjectIndexListsUniform {
856    fn default() -> Self {
857        Self {
858            data: Box::new([UVec4::ZERO; ViewClusterBindings::MAX_UNIFORM_ITEMS]),
859        }
860    }
861}
862
863impl Default for GpuClusterOffsetsAndCountsUniform {
864    fn default() -> Self {
865        Self {
866            data: Box::new([UVec4::ZERO; ViewClusterBindings::MAX_UNIFORM_ITEMS]),
867        }
868    }
869}