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