use std::{array, num::NonZeroU64, sync::Arc}; use bevy_core_pipeline::{ core_3d::ViewTransmissionTexture, prepass::ViewPrepassTextures, tonemapping::{ get_lut_bind_group_layout_entries, get_lut_bindings, Tonemapping, TonemappingLuts, }, }; use bevy_derive::{Deref, DerefMut}; use bevy_ecs::{ component::Component, entity::Entity, system::{Commands, Query, Res, Resource}, world::{FromWorld, World}, }; use bevy_math::Vec4; use bevy_render::{ globals::{GlobalsBuffer, GlobalsUniform}, render_asset::RenderAssets, render_resource::{binding_types::*, *}, renderer::RenderDevice, texture::{BevyDefault, FallbackImage, FallbackImageMsaa, FallbackImageZero, GpuImage}, view::{ Msaa, RenderVisibilityRanges, ViewUniform, ViewUniforms, VISIBILITY_RANGES_STORAGE_BUFFER_COUNT, }, }; #[cfg(all(feature = "webgl", target_arch = "wasm32", not(feature = "webgpu")))] use bevy_render::render_resource::binding_types::texture_cube; #[cfg(debug_assertions)] use bevy_utils::warn_once; use environment_map::EnvironmentMapLight; #[cfg(debug_assertions)] use crate::MESH_PIPELINE_VIEW_LAYOUT_SAFE_MAX_TEXTURES; use crate::{ environment_map::{self, RenderViewEnvironmentMapBindGroupEntries}, irradiance_volume::{ self, IrradianceVolume, RenderViewIrradianceVolumeBindGroupEntries, IRRADIANCE_VOLUMES_ARE_USABLE, }, prepass, EnvironmentMapUniformBuffer, FogMeta, GlobalClusterableObjectMeta, GpuClusterableObjects, GpuFog, GpuLights, LightMeta, LightProbesBuffer, LightProbesUniform, MeshPipeline, MeshPipelineKey, RenderViewLightProbes, ScreenSpaceAmbientOcclusionTextures, ScreenSpaceReflectionsBuffer, ScreenSpaceReflectionsUniform, ShadowSamplers, ViewClusterBindings, ViewShadowBindings, CLUSTERED_FORWARD_STORAGE_BUFFER_COUNT, }; #[derive(Clone)] pub struct MeshPipelineViewLayout { pub bind_group_layout: BindGroupLayout, #[cfg(debug_assertions)] pub texture_count: usize, } bitflags::bitflags! { /// A key that uniquely identifies a [`MeshPipelineViewLayout`]. /// /// Used to generate all possible layouts for the mesh pipeline in [`generate_view_layouts`], /// so special care must be taken to not add too many flags, as the number of possible layouts /// will grow exponentially. #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] #[repr(transparent)] pub struct MeshPipelineViewLayoutKey: u32 { const MULTISAMPLED = 1 << 0; const DEPTH_PREPASS = 1 << 1; const NORMAL_PREPASS = 1 << 2; const MOTION_VECTOR_PREPASS = 1 << 3; const DEFERRED_PREPASS = 1 << 4; } } impl MeshPipelineViewLayoutKey { // The number of possible layouts pub const COUNT: usize = Self::all().bits() as usize + 1; /// Builds a unique label for each layout based on the flags pub fn label(&self) -> String { use MeshPipelineViewLayoutKey as Key; format!( "mesh_view_layout{}{}{}{}{}", self.contains(Key::MULTISAMPLED) .then_some("_multisampled") .unwrap_or_default(), self.contains(Key::DEPTH_PREPASS) .then_some("_depth") .unwrap_or_default(), self.contains(Key::NORMAL_PREPASS) .then_some("_normal") .unwrap_or_default(), self.contains(Key::MOTION_VECTOR_PREPASS) .then_some("_motion") .unwrap_or_default(), self.contains(Key::DEFERRED_PREPASS) .then_some("_deferred") .unwrap_or_default(), ) } } impl From for MeshPipelineViewLayoutKey { fn from(value: MeshPipelineKey) -> Self { let mut result = MeshPipelineViewLayoutKey::empty(); if value.msaa_samples() > 1 { result |= MeshPipelineViewLayoutKey::MULTISAMPLED; } if value.contains(MeshPipelineKey::DEPTH_PREPASS) { result |= MeshPipelineViewLayoutKey::DEPTH_PREPASS; } if value.contains(MeshPipelineKey::NORMAL_PREPASS) { result |= MeshPipelineViewLayoutKey::NORMAL_PREPASS; } if value.contains(MeshPipelineKey::MOTION_VECTOR_PREPASS) { result |= MeshPipelineViewLayoutKey::MOTION_VECTOR_PREPASS; } if value.contains(MeshPipelineKey::DEFERRED_PREPASS) { result |= MeshPipelineViewLayoutKey::DEFERRED_PREPASS; } result } } impl From for MeshPipelineViewLayoutKey { fn from(value: Msaa) -> Self { let mut result = MeshPipelineViewLayoutKey::empty(); if value.samples() > 1 { result |= MeshPipelineViewLayoutKey::MULTISAMPLED; } result } } impl From> for MeshPipelineViewLayoutKey { fn from(value: Option<&ViewPrepassTextures>) -> Self { let mut result = MeshPipelineViewLayoutKey::empty(); if let Some(prepass_textures) = value { if prepass_textures.depth.is_some() { result |= MeshPipelineViewLayoutKey::DEPTH_PREPASS; } if prepass_textures.normal.is_some() { result |= MeshPipelineViewLayoutKey::NORMAL_PREPASS; } if prepass_textures.motion_vectors.is_some() { result |= MeshPipelineViewLayoutKey::MOTION_VECTOR_PREPASS; } if prepass_textures.deferred.is_some() { result |= MeshPipelineViewLayoutKey::DEFERRED_PREPASS; } } result } } fn buffer_layout( buffer_binding_type: BufferBindingType, has_dynamic_offset: bool, min_binding_size: Option, ) -> BindGroupLayoutEntryBuilder { match buffer_binding_type { BufferBindingType::Uniform => uniform_buffer_sized(has_dynamic_offset, min_binding_size), BufferBindingType::Storage { read_only } => { if read_only { storage_buffer_read_only_sized(has_dynamic_offset, min_binding_size) } else { storage_buffer_sized(has_dynamic_offset, min_binding_size) } } } } /// Returns the appropriate bind group layout vec based on the parameters fn layout_entries( clustered_forward_buffer_binding_type: BufferBindingType, visibility_ranges_buffer_binding_type: BufferBindingType, layout_key: MeshPipelineViewLayoutKey, render_device: &RenderDevice, ) -> Vec { let mut entries = DynamicBindGroupLayoutEntries::new_with_indices( ShaderStages::FRAGMENT, ( // View ( 0, uniform_buffer::(true).visibility(ShaderStages::VERTEX_FRAGMENT), ), // Lights (1, uniform_buffer::(true)), // Point Shadow Texture Cube Array ( 2, #[cfg(all( not(feature = "ios_simulator"), any( not(feature = "webgl"), not(target_arch = "wasm32"), feature = "webgpu" ) ))] texture_cube_array(TextureSampleType::Depth), #[cfg(any( feature = "ios_simulator", all(feature = "webgl", target_arch = "wasm32", not(feature = "webgpu")) ))] texture_cube(TextureSampleType::Depth), ), // Point Shadow Texture Array Sampler (3, sampler(SamplerBindingType::Comparison)), // Directional Shadow Texture Array ( 4, #[cfg(any( not(feature = "webgl"), not(target_arch = "wasm32"), feature = "webgpu" ))] texture_2d_array(TextureSampleType::Depth), #[cfg(all(feature = "webgl", target_arch = "wasm32", not(feature = "webgpu")))] texture_2d(TextureSampleType::Depth), ), // Directional Shadow Texture Array Sampler (5, sampler(SamplerBindingType::Comparison)), // ClusterableObjects ( 6, buffer_layout( clustered_forward_buffer_binding_type, false, Some(GpuClusterableObjects::min_size( clustered_forward_buffer_binding_type, )), ), ), // ClusteredLightIndexLists ( 7, buffer_layout( clustered_forward_buffer_binding_type, false, Some( ViewClusterBindings::min_size_clusterable_object_index_lists( clustered_forward_buffer_binding_type, ), ), ), ), // ClusterOffsetsAndCounts ( 8, buffer_layout( clustered_forward_buffer_binding_type, false, Some(ViewClusterBindings::min_size_cluster_offsets_and_counts( clustered_forward_buffer_binding_type, )), ), ), // Globals ( 9, uniform_buffer::(false).visibility(ShaderStages::VERTEX_FRAGMENT), ), // Fog (10, uniform_buffer::(true)), // Light probes (11, uniform_buffer::(true)), // Visibility ranges ( 12, buffer_layout( visibility_ranges_buffer_binding_type, false, Some(Vec4::min_size()), ) .visibility(ShaderStages::VERTEX), ), // Screen space reflection settings (13, uniform_buffer::(true)), // Screen space ambient occlusion texture ( 14, texture_2d(TextureSampleType::Float { filterable: false }), ), ), ); // EnvironmentMapLight let environment_map_entries = environment_map::get_bind_group_layout_entries(render_device); entries = entries.extend_with_indices(( (15, environment_map_entries[0]), (16, environment_map_entries[1]), (17, environment_map_entries[2]), (18, environment_map_entries[3]), )); // Irradiance volumes if IRRADIANCE_VOLUMES_ARE_USABLE { let irradiance_volume_entries = irradiance_volume::get_bind_group_layout_entries(render_device); entries = entries.extend_with_indices(( (19, irradiance_volume_entries[0]), (20, irradiance_volume_entries[1]), )); } // Tonemapping let tonemapping_lut_entries = get_lut_bind_group_layout_entries(); entries = entries.extend_with_indices(( (21, tonemapping_lut_entries[0]), (22, tonemapping_lut_entries[1]), )); // Prepass if cfg!(any(not(feature = "webgl"), not(target_arch = "wasm32"))) || (cfg!(all(feature = "webgl", target_arch = "wasm32")) && !layout_key.contains(MeshPipelineViewLayoutKey::MULTISAMPLED)) { for (entry, binding) in prepass::get_bind_group_layout_entries(layout_key) .iter() .zip([23, 24, 25, 26]) { if let Some(entry) = entry { entries = entries.extend_with_indices(((binding as u32, *entry),)); } } } // View Transmission Texture entries = entries.extend_with_indices(( ( 27, texture_2d(TextureSampleType::Float { filterable: true }), ), (28, sampler(SamplerBindingType::Filtering)), )); entries.to_vec() } /// Stores the view layouts for every combination of pipeline keys. /// /// This is wrapped in an [`Arc`] so that it can be efficiently cloned and /// placed inside specializable pipeline types. #[derive(Resource, Clone, Deref, DerefMut)] pub struct MeshPipelineViewLayouts( pub Arc<[MeshPipelineViewLayout; MeshPipelineViewLayoutKey::COUNT]>, ); impl FromWorld for MeshPipelineViewLayouts { fn from_world(world: &mut World) -> Self { // Generates all possible view layouts for the mesh pipeline, based on all combinations of // [`MeshPipelineViewLayoutKey`] flags. let render_device = world.resource::(); let clustered_forward_buffer_binding_type = render_device .get_supported_read_only_binding_type(CLUSTERED_FORWARD_STORAGE_BUFFER_COUNT); let visibility_ranges_buffer_binding_type = render_device .get_supported_read_only_binding_type(VISIBILITY_RANGES_STORAGE_BUFFER_COUNT); Self(Arc::new(array::from_fn(|i| { let key = MeshPipelineViewLayoutKey::from_bits_truncate(i as u32); let entries = layout_entries( clustered_forward_buffer_binding_type, visibility_ranges_buffer_binding_type, key, render_device, ); #[cfg(debug_assertions)] let texture_count: usize = entries .iter() .filter(|entry| matches!(entry.ty, BindingType::Texture { .. })) .count(); MeshPipelineViewLayout { bind_group_layout: render_device .create_bind_group_layout(key.label().as_str(), &entries), #[cfg(debug_assertions)] texture_count, } }))) } } impl MeshPipelineViewLayouts { pub fn get_view_layout(&self, layout_key: MeshPipelineViewLayoutKey) -> &BindGroupLayout { let index = layout_key.bits() as usize; let layout = &self[index]; #[cfg(debug_assertions)] if layout.texture_count > MESH_PIPELINE_VIEW_LAYOUT_SAFE_MAX_TEXTURES { // Issue our own warning here because Naga's error message is a bit cryptic in this situation warn_once!("Too many textures in mesh pipeline view layout, this might cause us to hit `wgpu::Limits::max_sampled_textures_per_shader_stage` in some environments."); } &layout.bind_group_layout } } /// Generates all possible view layouts for the mesh pipeline, based on all combinations of /// [`MeshPipelineViewLayoutKey`] flags. pub fn generate_view_layouts( render_device: &RenderDevice, clustered_forward_buffer_binding_type: BufferBindingType, visibility_ranges_buffer_binding_type: BufferBindingType, ) -> [MeshPipelineViewLayout; MeshPipelineViewLayoutKey::COUNT] { array::from_fn(|i| { let key = MeshPipelineViewLayoutKey::from_bits_truncate(i as u32); let entries = layout_entries( clustered_forward_buffer_binding_type, visibility_ranges_buffer_binding_type, key, render_device, ); #[cfg(debug_assertions)] let texture_count: usize = entries .iter() .filter(|entry| matches!(entry.ty, BindingType::Texture { .. })) .count(); MeshPipelineViewLayout { bind_group_layout: render_device .create_bind_group_layout(key.label().as_str(), &entries), #[cfg(debug_assertions)] texture_count, } }) } #[derive(Component)] pub struct MeshViewBindGroup { pub value: BindGroup, } #[allow(clippy::too_many_arguments)] pub fn prepare_mesh_view_bind_groups( mut commands: Commands, render_device: Res, mesh_pipeline: Res, shadow_samplers: Res, light_meta: Res, global_light_meta: Res, fog_meta: Res, (view_uniforms, environment_map_uniform): (Res, Res), views: Query<( Entity, &ViewShadowBindings, &ViewClusterBindings, &Msaa, Option<&ScreenSpaceAmbientOcclusionTextures>, Option<&ViewPrepassTextures>, Option<&ViewTransmissionTexture>, &Tonemapping, Option<&RenderViewLightProbes>, Option<&RenderViewLightProbes>, )>, (images, mut fallback_images, fallback_image, fallback_image_zero): ( Res>, FallbackImageMsaa, Res, Res, ), globals_buffer: Res, tonemapping_luts: Res, light_probes_buffer: Res, visibility_ranges: Res, ssr_buffer: Res, ) { if let ( Some(view_binding), Some(light_binding), Some(clusterable_objects_binding), Some(globals), Some(fog_binding), Some(light_probes_binding), Some(visibility_ranges_buffer), Some(ssr_binding), Some(environment_map_binding), ) = ( view_uniforms.uniforms.binding(), light_meta.view_gpu_lights.binding(), global_light_meta.gpu_clusterable_objects.binding(), globals_buffer.buffer.binding(), fog_meta.gpu_fogs.binding(), light_probes_buffer.binding(), visibility_ranges.buffer().buffer(), ssr_buffer.binding(), environment_map_uniform.binding(), ) { for ( entity, shadow_bindings, cluster_bindings, msaa, ssao_textures, prepass_textures, transmission_texture, tonemapping, render_view_environment_maps, render_view_irradiance_volumes, ) in &views { let fallback_ssao = fallback_images .image_for_samplecount(1, TextureFormat::bevy_default()) .texture_view .clone(); let ssao_view = ssao_textures .map(|t| &t.screen_space_ambient_occlusion_texture.default_view) .unwrap_or(&fallback_ssao); let layout = &mesh_pipeline.get_view_layout( MeshPipelineViewLayoutKey::from(*msaa) | MeshPipelineViewLayoutKey::from(prepass_textures), ); let mut entries = DynamicBindGroupEntries::new_with_indices(( (0, view_binding.clone()), (1, light_binding.clone()), (2, &shadow_bindings.point_light_depth_texture_view), (3, &shadow_samplers.point_light_sampler), (4, &shadow_bindings.directional_light_depth_texture_view), (5, &shadow_samplers.directional_light_sampler), (6, clusterable_objects_binding.clone()), ( 7, cluster_bindings .clusterable_object_index_lists_binding() .unwrap(), ), (8, cluster_bindings.offsets_and_counts_binding().unwrap()), (9, globals.clone()), (10, fog_binding.clone()), (11, light_probes_binding.clone()), (12, visibility_ranges_buffer.as_entire_binding()), (13, ssr_binding.clone()), (14, ssao_view), )); let environment_map_bind_group_entries = RenderViewEnvironmentMapBindGroupEntries::get( render_view_environment_maps, &images, &fallback_image, &render_device, ); match environment_map_bind_group_entries { RenderViewEnvironmentMapBindGroupEntries::Single { diffuse_texture_view, specular_texture_view, sampler, } => { entries = entries.extend_with_indices(( (15, diffuse_texture_view), (16, specular_texture_view), (17, sampler), (18, environment_map_binding.clone()), )); } RenderViewEnvironmentMapBindGroupEntries::Multiple { ref diffuse_texture_views, ref specular_texture_views, sampler, } => { entries = entries.extend_with_indices(( (15, diffuse_texture_views.as_slice()), (16, specular_texture_views.as_slice()), (17, sampler), (18, environment_map_binding.clone()), )); } } let irradiance_volume_bind_group_entries = if IRRADIANCE_VOLUMES_ARE_USABLE { Some(RenderViewIrradianceVolumeBindGroupEntries::get( render_view_irradiance_volumes, &images, &fallback_image, &render_device, )) } else { None }; match irradiance_volume_bind_group_entries { Some(RenderViewIrradianceVolumeBindGroupEntries::Single { texture_view, sampler, }) => { entries = entries.extend_with_indices(((19, texture_view), (20, sampler))); } Some(RenderViewIrradianceVolumeBindGroupEntries::Multiple { ref texture_views, sampler, }) => { entries = entries .extend_with_indices(((19, texture_views.as_slice()), (20, sampler))); } None => {} } let lut_bindings = get_lut_bindings(&images, &tonemapping_luts, tonemapping, &fallback_image); entries = entries.extend_with_indices(((21, lut_bindings.0), (22, lut_bindings.1))); // When using WebGL, we can't have a depth texture with multisampling let prepass_bindings; if cfg!(any(not(feature = "webgl"), not(target_arch = "wasm32"))) || msaa.samples() == 1 { prepass_bindings = prepass::get_bindings(prepass_textures); for (binding, index) in prepass_bindings .iter() .map(Option::as_ref) .zip([23, 24, 25, 26]) .flat_map(|(b, i)| b.map(|b| (b, i))) { entries = entries.extend_with_indices(((index, binding),)); } }; let transmission_view = transmission_texture .map(|transmission| &transmission.view) .unwrap_or(&fallback_image_zero.texture_view); let transmission_sampler = transmission_texture .map(|transmission| &transmission.sampler) .unwrap_or(&fallback_image_zero.sampler); entries = entries.extend_with_indices(((27, transmission_view), (28, transmission_sampler))); commands.entity(entity).insert(MeshViewBindGroup { value: render_device.create_bind_group("mesh_view_bind_group", layout, &entries), }); } } }