//! Light probes for baked global illumination.12use bevy_app::{App, Plugin};3use bevy_asset::AssetId;4use bevy_camera::Camera3d;5use bevy_derive::{Deref, DerefMut};6use bevy_ecs::{7component::Component,8entity::Entity,9query::{QueryData, ReadOnlyQueryData, With},10resource::Resource,11schedule::IntoScheduleConfigs,12system::{Commands, Local, Query, Res, ResMut},13};14use bevy_image::Image;15use bevy_light::{16cluster::VisibleClusterableObjects, EnvironmentMapLight, IrradianceVolume, LightProbe,17};18use bevy_math::{Affine3A, FloatOrd, Mat4, Vec3, Vec4};19use bevy_platform::collections::HashMap;20use bevy_render::{21extract_instances::ExtractInstancesPlugin,22render_asset::RenderAssets,23render_resource::{DynamicUniformBuffer, Sampler, ShaderType, TextureView},24renderer::{RenderAdapter, RenderAdapterInfo, RenderDevice, RenderQueue, WgpuWrapper},25settings::WgpuFeatures,26sync_world::{MainEntity, MainEntityHashMap, RenderEntity},27texture::{FallbackImage, GpuImage},28view::ExtractedView,29Extract, ExtractSchedule, Render, RenderApp, RenderSystems,30};31use bevy_shader::load_shader_library;32use bevy_transform::{components::Transform, prelude::GlobalTransform};33use bitflags::bitflags;34use tracing::error;3536use core::{any::TypeId, hash::Hash, ops::Deref};3738use crate::{39extract_clusters, generate::EnvironmentMapGenerationPlugin,40light_probe::environment_map::EnvironmentMapIds,41};4243pub mod environment_map;44pub mod generate;45pub mod irradiance_volume;4647/// 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 each50/// fragment, this number needs to be relatively small.51pub const MAX_VIEW_LIGHT_PROBES: usize = 8;5253/// How many texture bindings are used in the fragment shader, *not* counting54/// environment maps or irradiance volumes.55const STANDARD_MATERIAL_FRAGMENT_SHADER_MIN_TEXTURE_BINDINGS: usize = 16;5657/// Adds support for light probes: cuboid bounding regions that apply global58/// illumination to objects within them.59///60/// This also adds support for view environment maps: diffuse and specular61/// cubemaps applied to all objects that a view renders.62pub struct LightProbePlugin;6364/// 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 to68/// efficiently check for bounding box intersection.69light_from_world_transposed: [Vec4; 3],7071/// The falloff region, specified as a fraction of the light probe's72/// bounding box.73///74/// See the comments in [`LightProbe`] for more details.75falloff: Vec3,7677/// The boundaries of the simulated space used for parallax correction,78/// specified as *half* extents in light probe space.79///80/// If parallax correction is disabled in [`RenderLightProbe::flags`], this81/// field is ignored.82///83/// See the comments in [`bevy_light::ParallaxCorrection::Custom`] for more84/// details.85parallax_correction_bounds: Vec3,8687/// The index of the texture or textures in the appropriate binding array or88/// arrays.89///90/// For example, for reflection probes this is the index of the cubemap in91/// the diffuse and specular texture arrays.92texture_index: i32,9394/// Scale factor applied to the light generated by this light probe.95///96/// See the comment in [`EnvironmentMapLight`] for details.97intensity: f32,9899/// Various flags associated with the light probe: the bit value of100/// [`RenderLightProbeFlags`].101flags: u32,102}103104/// A per-view shader uniform that specifies all the light probes that the view105/// takes into account.106#[derive(ShaderType)]107pub struct LightProbesUniform {108/// The list of applicable reflection probes, sorted from nearest to the109/// camera to the farthest away from the camera.110reflection_probes: [RenderLightProbe; MAX_VIEW_LIGHT_PROBES],111112/// The list of applicable irradiance volumes, sorted from nearest to the113/// camera to the farthest away from the camera.114irradiance_volumes: [RenderLightProbe; MAX_VIEW_LIGHT_PROBES],115116/// The number of reflection probes in the list.117reflection_probe_count: i32,118119/// The number of irradiance volumes in the list.120irradiance_volume_count: i32,121122/// The index of the diffuse and specular environment maps associated with123/// the view itself. This is used as a fallback if no reflection probe in124/// the list contains the fragment.125view_cubemap_index: i32,126127/// The smallest valid mipmap level for the specular environment cubemap128/// associated with the view.129smallest_specular_mip_level_for_view: u32,130131/// The intensity of the environment cubemap associated with the view.132///133/// See the comment in [`EnvironmentMapLight`] for details.134intensity_for_view: f32,135136/// Whether the environment map attached to the view affects the diffuse137/// lighting for lightmapped meshes.138///139/// This will be 1 if the map does affect lightmapped meshes or 0 otherwise.140view_environment_map_affects_lightmapped_mesh_diffuse: u32,141}142143/// A GPU buffer that stores information about all light probes.144#[derive(Resource, Default, Deref, DerefMut)]145pub struct LightProbesBuffer(DynamicUniformBuffer<LightProbesUniform>);146147/// A component attached to each camera in the render world that stores the148/// index of the [`LightProbesUniform`] in the [`LightProbesBuffer`].149#[derive(Component, Default, Deref, DerefMut)]150pub struct ViewLightProbesUniformOffset(u32);151152/// Information that [`gather_light_probes`] keeps about each light probe.153///154/// This information is parameterized by the [`LightProbeComponent`] type. This155/// will either be [`EnvironmentMapLight`] for reflection probes or156/// [`IrradianceVolume`] for irradiance volumes.157struct LightProbeInfo<C>158where159C: LightProbeComponent,160{161// The entity of the light probe in the main world.162main_entity: MainEntity,163164// The transform from world space to light probe space.165// Stored as the transpose of the inverse transform to compress the structure166// on the GPU (from 4 `Vec4`s to 3 `Vec4`s). The shader will transpose it167// to recover the original inverse transform.168light_from_world: [Vec4; 3],169170// The transform from light probe space to world space.171world_from_light: Affine3A,172173// The falloff region, specified as a fraction of the light probe's174// bounding box.175//176// See the comments in [`LightProbe`] for more details.177falloff: Vec3,178179/// The boundaries of the simulated space used for parallax correction,180/// specified as *half* extents in light probe space.181///182/// If parallax correction is disabled in [`RenderLightProbe::flags`], this183/// field is ignored.184///185/// See the comments in [`bevy_light::ParallaxCorrection::Custom`] for more186/// details.187parallax_correction_bounds: Vec3,188189// Scale factor applied to the diffuse and specular light generated by this190// reflection probe.191//192// See the comment in [`EnvironmentMapLight`] for details.193intensity: f32,194195// Various flags associated with the light probe.196flags: RenderLightProbeFlags,197198// The IDs of all assets associated with this light probe.199//200// Because each type of light probe component may reference different types201// of assets (e.g. a reflection probe references two cubemap assets while an202// irradiance volume references a single 3D texture asset), this is generic.203asset_id: C::AssetId,204}205206bitflags! {207/// Various flags that can be associated with light probes.208#[derive(Clone, Copy, PartialEq, Debug)]209pub struct RenderLightProbeFlags: u8 {210/// Whether this light probe adds to the diffuse contribution of the211/// irradiance for meshes with lightmaps.212const AFFECTS_LIGHTMAPPED_MESH_DIFFUSE = 1;213/// Whether this light probe has parallax correction enabled.214///215/// See the comments in [`bevy_light::NoParallaxCorrection`] for more216/// information.217const ENABLE_PARALLAX_CORRECTION = 2;218}219}220221/// A component, part of the render world, that stores the mapping from asset ID222/// or IDs to the texture index in the appropriate binding arrays.223///224/// Cubemap textures belonging to environment maps are collected into binding225/// arrays, and the index of each texture is presented to the shader for runtime226/// lookup. 3D textures belonging to reflection probes are likewise collected227/// into binding arrays, and the shader accesses the 3D texture by index.228///229/// This component is attached to each view in the render world, because each230/// view may have a different set of light probes that it considers and therefore231/// the texture indices are per-view.232#[derive(Component, Default)]233pub struct RenderViewLightProbes<C>234where235C: LightProbeComponent,236{237/// The list of environment maps presented to the shader, in order.238pub binding_index_to_textures: Vec<C::AssetId>,239240/// The reverse of `binding_index_to_cubemap`: a map from the texture ID to241/// the index in `binding_index_to_cubemap`.242cubemap_to_binding_index: HashMap<C::AssetId, u32>,243244/// Information about each light probe, ready for upload to the GPU, sorted245/// in order from closest to the camera to farthest.246///247/// Note that this is not necessarily ordered by binding index. So don't248/// write code like249/// `render_light_probes[cubemap_to_binding_index[asset_id]]`; instead250/// search for the light probe with the appropriate binding index in this251/// array.252render_light_probes: Vec<RenderLightProbe>,253254/// A mapping from the main world entity to the index in255/// `render_light_probes`.256pub main_entity_to_render_light_probe_index: MainEntityHashMap<u32>,257258/// Information needed to render the light probe attached directly to the259/// view, if applicable.260///261/// A light probe attached directly to a view represents a "global" light262/// probe that affects all objects not in the bounding region of any light263/// probe. Currently, the only light probe type that supports this is the264/// [`EnvironmentMapLight`].265view_light_probe_info: Option<C::ViewLightProbeInfo>,266}267268/// A trait implemented by all components that represent light probes.269///270/// Currently, the two light probe types are [`EnvironmentMapLight`] and271/// [`IrradianceVolume`], for reflection probes and irradiance volumes272/// respectively.273///274/// Most light probe systems are written to be generic over the type of light275/// probe. This allows much of the code to be shared and enables easy addition276/// of more light probe types (e.g. real-time reflection planes) in the future.277pub trait LightProbeComponent: Send + Sync + Component + Sized {278/// Holds [`AssetId`]s of the texture or textures that this light probe279/// references.280///281/// This can just be [`AssetId`] if the light probe only references one282/// texture. If it references multiple textures, it will be a structure283/// containing those asset IDs.284type AssetId: Send + Sync + Clone + Eq + Hash;285286/// If the light probe can be attached to the view itself (as opposed to a287/// cuboid region within the scene), this contains the information that will288/// be passed to the GPU in order to render it. Otherwise, this will be289/// `()`.290///291/// Currently, only reflection probes (i.e. [`EnvironmentMapLight`]) can be292/// attached directly to views.293type ViewLightProbeInfo: Send + Sync + Default;294295/// Any additional query data needed to determine the296/// [`RenderLightProbeFlags`] for this light probe.297type QueryData: ReadOnlyQueryData;298299/// Returns the asset ID or asset IDs of the texture or textures referenced300/// by this light probe.301fn id(&self, image_assets: &RenderAssets<GpuImage>) -> Option<Self::AssetId>;302303/// Returns the intensity of this light probe.304///305/// This is a scaling factor that will be multiplied by the value or values306/// sampled from the texture.307fn intensity(&self) -> f32;308309/// Returns the appropriate value of [`RenderLightProbeFlags`] for this310/// component.311fn flags(312&self,313query_components: &<Self::QueryData as QueryData>::Item<'_, '_>,314) -> RenderLightProbeFlags;315316/// Creates an instance of [`RenderViewLightProbes`] containing all the317/// information needed to render this light probe.318///319/// This is called for every light probe in view every frame.320fn create_render_view_light_probes(321view_component: Option<&Self>,322image_assets: &RenderAssets<GpuImage>,323) -> RenderViewLightProbes<Self>;324325/// Given the matrix value of the `GlobalTransform` of the light probe,326/// returns the matrix that transforms world positions into light probe327/// space.328///329/// The default implementation simply returns the matrix unchanged, but some330/// light probes may want to perform other transforms.331fn get_world_from_light_matrix(&self, original_world_from_light: &Affine3A) -> Affine3A {332*original_world_from_light333}334335/// Returns the appropriate parallax correction bounds, as half extents in336/// light probe space, for this component.337///338/// See the comments in [`bevy_light::ParallaxCorrection::Custom`] for more339/// details.340fn parallax_correction_bounds(341&self,342_query_components: &<Self::QueryData as QueryData>::Item<'_, '_>,343) -> Vec3 {344Vec3::ZERO345}346}347348/// The uniform struct extracted from [`EnvironmentMapLight`].349/// Will be available for use in the Environment Map shader.350#[derive(Component, ShaderType, Clone)]351pub struct EnvironmentMapUniform {352/// The world space transformation matrix of the sample ray for environment cubemaps.353transform: Mat4,354}355356impl Default for EnvironmentMapUniform {357fn default() -> Self {358EnvironmentMapUniform {359transform: Mat4::IDENTITY,360}361}362}363364/// A GPU buffer that stores the environment map settings for each view.365#[derive(Resource, Default, Deref, DerefMut)]366pub struct EnvironmentMapUniformBuffer(pub DynamicUniformBuffer<EnvironmentMapUniform>);367368/// A component that stores the offset within the369/// [`EnvironmentMapUniformBuffer`] for each view.370#[derive(Component, Default, Deref, DerefMut)]371pub struct ViewEnvironmentMapUniformOffset(u32);372373impl Plugin for LightProbePlugin {374fn build(&self, app: &mut App) {375load_shader_library!(app, "light_probe.wgsl");376load_shader_library!(app, "environment_map.wgsl");377load_shader_library!(app, "irradiance_volume.wgsl");378379app.add_plugins((380EnvironmentMapGenerationPlugin,381ExtractInstancesPlugin::<EnvironmentMapIds>::new(),382));383384let Some(render_app) = app.get_sub_app_mut(RenderApp) else {385return;386};387388render_app389.init_resource::<LightProbesBuffer>()390.init_resource::<EnvironmentMapUniformBuffer>()391.add_systems(ExtractSchedule, gather_environment_map_uniform)392.add_systems(393ExtractSchedule,394gather_light_probes::<EnvironmentMapLight>.before(extract_clusters),395)396.add_systems(397ExtractSchedule,398gather_light_probes::<IrradianceVolume>.before(extract_clusters),399)400.add_systems(401Render,402(upload_light_probes, prepare_environment_uniform_buffer)403.in_set(RenderSystems::PrepareResources),404);405}406}407408/// Extracts [`EnvironmentMapLight`] from views and creates [`EnvironmentMapUniform`] for them.409///410/// Compared to the `ExtractComponentPlugin`, this implementation will create a default instance411/// if one does not already exist.412fn gather_environment_map_uniform(413view_query: Extract<Query<(RenderEntity, Option<&EnvironmentMapLight>), With<Camera3d>>>,414mut commands: Commands,415) {416for (view_entity, environment_map_light) in view_query.iter() {417let environment_map_uniform = if let Some(environment_map_light) = environment_map_light {418EnvironmentMapUniform {419transform: Transform::from_rotation(environment_map_light.rotation)420.to_matrix()421.inverse(),422}423} else {424EnvironmentMapUniform::default()425};426commands427.get_entity(view_entity)428.expect("Environment map light entity wasn't synced.")429.insert(environment_map_uniform);430}431}432433/// Gathers up all light probes of a single type in the scene and assigns them434/// to views, performing frustum culling and distance sorting in the process.435fn gather_light_probes<C>(436image_assets: Res<RenderAssets<GpuImage>>,437light_probe_query: Extract<Query<(Entity, &GlobalTransform, &LightProbe, &C, C::QueryData)>>,438view_query: Extract<439Query<440(441RenderEntity,442&GlobalTransform,443&VisibleClusterableObjects,444Option<&C>,445),446With<Camera3d>,447>,448>,449mut view_light_probe_info: Local<Vec<LightProbeInfo<C>>>,450mut commands: Commands,451) where452C: LightProbeComponent,453{454// Build up the light probes uniform and the key table.455for (view_entity, view_transform, visible_clusterable_objects, view_component) in456view_query.iter()457{458view_light_probe_info.clear();459view_light_probe_info.reserve(visible_clusterable_objects.light_probes.len());460if let Some(visible_light_probes) = visible_clusterable_objects461.light_probes462.get(&TypeId::of::<C>())463{464for &main_entity in visible_light_probes {465let Ok(query_row) = light_probe_query.get(main_entity) else {466// This should never happen. `assign_objects_to_clusters`467// should use a light probe query that matches exactly the468// same set of entities as our `light_probe_query`.469error!(470"Clustering shouldn't have clustered light probe {:?}",471main_entity472);473continue;474};475// If we don't successfully create `LightProbeInfo`, that means476// the light probe hasn't loaded yet. We don't add such light477// probes to `view_light_probe_info` so that they don't waste478// space in the GPU light probe buffer, which has a limited479// size.480if let Some(light_probe_info) = LightProbeInfo::new(query_row, &image_assets) {481view_light_probe_info.push(light_probe_info);482}483}484}485486// Sort by distance to camera.487view_light_probe_info.sort_by_cached_key(|light_probe_info| {488light_probe_info.camera_distance_sort_key(view_transform)489});490491// Create the light probes list.492let mut render_view_light_probes =493C::create_render_view_light_probes(view_component, &image_assets);494495// Gather up the light probes in the list.496render_view_light_probes.maybe_gather_light_probes(&view_light_probe_info);497498// Record the per-view light probes.499if render_view_light_probes.is_empty() {500commands501.get_entity(view_entity)502.expect("View entity wasn't synced.")503.remove::<RenderViewLightProbes<C>>();504} else {505commands506.get_entity(view_entity)507.expect("View entity wasn't synced.")508.insert(render_view_light_probes);509}510}511}512513/// Gathers up environment map settings for each applicable view and514/// writes them into a GPU buffer.515pub fn prepare_environment_uniform_buffer(516mut commands: Commands,517views: Query<(Entity, Option<&EnvironmentMapUniform>), With<ExtractedView>>,518mut environment_uniform_buffer: ResMut<EnvironmentMapUniformBuffer>,519render_device: Res<RenderDevice>,520render_queue: Res<RenderQueue>,521) {522let Some(mut writer) =523environment_uniform_buffer.get_writer(views.iter().len(), &render_device, &render_queue)524else {525return;526};527528for (view, environment_uniform) in views.iter() {529let uniform_offset = match environment_uniform {530None => 0,531Some(environment_uniform) => writer.write(environment_uniform),532};533commands534.entity(view)535.insert(ViewEnvironmentMapUniformOffset(uniform_offset));536}537}538539// A system that runs after [`gather_light_probes`] and populates the GPU540// uniforms with the results.541//542// Note that, unlike [`gather_light_probes`], this system is not generic over543// the type of light probe. It collects light probes of all types together into544// a single structure, ready to be passed to the shader.545fn upload_light_probes(546mut commands: Commands,547views: Query<Entity, With<ExtractedView>>,548mut light_probes_buffer: ResMut<LightProbesBuffer>,549mut view_light_probes_query: Query<(550Option<&RenderViewLightProbes<EnvironmentMapLight>>,551Option<&RenderViewLightProbes<IrradianceVolume>>,552)>,553render_device: Res<RenderDevice>,554render_queue: Res<RenderQueue>,555) {556// If there are no views, bail.557if views.is_empty() {558return;559}560561// Initialize the uniform buffer writer.562let mut writer = light_probes_buffer563.get_writer(views.iter().len(), &render_device, &render_queue)564.unwrap();565566// Process each view.567for view_entity in views.iter() {568let Ok((render_view_environment_maps, render_view_irradiance_volumes)) =569view_light_probes_query.get_mut(view_entity)570else {571error!("Failed to find `RenderViewLightProbes` for the view!");572continue;573};574575// Initialize the uniform with only the view environment map, if there576// is one.577let maybe_view_light_probe_info =578render_view_environment_maps.and_then(|maps| maps.view_light_probe_info.as_ref());579let mut light_probes_uniform = LightProbesUniform {580reflection_probes: [RenderLightProbe::default(); MAX_VIEW_LIGHT_PROBES],581irradiance_volumes: [RenderLightProbe::default(); MAX_VIEW_LIGHT_PROBES],582reflection_probe_count: render_view_environment_maps583.map(RenderViewLightProbes::len)584.unwrap_or_default()585.min(MAX_VIEW_LIGHT_PROBES) as i32,586irradiance_volume_count: render_view_irradiance_volumes587.map(RenderViewLightProbes::len)588.unwrap_or_default()589.min(MAX_VIEW_LIGHT_PROBES) as i32,590view_cubemap_index: match maybe_view_light_probe_info {591Some(view_light_probe_info) => view_light_probe_info.cubemap_index,592None => -1,593},594smallest_specular_mip_level_for_view: match maybe_view_light_probe_info {595Some(view_light_probe_info) => view_light_probe_info.smallest_specular_mip_level,596None => 0,597},598intensity_for_view: match maybe_view_light_probe_info {599Some(view_light_probe_info) => view_light_probe_info.intensity,600None => 1.0,601},602view_environment_map_affects_lightmapped_mesh_diffuse: match maybe_view_light_probe_info603{604Some(view_light_probe_info) => {605view_light_probe_info.affects_lightmapped_mesh_diffuse as u32606}607None => 1,608},609};610611// Add any environment maps that [`gather_light_probes`] found to the612// uniform.613if let Some(render_view_environment_maps) = render_view_environment_maps {614render_view_environment_maps.add_to_uniform(615&mut light_probes_uniform.reflection_probes,616&mut light_probes_uniform.reflection_probe_count,617);618}619620// Add any irradiance volumes that [`gather_light_probes`] found to the621// uniform.622if let Some(render_view_irradiance_volumes) = render_view_irradiance_volumes {623render_view_irradiance_volumes.add_to_uniform(624&mut light_probes_uniform.irradiance_volumes,625&mut light_probes_uniform.irradiance_volume_count,626);627}628629// Queue the view's uniforms to be written to the GPU.630let uniform_offset = writer.write(&light_probes_uniform);631632commands633.entity(view_entity)634.insert(ViewLightProbesUniformOffset(uniform_offset));635}636}637638impl Default for LightProbesUniform {639fn default() -> Self {640Self {641reflection_probes: [RenderLightProbe::default(); MAX_VIEW_LIGHT_PROBES],642irradiance_volumes: [RenderLightProbe::default(); MAX_VIEW_LIGHT_PROBES],643reflection_probe_count: 0,644irradiance_volume_count: 0,645view_cubemap_index: -1,646smallest_specular_mip_level_for_view: 0,647intensity_for_view: 1.0,648view_environment_map_affects_lightmapped_mesh_diffuse: 1,649}650}651}652653impl<C> LightProbeInfo<C>654where655C: LightProbeComponent,656{657/// Given the set of light probe components, constructs and returns658/// [`LightProbeInfo`]. This is done for every light probe in the scene659/// every frame.660fn new(661(main_entity, light_probe_transform, light_probe, environment_map, query_components): (662Entity,663&GlobalTransform,664&LightProbe,665&C,666<C::QueryData as QueryData>::Item<'_, '_>,667),668image_assets: &RenderAssets<GpuImage>,669) -> Option<LightProbeInfo<C>> {670let world_from_light =671environment_map.get_world_from_light_matrix(&light_probe_transform.affine());672let light_from_world_transposed = Mat4::from(world_from_light.inverse()).transpose();673environment_map.id(image_assets).map(|id| LightProbeInfo {674main_entity: main_entity.into(),675world_from_light,676light_from_world: [677light_from_world_transposed.x_axis,678light_from_world_transposed.y_axis,679light_from_world_transposed.z_axis,680],681falloff: light_probe.falloff,682parallax_correction_bounds: environment_map683.parallax_correction_bounds(&query_components),684asset_id: id,685intensity: environment_map.intensity(),686flags: environment_map.flags(&query_components),687})688}689690/// Returns the squared distance from this light probe to the camera,691/// suitable for distance sorting.692fn camera_distance_sort_key(&self, view_transform: &GlobalTransform) -> FloatOrd {693FloatOrd(694(self.world_from_light.translation - view_transform.translation_vec3a())695.length_squared(),696)697}698}699700impl<C> RenderViewLightProbes<C>701where702C: LightProbeComponent,703{704/// Creates a new empty list of light probes.705fn new() -> RenderViewLightProbes<C> {706RenderViewLightProbes {707binding_index_to_textures: vec![],708cubemap_to_binding_index: HashMap::default(),709main_entity_to_render_light_probe_index: HashMap::default(),710render_light_probes: vec![],711view_light_probe_info: None,712}713}714715/// Returns true if there are no light probes in the list.716pub(crate) fn is_empty(&self) -> bool {717self.render_light_probes.is_empty() && self.view_light_probe_info.is_none()718}719720/// Returns the number of light probes in the list.721pub(crate) fn len(&self) -> usize {722self.render_light_probes.len()723}724725/// Adds a cubemap to the list of bindings, if it wasn't there already, and726/// returns its index within that list.727pub(crate) fn get_or_insert_cubemap(&mut self, cubemap_id: &C::AssetId) -> u32 {728*self729.cubemap_to_binding_index730.entry((*cubemap_id).clone())731.or_insert_with(|| {732let index = self.binding_index_to_textures.len() as u32;733self.binding_index_to_textures.push((*cubemap_id).clone());734index735})736}737738/// Adds all the light probes in this structure to the supplied array, which739/// is expected to be shipped to the GPU.740fn add_to_uniform(741&self,742render_light_probes: &mut [RenderLightProbe; MAX_VIEW_LIGHT_PROBES],743render_light_probe_count: &mut i32,744) {745render_light_probes[0..self.render_light_probes.len()]746.copy_from_slice(&self.render_light_probes[..]);747*render_light_probe_count = self.render_light_probes.len() as i32;748}749750/// Gathers up all light probes of the given type in the scene and records751/// them in this structure.752fn maybe_gather_light_probes(&mut self, light_probes: &[LightProbeInfo<C>]) {753for light_probe in light_probes.iter().take(MAX_VIEW_LIGHT_PROBES) {754// Determine the index of the cubemap in the binding array.755let cubemap_index = self.get_or_insert_cubemap(&light_probe.asset_id);756757// Assign an ID, and write in the index.758let render_light_probe_index = self.render_light_probes.len() as u32;759debug_assert!((render_light_probe_index as usize) < MAX_VIEW_LIGHT_PROBES);760self.main_entity_to_render_light_probe_index761.insert(light_probe.main_entity, render_light_probe_index);762763// Write in the light probe data.764self.render_light_probes.push(RenderLightProbe {765light_from_world_transposed: light_probe.light_from_world,766falloff: light_probe.falloff,767parallax_correction_bounds: light_probe.parallax_correction_bounds,768texture_index: cubemap_index as i32,769intensity: light_probe.intensity,770flags: light_probe.flags.bits() as u32,771});772}773}774}775776impl<C> Clone for LightProbeInfo<C>777where778C: LightProbeComponent,779{780fn clone(&self) -> Self {781Self {782main_entity: self.main_entity,783light_from_world: self.light_from_world,784world_from_light: self.world_from_light,785falloff: self.falloff,786parallax_correction_bounds: self.parallax_correction_bounds,787intensity: self.intensity,788flags: self.flags,789asset_id: self.asset_id.clone(),790}791}792}793794/// Adds a diffuse or specular texture view to the `texture_views` list, and795/// populates `sampler` if this is the first such view.796pub(crate) fn add_cubemap_texture_view<'a>(797texture_views: &mut Vec<&'a <TextureView as Deref>::Target>,798sampler: &mut Option<&'a Sampler>,799image_id: AssetId<Image>,800images: &'a RenderAssets<GpuImage>,801fallback_image: &'a FallbackImage,802) {803match images.get(image_id) {804None => {805// Use the fallback image if the cubemap isn't loaded yet.806texture_views.push(&*fallback_image.cube.texture_view);807}808Some(image) => {809// If this is the first texture view, populate `sampler`.810if sampler.is_none() {811*sampler = Some(&image.sampler);812}813814texture_views.push(&*image.texture_view);815}816}817}818819/// Many things can go wrong when attempting to use texture binding arrays820/// (a.k.a. bindless textures). This function checks for these pitfalls:821///822/// 1. If GLSL support is enabled at the feature level, then in debug mode823/// `naga_oil` will attempt to compile all shader modules under GLSL to check824/// validity of names, even if GLSL isn't actually used. This will cause a crash825/// if binding arrays are enabled, because binding arrays are currently826/// unimplemented in the GLSL backend of Naga. Therefore, we disable binding827/// arrays if the `shader_format_glsl` feature is present.828///829/// 2. If there aren't enough texture bindings available to accommodate all the830/// binding arrays, the driver will panic. So we also bail out if there aren't831/// enough texture bindings available in the fragment shader.832///833/// 3. If binding arrays aren't supported on the hardware, then we obviously834/// can't use them. Adreno <= 610 claims to support bindless, but seems to be835/// too buggy to be usable.836///837/// 4. If binding arrays are supported on the hardware, but they can only be838/// accessed by uniform indices, that's not good enough, and we bail out.839///840/// If binding arrays aren't usable, we disable reflection probes and limit the841/// number of irradiance volumes in the scene to 1.842pub(crate) fn binding_arrays_are_usable(843render_device: &RenderDevice,844render_adapter: &RenderAdapter,845) -> bool {846let adapter_info = RenderAdapterInfo(WgpuWrapper::new(render_adapter.get_info()));847848!cfg!(feature = "shader_format_glsl")849&& bevy_render::get_adreno_model(&adapter_info).is_none_or(|model| model > 610)850&& render_device.limits().max_storage_textures_per_shader_stage851>= (STANDARD_MATERIAL_FRAGMENT_SHADER_MIN_TEXTURE_BINDINGS + MAX_VIEW_LIGHT_PROBES)852as u32853&& render_device.features().contains(854WgpuFeatures::TEXTURE_BINDING_ARRAY855| WgpuFeatures::SAMPLED_TEXTURE_AND_STORAGE_BUFFER_ARRAY_NON_UNIFORM_INDEXING,856)857}858859860