1use bevy_app::{App, Plugin};
35use bevy_asset::{AssetId, Handle};
36use bevy_camera::visibility::ViewVisibility;
37use bevy_derive::{Deref, DerefMut};
38use bevy_ecs::{
39 component::Component,
40 entity::Entity,
41 lifecycle::RemovedComponents,
42 query::{Changed, Or},
43 reflect::ReflectComponent,
44 resource::Resource,
45 schedule::IntoScheduleConfigs,
46 system::{Commands, Query, Res, ResMut},
47};
48use bevy_image::Image;
49use bevy_math::{uvec2, vec4, Rect, UVec2};
50use bevy_platform::collections::HashSet;
51use bevy_reflect::{std_traits::ReflectDefault, Reflect};
52use bevy_render::{
53 render_asset::RenderAssets,
54 render_resource::{Sampler, TextureView, WgpuSampler, WgpuTextureView},
55 renderer::RenderAdapter,
56 sync_world::MainEntity,
57 texture::{FallbackImage, GpuImage},
58 Extract, ExtractSchedule, RenderApp, RenderStartup,
59};
60use bevy_render::{renderer::RenderDevice, sync_world::MainEntityHashMap};
61use bevy_shader::load_shader_library;
62use bevy_utils::default;
63use fixedbitset::FixedBitSet;
64use nonmax::{NonMaxU16, NonMaxU32};
65use tracing::error;
66
67use crate::{binding_arrays_are_usable, MeshExtractionSystems};
68
69pub const LIGHTMAPS_PER_SLAB: usize = 4;
75
76pub struct LightmapPlugin;
78
79#[derive(Component, Clone, Reflect)]
87#[reflect(Component, Default, Clone)]
88pub struct Lightmap {
89 pub image: Handle<Image>,
91
92 pub uv_rect: Rect,
101
102 pub bicubic_sampling: bool,
108}
109
110#[derive(Debug)]
114pub(crate) struct RenderLightmap {
115 pub(crate) uv_rect: Rect,
121
122 pub(crate) slab_index: LightmapSlabIndex,
125
126 pub(crate) slot_index: LightmapSlotIndex,
131
132 pub(crate) bicubic_sampling: bool,
134}
135
136#[derive(Resource)]
141pub struct RenderLightmaps {
142 pub(crate) render_lightmaps: MainEntityHashMap<RenderLightmap>,
147
148 pub(crate) slabs: Vec<LightmapSlab>,
150
151 free_slabs: FixedBitSet,
152
153 pending_lightmaps: HashSet<(LightmapSlabIndex, LightmapSlotIndex)>,
154
155 pub(crate) bindless_supported: bool,
157}
158
159pub struct LightmapSlab {
163 lightmaps: Vec<AllocatedLightmap>,
165 free_slots_bitmask: u32,
166}
167
168struct AllocatedLightmap {
169 gpu_image: GpuImage,
170 asset_id: Option<AssetId<Image>>,
172}
173
174#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug, Deref, DerefMut)]
176#[repr(transparent)]
177pub struct LightmapSlabIndex(pub(crate) NonMaxU32);
178
179#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug, Deref, DerefMut)]
182#[repr(transparent)]
183pub struct LightmapSlotIndex(pub(crate) NonMaxU16);
184
185impl Plugin for LightmapPlugin {
186 fn build(&self, app: &mut App) {
187 load_shader_library!(app, "lightmap.wgsl");
188
189 let Some(render_app) = app.get_sub_app_mut(RenderApp) else {
190 return;
191 };
192 render_app
193 .add_systems(RenderStartup, init_render_lightmaps)
194 .add_systems(
195 ExtractSchedule,
196 extract_lightmaps.after(MeshExtractionSystems),
197 );
198 }
199}
200
201fn extract_lightmaps(
204 render_lightmaps: ResMut<RenderLightmaps>,
205 changed_lightmaps_query: Extract<
206 Query<
207 (Entity, &ViewVisibility, &Lightmap),
208 Or<(Changed<ViewVisibility>, Changed<Lightmap>)>,
209 >,
210 >,
211 mut removed_lightmaps_query: Extract<RemovedComponents<Lightmap>>,
212 images: Res<RenderAssets<GpuImage>>,
213 fallback_images: Res<FallbackImage>,
214) {
215 let render_lightmaps = render_lightmaps.into_inner();
216
217 for (entity, view_visibility, lightmap) in changed_lightmaps_query.iter() {
219 if render_lightmaps
220 .render_lightmaps
221 .contains_key(&MainEntity::from(entity))
222 {
223 continue;
224 }
225
226 if !view_visibility.get() {
228 continue;
229 }
230
231 let (slab_index, slot_index) =
232 render_lightmaps.allocate(&fallback_images, lightmap.image.id());
233 render_lightmaps.render_lightmaps.insert(
234 entity.into(),
235 RenderLightmap::new(
236 lightmap.uv_rect,
237 slab_index,
238 slot_index,
239 lightmap.bicubic_sampling,
240 ),
241 );
242
243 render_lightmaps
244 .pending_lightmaps
245 .insert((slab_index, slot_index));
246 }
247
248 for entity in removed_lightmaps_query.read() {
249 if changed_lightmaps_query.contains(entity) {
250 continue;
251 }
252
253 let Some(RenderLightmap {
254 slab_index,
255 slot_index,
256 ..
257 }) = render_lightmaps
258 .render_lightmaps
259 .remove(&MainEntity::from(entity))
260 else {
261 continue;
262 };
263
264 render_lightmaps.remove(&fallback_images, slab_index, slot_index);
265 render_lightmaps
266 .pending_lightmaps
267 .remove(&(slab_index, slot_index));
268 }
269
270 render_lightmaps
271 .pending_lightmaps
272 .retain(|&(slab_index, slot_index)| {
273 let Some(asset_id) = render_lightmaps.slabs[usize::from(slab_index)].lightmaps
274 [usize::from(slot_index)]
275 .asset_id
276 else {
277 error!(
278 "Allocated lightmap should have been removed from `pending_lightmaps` by now"
279 );
280 return false;
281 };
282
283 let Some(gpu_image) = images.get(asset_id) else {
284 return true;
285 };
286 render_lightmaps.slabs[usize::from(slab_index)].insert(slot_index, gpu_image.clone());
287 false
288 });
289}
290
291impl RenderLightmap {
292 fn new(
295 uv_rect: Rect,
296 slab_index: LightmapSlabIndex,
297 slot_index: LightmapSlotIndex,
298 bicubic_sampling: bool,
299 ) -> Self {
300 Self {
301 uv_rect,
302 slab_index,
303 slot_index,
304 bicubic_sampling,
305 }
306 }
307}
308
309pub(crate) fn pack_lightmap_uv_rect(maybe_rect: Option<Rect>) -> UVec2 {
311 match maybe_rect {
312 Some(rect) => {
313 let rect_uvec4 = (vec4(rect.min.x, rect.min.y, rect.max.x, rect.max.y) * 65535.0)
314 .round()
315 .as_uvec4();
316 uvec2(
317 rect_uvec4.x | (rect_uvec4.y << 16),
318 rect_uvec4.z | (rect_uvec4.w << 16),
319 )
320 }
321 None => UVec2::ZERO,
322 }
323}
324
325impl Default for Lightmap {
326 fn default() -> Self {
327 Self {
328 image: Default::default(),
329 uv_rect: Rect::new(0.0, 0.0, 1.0, 1.0),
330 bicubic_sampling: false,
331 }
332 }
333}
334
335pub fn init_render_lightmaps(
336 mut commands: Commands,
337 render_device: Res<RenderDevice>,
338 render_adapter: Res<RenderAdapter>,
339) {
340 let bindless_supported = binding_arrays_are_usable(&render_device, &render_adapter);
341
342 commands.insert_resource(RenderLightmaps {
343 render_lightmaps: default(),
344 slabs: vec![],
345 free_slabs: FixedBitSet::new(),
346 pending_lightmaps: default(),
347 bindless_supported,
348 });
349}
350
351impl RenderLightmaps {
352 fn create_slab(&mut self, fallback_images: &FallbackImage) -> LightmapSlabIndex {
355 let slab_index = LightmapSlabIndex::from(self.slabs.len());
356 self.free_slabs.grow_and_insert(slab_index.into());
357 self.slabs
358 .push(LightmapSlab::new(fallback_images, self.bindless_supported));
359 slab_index
360 }
361
362 fn allocate(
363 &mut self,
364 fallback_images: &FallbackImage,
365 image_id: AssetId<Image>,
366 ) -> (LightmapSlabIndex, LightmapSlotIndex) {
367 let slab_index = match self.free_slabs.minimum() {
368 None => self.create_slab(fallback_images),
369 Some(slab_index) => slab_index.into(),
370 };
371
372 let slab = &mut self.slabs[usize::from(slab_index)];
373 let slot_index = slab.allocate(image_id);
374 if slab.is_full() {
375 self.free_slabs.remove(slab_index.into());
376 }
377
378 (slab_index, slot_index)
379 }
380
381 fn remove(
382 &mut self,
383 fallback_images: &FallbackImage,
384 slab_index: LightmapSlabIndex,
385 slot_index: LightmapSlotIndex,
386 ) {
387 let slab = &mut self.slabs[usize::from(slab_index)];
388 slab.remove(fallback_images, slot_index);
389
390 if !slab.is_full() {
391 self.free_slabs.grow_and_insert(slot_index.into());
392 }
393 }
394}
395
396impl LightmapSlab {
397 fn new(fallback_images: &FallbackImage, bindless_supported: bool) -> LightmapSlab {
398 let count = if bindless_supported {
399 LIGHTMAPS_PER_SLAB
400 } else {
401 1
402 };
403
404 LightmapSlab {
405 lightmaps: (0..count)
406 .map(|_| AllocatedLightmap {
407 gpu_image: fallback_images.d2.clone(),
408 asset_id: None,
409 })
410 .collect(),
411 free_slots_bitmask: (1 << count) - 1,
412 }
413 }
414
415 fn is_full(&self) -> bool {
416 self.free_slots_bitmask == 0
417 }
418
419 fn allocate(&mut self, image_id: AssetId<Image>) -> LightmapSlotIndex {
420 let index = LightmapSlotIndex::from(self.free_slots_bitmask.trailing_zeros());
421 self.free_slots_bitmask &= !(1 << u32::from(index));
422 self.lightmaps[usize::from(index)].asset_id = Some(image_id);
423 index
424 }
425
426 fn insert(&mut self, index: LightmapSlotIndex, gpu_image: GpuImage) {
427 self.lightmaps[usize::from(index)] = AllocatedLightmap {
428 gpu_image,
429 asset_id: None,
430 }
431 }
432
433 fn remove(&mut self, fallback_images: &FallbackImage, index: LightmapSlotIndex) {
434 self.lightmaps[usize::from(index)] = AllocatedLightmap {
435 gpu_image: fallback_images.d2.clone(),
436 asset_id: None,
437 };
438 self.free_slots_bitmask |= 1 << u32::from(index);
439 }
440
441 pub(crate) fn build_binding_arrays(&self) -> (Vec<&WgpuTextureView>, Vec<&WgpuSampler>) {
449 (
450 self.lightmaps
451 .iter()
452 .map(|allocated_lightmap| &*allocated_lightmap.gpu_image.texture_view)
453 .collect(),
454 self.lightmaps
455 .iter()
456 .map(|allocated_lightmap| &*allocated_lightmap.gpu_image.sampler)
457 .collect(),
458 )
459 }
460
461 pub(crate) fn bindings_for_first_lightmap(&self) -> (&TextureView, &Sampler) {
466 (
467 &self.lightmaps[0].gpu_image.texture_view,
468 &self.lightmaps[0].gpu_image.sampler,
469 )
470 }
471}
472
473impl From<u32> for LightmapSlabIndex {
474 fn from(value: u32) -> Self {
475 Self(NonMaxU32::new(value).unwrap())
476 }
477}
478
479impl From<usize> for LightmapSlabIndex {
480 fn from(value: usize) -> Self {
481 Self::from(value as u32)
482 }
483}
484
485impl From<u32> for LightmapSlotIndex {
486 fn from(value: u32) -> Self {
487 Self(NonMaxU16::new(value as u16).unwrap())
488 }
489}
490
491impl From<usize> for LightmapSlotIndex {
492 fn from(value: usize) -> Self {
493 Self::from(value as u32)
494 }
495}
496
497impl From<LightmapSlabIndex> for usize {
498 fn from(value: LightmapSlabIndex) -> Self {
499 value.0.get() as usize
500 }
501}
502
503impl From<LightmapSlotIndex> for usize {
504 fn from(value: LightmapSlotIndex) -> Self {
505 value.0.get() as usize
506 }
507}
508
509impl From<LightmapSlotIndex> for u16 {
510 fn from(value: LightmapSlotIndex) -> Self {
511 value.0.get()
512 }
513}
514
515impl From<LightmapSlotIndex> for u32 {
516 fn from(value: LightmapSlotIndex) -> Self {
517 value.0.get() as u32
518 }
519}