//! Light probes for baked global illumination.12use bevy_app::{App, Plugin};3use bevy_asset::AssetId;4use bevy_camera::{5primitives::{Aabb, Frustum},6Camera3d,7};8use bevy_derive::{Deref, DerefMut};9use bevy_ecs::{10component::Component,11entity::Entity,12query::With,13resource::Resource,14schedule::IntoScheduleConfigs,15system::{Commands, Local, Query, Res, ResMut},16};17use bevy_image::Image;18use bevy_light::{EnvironmentMapLight, IrradianceVolume, LightProbe};19use bevy_math::{Affine3A, FloatOrd, Mat4, Vec3A, Vec4};20use bevy_platform::collections::HashMap;21use bevy_render::{22extract_instances::ExtractInstancesPlugin,23render_asset::RenderAssets,24render_resource::{DynamicUniformBuffer, Sampler, ShaderType, TextureView},25renderer::{RenderAdapter, RenderAdapterInfo, RenderDevice, RenderQueue, WgpuWrapper},26settings::WgpuFeatures,27sync_world::RenderEntity,28texture::{FallbackImage, GpuImage},29view::ExtractedView,30Extract, ExtractSchedule, Render, RenderApp, RenderSystems,31};32use bevy_shader::load_shader_library;33use bevy_transform::{components::Transform, prelude::GlobalTransform};34use tracing::error;3536use core::{hash::Hash, ops::Deref};3738use crate::{39generate::EnvironmentMapGenerationPlugin, light_probe::environment_map::EnvironmentMapIds,40};4142pub mod environment_map;43pub mod generate;44pub mod irradiance_volume;4546/// The maximum number of each type of light probe that each view will consider.47///48/// Because the fragment shader does a linear search through the list for each49/// fragment, this number needs to be relatively small.50pub const MAX_VIEW_LIGHT_PROBES: usize = 8;5152/// How many texture bindings are used in the fragment shader, *not* counting53/// environment maps or irradiance volumes.54const STANDARD_MATERIAL_FRAGMENT_SHADER_MIN_TEXTURE_BINDINGS: usize = 16;5556/// Adds support for light probes: cuboid bounding regions that apply global57/// illumination to objects within them.58///59/// This also adds support for view environment maps: diffuse and specular60/// cubemaps applied to all objects that a view renders.61pub struct LightProbePlugin;6263/// A GPU type that stores information about a light probe.64#[derive(Clone, Copy, ShaderType, Default)]65struct RenderLightProbe {66/// The transform from the world space to the model space. This is used to67/// efficiently check for bounding box intersection.68light_from_world_transposed: [Vec4; 3],6970/// The index of the texture or textures in the appropriate binding array or71/// arrays.72///73/// For example, for reflection probes this is the index of the cubemap in74/// the diffuse and specular texture arrays.75texture_index: i32,7677/// Scale factor applied to the light generated by this light probe.78///79/// See the comment in [`EnvironmentMapLight`] for details.80intensity: f32,8182/// Whether this light probe adds to the diffuse contribution of the83/// irradiance for meshes with lightmaps.84affects_lightmapped_mesh_diffuse: u32,85}8687/// A per-view shader uniform that specifies all the light probes that the view88/// takes into account.89#[derive(ShaderType)]90pub struct LightProbesUniform {91/// The list of applicable reflection probes, sorted from nearest to the92/// camera to the farthest away from the camera.93reflection_probes: [RenderLightProbe; MAX_VIEW_LIGHT_PROBES],9495/// The list of applicable irradiance volumes, sorted from nearest to the96/// camera to the farthest away from the camera.97irradiance_volumes: [RenderLightProbe; MAX_VIEW_LIGHT_PROBES],9899/// The number of reflection probes in the list.100reflection_probe_count: i32,101102/// The number of irradiance volumes in the list.103irradiance_volume_count: i32,104105/// The index of the diffuse and specular environment maps associated with106/// the view itself. This is used as a fallback if no reflection probe in107/// the list contains the fragment.108view_cubemap_index: i32,109110/// The smallest valid mipmap level for the specular environment cubemap111/// associated with the view.112smallest_specular_mip_level_for_view: u32,113114/// The intensity of the environment cubemap associated with the view.115///116/// See the comment in [`EnvironmentMapLight`] for details.117intensity_for_view: f32,118119/// Whether the environment map attached to the view affects the diffuse120/// lighting for lightmapped meshes.121///122/// This will be 1 if the map does affect lightmapped meshes or 0 otherwise.123view_environment_map_affects_lightmapped_mesh_diffuse: u32,124}125126/// A GPU buffer that stores information about all light probes.127#[derive(Resource, Default, Deref, DerefMut)]128pub struct LightProbesBuffer(DynamicUniformBuffer<LightProbesUniform>);129130/// A component attached to each camera in the render world that stores the131/// index of the [`LightProbesUniform`] in the [`LightProbesBuffer`].132#[derive(Component, Default, Deref, DerefMut)]133pub struct ViewLightProbesUniformOffset(u32);134135/// Information that [`gather_light_probes`] keeps about each light probe.136///137/// This information is parameterized by the [`LightProbeComponent`] type. This138/// will either be [`EnvironmentMapLight`] for reflection probes or139/// [`IrradianceVolume`] for irradiance volumes.140struct LightProbeInfo<C>141where142C: LightProbeComponent,143{144// The transform from world space to light probe space.145// Stored as the transpose of the inverse transform to compress the structure146// on the GPU (from 4 `Vec4`s to 3 `Vec4`s). The shader will transpose it147// to recover the original inverse transform.148light_from_world: [Vec4; 3],149150// The transform from light probe space to world space.151world_from_light: Affine3A,152153// Scale factor applied to the diffuse and specular light generated by this154// reflection probe.155//156// See the comment in [`EnvironmentMapLight`] for details.157intensity: f32,158159// Whether this light probe adds to the diffuse contribution of the160// irradiance for meshes with lightmaps.161affects_lightmapped_mesh_diffuse: bool,162163// The IDs of all assets associated with this light probe.164//165// Because each type of light probe component may reference different types166// of assets (e.g. a reflection probe references two cubemap assets while an167// irradiance volume references a single 3D texture asset), this is generic.168asset_id: C::AssetId,169}170171/// A component, part of the render world, that stores the mapping from asset ID172/// or IDs to the texture index in the appropriate binding arrays.173///174/// Cubemap textures belonging to environment maps are collected into binding175/// arrays, and the index of each texture is presented to the shader for runtime176/// lookup. 3D textures belonging to reflection probes are likewise collected177/// into binding arrays, and the shader accesses the 3D texture by index.178///179/// This component is attached to each view in the render world, because each180/// view may have a different set of light probes that it considers and therefore181/// the texture indices are per-view.182#[derive(Component, Default)]183pub struct RenderViewLightProbes<C>184where185C: LightProbeComponent,186{187/// The list of environment maps presented to the shader, in order.188binding_index_to_textures: Vec<C::AssetId>,189190/// The reverse of `binding_index_to_cubemap`: a map from the texture ID to191/// the index in `binding_index_to_cubemap`.192cubemap_to_binding_index: HashMap<C::AssetId, u32>,193194/// Information about each light probe, ready for upload to the GPU, sorted195/// in order from closest to the camera to farthest.196///197/// Note that this is not necessarily ordered by binding index. So don't198/// write code like199/// `render_light_probes[cubemap_to_binding_index[asset_id]]`; instead200/// search for the light probe with the appropriate binding index in this201/// array.202render_light_probes: Vec<RenderLightProbe>,203204/// Information needed to render the light probe attached directly to the205/// view, if applicable.206///207/// A light probe attached directly to a view represents a "global" light208/// probe that affects all objects not in the bounding region of any light209/// probe. Currently, the only light probe type that supports this is the210/// [`EnvironmentMapLight`].211view_light_probe_info: C::ViewLightProbeInfo,212}213214/// A trait implemented by all components that represent light probes.215///216/// Currently, the two light probe types are [`EnvironmentMapLight`] and217/// [`IrradianceVolume`], for reflection probes and irradiance volumes218/// respectively.219///220/// Most light probe systems are written to be generic over the type of light221/// probe. This allows much of the code to be shared and enables easy addition222/// of more light probe types (e.g. real-time reflection planes) in the future.223pub trait LightProbeComponent: Send + Sync + Component + Sized {224/// Holds [`AssetId`]s of the texture or textures that this light probe225/// references.226///227/// This can just be [`AssetId`] if the light probe only references one228/// texture. If it references multiple textures, it will be a structure229/// containing those asset IDs.230type AssetId: Send + Sync + Clone + Eq + Hash;231232/// If the light probe can be attached to the view itself (as opposed to a233/// cuboid region within the scene), this contains the information that will234/// be passed to the GPU in order to render it. Otherwise, this will be235/// `()`.236///237/// Currently, only reflection probes (i.e. [`EnvironmentMapLight`]) can be238/// attached directly to views.239type ViewLightProbeInfo: Send + Sync + Default;240241/// Returns the asset ID or asset IDs of the texture or textures referenced242/// by this light probe.243fn id(&self, image_assets: &RenderAssets<GpuImage>) -> Option<Self::AssetId>;244245/// Returns the intensity of this light probe.246///247/// This is a scaling factor that will be multiplied by the value or values248/// sampled from the texture.249fn intensity(&self) -> f32;250251/// Returns true if this light probe contributes diffuse lighting to meshes252/// with lightmaps or false otherwise.253fn affects_lightmapped_mesh_diffuse(&self) -> bool;254255/// Creates an instance of [`RenderViewLightProbes`] containing all the256/// information needed to render this light probe.257///258/// This is called for every light probe in view every frame.259fn create_render_view_light_probes(260view_component: Option<&Self>,261image_assets: &RenderAssets<GpuImage>,262) -> RenderViewLightProbes<Self>;263}264265/// The uniform struct extracted from [`EnvironmentMapLight`].266/// Will be available for use in the Environment Map shader.267#[derive(Component, ShaderType, Clone)]268pub struct EnvironmentMapUniform {269/// The world space transformation matrix of the sample ray for environment cubemaps.270transform: Mat4,271}272273impl Default for EnvironmentMapUniform {274fn default() -> Self {275EnvironmentMapUniform {276transform: Mat4::IDENTITY,277}278}279}280281/// A GPU buffer that stores the environment map settings for each view.282#[derive(Resource, Default, Deref, DerefMut)]283pub struct EnvironmentMapUniformBuffer(pub DynamicUniformBuffer<EnvironmentMapUniform>);284285/// A component that stores the offset within the286/// [`EnvironmentMapUniformBuffer`] for each view.287#[derive(Component, Default, Deref, DerefMut)]288pub struct ViewEnvironmentMapUniformOffset(u32);289290impl Plugin for LightProbePlugin {291fn build(&self, app: &mut App) {292load_shader_library!(app, "light_probe.wgsl");293load_shader_library!(app, "environment_map.wgsl");294load_shader_library!(app, "irradiance_volume.wgsl");295296app.add_plugins((297EnvironmentMapGenerationPlugin,298ExtractInstancesPlugin::<EnvironmentMapIds>::new(),299));300301let Some(render_app) = app.get_sub_app_mut(RenderApp) else {302return;303};304305render_app306.init_resource::<LightProbesBuffer>()307.init_resource::<EnvironmentMapUniformBuffer>()308.add_systems(ExtractSchedule, gather_environment_map_uniform)309.add_systems(ExtractSchedule, gather_light_probes::<EnvironmentMapLight>)310.add_systems(ExtractSchedule, gather_light_probes::<IrradianceVolume>)311.add_systems(312Render,313(upload_light_probes, prepare_environment_uniform_buffer)314.in_set(RenderSystems::PrepareResources),315);316}317}318319/// Extracts [`EnvironmentMapLight`] from views and creates [`EnvironmentMapUniform`] for them.320///321/// Compared to the `ExtractComponentPlugin`, this implementation will create a default instance322/// if one does not already exist.323fn gather_environment_map_uniform(324view_query: Extract<Query<(RenderEntity, Option<&EnvironmentMapLight>), With<Camera3d>>>,325mut commands: Commands,326) {327for (view_entity, environment_map_light) in view_query.iter() {328let environment_map_uniform = if let Some(environment_map_light) = environment_map_light {329EnvironmentMapUniform {330transform: Transform::from_rotation(environment_map_light.rotation)331.to_matrix()332.inverse(),333}334} else {335EnvironmentMapUniform::default()336};337commands338.get_entity(view_entity)339.expect("Environment map light entity wasn't synced.")340.insert(environment_map_uniform);341}342}343344/// Gathers up all light probes of a single type in the scene and assigns them345/// to views, performing frustum culling and distance sorting in the process.346fn gather_light_probes<C>(347image_assets: Res<RenderAssets<GpuImage>>,348light_probe_query: Extract<Query<(&GlobalTransform, &C), With<LightProbe>>>,349view_query: Extract<350Query<(RenderEntity, &GlobalTransform, &Frustum, Option<&C>), With<Camera3d>>,351>,352mut reflection_probes: Local<Vec<LightProbeInfo<C>>>,353mut view_reflection_probes: Local<Vec<LightProbeInfo<C>>>,354mut commands: Commands,355) where356C: LightProbeComponent,357{358// Create [`LightProbeInfo`] for every light probe in the scene.359reflection_probes.clear();360reflection_probes.extend(361light_probe_query362.iter()363.filter_map(|query_row| LightProbeInfo::new(query_row, &image_assets)),364);365// Build up the light probes uniform and the key table.366for (view_entity, view_transform, view_frustum, view_component) in view_query.iter() {367// Cull light probes outside the view frustum.368view_reflection_probes.clear();369view_reflection_probes.extend(370reflection_probes371.iter()372.filter(|light_probe_info| light_probe_info.frustum_cull(view_frustum))373.cloned(),374);375376// Sort by distance to camera.377view_reflection_probes.sort_by_cached_key(|light_probe_info| {378light_probe_info.camera_distance_sort_key(view_transform)379});380381// Create the light probes list.382let mut render_view_light_probes =383C::create_render_view_light_probes(view_component, &image_assets);384385// Gather up the light probes in the list.386render_view_light_probes.maybe_gather_light_probes(&view_reflection_probes);387388// Record the per-view light probes.389if render_view_light_probes.is_empty() {390commands391.get_entity(view_entity)392.expect("View entity wasn't synced.")393.remove::<RenderViewLightProbes<C>>();394} else {395commands396.get_entity(view_entity)397.expect("View entity wasn't synced.")398.insert(render_view_light_probes);399}400}401}402403/// Gathers up environment map settings for each applicable view and404/// writes them into a GPU buffer.405pub fn prepare_environment_uniform_buffer(406mut commands: Commands,407views: Query<(Entity, Option<&EnvironmentMapUniform>), With<ExtractedView>>,408mut environment_uniform_buffer: ResMut<EnvironmentMapUniformBuffer>,409render_device: Res<RenderDevice>,410render_queue: Res<RenderQueue>,411) {412let Some(mut writer) =413environment_uniform_buffer.get_writer(views.iter().len(), &render_device, &render_queue)414else {415return;416};417418for (view, environment_uniform) in views.iter() {419let uniform_offset = match environment_uniform {420None => 0,421Some(environment_uniform) => writer.write(environment_uniform),422};423commands424.entity(view)425.insert(ViewEnvironmentMapUniformOffset(uniform_offset));426}427}428429// A system that runs after [`gather_light_probes`] and populates the GPU430// uniforms with the results.431//432// Note that, unlike [`gather_light_probes`], this system is not generic over433// the type of light probe. It collects light probes of all types together into434// a single structure, ready to be passed to the shader.435fn upload_light_probes(436mut commands: Commands,437views: Query<Entity, With<ExtractedView>>,438mut light_probes_buffer: ResMut<LightProbesBuffer>,439mut view_light_probes_query: Query<(440Option<&RenderViewLightProbes<EnvironmentMapLight>>,441Option<&RenderViewLightProbes<IrradianceVolume>>,442)>,443render_device: Res<RenderDevice>,444render_queue: Res<RenderQueue>,445) {446// If there are no views, bail.447if views.is_empty() {448return;449}450451// Initialize the uniform buffer writer.452let mut writer = light_probes_buffer453.get_writer(views.iter().len(), &render_device, &render_queue)454.unwrap();455456// Process each view.457for view_entity in views.iter() {458let Ok((render_view_environment_maps, render_view_irradiance_volumes)) =459view_light_probes_query.get_mut(view_entity)460else {461error!("Failed to find `RenderViewLightProbes` for the view!");462continue;463};464465// Initialize the uniform with only the view environment map, if there466// is one.467let mut light_probes_uniform = LightProbesUniform {468reflection_probes: [RenderLightProbe::default(); MAX_VIEW_LIGHT_PROBES],469irradiance_volumes: [RenderLightProbe::default(); MAX_VIEW_LIGHT_PROBES],470reflection_probe_count: render_view_environment_maps471.map(RenderViewLightProbes::len)472.unwrap_or_default()473.min(MAX_VIEW_LIGHT_PROBES) as i32,474irradiance_volume_count: render_view_irradiance_volumes475.map(RenderViewLightProbes::len)476.unwrap_or_default()477.min(MAX_VIEW_LIGHT_PROBES) as i32,478view_cubemap_index: render_view_environment_maps479.map(|maps| maps.view_light_probe_info.cubemap_index)480.unwrap_or(-1),481smallest_specular_mip_level_for_view: render_view_environment_maps482.map(|maps| maps.view_light_probe_info.smallest_specular_mip_level)483.unwrap_or(0),484intensity_for_view: render_view_environment_maps485.map(|maps| maps.view_light_probe_info.intensity)486.unwrap_or(1.0),487view_environment_map_affects_lightmapped_mesh_diffuse: render_view_environment_maps488.map(|maps| maps.view_light_probe_info.affects_lightmapped_mesh_diffuse as u32)489.unwrap_or(1),490};491492// Add any environment maps that [`gather_light_probes`] found to the493// uniform.494if let Some(render_view_environment_maps) = render_view_environment_maps {495render_view_environment_maps.add_to_uniform(496&mut light_probes_uniform.reflection_probes,497&mut light_probes_uniform.reflection_probe_count,498);499}500501// Add any irradiance volumes that [`gather_light_probes`] found to the502// uniform.503if let Some(render_view_irradiance_volumes) = render_view_irradiance_volumes {504render_view_irradiance_volumes.add_to_uniform(505&mut light_probes_uniform.irradiance_volumes,506&mut light_probes_uniform.irradiance_volume_count,507);508}509510// Queue the view's uniforms to be written to the GPU.511let uniform_offset = writer.write(&light_probes_uniform);512513commands514.entity(view_entity)515.insert(ViewLightProbesUniformOffset(uniform_offset));516}517}518519impl Default for LightProbesUniform {520fn default() -> Self {521Self {522reflection_probes: [RenderLightProbe::default(); MAX_VIEW_LIGHT_PROBES],523irradiance_volumes: [RenderLightProbe::default(); MAX_VIEW_LIGHT_PROBES],524reflection_probe_count: 0,525irradiance_volume_count: 0,526view_cubemap_index: -1,527smallest_specular_mip_level_for_view: 0,528intensity_for_view: 1.0,529view_environment_map_affects_lightmapped_mesh_diffuse: 1,530}531}532}533534impl<C> LightProbeInfo<C>535where536C: LightProbeComponent,537{538/// Given the set of light probe components, constructs and returns539/// [`LightProbeInfo`]. This is done for every light probe in the scene540/// every frame.541fn new(542(light_probe_transform, environment_map): (&GlobalTransform, &C),543image_assets: &RenderAssets<GpuImage>,544) -> Option<LightProbeInfo<C>> {545let light_from_world_transposed =546Mat4::from(light_probe_transform.affine().inverse()).transpose();547environment_map.id(image_assets).map(|id| LightProbeInfo {548world_from_light: light_probe_transform.affine(),549light_from_world: [550light_from_world_transposed.x_axis,551light_from_world_transposed.y_axis,552light_from_world_transposed.z_axis,553],554asset_id: id,555intensity: environment_map.intensity(),556affects_lightmapped_mesh_diffuse: environment_map.affects_lightmapped_mesh_diffuse(),557})558}559560/// Returns true if this light probe is in the viewing frustum of the camera561/// or false if it isn't.562fn frustum_cull(&self, view_frustum: &Frustum) -> bool {563view_frustum.intersects_obb(564&Aabb {565center: Vec3A::default(),566half_extents: Vec3A::splat(0.5),567},568&self.world_from_light,569true,570false,571)572}573574/// Returns the squared distance from this light probe to the camera,575/// suitable for distance sorting.576fn camera_distance_sort_key(&self, view_transform: &GlobalTransform) -> FloatOrd {577FloatOrd(578(self.world_from_light.translation - view_transform.translation_vec3a())579.length_squared(),580)581}582}583584impl<C> RenderViewLightProbes<C>585where586C: LightProbeComponent,587{588/// Creates a new empty list of light probes.589fn new() -> RenderViewLightProbes<C> {590RenderViewLightProbes {591binding_index_to_textures: vec![],592cubemap_to_binding_index: HashMap::default(),593render_light_probes: vec![],594view_light_probe_info: C::ViewLightProbeInfo::default(),595}596}597598/// Returns true if there are no light probes in the list.599pub(crate) fn is_empty(&self) -> bool {600self.binding_index_to_textures.is_empty()601}602603/// Returns the number of light probes in the list.604pub(crate) fn len(&self) -> usize {605self.binding_index_to_textures.len()606}607608/// Adds a cubemap to the list of bindings, if it wasn't there already, and609/// returns its index within that list.610pub(crate) fn get_or_insert_cubemap(&mut self, cubemap_id: &C::AssetId) -> u32 {611*self612.cubemap_to_binding_index613.entry((*cubemap_id).clone())614.or_insert_with(|| {615let index = self.binding_index_to_textures.len() as u32;616self.binding_index_to_textures.push((*cubemap_id).clone());617index618})619}620621/// Adds all the light probes in this structure to the supplied array, which622/// is expected to be shipped to the GPU.623fn add_to_uniform(624&self,625render_light_probes: &mut [RenderLightProbe; MAX_VIEW_LIGHT_PROBES],626render_light_probe_count: &mut i32,627) {628render_light_probes[0..self.render_light_probes.len()]629.copy_from_slice(&self.render_light_probes[..]);630*render_light_probe_count = self.render_light_probes.len() as i32;631}632633/// Gathers up all light probes of the given type in the scene and records634/// them in this structure.635fn maybe_gather_light_probes(&mut self, light_probes: &[LightProbeInfo<C>]) {636for light_probe in light_probes.iter().take(MAX_VIEW_LIGHT_PROBES) {637// Determine the index of the cubemap in the binding array.638let cubemap_index = self.get_or_insert_cubemap(&light_probe.asset_id);639640// Write in the light probe data.641self.render_light_probes.push(RenderLightProbe {642light_from_world_transposed: light_probe.light_from_world,643texture_index: cubemap_index as i32,644intensity: light_probe.intensity,645affects_lightmapped_mesh_diffuse: light_probe.affects_lightmapped_mesh_diffuse646as u32,647});648}649}650}651652impl<C> Clone for LightProbeInfo<C>653where654C: LightProbeComponent,655{656fn clone(&self) -> Self {657Self {658light_from_world: self.light_from_world,659world_from_light: self.world_from_light,660intensity: self.intensity,661affects_lightmapped_mesh_diffuse: self.affects_lightmapped_mesh_diffuse,662asset_id: self.asset_id.clone(),663}664}665}666667/// Adds a diffuse or specular texture view to the `texture_views` list, and668/// populates `sampler` if this is the first such view.669pub(crate) fn add_cubemap_texture_view<'a>(670texture_views: &mut Vec<&'a <TextureView as Deref>::Target>,671sampler: &mut Option<&'a Sampler>,672image_id: AssetId<Image>,673images: &'a RenderAssets<GpuImage>,674fallback_image: &'a FallbackImage,675) {676match images.get(image_id) {677None => {678// Use the fallback image if the cubemap isn't loaded yet.679texture_views.push(&*fallback_image.cube.texture_view);680}681Some(image) => {682// If this is the first texture view, populate `sampler`.683if sampler.is_none() {684*sampler = Some(&image.sampler);685}686687texture_views.push(&*image.texture_view);688}689}690}691692/// Many things can go wrong when attempting to use texture binding arrays693/// (a.k.a. bindless textures). This function checks for these pitfalls:694///695/// 1. If GLSL support is enabled at the feature level, then in debug mode696/// `naga_oil` will attempt to compile all shader modules under GLSL to check697/// validity of names, even if GLSL isn't actually used. This will cause a crash698/// if binding arrays are enabled, because binding arrays are currently699/// unimplemented in the GLSL backend of Naga. Therefore, we disable binding700/// arrays if the `shader_format_glsl` feature is present.701///702/// 2. If there aren't enough texture bindings available to accommodate all the703/// binding arrays, the driver will panic. So we also bail out if there aren't704/// enough texture bindings available in the fragment shader.705///706/// 3. If binding arrays aren't supported on the hardware, then we obviously707/// can't use them. Adreno <= 610 claims to support bindless, but seems to be708/// too buggy to be usable.709///710/// 4. If binding arrays are supported on the hardware, but they can only be711/// accessed by uniform indices, that's not good enough, and we bail out.712///713/// If binding arrays aren't usable, we disable reflection probes and limit the714/// number of irradiance volumes in the scene to 1.715pub(crate) fn binding_arrays_are_usable(716render_device: &RenderDevice,717render_adapter: &RenderAdapter,718) -> bool {719let adapter_info = RenderAdapterInfo(WgpuWrapper::new(render_adapter.get_info()));720721!cfg!(feature = "shader_format_glsl")722&& bevy_render::get_adreno_model(&adapter_info).is_none_or(|model| model > 610)723&& render_device.limits().max_storage_textures_per_shader_stage724>= (STANDARD_MATERIAL_FRAGMENT_SHADER_MIN_TEXTURE_BINDINGS + MAX_VIEW_LIGHT_PROBES)725as u32726&& render_device.features().contains(727WgpuFeatures::TEXTURE_BINDING_ARRAY728| WgpuFeatures::SAMPLED_TEXTURE_AND_STORAGE_BUFFER_ARRAY_NON_UNIFORM_INDEXING,729)730}731732733