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_shader::load_shader_library;
63use bevy_utils::default;
64use fixedbitset::FixedBitSet;
65use nonmax::{NonMaxU16, NonMaxU32};
66use tracing::error;
67
68use crate::{binding_arrays_are_usable, MeshExtractionSystems};
69
70pub const LIGHTMAPS_PER_SLAB: usize = 4;
76
77pub struct LightmapPlugin;
79
80#[derive(Component, Clone, Reflect, FromTemplate)]
88#[reflect(Component, Default, Clone)]
89pub struct Lightmap {
90 pub image: Handle<Image>,
92
93 pub uv_rect: Rect,
102
103 pub bicubic_sampling: bool,
109}
110
111#[derive(Debug)]
115pub(crate) struct RenderLightmap {
116 pub(crate) uv_rect: Rect,
122
123 pub(crate) slab_index: LightmapSlabIndex,
126
127 pub(crate) slot_index: LightmapSlotIndex,
132
133 pub(crate) bicubic_sampling: bool,
135}
136
137#[derive(Resource)]
142pub struct RenderLightmaps {
143 pub(crate) render_lightmaps: MainEntityHashMap<RenderLightmap>,
148
149 pub(crate) slabs: Vec<LightmapSlab>,
151
152 free_slabs: FixedBitSet,
153
154 pending_lightmaps: HashSet<(LightmapSlabIndex, LightmapSlotIndex)>,
155
156 pub(crate) bindless_supported: bool,
158}
159
160pub struct LightmapSlab {
164 lightmaps: Vec<AllocatedLightmap>,
166 free_slots_bitmask: u32,
167}
168
169struct AllocatedLightmap {
170 gpu_image: GpuImage,
171 asset_id: Option<AssetId<Image>>,
173}
174
175#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug, Deref, DerefMut)]
177#[repr(transparent)]
178pub struct LightmapSlabIndex(pub(crate) NonMaxU32);
179
180#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug, Deref, DerefMut)]
183#[repr(transparent)]
184pub struct LightmapSlotIndex(pub(crate) NonMaxU16);
185
186impl Plugin for LightmapPlugin {
187 fn build(&self, app: &mut App) {
188 load_shader_library!(app, "lightmap.wgsl");
189
190 let Some(render_app) = app.get_sub_app_mut(RenderApp) else {
191 return;
192 };
193 render_app
194 .add_systems(RenderStartup, init_render_lightmaps)
195 .add_systems(
196 ExtractSchedule,
197 extract_lightmaps.after(MeshExtractionSystems),
198 );
199 }
200}
201
202fn extract_lightmaps(
205 render_lightmaps: ResMut<RenderLightmaps>,
206 changed_lightmaps_query: Extract<
207 Query<
208 (Entity, &ViewVisibility, &Lightmap),
209 Or<(Changed<ViewVisibility>, Changed<Lightmap>)>,
210 >,
211 >,
212 mut removed_lightmaps_query: Extract<RemovedComponents<Lightmap>>,
213 images: Res<RenderAssets<GpuImage>>,
214 fallback_images: Res<FallbackImage>,
215) {
216 let render_lightmaps = render_lightmaps.into_inner();
217
218 for (entity, view_visibility, lightmap) in changed_lightmaps_query.iter() {
220 if render_lightmaps
221 .render_lightmaps
222 .contains_key(&MainEntity::from(entity))
223 {
224 continue;
225 }
226
227 if !view_visibility.get() {
229 continue;
230 }
231
232 let (slab_index, slot_index) =
233 render_lightmaps.allocate(&fallback_images, lightmap.image.id());
234 render_lightmaps.render_lightmaps.insert(
235 entity.into(),
236 RenderLightmap::new(
237 lightmap.uv_rect,
238 slab_index,
239 slot_index,
240 lightmap.bicubic_sampling,
241 ),
242 );
243
244 render_lightmaps
245 .pending_lightmaps
246 .insert((slab_index, slot_index));
247 }
248
249 for entity in removed_lightmaps_query.read() {
250 if changed_lightmaps_query.contains(entity) {
251 continue;
252 }
253
254 let Some(RenderLightmap {
255 slab_index,
256 slot_index,
257 ..
258 }) = render_lightmaps
259 .render_lightmaps
260 .remove(&MainEntity::from(entity))
261 else {
262 continue;
263 };
264
265 render_lightmaps.remove(&fallback_images, slab_index, slot_index);
266 render_lightmaps
267 .pending_lightmaps
268 .remove(&(slab_index, slot_index));
269 }
270
271 render_lightmaps
272 .pending_lightmaps
273 .retain(|&(slab_index, slot_index)| {
274 let Some(asset_id) = render_lightmaps.slabs[usize::from(slab_index)].lightmaps
275 [usize::from(slot_index)]
276 .asset_id
277 else {
278 error!(
279 "Allocated lightmap should have been removed from `pending_lightmaps` by now"
280 );
281 return false;
282 };
283
284 let Some(gpu_image) = images.get(asset_id) else {
285 return true;
286 };
287 render_lightmaps.slabs[usize::from(slab_index)].insert(slot_index, gpu_image.clone());
288 false
289 });
290}
291
292impl RenderLightmap {
293 fn new(
296 uv_rect: Rect,
297 slab_index: LightmapSlabIndex,
298 slot_index: LightmapSlotIndex,
299 bicubic_sampling: bool,
300 ) -> Self {
301 Self {
302 uv_rect,
303 slab_index,
304 slot_index,
305 bicubic_sampling,
306 }
307 }
308}
309
310pub(crate) fn pack_lightmap_uv_rect(maybe_rect: Option<Rect>) -> UVec2 {
312 match maybe_rect {
313 Some(rect) => {
314 let rect_uvec4 = (vec4(rect.min.x, rect.min.y, rect.max.x, rect.max.y) * 65535.0)
315 .round()
316 .as_uvec4();
317 uvec2(
318 rect_uvec4.x | (rect_uvec4.y << 16),
319 rect_uvec4.z | (rect_uvec4.w << 16),
320 )
321 }
322 None => UVec2::ZERO,
323 }
324}
325
326impl Default for Lightmap {
327 fn default() -> Self {
328 Self {
329 image: Default::default(),
330 uv_rect: Rect::new(0.0, 0.0, 1.0, 1.0),
331 bicubic_sampling: false,
332 }
333 }
334}
335
336pub fn init_render_lightmaps(
337 mut commands: Commands,
338 render_device: Res<RenderDevice>,
339 render_adapter: Res<RenderAdapter>,
340) {
341 let bindless_supported = binding_arrays_are_usable(&render_device, &render_adapter);
342
343 commands.insert_resource(RenderLightmaps {
344 render_lightmaps: default(),
345 slabs: vec![],
346 free_slabs: FixedBitSet::new(),
347 pending_lightmaps: default(),
348 bindless_supported,
349 });
350}
351
352impl RenderLightmaps {
353 fn create_slab(&mut self, fallback_images: &FallbackImage) -> LightmapSlabIndex {
356 let slab_index = LightmapSlabIndex::from(self.slabs.len());
357 self.free_slabs.grow_and_insert(slab_index.into());
358 self.slabs
359 .push(LightmapSlab::new(fallback_images, self.bindless_supported));
360 slab_index
361 }
362
363 fn allocate(
364 &mut self,
365 fallback_images: &FallbackImage,
366 image_id: AssetId<Image>,
367 ) -> (LightmapSlabIndex, LightmapSlotIndex) {
368 let slab_index = match self.free_slabs.minimum() {
369 None => self.create_slab(fallback_images),
370 Some(slab_index) => slab_index.into(),
371 };
372
373 let slab = &mut self.slabs[usize::from(slab_index)];
374 let slot_index = slab.allocate(image_id);
375 if slab.is_full() {
376 self.free_slabs.remove(slab_index.into());
377 }
378
379 (slab_index, slot_index)
380 }
381
382 fn remove(
383 &mut self,
384 fallback_images: &FallbackImage,
385 slab_index: LightmapSlabIndex,
386 slot_index: LightmapSlotIndex,
387 ) {
388 let slab = &mut self.slabs[usize::from(slab_index)];
389 slab.remove(fallback_images, slot_index);
390
391 if !slab.is_full() {
392 self.free_slabs.grow_and_insert(slab_index.into());
393 }
394 }
395}
396
397impl LightmapSlab {
398 fn new(fallback_images: &FallbackImage, bindless_supported: bool) -> LightmapSlab {
399 let count = if bindless_supported {
400 LIGHTMAPS_PER_SLAB
401 } else {
402 1
403 };
404
405 LightmapSlab {
406 lightmaps: (0..count)
407 .map(|_| AllocatedLightmap {
408 gpu_image: fallback_images.d2.clone(),
409 asset_id: None,
410 })
411 .collect(),
412 free_slots_bitmask: (1 << count) - 1,
413 }
414 }
415
416 fn is_full(&self) -> bool {
417 self.free_slots_bitmask == 0
418 }
419
420 fn allocate(&mut self, image_id: AssetId<Image>) -> LightmapSlotIndex {
421 assert!(
422 !self.is_full(),
423 "Attempting to allocate on a full lightmap slab"
424 );
425 let index = LightmapSlotIndex::from(self.free_slots_bitmask.trailing_zeros());
426 self.free_slots_bitmask &= !(1 << u32::from(index));
427 self.lightmaps[usize::from(index)].asset_id = Some(image_id);
428 index
429 }
430
431 fn insert(&mut self, index: LightmapSlotIndex, gpu_image: GpuImage) {
432 self.lightmaps[usize::from(index)] = AllocatedLightmap {
433 gpu_image,
434 asset_id: None,
435 }
436 }
437
438 fn remove(&mut self, fallback_images: &FallbackImage, index: LightmapSlotIndex) {
439 self.lightmaps[usize::from(index)] = AllocatedLightmap {
440 gpu_image: fallback_images.d2.clone(),
441 asset_id: None,
442 };
443 self.free_slots_bitmask |= 1 << u32::from(index);
444 }
445
446 pub(crate) fn build_binding_arrays(&self) -> (Vec<&WgpuTextureView>, Vec<&WgpuSampler>) {
454 (
455 self.lightmaps
456 .iter()
457 .map(|allocated_lightmap| &*allocated_lightmap.gpu_image.texture_view)
458 .collect(),
459 self.lightmaps
460 .iter()
461 .map(|allocated_lightmap| &*allocated_lightmap.gpu_image.sampler)
462 .collect(),
463 )
464 }
465
466 pub(crate) fn bindings_for_first_lightmap(&self) -> (&TextureView, &Sampler) {
471 (
472 &self.lightmaps[0].gpu_image.texture_view,
473 &self.lightmaps[0].gpu_image.sampler,
474 )
475 }
476}
477
478impl From<u32> for LightmapSlabIndex {
479 fn from(value: u32) -> Self {
480 Self(NonMaxU32::new(value).unwrap())
481 }
482}
483
484impl From<usize> for LightmapSlabIndex {
485 fn from(value: usize) -> Self {
486 Self::from(value as u32)
487 }
488}
489
490impl From<u32> for LightmapSlotIndex {
491 fn from(value: u32) -> Self {
492 Self(NonMaxU16::new(value as u16).unwrap())
493 }
494}
495
496impl From<usize> for LightmapSlotIndex {
497 fn from(value: usize) -> Self {
498 Self::from(value as u32)
499 }
500}
501
502impl From<LightmapSlabIndex> for usize {
503 fn from(value: LightmapSlabIndex) -> Self {
504 value.0.get() as usize
505 }
506}
507
508impl From<LightmapSlotIndex> for usize {
509 fn from(value: LightmapSlotIndex) -> Self {
510 value.0.get() as usize
511 }
512}
513
514impl From<LightmapSlotIndex> for u16 {
515 fn from(value: LightmapSlotIndex) -> Self {
516 value.0.get()
517 }
518}
519
520impl From<LightmapSlotIndex> for u32 {
521 fn from(value: LightmapSlotIndex) -> Self {
522 value.0.get() as u32
523 }
524}