Merge branch 'main' into proper-json-schema
This commit is contained in:
commit
08f17bb337
@ -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);)*
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -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<ComponentId>));
|
||||
|
||||
/// 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<C: Component> Bundle for C {
|
||||
ids(components.register_component::<C>());
|
||||
}
|
||||
|
||||
fn register_required_components(
|
||||
components: &mut ComponentsRegistrator,
|
||||
required_components: &mut RequiredComponents,
|
||||
) {
|
||||
let component_id = components.register_component::<C>();
|
||||
<C as Component>::register_required_components(
|
||||
component_id,
|
||||
components,
|
||||
required_components,
|
||||
0,
|
||||
&mut Vec::new(),
|
||||
);
|
||||
}
|
||||
|
||||
fn get_component_ids(components: &Components, ids: &mut impl FnMut(Option<ComponentId>)) {
|
||||
ids(components.get_id(TypeId::of::<C>()));
|
||||
}
|
||||
@ -347,13 +327,6 @@ macro_rules! tuple_impl {
|
||||
fn get_component_ids(components: &Components, ids: &mut impl FnMut(Option<ComponentId>)){
|
||||
$(<$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(
|
||||
|
@ -199,16 +199,6 @@ unsafe impl<R: Relationship, L: SpawnableList<R> + Send + Sync + 'static> Bundle
|
||||
) {
|
||||
<R::RelationshipTarget as Bundle>::get_component_ids(components, ids);
|
||||
}
|
||||
|
||||
fn register_required_components(
|
||||
components: &mut crate::component::ComponentsRegistrator,
|
||||
required_components: &mut crate::component::RequiredComponents,
|
||||
) {
|
||||
<R::RelationshipTarget as Bundle>::register_required_components(
|
||||
components,
|
||||
required_components,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
impl<R: Relationship, L: SpawnableList<R>> DynamicBundle for SpawnRelatedBundle<R, L> {
|
||||
@ -267,16 +257,6 @@ unsafe impl<R: Relationship, B: Bundle> Bundle for SpawnOneRelated<R, B> {
|
||||
) {
|
||||
<R::RelationshipTarget as Bundle>::get_component_ids(components, ids);
|
||||
}
|
||||
|
||||
fn register_required_components(
|
||||
components: &mut crate::component::ComponentsRegistrator,
|
||||
required_components: &mut crate::component::RequiredComponents,
|
||||
) {
|
||||
<R::RelationshipTarget as Bundle>::register_required_components(
|
||||
components,
|
||||
required_components,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// [`RelationshipTarget`] methods that create a [`Bundle`] with a [`DynamicBundle::Effect`] that:
|
||||
|
@ -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 =
|
||||
|
581
crates/bevy_pbr/src/cluster/extract_and_prepare.rs
Normal file
581
crates/bevy_pbr/src/cluster/extract_and_prepare.rs
Normal file
@ -0,0 +1,581 @@
|
||||
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 super::{ClusterableObjectCounts, Clusters, GlobalClusterSettings};
|
||||
use crate::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::<GpuClusterableObject>() * 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::<RenderDevice>();
|
||||
let adapter = world.resource::<RenderAdapter>();
|
||||
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,
|
||||
max_uniform_buffer_clusterable_objects: MAX_UNIFORM_BUFFER_CLUSTERABLE_OBJECTS,
|
||||
view_cluster_bindings_max_indices: ViewClusterBindings::MAX_INDICES,
|
||||
}
|
||||
}
|
||||
|
||||
#[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<usize>,
|
||||
}
|
||||
|
||||
pub enum GpuClusterableObjects {
|
||||
Uniform(UniformBuffer<GpuClusterableObjectsUniform>),
|
||||
Storage(StorageBuffer<GpuClusterableObjectsStorage>),
|
||||
}
|
||||
|
||||
#[derive(ShaderType)]
|
||||
pub struct GpuClusterableObjectsUniform {
|
||||
data: Box<[GpuClusterableObject; MAX_UNIFORM_BUFFER_CLUSTERABLE_OBJECTS]>,
|
||||
}
|
||||
|
||||
#[derive(ShaderType, Default)]
|
||||
pub struct GpuClusterableObjectsStorage {
|
||||
#[size(runtime)]
|
||||
data: Vec<GpuClusterableObject>,
|
||||
}
|
||||
|
||||
#[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<ExtractedClusterableObjectElement>,
|
||||
}
|
||||
|
||||
#[derive(ShaderType)]
|
||||
struct GpuClusterOffsetsAndCountsUniform {
|
||||
data: Box<[UVec4; ViewClusterBindings::MAX_UNIFORM_ITEMS]>,
|
||||
}
|
||||
|
||||
#[derive(ShaderType, Default)]
|
||||
struct GpuClusterableObjectIndexListsStorage {
|
||||
#[size(runtime)]
|
||||
data: Vec<u32>,
|
||||
}
|
||||
|
||||
#[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<GpuClusterableObjectIndexListsUniform>,
|
||||
// NOTE: UVec4 is because all arrays in Std140 layout have 16-byte alignment
|
||||
cluster_offsets_and_counts: UniformBuffer<GpuClusterOffsetsAndCountsUniform>,
|
||||
},
|
||||
Storage {
|
||||
clusterable_object_index_lists: StorageBuffer<GpuClusterableObjectIndexListsStorage>,
|
||||
cluster_offsets_and_counts: StorageBuffer<GpuClusterOffsetsAndCountsStorage>,
|
||||
},
|
||||
}
|
||||
|
||||
#[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::<RenderDevice>()
|
||||
.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<GpuClusterableObject>) {
|
||||
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<BindingResource> {
|
||||
match self {
|
||||
GpuClusterableObjects::Uniform(buffer) => buffer.binding(),
|
||||
GpuClusterableObjects::Storage(buffer) => buffer.binding(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn min_size(buffer_binding_type: BufferBindingType) -> NonZero<u64> {
|
||||
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<Query<(RenderEntity, &Clusters, &Camera)>>,
|
||||
mapper: Extract<Query<RenderEntity>>,
|
||||
) {
|
||||
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<RenderDevice>,
|
||||
render_queue: Res<RenderQueue>,
|
||||
mesh_pipeline: Res<MeshPipeline>,
|
||||
global_clusterable_object_meta: Res<GlobalClusterableObjectMeta>,
|
||||
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<BindingResource> {
|
||||
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<BindingResource> {
|
||||
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<u64> {
|
||||
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<u64> {
|
||||
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]),
|
||||
}
|
||||
}
|
||||
}
|
@ -1,65 +1,35 @@
|
||||
//! 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};
|
||||
|
||||
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::<GpuClusterableObject>() * 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
|
||||
@ -73,21 +43,8 @@ const CLUSTER_COUNT_MASK: u32 = (1 << CLUSTER_COUNT_SIZE) - 1;
|
||||
pub struct GlobalClusterSettings {
|
||||
pub supports_storage_buffers: bool,
|
||||
pub clustered_decals_are_usable: bool,
|
||||
}
|
||||
|
||||
pub(crate) fn make_global_cluster_settings(world: &World) -> GlobalClusterSettings {
|
||||
let device = world.resource::<RenderDevice>();
|
||||
let adapter = world.resource::<RenderAdapter>();
|
||||
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,
|
||||
}
|
||||
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
|
||||
@ -159,6 +116,11 @@ pub struct Clusters {
|
||||
pub(crate) clusterable_objects: Vec<VisibleClusterableObjects>,
|
||||
}
|
||||
|
||||
/// 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<Entity>,
|
||||
@ -170,54 +132,6 @@ pub struct GlobalVisibleClusterableObjects {
|
||||
pub(crate) entities: HashSet<Entity>,
|
||||
}
|
||||
|
||||
#[derive(Resource)]
|
||||
pub struct GlobalClusterableObjectMeta {
|
||||
pub gpu_clusterable_objects: GpuClusterableObjects,
|
||||
pub entity_to_index: EntityHashMap<usize>,
|
||||
}
|
||||
|
||||
#[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<GpuClusterableObjectsUniform>),
|
||||
Storage(StorageBuffer<GpuClusterableObjectsStorage>),
|
||||
}
|
||||
|
||||
#[derive(ShaderType)]
|
||||
pub struct GpuClusterableObjectsUniform {
|
||||
data: Box<[GpuClusterableObject; MAX_UNIFORM_BUFFER_CLUSTERABLE_OBJECTS]>,
|
||||
}
|
||||
|
||||
#[derive(ShaderType, Default)]
|
||||
pub struct GpuClusterableObjectsStorage {
|
||||
#[size(runtime)]
|
||||
data: Vec<GpuClusterableObject>,
|
||||
}
|
||||
|
||||
#[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
|
||||
@ -249,7 +163,7 @@ struct ClusterableObjectCounts {
|
||||
#[derive(Component, Debug, Clone, Reflect, ExtractComponent)]
|
||||
#[reflect(Component, Debug, Clone)]
|
||||
#[require(Transform, Visibility, VisibilityClass)]
|
||||
#[component(on_add = visibility::add_visibility_class::<LightVisibilityClass>)]
|
||||
#[component(on_add = visibility::add_visibility_class::<ClusterVisibilityClass>)]
|
||||
pub struct ClusteredDecal {
|
||||
/// The image that the clustered decal projects.
|
||||
///
|
||||
@ -264,56 +178,6 @@ pub struct ClusteredDecal {
|
||||
pub tag: u32,
|
||||
}
|
||||
|
||||
enum ExtractedClusterableObjectElement {
|
||||
ClusterHeader(ClusterableObjectCounts),
|
||||
ClusterableObjectEntity(Entity),
|
||||
}
|
||||
|
||||
#[derive(Component)]
|
||||
pub struct ExtractedClusterableObjects {
|
||||
data: Vec<ExtractedClusterableObjectElement>,
|
||||
}
|
||||
|
||||
#[derive(ShaderType)]
|
||||
struct GpuClusterOffsetsAndCountsUniform {
|
||||
data: Box<[UVec4; ViewClusterBindings::MAX_UNIFORM_ITEMS]>,
|
||||
}
|
||||
|
||||
#[derive(ShaderType, Default)]
|
||||
struct GpuClusterableObjectIndexListsStorage {
|
||||
#[size(runtime)]
|
||||
data: Vec<u32>,
|
||||
}
|
||||
|
||||
#[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<GpuClusterableObjectIndexListsUniform>,
|
||||
// NOTE: UVec4 is because all arrays in Std140 layout have 16-byte alignment
|
||||
cluster_offsets_and_counts: UniformBuffer<GpuClusterOffsetsAndCountsUniform>,
|
||||
},
|
||||
Storage {
|
||||
clusterable_object_index_lists: StorageBuffer<GpuClusterableObjectIndexListsStorage>,
|
||||
cluster_offsets_and_counts: StorageBuffer<GpuClusterOffsetsAndCountsStorage>,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Component)]
|
||||
pub struct ViewClusterBindings {
|
||||
n_indices: usize,
|
||||
n_offsets: usize,
|
||||
buffers: ViewClusterBuffers,
|
||||
}
|
||||
|
||||
impl Default for ClusterZConfig {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
@ -482,433 +346,3 @@ impl GlobalVisibleClusterableObjects {
|
||||
self.entities.contains(&entity)
|
||||
}
|
||||
}
|
||||
|
||||
impl FromWorld for GlobalClusterableObjectMeta {
|
||||
fn from_world(world: &mut World) -> Self {
|
||||
Self::new(
|
||||
world
|
||||
.resource::<RenderDevice>()
|
||||
.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<GpuClusterableObject>) {
|
||||
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<BindingResource> {
|
||||
match self {
|
||||
GpuClusterableObjects::Uniform(buffer) => buffer.binding(),
|
||||
GpuClusterableObjects::Storage(buffer) => buffer.binding(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn min_size(buffer_binding_type: BufferBindingType) -> NonZero<u64> {
|
||||
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<Query<(RenderEntity, &Clusters, &Camera)>>,
|
||||
mapper: Extract<Query<RenderEntity>>,
|
||||
) {
|
||||
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<RenderDevice>,
|
||||
render_queue: Res<RenderQueue>,
|
||||
mesh_pipeline: Res<MeshPipeline>,
|
||||
global_clusterable_object_meta: Res<GlobalClusterableObjectMeta>,
|
||||
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<BindingResource> {
|
||||
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<BindingResource> {
|
||||
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<u64> {
|
||||
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<u64> {
|
||||
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]),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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::<LightVisibilityClass>)]
|
||||
#[component(on_add = visibility::add_visibility_class::<ClusterVisibilityClass>)]
|
||||
pub struct DirectionalLight {
|
||||
/// The color of the light.
|
||||
///
|
||||
|
@ -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 {
|
||||
|
@ -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::<LightVisibilityClass>)]
|
||||
#[component(on_add = visibility::add_visibility_class::<ClusterVisibilityClass>)]
|
||||
pub struct PointLight {
|
||||
/// The color of this light source.
|
||||
pub color: Color,
|
||||
|
@ -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::<LightVisibilityClass>)]
|
||||
#[component(on_add = visibility::add_visibility_class::<ClusterVisibilityClass>)]
|
||||
pub struct SpotLight {
|
||||
/// The color of the light.
|
||||
///
|
||||
|
@ -251,7 +251,7 @@ fn fragment(@builtin(position) position: vec4<f32>) -> @location(0) vec4<f32> {
|
||||
// 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;
|
||||
}
|
||||
|
@ -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.
|
@ -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.
|
Loading…
Reference in New Issue
Block a user