Skip to main content

bevy_pbr/render/
gpu_preprocess.rs

1//! GPU mesh preprocessing.
2//!
3//! This is an optional pass that uses a compute shader to reduce the amount of
4//! data that has to be transferred from the CPU to the GPU. When enabled,
5//! instead of transferring [`MeshUniform`]s to the GPU, we transfer the smaller
6//! [`MeshInputUniform`]s instead and use the GPU to calculate the remaining
7//! derived fields in [`MeshUniform`].
8
9use core::num::{NonZero, NonZeroU64};
10
11use bevy_app::{App, Plugin};
12use bevy_asset::{embedded_asset, load_embedded_asset, Handle};
13use bevy_core_pipeline::{
14    deferred::node::late_deferred_prepass,
15    mip_generation::experimental::depth::{early_downsample_depth, ViewDepthPyramid},
16    prepass::{
17        node::{early_prepass, late_prepass},
18        DeferredPrepass, DepthPrepass, MotionVectorPrepass, NormalPrepass, PreviousViewData,
19        PreviousViewUniformOffset, PreviousViewUniforms,
20    },
21    schedule::{Core3d, Core3dSystems},
22};
23use bevy_derive::{Deref, DerefMut};
24use bevy_ecs::{
25    component::Component,
26    entity::Entity,
27    prelude::resource_exists,
28    query::{Has, Or, With, Without},
29    resource::Resource,
30    schedule::{common_conditions::any_match_filter, IntoScheduleConfigs as _},
31    system::{Commands, Query, Res, ResMut},
32    world::{FromWorld, World},
33};
34use bevy_log::warn_once;
35use bevy_math::Vec4;
36use bevy_platform::collections::HashMap;
37use bevy_render::{
38    batching::gpu_preprocessing::{
39        clear_bin_unpacking_buffers, BatchedInstanceBuffers, BinUnpackingBuffers,
40        BinUnpackingBuffersKey, BinUnpackingJob, BinUnpackingMetadataIndex,
41        GpuBinUnpackingMetadata, GpuOcclusionCullingWorkItemBuffers, GpuPreprocessingMode,
42        GpuPreprocessingSupport, IndirectBatchSet, IndirectParametersBuffers,
43        IndirectParametersCpuMetadata, IndirectParametersGpuMetadata, IndirectParametersIndexed,
44        IndirectParametersNonIndexed, LatePreprocessWorkItemIndirectParameters, PreprocessWorkItem,
45        PreprocessWorkItemBuffers, UntypedPhaseBatchedInstanceBuffers,
46        UntypedPhaseIndirectParametersBuffers,
47    },
48    diagnostic::RecordDiagnostics as _,
49    occlusion_culling::OcclusionCulling,
50    render_phase::GpuRenderBinnedMeshInstance,
51    render_resource::{
52        binding_types::{storage_buffer, storage_buffer_read_only, texture_2d, uniform_buffer},
53        BindGroup, BindGroupEntries, BindGroupLayoutDescriptor, BindGroupLayoutEntries,
54        BindingResource, Buffer, BufferBinding, BufferVec, CachedComputePipelineId,
55        ComputePassDescriptor, ComputePipelineDescriptor, DynamicBindGroupLayoutEntries,
56        PartialBufferVec, PipelineCache, RawBufferVec, ShaderStages, ShaderType,
57        SparseBufferUpdateBindGroups, SparseBufferUpdateJobs, SparseBufferUpdatePipelines,
58        SpecializedComputePipeline, SpecializedComputePipelines, TextureSampleType,
59        UninitBufferVec,
60    },
61    renderer::{RenderContext, RenderDevice, RenderQueue, ViewQuery},
62    settings::WgpuFeatures,
63    view::{
64        ExtractedView, NoIndirectDrawing, RenderVisibilityRanges, RetainedViewEntity, ViewUniform,
65        ViewUniformOffset, ViewUniforms,
66    },
67    GpuResourceAppExt, Render, RenderApp, RenderSystems,
68};
69use bevy_shader::Shader;
70use bevy_utils::{default, TypeIdMap};
71use bitflags::bitflags;
72use smallvec::{smallvec, SmallVec};
73use tracing::warn;
74
75use crate::{LightEntity, MeshCullingData, MeshCullingDataBuffer, MeshInputUniform, MeshUniform};
76
77use super::{ShadowView, ViewLightEntities};
78
79/// The GPU workgroup size.
80const WORKGROUP_SIZE: usize = 64;
81
82/// A plugin that builds mesh uniforms on GPU.
83///
84/// This will only be added if the platform supports compute shaders (e.g. not
85/// on WebGL 2).
86pub struct GpuMeshPreprocessPlugin {
87    /// Whether we're building [`MeshUniform`]s on GPU.
88    ///
89    /// This requires compute shader support and so will be forcibly disabled if
90    /// the platform doesn't support those.
91    pub use_gpu_instance_buffer_builder: bool,
92}
93
94/// The compute shader pipelines for the GPU mesh preprocessing and indirect
95/// parameter building passes.
96#[derive(Resource)]
97pub struct PreprocessPipelines {
98    /// The pipeline used for CPU culling. This pipeline doesn't populate
99    /// indirect parameter metadata.
100    pub direct_preprocess: PreprocessPipeline,
101    /// The pipeline used for mesh preprocessing when GPU frustum culling is in
102    /// use, but occlusion culling isn't.
103    ///
104    /// This pipeline populates indirect parameter metadata.
105    pub gpu_frustum_culling_preprocess: PreprocessPipeline,
106    /// The pipeline used for the first phase of occlusion culling.
107    ///
108    /// This pipeline culls, transforms meshes, and populates indirect parameter
109    /// metadata.
110    pub early_gpu_occlusion_culling_preprocess: PreprocessPipeline,
111    /// The pipeline used for the second phase of occlusion culling.
112    ///
113    /// This pipeline culls, transforms meshes, and populates indirect parameter
114    /// metadata.
115    pub late_gpu_occlusion_culling_preprocess: PreprocessPipeline,
116    /// The pipeline that builds indirect draw parameters for indexed meshes,
117    /// when frustum culling is enabled but occlusion culling *isn't* enabled.
118    pub gpu_frustum_culling_build_indexed_indirect_params: BuildIndirectParametersPipeline,
119    /// The pipeline that builds indirect draw parameters for non-indexed
120    /// meshes, when frustum culling is enabled but occlusion culling *isn't*
121    /// enabled.
122    pub gpu_frustum_culling_build_non_indexed_indirect_params: BuildIndirectParametersPipeline,
123    /// Compute shader pipelines for the early prepass phase that draws meshes
124    /// visible in the previous frame.
125    pub early_phase: PreprocessPhasePipelines,
126    /// Compute shader pipelines for the late prepass phase that draws meshes
127    /// that weren't visible in the previous frame, but became visible this
128    /// frame.
129    pub late_phase: PreprocessPhasePipelines,
130    /// Compute shader pipelines for the main color phase.
131    pub main_phase: PreprocessPhasePipelines,
132    /// Compute shader pipelines for the bin unpacking step.
133    pub bin_unpacking: BinUnpackingPipeline,
134}
135
136/// Compute shader pipelines for a specific phase: early, late, or main.
137///
138/// The distinction between these phases is relevant for occlusion culling.
139#[derive(Clone)]
140pub struct PreprocessPhasePipelines {
141    /// The pipeline that resets the indirect draw counts used in
142    /// `multi_draw_indirect_count` to 0 in preparation for a new pass.
143    pub reset_indirect_batch_sets: ResetIndirectBatchSetsPipeline,
144    /// The pipeline used for indexed indirect parameter building.
145    ///
146    /// This pipeline converts indirect parameter metadata into indexed indirect
147    /// parameters.
148    pub gpu_occlusion_culling_build_indexed_indirect_params: BuildIndirectParametersPipeline,
149    /// The pipeline used for non-indexed indirect parameter building.
150    ///
151    /// This pipeline converts indirect parameter metadata into non-indexed
152    /// indirect parameters.
153    pub gpu_occlusion_culling_build_non_indexed_indirect_params: BuildIndirectParametersPipeline,
154}
155
156/// The pipeline for the GPU mesh preprocessing shader.
157pub struct PreprocessPipeline {
158    /// The bind group layout for the compute shader.
159    pub bind_group_layout: BindGroupLayoutDescriptor,
160    /// The shader asset handle.
161    pub shader: Handle<Shader>,
162    /// The pipeline ID for the compute shader.
163    ///
164    /// This gets filled in `prepare_preprocess_pipelines`.
165    pub pipeline_id: Option<CachedComputePipelineId>,
166}
167
168/// The pipeline for the batch set count reset shader.
169///
170/// This shader resets the indirect batch set count to 0 for each view. It runs
171/// in between every phase (early, late, and main).
172#[derive(Clone)]
173pub struct ResetIndirectBatchSetsPipeline {
174    /// The bind group layout for the compute shader.
175    pub bind_group_layout: BindGroupLayoutDescriptor,
176    /// The shader asset handle.
177    pub shader: Handle<Shader>,
178    /// The pipeline ID for the compute shader.
179    ///
180    /// This gets filled in `prepare_preprocess_pipelines`.
181    pub pipeline_id: Option<CachedComputePipelineId>,
182}
183
184/// The pipeline for the indirect parameter building shader.
185#[derive(Clone)]
186pub struct BuildIndirectParametersPipeline {
187    /// The bind group layout for the compute shader.
188    pub bind_group_layout: BindGroupLayoutDescriptor,
189    /// The shader asset handle.
190    pub shader: Handle<Shader>,
191    /// The pipeline ID for the compute shader.
192    ///
193    /// This gets filled in `prepare_preprocess_pipelines`.
194    pub pipeline_id: Option<CachedComputePipelineId>,
195}
196
197/// The pipeline for the `unpack_bins` compute shader.
198#[derive(Clone)]
199pub struct BinUnpackingPipeline {
200    /// The layout of the single bind group for that shader.
201    pub bind_group_layout: BindGroupLayoutDescriptor,
202    /// The shader asset handle.
203    pub shader: Handle<Shader>,
204    /// The pipeline ID for the compute shader.
205    ///
206    /// This gets filled in in the [`prepare_preprocess_pipelines`] system.
207    pub pipeline_id: Option<CachedComputePipelineId>,
208}
209
210bitflags! {
211    /// Specifies variants of the mesh preprocessing shader.
212    #[derive(Clone, Copy, PartialEq, Eq, Hash)]
213    pub struct PreprocessPipelineKey: u8 {
214        /// Whether GPU frustum culling is in use.
215        ///
216        /// This `#define`'s `FRUSTUM_CULLING` in the shader.
217        const FRUSTUM_CULLING = 1;
218        /// Whether GPU two-phase occlusion culling is in use.
219        ///
220        /// This `#define`'s `OCCLUSION_CULLING` in the shader.
221        const OCCLUSION_CULLING = 2;
222        /// Whether this is the early phase of GPU two-phase occlusion culling.
223        ///
224        /// This `#define`'s `EARLY_PHASE` in the shader.
225        const EARLY_PHASE = 4;
226    }
227
228    /// Specifies variants of the indirect parameter building shader.
229    #[derive(Clone, Copy, PartialEq, Eq, Hash)]
230    pub struct BuildIndirectParametersPipelineKey: u8 {
231        /// Whether the indirect parameter building shader is processing indexed
232        /// meshes (those that have index buffers).
233        ///
234        /// This defines `INDEXED` in the shader.
235        const INDEXED = 1;
236        /// Whether the GPU and driver supports `multi_draw_indirect_count`.
237        ///
238        /// This defines `MULTI_DRAW_INDIRECT_COUNT_SUPPORTED` in the shader.
239        const MULTI_DRAW_INDIRECT_COUNT_SUPPORTED = 2;
240        /// Whether GPU two-phase occlusion culling is in use.
241        ///
242        /// This `#define`'s `OCCLUSION_CULLING` in the shader.
243        const OCCLUSION_CULLING = 4;
244        /// Whether this is the early phase of GPU two-phase occlusion culling.
245        ///
246        /// This `#define`'s `EARLY_PHASE` in the shader.
247        const EARLY_PHASE = 8;
248        /// Whether this is the late phase of GPU two-phase occlusion culling.
249        ///
250        /// This `#define`'s `LATE_PHASE` in the shader.
251        const LATE_PHASE = 16;
252        /// Whether this is the phase that runs after the early and late phases,
253        /// and right before the main drawing logic, when GPU two-phase
254        /// occlusion culling is in use.
255        ///
256        /// This `#define`'s `MAIN_PHASE` in the shader.
257        const MAIN_PHASE = 32;
258    }
259}
260
261/// The compute shader bind group for the mesh preprocessing pass for each
262/// render phase.
263///
264/// This goes on the view. It maps the [`core::any::TypeId`] of a render phase
265/// (e.g.  [`bevy_core_pipeline::core_3d::Opaque3d`]) to the
266/// [`PhasePreprocessBindGroups`] for that phase.
267#[derive(Component, Clone, Deref, DerefMut)]
268pub struct PreprocessBindGroups(pub TypeIdMap<PhasePreprocessBindGroups>);
269
270/// The compute shader bind group for the mesh preprocessing step for a single
271/// render phase on a single view.
272#[derive(Clone)]
273pub enum PhasePreprocessBindGroups {
274    /// The bind group used for the single invocation of the compute shader when
275    /// indirect drawing is *not* being used.
276    ///
277    /// Because direct drawing doesn't require splitting the meshes into indexed
278    /// and non-indexed meshes, there's only one bind group in this case.
279    Direct(BindGroup),
280
281    /// The bind groups used for the compute shader when indirect drawing is
282    /// being used, but occlusion culling isn't being used.
283    ///
284    /// Because indirect drawing requires splitting the meshes into indexed and
285    /// non-indexed meshes, there are two bind groups here.
286    IndirectFrustumCulling {
287        /// The bind group for indexed meshes.
288        indexed: Option<BindGroup>,
289        /// The bind group for non-indexed meshes.
290        non_indexed: Option<BindGroup>,
291    },
292
293    /// The bind groups used for the compute shader when indirect drawing is
294    /// being used, but occlusion culling isn't being used.
295    ///
296    /// Because indirect drawing requires splitting the meshes into indexed and
297    /// non-indexed meshes, and because occlusion culling requires splitting
298    /// this phase into early and late versions, there are four bind groups
299    /// here.
300    IndirectOcclusionCulling {
301        /// The bind group for indexed meshes during the early mesh
302        /// preprocessing phase.
303        early_indexed: Option<BindGroup>,
304        /// The bind group for non-indexed meshes during the early mesh
305        /// preprocessing phase.
306        early_non_indexed: Option<BindGroup>,
307        /// The bind group for indexed meshes during the late mesh preprocessing
308        /// phase.
309        late_indexed: Option<BindGroup>,
310        /// The bind group for non-indexed meshes during the late mesh
311        /// preprocessing phase.
312        late_non_indexed: Option<BindGroup>,
313    },
314}
315
316/// The bind groups for the compute shaders that reset indirect draw counts and
317/// build indirect parameters.
318///
319/// There's one set of bind group for each phase. Phases are keyed off their
320/// [`core::any::TypeId`].
321#[derive(Resource, Default, Deref, DerefMut)]
322pub struct BuildIndirectParametersBindGroups(pub TypeIdMap<PhaseBuildIndirectParametersBindGroups>);
323
324impl BuildIndirectParametersBindGroups {
325    /// Creates a new, empty [`BuildIndirectParametersBindGroups`] table.
326    pub fn new() -> BuildIndirectParametersBindGroups {
327        Self::default()
328    }
329}
330
331/// The per-phase set of bind groups for the compute shaders that reset indirect
332/// draw counts and build indirect parameters.
333pub struct PhaseBuildIndirectParametersBindGroups {
334    /// The bind group for the `reset_indirect_batch_sets.wgsl` shader, for
335    /// indexed meshes.
336    reset_indexed_indirect_batch_sets: Option<BindGroup>,
337    /// The bind group for the `reset_indirect_batch_sets.wgsl` shader, for
338    /// non-indexed meshes.
339    reset_non_indexed_indirect_batch_sets: Option<BindGroup>,
340    /// The bind group for the `build_indirect_params.wgsl` shader, for indexed
341    /// meshes.
342    build_indexed_indirect: Option<BindGroup>,
343    /// The bind group for the `build_indirect_params.wgsl` shader, for
344    /// non-indexed meshes.
345    build_non_indexed_indirect: Option<BindGroup>,
346}
347
348/// A resource, part of the render world, that stores all the bind groups for
349/// the bin unpacking shader.
350///
351/// There will be one such bind group for each combination of view, phase, and
352/// mesh indexed-ness.
353#[derive(Clone, Resource, Default, Deref, DerefMut)]
354pub struct BinUnpackingBindGroups(
355    pub HashMap<BinUnpackingBuffersKey, ViewPhaseBinUnpackingBindGroups>,
356);
357
358/// The bind groups for the `unpack_bins` shader for a single (view, phase)
359/// combination.
360#[derive(Clone)]
361pub struct ViewPhaseBinUnpackingBindGroups {
362    /// The bind group for the indexed meshes.
363    indexed: Vec<ViewPhaseBinUnpackingBindGroup>,
364    /// The bind group for the non-indexed meshes.
365    non_indexed: Vec<ViewPhaseBinUnpackingBindGroup>,
366}
367
368/// The bind group for the `unpack_bins` shader for a single combination of
369/// view, phase, and mesh indexed-ness.
370#[derive(Clone)]
371pub struct ViewPhaseBinUnpackingBindGroup {
372    /// The index of the metadata in the
373    /// [`BinUnpackingBuffers::bin_unpacking_metadata`] buffer.
374    pub metadata_index: BinUnpackingMetadataIndex,
375    /// The actual shader bind group.
376    pub bind_group: BindGroup,
377    /// The number of mesh instances of the appropriate type (indexed or
378    /// non-indexed) for this batch set.
379    pub mesh_instance_count: u32,
380}
381
382/// Stops the `GpuPreprocessNode` attempting to generate the buffer for this view
383/// useful to avoid duplicating effort if the bind group is shared between views
384#[derive(Component, Default)]
385pub struct SkipGpuPreprocess;
386
387type WithAnyPrepass = Or<(
388    With<DepthPrepass>,
389    With<NormalPrepass>,
390    With<MotionVectorPrepass>,
391    With<DeferredPrepass>,
392)>;
393
394impl Plugin for GpuMeshPreprocessPlugin {
395    fn build(&self, app: &mut App) {
396        embedded_asset!(app, "mesh_preprocess.wgsl");
397        embedded_asset!(app, "reset_indirect_batch_sets.wgsl");
398        embedded_asset!(app, "build_indirect_params.wgsl");
399        embedded_asset!(app, "unpack_bins.wgsl");
400    }
401
402    fn finish(&self, app: &mut App) {
403        let Some(render_app) = app.get_sub_app_mut(RenderApp) else {
404            return;
405        };
406
407        // This plugin does nothing if GPU instance buffer building isn't in
408        // use.
409        let gpu_preprocessing_support = render_app.world().resource::<GpuPreprocessingSupport>();
410        if !self.use_gpu_instance_buffer_builder || !gpu_preprocessing_support.is_available() {
411            return;
412        }
413
414        render_app
415            .init_gpu_resource::<BinUnpackingBindGroups>()
416            .init_gpu_resource::<PreprocessPipelines>()
417            .init_gpu_resource::<SpecializedComputePipelines<PreprocessPipeline>>()
418            .init_gpu_resource::<SpecializedComputePipelines<ResetIndirectBatchSetsPipeline>>()
419            .init_gpu_resource::<SpecializedComputePipelines<BuildIndirectParametersPipeline>>()
420            .init_gpu_resource::<SpecializedComputePipelines<BinUnpackingPipeline>>()
421            .add_systems(
422                Render,
423                (
424                    clear_bin_unpacking_buffers.in_set(RenderSystems::PrepareResources),
425                    prepare_preprocess_pipelines.in_set(RenderSystems::Prepare),
426                    prepare_preprocess_bind_groups
427                        .run_if(resource_exists::<BatchedInstanceBuffers<
428                            MeshUniform,
429                            MeshInputUniform
430                        >>)
431                        .in_set(RenderSystems::PrepareBindGroups)
432                        .after(prepare_preprocess_pipelines),
433                    write_mesh_culling_data_buffer.in_set(RenderSystems::PrepareResourcesFlush),
434                ),
435            )
436            .add_systems(
437                Core3d,
438                (
439                    (
440                        clear_indirect_parameters_metadata,
441                        unpack_bins,
442                        early_gpu_preprocess,
443                        early_prepass_build_indirect_parameters.run_if(any_match_filter::<(
444                            With<PreprocessBindGroups>,
445                            Without<SkipGpuPreprocess>,
446                            Without<NoIndirectDrawing>,
447                            Or<(WithAnyPrepass, With<ShadowView>)>,
448                        )>),
449                    )
450                        .chain()
451                        .before(early_prepass),
452                    (
453                        late_gpu_preprocess,
454                        late_prepass_build_indirect_parameters.run_if(any_match_filter::<(
455                            With<PreprocessBindGroups>,
456                            Without<SkipGpuPreprocess>,
457                            Without<NoIndirectDrawing>,
458                            Or<(WithAnyPrepass, With<ShadowView>)>,
459                            With<OcclusionCulling>,
460                        )>),
461                    )
462                        .chain()
463                        .after(early_downsample_depth)
464                        .before(late_prepass),
465                    main_build_indirect_parameters
466                        .run_if(any_match_filter::<(
467                            With<PreprocessBindGroups>,
468                            Without<SkipGpuPreprocess>,
469                            Without<NoIndirectDrawing>,
470                        )>)
471                        .after(late_prepass_build_indirect_parameters)
472                        .after(late_deferred_prepass)
473                        .before(Core3dSystems::MainPass),
474                ),
475            );
476    }
477}
478
479pub fn clear_indirect_parameters_metadata(
480    indirect_parameters_buffers: Option<Res<IndirectParametersBuffers>>,
481    mut ctx: RenderContext,
482) {
483    let Some(indirect_parameters_buffers) = indirect_parameters_buffers else {
484        return;
485    };
486
487    // Clear out each indexed and non-indexed GPU-side buffer.
488    for phase_indirect_parameters_buffers in indirect_parameters_buffers.values() {
489        if let Some(indexed_gpu_metadata_buffer) = phase_indirect_parameters_buffers
490            .indexed
491            .gpu_metadata_buffer()
492        {
493            ctx.command_encoder().clear_buffer(
494                indexed_gpu_metadata_buffer,
495                0,
496                Some(
497                    phase_indirect_parameters_buffers.indexed.batch_count() as u64
498                        * size_of::<IndirectParametersGpuMetadata>() as u64,
499                ),
500            );
501        }
502
503        if let Some(non_indexed_gpu_metadata_buffer) = phase_indirect_parameters_buffers
504            .non_indexed
505            .gpu_metadata_buffer()
506        {
507            ctx.command_encoder().clear_buffer(
508                non_indexed_gpu_metadata_buffer,
509                0,
510                Some(
511                    phase_indirect_parameters_buffers.non_indexed.batch_count() as u64
512                        * size_of::<IndirectParametersGpuMetadata>() as u64,
513                ),
514            );
515        }
516    }
517}
518
519/// A rendering system that invokes a compute shader for each batch set in order
520/// to generate preprocessing jobs for the subsequent mesh preprocessing shader.
521///
522/// This shader exists because performing the unpack operation on the CPU is
523/// slow when there are many entities. By caching the bins on the GPU from frame
524/// to frame, we avoid having to perform a CPU-side traversal of every mesh
525/// instance every frame.
526pub fn unpack_bins(
527    current_view: ViewQuery<Option<&ViewLightEntities>, Without<SkipGpuPreprocess>>,
528    view_query: Query<&ExtractedView, Without<SkipGpuPreprocess>>,
529    light_query: Query<&LightEntity>,
530    batched_instance_buffers: Res<BatchedInstanceBuffers<MeshUniform, MeshInputUniform>>,
531    pipeline_cache: Res<PipelineCache>,
532    preprocess_pipelines: Res<PreprocessPipelines>,
533    bin_unpacking_bind_groups: Res<BinUnpackingBindGroups>,
534    mut render_context: RenderContext,
535) {
536    let diagnostics = render_context.diagnostic_recorder();
537    let diagnostics = diagnostics.as_deref();
538
539    let command_encoder = render_context.command_encoder();
540    let mut compute_pass = command_encoder.begin_compute_pass(&ComputePassDescriptor {
541        label: Some("bin unpacking"),
542        timestamp_writes: None,
543    });
544
545    let pass_span = diagnostics.pass_span(&mut compute_pass, "bin_unpacking");
546
547    // Gather up all views.
548    let view_entity = current_view.entity();
549    let shadow_cascade_views = current_view.into_inner();
550    let all_views =
551        gather_shadow_cascades_for_view(view_entity, shadow_cascade_views, &light_query);
552
553    // Don't run if the shaders haven't been compiled yet.
554    if let Some(bin_unpacking_pipeline_id) = preprocess_pipelines.bin_unpacking.pipeline_id
555        && let Some(bin_unpacking_pipeline) =
556            pipeline_cache.get_compute_pipeline(bin_unpacking_pipeline_id)
557    {
558        compute_pass.set_pipeline(bin_unpacking_pipeline);
559
560        // Loop over each view…
561        for view_entity in all_views {
562            let Ok(view) = view_query.get(view_entity) else {
563                continue;
564            };
565
566            // …and each phase within each view.
567            for phase_type_id in batched_instance_buffers.phase_instance_buffers.keys() {
568                let bin_unpacking_buffers_key = BinUnpackingBuffersKey {
569                    phase: *phase_type_id,
570                    view: view.retained_view_entity,
571                };
572
573                // Fetch the bind groups for this (view, phase) combination.
574                let Some(phase_bin_unpacking_bind_groups) =
575                    bin_unpacking_bind_groups.get(&bin_unpacking_buffers_key)
576                else {
577                    continue;
578                };
579
580                // Invoke the shader for all batch sets corresponding to indexed
581                // meshes and then for all batch sets corresponding to
582                // non-indexed meshes.
583                for bin_unpacking_bind_group in phase_bin_unpacking_bind_groups
584                    .indexed
585                    .iter()
586                    .chain(phase_bin_unpacking_bind_groups.non_indexed.iter())
587                {
588                    compute_pass.set_bind_group(0, &bin_unpacking_bind_group.bind_group, &[]);
589                    let workgroup_count = (bin_unpacking_bind_group.mesh_instance_count as usize)
590                        .div_ceil(WORKGROUP_SIZE);
591                    if workgroup_count > 0 {
592                        compute_pass.dispatch_workgroups(workgroup_count as u32, 1, 1);
593                    }
594                }
595            }
596        }
597    }
598
599    pass_span.end(&mut compute_pass);
600}
601
602pub fn early_gpu_preprocess(
603    current_view: ViewQuery<Option<&ViewLightEntities>, Without<SkipGpuPreprocess>>,
604    view_query: Query<
605        (
606            &ExtractedView,
607            Option<&PreprocessBindGroups>,
608            Option<&ViewUniformOffset>,
609            Has<NoIndirectDrawing>,
610            Has<OcclusionCulling>,
611        ),
612        Without<SkipGpuPreprocess>,
613    >,
614    light_query: Query<&LightEntity>,
615    batched_instance_buffers: Res<BatchedInstanceBuffers<MeshUniform, MeshInputUniform>>,
616    pipeline_cache: Res<PipelineCache>,
617    preprocess_pipelines: Res<PreprocessPipelines>,
618    mut ctx: RenderContext,
619) {
620    let diagnostics = ctx.diagnostic_recorder();
621    let diagnostics = diagnostics.as_deref();
622
623    let command_encoder = ctx.command_encoder();
624
625    let mut compute_pass = command_encoder.begin_compute_pass(&ComputePassDescriptor {
626        label: Some("early_mesh_preprocessing"),
627        timestamp_writes: None,
628    });
629
630    let pass_span = diagnostics.pass_span(&mut compute_pass, "early_mesh_preprocessing");
631
632    let view_entity = current_view.entity();
633    let shadow_cascade_views = current_view.into_inner();
634    let all_views =
635        gather_shadow_cascades_for_view(view_entity, shadow_cascade_views, &light_query);
636
637    // Run the compute passes.
638    for view_entity in all_views {
639        let Ok((view, bind_groups, view_uniform_offset, no_indirect_drawing, occlusion_culling)) =
640            view_query.get(view_entity)
641        else {
642            continue;
643        };
644
645        let Some(bind_groups) = bind_groups else {
646            continue;
647        };
648        let Some(view_uniform_offset) = view_uniform_offset else {
649            continue;
650        };
651
652        // Select the right pipeline, depending on whether GPU culling is in
653        // use.
654        let maybe_pipeline_id = if no_indirect_drawing {
655            preprocess_pipelines.direct_preprocess.pipeline_id
656        } else if occlusion_culling {
657            preprocess_pipelines
658                .early_gpu_occlusion_culling_preprocess
659                .pipeline_id
660        } else {
661            preprocess_pipelines
662                .gpu_frustum_culling_preprocess
663                .pipeline_id
664        };
665
666        // Fetch the pipeline.
667        let Some(preprocess_pipeline_id) = maybe_pipeline_id else {
668            warn!("The build mesh uniforms pipeline wasn't ready");
669            continue;
670        };
671
672        let Some(preprocess_pipeline) = pipeline_cache.get_compute_pipeline(preprocess_pipeline_id)
673        else {
674            // This will happen while the pipeline is being compiled and is fine.
675            continue;
676        };
677
678        compute_pass.set_pipeline(preprocess_pipeline);
679
680        // Loop over each render phase.
681        for (phase_type_id, batched_phase_instance_buffers) in
682            &batched_instance_buffers.phase_instance_buffers
683        {
684            // Grab the work item buffers for this view.
685            let Some(work_item_buffers) = batched_phase_instance_buffers
686                .work_item_buffers
687                .get(&view.retained_view_entity)
688            else {
689                continue;
690            };
691
692            // Fetch the bind group for the render phase.
693            let Some(phase_bind_groups) = bind_groups.get(phase_type_id) else {
694                continue;
695            };
696
697            // Make sure the mesh preprocessing shader has access to the
698            // view info it needs to do culling and motion vector
699            // computation.
700            let dynamic_offsets = [view_uniform_offset.offset];
701
702            // Are we drawing directly or indirectly?
703            match *phase_bind_groups {
704                PhasePreprocessBindGroups::Direct(ref bind_group) => {
705                    // Invoke the mesh preprocessing shader to transform
706                    // meshes only, but not cull.
707                    let PreprocessWorkItemBuffers::Direct(work_item_buffer) = work_item_buffers
708                    else {
709                        continue;
710                    };
711                    compute_pass.set_bind_group(0, bind_group, &dynamic_offsets);
712                    let workgroup_count = work_item_buffer.len().div_ceil(WORKGROUP_SIZE);
713                    if workgroup_count > 0 {
714                        compute_pass.dispatch_workgroups(workgroup_count as u32, 1, 1);
715                    }
716                }
717
718                PhasePreprocessBindGroups::IndirectFrustumCulling {
719                    indexed: ref maybe_indexed_bind_group,
720                    non_indexed: ref maybe_non_indexed_bind_group,
721                }
722                | PhasePreprocessBindGroups::IndirectOcclusionCulling {
723                    early_indexed: ref maybe_indexed_bind_group,
724                    early_non_indexed: ref maybe_non_indexed_bind_group,
725                    ..
726                } => {
727                    // Invoke the mesh preprocessing shader to transform and
728                    // cull the meshes.
729                    let PreprocessWorkItemBuffers::Indirect {
730                        indexed: indexed_buffer,
731                        non_indexed: non_indexed_buffer,
732                        ..
733                    } = work_item_buffers
734                    else {
735                        continue;
736                    };
737
738                    // Transform and cull indexed meshes if there are any.
739                    if let Some(indexed_bind_group) = maybe_indexed_bind_group {
740                        if let PreprocessWorkItemBuffers::Indirect {
741                            gpu_occlusion_culling:
742                                Some(GpuOcclusionCullingWorkItemBuffers {
743                                    late_indirect_parameters_indexed_offset,
744                                    ..
745                                }),
746                            ..
747                        } = *work_item_buffers
748                        {
749                            compute_pass.set_immediates(
750                                0,
751                                bytemuck::bytes_of(&late_indirect_parameters_indexed_offset),
752                            );
753                        }
754
755                        compute_pass.set_bind_group(0, indexed_bind_group, &dynamic_offsets);
756                        let workgroup_count = indexed_buffer.len().div_ceil(WORKGROUP_SIZE);
757                        if workgroup_count > 0 {
758                            compute_pass.dispatch_workgroups(workgroup_count as u32, 1, 1);
759                        }
760                    }
761
762                    // Transform and cull non-indexed meshes if there are any.
763                    if let Some(non_indexed_bind_group) = maybe_non_indexed_bind_group {
764                        if let PreprocessWorkItemBuffers::Indirect {
765                            gpu_occlusion_culling:
766                                Some(GpuOcclusionCullingWorkItemBuffers {
767                                    late_indirect_parameters_non_indexed_offset,
768                                    ..
769                                }),
770                            ..
771                        } = *work_item_buffers
772                        {
773                            compute_pass.set_immediates(
774                                0,
775                                bytemuck::bytes_of(&late_indirect_parameters_non_indexed_offset),
776                            );
777                        }
778
779                        compute_pass.set_bind_group(0, non_indexed_bind_group, &dynamic_offsets);
780                        let workgroup_count = non_indexed_buffer.len().div_ceil(WORKGROUP_SIZE);
781                        if workgroup_count > 0 {
782                            compute_pass.dispatch_workgroups(workgroup_count as u32, 1, 1);
783                        }
784                    }
785                }
786            }
787        }
788    }
789
790    pass_span.end(&mut compute_pass);
791}
792
793/// A helper function that returns all the shadow cascades that need to be
794/// rendered for the given view, as well as the view itself.
795fn gather_shadow_cascades_for_view(
796    view_entity: Entity,
797    shadow_cascade_views: Option<&ViewLightEntities>,
798    light_query: &Query<&LightEntity>,
799) -> SmallVec<[Entity; 8]> {
800    let mut all_views: SmallVec<[_; 8]> = SmallVec::new();
801    all_views.push(view_entity);
802    if let Some(shadow_cascade_views) = shadow_cascade_views {
803        all_views.extend(
804            shadow_cascade_views
805                .lights
806                .iter()
807                .filter(|light_entity| {
808                    light_query.get(**light_entity).is_ok_and(|light_entity| {
809                        matches!(*light_entity, LightEntity::Directional { .. })
810                    })
811                })
812                .copied(),
813        );
814    }
815    all_views
816}
817
818pub fn late_gpu_preprocess(
819    current_view: ViewQuery<
820        (&ExtractedView, &PreprocessBindGroups, &ViewUniformOffset),
821        (
822            Without<SkipGpuPreprocess>,
823            Without<NoIndirectDrawing>,
824            With<OcclusionCulling>,
825            With<DepthPrepass>,
826        ),
827    >,
828    batched_instance_buffers: Res<BatchedInstanceBuffers<MeshUniform, MeshInputUniform>>,
829    pipeline_cache: Res<PipelineCache>,
830    preprocess_pipelines: Res<PreprocessPipelines>,
831    mut ctx: RenderContext,
832) {
833    let (view, bind_groups, view_uniform_offset) = current_view.into_inner();
834
835    // Fetch the pipeline BEFORE starting diagnostic spans to avoid panic on early return
836    let maybe_pipeline_id = preprocess_pipelines
837        .late_gpu_occlusion_culling_preprocess
838        .pipeline_id;
839
840    let Some(preprocess_pipeline_id) = maybe_pipeline_id else {
841        warn_once!("The build mesh uniforms pipeline wasn't ready");
842        return;
843    };
844
845    let Some(preprocess_pipeline) = pipeline_cache.get_compute_pipeline(preprocess_pipeline_id)
846    else {
847        // This will happen while the pipeline is being compiled and is fine.
848        return;
849    };
850
851    let diagnostics = ctx.diagnostic_recorder();
852    let diagnostics = diagnostics.as_deref();
853
854    let command_encoder = ctx.command_encoder();
855
856    let mut compute_pass = command_encoder.begin_compute_pass(&ComputePassDescriptor {
857        label: Some("late_mesh_preprocessing"),
858        timestamp_writes: None,
859    });
860
861    let pass_span = diagnostics.pass_span(&mut compute_pass, "late_mesh_preprocessing");
862
863    compute_pass.set_pipeline(preprocess_pipeline);
864
865    // Loop over each phase. Because we built the phases in parallel,
866    // each phase has a separate set of instance buffers.
867    for (phase_type_id, batched_phase_instance_buffers) in
868        &batched_instance_buffers.phase_instance_buffers
869    {
870        let UntypedPhaseBatchedInstanceBuffers {
871            ref work_item_buffers,
872            ref late_indexed_indirect_parameters_buffer,
873            ref late_non_indexed_indirect_parameters_buffer,
874            ..
875        } = *batched_phase_instance_buffers;
876
877        // Grab the work item buffers for this view.
878        let Some(phase_work_item_buffers) = work_item_buffers.get(&view.retained_view_entity)
879        else {
880            continue;
881        };
882
883        let (
884            PreprocessWorkItemBuffers::Indirect {
885                gpu_occlusion_culling:
886                    Some(GpuOcclusionCullingWorkItemBuffers {
887                        late_indirect_parameters_indexed_offset,
888                        late_indirect_parameters_non_indexed_offset,
889                        ..
890                    }),
891                ..
892            },
893            Some(PhasePreprocessBindGroups::IndirectOcclusionCulling {
894                late_indexed: maybe_late_indexed_bind_group,
895                late_non_indexed: maybe_late_non_indexed_bind_group,
896                ..
897            }),
898            Some(late_indexed_indirect_parameters_buffer),
899            Some(late_non_indexed_indirect_parameters_buffer),
900        ) = (
901            phase_work_item_buffers,
902            bind_groups.get(phase_type_id),
903            late_indexed_indirect_parameters_buffer.buffer(),
904            late_non_indexed_indirect_parameters_buffer.buffer(),
905        )
906        else {
907            continue;
908        };
909
910        let mut dynamic_offsets: SmallVec<[u32; 1]> = smallvec![];
911        dynamic_offsets.push(view_uniform_offset.offset);
912
913        // If there's no space reserved for work items, then don't
914        // bother doing the dispatch, as there can't possibly be any
915        // meshes of the given class (indexed or non-indexed) in this
916        // phase.
917
918        // Transform and cull indexed meshes if there are any.
919        if let Some(late_indexed_bind_group) = maybe_late_indexed_bind_group {
920            compute_pass.set_immediates(
921                0,
922                bytemuck::bytes_of(late_indirect_parameters_indexed_offset),
923            );
924
925            compute_pass.set_bind_group(0, late_indexed_bind_group, &dynamic_offsets);
926            compute_pass.dispatch_workgroups_indirect(
927                late_indexed_indirect_parameters_buffer,
928                (*late_indirect_parameters_indexed_offset as u64)
929                    * (size_of::<LatePreprocessWorkItemIndirectParameters>() as u64),
930            );
931        }
932
933        // Transform and cull non-indexed meshes if there are any.
934        if let Some(late_non_indexed_bind_group) = maybe_late_non_indexed_bind_group {
935            compute_pass.set_immediates(
936                0,
937                bytemuck::bytes_of(late_indirect_parameters_non_indexed_offset),
938            );
939
940            compute_pass.set_bind_group(0, late_non_indexed_bind_group, &dynamic_offsets);
941            compute_pass.dispatch_workgroups_indirect(
942                late_non_indexed_indirect_parameters_buffer,
943                (*late_indirect_parameters_non_indexed_offset as u64)
944                    * (size_of::<LatePreprocessWorkItemIndirectParameters>() as u64),
945            );
946        }
947    }
948
949    pass_span.end(&mut compute_pass);
950}
951
952pub fn early_prepass_build_indirect_parameters(
953    preprocess_pipelines: Res<PreprocessPipelines>,
954    build_indirect_params_bind_groups: Option<Res<BuildIndirectParametersBindGroups>>,
955    pipeline_cache: Res<PipelineCache>,
956    indirect_parameters_buffers: Option<Res<IndirectParametersBuffers>>,
957    mut ctx: RenderContext,
958) {
959    run_build_indirect_parameters(
960        &mut ctx,
961        build_indirect_params_bind_groups.as_deref(),
962        &pipeline_cache,
963        indirect_parameters_buffers.as_deref(),
964        &preprocess_pipelines.early_phase,
965        "early_prepass_indirect_parameters_building",
966    );
967}
968
969pub fn late_prepass_build_indirect_parameters(
970    preprocess_pipelines: Res<PreprocessPipelines>,
971    build_indirect_params_bind_groups: Option<Res<BuildIndirectParametersBindGroups>>,
972    pipeline_cache: Res<PipelineCache>,
973    indirect_parameters_buffers: Option<Res<IndirectParametersBuffers>>,
974    mut ctx: RenderContext,
975) {
976    run_build_indirect_parameters(
977        &mut ctx,
978        build_indirect_params_bind_groups.as_deref(),
979        &pipeline_cache,
980        indirect_parameters_buffers.as_deref(),
981        &preprocess_pipelines.late_phase,
982        "late_prepass_indirect_parameters_building",
983    );
984}
985
986/// Builds indirect parameters for the main opaque and transparent passes.
987///
988/// The unused `_current_view` parameter is necessary so that we don't try to
989/// render a main pass for shadow views.
990pub fn main_build_indirect_parameters(
991    _current_view: ViewQuery<Entity, Without<ShadowView>>,
992    preprocess_pipelines: Res<PreprocessPipelines>,
993    build_indirect_params_bind_groups: Option<Res<BuildIndirectParametersBindGroups>>,
994    pipeline_cache: Res<PipelineCache>,
995    indirect_parameters_buffers: Option<Res<IndirectParametersBuffers>>,
996    mut ctx: RenderContext,
997) {
998    run_build_indirect_parameters(
999        &mut ctx,
1000        build_indirect_params_bind_groups.as_deref(),
1001        &pipeline_cache,
1002        indirect_parameters_buffers.as_deref(),
1003        &preprocess_pipelines.main_phase,
1004        "main_indirect_parameters_building",
1005    );
1006}
1007
1008fn run_build_indirect_parameters(
1009    ctx: &mut RenderContext,
1010    build_indirect_params_bind_groups: Option<&BuildIndirectParametersBindGroups>,
1011    pipeline_cache: &PipelineCache,
1012    indirect_parameters_buffers: Option<&IndirectParametersBuffers>,
1013    preprocess_phase_pipelines: &PreprocessPhasePipelines,
1014    label: &'static str,
1015) {
1016    let Some(build_indirect_params_bind_groups) = build_indirect_params_bind_groups else {
1017        return;
1018    };
1019
1020    let Some(indirect_parameters_buffers) = indirect_parameters_buffers else {
1021        return;
1022    };
1023
1024    let command_encoder = ctx.command_encoder();
1025
1026    let mut compute_pass = command_encoder.begin_compute_pass(&ComputePassDescriptor {
1027        label: Some(label),
1028        timestamp_writes: None,
1029    });
1030
1031    // Fetch the pipeline.
1032    let (
1033        Some(reset_indirect_batch_sets_pipeline_id),
1034        Some(build_indexed_indirect_params_pipeline_id),
1035        Some(build_non_indexed_indirect_params_pipeline_id),
1036    ) = (
1037        preprocess_phase_pipelines
1038            .reset_indirect_batch_sets
1039            .pipeline_id,
1040        preprocess_phase_pipelines
1041            .gpu_occlusion_culling_build_indexed_indirect_params
1042            .pipeline_id,
1043        preprocess_phase_pipelines
1044            .gpu_occlusion_culling_build_non_indexed_indirect_params
1045            .pipeline_id,
1046    )
1047    else {
1048        warn!("The build indirect parameters pipelines weren't ready");
1049        return;
1050    };
1051
1052    let (
1053        Some(reset_indirect_batch_sets_pipeline),
1054        Some(build_indexed_indirect_params_pipeline),
1055        Some(build_non_indexed_indirect_params_pipeline),
1056    ) = (
1057        pipeline_cache.get_compute_pipeline(reset_indirect_batch_sets_pipeline_id),
1058        pipeline_cache.get_compute_pipeline(build_indexed_indirect_params_pipeline_id),
1059        pipeline_cache.get_compute_pipeline(build_non_indexed_indirect_params_pipeline_id),
1060    )
1061    else {
1062        // This will happen while the pipeline is being compiled and is fine.
1063        return;
1064    };
1065
1066    // Loop over each phase. As each has as separate set of buffers, we need to
1067    // build indirect parameters individually for each phase.
1068    for (phase_type_id, phase_build_indirect_params_bind_groups) in
1069        build_indirect_params_bind_groups.iter()
1070    {
1071        let Some(phase_indirect_parameters_buffers) =
1072            indirect_parameters_buffers.get(phase_type_id)
1073        else {
1074            continue;
1075        };
1076
1077        // Build indexed indirect parameters.
1078        if let (
1079            Some(reset_indexed_indirect_batch_sets_bind_group),
1080            Some(build_indirect_indexed_params_bind_group),
1081        ) = (
1082            &phase_build_indirect_params_bind_groups.reset_indexed_indirect_batch_sets,
1083            &phase_build_indirect_params_bind_groups.build_indexed_indirect,
1084        ) {
1085            compute_pass.set_pipeline(reset_indirect_batch_sets_pipeline);
1086            compute_pass.set_bind_group(0, reset_indexed_indirect_batch_sets_bind_group, &[]);
1087            let workgroup_count = phase_indirect_parameters_buffers
1088                .batch_set_count(true)
1089                .div_ceil(WORKGROUP_SIZE);
1090            if workgroup_count > 0 {
1091                compute_pass.dispatch_workgroups(workgroup_count as u32, 1, 1);
1092            }
1093
1094            compute_pass.set_pipeline(build_indexed_indirect_params_pipeline);
1095            compute_pass.set_bind_group(0, build_indirect_indexed_params_bind_group, &[]);
1096            let workgroup_count = phase_indirect_parameters_buffers
1097                .indexed
1098                .batch_count()
1099                .div_ceil(WORKGROUP_SIZE);
1100            if workgroup_count > 0 {
1101                compute_pass.dispatch_workgroups(workgroup_count as u32, 1, 1);
1102            }
1103        }
1104
1105        // Build non-indexed indirect parameters.
1106        if let (
1107            Some(reset_non_indexed_indirect_batch_sets_bind_group),
1108            Some(build_indirect_non_indexed_params_bind_group),
1109        ) = (
1110            &phase_build_indirect_params_bind_groups.reset_non_indexed_indirect_batch_sets,
1111            &phase_build_indirect_params_bind_groups.build_non_indexed_indirect,
1112        ) {
1113            compute_pass.set_pipeline(reset_indirect_batch_sets_pipeline);
1114            compute_pass.set_bind_group(0, reset_non_indexed_indirect_batch_sets_bind_group, &[]);
1115            let workgroup_count = phase_indirect_parameters_buffers
1116                .batch_set_count(false)
1117                .div_ceil(WORKGROUP_SIZE);
1118            if workgroup_count > 0 {
1119                compute_pass.dispatch_workgroups(workgroup_count as u32, 1, 1);
1120            }
1121
1122            compute_pass.set_pipeline(build_non_indexed_indirect_params_pipeline);
1123            compute_pass.set_bind_group(0, build_indirect_non_indexed_params_bind_group, &[]);
1124            let workgroup_count = phase_indirect_parameters_buffers
1125                .non_indexed
1126                .batch_count()
1127                .div_ceil(WORKGROUP_SIZE);
1128            if workgroup_count > 0 {
1129                compute_pass.dispatch_workgroups(workgroup_count as u32, 1, 1);
1130            }
1131        }
1132    }
1133}
1134
1135impl PreprocessPipelines {
1136    /// Returns true if the preprocessing and indirect parameters pipelines have
1137    /// been loaded or false otherwise.
1138    pub(crate) fn pipelines_are_loaded(
1139        &self,
1140        pipeline_cache: &PipelineCache,
1141        preprocessing_support: &GpuPreprocessingSupport,
1142    ) -> bool {
1143        match preprocessing_support.max_supported_mode {
1144            GpuPreprocessingMode::None => false,
1145            GpuPreprocessingMode::PreprocessingOnly => {
1146                self.direct_preprocess.is_loaded(pipeline_cache)
1147                    && self
1148                        .gpu_frustum_culling_preprocess
1149                        .is_loaded(pipeline_cache)
1150            }
1151            GpuPreprocessingMode::Culling => {
1152                self.direct_preprocess.is_loaded(pipeline_cache)
1153                    && self
1154                        .gpu_frustum_culling_preprocess
1155                        .is_loaded(pipeline_cache)
1156                    && self
1157                        .early_gpu_occlusion_culling_preprocess
1158                        .is_loaded(pipeline_cache)
1159                    && self
1160                        .late_gpu_occlusion_culling_preprocess
1161                        .is_loaded(pipeline_cache)
1162                    && self
1163                        .gpu_frustum_culling_build_indexed_indirect_params
1164                        .is_loaded(pipeline_cache)
1165                    && self
1166                        .gpu_frustum_culling_build_non_indexed_indirect_params
1167                        .is_loaded(pipeline_cache)
1168                    && self.early_phase.is_loaded(pipeline_cache)
1169                    && self.late_phase.is_loaded(pipeline_cache)
1170                    && self.main_phase.is_loaded(pipeline_cache)
1171            }
1172        }
1173    }
1174}
1175
1176impl PreprocessPhasePipelines {
1177    fn is_loaded(&self, pipeline_cache: &PipelineCache) -> bool {
1178        self.reset_indirect_batch_sets.is_loaded(pipeline_cache)
1179            && self
1180                .gpu_occlusion_culling_build_indexed_indirect_params
1181                .is_loaded(pipeline_cache)
1182            && self
1183                .gpu_occlusion_culling_build_non_indexed_indirect_params
1184                .is_loaded(pipeline_cache)
1185    }
1186}
1187
1188impl PreprocessPipeline {
1189    fn is_loaded(&self, pipeline_cache: &PipelineCache) -> bool {
1190        self.pipeline_id
1191            .is_some_and(|pipeline_id| pipeline_cache.get_compute_pipeline(pipeline_id).is_some())
1192    }
1193}
1194
1195impl ResetIndirectBatchSetsPipeline {
1196    fn is_loaded(&self, pipeline_cache: &PipelineCache) -> bool {
1197        self.pipeline_id
1198            .is_some_and(|pipeline_id| pipeline_cache.get_compute_pipeline(pipeline_id).is_some())
1199    }
1200}
1201
1202impl BuildIndirectParametersPipeline {
1203    /// Returns true if this pipeline has been loaded into the pipeline cache or
1204    /// false otherwise.
1205    fn is_loaded(&self, pipeline_cache: &PipelineCache) -> bool {
1206        self.pipeline_id
1207            .is_some_and(|pipeline_id| pipeline_cache.get_compute_pipeline(pipeline_id).is_some())
1208    }
1209}
1210
1211impl SpecializedComputePipeline for PreprocessPipeline {
1212    type Key = PreprocessPipelineKey;
1213
1214    fn specialize(&self, key: Self::Key) -> ComputePipelineDescriptor {
1215        let mut shader_defs = vec!["WRITE_INDIRECT_PARAMETERS_METADATA".into()];
1216        if key.contains(PreprocessPipelineKey::FRUSTUM_CULLING) {
1217            shader_defs.push("INDIRECT".into());
1218            shader_defs.push("FRUSTUM_CULLING".into());
1219        }
1220        if key.contains(PreprocessPipelineKey::OCCLUSION_CULLING) {
1221            shader_defs.push("OCCLUSION_CULLING".into());
1222            if key.contains(PreprocessPipelineKey::EARLY_PHASE) {
1223                shader_defs.push("EARLY_PHASE".into());
1224            } else {
1225                shader_defs.push("LATE_PHASE".into());
1226            }
1227        }
1228
1229        ComputePipelineDescriptor {
1230            label: Some(
1231                format!(
1232                    "mesh preprocessing ({})",
1233                    if key.contains(
1234                        PreprocessPipelineKey::OCCLUSION_CULLING
1235                            | PreprocessPipelineKey::EARLY_PHASE
1236                    ) {
1237                        "early GPU occlusion culling"
1238                    } else if key.contains(PreprocessPipelineKey::OCCLUSION_CULLING) {
1239                        "late GPU occlusion culling"
1240                    } else if key.contains(PreprocessPipelineKey::FRUSTUM_CULLING) {
1241                        "GPU frustum culling"
1242                    } else {
1243                        "direct"
1244                    }
1245                )
1246                .into(),
1247            ),
1248            layout: vec![self.bind_group_layout.clone()],
1249            immediate_size: if key.contains(PreprocessPipelineKey::OCCLUSION_CULLING) {
1250                4
1251            } else {
1252                0
1253            },
1254            shader: self.shader.clone(),
1255            shader_defs,
1256            ..default()
1257        }
1258    }
1259}
1260
1261impl FromWorld for PreprocessPipelines {
1262    fn from_world(world: &mut World) -> Self {
1263        // GPU culling bind group parameters are a superset of those in the CPU
1264        // culling (direct) shader.
1265        let direct_bind_group_layout_entries = preprocess_direct_bind_group_layout_entries();
1266        let gpu_frustum_culling_bind_group_layout_entries = gpu_culling_bind_group_layout_entries();
1267        let gpu_early_occlusion_culling_bind_group_layout_entries =
1268            gpu_occlusion_culling_bind_group_layout_entries().extend_with_indices((
1269                (
1270                    12,
1271                    storage_buffer::<PreprocessWorkItem>(/*has_dynamic_offset=*/ false),
1272                ),
1273                (
1274                    13,
1275                    storage_buffer::<LatePreprocessWorkItemIndirectParameters>(
1276                        /*has_dynamic_offset=*/ false,
1277                    ),
1278                ),
1279            ));
1280        let gpu_late_occlusion_culling_bind_group_layout_entries =
1281            gpu_occlusion_culling_bind_group_layout_entries().extend_with_indices(((
1282                13,
1283                storage_buffer_read_only::<LatePreprocessWorkItemIndirectParameters>(
1284                    /*has_dynamic_offset=*/ false,
1285                ),
1286            ),));
1287
1288        let reset_indirect_batch_sets_bind_group_layout_entries =
1289            DynamicBindGroupLayoutEntries::sequential(
1290                ShaderStages::COMPUTE,
1291                (storage_buffer::<IndirectBatchSet>(false),),
1292            );
1293
1294        // Indexed and non-indexed bind group parameters share all the bind
1295        // group layout entries except the final one.
1296        let build_indexed_indirect_params_bind_group_layout_entries =
1297            build_indirect_params_bind_group_layout_entries()
1298                .extend_sequential((storage_buffer::<IndirectParametersIndexed>(false),));
1299        let build_non_indexed_indirect_params_bind_group_layout_entries =
1300            build_indirect_params_bind_group_layout_entries()
1301                .extend_sequential((storage_buffer::<IndirectParametersNonIndexed>(false),));
1302
1303        let bin_unpacking_bind_group_layout_entries = bin_unpacking_bind_group_layout_entries();
1304
1305        // Create the bind group layouts.
1306        let direct_bind_group_layout = BindGroupLayoutDescriptor::new(
1307            "build mesh uniforms direct bind group layout",
1308            &direct_bind_group_layout_entries,
1309        );
1310        let gpu_frustum_culling_bind_group_layout = BindGroupLayoutDescriptor::new(
1311            "build mesh uniforms GPU frustum culling bind group layout",
1312            &gpu_frustum_culling_bind_group_layout_entries,
1313        );
1314        let gpu_early_occlusion_culling_bind_group_layout = BindGroupLayoutDescriptor::new(
1315            "build mesh uniforms GPU early occlusion culling bind group layout",
1316            &gpu_early_occlusion_culling_bind_group_layout_entries,
1317        );
1318        let gpu_late_occlusion_culling_bind_group_layout = BindGroupLayoutDescriptor::new(
1319            "build mesh uniforms GPU late occlusion culling bind group layout",
1320            &gpu_late_occlusion_culling_bind_group_layout_entries,
1321        );
1322        let reset_indirect_batch_sets_bind_group_layout = BindGroupLayoutDescriptor::new(
1323            "reset indirect batch sets bind group layout",
1324            &reset_indirect_batch_sets_bind_group_layout_entries,
1325        );
1326        let build_indexed_indirect_params_bind_group_layout = BindGroupLayoutDescriptor::new(
1327            "build indexed indirect parameters bind group layout",
1328            &build_indexed_indirect_params_bind_group_layout_entries,
1329        );
1330        let build_non_indexed_indirect_params_bind_group_layout = BindGroupLayoutDescriptor::new(
1331            "build non-indexed indirect parameters bind group layout",
1332            &build_non_indexed_indirect_params_bind_group_layout_entries,
1333        );
1334        let bin_unpacking_bind_group_layout = BindGroupLayoutDescriptor::new(
1335            "bin unpacking bind group layout",
1336            &bin_unpacking_bind_group_layout_entries,
1337        );
1338
1339        let preprocess_shader = load_embedded_asset!(world, "mesh_preprocess.wgsl");
1340        let reset_indirect_batch_sets_shader =
1341            load_embedded_asset!(world, "reset_indirect_batch_sets.wgsl");
1342        let build_indirect_params_shader =
1343            load_embedded_asset!(world, "build_indirect_params.wgsl");
1344        let bin_unpacking_shader = load_embedded_asset!(world, "unpack_bins.wgsl");
1345
1346        let preprocess_phase_pipelines = PreprocessPhasePipelines {
1347            reset_indirect_batch_sets: ResetIndirectBatchSetsPipeline {
1348                bind_group_layout: reset_indirect_batch_sets_bind_group_layout.clone(),
1349                shader: reset_indirect_batch_sets_shader,
1350                pipeline_id: None,
1351            },
1352            gpu_occlusion_culling_build_indexed_indirect_params: BuildIndirectParametersPipeline {
1353                bind_group_layout: build_indexed_indirect_params_bind_group_layout.clone(),
1354                shader: build_indirect_params_shader.clone(),
1355                pipeline_id: None,
1356            },
1357            gpu_occlusion_culling_build_non_indexed_indirect_params:
1358                BuildIndirectParametersPipeline {
1359                    bind_group_layout: build_non_indexed_indirect_params_bind_group_layout.clone(),
1360                    shader: build_indirect_params_shader.clone(),
1361                    pipeline_id: None,
1362                },
1363        };
1364
1365        PreprocessPipelines {
1366            direct_preprocess: PreprocessPipeline {
1367                bind_group_layout: direct_bind_group_layout,
1368                shader: preprocess_shader.clone(),
1369                pipeline_id: None,
1370            },
1371            gpu_frustum_culling_preprocess: PreprocessPipeline {
1372                bind_group_layout: gpu_frustum_culling_bind_group_layout,
1373                shader: preprocess_shader.clone(),
1374                pipeline_id: None,
1375            },
1376            early_gpu_occlusion_culling_preprocess: PreprocessPipeline {
1377                bind_group_layout: gpu_early_occlusion_culling_bind_group_layout,
1378                shader: preprocess_shader.clone(),
1379                pipeline_id: None,
1380            },
1381            late_gpu_occlusion_culling_preprocess: PreprocessPipeline {
1382                bind_group_layout: gpu_late_occlusion_culling_bind_group_layout,
1383                shader: preprocess_shader,
1384                pipeline_id: None,
1385            },
1386            gpu_frustum_culling_build_indexed_indirect_params: BuildIndirectParametersPipeline {
1387                bind_group_layout: build_indexed_indirect_params_bind_group_layout.clone(),
1388                shader: build_indirect_params_shader.clone(),
1389                pipeline_id: None,
1390            },
1391            gpu_frustum_culling_build_non_indexed_indirect_params:
1392                BuildIndirectParametersPipeline {
1393                    bind_group_layout: build_non_indexed_indirect_params_bind_group_layout.clone(),
1394                    shader: build_indirect_params_shader,
1395                    pipeline_id: None,
1396                },
1397            early_phase: preprocess_phase_pipelines.clone(),
1398            late_phase: preprocess_phase_pipelines.clone(),
1399            main_phase: preprocess_phase_pipelines.clone(),
1400            bin_unpacking: BinUnpackingPipeline {
1401                bind_group_layout: bin_unpacking_bind_group_layout,
1402                shader: bin_unpacking_shader,
1403                pipeline_id: None,
1404            },
1405        }
1406    }
1407}
1408
1409fn preprocess_direct_bind_group_layout_entries() -> DynamicBindGroupLayoutEntries {
1410    DynamicBindGroupLayoutEntries::new_with_indices(
1411        ShaderStages::COMPUTE,
1412        (
1413            // `view`
1414            (
1415                0,
1416                uniform_buffer::<ViewUniform>(/* has_dynamic_offset= */ true),
1417            ),
1418            // `current_input`
1419            (3, storage_buffer_read_only::<MeshInputUniform>(false)),
1420            // `previous_input`
1421            (4, storage_buffer_read_only::<MeshInputUniform>(false)),
1422            // `indices`
1423            (5, storage_buffer_read_only::<PreprocessWorkItem>(false)),
1424            // `output`
1425            (6, storage_buffer::<MeshUniform>(false)),
1426        ),
1427    )
1428}
1429
1430// Returns the first 4 bind group layout entries shared between all invocations
1431// of the indirect parameters building shader.
1432fn build_indirect_params_bind_group_layout_entries() -> DynamicBindGroupLayoutEntries {
1433    DynamicBindGroupLayoutEntries::new_with_indices(
1434        ShaderStages::COMPUTE,
1435        (
1436            (0, storage_buffer_read_only::<MeshInputUniform>(false)),
1437            (
1438                1,
1439                storage_buffer_read_only::<IndirectParametersCpuMetadata>(false),
1440            ),
1441            (
1442                2,
1443                storage_buffer_read_only::<IndirectParametersGpuMetadata>(false),
1444            ),
1445            (3, storage_buffer::<IndirectBatchSet>(false)),
1446        ),
1447    )
1448}
1449
1450/// A system that specializes the `mesh_preprocess.wgsl` and
1451/// `build_indirect_params.wgsl` pipelines if necessary.
1452fn gpu_culling_bind_group_layout_entries() -> DynamicBindGroupLayoutEntries {
1453    // GPU culling bind group parameters are a superset of those in the CPU
1454    // culling (direct) shader.
1455    preprocess_direct_bind_group_layout_entries().extend_with_indices((
1456        // `indirect_parameters_cpu_metadata`
1457        (
1458            7,
1459            storage_buffer_read_only::<IndirectParametersCpuMetadata>(
1460                /* has_dynamic_offset= */ false,
1461            ),
1462        ),
1463        // `indirect_parameters_gpu_metadata`
1464        (
1465            8,
1466            storage_buffer::<IndirectParametersGpuMetadata>(/* has_dynamic_offset= */ false),
1467        ),
1468        // `mesh_culling_data`
1469        (
1470            9,
1471            storage_buffer_read_only::<MeshCullingData>(/* has_dynamic_offset= */ false),
1472        ),
1473        // `visibility_ranges`
1474        (
1475            10,
1476            storage_buffer_read_only::<Vec4>(/* has_dynamic_offset= */ false),
1477        ),
1478    ))
1479}
1480
1481fn gpu_occlusion_culling_bind_group_layout_entries() -> DynamicBindGroupLayoutEntries {
1482    gpu_culling_bind_group_layout_entries().extend_with_indices((
1483        (
1484            2,
1485            uniform_buffer::<PreviousViewData>(/*has_dynamic_offset=*/ false),
1486        ),
1487        (
1488            11,
1489            texture_2d(TextureSampleType::Float { filterable: true }),
1490        ),
1491    ))
1492}
1493
1494/// Creates and returns bind group layout entries for the GPU bin unpacking
1495/// shader (`unpack_bins`).
1496fn bin_unpacking_bind_group_layout_entries() -> BindGroupLayoutEntries<4> {
1497    BindGroupLayoutEntries::sequential(
1498        ShaderStages::COMPUTE,
1499        (
1500            // @group(0) @binding(0) var<uniform> bin_unpacking_metadata:
1501            // BinUnpackingMetadata;
1502            uniform_buffer::<GpuBinUnpackingMetadata>(false),
1503            // @group(0) @binding(1) var<storage> binned_mesh_instances:
1504            // array<BinnedMeshInstance>;
1505            storage_buffer_read_only::<GpuRenderBinnedMeshInstance>(false),
1506            // @group(0) @binding(2) var<storage, read_write>
1507            // preprocess_work_items: array<PreprocessWorkItem>;
1508            storage_buffer::<PreprocessWorkItem>(false),
1509            // @group(0) @binding(3) var<storage>
1510            // bin_index_to_indirect_parameters_offset: array<u32>;
1511            storage_buffer_read_only::<u32>(false),
1512        ),
1513    )
1514}
1515
1516/// A system that specializes the pipelines relating to mesh preprocessing if
1517/// necessary.
1518///
1519/// These pipelines include those corresponding to the mesh preprocessing shader
1520/// itself, in addition to those corresponding to the indirect batch set
1521/// resetting shader, the indirect parameters building shader, and the bin
1522/// unpacking shader.
1523pub fn prepare_preprocess_pipelines(
1524    pipeline_cache: Res<PipelineCache>,
1525    render_device: Res<RenderDevice>,
1526    mut specialized_preprocess_pipelines: ResMut<SpecializedComputePipelines<PreprocessPipeline>>,
1527    mut specialized_reset_indirect_batch_sets_pipelines: ResMut<
1528        SpecializedComputePipelines<ResetIndirectBatchSetsPipeline>,
1529    >,
1530    mut specialized_build_indirect_parameters_pipelines: ResMut<
1531        SpecializedComputePipelines<BuildIndirectParametersPipeline>,
1532    >,
1533    mut specialized_bin_unpacking_pipelines: ResMut<
1534        SpecializedComputePipelines<BinUnpackingPipeline>,
1535    >,
1536    preprocess_pipelines: ResMut<PreprocessPipelines>,
1537    gpu_preprocessing_support: Res<GpuPreprocessingSupport>,
1538) {
1539    let preprocess_pipelines = preprocess_pipelines.into_inner();
1540
1541    preprocess_pipelines.direct_preprocess.prepare(
1542        &pipeline_cache,
1543        &mut specialized_preprocess_pipelines,
1544        PreprocessPipelineKey::empty(),
1545    );
1546    preprocess_pipelines.gpu_frustum_culling_preprocess.prepare(
1547        &pipeline_cache,
1548        &mut specialized_preprocess_pipelines,
1549        PreprocessPipelineKey::FRUSTUM_CULLING,
1550    );
1551
1552    if gpu_preprocessing_support.is_culling_supported() {
1553        preprocess_pipelines
1554            .early_gpu_occlusion_culling_preprocess
1555            .prepare(
1556                &pipeline_cache,
1557                &mut specialized_preprocess_pipelines,
1558                PreprocessPipelineKey::FRUSTUM_CULLING
1559                    | PreprocessPipelineKey::OCCLUSION_CULLING
1560                    | PreprocessPipelineKey::EARLY_PHASE,
1561            );
1562        preprocess_pipelines
1563            .late_gpu_occlusion_culling_preprocess
1564            .prepare(
1565                &pipeline_cache,
1566                &mut specialized_preprocess_pipelines,
1567                PreprocessPipelineKey::FRUSTUM_CULLING | PreprocessPipelineKey::OCCLUSION_CULLING,
1568            );
1569    }
1570
1571    let mut build_indirect_parameters_pipeline_key = BuildIndirectParametersPipelineKey::empty();
1572
1573    // If the GPU and driver support `multi_draw_indirect_count`, tell the
1574    // shader that.
1575    if render_device
1576        .wgpu_device()
1577        .features()
1578        .contains(WgpuFeatures::MULTI_DRAW_INDIRECT_COUNT)
1579    {
1580        build_indirect_parameters_pipeline_key
1581            .insert(BuildIndirectParametersPipelineKey::MULTI_DRAW_INDIRECT_COUNT_SUPPORTED);
1582    }
1583
1584    preprocess_pipelines
1585        .gpu_frustum_culling_build_indexed_indirect_params
1586        .prepare(
1587            &pipeline_cache,
1588            &mut specialized_build_indirect_parameters_pipelines,
1589            build_indirect_parameters_pipeline_key | BuildIndirectParametersPipelineKey::INDEXED,
1590        );
1591    preprocess_pipelines
1592        .gpu_frustum_culling_build_non_indexed_indirect_params
1593        .prepare(
1594            &pipeline_cache,
1595            &mut specialized_build_indirect_parameters_pipelines,
1596            build_indirect_parameters_pipeline_key,
1597        );
1598
1599    if !gpu_preprocessing_support.is_culling_supported() {
1600        return;
1601    }
1602
1603    for (preprocess_phase_pipelines, build_indirect_parameters_phase_pipeline_key) in [
1604        (
1605            &mut preprocess_pipelines.early_phase,
1606            BuildIndirectParametersPipelineKey::EARLY_PHASE,
1607        ),
1608        (
1609            &mut preprocess_pipelines.late_phase,
1610            BuildIndirectParametersPipelineKey::LATE_PHASE,
1611        ),
1612        (
1613            &mut preprocess_pipelines.main_phase,
1614            BuildIndirectParametersPipelineKey::MAIN_PHASE,
1615        ),
1616    ] {
1617        preprocess_phase_pipelines
1618            .reset_indirect_batch_sets
1619            .prepare(
1620                &pipeline_cache,
1621                &mut specialized_reset_indirect_batch_sets_pipelines,
1622            );
1623        preprocess_phase_pipelines
1624            .gpu_occlusion_culling_build_indexed_indirect_params
1625            .prepare(
1626                &pipeline_cache,
1627                &mut specialized_build_indirect_parameters_pipelines,
1628                build_indirect_parameters_pipeline_key
1629                    | build_indirect_parameters_phase_pipeline_key
1630                    | BuildIndirectParametersPipelineKey::INDEXED
1631                    | BuildIndirectParametersPipelineKey::OCCLUSION_CULLING,
1632            );
1633        preprocess_phase_pipelines
1634            .gpu_occlusion_culling_build_non_indexed_indirect_params
1635            .prepare(
1636                &pipeline_cache,
1637                &mut specialized_build_indirect_parameters_pipelines,
1638                build_indirect_parameters_pipeline_key
1639                    | build_indirect_parameters_phase_pipeline_key
1640                    | BuildIndirectParametersPipelineKey::OCCLUSION_CULLING,
1641            );
1642    }
1643
1644    // Prepare the bin unpacking compute pipeline.
1645    preprocess_pipelines
1646        .bin_unpacking
1647        .prepare(&pipeline_cache, &mut specialized_bin_unpacking_pipelines);
1648}
1649
1650impl PreprocessPipeline {
1651    fn prepare(
1652        &mut self,
1653        pipeline_cache: &PipelineCache,
1654        pipelines: &mut SpecializedComputePipelines<PreprocessPipeline>,
1655        key: PreprocessPipelineKey,
1656    ) {
1657        if self.pipeline_id.is_some() {
1658            return;
1659        }
1660
1661        let preprocess_pipeline_id = pipelines.specialize(pipeline_cache, self, key);
1662        self.pipeline_id = Some(preprocess_pipeline_id);
1663    }
1664}
1665
1666impl SpecializedComputePipeline for ResetIndirectBatchSetsPipeline {
1667    type Key = ();
1668
1669    fn specialize(&self, _: Self::Key) -> ComputePipelineDescriptor {
1670        ComputePipelineDescriptor {
1671            label: Some("reset indirect batch sets".into()),
1672            layout: vec![self.bind_group_layout.clone()],
1673            shader: self.shader.clone(),
1674            ..default()
1675        }
1676    }
1677}
1678
1679impl SpecializedComputePipeline for BuildIndirectParametersPipeline {
1680    type Key = BuildIndirectParametersPipelineKey;
1681
1682    fn specialize(&self, key: Self::Key) -> ComputePipelineDescriptor {
1683        let mut shader_defs = vec![];
1684        if key.contains(BuildIndirectParametersPipelineKey::INDEXED) {
1685            shader_defs.push("INDEXED".into());
1686        }
1687        if key.contains(BuildIndirectParametersPipelineKey::MULTI_DRAW_INDIRECT_COUNT_SUPPORTED) {
1688            shader_defs.push("MULTI_DRAW_INDIRECT_COUNT_SUPPORTED".into());
1689        }
1690        if key.contains(BuildIndirectParametersPipelineKey::OCCLUSION_CULLING) {
1691            shader_defs.push("OCCLUSION_CULLING".into());
1692        }
1693        if key.contains(BuildIndirectParametersPipelineKey::EARLY_PHASE) {
1694            shader_defs.push("EARLY_PHASE".into());
1695        }
1696        if key.contains(BuildIndirectParametersPipelineKey::LATE_PHASE) {
1697            shader_defs.push("LATE_PHASE".into());
1698        }
1699        if key.contains(BuildIndirectParametersPipelineKey::MAIN_PHASE) {
1700            shader_defs.push("MAIN_PHASE".into());
1701        }
1702
1703        let label = format!(
1704            "{} build {}indexed indirect parameters",
1705            if !key.contains(BuildIndirectParametersPipelineKey::OCCLUSION_CULLING) {
1706                "frustum culling"
1707            } else if key.contains(BuildIndirectParametersPipelineKey::EARLY_PHASE) {
1708                "early occlusion culling"
1709            } else if key.contains(BuildIndirectParametersPipelineKey::LATE_PHASE) {
1710                "late occlusion culling"
1711            } else {
1712                "main occlusion culling"
1713            },
1714            if key.contains(BuildIndirectParametersPipelineKey::INDEXED) {
1715                ""
1716            } else {
1717                "non-"
1718            }
1719        );
1720
1721        ComputePipelineDescriptor {
1722            label: Some(label.into()),
1723            layout: vec![self.bind_group_layout.clone()],
1724            shader: self.shader.clone(),
1725            shader_defs,
1726            ..default()
1727        }
1728    }
1729}
1730
1731impl SpecializedComputePipeline for BinUnpackingPipeline {
1732    type Key = ();
1733
1734    fn specialize(&self, _: Self::Key) -> ComputePipelineDescriptor {
1735        ComputePipelineDescriptor {
1736            label: Some("bin unpacking".into()),
1737            layout: vec![self.bind_group_layout.clone()],
1738            shader: self.shader.clone(),
1739            shader_defs: vec![],
1740            ..default()
1741        }
1742    }
1743}
1744
1745impl ResetIndirectBatchSetsPipeline {
1746    fn prepare(
1747        &mut self,
1748        pipeline_cache: &PipelineCache,
1749        pipelines: &mut SpecializedComputePipelines<ResetIndirectBatchSetsPipeline>,
1750    ) {
1751        if self.pipeline_id.is_some() {
1752            return;
1753        }
1754
1755        let reset_indirect_batch_sets_pipeline_id = pipelines.specialize(pipeline_cache, self, ());
1756        self.pipeline_id = Some(reset_indirect_batch_sets_pipeline_id);
1757    }
1758}
1759
1760impl BuildIndirectParametersPipeline {
1761    fn prepare(
1762        &mut self,
1763        pipeline_cache: &PipelineCache,
1764        pipelines: &mut SpecializedComputePipelines<BuildIndirectParametersPipeline>,
1765        key: BuildIndirectParametersPipelineKey,
1766    ) {
1767        if self.pipeline_id.is_some() {
1768            return;
1769        }
1770
1771        let build_indirect_parameters_pipeline_id = pipelines.specialize(pipeline_cache, self, key);
1772        self.pipeline_id = Some(build_indirect_parameters_pipeline_id);
1773    }
1774}
1775
1776impl BinUnpackingPipeline {
1777    /// Specializes a single pipeline for the bin unpacking shader.
1778    fn prepare(
1779        &mut self,
1780        pipeline_cache: &PipelineCache,
1781        pipelines: &mut SpecializedComputePipelines<BinUnpackingPipeline>,
1782    ) {
1783        if self.pipeline_id.is_some() {
1784            return;
1785        }
1786
1787        let bin_unpacking_pipeline_id = pipelines.specialize(pipeline_cache, self, ());
1788        self.pipeline_id = Some(bin_unpacking_pipeline_id);
1789    }
1790}
1791
1792/// A system that attaches buffers to bind groups for the variants of the
1793/// compute shaders relating to mesh preprocessing.
1794#[expect(
1795    clippy::too_many_arguments,
1796    reason = "it's a system that needs a lot of arguments"
1797)]
1798pub fn prepare_preprocess_bind_groups(
1799    mut commands: Commands,
1800    views: Query<(Entity, &ExtractedView)>,
1801    view_depth_pyramids: Query<(&ViewDepthPyramid, &PreviousViewUniformOffset)>,
1802    render_device: Res<RenderDevice>,
1803    pipeline_cache: Res<PipelineCache>,
1804    batched_instance_buffers: Res<BatchedInstanceBuffers<MeshUniform, MeshInputUniform>>,
1805    indirect_parameters_buffers: Res<IndirectParametersBuffers>,
1806    bin_unpacking_buffers: Res<BinUnpackingBuffers>,
1807    mesh_culling_data_buffer: Res<MeshCullingDataBuffer>,
1808    visibility_ranges: Res<RenderVisibilityRanges>,
1809    view_uniforms: Res<ViewUniforms>,
1810    previous_view_uniforms: Res<PreviousViewUniforms>,
1811    pipelines: Res<PreprocessPipelines>,
1812    mut bin_unpacking_bind_groups: ResMut<BinUnpackingBindGroups>,
1813) {
1814    // Grab the `BatchedInstanceBuffers`.
1815    let BatchedInstanceBuffers {
1816        current_input_buffer: current_input_buffer_vec,
1817        previous_input_buffer: previous_input_buffer_vec,
1818        phase_instance_buffers,
1819    } = batched_instance_buffers.into_inner();
1820
1821    let (Some(current_input_buffer), Some(previous_input_buffer)) = (
1822        current_input_buffer_vec.buffer().buffer(),
1823        previous_input_buffer_vec.buffer(),
1824    ) else {
1825        return;
1826    };
1827
1828    // Record whether we have any meshes that are to be drawn indirectly. If we
1829    // don't, then we can skip building indirect parameters.
1830    let mut any_indirect = false;
1831
1832    // Loop over each view.
1833    for (view_entity, view) in &views {
1834        let mut bind_groups = TypeIdMap::default();
1835
1836        // Loop over each phase.
1837        for (phase_type_id, phase_instance_buffers) in phase_instance_buffers {
1838            let UntypedPhaseBatchedInstanceBuffers {
1839                data_buffer: ref data_buffer_vec,
1840                ref work_item_buffers,
1841                ref late_indexed_indirect_parameters_buffer,
1842                ref late_non_indexed_indirect_parameters_buffer,
1843            } = *phase_instance_buffers;
1844
1845            let Some(data_buffer) = data_buffer_vec.buffer() else {
1846                continue;
1847            };
1848
1849            // Grab the indirect parameters buffers for this phase.
1850            let Some(phase_indirect_parameters_buffers) =
1851                indirect_parameters_buffers.get(phase_type_id)
1852            else {
1853                continue;
1854            };
1855
1856            let Some(work_item_buffers) = work_item_buffers.get(&view.retained_view_entity) else {
1857                continue;
1858            };
1859
1860            // Create the `PreprocessBindGroupBuilder`.
1861            let preprocess_bind_group_builder = PreprocessBindGroupBuilder {
1862                view: view_entity,
1863                late_indexed_indirect_parameters_buffer,
1864                late_non_indexed_indirect_parameters_buffer,
1865                render_device: &render_device,
1866                pipeline_cache: &pipeline_cache,
1867                phase_indirect_parameters_buffers,
1868                mesh_culling_data_buffer: &mesh_culling_data_buffer,
1869                visibility_range_data_buffer: visibility_ranges.buffer(),
1870                view_uniforms: &view_uniforms,
1871                previous_view_uniforms: &previous_view_uniforms,
1872                pipelines: &pipelines,
1873                current_input_buffer,
1874                previous_input_buffer,
1875                data_buffer,
1876            };
1877
1878            // Depending on the type of work items we have, construct the
1879            // appropriate bind groups.
1880            let (was_indirect, bind_group) = match *work_item_buffers {
1881                PreprocessWorkItemBuffers::Direct(ref work_item_buffer) => (
1882                    false,
1883                    preprocess_bind_group_builder
1884                        .create_direct_preprocess_bind_groups(work_item_buffer),
1885                ),
1886
1887                PreprocessWorkItemBuffers::Indirect {
1888                    indexed: ref indexed_work_item_buffer,
1889                    non_indexed: ref non_indexed_work_item_buffer,
1890                    gpu_occlusion_culling: Some(ref gpu_occlusion_culling_work_item_buffers),
1891                } => (
1892                    true,
1893                    preprocess_bind_group_builder
1894                        .create_indirect_occlusion_culling_preprocess_bind_groups(
1895                            &view_depth_pyramids,
1896                            indexed_work_item_buffer,
1897                            non_indexed_work_item_buffer,
1898                            gpu_occlusion_culling_work_item_buffers,
1899                        ),
1900                ),
1901
1902                PreprocessWorkItemBuffers::Indirect {
1903                    indexed: ref indexed_work_item_buffer,
1904                    non_indexed: ref non_indexed_work_item_buffer,
1905                    gpu_occlusion_culling: None,
1906                } => (
1907                    true,
1908                    preprocess_bind_group_builder
1909                        .create_indirect_frustum_culling_preprocess_bind_groups(
1910                            indexed_work_item_buffer,
1911                            non_indexed_work_item_buffer,
1912                        ),
1913                ),
1914            };
1915
1916            // Write that bind group in.
1917            if let Some(bind_group) = bind_group {
1918                any_indirect = any_indirect || was_indirect;
1919                bind_groups.insert(*phase_type_id, bind_group);
1920            }
1921        }
1922
1923        // Save the bind groups.
1924        commands
1925            .entity(view_entity)
1926            .insert(PreprocessBindGroups(bind_groups));
1927    }
1928
1929    // Now, if there were any indirect draw commands, create the bind groups for
1930    // the indirect parameters building shader.
1931    if any_indirect {
1932        create_build_indirect_parameters_bind_groups(
1933            &mut commands,
1934            &render_device,
1935            &pipeline_cache,
1936            &pipelines,
1937            current_input_buffer,
1938            &indirect_parameters_buffers,
1939        );
1940    }
1941
1942    // Create the bind groups we'll need for each dispatch of the bin unpacking
1943    // (`unpack_bins`) shader.
1944    for (_, view) in &views {
1945        create_bin_unpacking_bind_groups(
1946            &mut bin_unpacking_bind_groups,
1947            &render_device,
1948            &pipeline_cache,
1949            &pipelines,
1950            &indirect_parameters_buffers,
1951            phase_instance_buffers,
1952            &bin_unpacking_buffers,
1953            &view.retained_view_entity,
1954        );
1955    }
1956}
1957
1958/// A temporary structure that stores all the information needed to construct
1959/// bind groups for the mesh preprocessing shader.
1960struct PreprocessBindGroupBuilder<'a> {
1961    /// The render-world entity corresponding to the current view.
1962    view: Entity,
1963    /// The indirect compute dispatch parameters buffer for indexed meshes in
1964    /// the late prepass.
1965    late_indexed_indirect_parameters_buffer:
1966        &'a RawBufferVec<LatePreprocessWorkItemIndirectParameters>,
1967    /// The indirect compute dispatch parameters buffer for non-indexed meshes
1968    /// in the late prepass.
1969    late_non_indexed_indirect_parameters_buffer:
1970        &'a RawBufferVec<LatePreprocessWorkItemIndirectParameters>,
1971    /// The device.
1972    render_device: &'a RenderDevice,
1973    /// The pipeline cache
1974    pipeline_cache: &'a PipelineCache,
1975    /// The buffers that store indirect draw parameters.
1976    phase_indirect_parameters_buffers: &'a UntypedPhaseIndirectParametersBuffers,
1977    /// The GPU buffer that stores the information needed to cull each mesh.
1978    mesh_culling_data_buffer: &'a MeshCullingDataBuffer,
1979    /// The device buffer that stores the information needed to process
1980    /// visibility ranges on the GPU.
1981    visibility_range_data_buffer: &'a BufferVec<Vec4>,
1982    /// The GPU buffer that stores information about the view.
1983    view_uniforms: &'a ViewUniforms,
1984    /// The GPU buffer that stores information about the view from last frame.
1985    previous_view_uniforms: &'a PreviousViewUniforms,
1986    /// The pipelines for the mesh preprocessing shader.
1987    pipelines: &'a PreprocessPipelines,
1988    /// The GPU buffer containing the list of [`MeshInputUniform`]s for the
1989    /// current frame.
1990    current_input_buffer: &'a Buffer,
1991    /// The GPU buffer containing the list of [`MeshInputUniform`]s for the
1992    /// previous frame.
1993    previous_input_buffer: &'a Buffer,
1994    /// The GPU buffer containing the list of [`MeshUniform`]s for the current
1995    /// frame.
1996    ///
1997    /// This is the buffer containing the mesh's final transforms that the
1998    /// shaders will write to.
1999    data_buffer: &'a Buffer,
2000}
2001
2002impl<'a> PreprocessBindGroupBuilder<'a> {
2003    /// Creates the bind groups for mesh preprocessing when GPU frustum culling
2004    /// and GPU occlusion culling are both disabled.
2005    fn create_direct_preprocess_bind_groups(
2006        &self,
2007        work_item_buffer: &RawBufferVec<PreprocessWorkItem>,
2008    ) -> Option<PhasePreprocessBindGroups> {
2009        // Don't use `as_entire_binding()` here; the shader reads the array
2010        // length and the underlying buffer may be longer than the actual size
2011        // of the vector.
2012        let work_item_buffer_size = NonZero::<u64>::try_from(
2013            work_item_buffer.len() as u64 * u64::from(PreprocessWorkItem::min_size()),
2014        )
2015        .ok();
2016
2017        Some(PhasePreprocessBindGroups::Direct(
2018            self.render_device.create_bind_group(
2019                "preprocess_direct_bind_group",
2020                &self
2021                    .pipeline_cache
2022                    .get_bind_group_layout(&self.pipelines.direct_preprocess.bind_group_layout),
2023                &BindGroupEntries::with_indices((
2024                    (0, self.view_uniforms.uniforms.binding()?),
2025                    (3, self.current_input_buffer.as_entire_binding()),
2026                    (4, self.previous_input_buffer.as_entire_binding()),
2027                    (
2028                        5,
2029                        BindingResource::Buffer(BufferBinding {
2030                            buffer: work_item_buffer.buffer()?,
2031                            offset: 0,
2032                            size: work_item_buffer_size,
2033                        }),
2034                    ),
2035                    (6, self.data_buffer.as_entire_binding()),
2036                )),
2037            ),
2038        ))
2039    }
2040
2041    /// Creates the bind groups for mesh preprocessing when GPU occlusion
2042    /// culling is enabled.
2043    fn create_indirect_occlusion_culling_preprocess_bind_groups(
2044        &self,
2045        view_depth_pyramids: &Query<(&ViewDepthPyramid, &PreviousViewUniformOffset)>,
2046        indexed_work_item_buffer: &PartialBufferVec<PreprocessWorkItem>,
2047        non_indexed_work_item_buffer: &PartialBufferVec<PreprocessWorkItem>,
2048        gpu_occlusion_culling_work_item_buffers: &GpuOcclusionCullingWorkItemBuffers,
2049    ) -> Option<PhasePreprocessBindGroups> {
2050        let GpuOcclusionCullingWorkItemBuffers {
2051            late_indexed: ref late_indexed_work_item_buffer,
2052            late_non_indexed: ref late_non_indexed_work_item_buffer,
2053            ..
2054        } = *gpu_occlusion_culling_work_item_buffers;
2055
2056        let (view_depth_pyramid, previous_view_uniform_offset) =
2057            view_depth_pyramids.get(self.view).ok()?;
2058
2059        Some(PhasePreprocessBindGroups::IndirectOcclusionCulling {
2060            early_indexed: self.create_indirect_occlusion_culling_early_indexed_bind_group(
2061                view_depth_pyramid,
2062                previous_view_uniform_offset,
2063                indexed_work_item_buffer,
2064                late_indexed_work_item_buffer,
2065            ),
2066
2067            early_non_indexed: self.create_indirect_occlusion_culling_early_non_indexed_bind_group(
2068                view_depth_pyramid,
2069                previous_view_uniform_offset,
2070                non_indexed_work_item_buffer,
2071                late_non_indexed_work_item_buffer,
2072            ),
2073
2074            late_indexed: self.create_indirect_occlusion_culling_late_indexed_bind_group(
2075                view_depth_pyramid,
2076                previous_view_uniform_offset,
2077                late_indexed_work_item_buffer,
2078            ),
2079
2080            late_non_indexed: self.create_indirect_occlusion_culling_late_non_indexed_bind_group(
2081                view_depth_pyramid,
2082                previous_view_uniform_offset,
2083                late_non_indexed_work_item_buffer,
2084            ),
2085        })
2086    }
2087
2088    /// Creates the bind group for the first phase of mesh preprocessing of
2089    /// indexed meshes when GPU occlusion culling is enabled.
2090    fn create_indirect_occlusion_culling_early_indexed_bind_group(
2091        &self,
2092        view_depth_pyramid: &ViewDepthPyramid,
2093        previous_view_uniform_offset: &PreviousViewUniformOffset,
2094        indexed_work_item_buffer: &PartialBufferVec<PreprocessWorkItem>,
2095        late_indexed_work_item_buffer: &UninitBufferVec<PreprocessWorkItem>,
2096    ) -> Option<BindGroup> {
2097        let mesh_culling_data_buffer = self.mesh_culling_data_buffer.buffer()?;
2098        let visibility_range_binding = self.visibility_range_data_buffer.binding()?;
2099        let view_uniforms_binding = self.view_uniforms.uniforms.binding()?;
2100        let previous_view_buffer = self.previous_view_uniforms.uniforms.buffer()?;
2101
2102        match (
2103            self.phase_indirect_parameters_buffers
2104                .indexed
2105                .cpu_metadata_buffer(),
2106            self.phase_indirect_parameters_buffers
2107                .indexed
2108                .gpu_metadata_buffer(),
2109            indexed_work_item_buffer.buffer(),
2110            late_indexed_work_item_buffer.buffer(),
2111            self.late_indexed_indirect_parameters_buffer.buffer(),
2112        ) {
2113            (
2114                Some(indexed_cpu_metadata_buffer),
2115                Some(indexed_gpu_metadata_buffer),
2116                Some(indexed_work_item_gpu_buffer),
2117                Some(late_indexed_work_item_gpu_buffer),
2118                Some(late_indexed_indirect_parameters_buffer),
2119            ) => {
2120                // Don't use `as_entire_binding()` here; the shader reads the array
2121                // length and the underlying buffer may be longer than the actual size
2122                // of the vector.
2123                let indexed_work_item_buffer_size = NonZero::<u64>::try_from(
2124                    indexed_work_item_buffer.len() as u64
2125                        * u64::from(PreprocessWorkItem::min_size()),
2126                )
2127                .ok();
2128
2129                Some(
2130                    self.render_device.create_bind_group(
2131                        "preprocess_early_indexed_gpu_occlusion_culling_bind_group",
2132                        &self.pipeline_cache.get_bind_group_layout(
2133                            &self
2134                                .pipelines
2135                                .early_gpu_occlusion_culling_preprocess
2136                                .bind_group_layout,
2137                        ),
2138                        &BindGroupEntries::with_indices((
2139                            (3, self.current_input_buffer.as_entire_binding()),
2140                            (4, self.previous_input_buffer.as_entire_binding()),
2141                            (
2142                                5,
2143                                BindingResource::Buffer(BufferBinding {
2144                                    buffer: indexed_work_item_gpu_buffer,
2145                                    offset: 0,
2146                                    size: indexed_work_item_buffer_size,
2147                                }),
2148                            ),
2149                            (6, self.data_buffer.as_entire_binding()),
2150                            (7, indexed_cpu_metadata_buffer.as_entire_binding()),
2151                            (8, indexed_gpu_metadata_buffer.as_entire_binding()),
2152                            (9, mesh_culling_data_buffer.as_entire_binding()),
2153                            (10, visibility_range_binding.clone()),
2154                            (0, view_uniforms_binding.clone()),
2155                            (11, &view_depth_pyramid.all_mips),
2156                            (
2157                                2,
2158                                BufferBinding {
2159                                    buffer: previous_view_buffer,
2160                                    offset: previous_view_uniform_offset.offset as u64,
2161                                    size: NonZeroU64::new(size_of::<PreviousViewData>() as u64),
2162                                },
2163                            ),
2164                            (
2165                                12,
2166                                BufferBinding {
2167                                    buffer: late_indexed_work_item_gpu_buffer,
2168                                    offset: 0,
2169                                    size: indexed_work_item_buffer_size,
2170                                },
2171                            ),
2172                            (
2173                                13,
2174                                BufferBinding {
2175                                    buffer: late_indexed_indirect_parameters_buffer,
2176                                    offset: 0,
2177                                    size: NonZeroU64::new(
2178                                        late_indexed_indirect_parameters_buffer.size(),
2179                                    ),
2180                                },
2181                            ),
2182                        )),
2183                    ),
2184                )
2185            }
2186            _ => None,
2187        }
2188    }
2189
2190    /// Creates the bind group for the first phase of mesh preprocessing of
2191    /// non-indexed meshes when GPU occlusion culling is enabled.
2192    fn create_indirect_occlusion_culling_early_non_indexed_bind_group(
2193        &self,
2194        view_depth_pyramid: &ViewDepthPyramid,
2195        previous_view_uniform_offset: &PreviousViewUniformOffset,
2196        non_indexed_work_item_buffer: &PartialBufferVec<PreprocessWorkItem>,
2197        late_non_indexed_work_item_buffer: &UninitBufferVec<PreprocessWorkItem>,
2198    ) -> Option<BindGroup> {
2199        let mesh_culling_data_buffer = self.mesh_culling_data_buffer.buffer()?;
2200        let visibility_range_binding = self.visibility_range_data_buffer.binding()?;
2201        let view_uniforms_binding = self.view_uniforms.uniforms.binding()?;
2202        let previous_view_buffer = self.previous_view_uniforms.uniforms.buffer()?;
2203
2204        match (
2205            self.phase_indirect_parameters_buffers
2206                .non_indexed
2207                .cpu_metadata_buffer(),
2208            self.phase_indirect_parameters_buffers
2209                .non_indexed
2210                .gpu_metadata_buffer(),
2211            non_indexed_work_item_buffer.buffer(),
2212            late_non_indexed_work_item_buffer.buffer(),
2213            self.late_non_indexed_indirect_parameters_buffer.buffer(),
2214        ) {
2215            (
2216                Some(non_indexed_cpu_metadata_buffer),
2217                Some(non_indexed_gpu_metadata_buffer),
2218                Some(non_indexed_work_item_gpu_buffer),
2219                Some(late_non_indexed_work_item_buffer),
2220                Some(late_non_indexed_indirect_parameters_buffer),
2221            ) => {
2222                // Don't use `as_entire_binding()` here; the shader reads the array
2223                // length and the underlying buffer may be longer than the actual size
2224                // of the vector.
2225                let non_indexed_work_item_buffer_size = NonZero::<u64>::try_from(
2226                    non_indexed_work_item_buffer.len() as u64
2227                        * u64::from(PreprocessWorkItem::min_size()),
2228                )
2229                .ok();
2230
2231                Some(
2232                    self.render_device.create_bind_group(
2233                        "preprocess_early_non_indexed_gpu_occlusion_culling_bind_group",
2234                        &self.pipeline_cache.get_bind_group_layout(
2235                            &self
2236                                .pipelines
2237                                .early_gpu_occlusion_culling_preprocess
2238                                .bind_group_layout,
2239                        ),
2240                        &BindGroupEntries::with_indices((
2241                            (3, self.current_input_buffer.as_entire_binding()),
2242                            (4, self.previous_input_buffer.as_entire_binding()),
2243                            (
2244                                5,
2245                                BindingResource::Buffer(BufferBinding {
2246                                    buffer: non_indexed_work_item_gpu_buffer,
2247                                    offset: 0,
2248                                    size: non_indexed_work_item_buffer_size,
2249                                }),
2250                            ),
2251                            (6, self.data_buffer.as_entire_binding()),
2252                            (7, non_indexed_cpu_metadata_buffer.as_entire_binding()),
2253                            (8, non_indexed_gpu_metadata_buffer.as_entire_binding()),
2254                            (9, mesh_culling_data_buffer.as_entire_binding()),
2255                            (10, visibility_range_binding.clone()),
2256                            (0, view_uniforms_binding.clone()),
2257                            (11, &view_depth_pyramid.all_mips),
2258                            (
2259                                2,
2260                                BufferBinding {
2261                                    buffer: previous_view_buffer,
2262                                    offset: previous_view_uniform_offset.offset as u64,
2263                                    size: NonZeroU64::new(size_of::<PreviousViewData>() as u64),
2264                                },
2265                            ),
2266                            (
2267                                12,
2268                                BufferBinding {
2269                                    buffer: late_non_indexed_work_item_buffer,
2270                                    offset: 0,
2271                                    size: non_indexed_work_item_buffer_size,
2272                                },
2273                            ),
2274                            (
2275                                13,
2276                                BufferBinding {
2277                                    buffer: late_non_indexed_indirect_parameters_buffer,
2278                                    offset: 0,
2279                                    size: NonZeroU64::new(
2280                                        late_non_indexed_indirect_parameters_buffer.size(),
2281                                    ),
2282                                },
2283                            ),
2284                        )),
2285                    ),
2286                )
2287            }
2288            _ => None,
2289        }
2290    }
2291
2292    /// Creates the bind group for the second phase of mesh preprocessing of
2293    /// indexed meshes when GPU occlusion culling is enabled.
2294    fn create_indirect_occlusion_culling_late_indexed_bind_group(
2295        &self,
2296        view_depth_pyramid: &ViewDepthPyramid,
2297        previous_view_uniform_offset: &PreviousViewUniformOffset,
2298        late_indexed_work_item_buffer: &UninitBufferVec<PreprocessWorkItem>,
2299    ) -> Option<BindGroup> {
2300        let mesh_culling_data_buffer = self.mesh_culling_data_buffer.buffer()?;
2301        let visibility_range_binding = self.visibility_range_data_buffer.binding()?;
2302        let view_uniforms_binding = self.view_uniforms.uniforms.binding()?;
2303        let previous_view_buffer = self.previous_view_uniforms.uniforms.buffer()?;
2304
2305        match (
2306            self.phase_indirect_parameters_buffers
2307                .indexed
2308                .cpu_metadata_buffer(),
2309            self.phase_indirect_parameters_buffers
2310                .indexed
2311                .gpu_metadata_buffer(),
2312            late_indexed_work_item_buffer.buffer(),
2313            self.late_indexed_indirect_parameters_buffer.buffer(),
2314        ) {
2315            (
2316                Some(indexed_cpu_metadata_buffer),
2317                Some(indexed_gpu_metadata_buffer),
2318                Some(late_indexed_work_item_gpu_buffer),
2319                Some(late_indexed_indirect_parameters_buffer),
2320            ) => {
2321                // Don't use `as_entire_binding()` here; the shader reads the array
2322                // length and the underlying buffer may be longer than the actual size
2323                // of the vector.
2324                let late_indexed_work_item_buffer_size = NonZero::<u64>::try_from(
2325                    late_indexed_work_item_buffer.len() as u64
2326                        * u64::from(PreprocessWorkItem::min_size()),
2327                )
2328                .ok();
2329
2330                Some(
2331                    self.render_device.create_bind_group(
2332                        "preprocess_late_indexed_gpu_occlusion_culling_bind_group",
2333                        &self.pipeline_cache.get_bind_group_layout(
2334                            &self
2335                                .pipelines
2336                                .late_gpu_occlusion_culling_preprocess
2337                                .bind_group_layout,
2338                        ),
2339                        &BindGroupEntries::with_indices((
2340                            (3, self.current_input_buffer.as_entire_binding()),
2341                            (4, self.previous_input_buffer.as_entire_binding()),
2342                            (
2343                                5,
2344                                BindingResource::Buffer(BufferBinding {
2345                                    buffer: late_indexed_work_item_gpu_buffer,
2346                                    offset: 0,
2347                                    size: late_indexed_work_item_buffer_size,
2348                                }),
2349                            ),
2350                            (6, self.data_buffer.as_entire_binding()),
2351                            (7, indexed_cpu_metadata_buffer.as_entire_binding()),
2352                            (8, indexed_gpu_metadata_buffer.as_entire_binding()),
2353                            (9, mesh_culling_data_buffer.as_entire_binding()),
2354                            (10, visibility_range_binding.clone()),
2355                            (0, view_uniforms_binding.clone()),
2356                            (11, &view_depth_pyramid.all_mips),
2357                            (
2358                                2,
2359                                BufferBinding {
2360                                    buffer: previous_view_buffer,
2361                                    offset: previous_view_uniform_offset.offset as u64,
2362                                    size: NonZeroU64::new(size_of::<PreviousViewData>() as u64),
2363                                },
2364                            ),
2365                            (
2366                                13,
2367                                BufferBinding {
2368                                    buffer: late_indexed_indirect_parameters_buffer,
2369                                    offset: 0,
2370                                    size: NonZeroU64::new(
2371                                        late_indexed_indirect_parameters_buffer.size(),
2372                                    ),
2373                                },
2374                            ),
2375                        )),
2376                    ),
2377                )
2378            }
2379            _ => None,
2380        }
2381    }
2382
2383    /// Creates the bind group for the second phase of mesh preprocessing of
2384    /// non-indexed meshes when GPU occlusion culling is enabled.
2385    fn create_indirect_occlusion_culling_late_non_indexed_bind_group(
2386        &self,
2387        view_depth_pyramid: &ViewDepthPyramid,
2388        previous_view_uniform_offset: &PreviousViewUniformOffset,
2389        late_non_indexed_work_item_buffer: &UninitBufferVec<PreprocessWorkItem>,
2390    ) -> Option<BindGroup> {
2391        let mesh_culling_data_buffer = self.mesh_culling_data_buffer.buffer()?;
2392        let visibility_range_binding = self.visibility_range_data_buffer.binding()?;
2393        let view_uniforms_binding = self.view_uniforms.uniforms.binding()?;
2394        let previous_view_buffer = self.previous_view_uniforms.uniforms.buffer()?;
2395
2396        match (
2397            self.phase_indirect_parameters_buffers
2398                .non_indexed
2399                .cpu_metadata_buffer(),
2400            self.phase_indirect_parameters_buffers
2401                .non_indexed
2402                .gpu_metadata_buffer(),
2403            late_non_indexed_work_item_buffer.buffer(),
2404            self.late_non_indexed_indirect_parameters_buffer.buffer(),
2405        ) {
2406            (
2407                Some(non_indexed_cpu_metadata_buffer),
2408                Some(non_indexed_gpu_metadata_buffer),
2409                Some(non_indexed_work_item_gpu_buffer),
2410                Some(late_non_indexed_indirect_parameters_buffer),
2411            ) => {
2412                // Don't use `as_entire_binding()` here; the shader reads the array
2413                // length and the underlying buffer may be longer than the actual size
2414                // of the vector.
2415                let non_indexed_work_item_buffer_size = NonZero::<u64>::try_from(
2416                    late_non_indexed_work_item_buffer.len() as u64
2417                        * u64::from(PreprocessWorkItem::min_size()),
2418                )
2419                .ok();
2420
2421                Some(
2422                    self.render_device.create_bind_group(
2423                        "preprocess_late_non_indexed_gpu_occlusion_culling_bind_group",
2424                        &self.pipeline_cache.get_bind_group_layout(
2425                            &self
2426                                .pipelines
2427                                .late_gpu_occlusion_culling_preprocess
2428                                .bind_group_layout,
2429                        ),
2430                        &BindGroupEntries::with_indices((
2431                            (3, self.current_input_buffer.as_entire_binding()),
2432                            (4, self.previous_input_buffer.as_entire_binding()),
2433                            (
2434                                5,
2435                                BindingResource::Buffer(BufferBinding {
2436                                    buffer: non_indexed_work_item_gpu_buffer,
2437                                    offset: 0,
2438                                    size: non_indexed_work_item_buffer_size,
2439                                }),
2440                            ),
2441                            (6, self.data_buffer.as_entire_binding()),
2442                            (7, non_indexed_cpu_metadata_buffer.as_entire_binding()),
2443                            (8, non_indexed_gpu_metadata_buffer.as_entire_binding()),
2444                            (9, mesh_culling_data_buffer.as_entire_binding()),
2445                            (10, visibility_range_binding.clone()),
2446                            (0, view_uniforms_binding.clone()),
2447                            (11, &view_depth_pyramid.all_mips),
2448                            (
2449                                2,
2450                                BufferBinding {
2451                                    buffer: previous_view_buffer,
2452                                    offset: previous_view_uniform_offset.offset as u64,
2453                                    size: NonZeroU64::new(size_of::<PreviousViewData>() as u64),
2454                                },
2455                            ),
2456                            (
2457                                13,
2458                                BufferBinding {
2459                                    buffer: late_non_indexed_indirect_parameters_buffer,
2460                                    offset: 0,
2461                                    size: NonZeroU64::new(
2462                                        late_non_indexed_indirect_parameters_buffer.size(),
2463                                    ),
2464                                },
2465                            ),
2466                        )),
2467                    ),
2468                )
2469            }
2470            _ => None,
2471        }
2472    }
2473
2474    /// Creates the bind groups for mesh preprocessing when GPU frustum culling
2475    /// is enabled, but GPU occlusion culling is disabled.
2476    fn create_indirect_frustum_culling_preprocess_bind_groups(
2477        &self,
2478        indexed_work_item_buffer: &PartialBufferVec<PreprocessWorkItem>,
2479        non_indexed_work_item_buffer: &PartialBufferVec<PreprocessWorkItem>,
2480    ) -> Option<PhasePreprocessBindGroups> {
2481        Some(PhasePreprocessBindGroups::IndirectFrustumCulling {
2482            indexed: self
2483                .create_indirect_frustum_culling_indexed_bind_group(indexed_work_item_buffer),
2484            non_indexed: self.create_indirect_frustum_culling_non_indexed_bind_group(
2485                non_indexed_work_item_buffer,
2486            ),
2487        })
2488    }
2489
2490    /// Creates the bind group for mesh preprocessing of indexed meshes when GPU
2491    /// frustum culling is enabled, but GPU occlusion culling is disabled.
2492    fn create_indirect_frustum_culling_indexed_bind_group(
2493        &self,
2494        indexed_work_item_buffer: &PartialBufferVec<PreprocessWorkItem>,
2495    ) -> Option<BindGroup> {
2496        let mesh_culling_data_buffer = self.mesh_culling_data_buffer.buffer()?;
2497        let visibility_range_binding = self.visibility_range_data_buffer.binding()?;
2498        let view_uniforms_binding = self.view_uniforms.uniforms.binding()?;
2499
2500        match (
2501            self.phase_indirect_parameters_buffers
2502                .indexed
2503                .cpu_metadata_buffer(),
2504            self.phase_indirect_parameters_buffers
2505                .indexed
2506                .gpu_metadata_buffer(),
2507            indexed_work_item_buffer.buffer(),
2508        ) {
2509            (
2510                Some(indexed_cpu_metadata_buffer),
2511                Some(indexed_gpu_metadata_buffer),
2512                Some(indexed_work_item_gpu_buffer),
2513            ) => {
2514                // Don't use `as_entire_binding()` here; the shader reads the array
2515                // length and the underlying buffer may be longer than the actual size
2516                // of the vector.
2517                let indexed_work_item_buffer_size = NonZero::<u64>::try_from(
2518                    indexed_work_item_buffer.len() as u64
2519                        * u64::from(PreprocessWorkItem::min_size()),
2520                )
2521                .ok();
2522
2523                Some(
2524                    self.render_device.create_bind_group(
2525                        "preprocess_gpu_indexed_frustum_culling_bind_group",
2526                        &self.pipeline_cache.get_bind_group_layout(
2527                            &self
2528                                .pipelines
2529                                .gpu_frustum_culling_preprocess
2530                                .bind_group_layout,
2531                        ),
2532                        &BindGroupEntries::with_indices((
2533                            (3, self.current_input_buffer.as_entire_binding()),
2534                            (4, self.previous_input_buffer.as_entire_binding()),
2535                            (
2536                                5,
2537                                BindingResource::Buffer(BufferBinding {
2538                                    buffer: indexed_work_item_gpu_buffer,
2539                                    offset: 0,
2540                                    size: indexed_work_item_buffer_size,
2541                                }),
2542                            ),
2543                            (6, self.data_buffer.as_entire_binding()),
2544                            (7, indexed_cpu_metadata_buffer.as_entire_binding()),
2545                            (8, indexed_gpu_metadata_buffer.as_entire_binding()),
2546                            (9, mesh_culling_data_buffer.as_entire_binding()),
2547                            (10, visibility_range_binding.clone()),
2548                            (0, view_uniforms_binding.clone()),
2549                        )),
2550                    ),
2551                )
2552            }
2553            _ => None,
2554        }
2555    }
2556
2557    /// Creates the bind group for mesh preprocessing of non-indexed meshes when
2558    /// GPU frustum culling is enabled, but GPU occlusion culling is disabled.
2559    fn create_indirect_frustum_culling_non_indexed_bind_group(
2560        &self,
2561        non_indexed_work_item_buffer: &PartialBufferVec<PreprocessWorkItem>,
2562    ) -> Option<BindGroup> {
2563        let mesh_culling_data_buffer = self.mesh_culling_data_buffer.buffer()?;
2564        let visibility_range_binding = self.visibility_range_data_buffer.binding()?;
2565        let view_uniforms_binding = self.view_uniforms.uniforms.binding()?;
2566
2567        match (
2568            self.phase_indirect_parameters_buffers
2569                .non_indexed
2570                .cpu_metadata_buffer(),
2571            self.phase_indirect_parameters_buffers
2572                .non_indexed
2573                .gpu_metadata_buffer(),
2574            non_indexed_work_item_buffer.buffer(),
2575        ) {
2576            (
2577                Some(non_indexed_cpu_metadata_buffer),
2578                Some(non_indexed_gpu_metadata_buffer),
2579                Some(non_indexed_work_item_gpu_buffer),
2580            ) => {
2581                // Don't use `as_entire_binding()` here; the shader reads the array
2582                // length and the underlying buffer may be longer than the actual size
2583                // of the vector.
2584                let non_indexed_work_item_buffer_size = NonZero::<u64>::try_from(
2585                    non_indexed_work_item_buffer.len() as u64
2586                        * u64::from(PreprocessWorkItem::min_size()),
2587                )
2588                .ok();
2589
2590                Some(
2591                    self.render_device.create_bind_group(
2592                        "preprocess_gpu_non_indexed_frustum_culling_bind_group",
2593                        &self.pipeline_cache.get_bind_group_layout(
2594                            &self
2595                                .pipelines
2596                                .gpu_frustum_culling_preprocess
2597                                .bind_group_layout,
2598                        ),
2599                        &BindGroupEntries::with_indices((
2600                            (3, self.current_input_buffer.as_entire_binding()),
2601                            (4, self.previous_input_buffer.as_entire_binding()),
2602                            (
2603                                5,
2604                                BindingResource::Buffer(BufferBinding {
2605                                    buffer: non_indexed_work_item_gpu_buffer,
2606                                    offset: 0,
2607                                    size: non_indexed_work_item_buffer_size,
2608                                }),
2609                            ),
2610                            (6, self.data_buffer.as_entire_binding()),
2611                            (7, non_indexed_cpu_metadata_buffer.as_entire_binding()),
2612                            (8, non_indexed_gpu_metadata_buffer.as_entire_binding()),
2613                            (9, mesh_culling_data_buffer.as_entire_binding()),
2614                            (10, visibility_range_binding.clone()),
2615                            (0, view_uniforms_binding.clone()),
2616                        )),
2617                    ),
2618                )
2619            }
2620            _ => None,
2621        }
2622    }
2623}
2624
2625/// A system that creates bind groups from the indirect parameters metadata and
2626/// data buffers for the indirect batch set reset shader and the indirect
2627/// parameter building shader.
2628fn create_build_indirect_parameters_bind_groups(
2629    commands: &mut Commands,
2630    render_device: &RenderDevice,
2631    pipeline_cache: &PipelineCache,
2632    pipelines: &PreprocessPipelines,
2633    current_input_buffer: &Buffer,
2634    indirect_parameters_buffers: &IndirectParametersBuffers,
2635) {
2636    let mut build_indirect_parameters_bind_groups = BuildIndirectParametersBindGroups::new();
2637
2638    for (phase_type_id, phase_indirect_parameters_buffer) in indirect_parameters_buffers.iter() {
2639        build_indirect_parameters_bind_groups.insert(
2640            *phase_type_id,
2641            PhaseBuildIndirectParametersBindGroups {
2642                reset_indexed_indirect_batch_sets: phase_indirect_parameters_buffer
2643                    .indexed
2644                    .batch_sets_buffer()
2645                    .map(|indexed_batch_sets_buffer| {
2646                        render_device.create_bind_group(
2647                            "reset_indexed_indirect_batch_sets_bind_group",
2648                            // The early bind group is good for the main phase and late
2649                            // phase too. They bind the same buffers.
2650                            &pipeline_cache.get_bind_group_layout(
2651                                &pipelines
2652                                    .early_phase
2653                                    .reset_indirect_batch_sets
2654                                    .bind_group_layout,
2655                            ),
2656                            &BindGroupEntries::sequential((
2657                                indexed_batch_sets_buffer.as_entire_binding(),
2658                            )),
2659                        )
2660                    }),
2661
2662                reset_non_indexed_indirect_batch_sets: phase_indirect_parameters_buffer
2663                    .non_indexed
2664                    .batch_sets_buffer()
2665                    .map(|non_indexed_batch_sets_buffer| {
2666                        render_device.create_bind_group(
2667                            "reset_non_indexed_indirect_batch_sets_bind_group",
2668                            // The early bind group is good for the main phase and late
2669                            // phase too. They bind the same buffers.
2670                            &pipeline_cache.get_bind_group_layout(
2671                                &pipelines
2672                                    .early_phase
2673                                    .reset_indirect_batch_sets
2674                                    .bind_group_layout,
2675                            ),
2676                            &BindGroupEntries::sequential((
2677                                non_indexed_batch_sets_buffer.as_entire_binding(),
2678                            )),
2679                        )
2680                    }),
2681
2682                build_indexed_indirect: match (
2683                    phase_indirect_parameters_buffer
2684                        .indexed
2685                        .cpu_metadata_buffer(),
2686                    phase_indirect_parameters_buffer
2687                        .indexed
2688                        .gpu_metadata_buffer(),
2689                    phase_indirect_parameters_buffer.indexed.data_buffer(),
2690                    phase_indirect_parameters_buffer.indexed.batch_sets_buffer(),
2691                ) {
2692                    (
2693                        Some(indexed_indirect_parameters_cpu_metadata_buffer),
2694                        Some(indexed_indirect_parameters_gpu_metadata_buffer),
2695                        Some(indexed_indirect_parameters_data_buffer),
2696                        Some(indexed_batch_sets_buffer),
2697                    ) => Some(
2698                        render_device.create_bind_group(
2699                            "build_indexed_indirect_parameters_bind_group",
2700                            // The frustum culling bind group is good for occlusion culling
2701                            // too. They bind the same buffers.
2702                            &pipeline_cache.get_bind_group_layout(
2703                                &pipelines
2704                                    .gpu_frustum_culling_build_indexed_indirect_params
2705                                    .bind_group_layout,
2706                            ),
2707                            &BindGroupEntries::sequential((
2708                                current_input_buffer.as_entire_binding(),
2709                                // Don't use `as_entire_binding` here; the shader reads
2710                                // the length and `RawBufferVec` overallocates.
2711                                BufferBinding {
2712                                    buffer: indexed_indirect_parameters_cpu_metadata_buffer,
2713                                    offset: 0,
2714                                    size: NonZeroU64::new(
2715                                        phase_indirect_parameters_buffer.indexed.batch_count()
2716                                            as u64
2717                                            * size_of::<IndirectParametersCpuMetadata>() as u64,
2718                                    ),
2719                                },
2720                                BufferBinding {
2721                                    buffer: indexed_indirect_parameters_gpu_metadata_buffer,
2722                                    offset: 0,
2723                                    size: NonZeroU64::new(
2724                                        phase_indirect_parameters_buffer.indexed.batch_count()
2725                                            as u64
2726                                            * size_of::<IndirectParametersGpuMetadata>() as u64,
2727                                    ),
2728                                },
2729                                indexed_batch_sets_buffer.as_entire_binding(),
2730                                indexed_indirect_parameters_data_buffer.as_entire_binding(),
2731                            )),
2732                        ),
2733                    ),
2734                    _ => None,
2735                },
2736
2737                build_non_indexed_indirect: match (
2738                    phase_indirect_parameters_buffer
2739                        .non_indexed
2740                        .cpu_metadata_buffer(),
2741                    phase_indirect_parameters_buffer
2742                        .non_indexed
2743                        .gpu_metadata_buffer(),
2744                    phase_indirect_parameters_buffer.non_indexed.data_buffer(),
2745                    phase_indirect_parameters_buffer
2746                        .non_indexed
2747                        .batch_sets_buffer(),
2748                ) {
2749                    (
2750                        Some(non_indexed_indirect_parameters_cpu_metadata_buffer),
2751                        Some(non_indexed_indirect_parameters_gpu_metadata_buffer),
2752                        Some(non_indexed_indirect_parameters_data_buffer),
2753                        Some(non_indexed_batch_sets_buffer),
2754                    ) => Some(
2755                        render_device.create_bind_group(
2756                            "build_non_indexed_indirect_parameters_bind_group",
2757                            // The frustum culling bind group is good for occlusion culling
2758                            // too. They bind the same buffers.
2759                            &pipeline_cache.get_bind_group_layout(
2760                                &pipelines
2761                                    .gpu_frustum_culling_build_non_indexed_indirect_params
2762                                    .bind_group_layout,
2763                            ),
2764                            &BindGroupEntries::sequential((
2765                                current_input_buffer.as_entire_binding(),
2766                                // Don't use `as_entire_binding` here; the shader reads
2767                                // the length and `RawBufferVec` overallocates.
2768                                BufferBinding {
2769                                    buffer: non_indexed_indirect_parameters_cpu_metadata_buffer,
2770                                    offset: 0,
2771                                    size: NonZeroU64::new(
2772                                        phase_indirect_parameters_buffer.non_indexed.batch_count()
2773                                            as u64
2774                                            * size_of::<IndirectParametersCpuMetadata>() as u64,
2775                                    ),
2776                                },
2777                                BufferBinding {
2778                                    buffer: non_indexed_indirect_parameters_gpu_metadata_buffer,
2779                                    offset: 0,
2780                                    size: NonZeroU64::new(
2781                                        phase_indirect_parameters_buffer.non_indexed.batch_count()
2782                                            as u64
2783                                            * size_of::<IndirectParametersGpuMetadata>() as u64,
2784                                    ),
2785                                },
2786                                non_indexed_batch_sets_buffer.as_entire_binding(),
2787                                non_indexed_indirect_parameters_data_buffer.as_entire_binding(),
2788                            )),
2789                        ),
2790                    ),
2791                    _ => None,
2792                },
2793            },
2794        );
2795    }
2796
2797    commands.insert_resource(build_indirect_parameters_bind_groups);
2798}
2799
2800/// Creates all bind groups needed to run the `unpack_bins` shader for all the
2801/// phases for a single view.
2802fn create_bin_unpacking_bind_groups(
2803    bin_unpacking_bind_groups: &mut BinUnpackingBindGroups,
2804    render_device: &RenderDevice,
2805    pipeline_cache: &PipelineCache,
2806    preprocess_pipelines: &PreprocessPipelines,
2807    indirect_parameters_buffers: &IndirectParametersBuffers,
2808    phase_instance_buffers: &TypeIdMap<UntypedPhaseBatchedInstanceBuffers<MeshUniform>>,
2809    bin_unpacking_buffers: &BinUnpackingBuffers,
2810    view_entity: &RetainedViewEntity,
2811) {
2812    let Some(bin_unpacking_metadata_buffer) = bin_unpacking_buffers.bin_unpacking_metadata.buffer()
2813    else {
2814        return;
2815    };
2816
2817    // We run the bin unpacking shader once per phase, so loop over all phases.
2818    for phase_type_id in indirect_parameters_buffers.keys() {
2819        let bin_unpacking_buffers_key = BinUnpackingBuffersKey {
2820            phase: *phase_type_id,
2821            view: *view_entity,
2822        };
2823
2824        // Fetch the buffers we need.
2825        let Some(phase_batched_instance_buffers) = phase_instance_buffers.get(phase_type_id) else {
2826            continue;
2827        };
2828        let Some(work_item_buffers) = phase_batched_instance_buffers
2829            .work_item_buffers
2830            .get(view_entity)
2831        else {
2832            continue;
2833        };
2834        let Some(view_phase_bin_unpacking_buffers) = bin_unpacking_buffers
2835            .view_phase_buffers
2836            .get(&bin_unpacking_buffers_key)
2837        else {
2838            continue;
2839        };
2840
2841        // Fetch the work item buffers.
2842        let maybe_indexed_work_item_buffer = match *work_item_buffers {
2843            PreprocessWorkItemBuffers::Direct(ref raw_buffer_vec) => raw_buffer_vec.buffer(),
2844            PreprocessWorkItemBuffers::Indirect { ref indexed, .. } => indexed.buffer(),
2845        };
2846        let maybe_non_indexed_work_item_buffer = match *work_item_buffers {
2847            PreprocessWorkItemBuffers::Direct(ref raw_buffer_vec) => raw_buffer_vec.buffer(),
2848            PreprocessWorkItemBuffers::Indirect {
2849                ref non_indexed, ..
2850            } => non_indexed.buffer(),
2851        };
2852
2853        // Create the actual bind groups.
2854        bin_unpacking_bind_groups.insert(
2855            bin_unpacking_buffers_key,
2856            ViewPhaseBinUnpackingBindGroups {
2857                indexed: match maybe_indexed_work_item_buffer {
2858                    Some(indexed_work_item_buffer) => view_phase_bin_unpacking_buffers
2859                        .indexed_unpacking_jobs
2860                        .iter()
2861                        .map(|job| {
2862                            create_bin_unpacking_bind_group(
2863                                render_device,
2864                                preprocess_pipelines,
2865                                pipeline_cache,
2866                                job,
2867                                bin_unpacking_metadata_buffer,
2868                                indexed_work_item_buffer,
2869                                true,
2870                            )
2871                        })
2872                        .collect(),
2873                    None => vec![],
2874                },
2875                non_indexed: match maybe_non_indexed_work_item_buffer {
2876                    Some(non_indexed_work_item_buffer) => view_phase_bin_unpacking_buffers
2877                        .non_indexed_unpacking_jobs
2878                        .iter()
2879                        .map(|job| {
2880                            create_bin_unpacking_bind_group(
2881                                render_device,
2882                                preprocess_pipelines,
2883                                pipeline_cache,
2884                                job,
2885                                bin_unpacking_metadata_buffer,
2886                                non_indexed_work_item_buffer,
2887                                false,
2888                            )
2889                        })
2890                        .collect(),
2891                    None => vec![],
2892                },
2893            },
2894        );
2895    }
2896}
2897
2898/// Creates a bind group for the bin unpacking shader for a single (view, phase,
2899/// mesh indexed-ness) combination.
2900fn create_bin_unpacking_bind_group(
2901    render_device: &RenderDevice,
2902    preprocess_pipelines: &PreprocessPipelines,
2903    pipeline_cache: &PipelineCache,
2904    job: &BinUnpackingJob,
2905    bin_unpacking_metadata_buffer: &Buffer,
2906    work_item_buffer: &Buffer,
2907    indexed: bool,
2908) -> ViewPhaseBinUnpackingBindGroup {
2909    let bind_group = render_device.create_bind_group(
2910        if indexed {
2911            "bin unpacking indexed bind group"
2912        } else {
2913            "bin unpacking non-indexed bind group"
2914        },
2915        &pipeline_cache
2916            .get_bind_group_layout(&preprocess_pipelines.bin_unpacking.bind_group_layout),
2917        &BindGroupEntries::sequential((
2918            // @group(0) @binding(0) var<uniform>
2919            // bin_unpacking_metadata:
2920            // BinUnpackingMetadata;
2921            BindingResource::Buffer(BufferBinding {
2922                buffer: bin_unpacking_metadata_buffer,
2923                offset: job.bin_unpacking_metadata_index.uniform_offset() as u64,
2924                size: NonZeroU64::new(size_of::<GpuBinUnpackingMetadata>() as u64),
2925            }),
2926            // @group(0) @binding(1) var<storage>
2927            // binned_mesh_instances:
2928            // array<BinnedMeshInstance>;
2929            job.render_binned_mesh_instance_buffer.as_entire_binding(),
2930            // @group(0) @binding(2) var<storage,
2931            // read_write> preprocess_work_items:
2932            // array<PreprocessWorkItem>;
2933            work_item_buffer.as_entire_binding(),
2934            // @group(0) @binding(3) var<storage>
2935            // bin_index_to_indirect_parameters_offset:
2936            // array<u32>;
2937            job.bin_index_to_indirect_parameters_offset_buffer
2938                .as_entire_binding(),
2939        )),
2940    );
2941    ViewPhaseBinUnpackingBindGroup {
2942        metadata_index: job.bin_unpacking_metadata_index,
2943        bind_group,
2944        mesh_instance_count: job.mesh_instance_count,
2945    }
2946}
2947
2948/// Writes the information needed to do GPU mesh culling to the GPU.
2949pub fn write_mesh_culling_data_buffer(
2950    render_device: Res<RenderDevice>,
2951    render_queue: Res<RenderQueue>,
2952    mut mesh_culling_data_buffer: ResMut<MeshCullingDataBuffer>,
2953    pipeline_cache: Res<PipelineCache>,
2954    mut sparse_buffer_update_jobs: ResMut<SparseBufferUpdateJobs>,
2955    mut sparse_buffer_update_bind_groups: ResMut<SparseBufferUpdateBindGroups>,
2956    sparse_buffer_update_pipelines: Res<SparseBufferUpdatePipelines>,
2957) {
2958    mesh_culling_data_buffer.write_buffers(&render_device, &render_queue);
2959    mesh_culling_data_buffer.prepare_to_populate_buffers(
2960        &render_device,
2961        &pipeline_cache,
2962        &mut sparse_buffer_update_jobs,
2963        &mut sparse_buffer_update_bind_groups,
2964        &sparse_buffer_update_pipelines,
2965    );
2966}