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