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