1use core::{num::NonZero, ops::Deref};
22
23use bevy_app::{App, Plugin};
24use bevy_asset::{AssetId, Handle};
25use bevy_camera::visibility::ViewVisibility;
26use bevy_derive::{Deref, DerefMut};
27use bevy_ecs::{
28 entity::{Entity, EntityHashMap},
29 query::With,
30 resource::Resource,
31 schedule::IntoScheduleConfigs as _,
32 system::{Commands, Local, Query, Res, ResMut},
33};
34use bevy_image::Image;
35use bevy_light::{ClusteredDecal, DirectionalLightTexture, PointLightTexture, SpotLightTexture};
36use bevy_math::{Mat4, Vec3};
37use bevy_platform::collections::HashMap;
38use bevy_render::{
39 render_asset::RenderAssets,
40 render_resource::{
41 binding_types, BindGroupLayoutEntryBuilder, Buffer, BufferUsages, RawBufferVec, Sampler,
42 SamplerBindingType, ShaderType, TextureSampleType, TextureView,
43 },
44 renderer::{RenderAdapter, RenderDevice, RenderQueue},
45 settings::WgpuFeatures,
46 sync_component::{SyncComponent, SyncComponentPlugin},
47 sync_world::RenderEntity,
48 texture::{FallbackImage, GpuImage},
49 Extract, ExtractSchedule, GpuResourceAppExt, Render, RenderApp, RenderSystems,
50};
51use bevy_shader::load_shader_library;
52use bevy_transform::components::GlobalTransform;
53use bytemuck::{Pod, Zeroable};
54
55use crate::{binding_arrays_are_usable, prepare_lights, GlobalClusterableObjectMeta};
56
57const IMAGES_PER_DECAL: usize = 4;
59
60pub struct ClusteredDecalPlugin;
65
66#[derive(Resource, Default)]
68pub struct RenderClusteredDecals {
69 pub binding_index_to_textures: Vec<AssetId<Image>>,
73 texture_to_binding_index: HashMap<AssetId<Image>, i32>,
77 decals: Vec<RenderClusteredDecal>,
79 entity_to_decal_index: EntityHashMap<usize>,
82}
83
84impl RenderClusteredDecals {
85 fn clear(&mut self) {
88 self.binding_index_to_textures.clear();
89 self.texture_to_binding_index.clear();
90 self.decals.clear();
91 self.entity_to_decal_index.clear();
92 }
93
94 pub fn insert_decal(
95 &mut self,
96 entity: Entity,
97 images: [Option<AssetId<Image>>; IMAGES_PER_DECAL],
98 local_from_world: Mat4,
99 world_position: Vec3,
100 bounding_sphere_radius: f32,
101 tag: u32,
102 ) {
103 let image_indices = images.map(|maybe_image_id| match maybe_image_id {
104 Some(ref image_id) => self.get_or_insert_image(image_id),
105 None => -1,
106 });
107 let decal_index = self.decals.len();
108 self.decals.push(RenderClusteredDecal {
109 local_from_world,
110 image_indices,
111 world_position,
112 bounding_sphere_radius,
113 tag,
114 pad_a: 0,
115 pad_b: 0,
116 pad_c: 0,
117 });
118 self.entity_to_decal_index.insert(entity, decal_index);
119 }
120
121 pub fn get(&self, entity: Entity) -> Option<usize> {
122 self.entity_to_decal_index.get(&entity).copied()
123 }
124
125 pub fn len(&self) -> usize {
127 self.decals.len()
128 }
129
130 pub fn is_empty(&self) -> bool {
132 self.decals.is_empty()
133 }
134}
135
136pub(crate) struct RenderViewClusteredDecalBindGroupEntries<'a> {
138 pub(crate) decals: &'a Buffer,
141 pub(crate) texture_views: Vec<&'a <TextureView as Deref>::Target>,
144 pub(crate) sampler: &'a Sampler,
147}
148
149#[derive(Resource, Deref, DerefMut)]
152pub struct DecalsBuffer(RawBufferVec<RenderClusteredDecal>);
153
154impl Default for DecalsBuffer {
155 fn default() -> Self {
156 DecalsBuffer(RawBufferVec::new(BufferUsages::STORAGE))
157 }
158}
159
160impl Plugin for ClusteredDecalPlugin {
161 fn build(&self, app: &mut App) {
162 load_shader_library!(app, "clustered.wgsl");
163
164 app.add_plugins(SyncComponentPlugin::<ClusteredDecal, Self>::default());
165
166 let Some(render_app) = app.get_sub_app_mut(RenderApp) else {
167 return;
168 };
169
170 render_app
171 .init_gpu_resource::<DecalsBuffer>()
172 .init_resource::<RenderClusteredDecals>()
173 .add_systems(ExtractSchedule, (extract_decals, extract_clustered_decal))
174 .add_systems(
175 Render,
176 prepare_decals
177 .in_set(RenderSystems::PrepareViews)
178 .after(prepare_lights),
179 )
180 .add_systems(
181 Render,
182 upload_decals.in_set(RenderSystems::PrepareResources),
183 );
184 }
185}
186
187impl SyncComponent<ClusteredDecalPlugin> for ClusteredDecal {
188 type Target = Self;
189}
190
191fn extract_clustered_decal(
194 mut commands: Commands,
195 mut previous_len: Local<usize>,
196 query: Extract<Query<(RenderEntity, &ClusteredDecal)>>,
197) {
198 let mut values = Vec::with_capacity(*previous_len);
199 for (entity, query_item) in &query {
200 values.push((entity, query_item.clone()));
201 }
202 *previous_len = values.len();
203 commands.try_insert_batch(values);
204}
205
206#[derive(Clone, Copy, Default, ShaderType, Pod, Zeroable)]
208#[repr(C)]
209pub struct RenderClusteredDecal {
210 local_from_world: Mat4,
215 image_indices: [i32; 4],
224 world_position: Vec3,
225 bounding_sphere_radius: f32,
226 tag: u32,
228 pad_a: u32,
230 pad_b: u32,
232 pad_c: u32,
234}
235
236pub fn extract_decals(
238 decals: Extract<
239 Query<(
240 RenderEntity,
241 &ClusteredDecal,
242 &GlobalTransform,
243 &ViewVisibility,
244 )>,
245 >,
246 spot_light_textures: Extract<
247 Query<(
248 RenderEntity,
249 &SpotLightTexture,
250 &GlobalTransform,
251 &ViewVisibility,
252 )>,
253 >,
254 point_light_textures: Extract<
255 Query<(
256 RenderEntity,
257 &PointLightTexture,
258 &GlobalTransform,
259 &ViewVisibility,
260 )>,
261 >,
262 directional_light_textures: Extract<
263 Query<(
264 RenderEntity,
265 &DirectionalLightTexture,
266 &GlobalTransform,
267 &ViewVisibility,
268 )>,
269 >,
270 mut render_decals: ResMut<RenderClusteredDecals>,
271) {
272 render_decals.clear();
274
275 extract_clustered_decals(&decals, &mut render_decals);
276 extract_spot_light_textures(&spot_light_textures, &mut render_decals);
277 extract_point_light_textures(&point_light_textures, &mut render_decals);
278 extract_directional_light_textures(&directional_light_textures, &mut render_decals);
279}
280
281fn extract_clustered_decals(
284 decals: &Extract<
285 Query<(
286 RenderEntity,
287 &ClusteredDecal,
288 &GlobalTransform,
289 &ViewVisibility,
290 )>,
291 >,
292 render_decals: &mut RenderClusteredDecals,
293) {
294 for (decal_entity, clustered_decal, global_transform, view_visibility) in decals {
296 if !view_visibility.get() {
298 continue;
299 }
300
301 render_decals.insert_decal(
304 decal_entity,
305 [
306 clustered_decal.base_color_texture.as_ref().map(Handle::id),
307 clustered_decal.normal_map_texture.as_ref().map(Handle::id),
308 clustered_decal
309 .metallic_roughness_texture
310 .as_ref()
311 .map(Handle::id),
312 clustered_decal.emissive_texture.as_ref().map(Handle::id),
313 ],
314 global_transform.affine().inverse().into(),
315 global_transform.translation(),
316 (global_transform.scale() * Vec3::ONE).length(),
317 clustered_decal.tag,
318 );
319 }
320}
321
322fn extract_spot_light_textures(
325 spot_light_textures: &Extract<
326 Query<(
327 RenderEntity,
328 &SpotLightTexture,
329 &GlobalTransform,
330 &ViewVisibility,
331 )>,
332 >,
333 render_decals: &mut RenderClusteredDecals,
334) {
335 for (decal_entity, texture, global_transform, view_visibility) in spot_light_textures {
336 if !view_visibility.get() {
338 continue;
339 }
340
341 render_decals.insert_decal(
342 decal_entity,
343 [Some(texture.image.id()), None, None, None],
344 global_transform.affine().inverse().into(),
345 global_transform.translation(),
346 (global_transform.scale() * Vec3::ONE).length(),
347 0,
348 );
349 }
350}
351
352fn extract_point_light_textures(
355 point_light_textures: &Extract<
356 Query<(
357 RenderEntity,
358 &PointLightTexture,
359 &GlobalTransform,
360 &ViewVisibility,
361 )>,
362 >,
363 render_decals: &mut RenderClusteredDecals,
364) {
365 for (decal_entity, texture, global_transform, view_visibility) in point_light_textures {
366 if !view_visibility.get() {
368 continue;
369 }
370
371 render_decals.insert_decal(
372 decal_entity,
373 [Some(texture.image.id()), None, None, None],
374 global_transform.affine().inverse().into(),
375 global_transform.translation(),
376 (global_transform.scale() * Vec3::ONE).length(),
377 texture.cubemap_layout as u32,
378 );
379 }
380}
381
382fn extract_directional_light_textures(
385 directional_light_textures: &Extract<
386 Query<(
387 RenderEntity,
388 &DirectionalLightTexture,
389 &GlobalTransform,
390 &ViewVisibility,
391 )>,
392 >,
393 render_decals: &mut RenderClusteredDecals,
394) {
395 for (decal_entity, texture, global_transform, view_visibility) in directional_light_textures {
396 if !view_visibility.get() {
398 continue;
399 }
400
401 render_decals.insert_decal(
402 decal_entity,
403 [Some(texture.image.id()), None, None, None],
404 global_transform.affine().inverse().into(),
405 global_transform.translation(),
406 (global_transform.scale() * Vec3::ONE).length(),
407 if texture.tiled { 1 } else { 0 },
408 );
409 }
410}
411
412fn prepare_decals(
414 decals: Query<Entity, With<ClusteredDecal>>,
415 mut global_clusterable_object_meta: ResMut<GlobalClusterableObjectMeta>,
416 render_decals: Res<RenderClusteredDecals>,
417) {
418 for decal_entity in &decals {
419 if let Some(index) = render_decals.entity_to_decal_index.get(&decal_entity) {
420 global_clusterable_object_meta
421 .entity_to_index
422 .insert(decal_entity, *index);
423 }
424 }
425}
426
427pub(crate) fn get_bind_group_layout_entries(
430 render_device: &RenderDevice,
431 render_adapter: &RenderAdapter,
432) -> Option<[BindGroupLayoutEntryBuilder; 3]> {
433 if !clustered_decals_are_usable(render_device, render_adapter) {
436 return None;
437 }
438
439 Some([
440 binding_types::storage_buffer_read_only::<RenderClusteredDecal>(false),
442 binding_types::texture_2d(TextureSampleType::Float { filterable: true })
444 .count(NonZero::<u32>::new(max_view_decals(render_device)).unwrap()),
445 binding_types::sampler(SamplerBindingType::Filtering),
447 ])
448}
449
450impl<'a> RenderViewClusteredDecalBindGroupEntries<'a> {
451 pub(crate) fn get(
454 render_decals: &RenderClusteredDecals,
455 decals_buffer: &'a DecalsBuffer,
456 images: &'a RenderAssets<GpuImage>,
457 fallback_image: &'a FallbackImage,
458 render_device: &RenderDevice,
459 render_adapter: &RenderAdapter,
460 ) -> Option<RenderViewClusteredDecalBindGroupEntries<'a>> {
461 if !clustered_decals_are_usable(render_device, render_adapter) {
463 return None;
464 }
465
466 let sampler = match render_decals
470 .binding_index_to_textures
471 .iter()
472 .filter_map(|image_id| images.get(*image_id))
473 .next()
474 {
475 Some(gpu_image) => &gpu_image.sampler,
476 None => &fallback_image.d2.sampler,
477 };
478
479 let mut texture_views = vec![];
481 for image_id in &render_decals.binding_index_to_textures {
482 match images.get(*image_id) {
483 None => texture_views.push(&*fallback_image.d2.texture_view),
484 Some(gpu_image) => texture_views.push(&*gpu_image.texture_view),
485 }
486 }
487
488 if !render_device
491 .features()
492 .contains(WgpuFeatures::PARTIALLY_BOUND_BINDING_ARRAY)
493 {
494 let max_view_decals = max_view_decals(render_device);
495 while texture_views.len() < max_view_decals as usize {
496 texture_views.push(&*fallback_image.d2.texture_view);
497 }
498 } else if texture_views.is_empty() {
499 texture_views.push(&*fallback_image.d2.texture_view);
500 }
501
502 Some(RenderViewClusteredDecalBindGroupEntries {
503 decals: decals_buffer.buffer()?,
504 texture_views,
505 sampler,
506 })
507 }
508}
509
510impl RenderClusteredDecals {
511 fn get_or_insert_image(&mut self, image_id: &AssetId<Image>) -> i32 {
514 *self
515 .texture_to_binding_index
516 .entry(*image_id)
517 .or_insert_with(|| {
518 let index = self.binding_index_to_textures.len() as i32;
519 self.binding_index_to_textures.push(*image_id);
520 index
521 })
522 }
523}
524
525fn upload_decals(
528 render_decals: Res<RenderClusteredDecals>,
529 mut decals_buffer: ResMut<DecalsBuffer>,
530 render_device: Res<RenderDevice>,
531 render_queue: Res<RenderQueue>,
532) {
533 decals_buffer.clear();
534
535 for &decal in &render_decals.decals {
536 decals_buffer.push(decal);
537 }
538
539 if decals_buffer.is_empty() {
542 decals_buffer.push(RenderClusteredDecal::default());
543 }
544
545 decals_buffer.write_buffer(&render_device, &render_queue);
546}
547
548pub fn clustered_decals_are_usable(
554 render_device: &RenderDevice,
555 render_adapter: &RenderAdapter,
556) -> bool {
557 binding_arrays_are_usable(render_device, render_adapter)
561 && cfg!(feature = "pbr_clustered_decals")
562}
563
564fn max_view_decals(render_device: &RenderDevice) -> u32 {
567 if render_device
572 .features()
573 .contains(WgpuFeatures::PARTIALLY_BOUND_BINDING_ARRAY)
574 {
575 1024
580 } else {
581 8
582 }
583}