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;
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::SyncComponentPlugin,
47 sync_world::RenderEntity,
48 texture::{FallbackImage, GpuImage},
49 Extract, ExtractSchedule, 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 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 tag: u32,
100 ) {
101 let image_indices = images.map(|maybe_image_id| match maybe_image_id {
102 Some(ref image_id) => self.get_or_insert_image(image_id),
103 None => -1,
104 });
105 let decal_index = self.decals.len();
106 self.decals.push(RenderClusteredDecal {
107 local_from_world,
108 image_indices,
109 tag,
110 pad_a: 0,
111 pad_b: 0,
112 pad_c: 0,
113 });
114 self.entity_to_decal_index.insert(entity, decal_index);
115 }
116
117 pub fn get(&self, entity: Entity) -> Option<usize> {
118 self.entity_to_decal_index.get(&entity).copied()
119 }
120}
121
122pub(crate) struct RenderViewClusteredDecalBindGroupEntries<'a> {
124 pub(crate) decals: &'a Buffer,
127 pub(crate) texture_views: Vec<&'a <TextureView as Deref>::Target>,
130 pub(crate) sampler: &'a Sampler,
133}
134
135#[derive(Resource, Deref, DerefMut)]
138pub struct DecalsBuffer(RawBufferVec<RenderClusteredDecal>);
139
140impl Default for DecalsBuffer {
141 fn default() -> Self {
142 DecalsBuffer(RawBufferVec::new(BufferUsages::STORAGE))
143 }
144}
145
146impl Plugin for ClusteredDecalPlugin {
147 fn build(&self, app: &mut App) {
148 load_shader_library!(app, "clustered.wgsl");
149
150 app.add_plugins(SyncComponentPlugin::<ClusteredDecal>::default());
151
152 let Some(render_app) = app.get_sub_app_mut(RenderApp) else {
153 return;
154 };
155
156 render_app
157 .init_resource::<DecalsBuffer>()
158 .init_resource::<RenderClusteredDecals>()
159 .add_systems(ExtractSchedule, (extract_decals, extract_clustered_decal))
160 .add_systems(
161 Render,
162 prepare_decals
163 .in_set(RenderSystems::ManageViews)
164 .after(prepare_lights),
165 )
166 .add_systems(
167 Render,
168 upload_decals.in_set(RenderSystems::PrepareResources),
169 );
170 }
171}
172
173fn extract_clustered_decal(
176 mut commands: Commands,
177 mut previous_len: Local<usize>,
178 query: Extract<Query<(RenderEntity, &ClusteredDecal)>>,
179) {
180 let mut values = Vec::with_capacity(*previous_len);
181 for (entity, query_item) in &query {
182 values.push((entity, query_item.clone()));
183 }
184 *previous_len = values.len();
185 commands.try_insert_batch(values);
186}
187
188#[derive(Clone, Copy, Default, ShaderType, Pod, Zeroable)]
190#[repr(C)]
191pub struct RenderClusteredDecal {
192 local_from_world: Mat4,
197 image_indices: [i32; 4],
206 tag: u32,
208 pad_a: u32,
210 pad_b: u32,
212 pad_c: u32,
214}
215
216pub fn extract_decals(
218 decals: Extract<
219 Query<(
220 RenderEntity,
221 &ClusteredDecal,
222 &GlobalTransform,
223 &ViewVisibility,
224 )>,
225 >,
226 spot_light_textures: Extract<
227 Query<(
228 RenderEntity,
229 &SpotLightTexture,
230 &GlobalTransform,
231 &ViewVisibility,
232 )>,
233 >,
234 point_light_textures: Extract<
235 Query<(
236 RenderEntity,
237 &PointLightTexture,
238 &GlobalTransform,
239 &ViewVisibility,
240 )>,
241 >,
242 directional_light_textures: Extract<
243 Query<(
244 RenderEntity,
245 &DirectionalLightTexture,
246 &GlobalTransform,
247 &ViewVisibility,
248 )>,
249 >,
250 mut render_decals: ResMut<RenderClusteredDecals>,
251) {
252 render_decals.clear();
254
255 extract_clustered_decals(&decals, &mut render_decals);
256 extract_spot_light_textures(&spot_light_textures, &mut render_decals);
257 extract_point_light_textures(&point_light_textures, &mut render_decals);
258 extract_directional_light_textures(&directional_light_textures, &mut render_decals);
259}
260
261fn extract_clustered_decals(
264 decals: &Extract<
265 Query<(
266 RenderEntity,
267 &ClusteredDecal,
268 &GlobalTransform,
269 &ViewVisibility,
270 )>,
271 >,
272 render_decals: &mut RenderClusteredDecals,
273) {
274 for (decal_entity, clustered_decal, global_transform, view_visibility) in decals {
276 if !view_visibility.get() {
278 continue;
279 }
280
281 render_decals.insert_decal(
284 decal_entity,
285 [
286 clustered_decal.base_color_texture.as_ref().map(Handle::id),
287 clustered_decal.normal_map_texture.as_ref().map(Handle::id),
288 clustered_decal
289 .metallic_roughness_texture
290 .as_ref()
291 .map(Handle::id),
292 clustered_decal.emissive_texture.as_ref().map(Handle::id),
293 ],
294 global_transform.affine().inverse().into(),
295 clustered_decal.tag,
296 );
297 }
298}
299
300fn extract_spot_light_textures(
303 spot_light_textures: &Extract<
304 Query<(
305 RenderEntity,
306 &SpotLightTexture,
307 &GlobalTransform,
308 &ViewVisibility,
309 )>,
310 >,
311 render_decals: &mut RenderClusteredDecals,
312) {
313 for (decal_entity, texture, global_transform, view_visibility) in spot_light_textures {
314 if !view_visibility.get() {
316 continue;
317 }
318
319 render_decals.insert_decal(
320 decal_entity,
321 [Some(texture.image.id()), None, None, None],
322 global_transform.affine().inverse().into(),
323 0,
324 );
325 }
326}
327
328fn extract_point_light_textures(
331 point_light_textures: &Extract<
332 Query<(
333 RenderEntity,
334 &PointLightTexture,
335 &GlobalTransform,
336 &ViewVisibility,
337 )>,
338 >,
339 render_decals: &mut RenderClusteredDecals,
340) {
341 for (decal_entity, texture, global_transform, view_visibility) in point_light_textures {
342 if !view_visibility.get() {
344 continue;
345 }
346
347 render_decals.insert_decal(
348 decal_entity,
349 [Some(texture.image.id()), None, None, None],
350 global_transform.affine().inverse().into(),
351 texture.cubemap_layout as u32,
352 );
353 }
354}
355
356fn extract_directional_light_textures(
359 directional_light_textures: &Extract<
360 Query<(
361 RenderEntity,
362 &DirectionalLightTexture,
363 &GlobalTransform,
364 &ViewVisibility,
365 )>,
366 >,
367 render_decals: &mut RenderClusteredDecals,
368) {
369 for (decal_entity, texture, global_transform, view_visibility) in directional_light_textures {
370 if !view_visibility.get() {
372 continue;
373 }
374
375 render_decals.insert_decal(
376 decal_entity,
377 [Some(texture.image.id()), None, None, None],
378 global_transform.affine().inverse().into(),
379 if texture.tiled { 1 } else { 0 },
380 );
381 }
382}
383
384fn prepare_decals(
386 decals: Query<Entity, With<ClusteredDecal>>,
387 mut global_clusterable_object_meta: ResMut<GlobalClusterableObjectMeta>,
388 render_decals: Res<RenderClusteredDecals>,
389) {
390 for decal_entity in &decals {
391 if let Some(index) = render_decals.entity_to_decal_index.get(&decal_entity) {
392 global_clusterable_object_meta
393 .entity_to_index
394 .insert(decal_entity, *index);
395 }
396 }
397}
398
399pub(crate) fn get_bind_group_layout_entries(
402 render_device: &RenderDevice,
403 render_adapter: &RenderAdapter,
404) -> Option<[BindGroupLayoutEntryBuilder; 3]> {
405 if !clustered_decals_are_usable(render_device, render_adapter) {
408 return None;
409 }
410
411 Some([
412 binding_types::storage_buffer_read_only::<RenderClusteredDecal>(false),
414 binding_types::texture_2d(TextureSampleType::Float { filterable: true })
416 .count(NonZero::<u32>::new(max_view_decals(render_device)).unwrap()),
417 binding_types::sampler(SamplerBindingType::Filtering),
419 ])
420}
421
422impl<'a> RenderViewClusteredDecalBindGroupEntries<'a> {
423 pub(crate) fn get(
426 render_decals: &RenderClusteredDecals,
427 decals_buffer: &'a DecalsBuffer,
428 images: &'a RenderAssets<GpuImage>,
429 fallback_image: &'a FallbackImage,
430 render_device: &RenderDevice,
431 render_adapter: &RenderAdapter,
432 ) -> Option<RenderViewClusteredDecalBindGroupEntries<'a>> {
433 if !clustered_decals_are_usable(render_device, render_adapter) {
435 return None;
436 }
437
438 let sampler = match render_decals
442 .binding_index_to_textures
443 .iter()
444 .filter_map(|image_id| images.get(*image_id))
445 .next()
446 {
447 Some(gpu_image) => &gpu_image.sampler,
448 None => &fallback_image.d2.sampler,
449 };
450
451 let mut texture_views = vec![];
453 for image_id in &render_decals.binding_index_to_textures {
454 match images.get(*image_id) {
455 None => texture_views.push(&*fallback_image.d2.texture_view),
456 Some(gpu_image) => texture_views.push(&*gpu_image.texture_view),
457 }
458 }
459
460 if !render_device
463 .features()
464 .contains(WgpuFeatures::PARTIALLY_BOUND_BINDING_ARRAY)
465 {
466 let max_view_decals = max_view_decals(render_device);
467 while texture_views.len() < max_view_decals as usize {
468 texture_views.push(&*fallback_image.d2.texture_view);
469 }
470 } else if texture_views.is_empty() {
471 texture_views.push(&*fallback_image.d2.texture_view);
472 }
473
474 Some(RenderViewClusteredDecalBindGroupEntries {
475 decals: decals_buffer.buffer()?,
476 texture_views,
477 sampler,
478 })
479 }
480}
481
482impl RenderClusteredDecals {
483 fn get_or_insert_image(&mut self, image_id: &AssetId<Image>) -> i32 {
486 *self
487 .texture_to_binding_index
488 .entry(*image_id)
489 .or_insert_with(|| {
490 let index = self.binding_index_to_textures.len() as i32;
491 self.binding_index_to_textures.push(*image_id);
492 index
493 })
494 }
495}
496
497fn upload_decals(
500 render_decals: Res<RenderClusteredDecals>,
501 mut decals_buffer: ResMut<DecalsBuffer>,
502 render_device: Res<RenderDevice>,
503 render_queue: Res<RenderQueue>,
504) {
505 decals_buffer.clear();
506
507 for &decal in &render_decals.decals {
508 decals_buffer.push(decal);
509 }
510
511 if decals_buffer.is_empty() {
514 decals_buffer.push(RenderClusteredDecal::default());
515 }
516
517 decals_buffer.write_buffer(&render_device, &render_queue);
518}
519
520pub fn clustered_decals_are_usable(
526 render_device: &RenderDevice,
527 render_adapter: &RenderAdapter,
528) -> bool {
529 binding_arrays_are_usable(render_device, render_adapter)
533 && cfg!(feature = "pbr_clustered_decals")
534}
535
536fn max_view_decals(render_device: &RenderDevice) -> u32 {
539 if render_device
544 .features()
545 .contains(WgpuFeatures::PARTIALLY_BOUND_BINDING_ARRAY)
546 {
547 1024
552 } else {
553 8
554 }
555}