Skip to main content

bevy_pbr/light_probe/
mod.rs

1//! Light probes for baked global illumination.
2
3use bevy_app::{App, Plugin};
4use bevy_asset::AssetId;
5use bevy_camera::{visibility::VisibleEntities, Camera3d};
6use bevy_derive::{Deref, DerefMut};
7use bevy_ecs::{
8    component::Component,
9    entity::Entity,
10    query::{QueryData, ReadOnlyQueryData, With},
11    resource::Resource,
12    schedule::IntoScheduleConfigs,
13    system::{Commands, Local, Query, Res, ResMut},
14};
15use bevy_image::Image;
16use bevy_light::{
17    cluster::ClusterVisibilityClass, EnvironmentMapLight, IrradianceVolume, LightProbe,
18};
19use bevy_math::{Affine3A, FloatOrd, Mat4, Quat, Vec3, Vec4};
20use bevy_platform::collections::HashMap;
21use bevy_render::{
22    extract_instances::ExtractInstancesPlugin,
23    render_asset::RenderAssets,
24    render_resource::{DynamicUniformBuffer, Sampler, ShaderType, TextureView},
25    renderer::{RenderAdapter, RenderAdapterInfo, RenderDevice, RenderQueue, WgpuWrapper},
26    settings::WgpuFeatures,
27    sync_world::{MainEntity, MainEntityHashMap, RenderEntity},
28    texture::{FallbackImage, GpuImage},
29    view::ExtractedView,
30    Extract, ExtractSchedule, GpuResourceAppExt, Render, RenderApp, RenderSystems,
31};
32use bevy_shader::load_shader_library;
33use bevy_transform::prelude::GlobalTransform;
34use bitflags::bitflags;
35use tracing::error;
36
37use core::{any::TypeId, hash::Hash, ops::Deref};
38
39use crate::{
40    extract_clusters_for_cpu_clustering, generate::EnvironmentMapGenerationPlugin,
41    gpu::extract_clusters_for_gpu_clustering, light_probe::environment_map::EnvironmentMapIds,
42};
43
44pub mod environment_map;
45pub mod generate;
46pub mod irradiance_volume;
47
48/// The maximum number of each type of light probe that each view will consider.
49///
50/// Because the fragment shader does a linear search through the list for each
51/// fragment, this number needs to be relatively small.
52pub const MAX_VIEW_LIGHT_PROBES: usize = 8;
53
54/// How many texture bindings are used in the fragment shader, *not* counting
55/// environment maps or irradiance volumes.
56const STANDARD_MATERIAL_FRAGMENT_SHADER_MIN_TEXTURE_BINDINGS: usize = 16;
57
58/// Adds support for light probes: cuboid bounding regions that apply global
59/// illumination to objects within them.
60///
61/// This also adds support for view environment maps: diffuse and specular
62/// cubemaps applied to all objects that a view renders.
63pub struct LightProbePlugin;
64
65/// A GPU type that stores information about a light probe.
66#[derive(Clone, Copy, ShaderType, Default)]
67struct RenderLightProbe {
68    /// The transform from the world space to the model space. This is used to
69    /// efficiently check for bounding box intersection.
70    light_from_world_transposed: [Vec4; 3],
71
72    /// The falloff region, specified as a fraction of the light probe's
73    /// bounding box.
74    ///
75    /// See the comments in [`LightProbe`] for more details.
76    falloff: Vec3,
77
78    /// The radius of the bounding sphere that encompasses this light probe.
79    ///
80    /// This could be computed from [`Self::light_from_world_transposed`], but
81    /// that requires inverting a matrix, which we don't want to do in the GPU
82    /// light clustering kernel.
83    bounding_sphere_radius: f32,
84
85    /// The boundaries of the simulated space used for parallax correction,
86    /// specified as *half* extents in light probe space.
87    ///
88    /// If parallax correction is disabled in [`RenderLightProbe::flags`], this
89    /// field is ignored.
90    ///
91    /// See the comments in [`bevy_light::ParallaxCorrection::Custom`] for more
92    /// details.
93    parallax_correction_bounds: Vec3,
94
95    /// Scale factor applied to the light generated by this light probe.
96    ///
97    /// See the comment in [`EnvironmentMapLight`] for details.
98    intensity: f32,
99
100    /// The world-space position of this light probe.
101    ///
102    /// This could be computed from [`Self::light_from_world_transposed`], but
103    /// that requires inverting a matrix, which we don't want to do in the GPU
104    /// light clustering kernel.
105    world_position: Vec3,
106
107    /// The index of the texture or textures in the appropriate binding array or
108    /// arrays.
109    ///
110    /// For example, for reflection probes this is the index of the cubemap in
111    /// the diffuse and specular texture arrays.
112    texture_index: i32,
113
114    /// Various flags associated with the light probe: the bit value of
115    /// [`RenderLightProbeFlags`].
116    flags: u32,
117}
118
119/// A per-view shader uniform that specifies all the light probes that the view
120/// takes into account.
121#[derive(ShaderType)]
122pub struct LightProbesUniform {
123    /// The list of applicable reflection probes, sorted from nearest to the
124    /// camera to the farthest away from the camera.
125    reflection_probes: [RenderLightProbe; MAX_VIEW_LIGHT_PROBES],
126
127    /// The list of applicable irradiance volumes, sorted from nearest to the
128    /// camera to the farthest away from the camera.
129    irradiance_volumes: [RenderLightProbe; MAX_VIEW_LIGHT_PROBES],
130
131    /// The number of reflection probes in the list.
132    reflection_probe_count: i32,
133
134    /// The number of irradiance volumes in the list.
135    irradiance_volume_count: i32,
136
137    /// The index of the diffuse and specular environment maps associated with
138    /// the view itself. This is used as a fallback if no reflection probe in
139    /// the list contains the fragment.
140    view_cubemap_index: i32,
141
142    /// The smallest valid mipmap level for the specular environment cubemap
143    /// associated with the view.
144    smallest_specular_mip_level_for_view: u32,
145
146    /// World space rotation applied to environment map cubemaps associated with the view itself.
147    view_rotation: Vec4,
148
149    /// The intensity of the environment cubemap associated with the view.
150    ///
151    /// See the comment in [`EnvironmentMapLight`] for details.
152    intensity_for_view: f32,
153
154    /// Whether the environment map attached to the view affects the diffuse
155    /// lighting for lightmapped meshes.
156    ///
157    /// This will be 1 if the map does affect lightmapped meshes or 0 otherwise.
158    view_environment_map_affects_lightmapped_mesh_diffuse: u32,
159}
160
161/// A GPU buffer that stores information about all light probes.
162#[derive(Resource, Default, Deref, DerefMut)]
163pub struct LightProbesBuffer(DynamicUniformBuffer<LightProbesUniform>);
164
165/// A component attached to each camera in the render world that stores the
166/// index of the [`LightProbesUniform`] in the [`LightProbesBuffer`].
167#[derive(Component, Default, Deref, DerefMut)]
168pub struct ViewLightProbesUniformOffset(u32);
169
170/// Information that [`gather_light_probes`] keeps about each light probe.
171///
172/// This information is parameterized by the [`LightProbeComponent`] type. This
173/// will either be [`EnvironmentMapLight`] for reflection probes or
174/// [`IrradianceVolume`] for irradiance volumes.
175struct LightProbeInfo<C>
176where
177    C: LightProbeComponent,
178{
179    // The entity of the light probe in the main world.
180    main_entity: MainEntity,
181
182    // The transform from world space to light probe space.
183    // Stored as the transpose of the inverse transform to compress the structure
184    // on the GPU (from 4 `Vec4`s to 3 `Vec4`s). The shader will transpose it
185    // to recover the original inverse transform.
186    light_from_world: [Vec4; 3],
187
188    // The transform from light probe space to world space.
189    world_from_light: Affine3A,
190
191    /// The radius of the bounding sphere that encompasses this light probe.
192    ///
193    /// This could be computed from [`Self::light_from_world`], but that
194    /// requires inverting a matrix, which we don't want to do in the GPU light
195    /// clustering kernel.
196    bounding_sphere_radius: f32,
197
198    // The falloff region, specified as a fraction of the light probe's
199    // bounding box.
200    //
201    // See the comments in [`LightProbe`] for more details.
202    falloff: Vec3,
203
204    /// The boundaries of the simulated space used for parallax correction,
205    /// specified as *half* extents in light probe space.
206    ///
207    /// If parallax correction is disabled in [`RenderLightProbe::flags`], this
208    /// field is ignored.
209    ///
210    /// See the comments in [`bevy_light::ParallaxCorrection::Custom`] for more
211    /// details.
212    parallax_correction_bounds: Vec3,
213
214    // Scale factor applied to the diffuse and specular light generated by this
215    // reflection probe.
216    //
217    // See the comment in [`EnvironmentMapLight`] for details.
218    intensity: f32,
219
220    // Various flags associated with the light probe.
221    flags: RenderLightProbeFlags,
222
223    // The IDs of all assets associated with this light probe.
224    //
225    // Because each type of light probe component may reference different types
226    // of assets (e.g. a reflection probe references two cubemap assets while an
227    // irradiance volume references a single 3D texture asset), this is generic.
228    asset_id: C::AssetId,
229}
230
231bitflags! {
232    /// Various flags that can be associated with light probes.
233    #[derive(Clone, Copy, PartialEq, Debug)]
234    pub struct RenderLightProbeFlags: u8 {
235        /// Whether this light probe adds to the diffuse contribution of the
236        /// irradiance for meshes with lightmaps.
237        const AFFECTS_LIGHTMAPPED_MESH_DIFFUSE = 1;
238        /// Whether this light probe has parallax correction enabled.
239        ///
240        /// See the comments in [`bevy_light::NoParallaxCorrection`] for more
241        /// information.
242        const ENABLE_PARALLAX_CORRECTION = 2;
243    }
244}
245
246/// A component, part of the render world, that stores the mapping from asset ID
247/// or IDs to the texture index in the appropriate binding arrays.
248///
249/// Cubemap textures belonging to environment maps are collected into binding
250/// arrays, and the index of each texture is presented to the shader for runtime
251/// lookup. 3D textures belonging to reflection probes are likewise collected
252/// into binding arrays, and the shader accesses the 3D texture by index.
253///
254/// This component is attached to each view in the render world, because each
255/// view may have a different set of light probes that it considers and therefore
256/// the texture indices are per-view.
257#[derive(Component, Default)]
258pub struct RenderViewLightProbes<C>
259where
260    C: LightProbeComponent,
261{
262    /// The list of environment maps presented to the shader, in order.
263    pub binding_index_to_textures: Vec<C::AssetId>,
264
265    /// The reverse of `binding_index_to_cubemap`: a map from the texture ID to
266    /// the index in `binding_index_to_cubemap`.
267    cubemap_to_binding_index: HashMap<C::AssetId, u32>,
268
269    /// Information about each light probe, ready for upload to the GPU, sorted
270    /// in order from closest to the camera to farthest.
271    ///
272    /// Note that this is not necessarily ordered by binding index. So don't
273    /// write code like
274    /// `render_light_probes[cubemap_to_binding_index[asset_id]]`; instead
275    /// search for the light probe with the appropriate binding index in this
276    /// array.
277    render_light_probes: Vec<RenderLightProbe>,
278
279    /// A mapping from the main world entity to the index in
280    /// `render_light_probes`.
281    pub main_entity_to_render_light_probe_index: MainEntityHashMap<u32>,
282
283    /// Information needed to render the light probe attached directly to the
284    /// view, if applicable.
285    ///
286    /// A light probe attached directly to a view represents a "global" light
287    /// probe that affects all objects not in the bounding region of any light
288    /// probe. Currently, the only light probe type that supports this is the
289    /// [`EnvironmentMapLight`].
290    view_light_probe_info: Option<C::ViewLightProbeInfo>,
291}
292
293/// A trait implemented by all components that represent light probes.
294///
295/// Currently, the two light probe types are [`EnvironmentMapLight`] and
296/// [`IrradianceVolume`], for reflection probes and irradiance volumes
297/// respectively.
298///
299/// Most light probe systems are written to be generic over the type of light
300/// probe. This allows much of the code to be shared and enables easy addition
301/// of more light probe types (e.g. real-time reflection planes) in the future.
302pub trait LightProbeComponent: Send + Sync + Component + Sized {
303    /// Holds [`AssetId`]s of the texture or textures that this light probe
304    /// references.
305    ///
306    /// This can just be [`AssetId`] if the light probe only references one
307    /// texture. If it references multiple textures, it will be a structure
308    /// containing those asset IDs.
309    type AssetId: Send + Sync + Clone + Eq + Hash;
310
311    /// If the light probe can be attached to the view itself (as opposed to a
312    /// cuboid region within the scene), this contains the information that will
313    /// be passed to the GPU in order to render it. Otherwise, this will be
314    /// `()`.
315    ///
316    /// Currently, only reflection probes (i.e. [`EnvironmentMapLight`]) can be
317    /// attached directly to views.
318    type ViewLightProbeInfo: Send + Sync + Default;
319
320    /// Any additional query data needed to determine the
321    /// [`RenderLightProbeFlags`] for this light probe.
322    type QueryData: ReadOnlyQueryData;
323
324    /// Returns the asset ID or asset IDs of the texture or textures referenced
325    /// by this light probe.
326    fn id(&self, image_assets: &RenderAssets<GpuImage>) -> Option<Self::AssetId>;
327
328    /// Returns the intensity of this light probe.
329    ///
330    /// This is a scaling factor that will be multiplied by the value or values
331    /// sampled from the texture.
332    fn intensity(&self) -> f32;
333
334    /// Returns the appropriate value of [`RenderLightProbeFlags`] for this
335    /// component.
336    fn flags(
337        &self,
338        query_components: &<Self::QueryData as QueryData>::Item<'_, '_>,
339    ) -> RenderLightProbeFlags;
340
341    /// Creates an instance of [`RenderViewLightProbes`] containing all the
342    /// information needed to render this light probe.
343    ///
344    /// This is called for every light probe in view every frame.
345    fn create_render_view_light_probes(
346        view_component: Option<&Self>,
347        image_assets: &RenderAssets<GpuImage>,
348    ) -> RenderViewLightProbes<Self>;
349
350    /// Given the matrix value of the `GlobalTransform` of the light probe,
351    /// returns the matrix that transforms world positions into light probe
352    /// space.
353    ///
354    /// The default implementation simply returns the matrix unchanged, but some
355    /// light probes may want to perform other transforms.
356    fn get_world_from_light_matrix(&self, original_world_from_light: &Affine3A) -> Affine3A {
357        *original_world_from_light
358    }
359
360    /// Returns the appropriate parallax correction bounds, as half extents in
361    /// light probe space, for this component.
362    ///
363    /// See the comments in [`bevy_light::ParallaxCorrection::Custom`] for more
364    /// details.
365    fn parallax_correction_bounds(
366        &self,
367        _query_components: &<Self::QueryData as QueryData>::Item<'_, '_>,
368    ) -> Vec3 {
369        Vec3::ZERO
370    }
371}
372
373impl Plugin for LightProbePlugin {
374    fn build(&self, app: &mut App) {
375        load_shader_library!(app, "light_probe.wgsl");
376        load_shader_library!(app, "environment_map.wgsl");
377        load_shader_library!(app, "irradiance_volume.wgsl");
378
379        app.add_plugins((
380            EnvironmentMapGenerationPlugin,
381            ExtractInstancesPlugin::<EnvironmentMapIds>::new(),
382        ));
383
384        let Some(render_app) = app.get_sub_app_mut(RenderApp) else {
385            return;
386        };
387
388        render_app
389            .init_gpu_resource::<LightProbesBuffer>()
390            .add_systems(
391                ExtractSchedule,
392                gather_light_probes::<EnvironmentMapLight>
393                    .before(extract_clusters_for_cpu_clustering)
394                    .before(extract_clusters_for_gpu_clustering),
395            )
396            .add_systems(
397                ExtractSchedule,
398                gather_light_probes::<IrradianceVolume>
399                    .before(extract_clusters_for_cpu_clustering)
400                    .before(extract_clusters_for_gpu_clustering),
401            )
402            .add_systems(
403                Render,
404                upload_light_probes.in_set(RenderSystems::PrepareResources),
405            );
406    }
407}
408
409/// Gathers up all light probes of a single type in the scene and assigns them
410/// to views, performing frustum culling and distance sorting in the process.
411fn gather_light_probes<C>(
412    image_assets: Res<RenderAssets<GpuImage>>,
413    light_probe_query: Extract<Query<(Entity, &GlobalTransform, &LightProbe, &C, C::QueryData)>>,
414    view_query: Extract<
415        Query<(RenderEntity, &GlobalTransform, &VisibleEntities, Option<&C>), With<Camera3d>>,
416    >,
417    mut view_light_probe_info: Local<Vec<LightProbeInfo<C>>>,
418    mut commands: Commands,
419) where
420    C: LightProbeComponent,
421{
422    // Build up the light probes uniform and the key table.
423    for (view_entity, view_transform, visible_entities, view_component) in view_query.iter() {
424        view_light_probe_info.clear();
425        let visible_light_probes = visible_entities.get(TypeId::of::<ClusterVisibilityClass>());
426        for &main_entity in visible_light_probes {
427            let Ok(query_row) = light_probe_query.get(main_entity) else {
428                // This might not be a light probe. If so, ignore it.
429                continue;
430            };
431            // If we don't successfully create `LightProbeInfo`, that means
432            // the light probe hasn't loaded yet. We don't add such light
433            // probes to `view_light_probe_info` so that they don't waste
434            // space in the GPU light probe buffer, which has a limited
435            // size.
436            if let Some(light_probe_info) = LightProbeInfo::new(query_row, &image_assets) {
437                view_light_probe_info.push(light_probe_info);
438            }
439        }
440
441        // Sort by distance to camera.
442        view_light_probe_info.sort_by_cached_key(|light_probe_info| {
443            light_probe_info.camera_distance_sort_key(view_transform)
444        });
445
446        // Create the light probes list.
447        let mut render_view_light_probes =
448            C::create_render_view_light_probes(view_component, &image_assets);
449
450        // Gather up the light probes in the list.
451        render_view_light_probes.maybe_gather_light_probes(&view_light_probe_info);
452
453        // Record the per-view light probes.
454        if render_view_light_probes.is_empty() {
455            commands
456                .get_entity(view_entity)
457                .expect("View entity wasn't synced.")
458                .remove::<RenderViewLightProbes<C>>();
459        } else {
460            commands
461                .get_entity(view_entity)
462                .expect("View entity wasn't synced.")
463                .insert(render_view_light_probes);
464        }
465    }
466}
467
468// A system that runs after [`gather_light_probes`] and populates the GPU
469// uniforms with the results.
470//
471// Note that, unlike [`gather_light_probes`], this system is not generic over
472// the type of light probe. It collects light probes of all types together into
473// a single structure, ready to be passed to the shader.
474fn upload_light_probes(
475    mut commands: Commands,
476    views: Query<Entity, With<ExtractedView>>,
477    mut light_probes_buffer: ResMut<LightProbesBuffer>,
478    mut view_light_probes_query: Query<(
479        Option<&RenderViewLightProbes<EnvironmentMapLight>>,
480        Option<&RenderViewLightProbes<IrradianceVolume>>,
481    )>,
482    render_device: Res<RenderDevice>,
483    render_queue: Res<RenderQueue>,
484) {
485    // If there are no views, bail.
486    if views.is_empty() {
487        return;
488    }
489
490    // Initialize the uniform buffer writer.
491    let Some(mut writer) =
492        light_probes_buffer.get_writer(views.iter().len(), &render_device, &render_queue)
493    else {
494        return;
495    };
496
497    // Process each view.
498    for view_entity in views.iter() {
499        let Ok((render_view_environment_maps, render_view_irradiance_volumes)) =
500            view_light_probes_query.get_mut(view_entity)
501        else {
502            error!("Failed to find `RenderViewLightProbes` for the view!");
503            continue;
504        };
505
506        // Initialize the uniform with only the view environment map, if there
507        // is one.
508        let maybe_view_light_probe_info =
509            render_view_environment_maps.and_then(|maps| maps.view_light_probe_info.as_ref());
510        let mut light_probes_uniform = LightProbesUniform {
511            reflection_probes: [RenderLightProbe::default(); MAX_VIEW_LIGHT_PROBES],
512            irradiance_volumes: [RenderLightProbe::default(); MAX_VIEW_LIGHT_PROBES],
513            reflection_probe_count: render_view_environment_maps
514                .map(RenderViewLightProbes::len)
515                .unwrap_or_default()
516                .min(MAX_VIEW_LIGHT_PROBES) as i32,
517            irradiance_volume_count: render_view_irradiance_volumes
518                .map(RenderViewLightProbes::len)
519                .unwrap_or_default()
520                .min(MAX_VIEW_LIGHT_PROBES) as i32,
521            view_cubemap_index: match maybe_view_light_probe_info {
522                Some(view_light_probe_info) => view_light_probe_info.cubemap_index,
523                None => -1,
524            },
525            smallest_specular_mip_level_for_view: match maybe_view_light_probe_info {
526                Some(view_light_probe_info) => view_light_probe_info.smallest_specular_mip_level,
527                None => 0,
528            },
529            intensity_for_view: match maybe_view_light_probe_info {
530                Some(view_light_probe_info) => view_light_probe_info.intensity,
531                None => 1.0,
532            },
533            view_environment_map_affects_lightmapped_mesh_diffuse: match maybe_view_light_probe_info
534            {
535                Some(view_light_probe_info) => {
536                    view_light_probe_info.affects_lightmapped_mesh_diffuse as u32
537                }
538                None => 1,
539            },
540            view_rotation: match maybe_view_light_probe_info {
541                Some(view_light_probe_info) => view_light_probe_info.rotation.inverse().into(),
542                None => Quat::IDENTITY.into(),
543            },
544        };
545
546        // Add any environment maps that [`gather_light_probes`] found to the
547        // uniform.
548        if let Some(render_view_environment_maps) = render_view_environment_maps {
549            render_view_environment_maps.add_to_uniform(
550                &mut light_probes_uniform.reflection_probes,
551                &mut light_probes_uniform.reflection_probe_count,
552            );
553        }
554
555        // Add any irradiance volumes that [`gather_light_probes`] found to the
556        // uniform.
557        if let Some(render_view_irradiance_volumes) = render_view_irradiance_volumes {
558            render_view_irradiance_volumes.add_to_uniform(
559                &mut light_probes_uniform.irradiance_volumes,
560                &mut light_probes_uniform.irradiance_volume_count,
561            );
562        }
563
564        // Queue the view's uniforms to be written to the GPU.
565        let uniform_offset = writer.write(&light_probes_uniform);
566
567        commands
568            .entity(view_entity)
569            .insert(ViewLightProbesUniformOffset(uniform_offset));
570    }
571}
572
573impl Default for LightProbesUniform {
574    fn default() -> Self {
575        Self {
576            reflection_probes: [RenderLightProbe::default(); MAX_VIEW_LIGHT_PROBES],
577            irradiance_volumes: [RenderLightProbe::default(); MAX_VIEW_LIGHT_PROBES],
578            reflection_probe_count: 0,
579            irradiance_volume_count: 0,
580            view_cubemap_index: -1,
581            smallest_specular_mip_level_for_view: 0,
582            intensity_for_view: 1.0,
583            view_environment_map_affects_lightmapped_mesh_diffuse: 1,
584            view_rotation: Quat::IDENTITY.into(),
585        }
586    }
587}
588
589impl<C> LightProbeInfo<C>
590where
591    C: LightProbeComponent,
592{
593    /// Given the set of light probe components, constructs and returns
594    /// [`LightProbeInfo`]. This is done for every light probe in the scene
595    /// every frame.
596    fn new(
597        (main_entity, light_probe_transform, light_probe, environment_map, query_components): (
598            Entity,
599            &GlobalTransform,
600            &LightProbe,
601            &C,
602            <C::QueryData as QueryData>::Item<'_, '_>,
603        ),
604        image_assets: &RenderAssets<GpuImage>,
605    ) -> Option<LightProbeInfo<C>> {
606        let world_from_light =
607            environment_map.get_world_from_light_matrix(&light_probe_transform.affine());
608        let light_from_world_transposed = Mat4::from(world_from_light.inverse()).transpose();
609        let bounding_sphere_radius = (world_from_light.matrix3 * Vec3::ONE).length();
610        environment_map.id(image_assets).map(|id| LightProbeInfo {
611            main_entity: main_entity.into(),
612            world_from_light,
613            light_from_world: [
614                light_from_world_transposed.x_axis,
615                light_from_world_transposed.y_axis,
616                light_from_world_transposed.z_axis,
617            ],
618            bounding_sphere_radius,
619            falloff: light_probe.falloff,
620            parallax_correction_bounds: environment_map
621                .parallax_correction_bounds(&query_components),
622            asset_id: id,
623            intensity: environment_map.intensity(),
624            flags: environment_map.flags(&query_components),
625        })
626    }
627
628    /// Returns the squared distance from this light probe to the camera,
629    /// suitable for distance sorting.
630    fn camera_distance_sort_key(&self, view_transform: &GlobalTransform) -> FloatOrd {
631        FloatOrd(
632            (self.world_from_light.translation - view_transform.translation_vec3a())
633                .length_squared(),
634        )
635    }
636}
637
638impl<C> RenderViewLightProbes<C>
639where
640    C: LightProbeComponent,
641{
642    /// Creates a new empty list of light probes.
643    fn new() -> RenderViewLightProbes<C> {
644        RenderViewLightProbes {
645            binding_index_to_textures: vec![],
646            cubemap_to_binding_index: HashMap::default(),
647            main_entity_to_render_light_probe_index: HashMap::default(),
648            render_light_probes: vec![],
649            view_light_probe_info: None,
650        }
651    }
652
653    /// Returns true if there are no light probes in the list.
654    pub(crate) fn is_empty(&self) -> bool {
655        self.render_light_probes.is_empty() && self.view_light_probe_info.is_none()
656    }
657
658    /// Returns the number of light probes in the list.
659    pub(crate) fn len(&self) -> usize {
660        self.render_light_probes.len()
661    }
662
663    /// Adds a cubemap to the list of bindings, if it wasn't there already, and
664    /// returns its index within that list.
665    pub(crate) fn get_or_insert_cubemap(&mut self, cubemap_id: &C::AssetId) -> u32 {
666        *self
667            .cubemap_to_binding_index
668            .entry((*cubemap_id).clone())
669            .or_insert_with(|| {
670                let index = self.binding_index_to_textures.len() as u32;
671                self.binding_index_to_textures.push((*cubemap_id).clone());
672                index
673            })
674    }
675
676    /// Adds all the light probes in this structure to the supplied array, which
677    /// is expected to be shipped to the GPU.
678    fn add_to_uniform(
679        &self,
680        render_light_probes: &mut [RenderLightProbe; MAX_VIEW_LIGHT_PROBES],
681        render_light_probe_count: &mut i32,
682    ) {
683        render_light_probes[0..self.render_light_probes.len()]
684            .copy_from_slice(&self.render_light_probes[..]);
685        *render_light_probe_count = self.render_light_probes.len() as i32;
686    }
687
688    /// Gathers up all light probes of the given type in the scene and records
689    /// them in this structure.
690    fn maybe_gather_light_probes(&mut self, light_probes: &[LightProbeInfo<C>]) {
691        for light_probe in light_probes.iter().take(MAX_VIEW_LIGHT_PROBES) {
692            // Determine the index of the cubemap in the binding array.
693            let cubemap_index = self.get_or_insert_cubemap(&light_probe.asset_id);
694
695            // Assign an ID, and write in the index.
696            let render_light_probe_index = self.render_light_probes.len() as u32;
697            debug_assert!((render_light_probe_index as usize) < MAX_VIEW_LIGHT_PROBES);
698            self.main_entity_to_render_light_probe_index
699                .insert(light_probe.main_entity, render_light_probe_index);
700
701            // Write in the light probe data.
702            self.render_light_probes.push(RenderLightProbe {
703                light_from_world_transposed: light_probe.light_from_world,
704                falloff: light_probe.falloff,
705                parallax_correction_bounds: light_probe.parallax_correction_bounds,
706                world_position: light_probe.world_from_light.translation.into(),
707                bounding_sphere_radius: light_probe.bounding_sphere_radius,
708                texture_index: cubemap_index as i32,
709                intensity: light_probe.intensity,
710                flags: light_probe.flags.bits() as u32,
711            });
712        }
713    }
714}
715
716impl<C> Clone for LightProbeInfo<C>
717where
718    C: LightProbeComponent,
719{
720    fn clone(&self) -> Self {
721        Self {
722            main_entity: self.main_entity,
723            light_from_world: self.light_from_world,
724            world_from_light: self.world_from_light,
725            falloff: self.falloff,
726            parallax_correction_bounds: self.parallax_correction_bounds,
727            bounding_sphere_radius: self.bounding_sphere_radius,
728            intensity: self.intensity,
729            flags: self.flags,
730            asset_id: self.asset_id.clone(),
731        }
732    }
733}
734
735/// Adds a diffuse or specular texture view to the `texture_views` list, and
736/// populates `sampler` if this is the first such view.
737pub(crate) fn add_cubemap_texture_view<'a>(
738    texture_views: &mut Vec<&'a <TextureView as Deref>::Target>,
739    sampler: &mut Option<&'a Sampler>,
740    image_id: AssetId<Image>,
741    images: &'a RenderAssets<GpuImage>,
742    fallback_image: &'a FallbackImage,
743) {
744    match images.get(image_id) {
745        None => {
746            // Use the fallback image if the cubemap isn't loaded yet.
747            texture_views.push(&*fallback_image.cube.texture_view);
748        }
749        Some(image) => {
750            // If this is the first texture view, populate `sampler`.
751            if sampler.is_none() {
752                *sampler = Some(&image.sampler);
753            }
754
755            texture_views.push(&*image.texture_view);
756        }
757    }
758}
759
760/// Many things can go wrong when attempting to use texture binding arrays
761/// (a.k.a. bindless textures). This function checks for these pitfalls:
762///
763/// 1. If GLSL support is enabled at the feature level, then in debug mode
764///    `naga_oil` will attempt to compile all shader modules under GLSL to check
765///    validity of names, even if GLSL isn't actually used. This will cause a crash
766///    if binding arrays are enabled, because binding arrays are currently
767///    unimplemented in the GLSL backend of Naga. Therefore, we disable binding
768///    arrays if the `shader_format_glsl` feature is present.
769///
770/// 2. If there aren't enough texture bindings available to accommodate all the
771///    binding arrays, the driver will panic. So we also bail out if there aren't
772///    enough texture bindings available in the fragment shader.
773///
774/// 3. If binding arrays aren't supported on the hardware, then we obviously
775///    can't use them. Adreno <= 610 claims to support bindless, but seems to be
776///    too buggy to be usable.
777///
778/// 4. If binding arrays are supported on the hardware, but they can only be
779///    accessed by uniform indices, that's not good enough, and we bail out.
780///
781/// If binding arrays aren't usable, we disable reflection probes and limit the
782/// number of irradiance volumes in the scene to 1.
783pub(crate) fn binding_arrays_are_usable(
784    render_device: &RenderDevice,
785    render_adapter: &RenderAdapter,
786) -> bool {
787    let adapter_info = RenderAdapterInfo(WgpuWrapper::new(render_adapter.get_info()));
788
789    !cfg!(feature = "shader_format_glsl")
790        && bevy_render::get_adreno_model(&adapter_info).is_none_or(|model| model > 610)
791        && render_device.limits().max_storage_textures_per_shader_stage
792            >= (STANDARD_MATERIAL_FRAGMENT_SHADER_MIN_TEXTURE_BINDINGS + MAX_VIEW_LIGHT_PROBES)
793                as u32
794        && render_device.features().contains(
795            WgpuFeatures::TEXTURE_BINDING_ARRAY
796                | WgpuFeatures::SAMPLED_TEXTURE_AND_STORAGE_BUFFER_ARRAY_NON_UNIFORM_INDEXING,
797        )
798}