1use bevy_app::{App, Plugin};
35use bevy_asset::{load_internal_asset, weak_handle, AssetId, Handle};
36use bevy_derive::{Deref, DerefMut};
37use bevy_ecs::{
38 component::Component,
39 entity::Entity,
40 query::{Changed, Or},
41 reflect::ReflectComponent,
42 removal_detection::RemovedComponents,
43 resource::Resource,
44 schedule::IntoScheduleConfigs,
45 system::{Query, Res, ResMut},
46 world::{FromWorld, World},
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, Shader, TextureView, WgpuSampler, WgpuTextureView},
55 renderer::RenderAdapter,
56 sync_world::MainEntity,
57 texture::{FallbackImage, GpuImage},
58 view::ViewVisibility,
59 Extract, ExtractSchedule, RenderApp,
60};
61use bevy_render::{renderer::RenderDevice, sync_world::MainEntityHashMap};
62use bevy_utils::default;
63use fixedbitset::FixedBitSet;
64use nonmax::{NonMaxU16, NonMaxU32};
65use tracing::error;
66
67use crate::{binding_arrays_are_usable, ExtractMeshesSet};
68
69pub const LIGHTMAP_SHADER_HANDLE: Handle<Shader> =
71 weak_handle!("fc28203f-f258-47f3-973c-ce7d1dd70e59");
72
73pub const LIGHTMAPS_PER_SLAB: usize = 4;
79
80pub struct LightmapPlugin;
82
83#[derive(Component, Clone, Reflect)]
91#[reflect(Component, Default, Clone)]
92pub struct Lightmap {
93 pub image: Handle<Image>,
95
96 pub uv_rect: Rect,
105
106 pub bicubic_sampling: bool,
112}
113
114#[derive(Debug)]
118pub(crate) struct RenderLightmap {
119 pub(crate) uv_rect: Rect,
125
126 pub(crate) slab_index: LightmapSlabIndex,
129
130 pub(crate) slot_index: LightmapSlotIndex,
135
136 pub(crate) bicubic_sampling: bool,
138}
139
140#[derive(Resource)]
145pub struct RenderLightmaps {
146 pub(crate) render_lightmaps: MainEntityHashMap<RenderLightmap>,
151
152 pub(crate) slabs: Vec<LightmapSlab>,
154
155 free_slabs: FixedBitSet,
156
157 pending_lightmaps: HashSet<(LightmapSlabIndex, LightmapSlotIndex)>,
158
159 pub(crate) bindless_supported: bool,
161}
162
163pub struct LightmapSlab {
167 lightmaps: Vec<AllocatedLightmap>,
169 free_slots_bitmask: u32,
170}
171
172struct AllocatedLightmap {
173 gpu_image: GpuImage,
174 asset_id: Option<AssetId<Image>>,
176}
177
178#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug, Deref, DerefMut)]
180#[repr(transparent)]
181pub struct LightmapSlabIndex(pub(crate) NonMaxU32);
182
183#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug, Deref, DerefMut)]
186#[repr(transparent)]
187pub struct LightmapSlotIndex(pub(crate) NonMaxU16);
188
189impl Plugin for LightmapPlugin {
190 fn build(&self, app: &mut App) {
191 load_internal_asset!(
192 app,
193 LIGHTMAP_SHADER_HANDLE,
194 "lightmap.wgsl",
195 Shader::from_wgsl
196 );
197 }
198
199 fn finish(&self, app: &mut App) {
200 let Some(render_app) = app.get_sub_app_mut(RenderApp) else {
201 return;
202 };
203
204 render_app
205 .init_resource::<RenderLightmaps>()
206 .add_systems(ExtractSchedule, extract_lightmaps.after(ExtractMeshesSet));
207 }
208}
209
210fn extract_lightmaps(
213 render_lightmaps: ResMut<RenderLightmaps>,
214 changed_lightmaps_query: Extract<
215 Query<
216 (Entity, &ViewVisibility, &Lightmap),
217 Or<(Changed<ViewVisibility>, Changed<Lightmap>)>,
218 >,
219 >,
220 mut removed_lightmaps_query: Extract<RemovedComponents<Lightmap>>,
221 images: Res<RenderAssets<GpuImage>>,
222 fallback_images: Res<FallbackImage>,
223) {
224 let render_lightmaps = render_lightmaps.into_inner();
225
226 for (entity, view_visibility, lightmap) in changed_lightmaps_query.iter() {
228 if render_lightmaps
229 .render_lightmaps
230 .contains_key(&MainEntity::from(entity))
231 {
232 continue;
233 }
234
235 if !view_visibility.get() {
237 continue;
238 }
239
240 let (slab_index, slot_index) =
241 render_lightmaps.allocate(&fallback_images, lightmap.image.id());
242 render_lightmaps.render_lightmaps.insert(
243 entity.into(),
244 RenderLightmap::new(
245 lightmap.uv_rect,
246 slab_index,
247 slot_index,
248 lightmap.bicubic_sampling,
249 ),
250 );
251
252 render_lightmaps
253 .pending_lightmaps
254 .insert((slab_index, slot_index));
255 }
256
257 for entity in removed_lightmaps_query.read() {
258 if changed_lightmaps_query.contains(entity) {
259 continue;
260 }
261
262 let Some(RenderLightmap {
263 slab_index,
264 slot_index,
265 ..
266 }) = render_lightmaps
267 .render_lightmaps
268 .remove(&MainEntity::from(entity))
269 else {
270 continue;
271 };
272
273 render_lightmaps.remove(&fallback_images, slab_index, slot_index);
274 render_lightmaps
275 .pending_lightmaps
276 .remove(&(slab_index, slot_index));
277 }
278
279 render_lightmaps
280 .pending_lightmaps
281 .retain(|&(slab_index, slot_index)| {
282 let Some(asset_id) = render_lightmaps.slabs[usize::from(slab_index)].lightmaps
283 [usize::from(slot_index)]
284 .asset_id
285 else {
286 error!(
287 "Allocated lightmap should have been removed from `pending_lightmaps` by now"
288 );
289 return false;
290 };
291
292 let Some(gpu_image) = images.get(asset_id) else {
293 return true;
294 };
295 render_lightmaps.slabs[usize::from(slab_index)].insert(slot_index, gpu_image.clone());
296 false
297 });
298}
299
300impl RenderLightmap {
301 fn new(
304 uv_rect: Rect,
305 slab_index: LightmapSlabIndex,
306 slot_index: LightmapSlotIndex,
307 bicubic_sampling: bool,
308 ) -> Self {
309 Self {
310 uv_rect,
311 slab_index,
312 slot_index,
313 bicubic_sampling,
314 }
315 }
316}
317
318pub(crate) fn pack_lightmap_uv_rect(maybe_rect: Option<Rect>) -> UVec2 {
320 match maybe_rect {
321 Some(rect) => {
322 let rect_uvec4 = (vec4(rect.min.x, rect.min.y, rect.max.x, rect.max.y) * 65535.0)
323 .round()
324 .as_uvec4();
325 uvec2(
326 rect_uvec4.x | (rect_uvec4.y << 16),
327 rect_uvec4.z | (rect_uvec4.w << 16),
328 )
329 }
330 None => UVec2::ZERO,
331 }
332}
333
334impl Default for Lightmap {
335 fn default() -> Self {
336 Self {
337 image: Default::default(),
338 uv_rect: Rect::new(0.0, 0.0, 1.0, 1.0),
339 bicubic_sampling: false,
340 }
341 }
342}
343
344impl FromWorld for RenderLightmaps {
345 fn from_world(world: &mut World) -> Self {
346 let render_device = world.resource::<RenderDevice>();
347 let render_adapter = world.resource::<RenderAdapter>();
348
349 let bindless_supported = binding_arrays_are_usable(render_device, render_adapter);
350
351 RenderLightmaps {
352 render_lightmaps: default(),
353 slabs: vec![],
354 free_slabs: FixedBitSet::new(),
355 pending_lightmaps: default(),
356 bindless_supported,
357 }
358 }
359}
360
361impl RenderLightmaps {
362 fn create_slab(&mut self, fallback_images: &FallbackImage) -> LightmapSlabIndex {
365 let slab_index = LightmapSlabIndex::from(self.slabs.len());
366 self.free_slabs.grow_and_insert(slab_index.into());
367 self.slabs
368 .push(LightmapSlab::new(fallback_images, self.bindless_supported));
369 slab_index
370 }
371
372 fn allocate(
373 &mut self,
374 fallback_images: &FallbackImage,
375 image_id: AssetId<Image>,
376 ) -> (LightmapSlabIndex, LightmapSlotIndex) {
377 let slab_index = match self.free_slabs.minimum() {
378 None => self.create_slab(fallback_images),
379 Some(slab_index) => slab_index.into(),
380 };
381
382 let slab = &mut self.slabs[usize::from(slab_index)];
383 let slot_index = slab.allocate(image_id);
384 if slab.is_full() {
385 self.free_slabs.remove(slab_index.into());
386 }
387
388 (slab_index, slot_index)
389 }
390
391 fn remove(
392 &mut self,
393 fallback_images: &FallbackImage,
394 slab_index: LightmapSlabIndex,
395 slot_index: LightmapSlotIndex,
396 ) {
397 let slab = &mut self.slabs[usize::from(slab_index)];
398 slab.remove(fallback_images, slot_index);
399
400 if !slab.is_full() {
401 self.free_slabs.grow_and_insert(slot_index.into());
402 }
403 }
404}
405
406impl LightmapSlab {
407 fn new(fallback_images: &FallbackImage, bindless_supported: bool) -> LightmapSlab {
408 let count = if bindless_supported {
409 LIGHTMAPS_PER_SLAB
410 } else {
411 1
412 };
413
414 LightmapSlab {
415 lightmaps: (0..count)
416 .map(|_| AllocatedLightmap {
417 gpu_image: fallback_images.d2.clone(),
418 asset_id: None,
419 })
420 .collect(),
421 free_slots_bitmask: (1 << count) - 1,
422 }
423 }
424
425 fn is_full(&self) -> bool {
426 self.free_slots_bitmask == 0
427 }
428
429 fn allocate(&mut self, image_id: AssetId<Image>) -> LightmapSlotIndex {
430 let index = LightmapSlotIndex::from(self.free_slots_bitmask.trailing_zeros());
431 self.free_slots_bitmask &= !(1 << u32::from(index));
432 self.lightmaps[usize::from(index)].asset_id = Some(image_id);
433 index
434 }
435
436 fn insert(&mut self, index: LightmapSlotIndex, gpu_image: GpuImage) {
437 self.lightmaps[usize::from(index)] = AllocatedLightmap {
438 gpu_image,
439 asset_id: None,
440 }
441 }
442
443 fn remove(&mut self, fallback_images: &FallbackImage, index: LightmapSlotIndex) {
444 self.lightmaps[usize::from(index)] = AllocatedLightmap {
445 gpu_image: fallback_images.d2.clone(),
446 asset_id: None,
447 };
448 self.free_slots_bitmask |= 1 << u32::from(index);
449 }
450
451 pub(crate) fn build_binding_arrays(&self) -> (Vec<&WgpuTextureView>, Vec<&WgpuSampler>) {
459 (
460 self.lightmaps
461 .iter()
462 .map(|allocated_lightmap| &*allocated_lightmap.gpu_image.texture_view)
463 .collect(),
464 self.lightmaps
465 .iter()
466 .map(|allocated_lightmap| &*allocated_lightmap.gpu_image.sampler)
467 .collect(),
468 )
469 }
470
471 pub(crate) fn bindings_for_first_lightmap(&self) -> (&TextureView, &Sampler) {
476 (
477 &self.lightmaps[0].gpu_image.texture_view,
478 &self.lightmaps[0].gpu_image.sampler,
479 )
480 }
481}
482
483impl From<u32> for LightmapSlabIndex {
484 fn from(value: u32) -> Self {
485 Self(NonMaxU32::new(value).unwrap())
486 }
487}
488
489impl From<usize> for LightmapSlabIndex {
490 fn from(value: usize) -> Self {
491 Self::from(value as u32)
492 }
493}
494
495impl From<u32> for LightmapSlotIndex {
496 fn from(value: u32) -> Self {
497 Self(NonMaxU16::new(value as u16).unwrap())
498 }
499}
500
501impl From<usize> for LightmapSlotIndex {
502 fn from(value: usize) -> Self {
503 Self::from(value as u32)
504 }
505}
506
507impl From<LightmapSlabIndex> for usize {
508 fn from(value: LightmapSlabIndex) -> Self {
509 value.0.get() as usize
510 }
511}
512
513impl From<LightmapSlotIndex> for usize {
514 fn from(value: LightmapSlotIndex) -> Self {
515 value.0.get() as usize
516 }
517}
518
519impl From<LightmapSlotIndex> for u16 {
520 fn from(value: LightmapSlotIndex) -> Self {
521 value.0.get()
522 }
523}
524
525impl From<LightmapSlotIndex> for u32 {
526 fn from(value: LightmapSlotIndex) -> Self {
527 value.0.get() as u32
528 }
529}