1use core::{num::NonZero, ops::Deref};
18
19use bevy_app::{App, Plugin};
20use bevy_asset::{load_internal_asset, weak_handle, AssetId, Handle};
21use bevy_derive::{Deref, DerefMut};
22use bevy_ecs::{
23 component::Component,
24 entity::{Entity, EntityHashMap},
25 prelude::ReflectComponent,
26 query::With,
27 resource::Resource,
28 schedule::IntoScheduleConfigs as _,
29 system::{Query, Res, ResMut},
30};
31use bevy_image::Image;
32use bevy_math::Mat4;
33use bevy_platform::collections::HashMap;
34use bevy_reflect::Reflect;
35use bevy_render::{
36 extract_component::{ExtractComponent, ExtractComponentPlugin},
37 render_asset::RenderAssets,
38 render_resource::{
39 binding_types, BindGroupLayoutEntryBuilder, Buffer, BufferUsages, RawBufferVec, Sampler,
40 SamplerBindingType, Shader, ShaderType, TextureSampleType, TextureView,
41 },
42 renderer::{RenderAdapter, RenderDevice, RenderQueue},
43 sync_world::RenderEntity,
44 texture::{FallbackImage, GpuImage},
45 view::{self, ViewVisibility, Visibility, VisibilityClass},
46 Extract, ExtractSchedule, Render, RenderApp, RenderSet,
47};
48use bevy_transform::{components::GlobalTransform, prelude::Transform};
49use bytemuck::{Pod, Zeroable};
50
51use crate::{
52 binding_arrays_are_usable, prepare_lights, GlobalClusterableObjectMeta, LightVisibilityClass,
53};
54
55pub(crate) const CLUSTERED_DECAL_SHADER_HANDLE: Handle<Shader> =
57 weak_handle!("87929002-3509-42f1-8279-2d2765dd145c");
58
59pub(crate) const MAX_VIEW_DECALS: usize = 8;
65
66pub struct ClusteredDecalPlugin;
71
72#[derive(Component, Debug, Clone, Reflect, ExtractComponent)]
83#[reflect(Component, Debug, Clone)]
84#[require(Transform, Visibility, VisibilityClass)]
85#[component(on_add = view::add_visibility_class::<LightVisibilityClass>)]
86pub struct ClusteredDecal {
87 pub image: Handle<Image>,
93
94 pub tag: u32,
98}
99
100#[derive(Resource, Default)]
102pub struct RenderClusteredDecals {
103 binding_index_to_textures: Vec<AssetId<Image>>,
107 texture_to_binding_index: HashMap<AssetId<Image>, u32>,
111 decals: Vec<RenderClusteredDecal>,
113 entity_to_decal_index: EntityHashMap<usize>,
116}
117
118impl RenderClusteredDecals {
119 fn clear(&mut self) {
122 self.binding_index_to_textures.clear();
123 self.texture_to_binding_index.clear();
124 self.decals.clear();
125 self.entity_to_decal_index.clear();
126 }
127}
128
129pub(crate) struct RenderViewClusteredDecalBindGroupEntries<'a> {
131 pub(crate) decals: &'a Buffer,
134 pub(crate) texture_views: Vec<&'a <TextureView as Deref>::Target>,
137 pub(crate) sampler: &'a Sampler,
140}
141
142#[derive(Resource, Deref, DerefMut)]
145pub struct DecalsBuffer(RawBufferVec<RenderClusteredDecal>);
146
147impl Default for DecalsBuffer {
148 fn default() -> Self {
149 DecalsBuffer(RawBufferVec::new(BufferUsages::STORAGE))
150 }
151}
152
153impl Plugin for ClusteredDecalPlugin {
154 fn build(&self, app: &mut App) {
155 load_internal_asset!(
156 app,
157 CLUSTERED_DECAL_SHADER_HANDLE,
158 "clustered.wgsl",
159 Shader::from_wgsl
160 );
161
162 app.add_plugins(ExtractComponentPlugin::<ClusteredDecal>::default())
163 .register_type::<ClusteredDecal>();
164
165 let Some(render_app) = app.get_sub_app_mut(RenderApp) else {
166 return;
167 };
168
169 render_app
170 .init_resource::<DecalsBuffer>()
171 .init_resource::<RenderClusteredDecals>()
172 .add_systems(ExtractSchedule, extract_decals)
173 .add_systems(
174 Render,
175 prepare_decals
176 .in_set(RenderSet::ManageViews)
177 .after(prepare_lights),
178 )
179 .add_systems(Render, upload_decals.in_set(RenderSet::PrepareResources));
180 }
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 mut render_decals: ResMut<RenderClusteredDecals>,
213) {
214 render_decals.clear();
216
217 for (decal_entity, clustered_decal, global_transform, view_visibility) in &decals {
219 if !view_visibility.get() {
221 continue;
222 }
223
224 let image_index = render_decals.get_or_insert_image(&clustered_decal.image.id());
226
227 let decal_index = render_decals.decals.len();
229 render_decals
230 .entity_to_decal_index
231 .insert(decal_entity, decal_index);
232
233 render_decals.decals.push(RenderClusteredDecal {
234 local_from_world: global_transform.affine().inverse().into(),
235 image_index,
236 tag: clustered_decal.tag,
237 pad_a: 0,
238 pad_b: 0,
239 });
240 }
241}
242
243fn prepare_decals(
245 decals: Query<Entity, With<ClusteredDecal>>,
246 mut global_clusterable_object_meta: ResMut<GlobalClusterableObjectMeta>,
247 render_decals: Res<RenderClusteredDecals>,
248) {
249 for decal_entity in &decals {
250 if let Some(index) = render_decals.entity_to_decal_index.get(&decal_entity) {
251 global_clusterable_object_meta
252 .entity_to_index
253 .insert(decal_entity, *index);
254 }
255 }
256}
257
258pub(crate) fn get_bind_group_layout_entries(
261 render_device: &RenderDevice,
262 render_adapter: &RenderAdapter,
263) -> Option<[BindGroupLayoutEntryBuilder; 3]> {
264 if !clustered_decals_are_usable(render_device, render_adapter) {
267 return None;
268 }
269
270 Some([
271 binding_types::storage_buffer_read_only::<RenderClusteredDecal>(false),
273 binding_types::texture_2d(TextureSampleType::Float { filterable: true })
275 .count(NonZero::<u32>::new(MAX_VIEW_DECALS as u32).unwrap()),
276 binding_types::sampler(SamplerBindingType::Filtering),
278 ])
279}
280
281impl<'a> RenderViewClusteredDecalBindGroupEntries<'a> {
282 pub(crate) fn get(
285 render_decals: &RenderClusteredDecals,
286 decals_buffer: &'a DecalsBuffer,
287 images: &'a RenderAssets<GpuImage>,
288 fallback_image: &'a FallbackImage,
289 render_device: &RenderDevice,
290 render_adapter: &RenderAdapter,
291 ) -> Option<RenderViewClusteredDecalBindGroupEntries<'a>> {
292 if !clustered_decals_are_usable(render_device, render_adapter) {
294 return None;
295 }
296
297 let sampler = match render_decals
301 .binding_index_to_textures
302 .iter()
303 .filter_map(|image_id| images.get(*image_id))
304 .next()
305 {
306 Some(gpu_image) => &gpu_image.sampler,
307 None => &fallback_image.d2.sampler,
308 };
309
310 let mut texture_views = vec![];
312 for image_id in &render_decals.binding_index_to_textures {
313 match images.get(*image_id) {
314 None => texture_views.push(&*fallback_image.d2.texture_view),
315 Some(gpu_image) => texture_views.push(&*gpu_image.texture_view),
316 }
317 }
318
319 while texture_views.len() < MAX_VIEW_DECALS {
322 texture_views.push(&*fallback_image.d2.texture_view);
323 }
324
325 Some(RenderViewClusteredDecalBindGroupEntries {
326 decals: decals_buffer.buffer()?,
327 texture_views,
328 sampler,
329 })
330 }
331}
332
333impl RenderClusteredDecals {
334 fn get_or_insert_image(&mut self, image_id: &AssetId<Image>) -> u32 {
337 *self
338 .texture_to_binding_index
339 .entry(*image_id)
340 .or_insert_with(|| {
341 let index = self.binding_index_to_textures.len() as u32;
342 self.binding_index_to_textures.push(*image_id);
343 index
344 })
345 }
346}
347
348fn upload_decals(
351 render_decals: Res<RenderClusteredDecals>,
352 mut decals_buffer: ResMut<DecalsBuffer>,
353 render_device: Res<RenderDevice>,
354 render_queue: Res<RenderQueue>,
355) {
356 decals_buffer.clear();
357
358 for &decal in &render_decals.decals {
359 decals_buffer.push(decal);
360 }
361
362 if decals_buffer.is_empty() {
365 decals_buffer.push(RenderClusteredDecal::default());
366 }
367
368 decals_buffer.write_buffer(&render_device, &render_queue);
369}
370
371pub fn clustered_decals_are_usable(
377 render_device: &RenderDevice,
378 render_adapter: &RenderAdapter,
379) -> bool {
380 binding_arrays_are_usable(render_device, render_adapter)
384 && cfg!(not(any(target_os = "macos", target_os = "ios")))
385}