Skip to main content

bevy_pbr/render/
mesh_view_bindings.rs

1use crate::{
2    AreaLightLuts, DfgLut, ViewFogUniformOffset, ViewLightProbesUniformOffset,
3    ViewLightsUniformOffset, ViewScreenSpaceReflectionsUniformOffset,
4};
5use arrayvec::ArrayVec;
6use bevy_core_pipeline::{
7    oit::{
8        OitBuffers, OrderIndependentTransparencySettings,
9        OrderIndependentTransparencySettingsOffset,
10    },
11    prepass::ViewPrepassTextures,
12    tonemapping::{
13        get_lut_bind_group_layout_entries, get_lut_bindings, Tonemapping, TonemappingLuts,
14    },
15};
16use bevy_ecs::{
17    component::Component,
18    entity::Entity,
19    query::Has,
20    resource::Resource,
21    system::{Commands, Local, Query, Res},
22};
23use bevy_light::{EnvironmentMapLight, IrradianceVolume};
24use bevy_math::Vec4;
25use bevy_platform::sync::Arc;
26use bevy_render::{
27    camera::ExtractedCamera,
28    globals::{GlobalsBuffer, GlobalsUniform},
29    render_asset::RenderAssets,
30    render_resource::{binding_types::*, *},
31    renderer::{RenderAdapter, RenderDevice},
32    texture::{FallbackImage, FallbackImageZero, GpuImage},
33    view::{
34        Msaa, RenderVisibilityRanges, ViewUniform, ViewUniformOffset, ViewUniforms,
35        VISIBILITY_RANGES_STORAGE_BUFFER_COUNT,
36    },
37};
38use core::fmt::Write;
39use core::num::NonZero;
40
41use crate::{
42    contact_shadows::{
43        ContactShadowsBuffer, ContactShadowsUniform, ViewContactShadowsUniformOffset,
44    },
45    decal::{
46        self,
47        clustered::{
48            DecalsBuffer, RenderClusteredDecals, RenderViewClusteredDecalBindGroupEntries,
49        },
50    },
51    environment_map::{self, RenderViewEnvironmentMapBindGroupEntries},
52    irradiance_volume::{
53        self, RenderViewIrradianceVolumeBindGroupEntries, IRRADIANCE_VOLUMES_ARE_USABLE,
54    },
55    prepass,
56    resources::{AtmosphereBuffer, AtmosphereSampler, AtmosphereTextures, GpuAtmosphere},
57    Bluenoise, ExtractedAtmosphere, FogMeta, GlobalClusterableObjectMeta, GpuClusteredLights,
58    GpuFog, GpuLights, LightMeta, LightProbesBuffer, LightProbesUniform, MeshPipeline,
59    MeshPipelineKey, RenderViewLightProbes, ScreenSpaceAmbientOcclusionResources,
60    ScreenSpaceReflectionsBuffer, ScreenSpaceReflectionsUniform, ShadowSamplers,
61    ViewClusterBindings, ViewShadowBindings, ViewTransmissionTexture,
62    CLUSTERED_FORWARD_STORAGE_BUFFER_COUNT,
63};
64
65#[cfg(all(feature = "webgl", target_arch = "wasm32", not(feature = "webgpu")))]
66use bevy_render::render_resource::binding_types::texture_cube;
67
68#[cfg(debug_assertions)]
69use {crate::MESH_PIPELINE_VIEW_LAYOUT_SAFE_MAX_TEXTURES, bevy_utils::once, tracing::warn};
70
71pub const TONEMAPPING_LUT_TEXTURE_BINDING_INDEX: u32 = 18;
72pub const TONEMAPPING_LUT_SAMPLER_BINDING_INDEX: u32 = 19;
73
74#[derive(Clone)]
75pub struct MeshPipelineViewLayout {
76    pub main_layout: BindGroupLayoutDescriptor,
77    pub binding_array_layout: BindGroupLayoutDescriptor,
78    pub empty_layout: BindGroupLayoutDescriptor,
79}
80
81bitflags::bitflags! {
82    /// A key that uniquely identifies a [`MeshPipelineViewLayout`].
83    #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
84    #[repr(transparent)]
85    pub struct MeshPipelineViewLayoutKey: u32 {
86        const MULTISAMPLED                     = 1 << 0;
87        const DEPTH_PREPASS                    = 1 << 1;
88        const NORMAL_PREPASS                   = 1 << 2;
89        const MOTION_VECTOR_PREPASS            = 1 << 3;
90        const DEFERRED_PREPASS                 = 1 << 4;
91        const OIT_ENABLED                      = 1 << 5;
92        const ATMOSPHERE                       = 1 << 6;
93        const STBN                             = 1 << 7;
94        const TONEMAP_IN_SHADER                = 1 << 8;
95        const ENVIRONMENT_MAP                  = 1 << 9;
96        const SCREEN_SPACE_AMBIENT_OCCLUSION   = 1 << 10;
97        const IRRADIANCE_VOLUME                = 1 << 11;
98        const SCREEN_SPACE_REFLECTIONS         = 1 << 12;
99        const CONTACT_SHADOWS                  = 1 << 13;
100        const DISTANCE_FOG                     = 1 << 14;
101        const AREA_LIGHT_LUTS                  = 1 << 15;
102    }
103}
104
105impl MeshPipelineViewLayoutKey {
106    /// Builds a unique label for each layout based on the flags
107    pub fn label(&self) -> String {
108        let iter = self
109            .iter_names()
110            .filter(|(_, key)| self.contains(*key))
111            .map(|(name, _)| name.to_lowercase());
112        let (lower, _) = iter.size_hint();
113        let sep = ",";
114        let mut result = String::with_capacity(sep.len() * lower);
115
116        for (i, name) in iter.enumerate() {
117            if i != 0 {
118                result.push_str(sep);
119            }
120            write!(&mut result, "{}", name).unwrap();
121        }
122
123        format!("mesh_view_layout:{}", result)
124    }
125}
126
127impl From<MeshPipelineKey> for MeshPipelineViewLayoutKey {
128    fn from(value: MeshPipelineKey) -> Self {
129        let mut result = MeshPipelineViewLayoutKey::empty();
130
131        if value.msaa_samples() > 1 {
132            result |= MeshPipelineViewLayoutKey::MULTISAMPLED;
133        }
134        if value.contains(MeshPipelineKey::DEPTH_PREPASS) {
135            result |= MeshPipelineViewLayoutKey::DEPTH_PREPASS;
136        }
137        if value.contains(MeshPipelineKey::NORMAL_PREPASS) {
138            result |= MeshPipelineViewLayoutKey::NORMAL_PREPASS;
139        }
140        if value.contains(MeshPipelineKey::MOTION_VECTOR_PREPASS) {
141            result |= MeshPipelineViewLayoutKey::MOTION_VECTOR_PREPASS;
142        }
143        if value.contains(MeshPipelineKey::DEFERRED_PREPASS) {
144            result |= MeshPipelineViewLayoutKey::DEFERRED_PREPASS;
145        }
146        if value.contains(MeshPipelineKey::OIT_ENABLED) {
147            result |= MeshPipelineViewLayoutKey::OIT_ENABLED;
148        }
149        if value.contains(MeshPipelineKey::ATMOSPHERE) {
150            result |= MeshPipelineViewLayoutKey::ATMOSPHERE;
151        }
152
153        if cfg!(feature = "bluenoise_texture") {
154            result |= MeshPipelineViewLayoutKey::STBN;
155        }
156
157        if cfg!(feature = "area_light_luts") {
158            result |= MeshPipelineViewLayoutKey::AREA_LIGHT_LUTS;
159        }
160
161        if value.contains(MeshPipelineKey::TONEMAP_IN_SHADER) {
162            result |= MeshPipelineViewLayoutKey::TONEMAP_IN_SHADER;
163        }
164        if value.contains(MeshPipelineKey::ENVIRONMENT_MAP) {
165            result |= MeshPipelineViewLayoutKey::ENVIRONMENT_MAP;
166        }
167        if value.contains(MeshPipelineKey::SCREEN_SPACE_AMBIENT_OCCLUSION) {
168            result |= MeshPipelineViewLayoutKey::SCREEN_SPACE_AMBIENT_OCCLUSION;
169        }
170        if value.contains(MeshPipelineKey::IRRADIANCE_VOLUME) {
171            result |= MeshPipelineViewLayoutKey::IRRADIANCE_VOLUME;
172        }
173        if value.contains(MeshPipelineKey::SCREEN_SPACE_REFLECTIONS) {
174            result |= MeshPipelineViewLayoutKey::SCREEN_SPACE_REFLECTIONS;
175        }
176        if value.contains(MeshPipelineKey::CONTACT_SHADOWS) {
177            result |= MeshPipelineViewLayoutKey::CONTACT_SHADOWS;
178        }
179        if value.contains(MeshPipelineKey::DISTANCE_FOG) {
180            result |= MeshPipelineViewLayoutKey::DISTANCE_FOG;
181        }
182
183        result
184    }
185}
186
187impl From<Msaa> for MeshPipelineViewLayoutKey {
188    fn from(value: Msaa) -> Self {
189        let mut result = MeshPipelineViewLayoutKey::empty();
190
191        if value.samples() > 1 {
192            result |= MeshPipelineViewLayoutKey::MULTISAMPLED;
193        }
194
195        result
196    }
197}
198
199impl From<Option<&ViewPrepassTextures>> for MeshPipelineViewLayoutKey {
200    fn from(value: Option<&ViewPrepassTextures>) -> Self {
201        let mut result = MeshPipelineViewLayoutKey::empty();
202
203        if let Some(prepass_textures) = value {
204            if prepass_textures.depth.is_some() {
205                result |= MeshPipelineViewLayoutKey::DEPTH_PREPASS;
206            }
207            if prepass_textures.normal.is_some() {
208                result |= MeshPipelineViewLayoutKey::NORMAL_PREPASS;
209            }
210            if prepass_textures.motion_vectors.is_some() {
211                result |= MeshPipelineViewLayoutKey::MOTION_VECTOR_PREPASS;
212            }
213            if prepass_textures.deferred.is_some() {
214                result |= MeshPipelineViewLayoutKey::DEFERRED_PREPASS;
215            }
216        }
217
218        result
219    }
220}
221
222pub(crate) fn buffer_layout(
223    buffer_binding_type: BufferBindingType,
224    has_dynamic_offset: bool,
225    min_binding_size: Option<NonZero<u64>>,
226) -> BindGroupLayoutEntryBuilder {
227    match buffer_binding_type {
228        BufferBindingType::Uniform => uniform_buffer_sized(has_dynamic_offset, min_binding_size),
229        BufferBindingType::Storage { read_only } => {
230            if read_only {
231                storage_buffer_read_only_sized(has_dynamic_offset, min_binding_size)
232            } else {
233                storage_buffer_sized(has_dynamic_offset, min_binding_size)
234            }
235        }
236    }
237}
238
239/// Returns the appropriate bind group layout vec based on the parameters
240fn layout_entries(
241    layout_key: MeshPipelineViewLayoutKey,
242    &MeshPipelineViewLayoutParams {
243        clustered_forward_buffer_binding_type,
244        visibility_ranges_buffer_binding_type,
245        environment_map_entries,
246        irradiance_volume_entries,
247        clustered_decal_entries,
248        is_oit_supported,
249    }: &MeshPipelineViewLayoutParams,
250) -> [Vec<BindGroupLayoutEntry>; 2] {
251    let mut entries = DynamicBindGroupLayoutEntries::new_with_indices(
252        ShaderStages::FRAGMENT,
253        (
254            // View
255            (
256                0,
257                uniform_buffer::<ViewUniform>(true).visibility(ShaderStages::VERTEX_FRAGMENT),
258            ),
259            // Lights
260            (1, uniform_buffer::<GpuLights>(true)),
261            // Point Shadow Texture Cube Array
262            (
263                2,
264                #[cfg(all(
265                    not(target_abi = "sim"),
266                    any(
267                        not(feature = "webgl"),
268                        not(target_arch = "wasm32"),
269                        feature = "webgpu"
270                    )
271                ))]
272                texture_cube_array(TextureSampleType::Depth),
273                #[cfg(any(
274                    target_abi = "sim",
275                    all(feature = "webgl", target_arch = "wasm32", not(feature = "webgpu"))
276                ))]
277                texture_cube(TextureSampleType::Depth),
278            ),
279            // Point Shadow Texture Array Comparison Sampler
280            (3, sampler(SamplerBindingType::Comparison)),
281            // Point Shadow Texture Array Linear Sampler
282            #[cfg(feature = "experimental_pbr_pcss")]
283            (4, sampler(SamplerBindingType::Filtering)),
284            // Directional Shadow Texture Array
285            (
286                5,
287                #[cfg(any(
288                    not(feature = "webgl"),
289                    not(target_arch = "wasm32"),
290                    feature = "webgpu"
291                ))]
292                texture_2d_array(TextureSampleType::Depth),
293                #[cfg(all(feature = "webgl", target_arch = "wasm32", not(feature = "webgpu")))]
294                texture_2d(TextureSampleType::Depth),
295            ),
296            // Directional Shadow Texture Array Comparison Sampler
297            (6, sampler(SamplerBindingType::Comparison)),
298            // Directional Shadow Texture Array Linear Sampler
299            #[cfg(feature = "experimental_pbr_pcss")]
300            (7, sampler(SamplerBindingType::Filtering)),
301            // PointLights
302            (
303                8,
304                buffer_layout(
305                    clustered_forward_buffer_binding_type,
306                    false,
307                    Some(GpuClusteredLights::min_size(
308                        clustered_forward_buffer_binding_type,
309                    )),
310                ),
311            ),
312            // ClusteredLightIndexLists
313            (
314                9,
315                buffer_layout(
316                    clustered_forward_buffer_binding_type,
317                    false,
318                    Some(
319                        ViewClusterBindings::min_size_clusterable_object_index_lists(
320                            clustered_forward_buffer_binding_type,
321                        ),
322                    ),
323                ),
324            ),
325            // ClusterOffsetsAndCounts
326            (
327                10,
328                buffer_layout(
329                    clustered_forward_buffer_binding_type,
330                    false,
331                    Some(ViewClusterBindings::min_size_cluster_offsets_and_counts(
332                        clustered_forward_buffer_binding_type,
333                    )),
334                ),
335            ),
336            // Globals
337            (
338                11,
339                uniform_buffer::<GlobalsUniform>(false).visibility(ShaderStages::VERTEX_FRAGMENT),
340            ),
341            // Light probes
342            (12, uniform_buffer::<LightProbesUniform>(true)),
343            // Visibility ranges
344            (
345                14,
346                buffer_layout(
347                    visibility_ranges_buffer_binding_type,
348                    false,
349                    Some(Vec4::min_size()),
350                )
351                .visibility(ShaderStages::VERTEX),
352            ),
353        ),
354    );
355
356    if layout_key.contains(MeshPipelineViewLayoutKey::DISTANCE_FOG) {
357        entries = entries.extend_with_indices((
358            // Fog
359            (13, uniform_buffer::<GpuFog>(true)),
360        ));
361    }
362    if layout_key.contains(MeshPipelineViewLayoutKey::SCREEN_SPACE_REFLECTIONS) {
363        entries = entries.extend_with_indices((
364            // Screen space reflection settings
365            (15, uniform_buffer::<ScreenSpaceReflectionsUniform>(true)),
366        ));
367    }
368    if layout_key.contains(MeshPipelineViewLayoutKey::CONTACT_SHADOWS) {
369        entries = entries.extend_with_indices((
370            // Contact shadows settings
371            (16, uniform_buffer::<ContactShadowsUniform>(true)),
372        ));
373    }
374    if layout_key.contains(MeshPipelineViewLayoutKey::SCREEN_SPACE_AMBIENT_OCCLUSION) {
375        entries = entries.extend_with_indices((
376            // Screen space ambient occlusion texture
377            (
378                17,
379                texture_2d(TextureSampleType::Float { filterable: false }),
380            ),
381        ));
382    }
383
384    if layout_key.contains(MeshPipelineViewLayoutKey::TONEMAP_IN_SHADER) {
385        // Tonemapping
386        let tonemapping_lut_entries = get_lut_bind_group_layout_entries();
387        entries = entries.extend_with_indices((
388            (
389                TONEMAPPING_LUT_TEXTURE_BINDING_INDEX,
390                tonemapping_lut_entries[0],
391            ),
392            (
393                TONEMAPPING_LUT_SAMPLER_BINDING_INDEX,
394                tonemapping_lut_entries[1],
395            ),
396        ));
397    }
398
399    // Prepass
400    if cfg!(any(feature = "webgpu", not(target_arch = "wasm32")))
401        || !layout_key.contains(MeshPipelineViewLayoutKey::MULTISAMPLED)
402    {
403        for (entry, binding) in prepass::get_bind_group_layout_entries(layout_key)
404            .iter()
405            .zip([20, 21, 22, 23])
406        {
407            if let Some(entry) = entry {
408                entries = entries.extend_with_indices(((binding as u32, *entry),));
409            }
410        }
411    }
412
413    // View Transmission Texture
414    entries = entries.extend_with_indices((
415        (
416            24,
417            texture_2d(TextureSampleType::Float { filterable: true }),
418        ),
419        (25, sampler(SamplerBindingType::Filtering)),
420    ));
421
422    // OIT
423    if layout_key.contains(MeshPipelineViewLayoutKey::OIT_ENABLED) {
424        // Check if we can use OIT. This is a hack to avoid errors on webgl --
425        // the OIT plugin will warn the user that OIT is not supported on their
426        // platform, so we don't need to do it here.
427        if is_oit_supported {
428            entries = entries.extend_with_indices((
429                (
430                    26,
431                    uniform_buffer::<OrderIndependentTransparencySettings>(true),
432                ),
433                // oit_nodes_capacity
434                (27, uniform_buffer::<u32>(false)),
435                // oit_nodes
436                (28, storage_buffer_sized(false, None)),
437                // oit_heads,
438                (29, storage_buffer_sized(false, None)),
439                // oit_atomic_counter
440                (
441                    30,
442                    storage_buffer_sized(false, NonZero::<u64>::new(size_of::<u32>() as u64)),
443                ),
444            ));
445        }
446    }
447
448    // Atmosphere
449    if layout_key.contains(MeshPipelineViewLayoutKey::ATMOSPHERE) {
450        entries = entries.extend_with_indices((
451            // transmittance LUT
452            (
453                31,
454                texture_2d(TextureSampleType::Float { filterable: true }),
455            ),
456            (32, sampler(SamplerBindingType::Filtering)),
457            // atmosphere data buffer
458            (33, storage_buffer_read_only::<GpuAtmosphere>(false)),
459        ));
460    }
461
462    // Blue noise
463    if layout_key.contains(MeshPipelineViewLayoutKey::STBN) {
464        entries = entries.extend_with_indices(((
465            34,
466            texture_2d_array(TextureSampleType::Float { filterable: false }),
467        ),));
468    }
469    // LTC LUTs for area lights
470    if cfg!(feature = "area_light_luts") {
471        entries = entries.extend_with_indices((
472            (
473                35,
474                texture_2d_array(TextureSampleType::Float { filterable: true }),
475            ),
476            (36, sampler(SamplerBindingType::Filtering)),
477        ));
478    }
479    // DFG LUT
480    if cfg!(feature = "dfg_lut") {
481        entries = entries.extend_with_indices((
482            (
483                37,
484                texture_2d(TextureSampleType::Float { filterable: true }),
485            ),
486            (38, sampler(SamplerBindingType::Filtering)),
487        ));
488    }
489
490    let mut binding_array_entries = DynamicBindGroupLayoutEntries::new(ShaderStages::FRAGMENT);
491    if layout_key.contains(MeshPipelineViewLayoutKey::ENVIRONMENT_MAP) {
492        binding_array_entries = binding_array_entries.extend_with_indices((
493            (0, environment_map_entries[0]),
494            (1, environment_map_entries[1]),
495            (2, environment_map_entries[2]),
496        ));
497    }
498
499    if layout_key.contains(MeshPipelineViewLayoutKey::IRRADIANCE_VOLUME) {
500        // Irradiance volumes
501        if IRRADIANCE_VOLUMES_ARE_USABLE {
502            binding_array_entries = binding_array_entries.extend_with_indices((
503                (3, irradiance_volume_entries[0]),
504                (4, irradiance_volume_entries[1]),
505            ));
506        }
507    }
508
509    // Clustered decals
510    if let Some(clustered_decal_entries) = clustered_decal_entries {
511        binding_array_entries = binding_array_entries.extend_with_indices((
512            (5, clustered_decal_entries[0]),
513            (6, clustered_decal_entries[1]),
514            (7, clustered_decal_entries[2]),
515        ));
516    }
517
518    [entries.to_vec(), binding_array_entries.to_vec()]
519}
520
521/// Parameters needed by [`layout_entries`].
522#[derive(Clone, Copy)]
523struct MeshPipelineViewLayoutParams {
524    clustered_forward_buffer_binding_type: BufferBindingType,
525    visibility_ranges_buffer_binding_type: BufferBindingType,
526    environment_map_entries: [BindGroupLayoutEntryBuilder; 3],
527    irradiance_volume_entries: [BindGroupLayoutEntryBuilder; 2],
528    clustered_decal_entries: Option<[BindGroupLayoutEntryBuilder; 3]>,
529    is_oit_supported: bool,
530}
531
532/// Stores the view layouts entries for creating bind group layouts of pipeline keys.
533#[derive(Resource, Clone)]
534pub struct MeshPipelineViewLayouts {
535    params: Arc<MeshPipelineViewLayoutParams>,
536}
537
538pub fn init_mesh_pipeline_view_layouts(
539    mut commands: Commands,
540    render_device: Res<RenderDevice>,
541    render_adapter: Res<RenderAdapter>,
542) {
543    let clustered_forward_buffer_binding_type =
544        render_device.get_supported_read_only_binding_type(CLUSTERED_FORWARD_STORAGE_BUFFER_COUNT);
545    let visibility_ranges_buffer_binding_type =
546        render_device.get_supported_read_only_binding_type(VISIBILITY_RANGES_STORAGE_BUFFER_COUNT);
547
548    let res = MeshPipelineViewLayouts {
549        params: Arc::new(MeshPipelineViewLayoutParams {
550            clustered_forward_buffer_binding_type,
551            visibility_ranges_buffer_binding_type,
552            environment_map_entries: environment_map::get_bind_group_layout_entries(
553                &render_device,
554                &render_adapter,
555            ),
556            irradiance_volume_entries: irradiance_volume::get_bind_group_layout_entries(
557                &render_device,
558                &render_adapter,
559            ),
560            clustered_decal_entries: decal::clustered::get_bind_group_layout_entries(
561                &render_device,
562                &render_adapter,
563            ),
564            is_oit_supported: bevy_core_pipeline::oit::resolve::is_oit_supported(
565                &render_adapter,
566                &render_device,
567                false,
568            ),
569        }),
570    };
571
572    commands.insert_resource(res);
573}
574
575impl MeshPipelineViewLayouts {
576    /// Get view bind group layout for the given key.
577    pub fn get_view_layout(&self, layout_key: MeshPipelineViewLayoutKey) -> MeshPipelineViewLayout {
578        let mut entries = layout_entries(layout_key, &self.params);
579
580        #[cfg(debug_assertions)]
581        let texture_count: usize = entries
582            .iter()
583            .flat_map(|e| {
584                e.iter()
585                    .filter(|entry| matches!(entry.ty, BindingType::Texture { .. }))
586            })
587            .count();
588
589        #[cfg(debug_assertions)]
590        if texture_count > MESH_PIPELINE_VIEW_LAYOUT_SAFE_MAX_TEXTURES {
591            // Issue our own warning here because Naga's error message is a bit cryptic in this situation
592            once!(warn!(
593                "Too many textures in mesh pipeline view layout, this might cause us \
594            to hit `wgpu::Limits::max_sampled_textures_per_shader_stage` in some environments."
595            ));
596        }
597
598        MeshPipelineViewLayout {
599            main_layout: BindGroupLayoutDescriptor {
600                label: layout_key.label().into(),
601                entries: core::mem::take(&mut entries[0]),
602            },
603            binding_array_layout: BindGroupLayoutDescriptor {
604                label: layout_key
605                    .label()
606                    .replace("mesh_view_layout:", "mesh_view_layout_binding_array:")
607                    .into(),
608                entries: core::mem::take(&mut entries[1]),
609            },
610            empty_layout: BindGroupLayoutDescriptor {
611                label: "mesh_view_layout_empty".into(),
612                entries: vec![],
613            },
614        }
615    }
616}
617
618#[derive(Component)]
619pub struct MeshViewBindGroup {
620    pub main: BindGroup,
621    pub main_offsets: ArrayVec<u32, 7>,
622    pub binding_array: BindGroup,
623    pub empty: BindGroup,
624}
625
626pub fn prepare_mesh_view_bind_groups(
627    mut commands: Commands,
628    (render_device, pipeline_cache, render_adapter): (
629        Res<RenderDevice>,
630        Res<PipelineCache>,
631        Res<RenderAdapter>,
632    ),
633    mesh_pipeline: Res<MeshPipeline>,
634    shadow_samplers: Res<ShadowSamplers>,
635    (light_meta, global_clusterable_object_meta, fog_meta, view_uniforms): (
636        Res<LightMeta>,
637        Res<GlobalClusterableObjectMeta>,
638        Res<FogMeta>,
639        Res<ViewUniforms>,
640    ),
641    views: Query<(
642        Entity,
643        Option<&ExtractedCamera>,
644        &ViewShadowBindings,
645        &ViewClusterBindings,
646        &Msaa,
647        Option<&ScreenSpaceAmbientOcclusionResources>,
648        Option<&ViewPrepassTextures>,
649        Option<&ViewTransmissionTexture>,
650        Option<&AtmosphereTextures>,
651        &Tonemapping,
652        (
653            Option<&RenderViewLightProbes<EnvironmentMapLight>>,
654            Option<&RenderViewLightProbes<IrradianceVolume>>,
655        ),
656        Has<ExtractedAtmosphere>,
657        (
658            &ViewUniformOffset,
659            &ViewLightsUniformOffset,
660            &ViewLightProbesUniformOffset,
661            Option<&ViewFogUniformOffset>,
662            Option<&ViewScreenSpaceReflectionsUniformOffset>,
663            Option<&ViewContactShadowsUniformOffset>,
664            Option<&OrderIndependentTransparencySettingsOffset>,
665        ),
666    )>,
667    (images, fallback_image, fallback_image_zero): (
668        Res<RenderAssets<GpuImage>>,
669        Res<FallbackImage>,
670        Res<FallbackImageZero>,
671    ),
672    globals_buffer: Res<GlobalsBuffer>,
673    tonemapping_luts: Res<TonemappingLuts>,
674    light_probes_buffer: Res<LightProbesBuffer>,
675    visibility_ranges: Res<RenderVisibilityRanges>,
676    (ssr_buffer, contact_shadows_buffer, oit_buffers): (
677        Res<ScreenSpaceReflectionsBuffer>,
678        Res<ContactShadowsBuffer>,
679        Res<OitBuffers>,
680    ),
681    (
682        decals_buffer,
683        render_decals,
684        atmosphere_buffer,
685        atmosphere_sampler,
686        blue_noise,
687        area_light_luts,
688        dfg_lut,
689    ): (
690        Res<DecalsBuffer>,
691        Res<RenderClusteredDecals>,
692        Option<Res<AtmosphereBuffer>>,
693        Option<Res<AtmosphereSampler>>,
694        Res<Bluenoise>,
695        Res<AreaLightLuts>,
696        Res<DfgLut>,
697    ),
698    // TODO: Figure out how to reuse the memory. `BindGroupEntry` is non-send on wasm with atomics.
699    #[cfg(not(all(target_arch = "wasm32", target_feature = "atomics")))] mut entries_cache: Local<
700        Vec<BindGroupEntry>,
701    >,
702    #[cfg(not(all(target_arch = "wasm32", target_feature = "atomics")))]
703    mut entries_binding_array_cache: Local<Vec<BindGroupEntry>>,
704) {
705    if let (
706        Some(view_binding),
707        Some(light_binding),
708        Some(clusterable_objects_binding),
709        Some(globals),
710        Some(light_probes_binding),
711        Some(visibility_ranges_buffer),
712    ) = (
713        view_uniforms.uniforms.binding(),
714        light_meta.view_gpu_lights.binding(),
715        global_clusterable_object_meta
716            .gpu_clustered_lights
717            .binding(),
718        globals_buffer.buffer.binding(),
719        light_probes_buffer.binding(),
720        visibility_ranges.buffer().buffer(),
721    ) {
722        for (
723            entity,
724            camera,
725            shadow_bindings,
726            cluster_bindings,
727            msaa,
728            ssao_resources,
729            prepass_textures,
730            transmission_texture,
731            atmosphere_textures,
732            tonemapping,
733            (render_view_environment_maps, render_view_irradiance_volumes),
734            has_atmosphere,
735            (
736                view_uniform_offset,
737                view_lights_offset,
738                view_light_probes_offset,
739                view_fog_offset,
740                view_ssr_offset,
741                view_contact_shadows_offset,
742                view_oit_settings_offset,
743            ),
744        ) in &views
745        {
746            let mut entries = DynamicBindGroupEntries::new();
747            let mut entries_binding_array = DynamicBindGroupEntries::new();
748            #[cfg(not(all(target_arch = "wasm32", target_feature = "atomics")))]
749            {
750                // Take cache that has static lifetime for `DynamicBindGroupEntries`.
751                // See <https://users.rust-lang.org/t/how-to-cache-a-vectors-capacity/94478/10>.
752                entries.entries = core::mem::take(&mut *entries_cache)
753                    .into_iter()
754                    .map(|_| -> BindGroupEntry { unreachable!() })
755                    .collect();
756                entries_binding_array.entries = core::mem::take(&mut *entries_binding_array_cache)
757                    .into_iter()
758                    .map(|_| -> BindGroupEntry { unreachable!() })
759                    .collect();
760            }
761
762            let tonemap_in_shader = camera.is_none_or(|camera| !camera.hdr);
763            let mut layout_key = MeshPipelineViewLayoutKey::from(*msaa)
764                | MeshPipelineViewLayoutKey::from(prepass_textures);
765            let mut offsets = ArrayVec::from_iter([
766                view_uniform_offset.offset,
767                view_lights_offset.offset,
768                **view_light_probes_offset,
769            ]);
770
771            entries = entries.extend_with_indices((
772                (0, view_binding.clone()),
773                (1, light_binding.clone()),
774                (2, &shadow_bindings.point_light_depth_texture_view),
775                (3, &shadow_samplers.point_light_comparison_sampler),
776                #[cfg(feature = "experimental_pbr_pcss")]
777                (4, &shadow_samplers.point_light_linear_sampler),
778                (5, &shadow_bindings.directional_light_depth_texture_view),
779                (6, &shadow_samplers.directional_light_comparison_sampler),
780                #[cfg(feature = "experimental_pbr_pcss")]
781                (7, &shadow_samplers.directional_light_linear_sampler),
782                (8, clusterable_objects_binding.clone()),
783                (
784                    9,
785                    cluster_bindings
786                        .clusterable_object_index_lists_binding()
787                        .unwrap(),
788                ),
789                (10, cluster_bindings.offsets_and_counts_binding().unwrap()),
790                (11, globals.clone()),
791                (12, light_probes_binding.clone()),
792                (14, visibility_ranges_buffer.as_entire_binding()),
793            ));
794
795            if let Some(view_fog_offset) = view_fog_offset {
796                layout_key |= MeshPipelineViewLayoutKey::DISTANCE_FOG;
797                offsets.push(view_fog_offset.offset);
798                entries =
799                    entries.extend_with_indices(((13, fog_meta.gpu_fogs.binding().unwrap()),));
800            }
801
802            if let Some(view_ssr_offset) = view_ssr_offset {
803                layout_key |= MeshPipelineViewLayoutKey::SCREEN_SPACE_REFLECTIONS;
804                offsets.push(**view_ssr_offset);
805                entries = entries.extend_with_indices(((15, ssr_buffer.binding().unwrap()),));
806            }
807
808            if let Some(view_contact_shadows_offset) = view_contact_shadows_offset {
809                layout_key |= MeshPipelineViewLayoutKey::CONTACT_SHADOWS;
810                offsets.push(**view_contact_shadows_offset);
811                entries = entries
812                    .extend_with_indices(((16, contact_shadows_buffer.0.binding().unwrap()),));
813            }
814
815            if let Some(view_oit_settings_offset) = view_oit_settings_offset {
816                layout_key |= MeshPipelineViewLayoutKey::OIT_ENABLED;
817                offsets.push(view_oit_settings_offset.offset);
818                entries = entries.extend_with_indices((
819                    (26, oit_buffers.settings.binding().unwrap()),
820                    (27, oit_buffers.nodes_capacity.binding().unwrap()),
821                    (28, oit_buffers.nodes.binding().unwrap()),
822                    (29, oit_buffers.heads.binding().unwrap()),
823                    (30, oit_buffers.atomic_counter.binding().unwrap()),
824                ));
825            }
826
827            if has_atmosphere
828                && let Some(atmosphere_textures) = atmosphere_textures
829                && let Some(atmosphere_buffer) = atmosphere_buffer.as_ref()
830                && let Some(atmosphere_sampler) = atmosphere_sampler.as_ref()
831                && let Some(atmosphere_buffer_binding) = atmosphere_buffer.buffer.binding()
832            {
833                layout_key |= MeshPipelineViewLayoutKey::ATMOSPHERE;
834                entries = entries.extend_with_indices((
835                    (31, &atmosphere_textures.transmittance_lut.default_view),
836                    (32, &***atmosphere_sampler),
837                    (33, atmosphere_buffer_binding),
838                ));
839            }
840
841            if cfg!(feature = "bluenoise_texture") {
842                layout_key |= MeshPipelineViewLayoutKey::STBN;
843                let stbn_view = &images
844                    .get(&blue_noise.texture)
845                    .expect("STBN texture is added unconditionally with at least a placeholder")
846                    .texture_view;
847                entries = entries.extend_with_indices(((34, stbn_view),));
848            }
849
850            if tonemap_in_shader {
851                layout_key |= MeshPipelineViewLayoutKey::TONEMAP_IN_SHADER;
852                let lut_bindings =
853                    get_lut_bindings(&images, &tonemapping_luts, tonemapping, &fallback_image);
854                entries = entries.extend_with_indices((
855                    (TONEMAPPING_LUT_TEXTURE_BINDING_INDEX, lut_bindings.0),
856                    (TONEMAPPING_LUT_SAMPLER_BINDING_INDEX, lut_bindings.1),
857                ));
858            }
859
860            if let Some(ssao_resources) = ssao_resources {
861                layout_key |= MeshPipelineViewLayoutKey::SCREEN_SPACE_AMBIENT_OCCLUSION;
862                let ssao_view = &ssao_resources
863                    .screen_space_ambient_occlusion_texture
864                    .default_view;
865                entries = entries.extend_with_indices(((17, ssao_view),));
866            }
867
868            let transmission_view = transmission_texture
869                .map(|transmission| &transmission.view)
870                .unwrap_or(&fallback_image_zero.texture_view);
871
872            let transmission_sampler = transmission_texture
873                .map(|transmission| &transmission.sampler)
874                .unwrap_or(&fallback_image_zero.sampler);
875
876            entries =
877                entries.extend_with_indices(((24, transmission_view), (25, transmission_sampler)));
878
879            // When using WebGL, we can't have a multisampled texture with `TEXTURE_BINDING`
880            // See https://github.com/gfx-rs/wgpu/issues/5263
881            let prepass_bindings;
882            if cfg!(any(feature = "webgpu", not(target_arch = "wasm32"))) || msaa.samples() == 1 {
883                prepass_bindings = prepass::get_bindings(prepass_textures);
884                for (binding, index) in prepass_bindings
885                    .iter()
886                    .map(Option::as_ref)
887                    .zip([20, 21, 22, 23])
888                    .flat_map(|(b, i)| b.map(|b| (b, i)))
889                {
890                    entries = entries.extend_with_indices(((index, binding),));
891                }
892            };
893
894            // LTC LUTs for area lights
895            if cfg!(feature = "area_light_luts") {
896                let (ltc_view, ltc_sampler) = images
897                    .get(&area_light_luts.image)
898                    .map(|img| (&img.texture_view, &img.sampler))
899                    .unwrap_or((
900                        &fallback_image.d2_array.texture_view,
901                        &fallback_image.d2_array.sampler,
902                    ));
903                entries = entries.extend_with_indices(((35, ltc_view), (36, ltc_sampler)));
904            }
905
906            // DFG LUT
907            if cfg!(feature = "dfg_lut") {
908                let (dfg_view, dfg_sampler) = images
909                    .get(&dfg_lut.texture)
910                    .map(|img| (&img.texture_view, &img.sampler))
911                    .unwrap_or((&fallback_image.d2.texture_view, &fallback_image.d2.sampler));
912                entries = entries.extend_with_indices(((37, dfg_view), (38, dfg_sampler)));
913            }
914
915            let environment_map_bind_group_entries =
916                render_view_environment_maps.map(|render_view_environment_maps| {
917                    layout_key |= MeshPipelineViewLayoutKey::ENVIRONMENT_MAP;
918
919                    RenderViewEnvironmentMapBindGroupEntries::get(
920                        Some(render_view_environment_maps),
921                        &images,
922                        &fallback_image,
923                        &render_device,
924                        &render_adapter,
925                    )
926                });
927            match environment_map_bind_group_entries {
928                Some(RenderViewEnvironmentMapBindGroupEntries::Single {
929                    diffuse_texture_view,
930                    specular_texture_view,
931                    sampler,
932                }) => {
933                    entries_binding_array = entries_binding_array.extend_with_indices((
934                        (0, diffuse_texture_view),
935                        (1, specular_texture_view),
936                        (2, sampler),
937                    ));
938                }
939                Some(RenderViewEnvironmentMapBindGroupEntries::Multiple {
940                    ref diffuse_texture_views,
941                    ref specular_texture_views,
942                    sampler,
943                }) => {
944                    entries_binding_array = entries_binding_array.extend_with_indices((
945                        (0, diffuse_texture_views.as_slice()),
946                        (1, specular_texture_views.as_slice()),
947                        (2, sampler),
948                    ));
949                }
950                None => {}
951            }
952
953            let irradiance_volume_bind_group_entries =
954                if render_view_irradiance_volumes.is_some() && IRRADIANCE_VOLUMES_ARE_USABLE {
955                    layout_key |= MeshPipelineViewLayoutKey::IRRADIANCE_VOLUME;
956
957                    Some(RenderViewIrradianceVolumeBindGroupEntries::get(
958                        render_view_irradiance_volumes,
959                        &images,
960                        &fallback_image,
961                        &render_device,
962                        &render_adapter,
963                    ))
964                } else {
965                    None
966                };
967
968            match irradiance_volume_bind_group_entries {
969                Some(RenderViewIrradianceVolumeBindGroupEntries::Single {
970                    texture_view,
971                    sampler,
972                }) => {
973                    entries_binding_array = entries_binding_array
974                        .extend_with_indices(((3, texture_view), (4, sampler)));
975                }
976                Some(RenderViewIrradianceVolumeBindGroupEntries::Multiple {
977                    ref texture_views,
978                    sampler,
979                }) => {
980                    entries_binding_array = entries_binding_array
981                        .extend_with_indices(((3, texture_views.as_slice()), (4, sampler)));
982                }
983                None => {}
984            }
985
986            let decal_bind_group_entries = RenderViewClusteredDecalBindGroupEntries::get(
987                &render_decals,
988                &decals_buffer,
989                &images,
990                &fallback_image,
991                &render_device,
992                &render_adapter,
993            );
994
995            // Add the decal bind group entries.
996            if let Some(ref render_view_decal_bind_group_entries) = decal_bind_group_entries {
997                entries_binding_array = entries_binding_array.extend_with_indices((
998                    // `clustered_decals`
999                    (
1000                        5,
1001                        render_view_decal_bind_group_entries
1002                            .decals
1003                            .as_entire_binding(),
1004                    ),
1005                    // `clustered_decal_textures`
1006                    (
1007                        6,
1008                        render_view_decal_bind_group_entries
1009                            .texture_views
1010                            .as_slice(),
1011                    ),
1012                    // `clustered_decal_sampler`
1013                    (7, render_view_decal_bind_group_entries.sampler),
1014                ));
1015            }
1016
1017            let layout = mesh_pipeline.get_view_layout(layout_key);
1018            commands.entity(entity).insert((MeshViewBindGroup {
1019                main_offsets: offsets,
1020                main: render_device.create_bind_group(
1021                    "mesh_view_bind_group",
1022                    &pipeline_cache.get_bind_group_layout(&layout.main_layout),
1023                    &entries,
1024                ),
1025                binding_array: render_device.create_bind_group(
1026                    "mesh_view_bind_group_binding_array",
1027                    &pipeline_cache.get_bind_group_layout(&layout.binding_array_layout),
1028                    &entries_binding_array,
1029                ),
1030                empty: render_device.create_bind_group(
1031                    "mesh_view_bind_group_empty",
1032                    &pipeline_cache.get_bind_group_layout(&layout.empty_layout),
1033                    &[],
1034                ),
1035            },));
1036
1037            #[cfg(not(all(target_arch = "wasm32", target_feature = "atomics")))]
1038            {
1039                entries.entries.clear();
1040                entries_binding_array.entries.clear();
1041                *entries_cache = entries
1042                    .entries
1043                    .into_iter()
1044                    .map(|_| -> BindGroupEntry<'static> { unreachable!() })
1045                    .collect();
1046                *entries_binding_array_cache = entries_binding_array
1047                    .entries
1048                    .into_iter()
1049                    .map(|_| -> BindGroupEntry<'static> { unreachable!() })
1050                    .collect();
1051            }
1052        }
1053    }
1054}