From b5cefe2b5dd50df37cea4dd000137f56a3b47023 Mon Sep 17 00:00:00 2001 From: atlv Date: Sun, 6 Jul 2025 13:19:36 -0400 Subject: [PATCH 1/5] extract cluster extract to a separate module (#19973) # Objective - prepare bevy_light for split ## Solution - split render world extract related cluster code from main world ecs stuff re-exports make this not breaking --- .../src/cluster/extract_and_prepare.rs | 578 +++++++++++++++++ crates/bevy_pbr/src/cluster/mod.rs | 594 +----------------- 2 files changed, 589 insertions(+), 583 deletions(-) create mode 100644 crates/bevy_pbr/src/cluster/extract_and_prepare.rs diff --git a/crates/bevy_pbr/src/cluster/extract_and_prepare.rs b/crates/bevy_pbr/src/cluster/extract_and_prepare.rs new file mode 100644 index 0000000000..9fa6a5996c --- /dev/null +++ b/crates/bevy_pbr/src/cluster/extract_and_prepare.rs @@ -0,0 +1,578 @@ +use core::num::NonZero; + +use bevy_camera::Camera; +use bevy_ecs::{entity::EntityHashMap, prelude::*}; +use bevy_math::{uvec4, UVec3, UVec4, Vec4}; +use bevy_render::{ + render_resource::{ + BindingResource, BufferBindingType, ShaderSize, ShaderType, StorageBuffer, UniformBuffer, + }, + renderer::{RenderAdapter, RenderDevice, RenderQueue}, + sync_world::RenderEntity, + Extract, +}; +use tracing::warn; + +use crate::{cluster::ClusterableObjectCounts, Clusters, GlobalClusterSettings, MeshPipeline}; + +// NOTE: this must be kept in sync with the same constants in +// `mesh_view_types.wgsl`. +pub const MAX_UNIFORM_BUFFER_CLUSTERABLE_OBJECTS: usize = 204; +// Make sure that the clusterable object buffer doesn't overflow the maximum +// size of a UBO on WebGL 2. +const _: () = + assert!(size_of::() * MAX_UNIFORM_BUFFER_CLUSTERABLE_OBJECTS <= 16384); + +// NOTE: Clustered-forward rendering requires 3 storage buffer bindings so check that +// at least that many are supported using this constant and SupportedBindingType::from_device() +pub const CLUSTERED_FORWARD_STORAGE_BUFFER_COUNT: u32 = 3; + +// this must match CLUSTER_COUNT_SIZE in pbr.wgsl +// and must be large enough to contain MAX_UNIFORM_BUFFER_CLUSTERABLE_OBJECTS +const CLUSTER_COUNT_SIZE: u32 = 9; + +const CLUSTER_OFFSET_MASK: u32 = (1 << (32 - (CLUSTER_COUNT_SIZE * 2))) - 1; +const CLUSTER_COUNT_MASK: u32 = (1 << CLUSTER_COUNT_SIZE) - 1; + +pub(crate) fn make_global_cluster_settings(world: &World) -> GlobalClusterSettings { + let device = world.resource::(); + let adapter = world.resource::(); + let clustered_decals_are_usable = + crate::decal::clustered::clustered_decals_are_usable(device, adapter); + let supports_storage_buffers = matches!( + device.get_supported_read_only_binding_type(CLUSTERED_FORWARD_STORAGE_BUFFER_COUNT), + BufferBindingType::Storage { .. } + ); + GlobalClusterSettings { + supports_storage_buffers, + clustered_decals_are_usable, + } +} + +#[derive(Copy, Clone, ShaderType, Default, Debug)] +pub struct GpuClusterableObject { + // For point lights: the lower-right 2x2 values of the projection matrix [2][2] [2][3] [3][2] [3][3] + // For spot lights: 2 components of the direction (x,z), spot_scale and spot_offset + pub(crate) light_custom_data: Vec4, + pub(crate) color_inverse_square_range: Vec4, + pub(crate) position_radius: Vec4, + pub(crate) flags: u32, + pub(crate) shadow_depth_bias: f32, + pub(crate) shadow_normal_bias: f32, + pub(crate) spot_light_tan_angle: f32, + pub(crate) soft_shadow_size: f32, + pub(crate) shadow_map_near_z: f32, + pub(crate) decal_index: u32, + pub(crate) pad: f32, +} + +#[derive(Resource)] +pub struct GlobalClusterableObjectMeta { + pub gpu_clusterable_objects: GpuClusterableObjects, + pub entity_to_index: EntityHashMap, +} + +pub enum GpuClusterableObjects { + Uniform(UniformBuffer), + Storage(StorageBuffer), +} + +#[derive(ShaderType)] +pub struct GpuClusterableObjectsUniform { + data: Box<[GpuClusterableObject; MAX_UNIFORM_BUFFER_CLUSTERABLE_OBJECTS]>, +} + +#[derive(ShaderType, Default)] +pub struct GpuClusterableObjectsStorage { + #[size(runtime)] + data: Vec, +} + +#[derive(Component)] +pub struct ExtractedClusterConfig { + /// Special near value for cluster calculations + pub(crate) near: f32, + pub(crate) far: f32, + /// Number of clusters in `X` / `Y` / `Z` in the view frustum + pub(crate) dimensions: UVec3, +} + +enum ExtractedClusterableObjectElement { + ClusterHeader(ClusterableObjectCounts), + ClusterableObjectEntity(Entity), +} + +#[derive(Component)] +pub struct ExtractedClusterableObjects { + data: Vec, +} + +#[derive(ShaderType)] +struct GpuClusterOffsetsAndCountsUniform { + data: Box<[UVec4; ViewClusterBindings::MAX_UNIFORM_ITEMS]>, +} + +#[derive(ShaderType, Default)] +struct GpuClusterableObjectIndexListsStorage { + #[size(runtime)] + data: Vec, +} + +#[derive(ShaderType, Default)] +struct GpuClusterOffsetsAndCountsStorage { + /// The starting offset, followed by the number of point lights, spot + /// lights, reflection probes, and irradiance volumes in each cluster, in + /// that order. The remaining fields are filled with zeroes. + #[size(runtime)] + data: Vec<[UVec4; 2]>, +} + +enum ViewClusterBuffers { + Uniform { + // NOTE: UVec4 is because all arrays in Std140 layout have 16-byte alignment + clusterable_object_index_lists: UniformBuffer, + // NOTE: UVec4 is because all arrays in Std140 layout have 16-byte alignment + cluster_offsets_and_counts: UniformBuffer, + }, + Storage { + clusterable_object_index_lists: StorageBuffer, + cluster_offsets_and_counts: StorageBuffer, + }, +} + +#[derive(Component)] +pub struct ViewClusterBindings { + n_indices: usize, + n_offsets: usize, + buffers: ViewClusterBuffers, +} + +impl FromWorld for GlobalClusterableObjectMeta { + fn from_world(world: &mut World) -> Self { + Self::new( + world + .resource::() + .get_supported_read_only_binding_type(CLUSTERED_FORWARD_STORAGE_BUFFER_COUNT), + ) + } +} + +impl GlobalClusterableObjectMeta { + pub fn new(buffer_binding_type: BufferBindingType) -> Self { + Self { + gpu_clusterable_objects: GpuClusterableObjects::new(buffer_binding_type), + entity_to_index: EntityHashMap::default(), + } + } +} + +impl GpuClusterableObjects { + fn new(buffer_binding_type: BufferBindingType) -> Self { + match buffer_binding_type { + BufferBindingType::Storage { .. } => Self::storage(), + BufferBindingType::Uniform => Self::uniform(), + } + } + + fn uniform() -> Self { + Self::Uniform(UniformBuffer::default()) + } + + fn storage() -> Self { + Self::Storage(StorageBuffer::default()) + } + + pub(crate) fn set(&mut self, mut clusterable_objects: Vec) { + match self { + GpuClusterableObjects::Uniform(buffer) => { + let len = clusterable_objects + .len() + .min(MAX_UNIFORM_BUFFER_CLUSTERABLE_OBJECTS); + let src = &clusterable_objects[..len]; + let dst = &mut buffer.get_mut().data[..len]; + dst.copy_from_slice(src); + } + GpuClusterableObjects::Storage(buffer) => { + buffer.get_mut().data.clear(); + buffer.get_mut().data.append(&mut clusterable_objects); + } + } + } + + pub(crate) fn write_buffer( + &mut self, + render_device: &RenderDevice, + render_queue: &RenderQueue, + ) { + match self { + GpuClusterableObjects::Uniform(buffer) => { + buffer.write_buffer(render_device, render_queue); + } + GpuClusterableObjects::Storage(buffer) => { + buffer.write_buffer(render_device, render_queue); + } + } + } + + pub fn binding(&self) -> Option { + match self { + GpuClusterableObjects::Uniform(buffer) => buffer.binding(), + GpuClusterableObjects::Storage(buffer) => buffer.binding(), + } + } + + pub fn min_size(buffer_binding_type: BufferBindingType) -> NonZero { + match buffer_binding_type { + BufferBindingType::Storage { .. } => GpuClusterableObjectsStorage::min_size(), + BufferBindingType::Uniform => GpuClusterableObjectsUniform::min_size(), + } + } +} + +impl Default for GpuClusterableObjectsUniform { + fn default() -> Self { + Self { + data: Box::new( + [GpuClusterableObject::default(); MAX_UNIFORM_BUFFER_CLUSTERABLE_OBJECTS], + ), + } + } +} + +/// Extracts clusters from the main world from the render world. +pub fn extract_clusters( + mut commands: Commands, + views: Extract>, + mapper: Extract>, +) { + for (entity, clusters, camera) in &views { + let mut entity_commands = commands + .get_entity(entity) + .expect("Clusters entity wasn't synced."); + if !camera.is_active { + entity_commands.remove::<(ExtractedClusterableObjects, ExtractedClusterConfig)>(); + continue; + } + + let entity_count: usize = clusters + .clusterable_objects + .iter() + .map(|l| l.entities.len()) + .sum(); + let mut data = Vec::with_capacity(clusters.clusterable_objects.len() + entity_count); + for cluster_objects in &clusters.clusterable_objects { + data.push(ExtractedClusterableObjectElement::ClusterHeader( + cluster_objects.counts, + )); + for clusterable_entity in &cluster_objects.entities { + if let Ok(entity) = mapper.get(*clusterable_entity) { + data.push(ExtractedClusterableObjectElement::ClusterableObjectEntity( + entity, + )); + } + } + } + + entity_commands.insert(( + ExtractedClusterableObjects { data }, + ExtractedClusterConfig { + near: clusters.near, + far: clusters.far, + dimensions: clusters.dimensions, + }, + )); + } +} + +pub fn prepare_clusters( + mut commands: Commands, + render_device: Res, + render_queue: Res, + mesh_pipeline: Res, + global_clusterable_object_meta: Res, + views: Query<(Entity, &ExtractedClusterableObjects)>, +) { + let render_device = render_device.into_inner(); + let supports_storage_buffers = matches!( + mesh_pipeline.clustered_forward_buffer_binding_type, + BufferBindingType::Storage { .. } + ); + for (entity, extracted_clusters) in &views { + let mut view_clusters_bindings = + ViewClusterBindings::new(mesh_pipeline.clustered_forward_buffer_binding_type); + view_clusters_bindings.clear(); + + for record in &extracted_clusters.data { + match record { + ExtractedClusterableObjectElement::ClusterHeader(counts) => { + let offset = view_clusters_bindings.n_indices(); + view_clusters_bindings.push_offset_and_counts(offset, counts); + } + ExtractedClusterableObjectElement::ClusterableObjectEntity(entity) => { + if let Some(clusterable_object_index) = + global_clusterable_object_meta.entity_to_index.get(entity) + { + if view_clusters_bindings.n_indices() >= ViewClusterBindings::MAX_INDICES + && !supports_storage_buffers + { + warn!( + "Clusterable object index lists are full! The clusterable \ + objects in the view are present in too many clusters." + ); + break; + } + view_clusters_bindings.push_index(*clusterable_object_index); + } + } + } + } + + view_clusters_bindings.write_buffers(render_device, &render_queue); + + commands.entity(entity).insert(view_clusters_bindings); + } +} + +impl ViewClusterBindings { + pub const MAX_OFFSETS: usize = 16384 / 4; + const MAX_UNIFORM_ITEMS: usize = Self::MAX_OFFSETS / 4; + pub const MAX_INDICES: usize = 16384; + + pub fn new(buffer_binding_type: BufferBindingType) -> Self { + Self { + n_indices: 0, + n_offsets: 0, + buffers: ViewClusterBuffers::new(buffer_binding_type), + } + } + + pub fn clear(&mut self) { + match &mut self.buffers { + ViewClusterBuffers::Uniform { + clusterable_object_index_lists, + cluster_offsets_and_counts, + } => { + *clusterable_object_index_lists.get_mut().data = + [UVec4::ZERO; Self::MAX_UNIFORM_ITEMS]; + *cluster_offsets_and_counts.get_mut().data = [UVec4::ZERO; Self::MAX_UNIFORM_ITEMS]; + } + ViewClusterBuffers::Storage { + clusterable_object_index_lists, + cluster_offsets_and_counts, + .. + } => { + clusterable_object_index_lists.get_mut().data.clear(); + cluster_offsets_and_counts.get_mut().data.clear(); + } + } + } + + fn push_offset_and_counts(&mut self, offset: usize, counts: &ClusterableObjectCounts) { + match &mut self.buffers { + ViewClusterBuffers::Uniform { + cluster_offsets_and_counts, + .. + } => { + let array_index = self.n_offsets >> 2; // >> 2 is equivalent to / 4 + if array_index >= Self::MAX_UNIFORM_ITEMS { + warn!("cluster offset and count out of bounds!"); + return; + } + let component = self.n_offsets & ((1 << 2) - 1); + let packed = + pack_offset_and_counts(offset, counts.point_lights, counts.spot_lights); + + cluster_offsets_and_counts.get_mut().data[array_index][component] = packed; + } + ViewClusterBuffers::Storage { + cluster_offsets_and_counts, + .. + } => { + cluster_offsets_and_counts.get_mut().data.push([ + uvec4( + offset as u32, + counts.point_lights, + counts.spot_lights, + counts.reflection_probes, + ), + uvec4(counts.irradiance_volumes, counts.decals, 0, 0), + ]); + } + } + + self.n_offsets += 1; + } + + pub fn n_indices(&self) -> usize { + self.n_indices + } + + pub fn push_index(&mut self, index: usize) { + match &mut self.buffers { + ViewClusterBuffers::Uniform { + clusterable_object_index_lists, + .. + } => { + let array_index = self.n_indices >> 4; // >> 4 is equivalent to / 16 + let component = (self.n_indices >> 2) & ((1 << 2) - 1); + let sub_index = self.n_indices & ((1 << 2) - 1); + let index = index as u32; + + clusterable_object_index_lists.get_mut().data[array_index][component] |= + index << (8 * sub_index); + } + ViewClusterBuffers::Storage { + clusterable_object_index_lists, + .. + } => { + clusterable_object_index_lists + .get_mut() + .data + .push(index as u32); + } + } + + self.n_indices += 1; + } + + pub fn write_buffers(&mut self, render_device: &RenderDevice, render_queue: &RenderQueue) { + match &mut self.buffers { + ViewClusterBuffers::Uniform { + clusterable_object_index_lists, + cluster_offsets_and_counts, + } => { + clusterable_object_index_lists.write_buffer(render_device, render_queue); + cluster_offsets_and_counts.write_buffer(render_device, render_queue); + } + ViewClusterBuffers::Storage { + clusterable_object_index_lists, + cluster_offsets_and_counts, + } => { + clusterable_object_index_lists.write_buffer(render_device, render_queue); + cluster_offsets_and_counts.write_buffer(render_device, render_queue); + } + } + } + + pub fn clusterable_object_index_lists_binding(&self) -> Option { + match &self.buffers { + ViewClusterBuffers::Uniform { + clusterable_object_index_lists, + .. + } => clusterable_object_index_lists.binding(), + ViewClusterBuffers::Storage { + clusterable_object_index_lists, + .. + } => clusterable_object_index_lists.binding(), + } + } + + pub fn offsets_and_counts_binding(&self) -> Option { + match &self.buffers { + ViewClusterBuffers::Uniform { + cluster_offsets_and_counts, + .. + } => cluster_offsets_and_counts.binding(), + ViewClusterBuffers::Storage { + cluster_offsets_and_counts, + .. + } => cluster_offsets_and_counts.binding(), + } + } + + pub fn min_size_clusterable_object_index_lists( + buffer_binding_type: BufferBindingType, + ) -> NonZero { + match buffer_binding_type { + BufferBindingType::Storage { .. } => GpuClusterableObjectIndexListsStorage::min_size(), + BufferBindingType::Uniform => GpuClusterableObjectIndexListsUniform::min_size(), + } + } + + pub fn min_size_cluster_offsets_and_counts( + buffer_binding_type: BufferBindingType, + ) -> NonZero { + match buffer_binding_type { + BufferBindingType::Storage { .. } => GpuClusterOffsetsAndCountsStorage::min_size(), + BufferBindingType::Uniform => GpuClusterOffsetsAndCountsUniform::min_size(), + } + } +} + +impl ViewClusterBuffers { + fn new(buffer_binding_type: BufferBindingType) -> Self { + match buffer_binding_type { + BufferBindingType::Storage { .. } => Self::storage(), + BufferBindingType::Uniform => Self::uniform(), + } + } + + fn uniform() -> Self { + ViewClusterBuffers::Uniform { + clusterable_object_index_lists: UniformBuffer::default(), + cluster_offsets_and_counts: UniformBuffer::default(), + } + } + + fn storage() -> Self { + ViewClusterBuffers::Storage { + clusterable_object_index_lists: StorageBuffer::default(), + cluster_offsets_and_counts: StorageBuffer::default(), + } + } +} + +// Compresses the offset and counts of point and spot lights so that they fit in +// a UBO. +// +// This function is only used if storage buffers are unavailable on this +// platform: typically, on WebGL 2. +// +// NOTE: With uniform buffer max binding size as 16384 bytes +// that means we can fit 204 clusterable objects in one uniform +// buffer, which means the count can be at most 204 so it +// needs 9 bits. +// The array of indices can also use u8 and that means the +// offset in to the array of indices needs to be able to address +// 16384 values. log2(16384) = 14 bits. +// We use 32 bits to store the offset and counts so +// we pack the offset into the upper 14 bits of a u32, +// the point light count into bits 9-17, and the spot light count into bits 0-8. +// [ 31 .. 18 | 17 .. 9 | 8 .. 0 ] +// [ offset | point light count | spot light count ] +// +// NOTE: This assumes CPU and GPU endianness are the same which is true +// for all common and tested x86/ARM CPUs and AMD/NVIDIA/Intel/Apple/etc GPUs +// +// NOTE: On platforms that use this function, we don't cluster light probes, so +// the number of light probes is irrelevant. +fn pack_offset_and_counts(offset: usize, point_count: u32, spot_count: u32) -> u32 { + ((offset as u32 & CLUSTER_OFFSET_MASK) << (CLUSTER_COUNT_SIZE * 2)) + | ((point_count & CLUSTER_COUNT_MASK) << CLUSTER_COUNT_SIZE) + | (spot_count & CLUSTER_COUNT_MASK) +} + +#[derive(ShaderType)] +struct GpuClusterableObjectIndexListsUniform { + data: Box<[UVec4; ViewClusterBindings::MAX_UNIFORM_ITEMS]>, +} + +// NOTE: Assert at compile time that GpuClusterableObjectIndexListsUniform +// fits within the maximum uniform buffer binding size +const _: () = assert!(GpuClusterableObjectIndexListsUniform::SHADER_SIZE.get() <= 16384); + +impl Default for GpuClusterableObjectIndexListsUniform { + fn default() -> Self { + Self { + data: Box::new([UVec4::ZERO; ViewClusterBindings::MAX_UNIFORM_ITEMS]), + } + } +} + +impl Default for GpuClusterOffsetsAndCountsUniform { + fn default() -> Self { + Self { + data: Box::new([UVec4::ZERO; ViewClusterBindings::MAX_UNIFORM_ITEMS]), + } + } +} diff --git a/crates/bevy_pbr/src/cluster/mod.rs b/crates/bevy_pbr/src/cluster/mod.rs index e7f03cc55c..9cddc0a1b6 100644 --- a/crates/bevy_pbr/src/cluster/mod.rs +++ b/crates/bevy_pbr/src/cluster/mod.rs @@ -1,65 +1,36 @@ //! Spatial clustering of objects, currently just point and spot lights. -use core::num::NonZero; - use bevy_asset::Handle; -use bevy_camera::visibility; -use bevy_core_pipeline::core_3d::Camera3d; +use bevy_camera::{ + visibility::{self, Visibility, VisibilityClass}, + Camera, Camera3d, +}; use bevy_ecs::{ component::Component, - entity::{Entity, EntityHashMap}, + entity::Entity, query::{With, Without}, reflect::ReflectComponent, resource::Resource, - system::{Commands, Query, Res}, - world::{FromWorld, World}, + system::{Commands, Query}, }; use bevy_image::Image; -use bevy_math::{uvec4, AspectRatio, UVec2, UVec3, UVec4, Vec3Swizzles as _, Vec4}; +use bevy_math::{AspectRatio, UVec2, UVec3, Vec3Swizzles as _}; use bevy_platform::collections::HashSet; use bevy_reflect::{std_traits::ReflectDefault, Reflect}; -use bevy_render::{ - camera::Camera, - extract_component::ExtractComponent, - render_resource::{ - BindingResource, BufferBindingType, ShaderSize as _, ShaderType, StorageBuffer, - UniformBuffer, - }, - renderer::{RenderAdapter, RenderDevice, RenderQueue}, - sync_world::RenderEntity, - view::{Visibility, VisibilityClass}, - Extract, -}; +use bevy_render::extract_component::ExtractComponent; use bevy_transform::components::Transform; use tracing::warn; pub(crate) use crate::cluster::assign::assign_objects_to_clusters; -use crate::{LightVisibilityClass, MeshPipeline}; +use crate::LightVisibilityClass; pub(crate) mod assign; +mod extract_and_prepare; +pub use extract_and_prepare::*; #[cfg(test)] mod test; -// NOTE: this must be kept in sync with the same constants in -// `mesh_view_types.wgsl`. -pub const MAX_UNIFORM_BUFFER_CLUSTERABLE_OBJECTS: usize = 204; -// Make sure that the clusterable object buffer doesn't overflow the maximum -// size of a UBO on WebGL 2. -const _: () = - assert!(size_of::() * MAX_UNIFORM_BUFFER_CLUSTERABLE_OBJECTS <= 16384); - -// NOTE: Clustered-forward rendering requires 3 storage buffer bindings so check that -// at least that many are supported using this constant and SupportedBindingType::from_device() -pub const CLUSTERED_FORWARD_STORAGE_BUFFER_COUNT: u32 = 3; - -// this must match CLUSTER_COUNT_SIZE in pbr.wgsl -// and must be large enough to contain MAX_UNIFORM_BUFFER_CLUSTERABLE_OBJECTS -const CLUSTER_COUNT_SIZE: u32 = 9; - -const CLUSTER_OFFSET_MASK: u32 = (1 << (32 - (CLUSTER_COUNT_SIZE * 2))) - 1; -const CLUSTER_COUNT_MASK: u32 = (1 << CLUSTER_COUNT_SIZE) - 1; - // Clustered-forward rendering notes // The main initial reference material used was this rather accessible article: // http://www.aortiz.me/2018/12/21/CG.html @@ -75,21 +46,6 @@ pub struct GlobalClusterSettings { pub clustered_decals_are_usable: bool, } -pub(crate) fn make_global_cluster_settings(world: &World) -> GlobalClusterSettings { - let device = world.resource::(); - let adapter = world.resource::(); - let clustered_decals_are_usable = - crate::decal::clustered::clustered_decals_are_usable(device, adapter); - let supports_storage_buffers = matches!( - device.get_supported_read_only_binding_type(CLUSTERED_FORWARD_STORAGE_BUFFER_COUNT), - BufferBindingType::Storage { .. } - ); - GlobalClusterSettings { - supports_storage_buffers, - clustered_decals_are_usable, - } -} - /// Configure the far z-plane mode used for the furthest depth slice for clustered forward /// rendering #[derive(Debug, Copy, Clone, Reflect)] @@ -170,54 +126,6 @@ pub struct GlobalVisibleClusterableObjects { pub(crate) entities: HashSet, } -#[derive(Resource)] -pub struct GlobalClusterableObjectMeta { - pub gpu_clusterable_objects: GpuClusterableObjects, - pub entity_to_index: EntityHashMap, -} - -#[derive(Copy, Clone, ShaderType, Default, Debug)] -pub struct GpuClusterableObject { - // For point lights: the lower-right 2x2 values of the projection matrix [2][2] [2][3] [3][2] [3][3] - // For spot lights: 2 components of the direction (x,z), spot_scale and spot_offset - pub(crate) light_custom_data: Vec4, - pub(crate) color_inverse_square_range: Vec4, - pub(crate) position_radius: Vec4, - pub(crate) flags: u32, - pub(crate) shadow_depth_bias: f32, - pub(crate) shadow_normal_bias: f32, - pub(crate) spot_light_tan_angle: f32, - pub(crate) soft_shadow_size: f32, - pub(crate) shadow_map_near_z: f32, - pub(crate) decal_index: u32, - pub(crate) pad: f32, -} - -pub enum GpuClusterableObjects { - Uniform(UniformBuffer), - Storage(StorageBuffer), -} - -#[derive(ShaderType)] -pub struct GpuClusterableObjectsUniform { - data: Box<[GpuClusterableObject; MAX_UNIFORM_BUFFER_CLUSTERABLE_OBJECTS]>, -} - -#[derive(ShaderType, Default)] -pub struct GpuClusterableObjectsStorage { - #[size(runtime)] - data: Vec, -} - -#[derive(Component)] -pub struct ExtractedClusterConfig { - /// Special near value for cluster calculations - pub(crate) near: f32, - pub(crate) far: f32, - /// Number of clusters in `X` / `Y` / `Z` in the view frustum - pub(crate) dimensions: UVec3, -} - /// Stores the number of each type of clusterable object in a single cluster. /// /// Note that `reflection_probes` and `irradiance_volumes` won't be clustered if @@ -264,56 +172,6 @@ pub struct ClusteredDecal { pub tag: u32, } -enum ExtractedClusterableObjectElement { - ClusterHeader(ClusterableObjectCounts), - ClusterableObjectEntity(Entity), -} - -#[derive(Component)] -pub struct ExtractedClusterableObjects { - data: Vec, -} - -#[derive(ShaderType)] -struct GpuClusterOffsetsAndCountsUniform { - data: Box<[UVec4; ViewClusterBindings::MAX_UNIFORM_ITEMS]>, -} - -#[derive(ShaderType, Default)] -struct GpuClusterableObjectIndexListsStorage { - #[size(runtime)] - data: Vec, -} - -#[derive(ShaderType, Default)] -struct GpuClusterOffsetsAndCountsStorage { - /// The starting offset, followed by the number of point lights, spot - /// lights, reflection probes, and irradiance volumes in each cluster, in - /// that order. The remaining fields are filled with zeroes. - #[size(runtime)] - data: Vec<[UVec4; 2]>, -} - -enum ViewClusterBuffers { - Uniform { - // NOTE: UVec4 is because all arrays in Std140 layout have 16-byte alignment - clusterable_object_index_lists: UniformBuffer, - // NOTE: UVec4 is because all arrays in Std140 layout have 16-byte alignment - cluster_offsets_and_counts: UniformBuffer, - }, - Storage { - clusterable_object_index_lists: StorageBuffer, - cluster_offsets_and_counts: StorageBuffer, - }, -} - -#[derive(Component)] -pub struct ViewClusterBindings { - n_indices: usize, - n_offsets: usize, - buffers: ViewClusterBuffers, -} - impl Default for ClusterZConfig { fn default() -> Self { Self { @@ -482,433 +340,3 @@ impl GlobalVisibleClusterableObjects { self.entities.contains(&entity) } } - -impl FromWorld for GlobalClusterableObjectMeta { - fn from_world(world: &mut World) -> Self { - Self::new( - world - .resource::() - .get_supported_read_only_binding_type(CLUSTERED_FORWARD_STORAGE_BUFFER_COUNT), - ) - } -} - -impl GlobalClusterableObjectMeta { - pub fn new(buffer_binding_type: BufferBindingType) -> Self { - Self { - gpu_clusterable_objects: GpuClusterableObjects::new(buffer_binding_type), - entity_to_index: EntityHashMap::default(), - } - } -} - -impl GpuClusterableObjects { - fn new(buffer_binding_type: BufferBindingType) -> Self { - match buffer_binding_type { - BufferBindingType::Storage { .. } => Self::storage(), - BufferBindingType::Uniform => Self::uniform(), - } - } - - fn uniform() -> Self { - Self::Uniform(UniformBuffer::default()) - } - - fn storage() -> Self { - Self::Storage(StorageBuffer::default()) - } - - pub(crate) fn set(&mut self, mut clusterable_objects: Vec) { - match self { - GpuClusterableObjects::Uniform(buffer) => { - let len = clusterable_objects - .len() - .min(MAX_UNIFORM_BUFFER_CLUSTERABLE_OBJECTS); - let src = &clusterable_objects[..len]; - let dst = &mut buffer.get_mut().data[..len]; - dst.copy_from_slice(src); - } - GpuClusterableObjects::Storage(buffer) => { - buffer.get_mut().data.clear(); - buffer.get_mut().data.append(&mut clusterable_objects); - } - } - } - - pub(crate) fn write_buffer( - &mut self, - render_device: &RenderDevice, - render_queue: &RenderQueue, - ) { - match self { - GpuClusterableObjects::Uniform(buffer) => { - buffer.write_buffer(render_device, render_queue); - } - GpuClusterableObjects::Storage(buffer) => { - buffer.write_buffer(render_device, render_queue); - } - } - } - - pub fn binding(&self) -> Option { - match self { - GpuClusterableObjects::Uniform(buffer) => buffer.binding(), - GpuClusterableObjects::Storage(buffer) => buffer.binding(), - } - } - - pub fn min_size(buffer_binding_type: BufferBindingType) -> NonZero { - match buffer_binding_type { - BufferBindingType::Storage { .. } => GpuClusterableObjectsStorage::min_size(), - BufferBindingType::Uniform => GpuClusterableObjectsUniform::min_size(), - } - } -} - -impl Default for GpuClusterableObjectsUniform { - fn default() -> Self { - Self { - data: Box::new( - [GpuClusterableObject::default(); MAX_UNIFORM_BUFFER_CLUSTERABLE_OBJECTS], - ), - } - } -} - -/// Extracts clusters from the main world from the render world. -pub fn extract_clusters( - mut commands: Commands, - views: Extract>, - mapper: Extract>, -) { - for (entity, clusters, camera) in &views { - let mut entity_commands = commands - .get_entity(entity) - .expect("Clusters entity wasn't synced."); - if !camera.is_active { - entity_commands.remove::<(ExtractedClusterableObjects, ExtractedClusterConfig)>(); - continue; - } - - let entity_count: usize = clusters - .clusterable_objects - .iter() - .map(|l| l.entities.len()) - .sum(); - let mut data = Vec::with_capacity(clusters.clusterable_objects.len() + entity_count); - for cluster_objects in &clusters.clusterable_objects { - data.push(ExtractedClusterableObjectElement::ClusterHeader( - cluster_objects.counts, - )); - for clusterable_entity in &cluster_objects.entities { - if let Ok(entity) = mapper.get(*clusterable_entity) { - data.push(ExtractedClusterableObjectElement::ClusterableObjectEntity( - entity, - )); - } - } - } - - entity_commands.insert(( - ExtractedClusterableObjects { data }, - ExtractedClusterConfig { - near: clusters.near, - far: clusters.far, - dimensions: clusters.dimensions, - }, - )); - } -} - -pub fn prepare_clusters( - mut commands: Commands, - render_device: Res, - render_queue: Res, - mesh_pipeline: Res, - global_clusterable_object_meta: Res, - views: Query<(Entity, &ExtractedClusterableObjects)>, -) { - let render_device = render_device.into_inner(); - let supports_storage_buffers = matches!( - mesh_pipeline.clustered_forward_buffer_binding_type, - BufferBindingType::Storage { .. } - ); - for (entity, extracted_clusters) in &views { - let mut view_clusters_bindings = - ViewClusterBindings::new(mesh_pipeline.clustered_forward_buffer_binding_type); - view_clusters_bindings.clear(); - - for record in &extracted_clusters.data { - match record { - ExtractedClusterableObjectElement::ClusterHeader(counts) => { - let offset = view_clusters_bindings.n_indices(); - view_clusters_bindings.push_offset_and_counts(offset, counts); - } - ExtractedClusterableObjectElement::ClusterableObjectEntity(entity) => { - if let Some(clusterable_object_index) = - global_clusterable_object_meta.entity_to_index.get(entity) - { - if view_clusters_bindings.n_indices() >= ViewClusterBindings::MAX_INDICES - && !supports_storage_buffers - { - warn!( - "Clusterable object index lists are full! The clusterable \ - objects in the view are present in too many clusters." - ); - break; - } - view_clusters_bindings.push_index(*clusterable_object_index); - } - } - } - } - - view_clusters_bindings.write_buffers(render_device, &render_queue); - - commands.entity(entity).insert(view_clusters_bindings); - } -} - -impl ViewClusterBindings { - pub const MAX_OFFSETS: usize = 16384 / 4; - const MAX_UNIFORM_ITEMS: usize = Self::MAX_OFFSETS / 4; - pub const MAX_INDICES: usize = 16384; - - pub fn new(buffer_binding_type: BufferBindingType) -> Self { - Self { - n_indices: 0, - n_offsets: 0, - buffers: ViewClusterBuffers::new(buffer_binding_type), - } - } - - pub fn clear(&mut self) { - match &mut self.buffers { - ViewClusterBuffers::Uniform { - clusterable_object_index_lists, - cluster_offsets_and_counts, - } => { - *clusterable_object_index_lists.get_mut().data = - [UVec4::ZERO; Self::MAX_UNIFORM_ITEMS]; - *cluster_offsets_and_counts.get_mut().data = [UVec4::ZERO; Self::MAX_UNIFORM_ITEMS]; - } - ViewClusterBuffers::Storage { - clusterable_object_index_lists, - cluster_offsets_and_counts, - .. - } => { - clusterable_object_index_lists.get_mut().data.clear(); - cluster_offsets_and_counts.get_mut().data.clear(); - } - } - } - - fn push_offset_and_counts(&mut self, offset: usize, counts: &ClusterableObjectCounts) { - match &mut self.buffers { - ViewClusterBuffers::Uniform { - cluster_offsets_and_counts, - .. - } => { - let array_index = self.n_offsets >> 2; // >> 2 is equivalent to / 4 - if array_index >= Self::MAX_UNIFORM_ITEMS { - warn!("cluster offset and count out of bounds!"); - return; - } - let component = self.n_offsets & ((1 << 2) - 1); - let packed = - pack_offset_and_counts(offset, counts.point_lights, counts.spot_lights); - - cluster_offsets_and_counts.get_mut().data[array_index][component] = packed; - } - ViewClusterBuffers::Storage { - cluster_offsets_and_counts, - .. - } => { - cluster_offsets_and_counts.get_mut().data.push([ - uvec4( - offset as u32, - counts.point_lights, - counts.spot_lights, - counts.reflection_probes, - ), - uvec4(counts.irradiance_volumes, counts.decals, 0, 0), - ]); - } - } - - self.n_offsets += 1; - } - - pub fn n_indices(&self) -> usize { - self.n_indices - } - - pub fn push_index(&mut self, index: usize) { - match &mut self.buffers { - ViewClusterBuffers::Uniform { - clusterable_object_index_lists, - .. - } => { - let array_index = self.n_indices >> 4; // >> 4 is equivalent to / 16 - let component = (self.n_indices >> 2) & ((1 << 2) - 1); - let sub_index = self.n_indices & ((1 << 2) - 1); - let index = index as u32; - - clusterable_object_index_lists.get_mut().data[array_index][component] |= - index << (8 * sub_index); - } - ViewClusterBuffers::Storage { - clusterable_object_index_lists, - .. - } => { - clusterable_object_index_lists - .get_mut() - .data - .push(index as u32); - } - } - - self.n_indices += 1; - } - - pub fn write_buffers(&mut self, render_device: &RenderDevice, render_queue: &RenderQueue) { - match &mut self.buffers { - ViewClusterBuffers::Uniform { - clusterable_object_index_lists, - cluster_offsets_and_counts, - } => { - clusterable_object_index_lists.write_buffer(render_device, render_queue); - cluster_offsets_and_counts.write_buffer(render_device, render_queue); - } - ViewClusterBuffers::Storage { - clusterable_object_index_lists, - cluster_offsets_and_counts, - } => { - clusterable_object_index_lists.write_buffer(render_device, render_queue); - cluster_offsets_and_counts.write_buffer(render_device, render_queue); - } - } - } - - pub fn clusterable_object_index_lists_binding(&self) -> Option { - match &self.buffers { - ViewClusterBuffers::Uniform { - clusterable_object_index_lists, - .. - } => clusterable_object_index_lists.binding(), - ViewClusterBuffers::Storage { - clusterable_object_index_lists, - .. - } => clusterable_object_index_lists.binding(), - } - } - - pub fn offsets_and_counts_binding(&self) -> Option { - match &self.buffers { - ViewClusterBuffers::Uniform { - cluster_offsets_and_counts, - .. - } => cluster_offsets_and_counts.binding(), - ViewClusterBuffers::Storage { - cluster_offsets_and_counts, - .. - } => cluster_offsets_and_counts.binding(), - } - } - - pub fn min_size_clusterable_object_index_lists( - buffer_binding_type: BufferBindingType, - ) -> NonZero { - match buffer_binding_type { - BufferBindingType::Storage { .. } => GpuClusterableObjectIndexListsStorage::min_size(), - BufferBindingType::Uniform => GpuClusterableObjectIndexListsUniform::min_size(), - } - } - - pub fn min_size_cluster_offsets_and_counts( - buffer_binding_type: BufferBindingType, - ) -> NonZero { - match buffer_binding_type { - BufferBindingType::Storage { .. } => GpuClusterOffsetsAndCountsStorage::min_size(), - BufferBindingType::Uniform => GpuClusterOffsetsAndCountsUniform::min_size(), - } - } -} - -impl ViewClusterBuffers { - fn new(buffer_binding_type: BufferBindingType) -> Self { - match buffer_binding_type { - BufferBindingType::Storage { .. } => Self::storage(), - BufferBindingType::Uniform => Self::uniform(), - } - } - - fn uniform() -> Self { - ViewClusterBuffers::Uniform { - clusterable_object_index_lists: UniformBuffer::default(), - cluster_offsets_and_counts: UniformBuffer::default(), - } - } - - fn storage() -> Self { - ViewClusterBuffers::Storage { - clusterable_object_index_lists: StorageBuffer::default(), - cluster_offsets_and_counts: StorageBuffer::default(), - } - } -} - -// Compresses the offset and counts of point and spot lights so that they fit in -// a UBO. -// -// This function is only used if storage buffers are unavailable on this -// platform: typically, on WebGL 2. -// -// NOTE: With uniform buffer max binding size as 16384 bytes -// that means we can fit 204 clusterable objects in one uniform -// buffer, which means the count can be at most 204 so it -// needs 9 bits. -// The array of indices can also use u8 and that means the -// offset in to the array of indices needs to be able to address -// 16384 values. log2(16384) = 14 bits. -// We use 32 bits to store the offset and counts so -// we pack the offset into the upper 14 bits of a u32, -// the point light count into bits 9-17, and the spot light count into bits 0-8. -// [ 31 .. 18 | 17 .. 9 | 8 .. 0 ] -// [ offset | point light count | spot light count ] -// -// NOTE: This assumes CPU and GPU endianness are the same which is true -// for all common and tested x86/ARM CPUs and AMD/NVIDIA/Intel/Apple/etc GPUs -// -// NOTE: On platforms that use this function, we don't cluster light probes, so -// the number of light probes is irrelevant. -fn pack_offset_and_counts(offset: usize, point_count: u32, spot_count: u32) -> u32 { - ((offset as u32 & CLUSTER_OFFSET_MASK) << (CLUSTER_COUNT_SIZE * 2)) - | ((point_count & CLUSTER_COUNT_MASK) << CLUSTER_COUNT_SIZE) - | (spot_count & CLUSTER_COUNT_MASK) -} - -#[derive(ShaderType)] -struct GpuClusterableObjectIndexListsUniform { - data: Box<[UVec4; ViewClusterBindings::MAX_UNIFORM_ITEMS]>, -} - -// NOTE: Assert at compile time that GpuClusterableObjectIndexListsUniform -// fits within the maximum uniform buffer binding size -const _: () = assert!(GpuClusterableObjectIndexListsUniform::SHADER_SIZE.get() <= 16384); - -impl Default for GpuClusterableObjectIndexListsUniform { - fn default() -> Self { - Self { - data: Box::new([UVec4::ZERO; ViewClusterBindings::MAX_UNIFORM_ITEMS]), - } - } -} - -impl Default for GpuClusterOffsetsAndCountsUniform { - fn default() -> Self { - Self { - data: Box::new([UVec4::ZERO; ViewClusterBindings::MAX_UNIFORM_ITEMS]), - } - } -} From 8e89511e47d27c96fa63fb0b000c45a7baaa260a Mon Sep 17 00:00:00 2001 From: Giacomo Stevanato Date: Sun, 6 Jul 2025 20:15:28 +0200 Subject: [PATCH 2/5] Remove `Bundle::register_required_components` (#19967) # Objective - `Bundle::register_required_components` is not used anywhere, let's remove it --- crates/bevy_ecs/macros/src/lib.rs | 7 ----- crates/bevy_ecs/src/bundle.rs | 27 ------------------- crates/bevy_ecs/src/spawn.rs | 20 -------------- ...ove_bundle_register_required_components.md | 6 +++++ 4 files changed, 6 insertions(+), 54 deletions(-) create mode 100644 release-content/migration-guides/remove_bundle_register_required_components.md diff --git a/crates/bevy_ecs/macros/src/lib.rs b/crates/bevy_ecs/macros/src/lib.rs index 20f7ad4275..7b388f4a14 100644 --- a/crates/bevy_ecs/macros/src/lib.rs +++ b/crates/bevy_ecs/macros/src/lib.rs @@ -159,13 +159,6 @@ pub fn derive_bundle(input: TokenStream) -> TokenStream { ) { #(<#active_field_types as #ecs_path::bundle::Bundle>::get_component_ids(components, &mut *ids);)* } - - fn register_required_components( - components: &mut #ecs_path::component::ComponentsRegistrator, - required_components: &mut #ecs_path::component::RequiredComponents - ) { - #(<#active_field_types as #ecs_path::bundle::Bundle>::register_required_components(components, required_components);)* - } } }; diff --git a/crates/bevy_ecs/src/bundle.rs b/crates/bevy_ecs/src/bundle.rs index 8efdc60ad9..78948f81b1 100644 --- a/crates/bevy_ecs/src/bundle.rs +++ b/crates/bevy_ecs/src/bundle.rs @@ -207,12 +207,6 @@ pub unsafe trait Bundle: DynamicBundle + Send + Sync + 'static { /// Gets this [`Bundle`]'s component ids. This will be [`None`] if the component has not been registered. fn get_component_ids(components: &Components, ids: &mut impl FnMut(Option)); - - /// Registers components that are required by the components in this [`Bundle`]. - fn register_required_components( - _components: &mut ComponentsRegistrator, - _required_components: &mut RequiredComponents, - ); } /// Creates a [`Bundle`] by taking it from internal storage. @@ -279,20 +273,6 @@ unsafe impl Bundle for C { ids(components.register_component::()); } - fn register_required_components( - components: &mut ComponentsRegistrator, - required_components: &mut RequiredComponents, - ) { - let component_id = components.register_component::(); - ::register_required_components( - component_id, - components, - required_components, - 0, - &mut Vec::new(), - ); - } - fn get_component_ids(components: &Components, ids: &mut impl FnMut(Option)) { ids(components.get_id(TypeId::of::())); } @@ -347,13 +327,6 @@ macro_rules! tuple_impl { fn get_component_ids(components: &Components, ids: &mut impl FnMut(Option)){ $(<$name as Bundle>::get_component_ids(components, ids);)* } - - fn register_required_components( - components: &mut ComponentsRegistrator, - required_components: &mut RequiredComponents, - ) { - $(<$name as Bundle>::register_required_components(components, required_components);)* - } } #[expect( diff --git a/crates/bevy_ecs/src/spawn.rs b/crates/bevy_ecs/src/spawn.rs index 0c30c14b9c..8e1a019222 100644 --- a/crates/bevy_ecs/src/spawn.rs +++ b/crates/bevy_ecs/src/spawn.rs @@ -199,16 +199,6 @@ unsafe impl + Send + Sync + 'static> Bundle ) { ::get_component_ids(components, ids); } - - fn register_required_components( - components: &mut crate::component::ComponentsRegistrator, - required_components: &mut crate::component::RequiredComponents, - ) { - ::register_required_components( - components, - required_components, - ); - } } impl> DynamicBundle for SpawnRelatedBundle { @@ -267,16 +257,6 @@ unsafe impl Bundle for SpawnOneRelated { ) { ::get_component_ids(components, ids); } - - fn register_required_components( - components: &mut crate::component::ComponentsRegistrator, - required_components: &mut crate::component::RequiredComponents, - ) { - ::register_required_components( - components, - required_components, - ); - } } /// [`RelationshipTarget`] methods that create a [`Bundle`] with a [`DynamicBundle::Effect`] that: diff --git a/release-content/migration-guides/remove_bundle_register_required_components.md b/release-content/migration-guides/remove_bundle_register_required_components.md new file mode 100644 index 0000000000..87b65d7a91 --- /dev/null +++ b/release-content/migration-guides/remove_bundle_register_required_components.md @@ -0,0 +1,6 @@ +--- +title: Remove Bundle::register_required_components +pull_requests: [19967] +--- + +This method was effectively dead-code as it was never used by the ECS to compute required components, hence it was removed. if you were overriding its implementation you can just remove it, as it never did anything. If you were using it in any other way, please open an issue. From 1579256709dd64dcc4910b0bad5e81622d9ede0c Mon Sep 17 00:00:00 2001 From: atlv Date: Sun, 6 Jul 2025 15:04:30 -0400 Subject: [PATCH 3/5] Rename light visibility class (#19986) # Objective - prepare bevy_light for split - make struct named better - put it where it belongs ## Solution - do those things ## Testing - 3d_scene, lighting --------- Co-authored-by: Alice Cecile --- .../src/cluster/extract_and_prepare.rs | 3 ++- crates/bevy_pbr/src/cluster/mod.rs | 8 +++++-- .../bevy_pbr/src/light/directional_light.rs | 5 +++-- crates/bevy_pbr/src/light/mod.rs | 22 ++++++++----------- crates/bevy_pbr/src/light/point_light.rs | 4 ++-- crates/bevy_pbr/src/light/spot_light.rs | 4 ++-- .../LightVisibilityClass_rename.md | 8 +++++++ 7 files changed, 32 insertions(+), 22 deletions(-) create mode 100644 release-content/migration-guides/LightVisibilityClass_rename.md diff --git a/crates/bevy_pbr/src/cluster/extract_and_prepare.rs b/crates/bevy_pbr/src/cluster/extract_and_prepare.rs index 9fa6a5996c..56267f46b2 100644 --- a/crates/bevy_pbr/src/cluster/extract_and_prepare.rs +++ b/crates/bevy_pbr/src/cluster/extract_and_prepare.rs @@ -13,7 +13,8 @@ use bevy_render::{ }; use tracing::warn; -use crate::{cluster::ClusterableObjectCounts, Clusters, GlobalClusterSettings, MeshPipeline}; +use super::{ClusterableObjectCounts, Clusters, GlobalClusterSettings}; +use crate::MeshPipeline; // NOTE: this must be kept in sync with the same constants in // `mesh_view_types.wgsl`. diff --git a/crates/bevy_pbr/src/cluster/mod.rs b/crates/bevy_pbr/src/cluster/mod.rs index 9cddc0a1b6..90af295cf6 100644 --- a/crates/bevy_pbr/src/cluster/mod.rs +++ b/crates/bevy_pbr/src/cluster/mod.rs @@ -22,7 +22,6 @@ use bevy_transform::components::Transform; use tracing::warn; pub(crate) use crate::cluster::assign::assign_objects_to_clusters; -use crate::LightVisibilityClass; pub(crate) mod assign; mod extract_and_prepare; @@ -115,6 +114,11 @@ pub struct Clusters { pub(crate) clusterable_objects: Vec, } +/// The [`VisibilityClass`] used for clusterables (decals, point lights, directional lights, and spot lights). +/// +/// [`VisibilityClass`]: bevy_camera::visibility::VisibilityClass +pub struct ClusterVisibilityClass; + #[derive(Clone, Component, Debug, Default)] pub struct VisibleClusterableObjects { pub(crate) entities: Vec, @@ -157,7 +161,7 @@ struct ClusterableObjectCounts { #[derive(Component, Debug, Clone, Reflect, ExtractComponent)] #[reflect(Component, Debug, Clone)] #[require(Transform, Visibility, VisibilityClass)] -#[component(on_add = visibility::add_visibility_class::)] +#[component(on_add = visibility::add_visibility_class::)] pub struct ClusteredDecal { /// The image that the clustered decal projects. /// diff --git a/crates/bevy_pbr/src/light/directional_light.rs b/crates/bevy_pbr/src/light/directional_light.rs index dd2da1d975..0b54b63cfe 100644 --- a/crates/bevy_pbr/src/light/directional_light.rs +++ b/crates/bevy_pbr/src/light/directional_light.rs @@ -10,7 +10,8 @@ use bevy_image::Image; use bevy_reflect::prelude::*; use bevy_transform::components::Transform; -use crate::{cascade::CascadeShadowConfig, light_consts, Cascades, LightVisibilityClass}; +use super::{cascade::CascadeShadowConfig, light_consts, Cascades}; +use crate::cluster::ClusterVisibilityClass; /// A Directional light. /// @@ -63,7 +64,7 @@ use crate::{cascade::CascadeShadowConfig, light_consts, Cascades, LightVisibilit Visibility, VisibilityClass )] -#[component(on_add = visibility::add_visibility_class::)] +#[component(on_add = visibility::add_visibility_class::)] pub struct DirectionalLight { /// The color of the light. /// diff --git a/crates/bevy_pbr/src/light/mod.rs b/crates/bevy_pbr/src/light/mod.rs index 53199d39f1..a220b65439 100644 --- a/crates/bevy_pbr/src/light/mod.rs +++ b/crates/bevy_pbr/src/light/mod.rs @@ -16,23 +16,24 @@ use bevy_transform::{components::GlobalTransform, TransformSystems}; use bevy_utils::Parallel; use core::ops::DerefMut; -pub use crate::light::spot_light::{spot_light_clip_from_view, spot_light_world_from_view}; -use crate::{ - add_clusters, assign_objects_to_clusters, - cascade::{build_directional_light_cascades, clear_directional_light_cascades}, - CascadeShadowConfig, Cascades, VisibleClusterableObjects, -}; +use crate::cluster::{add_clusters, assign_objects_to_clusters, VisibleClusterableObjects}; mod ambient_light; pub use ambient_light::AmbientLight; - pub mod cascade; +use cascade::{ + build_directional_light_cascades, clear_directional_light_cascades, CascadeShadowConfig, + Cascades, +}; mod point_light; pub use point_light::{ update_point_light_frusta, PointLight, PointLightShadowMap, PointLightTexture, }; mod spot_light; -pub use spot_light::{update_spot_light_frusta, SpotLight, SpotLightTexture}; +pub use spot_light::{ + spot_light_clip_from_view, spot_light_world_from_view, update_spot_light_frusta, SpotLight, + SpotLightTexture, +}; mod directional_light; pub use directional_light::{ update_directional_light_frusta, DirectionalLight, DirectionalLightShadowMap, @@ -243,11 +244,6 @@ pub enum ShadowFilteringMethod { Temporal, } -/// The [`VisibilityClass`] used for all lights (point, directional, and spot). -/// -/// [`VisibilityClass`]: bevy_camera::visibility::VisibilityClass -pub struct LightVisibilityClass; - /// System sets used to run light-related systems. #[derive(Debug, Hash, PartialEq, Eq, Clone, SystemSet)] pub enum SimulationLightSystems { diff --git a/crates/bevy_pbr/src/light/point_light.rs b/crates/bevy_pbr/src/light/point_light.rs index 8ba108adcc..84c024b7a2 100644 --- a/crates/bevy_pbr/src/light/point_light.rs +++ b/crates/bevy_pbr/src/light/point_light.rs @@ -10,7 +10,7 @@ use bevy_math::Mat4; use bevy_reflect::prelude::*; use bevy_transform::components::{GlobalTransform, Transform}; -use crate::{GlobalVisibleClusterableObjects, LightVisibilityClass}; +use crate::cluster::{ClusterVisibilityClass, GlobalVisibleClusterableObjects}; /// A light that emits light in all directions from a central point. /// @@ -44,7 +44,7 @@ use crate::{GlobalVisibleClusterableObjects, LightVisibilityClass}; Visibility, VisibilityClass )] -#[component(on_add = visibility::add_visibility_class::)] +#[component(on_add = visibility::add_visibility_class::)] pub struct PointLight { /// The color of this light source. pub color: Color, diff --git a/crates/bevy_pbr/src/light/spot_light.rs b/crates/bevy_pbr/src/light/spot_light.rs index 0e09b1c509..c75a3124d3 100644 --- a/crates/bevy_pbr/src/light/spot_light.rs +++ b/crates/bevy_pbr/src/light/spot_light.rs @@ -11,7 +11,7 @@ use bevy_reflect::prelude::*; use bevy_render::view::VisibleMeshEntities; use bevy_transform::components::{GlobalTransform, Transform}; -use crate::{GlobalVisibleClusterableObjects, LightVisibilityClass}; +use crate::cluster::{ClusterVisibilityClass, GlobalVisibleClusterableObjects}; /// A light that emits light in a given direction from a central point. /// @@ -21,7 +21,7 @@ use crate::{GlobalVisibleClusterableObjects, LightVisibilityClass}; #[derive(Component, Debug, Clone, Copy, Reflect)] #[reflect(Component, Default, Debug, Clone)] #[require(Frustum, VisibleMeshEntities, Transform, Visibility, VisibilityClass)] -#[component(on_add = visibility::add_visibility_class::)] +#[component(on_add = visibility::add_visibility_class::)] pub struct SpotLight { /// The color of the light. /// diff --git a/release-content/migration-guides/LightVisibilityClass_rename.md b/release-content/migration-guides/LightVisibilityClass_rename.md new file mode 100644 index 0000000000..fcf1f4cb13 --- /dev/null +++ b/release-content/migration-guides/LightVisibilityClass_rename.md @@ -0,0 +1,8 @@ +--- +title: `LightVisibilityClass` renamed to `ClusterVisibilityClass` +pull_requests: [19986] +--- + +When clustered decals were added, they used `LightVisibilityClass` to share the clustering infrastructure. +This revealed that this visibility class wasn't really about lights, but about clustering. +It has been renamed to `ClusterVisibilityClass` and moved to live alongside clustering-specific types. From baa88b98a36bf0af584a135cd30bd5847e4c2d4e Mon Sep 17 00:00:00 2001 From: atlv Date: Sun, 6 Jul 2025 15:13:10 -0400 Subject: [PATCH 4/5] fix variable-termination loop gradients by sampling specific lod (#19988) # Objective - Calculating gradients in variable-termination loop is bad, and we dont need to here ## Solution - Sample mip 0 always ## Testing - volumetric_fog example --- crates/bevy_pbr/src/volumetric_fog/volumetric_fog.wgsl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/bevy_pbr/src/volumetric_fog/volumetric_fog.wgsl b/crates/bevy_pbr/src/volumetric_fog/volumetric_fog.wgsl index 058f73fca9..43e3fc9278 100644 --- a/crates/bevy_pbr/src/volumetric_fog/volumetric_fog.wgsl +++ b/crates/bevy_pbr/src/volumetric_fog/volumetric_fog.wgsl @@ -251,7 +251,7 @@ fn fragment(@builtin(position) position: vec4) -> @location(0) vec4 { // case. let P_uvw = Ro_uvw + Rd_step_uvw * f32(step); if (all(P_uvw >= vec3(0.0)) && all(P_uvw <= vec3(1.0))) { - density *= textureSample(density_texture, density_sampler, P_uvw + density_texture_offset).r; + density *= textureSampleLevel(density_texture, density_sampler, P_uvw + density_texture_offset, 0.0).r; } else { density = 0.0; } From 5b38989ac4a5b02f577407106009a514b8256fc5 Mon Sep 17 00:00:00 2001 From: atlv Date: Sun, 6 Jul 2025 15:36:58 -0400 Subject: [PATCH 5/5] dont hard code clustering limits on cpu side so they can be informed by Limits later (#19985) # Objective - prepare bevy_light for split - make limits more dynamically configurable ## Solution - use settings struct ## Testing - 3d_scene, lighting --- crates/bevy_pbr/src/cluster/assign.rs | 26 ++++++++++++------- .../src/cluster/extract_and_prepare.rs | 2 ++ crates/bevy_pbr/src/cluster/mod.rs | 2 ++ 3 files changed, 20 insertions(+), 10 deletions(-) diff --git a/crates/bevy_pbr/src/cluster/assign.rs b/crates/bevy_pbr/src/cluster/assign.rs index 9dc9a56b52..e204644204 100644 --- a/crates/bevy_pbr/src/cluster/assign.rs +++ b/crates/bevy_pbr/src/cluster/assign.rs @@ -20,8 +20,7 @@ use tracing::warn; use super::{ ClusterConfig, ClusterFarZMode, ClusteredDecal, Clusters, GlobalClusterSettings, - GlobalVisibleClusterableObjects, ViewClusterBindings, VisibleClusterableObjects, - MAX_UNIFORM_BUFFER_CLUSTERABLE_OBJECTS, + GlobalVisibleClusterableObjects, VisibleClusterableObjects, }; use crate::{ prelude::EnvironmentMapLight, ExtractedPointLight, LightProbe, PointLight, SpotLight, @@ -263,7 +262,7 @@ pub(crate) fn assign_objects_to_clusters( })); } - if clusterable_objects.len() > MAX_UNIFORM_BUFFER_CLUSTERABLE_OBJECTS + if clusterable_objects.len() > global_cluster_settings.max_uniform_buffer_clusterable_objects && !global_cluster_settings.supports_storage_buffers { clusterable_objects.sort_by_cached_key(|clusterable_object| { @@ -282,7 +281,9 @@ pub(crate) fn assign_objects_to_clusters( let mut clusterable_objects_in_view_count = 0; clusterable_objects.retain(|clusterable_object| { // take one extra clusterable object to check if we should emit the warning - if clusterable_objects_in_view_count == MAX_UNIFORM_BUFFER_CLUSTERABLE_OBJECTS + 1 { + if clusterable_objects_in_view_count + == global_cluster_settings.max_uniform_buffer_clusterable_objects + 1 + { false } else { let clusterable_object_sphere = clusterable_object.sphere(); @@ -298,17 +299,19 @@ pub(crate) fn assign_objects_to_clusters( } }); - if clusterable_objects.len() > MAX_UNIFORM_BUFFER_CLUSTERABLE_OBJECTS + if clusterable_objects.len() + > global_cluster_settings.max_uniform_buffer_clusterable_objects && !*max_clusterable_objects_warning_emitted { warn!( - "MAX_UNIFORM_BUFFER_CLUSTERABLE_OBJECTS ({}) exceeded", - MAX_UNIFORM_BUFFER_CLUSTERABLE_OBJECTS + "max_uniform_buffer_clusterable_objects ({}) exceeded", + global_cluster_settings.max_uniform_buffer_clusterable_objects ); *max_clusterable_objects_warning_emitted = true; } - clusterable_objects.truncate(MAX_UNIFORM_BUFFER_CLUSTERABLE_OBJECTS); + clusterable_objects + .truncate(global_cluster_settings.max_uniform_buffer_clusterable_objects); } for ( @@ -448,14 +451,17 @@ pub(crate) fn assign_objects_to_clusters( (xy_count.x + x_overlap) * (xy_count.y + y_overlap) * z_count as f32; } - if cluster_index_estimate > ViewClusterBindings::MAX_INDICES as f32 { + if cluster_index_estimate + > global_cluster_settings.view_cluster_bindings_max_indices as f32 + { // scale x and y cluster count to be able to fit all our indices // we take the ratio of the actual indices over the index estimate. // this is not guaranteed to be small enough due to overlapped tiles, but // the conservative estimate is more than sufficient to cover the // difference - let index_ratio = ViewClusterBindings::MAX_INDICES as f32 / cluster_index_estimate; + let index_ratio = global_cluster_settings.view_cluster_bindings_max_indices as f32 + / cluster_index_estimate; let xy_ratio = index_ratio.sqrt(); requested_cluster_dimensions.x = diff --git a/crates/bevy_pbr/src/cluster/extract_and_prepare.rs b/crates/bevy_pbr/src/cluster/extract_and_prepare.rs index 56267f46b2..53187a2b02 100644 --- a/crates/bevy_pbr/src/cluster/extract_and_prepare.rs +++ b/crates/bevy_pbr/src/cluster/extract_and_prepare.rs @@ -47,6 +47,8 @@ pub(crate) fn make_global_cluster_settings(world: &World) -> GlobalClusterSettin GlobalClusterSettings { supports_storage_buffers, clustered_decals_are_usable, + max_uniform_buffer_clusterable_objects: MAX_UNIFORM_BUFFER_CLUSTERABLE_OBJECTS, + view_cluster_bindings_max_indices: ViewClusterBindings::MAX_INDICES, } } diff --git a/crates/bevy_pbr/src/cluster/mod.rs b/crates/bevy_pbr/src/cluster/mod.rs index 90af295cf6..5132326fb1 100644 --- a/crates/bevy_pbr/src/cluster/mod.rs +++ b/crates/bevy_pbr/src/cluster/mod.rs @@ -43,6 +43,8 @@ mod test; pub struct GlobalClusterSettings { pub supports_storage_buffers: bool, pub clustered_decals_are_usable: bool, + pub max_uniform_buffer_clusterable_objects: usize, + pub view_cluster_bindings_max_indices: usize, } /// Configure the far z-plane mode used for the furthest depth slice for clustered forward