bevy_pbr/prepass/
mod.rs

1mod prepass_bindings;
2
3use crate::{
4    alpha_mode_pipeline_key, binding_arrays_are_usable, buffer_layout,
5    collect_meshes_for_gpu_building, init_material_pipeline, set_mesh_motion_vector_flags,
6    setup_morph_and_skinning_defs, skin, DeferredDrawFunction, DeferredFragmentShader,
7    DeferredVertexShader, DrawMesh, EntitySpecializationTicks, ErasedMaterialPipelineKey, Material,
8    MaterialPipeline, MaterialProperties, MeshLayouts, MeshPipeline, MeshPipelineKey,
9    OpaqueRendererMethod, PreparedMaterial, PrepassDrawFunction, PrepassFragmentShader,
10    PrepassVertexShader, RenderLightmaps, RenderMaterialInstances, RenderMeshInstanceFlags,
11    RenderMeshInstances, RenderPhaseType, SetMaterialBindGroup, SetMeshBindGroup, ShadowView,
12};
13use bevy_app::{App, Plugin, PreUpdate};
14use bevy_asset::{embedded_asset, load_embedded_asset, AssetServer, Handle};
15use bevy_camera::{Camera, Camera3d};
16use bevy_core_pipeline::{core_3d::CORE_3D_DEPTH_FORMAT, deferred::*, prepass::*};
17use bevy_ecs::{
18    prelude::*,
19    system::{
20        lifetimeless::{Read, SRes},
21        SystemParamItem,
22    },
23};
24use bevy_math::{Affine3A, Mat4, Vec4};
25use bevy_mesh::{Mesh, Mesh3d, MeshVertexBufferLayoutRef};
26use bevy_render::{
27    alpha::AlphaMode,
28    batching::gpu_preprocessing::GpuPreprocessingSupport,
29    globals::{GlobalsBuffer, GlobalsUniform},
30    mesh::{allocator::MeshAllocator, RenderMesh},
31    render_asset::{prepare_assets, RenderAssets},
32    render_phase::*,
33    render_resource::{binding_types::uniform_buffer, *},
34    renderer::{RenderAdapter, RenderDevice, RenderQueue},
35    sync_world::RenderEntity,
36    view::{
37        ExtractedView, Msaa, RenderVisibilityRanges, RetainedViewEntity, ViewUniform,
38        ViewUniformOffset, ViewUniforms, VISIBILITY_RANGES_STORAGE_BUFFER_COUNT,
39    },
40    Extract, ExtractSchedule, Render, RenderApp, RenderDebugFlags, RenderStartup, RenderSystems,
41};
42use bevy_shader::{load_shader_library, Shader, ShaderDefVal};
43use bevy_transform::prelude::GlobalTransform;
44pub use prepass_bindings::*;
45use tracing::{error, warn};
46
47#[cfg(feature = "meshlet")]
48use crate::meshlet::{
49    prepare_material_meshlet_meshes_prepass, queue_material_meshlet_meshes, InstanceManager,
50    MeshletMesh3d,
51};
52
53use alloc::sync::Arc;
54use bevy_derive::{Deref, DerefMut};
55use bevy_ecs::{component::Tick, system::SystemChangeTick};
56use bevy_platform::collections::HashMap;
57use bevy_render::{
58    erased_render_asset::ErasedRenderAssets,
59    sync_world::MainEntityHashMap,
60    view::RenderVisibleEntities,
61    RenderSystems::{PrepareAssets, PrepareResources},
62};
63use bevy_utils::default;
64use core::marker::PhantomData;
65
66/// Sets up everything required to use the prepass pipeline.
67///
68/// This does not add the actual prepasses, see [`PrepassPlugin`] for that.
69pub struct PrepassPipelinePlugin;
70
71impl Plugin for PrepassPipelinePlugin {
72    fn build(&self, app: &mut App) {
73        embedded_asset!(app, "prepass.wgsl");
74
75        load_shader_library!(app, "prepass_bindings.wgsl");
76        load_shader_library!(app, "prepass_utils.wgsl");
77        load_shader_library!(app, "prepass_io.wgsl");
78
79        let Some(render_app) = app.get_sub_app_mut(RenderApp) else {
80            return;
81        };
82
83        render_app
84            .add_systems(
85                RenderStartup,
86                (
87                    init_prepass_pipeline.after(init_material_pipeline),
88                    init_prepass_view_bind_group,
89                )
90                    .chain(),
91            )
92            .add_systems(
93                Render,
94                prepare_prepass_view_bind_group.in_set(RenderSystems::PrepareBindGroups),
95            )
96            .init_resource::<SpecializedMeshPipelines<PrepassPipelineSpecializer>>();
97    }
98}
99
100/// Sets up the prepasses for a material.
101///
102/// This depends on the [`PrepassPipelinePlugin`].
103pub struct PrepassPlugin {
104    /// Debugging flags that can optionally be set when constructing the renderer.
105    pub debug_flags: RenderDebugFlags,
106}
107
108impl PrepassPlugin {
109    /// Creates a new [`PrepassPlugin`] with the given debug flags.
110    pub fn new(debug_flags: RenderDebugFlags) -> Self {
111        PrepassPlugin { debug_flags }
112    }
113}
114
115impl Plugin for PrepassPlugin {
116    fn build(&self, app: &mut App) {
117        let no_prepass_plugin_loaded = app
118            .world()
119            .get_resource::<AnyPrepassPluginLoaded>()
120            .is_none();
121
122        if no_prepass_plugin_loaded {
123            app.insert_resource(AnyPrepassPluginLoaded)
124                // At the start of each frame, last frame's GlobalTransforms become this frame's PreviousGlobalTransforms
125                // and last frame's view projection matrices become this frame's PreviousViewProjections
126                .add_systems(
127                    PreUpdate,
128                    (
129                        update_mesh_previous_global_transforms,
130                        update_previous_view_data,
131                    ),
132                )
133                .add_plugins((
134                    BinnedRenderPhasePlugin::<Opaque3dPrepass, MeshPipeline>::new(self.debug_flags),
135                    BinnedRenderPhasePlugin::<AlphaMask3dPrepass, MeshPipeline>::new(
136                        self.debug_flags,
137                    ),
138                ));
139        }
140
141        let Some(render_app) = app.get_sub_app_mut(RenderApp) else {
142            return;
143        };
144
145        if no_prepass_plugin_loaded {
146            render_app
147                .add_systems(ExtractSchedule, extract_camera_previous_view_data)
148                .add_systems(
149                    Render,
150                    prepare_previous_view_uniforms.in_set(PrepareResources),
151                );
152        }
153
154        render_app
155            .init_resource::<ViewPrepassSpecializationTicks>()
156            .init_resource::<ViewKeyPrepassCache>()
157            .init_resource::<SpecializedPrepassMaterialPipelineCache>()
158            .add_render_command::<Opaque3dPrepass, DrawPrepass>()
159            .add_render_command::<AlphaMask3dPrepass, DrawPrepass>()
160            .add_render_command::<Opaque3dDeferred, DrawPrepass>()
161            .add_render_command::<AlphaMask3dDeferred, DrawPrepass>()
162            .add_systems(
163                Render,
164                (
165                    check_prepass_views_need_specialization.in_set(PrepareAssets),
166                    specialize_prepass_material_meshes
167                        .in_set(RenderSystems::PrepareMeshes)
168                        .after(prepare_assets::<RenderMesh>)
169                        .after(collect_meshes_for_gpu_building)
170                        .after(set_mesh_motion_vector_flags),
171                    queue_prepass_material_meshes.in_set(RenderSystems::QueueMeshes),
172                ),
173            );
174
175        #[cfg(feature = "meshlet")]
176        render_app.add_systems(
177            Render,
178            prepare_material_meshlet_meshes_prepass
179                .in_set(RenderSystems::QueueMeshes)
180                .before(queue_material_meshlet_meshes)
181                .run_if(resource_exists::<InstanceManager>),
182        );
183    }
184}
185
186/// Marker resource for whether prepass is enabled globally for this material type
187#[derive(Resource, Debug)]
188pub struct PrepassEnabled<M: Material>(PhantomData<M>);
189
190impl<M: Material> Default for PrepassEnabled<M> {
191    fn default() -> Self {
192        PrepassEnabled(PhantomData)
193    }
194}
195
196#[derive(Resource)]
197struct AnyPrepassPluginLoaded;
198
199pub fn update_previous_view_data(
200    mut commands: Commands,
201    query: Query<(Entity, &Camera, &GlobalTransform), Or<(With<Camera3d>, With<ShadowView>)>>,
202) {
203    for (entity, camera, camera_transform) in &query {
204        let world_from_view = camera_transform.affine();
205        let view_from_world = Mat4::from(world_from_view.inverse());
206        let view_from_clip = camera.clip_from_view().inverse();
207
208        commands.entity(entity).try_insert(PreviousViewData {
209            view_from_world,
210            clip_from_world: camera.clip_from_view() * view_from_world,
211            clip_from_view: camera.clip_from_view(),
212            world_from_clip: Mat4::from(world_from_view) * view_from_clip,
213            view_from_clip,
214        });
215    }
216}
217
218#[derive(Component, PartialEq, Default)]
219pub struct PreviousGlobalTransform(pub Affine3A);
220
221#[cfg(not(feature = "meshlet"))]
222type PreviousMeshFilter = With<Mesh3d>;
223#[cfg(feature = "meshlet")]
224type PreviousMeshFilter = Or<(With<Mesh3d>, With<MeshletMesh3d>)>;
225
226pub fn update_mesh_previous_global_transforms(
227    mut commands: Commands,
228    views: Query<&Camera, Or<(With<Camera3d>, With<ShadowView>)>>,
229    new_meshes: Query<
230        (Entity, &GlobalTransform),
231        (PreviousMeshFilter, Without<PreviousGlobalTransform>),
232    >,
233    mut meshes: Query<(&GlobalTransform, &mut PreviousGlobalTransform), PreviousMeshFilter>,
234) {
235    let should_run = views.iter().any(|camera| camera.is_active);
236
237    if should_run {
238        for (entity, transform) in &new_meshes {
239            let new_previous_transform = PreviousGlobalTransform(transform.affine());
240            commands.entity(entity).try_insert(new_previous_transform);
241        }
242        meshes.par_iter_mut().for_each(|(transform, mut previous)| {
243            previous.set_if_neq(PreviousGlobalTransform(transform.affine()));
244        });
245    }
246}
247
248#[derive(Resource, Clone)]
249pub struct PrepassPipeline {
250    pub view_layout_motion_vectors: BindGroupLayout,
251    pub view_layout_no_motion_vectors: BindGroupLayout,
252    pub mesh_layouts: MeshLayouts,
253    pub empty_layout: BindGroupLayout,
254    pub default_prepass_shader: Handle<Shader>,
255
256    /// Whether skins will use uniform buffers on account of storage buffers
257    /// being unavailable on this platform.
258    pub skins_use_uniform_buffers: bool,
259
260    pub depth_clip_control_supported: bool,
261
262    /// Whether binding arrays (a.k.a. bindless textures) are usable on the
263    /// current render device.
264    pub binding_arrays_are_usable: bool,
265    pub material_pipeline: MaterialPipeline,
266}
267
268pub fn init_prepass_pipeline(
269    mut commands: Commands,
270    render_device: Res<RenderDevice>,
271    render_adapter: Res<RenderAdapter>,
272    mesh_pipeline: Res<MeshPipeline>,
273    material_pipeline: Res<MaterialPipeline>,
274    asset_server: Res<AssetServer>,
275) {
276    let visibility_ranges_buffer_binding_type =
277        render_device.get_supported_read_only_binding_type(VISIBILITY_RANGES_STORAGE_BUFFER_COUNT);
278
279    let view_layout_motion_vectors = render_device.create_bind_group_layout(
280        "prepass_view_layout_motion_vectors",
281        &BindGroupLayoutEntries::with_indices(
282            ShaderStages::VERTEX_FRAGMENT,
283            (
284                // View
285                (0, uniform_buffer::<ViewUniform>(true)),
286                // Globals
287                (1, uniform_buffer::<GlobalsUniform>(false)),
288                // PreviousViewUniforms
289                (2, uniform_buffer::<PreviousViewData>(true)),
290                // VisibilityRanges
291                (
292                    14,
293                    buffer_layout(
294                        visibility_ranges_buffer_binding_type,
295                        false,
296                        Some(Vec4::min_size()),
297                    )
298                    .visibility(ShaderStages::VERTEX),
299                ),
300            ),
301        ),
302    );
303
304    let view_layout_no_motion_vectors = render_device.create_bind_group_layout(
305        "prepass_view_layout_no_motion_vectors",
306        &BindGroupLayoutEntries::with_indices(
307            ShaderStages::VERTEX_FRAGMENT,
308            (
309                // View
310                (0, uniform_buffer::<ViewUniform>(true)),
311                // Globals
312                (1, uniform_buffer::<GlobalsUniform>(false)),
313                // VisibilityRanges
314                (
315                    14,
316                    buffer_layout(
317                        visibility_ranges_buffer_binding_type,
318                        false,
319                        Some(Vec4::min_size()),
320                    )
321                    .visibility(ShaderStages::VERTEX),
322                ),
323            ),
324        ),
325    );
326
327    let depth_clip_control_supported = render_device
328        .features()
329        .contains(WgpuFeatures::DEPTH_CLIP_CONTROL);
330    commands.insert_resource(PrepassPipeline {
331        view_layout_motion_vectors,
332        view_layout_no_motion_vectors,
333        mesh_layouts: mesh_pipeline.mesh_layouts.clone(),
334        default_prepass_shader: load_embedded_asset!(asset_server.as_ref(), "prepass.wgsl"),
335        skins_use_uniform_buffers: skin::skins_use_uniform_buffers(&render_device),
336        depth_clip_control_supported,
337        binding_arrays_are_usable: binding_arrays_are_usable(&render_device, &render_adapter),
338        empty_layout: render_device.create_bind_group_layout("prepass_empty_layout", &[]),
339        material_pipeline: material_pipeline.clone(),
340    });
341}
342
343pub struct PrepassPipelineSpecializer {
344    pub(crate) pipeline: PrepassPipeline,
345    pub(crate) properties: Arc<MaterialProperties>,
346}
347
348impl SpecializedMeshPipeline for PrepassPipelineSpecializer {
349    type Key = ErasedMaterialPipelineKey;
350
351    fn specialize(
352        &self,
353        key: Self::Key,
354        layout: &MeshVertexBufferLayoutRef,
355    ) -> Result<RenderPipelineDescriptor, SpecializedMeshPipelineError> {
356        let mut shader_defs = Vec::new();
357        if self.properties.bindless {
358            shader_defs.push("BINDLESS".into());
359        }
360        let mut descriptor =
361            self.pipeline
362                .specialize(key.mesh_key, shader_defs, layout, &self.properties)?;
363
364        // This is a bit risky because it's possible to change something that would
365        // break the prepass but be fine in the main pass.
366        // Since this api is pretty low-level it doesn't matter that much, but it is a potential issue.
367        if let Some(specialize) = self.properties.specialize {
368            specialize(
369                &self.pipeline.material_pipeline,
370                &mut descriptor,
371                layout,
372                key,
373            )?;
374        }
375
376        Ok(descriptor)
377    }
378}
379
380impl PrepassPipeline {
381    fn specialize(
382        &self,
383        mesh_key: MeshPipelineKey,
384        shader_defs: Vec<ShaderDefVal>,
385        layout: &MeshVertexBufferLayoutRef,
386        material_properties: &MaterialProperties,
387    ) -> Result<RenderPipelineDescriptor, SpecializedMeshPipelineError> {
388        let mut shader_defs = shader_defs;
389        let mut bind_group_layouts = vec![
390            if mesh_key.contains(MeshPipelineKey::MOTION_VECTOR_PREPASS) {
391                self.view_layout_motion_vectors.clone()
392            } else {
393                self.view_layout_no_motion_vectors.clone()
394            },
395            self.empty_layout.clone(),
396        ];
397        let mut vertex_attributes = Vec::new();
398
399        // Let the shader code know that it's running in a prepass pipeline.
400        // (PBR code will use this to detect that it's running in deferred mode,
401        // since that's the only time it gets called from a prepass pipeline.)
402        shader_defs.push("PREPASS_PIPELINE".into());
403
404        shader_defs.push(ShaderDefVal::UInt(
405            "MATERIAL_BIND_GROUP".into(),
406            crate::MATERIAL_BIND_GROUP_INDEX as u32,
407        ));
408        // NOTE: Eventually, it would be nice to only add this when the shaders are overloaded by the Material.
409        // The main limitation right now is that bind group order is hardcoded in shaders.
410        bind_group_layouts.push(
411            material_properties
412                .material_layout
413                .as_ref()
414                .unwrap()
415                .clone(),
416        );
417        #[cfg(all(feature = "webgl", target_arch = "wasm32", not(feature = "webgpu")))]
418        shader_defs.push("WEBGL2".into());
419        shader_defs.push("VERTEX_OUTPUT_INSTANCE_INDEX".into());
420        if mesh_key.contains(MeshPipelineKey::DEPTH_PREPASS) {
421            shader_defs.push("DEPTH_PREPASS".into());
422        }
423        if mesh_key.contains(MeshPipelineKey::MAY_DISCARD) {
424            shader_defs.push("MAY_DISCARD".into());
425        }
426        let blend_key = mesh_key.intersection(MeshPipelineKey::BLEND_RESERVED_BITS);
427        if blend_key == MeshPipelineKey::BLEND_PREMULTIPLIED_ALPHA {
428            shader_defs.push("BLEND_PREMULTIPLIED_ALPHA".into());
429        }
430        if blend_key == MeshPipelineKey::BLEND_ALPHA {
431            shader_defs.push("BLEND_ALPHA".into());
432        }
433        if layout.0.contains(Mesh::ATTRIBUTE_POSITION) {
434            shader_defs.push("VERTEX_POSITIONS".into());
435            vertex_attributes.push(Mesh::ATTRIBUTE_POSITION.at_shader_location(0));
436        }
437        // For directional light shadow map views, use unclipped depth via either the native GPU feature,
438        // or emulated by setting depth in the fragment shader for GPUs that don't support it natively.
439        let emulate_unclipped_depth = mesh_key.contains(MeshPipelineKey::UNCLIPPED_DEPTH_ORTHO)
440            && !self.depth_clip_control_supported;
441        if emulate_unclipped_depth {
442            shader_defs.push("UNCLIPPED_DEPTH_ORTHO_EMULATION".into());
443            // PERF: This line forces the "prepass fragment shader" to always run in
444            // common scenarios like "directional light calculation". Doing so resolves
445            // a pretty nasty depth clamping bug, but it also feels a bit excessive.
446            // We should try to find a way to resolve this without forcing the fragment
447            // shader to run.
448            // https://github.com/bevyengine/bevy/pull/8877
449            shader_defs.push("PREPASS_FRAGMENT".into());
450        }
451        let unclipped_depth = mesh_key.contains(MeshPipelineKey::UNCLIPPED_DEPTH_ORTHO)
452            && self.depth_clip_control_supported;
453        if layout.0.contains(Mesh::ATTRIBUTE_UV_0) {
454            shader_defs.push("VERTEX_UVS".into());
455            shader_defs.push("VERTEX_UVS_A".into());
456            vertex_attributes.push(Mesh::ATTRIBUTE_UV_0.at_shader_location(1));
457        }
458        if layout.0.contains(Mesh::ATTRIBUTE_UV_1) {
459            shader_defs.push("VERTEX_UVS".into());
460            shader_defs.push("VERTEX_UVS_B".into());
461            vertex_attributes.push(Mesh::ATTRIBUTE_UV_1.at_shader_location(2));
462        }
463        if mesh_key.contains(MeshPipelineKey::NORMAL_PREPASS) {
464            shader_defs.push("NORMAL_PREPASS".into());
465        }
466        if mesh_key.intersects(MeshPipelineKey::NORMAL_PREPASS | MeshPipelineKey::DEFERRED_PREPASS)
467        {
468            shader_defs.push("NORMAL_PREPASS_OR_DEFERRED_PREPASS".into());
469            if layout.0.contains(Mesh::ATTRIBUTE_NORMAL) {
470                shader_defs.push("VERTEX_NORMALS".into());
471                vertex_attributes.push(Mesh::ATTRIBUTE_NORMAL.at_shader_location(3));
472            } else if mesh_key.contains(MeshPipelineKey::NORMAL_PREPASS) {
473                warn!(
474                    "The default normal prepass expects the mesh to have vertex normal attributes."
475                );
476            }
477            if layout.0.contains(Mesh::ATTRIBUTE_TANGENT) {
478                shader_defs.push("VERTEX_TANGENTS".into());
479                vertex_attributes.push(Mesh::ATTRIBUTE_TANGENT.at_shader_location(4));
480            }
481        }
482        if mesh_key
483            .intersects(MeshPipelineKey::MOTION_VECTOR_PREPASS | MeshPipelineKey::DEFERRED_PREPASS)
484        {
485            shader_defs.push("MOTION_VECTOR_PREPASS_OR_DEFERRED_PREPASS".into());
486        }
487        if mesh_key.contains(MeshPipelineKey::DEFERRED_PREPASS) {
488            shader_defs.push("DEFERRED_PREPASS".into());
489        }
490        if mesh_key.contains(MeshPipelineKey::LIGHTMAPPED) {
491            shader_defs.push("LIGHTMAP".into());
492        }
493        if mesh_key.contains(MeshPipelineKey::LIGHTMAP_BICUBIC_SAMPLING) {
494            shader_defs.push("LIGHTMAP_BICUBIC_SAMPLING".into());
495        }
496        if layout.0.contains(Mesh::ATTRIBUTE_COLOR) {
497            shader_defs.push("VERTEX_COLORS".into());
498            vertex_attributes.push(Mesh::ATTRIBUTE_COLOR.at_shader_location(7));
499        }
500        if mesh_key.contains(MeshPipelineKey::MOTION_VECTOR_PREPASS) {
501            shader_defs.push("MOTION_VECTOR_PREPASS".into());
502        }
503        if mesh_key.contains(MeshPipelineKey::HAS_PREVIOUS_SKIN) {
504            shader_defs.push("HAS_PREVIOUS_SKIN".into());
505        }
506        if mesh_key.contains(MeshPipelineKey::HAS_PREVIOUS_MORPH) {
507            shader_defs.push("HAS_PREVIOUS_MORPH".into());
508        }
509        if self.binding_arrays_are_usable {
510            shader_defs.push("MULTIPLE_LIGHTMAPS_IN_ARRAY".into());
511        }
512        if mesh_key.contains(MeshPipelineKey::VISIBILITY_RANGE_DITHER) {
513            shader_defs.push("VISIBILITY_RANGE_DITHER".into());
514        }
515        if mesh_key.intersects(
516            MeshPipelineKey::NORMAL_PREPASS
517                | MeshPipelineKey::MOTION_VECTOR_PREPASS
518                | MeshPipelineKey::DEFERRED_PREPASS,
519        ) {
520            shader_defs.push("PREPASS_FRAGMENT".into());
521        }
522        let bind_group = setup_morph_and_skinning_defs(
523            &self.mesh_layouts,
524            layout,
525            5,
526            &mesh_key,
527            &mut shader_defs,
528            &mut vertex_attributes,
529            self.skins_use_uniform_buffers,
530        );
531        bind_group_layouts.insert(2, bind_group);
532        let vertex_buffer_layout = layout.0.get_layout(&vertex_attributes)?;
533        // Setup prepass fragment targets - normals in slot 0 (or None if not needed), motion vectors in slot 1
534        let mut targets = prepass_target_descriptors(
535            mesh_key.contains(MeshPipelineKey::NORMAL_PREPASS),
536            mesh_key.contains(MeshPipelineKey::MOTION_VECTOR_PREPASS),
537            mesh_key.contains(MeshPipelineKey::DEFERRED_PREPASS),
538        );
539
540        if targets.iter().all(Option::is_none) {
541            // if no targets are required then clear the list, so that no fragment shader is required
542            // (though one may still be used for discarding depth buffer writes)
543            targets.clear();
544        }
545
546        // The fragment shader is only used when the normal prepass or motion vectors prepass
547        // is enabled, the material uses alpha cutoff values and doesn't rely on the standard
548        // prepass shader, or we are emulating unclipped depth in the fragment shader.
549        let fragment_required = !targets.is_empty()
550            || emulate_unclipped_depth
551            || (mesh_key.contains(MeshPipelineKey::MAY_DISCARD)
552                && material_properties
553                    .get_shader(PrepassFragmentShader)
554                    .is_some());
555
556        let fragment = fragment_required.then(|| {
557            // Use the fragment shader from the material
558            let frag_shader_handle = if mesh_key.contains(MeshPipelineKey::DEFERRED_PREPASS) {
559                match material_properties.get_shader(DeferredFragmentShader) {
560                    Some(frag_shader_handle) => frag_shader_handle,
561                    None => self.default_prepass_shader.clone(),
562                }
563            } else {
564                match material_properties.get_shader(PrepassFragmentShader) {
565                    Some(frag_shader_handle) => frag_shader_handle,
566                    None => self.default_prepass_shader.clone(),
567                }
568            };
569
570            FragmentState {
571                shader: frag_shader_handle,
572                shader_defs: shader_defs.clone(),
573                targets,
574                ..default()
575            }
576        });
577
578        // Use the vertex shader from the material if present
579        let vert_shader_handle = if mesh_key.contains(MeshPipelineKey::DEFERRED_PREPASS) {
580            if let Some(handle) = material_properties.get_shader(DeferredVertexShader) {
581                handle
582            } else {
583                self.default_prepass_shader.clone()
584            }
585        } else if let Some(handle) = material_properties.get_shader(PrepassVertexShader) {
586            handle
587        } else {
588            self.default_prepass_shader.clone()
589        };
590        let descriptor = RenderPipelineDescriptor {
591            vertex: VertexState {
592                shader: vert_shader_handle,
593                shader_defs,
594                buffers: vec![vertex_buffer_layout],
595                ..default()
596            },
597            fragment,
598            layout: bind_group_layouts,
599            primitive: PrimitiveState {
600                topology: mesh_key.primitive_topology(),
601                unclipped_depth,
602                ..default()
603            },
604            depth_stencil: Some(DepthStencilState {
605                format: CORE_3D_DEPTH_FORMAT,
606                depth_write_enabled: true,
607                depth_compare: CompareFunction::GreaterEqual,
608                stencil: StencilState {
609                    front: StencilFaceState::IGNORE,
610                    back: StencilFaceState::IGNORE,
611                    read_mask: 0,
612                    write_mask: 0,
613                },
614                bias: DepthBiasState {
615                    constant: 0,
616                    slope_scale: 0.0,
617                    clamp: 0.0,
618                },
619            }),
620            multisample: MultisampleState {
621                count: mesh_key.msaa_samples(),
622                mask: !0,
623                alpha_to_coverage_enabled: false,
624            },
625            label: Some("prepass_pipeline".into()),
626            ..default()
627        };
628        Ok(descriptor)
629    }
630}
631
632// Extract the render phases for the prepass
633pub fn extract_camera_previous_view_data(
634    mut commands: Commands,
635    cameras_3d: Extract<Query<(RenderEntity, &Camera, Option<&PreviousViewData>), With<Camera3d>>>,
636) {
637    for (entity, camera, maybe_previous_view_data) in cameras_3d.iter() {
638        let mut entity = commands
639            .get_entity(entity)
640            .expect("Camera entity wasn't synced.");
641        if camera.is_active {
642            if let Some(previous_view_data) = maybe_previous_view_data {
643                entity.insert(previous_view_data.clone());
644            }
645        } else {
646            entity.remove::<PreviousViewData>();
647        }
648    }
649}
650
651pub fn prepare_previous_view_uniforms(
652    mut commands: Commands,
653    render_device: Res<RenderDevice>,
654    render_queue: Res<RenderQueue>,
655    mut previous_view_uniforms: ResMut<PreviousViewUniforms>,
656    views: Query<
657        (Entity, &ExtractedView, Option<&PreviousViewData>),
658        Or<(With<Camera3d>, With<ShadowView>)>,
659    >,
660) {
661    let views_iter = views.iter();
662    let view_count = views_iter.len();
663    let Some(mut writer) =
664        previous_view_uniforms
665            .uniforms
666            .get_writer(view_count, &render_device, &render_queue)
667    else {
668        return;
669    };
670
671    for (entity, camera, maybe_previous_view_uniforms) in views_iter {
672        let prev_view_data = match maybe_previous_view_uniforms {
673            Some(previous_view) => previous_view.clone(),
674            None => {
675                let world_from_view = camera.world_from_view.affine();
676                let view_from_world = Mat4::from(world_from_view.inverse());
677                let view_from_clip = camera.clip_from_view.inverse();
678
679                PreviousViewData {
680                    view_from_world,
681                    clip_from_world: camera.clip_from_view * view_from_world,
682                    clip_from_view: camera.clip_from_view,
683                    world_from_clip: Mat4::from(world_from_view) * view_from_clip,
684                    view_from_clip,
685                }
686            }
687        };
688
689        commands.entity(entity).insert(PreviousViewUniformOffset {
690            offset: writer.write(&prev_view_data),
691        });
692    }
693}
694
695#[derive(Resource)]
696pub struct PrepassViewBindGroup {
697    pub motion_vectors: Option<BindGroup>,
698    pub no_motion_vectors: Option<BindGroup>,
699    pub empty_bind_group: BindGroup,
700}
701
702pub fn init_prepass_view_bind_group(
703    mut commands: Commands,
704    render_device: Res<RenderDevice>,
705    pipeline: Res<PrepassPipeline>,
706) {
707    let empty_bind_group = render_device.create_bind_group(
708        "prepass_view_empty_bind_group",
709        &pipeline.empty_layout,
710        &[],
711    );
712    commands.insert_resource(PrepassViewBindGroup {
713        motion_vectors: None,
714        no_motion_vectors: None,
715        empty_bind_group,
716    });
717}
718
719pub fn prepare_prepass_view_bind_group(
720    render_device: Res<RenderDevice>,
721    prepass_pipeline: Res<PrepassPipeline>,
722    view_uniforms: Res<ViewUniforms>,
723    globals_buffer: Res<GlobalsBuffer>,
724    previous_view_uniforms: Res<PreviousViewUniforms>,
725    visibility_ranges: Res<RenderVisibilityRanges>,
726    mut prepass_view_bind_group: ResMut<PrepassViewBindGroup>,
727) {
728    if let (Some(view_binding), Some(globals_binding), Some(visibility_ranges_buffer)) = (
729        view_uniforms.uniforms.binding(),
730        globals_buffer.buffer.binding(),
731        visibility_ranges.buffer().buffer(),
732    ) {
733        prepass_view_bind_group.no_motion_vectors = Some(render_device.create_bind_group(
734            "prepass_view_no_motion_vectors_bind_group",
735            &prepass_pipeline.view_layout_no_motion_vectors,
736            &BindGroupEntries::with_indices((
737                (0, view_binding.clone()),
738                (1, globals_binding.clone()),
739                (14, visibility_ranges_buffer.as_entire_binding()),
740            )),
741        ));
742
743        if let Some(previous_view_uniforms_binding) = previous_view_uniforms.uniforms.binding() {
744            prepass_view_bind_group.motion_vectors = Some(render_device.create_bind_group(
745                "prepass_view_motion_vectors_bind_group",
746                &prepass_pipeline.view_layout_motion_vectors,
747                &BindGroupEntries::with_indices((
748                    (0, view_binding),
749                    (1, globals_binding),
750                    (2, previous_view_uniforms_binding),
751                    (14, visibility_ranges_buffer.as_entire_binding()),
752                )),
753            ));
754        }
755    }
756}
757
758/// Stores the [`SpecializedPrepassMaterialViewPipelineCache`] for each view.
759#[derive(Resource, Deref, DerefMut, Default)]
760pub struct SpecializedPrepassMaterialPipelineCache {
761    // view_entity -> view pipeline cache
762    #[deref]
763    map: HashMap<RetainedViewEntity, SpecializedPrepassMaterialViewPipelineCache>,
764}
765
766/// Stores the cached render pipeline ID for each entity in a single view, as
767/// well as the last time it was changed.
768#[derive(Deref, DerefMut, Default)]
769pub struct SpecializedPrepassMaterialViewPipelineCache {
770    // material entity -> (tick, pipeline_id)
771    #[deref]
772    map: MainEntityHashMap<(Tick, CachedRenderPipelineId)>,
773}
774
775#[derive(Resource, Deref, DerefMut, Default, Clone)]
776pub struct ViewKeyPrepassCache(HashMap<RetainedViewEntity, MeshPipelineKey>);
777
778#[derive(Resource, Deref, DerefMut, Default, Clone)]
779pub struct ViewPrepassSpecializationTicks(HashMap<RetainedViewEntity, Tick>);
780
781pub fn check_prepass_views_need_specialization(
782    mut view_key_cache: ResMut<ViewKeyPrepassCache>,
783    mut view_specialization_ticks: ResMut<ViewPrepassSpecializationTicks>,
784    mut views: Query<(
785        &ExtractedView,
786        &Msaa,
787        Option<&DepthPrepass>,
788        Option<&NormalPrepass>,
789        Option<&MotionVectorPrepass>,
790    )>,
791    ticks: SystemChangeTick,
792) {
793    for (view, msaa, depth_prepass, normal_prepass, motion_vector_prepass) in views.iter_mut() {
794        let mut view_key = MeshPipelineKey::from_msaa_samples(msaa.samples());
795        if depth_prepass.is_some() {
796            view_key |= MeshPipelineKey::DEPTH_PREPASS;
797        }
798        if normal_prepass.is_some() {
799            view_key |= MeshPipelineKey::NORMAL_PREPASS;
800        }
801        if motion_vector_prepass.is_some() {
802            view_key |= MeshPipelineKey::MOTION_VECTOR_PREPASS;
803        }
804
805        if let Some(current_key) = view_key_cache.get_mut(&view.retained_view_entity) {
806            if *current_key != view_key {
807                view_key_cache.insert(view.retained_view_entity, view_key);
808                view_specialization_ticks.insert(view.retained_view_entity, ticks.this_run());
809            }
810        } else {
811            view_key_cache.insert(view.retained_view_entity, view_key);
812            view_specialization_ticks.insert(view.retained_view_entity, ticks.this_run());
813        }
814    }
815}
816
817pub fn specialize_prepass_material_meshes(
818    render_meshes: Res<RenderAssets<RenderMesh>>,
819    render_materials: Res<ErasedRenderAssets<PreparedMaterial>>,
820    render_mesh_instances: Res<RenderMeshInstances>,
821    render_material_instances: Res<RenderMaterialInstances>,
822    render_lightmaps: Res<RenderLightmaps>,
823    render_visibility_ranges: Res<RenderVisibilityRanges>,
824    view_key_cache: Res<ViewKeyPrepassCache>,
825    views: Query<(
826        &ExtractedView,
827        &RenderVisibleEntities,
828        &Msaa,
829        Option<&MotionVectorPrepass>,
830        Option<&DeferredPrepass>,
831    )>,
832    (
833        opaque_prepass_render_phases,
834        alpha_mask_prepass_render_phases,
835        opaque_deferred_render_phases,
836        alpha_mask_deferred_render_phases,
837    ): (
838        Res<ViewBinnedRenderPhases<Opaque3dPrepass>>,
839        Res<ViewBinnedRenderPhases<AlphaMask3dPrepass>>,
840        Res<ViewBinnedRenderPhases<Opaque3dDeferred>>,
841        Res<ViewBinnedRenderPhases<AlphaMask3dDeferred>>,
842    ),
843    (
844        mut specialized_material_pipeline_cache,
845        ticks,
846        prepass_pipeline,
847        mut pipelines,
848        pipeline_cache,
849        view_specialization_ticks,
850        entity_specialization_ticks,
851    ): (
852        ResMut<SpecializedPrepassMaterialPipelineCache>,
853        SystemChangeTick,
854        Res<PrepassPipeline>,
855        ResMut<SpecializedMeshPipelines<PrepassPipelineSpecializer>>,
856        Res<PipelineCache>,
857        Res<ViewPrepassSpecializationTicks>,
858        Res<EntitySpecializationTicks>,
859    ),
860) {
861    for (extracted_view, visible_entities, msaa, motion_vector_prepass, deferred_prepass) in &views
862    {
863        if !opaque_deferred_render_phases.contains_key(&extracted_view.retained_view_entity)
864            && !alpha_mask_deferred_render_phases.contains_key(&extracted_view.retained_view_entity)
865            && !opaque_prepass_render_phases.contains_key(&extracted_view.retained_view_entity)
866            && !alpha_mask_prepass_render_phases.contains_key(&extracted_view.retained_view_entity)
867        {
868            continue;
869        }
870
871        let Some(view_key) = view_key_cache.get(&extracted_view.retained_view_entity) else {
872            continue;
873        };
874
875        let view_tick = view_specialization_ticks
876            .get(&extracted_view.retained_view_entity)
877            .unwrap();
878        let view_specialized_material_pipeline_cache = specialized_material_pipeline_cache
879            .entry(extracted_view.retained_view_entity)
880            .or_default();
881
882        for (_, visible_entity) in visible_entities.iter::<Mesh3d>() {
883            let Some(material_instance) = render_material_instances.instances.get(visible_entity)
884            else {
885                continue;
886            };
887            let Some(mesh_instance) = render_mesh_instances.render_mesh_queue_data(*visible_entity)
888            else {
889                continue;
890            };
891            let entity_tick = entity_specialization_ticks.get(visible_entity).unwrap();
892            let last_specialized_tick = view_specialized_material_pipeline_cache
893                .get(visible_entity)
894                .map(|(tick, _)| *tick);
895            let needs_specialization = last_specialized_tick.is_none_or(|tick| {
896                view_tick.is_newer_than(tick, ticks.this_run())
897                    || entity_tick.is_newer_than(tick, ticks.this_run())
898            });
899            if !needs_specialization {
900                continue;
901            }
902            let Some(material) = render_materials.get(material_instance.asset_id) else {
903                continue;
904            };
905            if !material.properties.prepass_enabled && !material.properties.shadows_enabled {
906                // If the material was previously specialized for prepass, remove it
907                view_specialized_material_pipeline_cache.remove(visible_entity);
908                continue;
909            }
910            let Some(mesh) = render_meshes.get(mesh_instance.mesh_asset_id) else {
911                continue;
912            };
913
914            let mut mesh_key = *view_key | MeshPipelineKey::from_bits_retain(mesh.key_bits.bits());
915
916            let alpha_mode = material.properties.alpha_mode;
917            match alpha_mode {
918                AlphaMode::Opaque | AlphaMode::AlphaToCoverage | AlphaMode::Mask(_) => {
919                    mesh_key |= alpha_mode_pipeline_key(alpha_mode, msaa);
920                }
921                AlphaMode::Blend
922                | AlphaMode::Premultiplied
923                | AlphaMode::Add
924                | AlphaMode::Multiply => {
925                    // In case this material was previously in a valid alpha_mode, remove it to
926                    // stop the queue system from assuming its retained cache to be valid.
927                    view_specialized_material_pipeline_cache.remove(visible_entity);
928                    continue;
929                }
930            }
931
932            if material.properties.reads_view_transmission_texture {
933                // No-op: Materials reading from `ViewTransmissionTexture` are not rendered in the `Opaque3d`
934                // phase, and are therefore also excluded from the prepass much like alpha-blended materials.
935                view_specialized_material_pipeline_cache.remove(visible_entity);
936                continue;
937            }
938
939            let forward = match material.properties.render_method {
940                OpaqueRendererMethod::Forward => true,
941                OpaqueRendererMethod::Deferred => false,
942                OpaqueRendererMethod::Auto => unreachable!(),
943            };
944
945            let deferred = deferred_prepass.is_some() && !forward;
946
947            if deferred {
948                mesh_key |= MeshPipelineKey::DEFERRED_PREPASS;
949            }
950
951            if let Some(lightmap) = render_lightmaps.render_lightmaps.get(visible_entity) {
952                // Even though we don't use the lightmap in the forward prepass, the
953                // `SetMeshBindGroup` render command will bind the data for it. So
954                // we need to include the appropriate flag in the mesh pipeline key
955                // to ensure that the necessary bind group layout entries are
956                // present.
957                mesh_key |= MeshPipelineKey::LIGHTMAPPED;
958
959                if lightmap.bicubic_sampling && deferred {
960                    mesh_key |= MeshPipelineKey::LIGHTMAP_BICUBIC_SAMPLING;
961                }
962            }
963
964            if render_visibility_ranges.entity_has_crossfading_visibility_ranges(*visible_entity) {
965                mesh_key |= MeshPipelineKey::VISIBILITY_RANGE_DITHER;
966            }
967
968            // If the previous frame has skins or morph targets, note that.
969            if motion_vector_prepass.is_some() {
970                if mesh_instance
971                    .flags
972                    .contains(RenderMeshInstanceFlags::HAS_PREVIOUS_SKIN)
973                {
974                    mesh_key |= MeshPipelineKey::HAS_PREVIOUS_SKIN;
975                }
976                if mesh_instance
977                    .flags
978                    .contains(RenderMeshInstanceFlags::HAS_PREVIOUS_MORPH)
979                {
980                    mesh_key |= MeshPipelineKey::HAS_PREVIOUS_MORPH;
981                }
982            }
983
984            let erased_key = ErasedMaterialPipelineKey {
985                mesh_key,
986                material_key: material.properties.material_key.clone(),
987                type_id: material_instance.asset_id.type_id(),
988            };
989            let prepass_pipeline_specializer = PrepassPipelineSpecializer {
990                pipeline: prepass_pipeline.clone(),
991                properties: material.properties.clone(),
992            };
993            let pipeline_id = pipelines.specialize(
994                &pipeline_cache,
995                &prepass_pipeline_specializer,
996                erased_key,
997                &mesh.layout,
998            );
999            let pipeline_id = match pipeline_id {
1000                Ok(id) => id,
1001                Err(err) => {
1002                    error!("{}", err);
1003                    continue;
1004                }
1005            };
1006
1007            view_specialized_material_pipeline_cache
1008                .insert(*visible_entity, (ticks.this_run(), pipeline_id));
1009        }
1010    }
1011}
1012
1013pub fn queue_prepass_material_meshes(
1014    render_mesh_instances: Res<RenderMeshInstances>,
1015    render_materials: Res<ErasedRenderAssets<PreparedMaterial>>,
1016    render_material_instances: Res<RenderMaterialInstances>,
1017    mesh_allocator: Res<MeshAllocator>,
1018    gpu_preprocessing_support: Res<GpuPreprocessingSupport>,
1019    mut opaque_prepass_render_phases: ResMut<ViewBinnedRenderPhases<Opaque3dPrepass>>,
1020    mut alpha_mask_prepass_render_phases: ResMut<ViewBinnedRenderPhases<AlphaMask3dPrepass>>,
1021    mut opaque_deferred_render_phases: ResMut<ViewBinnedRenderPhases<Opaque3dDeferred>>,
1022    mut alpha_mask_deferred_render_phases: ResMut<ViewBinnedRenderPhases<AlphaMask3dDeferred>>,
1023    views: Query<(&ExtractedView, &RenderVisibleEntities)>,
1024    specialized_material_pipeline_cache: Res<SpecializedPrepassMaterialPipelineCache>,
1025) {
1026    for (extracted_view, visible_entities) in &views {
1027        let (
1028            mut opaque_phase,
1029            mut alpha_mask_phase,
1030            mut opaque_deferred_phase,
1031            mut alpha_mask_deferred_phase,
1032        ) = (
1033            opaque_prepass_render_phases.get_mut(&extracted_view.retained_view_entity),
1034            alpha_mask_prepass_render_phases.get_mut(&extracted_view.retained_view_entity),
1035            opaque_deferred_render_phases.get_mut(&extracted_view.retained_view_entity),
1036            alpha_mask_deferred_render_phases.get_mut(&extracted_view.retained_view_entity),
1037        );
1038
1039        let Some(view_specialized_material_pipeline_cache) =
1040            specialized_material_pipeline_cache.get(&extracted_view.retained_view_entity)
1041        else {
1042            continue;
1043        };
1044
1045        // Skip if there's no place to put the mesh.
1046        if opaque_phase.is_none()
1047            && alpha_mask_phase.is_none()
1048            && opaque_deferred_phase.is_none()
1049            && alpha_mask_deferred_phase.is_none()
1050        {
1051            continue;
1052        }
1053
1054        for (render_entity, visible_entity) in visible_entities.iter::<Mesh3d>() {
1055            let Some((current_change_tick, pipeline_id)) =
1056                view_specialized_material_pipeline_cache.get(visible_entity)
1057            else {
1058                continue;
1059            };
1060
1061            // Skip the entity if it's cached in a bin and up to date.
1062            if opaque_phase.as_mut().is_some_and(|phase| {
1063                phase.validate_cached_entity(*visible_entity, *current_change_tick)
1064            }) || alpha_mask_phase.as_mut().is_some_and(|phase| {
1065                phase.validate_cached_entity(*visible_entity, *current_change_tick)
1066            }) || opaque_deferred_phase.as_mut().is_some_and(|phase| {
1067                phase.validate_cached_entity(*visible_entity, *current_change_tick)
1068            }) || alpha_mask_deferred_phase.as_mut().is_some_and(|phase| {
1069                phase.validate_cached_entity(*visible_entity, *current_change_tick)
1070            }) {
1071                continue;
1072            }
1073
1074            let Some(material_instance) = render_material_instances.instances.get(visible_entity)
1075            else {
1076                continue;
1077            };
1078            let Some(mesh_instance) = render_mesh_instances.render_mesh_queue_data(*visible_entity)
1079            else {
1080                continue;
1081            };
1082            let Some(material) = render_materials.get(material_instance.asset_id) else {
1083                continue;
1084            };
1085            let (vertex_slab, index_slab) = mesh_allocator.mesh_slabs(&mesh_instance.mesh_asset_id);
1086
1087            let deferred = match material.properties.render_method {
1088                OpaqueRendererMethod::Forward => false,
1089                OpaqueRendererMethod::Deferred => true,
1090                OpaqueRendererMethod::Auto => unreachable!(),
1091            };
1092
1093            match material.properties.render_phase_type {
1094                RenderPhaseType::Opaque => {
1095                    if deferred {
1096                        opaque_deferred_phase.as_mut().unwrap().add(
1097                            OpaqueNoLightmap3dBatchSetKey {
1098                                draw_function: material
1099                                    .properties
1100                                    .get_draw_function(DeferredDrawFunction)
1101                                    .unwrap(),
1102                                pipeline: *pipeline_id,
1103                                material_bind_group_index: Some(material.binding.group.0),
1104                                vertex_slab: vertex_slab.unwrap_or_default(),
1105                                index_slab,
1106                            },
1107                            OpaqueNoLightmap3dBinKey {
1108                                asset_id: mesh_instance.mesh_asset_id.into(),
1109                            },
1110                            (*render_entity, *visible_entity),
1111                            mesh_instance.current_uniform_index,
1112                            BinnedRenderPhaseType::mesh(
1113                                mesh_instance.should_batch(),
1114                                &gpu_preprocessing_support,
1115                            ),
1116                            *current_change_tick,
1117                        );
1118                    } else if let Some(opaque_phase) = opaque_phase.as_mut() {
1119                        let (vertex_slab, index_slab) =
1120                            mesh_allocator.mesh_slabs(&mesh_instance.mesh_asset_id);
1121                        opaque_phase.add(
1122                            OpaqueNoLightmap3dBatchSetKey {
1123                                draw_function: material
1124                                    .properties
1125                                    .get_draw_function(PrepassDrawFunction)
1126                                    .unwrap(),
1127                                pipeline: *pipeline_id,
1128                                material_bind_group_index: Some(material.binding.group.0),
1129                                vertex_slab: vertex_slab.unwrap_or_default(),
1130                                index_slab,
1131                            },
1132                            OpaqueNoLightmap3dBinKey {
1133                                asset_id: mesh_instance.mesh_asset_id.into(),
1134                            },
1135                            (*render_entity, *visible_entity),
1136                            mesh_instance.current_uniform_index,
1137                            BinnedRenderPhaseType::mesh(
1138                                mesh_instance.should_batch(),
1139                                &gpu_preprocessing_support,
1140                            ),
1141                            *current_change_tick,
1142                        );
1143                    }
1144                }
1145                RenderPhaseType::AlphaMask => {
1146                    if deferred {
1147                        let (vertex_slab, index_slab) =
1148                            mesh_allocator.mesh_slabs(&mesh_instance.mesh_asset_id);
1149                        let batch_set_key = OpaqueNoLightmap3dBatchSetKey {
1150                            draw_function: material
1151                                .properties
1152                                .get_draw_function(DeferredDrawFunction)
1153                                .unwrap(),
1154                            pipeline: *pipeline_id,
1155                            material_bind_group_index: Some(material.binding.group.0),
1156                            vertex_slab: vertex_slab.unwrap_or_default(),
1157                            index_slab,
1158                        };
1159                        let bin_key = OpaqueNoLightmap3dBinKey {
1160                            asset_id: mesh_instance.mesh_asset_id.into(),
1161                        };
1162                        alpha_mask_deferred_phase.as_mut().unwrap().add(
1163                            batch_set_key,
1164                            bin_key,
1165                            (*render_entity, *visible_entity),
1166                            mesh_instance.current_uniform_index,
1167                            BinnedRenderPhaseType::mesh(
1168                                mesh_instance.should_batch(),
1169                                &gpu_preprocessing_support,
1170                            ),
1171                            *current_change_tick,
1172                        );
1173                    } else if let Some(alpha_mask_phase) = alpha_mask_phase.as_mut() {
1174                        let (vertex_slab, index_slab) =
1175                            mesh_allocator.mesh_slabs(&mesh_instance.mesh_asset_id);
1176                        let batch_set_key = OpaqueNoLightmap3dBatchSetKey {
1177                            draw_function: material
1178                                .properties
1179                                .get_draw_function(PrepassDrawFunction)
1180                                .unwrap(),
1181                            pipeline: *pipeline_id,
1182                            material_bind_group_index: Some(material.binding.group.0),
1183                            vertex_slab: vertex_slab.unwrap_or_default(),
1184                            index_slab,
1185                        };
1186                        let bin_key = OpaqueNoLightmap3dBinKey {
1187                            asset_id: mesh_instance.mesh_asset_id.into(),
1188                        };
1189                        alpha_mask_phase.add(
1190                            batch_set_key,
1191                            bin_key,
1192                            (*render_entity, *visible_entity),
1193                            mesh_instance.current_uniform_index,
1194                            BinnedRenderPhaseType::mesh(
1195                                mesh_instance.should_batch(),
1196                                &gpu_preprocessing_support,
1197                            ),
1198                            *current_change_tick,
1199                        );
1200                    }
1201                }
1202                _ => {}
1203            }
1204        }
1205    }
1206}
1207
1208pub struct SetPrepassViewBindGroup<const I: usize>;
1209impl<P: PhaseItem, const I: usize> RenderCommand<P> for SetPrepassViewBindGroup<I> {
1210    type Param = SRes<PrepassViewBindGroup>;
1211    type ViewQuery = (
1212        Read<ViewUniformOffset>,
1213        Has<MotionVectorPrepass>,
1214        Option<Read<PreviousViewUniformOffset>>,
1215    );
1216    type ItemQuery = ();
1217
1218    #[inline]
1219    fn render<'w>(
1220        _item: &P,
1221        (view_uniform_offset, has_motion_vector_prepass, previous_view_uniform_offset): (
1222            &'_ ViewUniformOffset,
1223            bool,
1224            Option<&'_ PreviousViewUniformOffset>,
1225        ),
1226        _entity: Option<()>,
1227        prepass_view_bind_group: SystemParamItem<'w, '_, Self::Param>,
1228        pass: &mut TrackedRenderPass<'w>,
1229    ) -> RenderCommandResult {
1230        let prepass_view_bind_group = prepass_view_bind_group.into_inner();
1231
1232        match previous_view_uniform_offset {
1233            Some(previous_view_uniform_offset) if has_motion_vector_prepass => {
1234                pass.set_bind_group(
1235                    I,
1236                    prepass_view_bind_group.motion_vectors.as_ref().unwrap(),
1237                    &[
1238                        view_uniform_offset.offset,
1239                        previous_view_uniform_offset.offset,
1240                    ],
1241                );
1242            }
1243            _ => {
1244                pass.set_bind_group(
1245                    I,
1246                    prepass_view_bind_group.no_motion_vectors.as_ref().unwrap(),
1247                    &[view_uniform_offset.offset],
1248                );
1249            }
1250        }
1251        RenderCommandResult::Success
1252    }
1253}
1254
1255pub struct SetPrepassViewEmptyBindGroup<const I: usize>;
1256impl<P: PhaseItem, const I: usize> RenderCommand<P> for SetPrepassViewEmptyBindGroup<I> {
1257    type Param = SRes<PrepassViewBindGroup>;
1258    type ViewQuery = ();
1259    type ItemQuery = ();
1260
1261    #[inline]
1262    fn render<'w>(
1263        _item: &P,
1264        _view: (),
1265        _entity: Option<()>,
1266        prepass_view_bind_group: SystemParamItem<'w, '_, Self::Param>,
1267        pass: &mut TrackedRenderPass<'w>,
1268    ) -> RenderCommandResult {
1269        let prepass_view_bind_group = prepass_view_bind_group.into_inner();
1270        pass.set_bind_group(I, &prepass_view_bind_group.empty_bind_group, &[]);
1271        RenderCommandResult::Success
1272    }
1273}
1274
1275pub type DrawPrepass = (
1276    SetItemPipeline,
1277    SetPrepassViewBindGroup<0>,
1278    SetPrepassViewEmptyBindGroup<1>,
1279    SetMeshBindGroup<2>,
1280    SetMaterialBindGroup<3>,
1281    DrawMesh,
1282);