1use core::{num::NonZero, ops::Deref};
18
19use bevy_app::{App, Plugin};
20use bevy_asset::AssetId;
21use bevy_camera::visibility::ViewVisibility;
22use bevy_derive::{Deref, DerefMut};
23use bevy_ecs::{
24 entity::{Entity, EntityHashMap},
25 query::With,
26 resource::Resource,
27 schedule::IntoScheduleConfigs as _,
28 system::{Commands, Local, Query, Res, ResMut},
29};
30use bevy_image::Image;
31use bevy_light::{ClusteredDecal, DirectionalLightTexture, PointLightTexture, SpotLightTexture};
32use bevy_math::Mat4;
33use bevy_platform::collections::HashMap;
34use bevy_render::{
35 render_asset::RenderAssets,
36 render_resource::{
37 binding_types, BindGroupLayoutEntryBuilder, Buffer, BufferUsages, RawBufferVec, Sampler,
38 SamplerBindingType, ShaderType, TextureSampleType, TextureView,
39 },
40 renderer::{RenderAdapter, RenderDevice, RenderQueue},
41 sync_component::SyncComponentPlugin,
42 sync_world::RenderEntity,
43 texture::{FallbackImage, GpuImage},
44 Extract, ExtractSchedule, Render, RenderApp, RenderSystems,
45};
46use bevy_shader::load_shader_library;
47use bevy_transform::components::GlobalTransform;
48use bytemuck::{Pod, Zeroable};
49
50use crate::{binding_arrays_are_usable, prepare_lights, GlobalClusterableObjectMeta};
51
52pub(crate) const MAX_VIEW_DECALS: usize = 8;
58
59pub struct ClusteredDecalPlugin;
64
65#[derive(Resource, Default)]
67pub struct RenderClusteredDecals {
68 binding_index_to_textures: Vec<AssetId<Image>>,
72 texture_to_binding_index: HashMap<AssetId<Image>, u32>,
76 decals: Vec<RenderClusteredDecal>,
78 entity_to_decal_index: EntityHashMap<usize>,
81}
82
83impl RenderClusteredDecals {
84 fn clear(&mut self) {
87 self.binding_index_to_textures.clear();
88 self.texture_to_binding_index.clear();
89 self.decals.clear();
90 self.entity_to_decal_index.clear();
91 }
92
93 pub fn insert_decal(
94 &mut self,
95 entity: Entity,
96 image: &AssetId<Image>,
97 local_from_world: Mat4,
98 tag: u32,
99 ) {
100 let image_index = self.get_or_insert_image(image);
101 let decal_index = self.decals.len();
102 self.decals.push(RenderClusteredDecal {
103 local_from_world,
104 image_index,
105 tag,
106 pad_a: 0,
107 pad_b: 0,
108 });
109 self.entity_to_decal_index.insert(entity, decal_index);
110 }
111
112 pub fn get(&self, entity: Entity) -> Option<usize> {
113 self.entity_to_decal_index.get(&entity).copied()
114 }
115}
116
117pub(crate) struct RenderViewClusteredDecalBindGroupEntries<'a> {
119 pub(crate) decals: &'a Buffer,
122 pub(crate) texture_views: Vec<&'a <TextureView as Deref>::Target>,
125 pub(crate) sampler: &'a Sampler,
128}
129
130#[derive(Resource, Deref, DerefMut)]
133pub struct DecalsBuffer(RawBufferVec<RenderClusteredDecal>);
134
135impl Default for DecalsBuffer {
136 fn default() -> Self {
137 DecalsBuffer(RawBufferVec::new(BufferUsages::STORAGE))
138 }
139}
140
141impl Plugin for ClusteredDecalPlugin {
142 fn build(&self, app: &mut App) {
143 load_shader_library!(app, "clustered.wgsl");
144
145 app.add_plugins(SyncComponentPlugin::<ClusteredDecal>::default());
146
147 let Some(render_app) = app.get_sub_app_mut(RenderApp) else {
148 return;
149 };
150
151 render_app
152 .init_resource::<DecalsBuffer>()
153 .init_resource::<RenderClusteredDecals>()
154 .add_systems(ExtractSchedule, (extract_decals, extract_clustered_decal))
155 .add_systems(
156 Render,
157 prepare_decals
158 .in_set(RenderSystems::ManageViews)
159 .after(prepare_lights),
160 )
161 .add_systems(
162 Render,
163 upload_decals.in_set(RenderSystems::PrepareResources),
164 );
165 }
166}
167
168fn extract_clustered_decal(
171 mut commands: Commands,
172 mut previous_len: Local<usize>,
173 query: Extract<Query<(RenderEntity, &ClusteredDecal)>>,
174) {
175 let mut values = Vec::with_capacity(*previous_len);
176 for (entity, query_item) in &query {
177 values.push((entity, query_item.clone()));
178 }
179 *previous_len = values.len();
180 commands.try_insert_batch(values);
181}
182
183#[derive(Clone, Copy, Default, ShaderType, Pod, Zeroable)]
185#[repr(C)]
186pub struct RenderClusteredDecal {
187 local_from_world: Mat4,
192 image_index: u32,
194 tag: u32,
196 pad_a: u32,
198 pad_b: u32,
200}
201
202pub fn extract_decals(
204 decals: Extract<
205 Query<(
206 RenderEntity,
207 &ClusteredDecal,
208 &GlobalTransform,
209 &ViewVisibility,
210 )>,
211 >,
212 spot_light_textures: Extract<
213 Query<(
214 RenderEntity,
215 &SpotLightTexture,
216 &GlobalTransform,
217 &ViewVisibility,
218 )>,
219 >,
220 point_light_textures: Extract<
221 Query<(
222 RenderEntity,
223 &PointLightTexture,
224 &GlobalTransform,
225 &ViewVisibility,
226 )>,
227 >,
228 directional_light_textures: Extract<
229 Query<(
230 RenderEntity,
231 &DirectionalLightTexture,
232 &GlobalTransform,
233 &ViewVisibility,
234 )>,
235 >,
236 mut render_decals: ResMut<RenderClusteredDecals>,
237) {
238 render_decals.clear();
240
241 for (decal_entity, clustered_decal, global_transform, view_visibility) in &decals {
243 if !view_visibility.get() {
245 continue;
246 }
247
248 render_decals.insert_decal(
249 decal_entity,
250 &clustered_decal.image.id(),
251 global_transform.affine().inverse().into(),
252 clustered_decal.tag,
253 );
254 }
255
256 for (decal_entity, texture, global_transform, view_visibility) in &spot_light_textures {
257 if !view_visibility.get() {
259 continue;
260 }
261
262 render_decals.insert_decal(
263 decal_entity,
264 &texture.image.id(),
265 global_transform.affine().inverse().into(),
266 0,
267 );
268 }
269
270 for (decal_entity, texture, global_transform, view_visibility) in &point_light_textures {
271 if !view_visibility.get() {
273 continue;
274 }
275
276 render_decals.insert_decal(
277 decal_entity,
278 &texture.image.id(),
279 global_transform.affine().inverse().into(),
280 texture.cubemap_layout as u32,
281 );
282 }
283
284 for (decal_entity, texture, global_transform, view_visibility) in &directional_light_textures {
285 if !view_visibility.get() {
287 continue;
288 }
289
290 render_decals.insert_decal(
291 decal_entity,
292 &texture.image.id(),
293 global_transform.affine().inverse().into(),
294 if texture.tiled { 1 } else { 0 },
295 );
296 }
297}
298
299fn prepare_decals(
301 decals: Query<Entity, With<ClusteredDecal>>,
302 mut global_clusterable_object_meta: ResMut<GlobalClusterableObjectMeta>,
303 render_decals: Res<RenderClusteredDecals>,
304) {
305 for decal_entity in &decals {
306 if let Some(index) = render_decals.entity_to_decal_index.get(&decal_entity) {
307 global_clusterable_object_meta
308 .entity_to_index
309 .insert(decal_entity, *index);
310 }
311 }
312}
313
314pub(crate) fn get_bind_group_layout_entries(
317 render_device: &RenderDevice,
318 render_adapter: &RenderAdapter,
319) -> Option<[BindGroupLayoutEntryBuilder; 3]> {
320 if !clustered_decals_are_usable(render_device, render_adapter) {
323 return None;
324 }
325
326 Some([
327 binding_types::storage_buffer_read_only::<RenderClusteredDecal>(false),
329 binding_types::texture_2d(TextureSampleType::Float { filterable: true })
331 .count(NonZero::<u32>::new(MAX_VIEW_DECALS as u32).unwrap()),
332 binding_types::sampler(SamplerBindingType::Filtering),
334 ])
335}
336
337impl<'a> RenderViewClusteredDecalBindGroupEntries<'a> {
338 pub(crate) fn get(
341 render_decals: &RenderClusteredDecals,
342 decals_buffer: &'a DecalsBuffer,
343 images: &'a RenderAssets<GpuImage>,
344 fallback_image: &'a FallbackImage,
345 render_device: &RenderDevice,
346 render_adapter: &RenderAdapter,
347 ) -> Option<RenderViewClusteredDecalBindGroupEntries<'a>> {
348 if !clustered_decals_are_usable(render_device, render_adapter) {
350 return None;
351 }
352
353 let sampler = match render_decals
357 .binding_index_to_textures
358 .iter()
359 .filter_map(|image_id| images.get(*image_id))
360 .next()
361 {
362 Some(gpu_image) => &gpu_image.sampler,
363 None => &fallback_image.d2.sampler,
364 };
365
366 let mut texture_views = vec![];
368 for image_id in &render_decals.binding_index_to_textures {
369 match images.get(*image_id) {
370 None => texture_views.push(&*fallback_image.d2.texture_view),
371 Some(gpu_image) => texture_views.push(&*gpu_image.texture_view),
372 }
373 }
374
375 while texture_views.len() < MAX_VIEW_DECALS {
378 texture_views.push(&*fallback_image.d2.texture_view);
379 }
380
381 Some(RenderViewClusteredDecalBindGroupEntries {
382 decals: decals_buffer.buffer()?,
383 texture_views,
384 sampler,
385 })
386 }
387}
388
389impl RenderClusteredDecals {
390 fn get_or_insert_image(&mut self, image_id: &AssetId<Image>) -> u32 {
393 *self
394 .texture_to_binding_index
395 .entry(*image_id)
396 .or_insert_with(|| {
397 let index = self.binding_index_to_textures.len() as u32;
398 self.binding_index_to_textures.push(*image_id);
399 index
400 })
401 }
402}
403
404fn upload_decals(
407 render_decals: Res<RenderClusteredDecals>,
408 mut decals_buffer: ResMut<DecalsBuffer>,
409 render_device: Res<RenderDevice>,
410 render_queue: Res<RenderQueue>,
411) {
412 decals_buffer.clear();
413
414 for &decal in &render_decals.decals {
415 decals_buffer.push(decal);
416 }
417
418 if decals_buffer.is_empty() {
421 decals_buffer.push(RenderClusteredDecal::default());
422 }
423
424 decals_buffer.write_buffer(&render_device, &render_queue);
425}
426
427pub fn clustered_decals_are_usable(
433 render_device: &RenderDevice,
434 render_adapter: &RenderAdapter,
435) -> bool {
436 binding_arrays_are_usable(render_device, render_adapter)
440 && cfg!(feature = "pbr_clustered_decals")
441}