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 template::FromTemplate,
48};
49use bevy_image::Image;
50use bevy_math::{uvec2, vec4, Rect, UVec2};
51use bevy_platform::collections::HashSet;
52use bevy_reflect::{std_traits::ReflectDefault, Reflect};
53use bevy_render::{
54 render_asset::RenderAssets,
55 render_resource::{Sampler, TextureView, WgpuSampler, WgpuTextureView},
56 renderer::RenderAdapter,
57 sync_world::MainEntity,
58 texture::{FallbackImage, GpuImage},
59 Extract, ExtractSchedule, RenderApp, RenderStartup,
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, MeshExtractionSystems};
68
69pub const LIGHTMAPS_PER_SLAB: usize = 4;
75
76pub struct LightmapPlugin;
78
79#[derive(Component, Clone, Reflect, FromTemplate)]
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 let Some(render_app) = app.get_sub_app_mut(RenderApp) else {
188 return;
189 };
190 render_app
191 .add_systems(RenderStartup, init_render_lightmaps)
192 .add_systems(
193 ExtractSchedule,
194 extract_lightmaps.after(MeshExtractionSystems),
195 );
196 }
197}
198
199fn extract_lightmaps(
202 render_lightmaps: ResMut<RenderLightmaps>,
203 changed_lightmaps_query: Extract<
204 Query<
205 (Entity, &ViewVisibility, &Lightmap),
206 Or<(Changed<ViewVisibility>, Changed<Lightmap>)>,
207 >,
208 >,
209 mut removed_lightmaps_query: Extract<RemovedComponents<Lightmap>>,
210 images: Res<RenderAssets<GpuImage>>,
211 fallback_images: Res<FallbackImage>,
212) {
213 let render_lightmaps = render_lightmaps.into_inner();
214
215 for (entity, view_visibility, lightmap) in changed_lightmaps_query.iter() {
217 if render_lightmaps
218 .render_lightmaps
219 .contains_key(&MainEntity::from(entity))
220 {
221 continue;
222 }
223
224 if !view_visibility.get() {
226 continue;
227 }
228
229 let (slab_index, slot_index) =
230 render_lightmaps.allocate(&fallback_images, lightmap.image.id());
231 render_lightmaps.render_lightmaps.insert(
232 entity.into(),
233 RenderLightmap::new(
234 lightmap.uv_rect,
235 slab_index,
236 slot_index,
237 lightmap.bicubic_sampling,
238 ),
239 );
240
241 render_lightmaps
242 .pending_lightmaps
243 .insert((slab_index, slot_index));
244 }
245
246 for entity in removed_lightmaps_query.read() {
247 if changed_lightmaps_query.contains(entity) {
248 continue;
249 }
250
251 let Some(RenderLightmap {
252 slab_index,
253 slot_index,
254 ..
255 }) = render_lightmaps
256 .render_lightmaps
257 .remove(&MainEntity::from(entity))
258 else {
259 continue;
260 };
261
262 render_lightmaps.remove(&fallback_images, slab_index, slot_index);
263 render_lightmaps
264 .pending_lightmaps
265 .remove(&(slab_index, slot_index));
266 }
267
268 render_lightmaps
269 .pending_lightmaps
270 .retain(|&(slab_index, slot_index)| {
271 let Some(asset_id) = render_lightmaps.slabs[usize::from(slab_index)].lightmaps
272 [usize::from(slot_index)]
273 .asset_id
274 else {
275 error!(
276 "Allocated lightmap should have been removed from `pending_lightmaps` by now"
277 );
278 return false;
279 };
280
281 let Some(gpu_image) = images.get(asset_id) else {
282 return true;
283 };
284 render_lightmaps.slabs[usize::from(slab_index)].insert(slot_index, gpu_image.clone());
285 false
286 });
287}
288
289impl RenderLightmap {
290 fn new(
293 uv_rect: Rect,
294 slab_index: LightmapSlabIndex,
295 slot_index: LightmapSlotIndex,
296 bicubic_sampling: bool,
297 ) -> Self {
298 Self {
299 uv_rect,
300 slab_index,
301 slot_index,
302 bicubic_sampling,
303 }
304 }
305}
306
307pub(crate) fn pack_lightmap_uv_rect(maybe_rect: Option<Rect>) -> UVec2 {
309 match maybe_rect {
310 Some(rect) => {
311 let rect_uvec4 = (vec4(rect.min.x, rect.min.y, rect.max.x, rect.max.y) * 65535.0)
312 .round()
313 .as_uvec4();
314 uvec2(
315 rect_uvec4.x | (rect_uvec4.y << 16),
316 rect_uvec4.z | (rect_uvec4.w << 16),
317 )
318 }
319 None => UVec2::ZERO,
320 }
321}
322
323impl Default for Lightmap {
324 fn default() -> Self {
325 Self {
326 image: Default::default(),
327 uv_rect: Rect::new(0.0, 0.0, 1.0, 1.0),
328 bicubic_sampling: false,
329 }
330 }
331}
332
333pub fn init_render_lightmaps(
334 mut commands: Commands,
335 render_device: Res<RenderDevice>,
336 render_adapter: Res<RenderAdapter>,
337) {
338 let bindless_supported = binding_arrays_are_usable(&render_device, &render_adapter);
339
340 commands.insert_resource(RenderLightmaps {
341 render_lightmaps: default(),
342 slabs: vec![],
343 free_slabs: FixedBitSet::new(),
344 pending_lightmaps: default(),
345 bindless_supported,
346 });
347}
348
349impl RenderLightmaps {
350 fn create_slab(&mut self, fallback_images: &FallbackImage) -> LightmapSlabIndex {
353 let slab_index = LightmapSlabIndex::from(self.slabs.len());
354 self.free_slabs.grow_and_insert(slab_index.into());
355 self.slabs
356 .push(LightmapSlab::new(fallback_images, self.bindless_supported));
357 slab_index
358 }
359
360 fn allocate(
361 &mut self,
362 fallback_images: &FallbackImage,
363 image_id: AssetId<Image>,
364 ) -> (LightmapSlabIndex, LightmapSlotIndex) {
365 let slab_index = match self.free_slabs.minimum() {
366 None => self.create_slab(fallback_images),
367 Some(slab_index) => slab_index.into(),
368 };
369
370 let slab = &mut self.slabs[usize::from(slab_index)];
371 let slot_index = slab.allocate(image_id);
372 if slab.is_full() {
373 self.free_slabs.remove(slab_index.into());
374 }
375
376 (slab_index, slot_index)
377 }
378
379 fn remove(
380 &mut self,
381 fallback_images: &FallbackImage,
382 slab_index: LightmapSlabIndex,
383 slot_index: LightmapSlotIndex,
384 ) {
385 let slab = &mut self.slabs[usize::from(slab_index)];
386 slab.remove(fallback_images, slot_index);
387
388 if !slab.is_full() {
389 self.free_slabs.grow_and_insert(slab_index.into());
390 }
391 }
392}
393
394impl LightmapSlab {
395 fn new(fallback_images: &FallbackImage, bindless_supported: bool) -> LightmapSlab {
396 let count = if bindless_supported {
397 LIGHTMAPS_PER_SLAB
398 } else {
399 1
400 };
401
402 LightmapSlab {
403 lightmaps: (0..count)
404 .map(|_| AllocatedLightmap {
405 gpu_image: fallback_images.d2.clone(),
406 asset_id: None,
407 })
408 .collect(),
409 free_slots_bitmask: (1 << count) - 1,
410 }
411 }
412
413 fn is_full(&self) -> bool {
414 self.free_slots_bitmask == 0
415 }
416
417 fn allocate(&mut self, image_id: AssetId<Image>) -> LightmapSlotIndex {
418 assert!(
419 !self.is_full(),
420 "Attempting to allocate on a full lightmap slab"
421 );
422 let index = LightmapSlotIndex::from(self.free_slots_bitmask.trailing_zeros());
423 self.free_slots_bitmask &= !(1 << u32::from(index));
424 self.lightmaps[usize::from(index)].asset_id = Some(image_id);
425 index
426 }
427
428 fn insert(&mut self, index: LightmapSlotIndex, gpu_image: GpuImage) {
429 self.lightmaps[usize::from(index)] = AllocatedLightmap {
430 gpu_image,
431 asset_id: None,
432 }
433 }
434
435 fn remove(&mut self, fallback_images: &FallbackImage, index: LightmapSlotIndex) {
436 self.lightmaps[usize::from(index)] = AllocatedLightmap {
437 gpu_image: fallback_images.d2.clone(),
438 asset_id: None,
439 };
440 self.free_slots_bitmask |= 1 << u32::from(index);
441 }
442
443 pub(crate) fn build_binding_arrays(&self) -> (Vec<&WgpuTextureView>, Vec<&WgpuSampler>) {
451 (
452 self.lightmaps
453 .iter()
454 .map(|allocated_lightmap| &*allocated_lightmap.gpu_image.texture_view)
455 .collect(),
456 self.lightmaps
457 .iter()
458 .map(|allocated_lightmap| &*allocated_lightmap.gpu_image.sampler)
459 .collect(),
460 )
461 }
462
463 pub(crate) fn bindings_for_first_lightmap(&self) -> (&TextureView, &Sampler) {
468 (
469 &self.lightmaps[0].gpu_image.texture_view,
470 &self.lightmaps[0].gpu_image.sampler,
471 )
472 }
473}
474
475impl From<u32> for LightmapSlabIndex {
476 fn from(value: u32) -> Self {
477 Self(NonMaxU32::new(value).unwrap())
478 }
479}
480
481impl From<usize> for LightmapSlabIndex {
482 fn from(value: usize) -> Self {
483 Self::from(value as u32)
484 }
485}
486
487impl From<u32> for LightmapSlotIndex {
488 fn from(value: u32) -> Self {
489 Self(NonMaxU16::new(value as u16).unwrap())
490 }
491}
492
493impl From<usize> for LightmapSlotIndex {
494 fn from(value: usize) -> Self {
495 Self::from(value as u32)
496 }
497}
498
499impl From<LightmapSlabIndex> for usize {
500 fn from(value: LightmapSlabIndex) -> Self {
501 value.0.get() as usize
502 }
503}
504
505impl From<LightmapSlotIndex> for usize {
506 fn from(value: LightmapSlotIndex) -> Self {
507 value.0.get() as usize
508 }
509}
510
511impl From<LightmapSlotIndex> for u16 {
512 fn from(value: LightmapSlotIndex) -> Self {
513 value.0.get()
514 }
515}
516
517impl From<LightmapSlotIndex> for u32 {
518 fn from(value: LightmapSlotIndex) -> Self {
519 value.0.get() as u32
520 }
521}