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 => {
987                    // In case this material was previously in a valid alpha_mode, remove it to
988                    // stop the queue system from assuming its retained cache to be valid.
989                    view_specialized_material_pipeline_cache.remove(visible_entity);
990                    continue;
991                }
992            }
993
994            if material.properties.reads_view_transmission_texture {
995                // No-op: Materials reading from `ViewTransmissionTexture` are not rendered in the `Opaque3d`
996                // phase, and are therefore also excluded from the prepass much like alpha-blended materials.
997                view_specialized_material_pipeline_cache.remove(visible_entity);
998                continue;
999            }
1000
1001            let forward = match material.properties.render_method {
1002                OpaqueRendererMethod::Forward => true,
1003                OpaqueRendererMethod::Deferred => false,
1004                OpaqueRendererMethod::Auto => unreachable!(),
1005            };
1006
1007            let deferred = deferred_prepass.is_some() && !forward;
1008
1009            if deferred {
1010                mesh_key |= MeshPipelineKey::DEFERRED_PREPASS;
1011            }
1012
1013            if let Some(lightmap) = render_lightmaps.render_lightmaps.get(visible_entity) {
1014                // Even though we don't use the lightmap in the forward prepass, the
1015                // `SetMeshBindGroup` render command will bind the data for it. So
1016                // we need to include the appropriate flag in the mesh pipeline key
1017                // to ensure that the necessary bind group layout entries are
1018                // present.
1019                mesh_key |= MeshPipelineKey::LIGHTMAPPED;
1020
1021                if lightmap.bicubic_sampling && deferred {
1022                    mesh_key |= MeshPipelineKey::LIGHTMAP_BICUBIC_SAMPLING;
1023                }
1024            }
1025
1026            if render_visibility_ranges.entity_has_crossfading_visibility_ranges(*visible_entity) {
1027                mesh_key |= MeshPipelineKey::VISIBILITY_RANGE_DITHER;
1028            }
1029
1030            // If the previous frame has skins or morph targets, note that.
1031            if motion_vector_prepass.is_some() {
1032                if mesh_instance
1033                    .flags
1034                    .contains(RenderMeshInstanceFlags::HAS_PREVIOUS_SKIN)
1035                {
1036                    mesh_key |= MeshPipelineKey::HAS_PREVIOUS_SKIN;
1037                }
1038                if mesh_instance
1039                    .flags
1040                    .contains(RenderMeshInstanceFlags::HAS_PREVIOUS_MORPH)
1041                {
1042                    mesh_key |= MeshPipelineKey::HAS_PREVIOUS_MORPH;
1043                }
1044            }
1045
1046            let pipeline_id = pipelines.specialize(
1047                &pipeline_cache,
1048                &prepass_pipeline,
1049                MaterialPipelineKey {
1050                    mesh_key,
1051                    bind_group_data: material_bind_group
1052                        .get_extra_data(material.binding.slot)
1053                        .clone(),
1054                },
1055                &mesh.layout,
1056            );
1057            let pipeline_id = match pipeline_id {
1058                Ok(id) => id,
1059                Err(err) => {
1060                    error!("{}", err);
1061                    continue;
1062                }
1063            };
1064
1065            view_specialized_material_pipeline_cache
1066                .insert(*visible_entity, (ticks.this_run(), pipeline_id));
1067        }
1068    }
1069}
1070
1071pub fn queue_prepass_material_meshes<M: Material>(
1072    render_mesh_instances: Res<RenderMeshInstances>,
1073    render_materials: Res<RenderAssets<PreparedMaterial<M>>>,
1074    render_material_instances: Res<RenderMaterialInstances>,
1075    mesh_allocator: Res<MeshAllocator>,
1076    gpu_preprocessing_support: Res<GpuPreprocessingSupport>,
1077    mut opaque_prepass_render_phases: ResMut<ViewBinnedRenderPhases<Opaque3dPrepass>>,
1078    mut alpha_mask_prepass_render_phases: ResMut<ViewBinnedRenderPhases<AlphaMask3dPrepass>>,
1079    mut opaque_deferred_render_phases: ResMut<ViewBinnedRenderPhases<Opaque3dDeferred>>,
1080    mut alpha_mask_deferred_render_phases: ResMut<ViewBinnedRenderPhases<AlphaMask3dDeferred>>,
1081    views: Query<(&ExtractedView, &RenderVisibleEntities)>,
1082    specialized_material_pipeline_cache: Res<SpecializedPrepassMaterialPipelineCache<M>>,
1083) where
1084    M::Data: PartialEq + Eq + Hash + Clone,
1085{
1086    for (extracted_view, visible_entities) in &views {
1087        let (
1088            mut opaque_phase,
1089            mut alpha_mask_phase,
1090            mut opaque_deferred_phase,
1091            mut alpha_mask_deferred_phase,
1092        ) = (
1093            opaque_prepass_render_phases.get_mut(&extracted_view.retained_view_entity),
1094            alpha_mask_prepass_render_phases.get_mut(&extracted_view.retained_view_entity),
1095            opaque_deferred_render_phases.get_mut(&extracted_view.retained_view_entity),
1096            alpha_mask_deferred_render_phases.get_mut(&extracted_view.retained_view_entity),
1097        );
1098
1099        let Some(view_specialized_material_pipeline_cache) =
1100            specialized_material_pipeline_cache.get(&extracted_view.retained_view_entity)
1101        else {
1102            continue;
1103        };
1104
1105        // Skip if there's no place to put the mesh.
1106        if opaque_phase.is_none()
1107            && alpha_mask_phase.is_none()
1108            && opaque_deferred_phase.is_none()
1109            && alpha_mask_deferred_phase.is_none()
1110        {
1111            continue;
1112        }
1113
1114        for (render_entity, visible_entity) in visible_entities.iter::<Mesh3d>() {
1115            let Some((current_change_tick, pipeline_id)) =
1116                view_specialized_material_pipeline_cache.get(visible_entity)
1117            else {
1118                continue;
1119            };
1120
1121            // Skip the entity if it's cached in a bin and up to date.
1122            if opaque_phase.as_mut().is_some_and(|phase| {
1123                phase.validate_cached_entity(*visible_entity, *current_change_tick)
1124            }) || alpha_mask_phase.as_mut().is_some_and(|phase| {
1125                phase.validate_cached_entity(*visible_entity, *current_change_tick)
1126            }) || opaque_deferred_phase.as_mut().is_some_and(|phase| {
1127                phase.validate_cached_entity(*visible_entity, *current_change_tick)
1128            }) || alpha_mask_deferred_phase.as_mut().is_some_and(|phase| {
1129                phase.validate_cached_entity(*visible_entity, *current_change_tick)
1130            }) {
1131                continue;
1132            }
1133
1134            let Some(material_instance) = render_material_instances.instances.get(visible_entity)
1135            else {
1136                continue;
1137            };
1138            let Ok(material_asset_id) = material_instance.asset_id.try_typed::<M>() else {
1139                continue;
1140            };
1141            let Some(mesh_instance) = render_mesh_instances.render_mesh_queue_data(*visible_entity)
1142            else {
1143                continue;
1144            };
1145            let Some(material) = render_materials.get(material_asset_id) else {
1146                continue;
1147            };
1148            let (vertex_slab, index_slab) = mesh_allocator.mesh_slabs(&mesh_instance.mesh_asset_id);
1149
1150            let deferred = match material.properties.render_method {
1151                OpaqueRendererMethod::Forward => false,
1152                OpaqueRendererMethod::Deferred => true,
1153                OpaqueRendererMethod::Auto => unreachable!(),
1154            };
1155
1156            match material.properties.render_phase_type {
1157                RenderPhaseType::Opaque => {
1158                    if deferred {
1159                        opaque_deferred_phase.as_mut().unwrap().add(
1160                            OpaqueNoLightmap3dBatchSetKey {
1161                                draw_function: material
1162                                    .properties
1163                                    .deferred_draw_function_id
1164                                    .unwrap(),
1165                                pipeline: *pipeline_id,
1166                                material_bind_group_index: Some(material.binding.group.0),
1167                                vertex_slab: vertex_slab.unwrap_or_default(),
1168                                index_slab,
1169                            },
1170                            OpaqueNoLightmap3dBinKey {
1171                                asset_id: mesh_instance.mesh_asset_id.into(),
1172                            },
1173                            (*render_entity, *visible_entity),
1174                            mesh_instance.current_uniform_index,
1175                            BinnedRenderPhaseType::mesh(
1176                                mesh_instance.should_batch(),
1177                                &gpu_preprocessing_support,
1178                            ),
1179                            *current_change_tick,
1180                        );
1181                    } else if let Some(opaque_phase) = opaque_phase.as_mut() {
1182                        let (vertex_slab, index_slab) =
1183                            mesh_allocator.mesh_slabs(&mesh_instance.mesh_asset_id);
1184                        opaque_phase.add(
1185                            OpaqueNoLightmap3dBatchSetKey {
1186                                draw_function: material
1187                                    .properties
1188                                    .prepass_draw_function_id
1189                                    .unwrap(),
1190                                pipeline: *pipeline_id,
1191                                material_bind_group_index: Some(material.binding.group.0),
1192                                vertex_slab: vertex_slab.unwrap_or_default(),
1193                                index_slab,
1194                            },
1195                            OpaqueNoLightmap3dBinKey {
1196                                asset_id: mesh_instance.mesh_asset_id.into(),
1197                            },
1198                            (*render_entity, *visible_entity),
1199                            mesh_instance.current_uniform_index,
1200                            BinnedRenderPhaseType::mesh(
1201                                mesh_instance.should_batch(),
1202                                &gpu_preprocessing_support,
1203                            ),
1204                            *current_change_tick,
1205                        );
1206                    }
1207                }
1208                RenderPhaseType::AlphaMask => {
1209                    if deferred {
1210                        let (vertex_slab, index_slab) =
1211                            mesh_allocator.mesh_slabs(&mesh_instance.mesh_asset_id);
1212                        let batch_set_key = OpaqueNoLightmap3dBatchSetKey {
1213                            draw_function: material.properties.deferred_draw_function_id.unwrap(),
1214                            pipeline: *pipeline_id,
1215                            material_bind_group_index: Some(material.binding.group.0),
1216                            vertex_slab: vertex_slab.unwrap_or_default(),
1217                            index_slab,
1218                        };
1219                        let bin_key = OpaqueNoLightmap3dBinKey {
1220                            asset_id: mesh_instance.mesh_asset_id.into(),
1221                        };
1222                        alpha_mask_deferred_phase.as_mut().unwrap().add(
1223                            batch_set_key,
1224                            bin_key,
1225                            (*render_entity, *visible_entity),
1226                            mesh_instance.current_uniform_index,
1227                            BinnedRenderPhaseType::mesh(
1228                                mesh_instance.should_batch(),
1229                                &gpu_preprocessing_support,
1230                            ),
1231                            *current_change_tick,
1232                        );
1233                    } else if let Some(alpha_mask_phase) = alpha_mask_phase.as_mut() {
1234                        let (vertex_slab, index_slab) =
1235                            mesh_allocator.mesh_slabs(&mesh_instance.mesh_asset_id);
1236                        let batch_set_key = OpaqueNoLightmap3dBatchSetKey {
1237                            draw_function: material.properties.prepass_draw_function_id.unwrap(),
1238                            pipeline: *pipeline_id,
1239                            material_bind_group_index: Some(material.binding.group.0),
1240                            vertex_slab: vertex_slab.unwrap_or_default(),
1241                            index_slab,
1242                        };
1243                        let bin_key = OpaqueNoLightmap3dBinKey {
1244                            asset_id: mesh_instance.mesh_asset_id.into(),
1245                        };
1246                        alpha_mask_phase.add(
1247                            batch_set_key,
1248                            bin_key,
1249                            (*render_entity, *visible_entity),
1250                            mesh_instance.current_uniform_index,
1251                            BinnedRenderPhaseType::mesh(
1252                                mesh_instance.should_batch(),
1253                                &gpu_preprocessing_support,
1254                            ),
1255                            *current_change_tick,
1256                        );
1257                    }
1258                }
1259                _ => {}
1260            }
1261        }
1262    }
1263}
1264
1265pub struct SetPrepassViewBindGroup<const I: usize>;
1266impl<P: PhaseItem, const I: usize> RenderCommand<P> for SetPrepassViewBindGroup<I> {
1267    type Param = SRes<PrepassViewBindGroup>;
1268    type ViewQuery = (
1269        Read<ViewUniformOffset>,
1270        Has<MotionVectorPrepass>,
1271        Option<Read<PreviousViewUniformOffset>>,
1272    );
1273    type ItemQuery = ();
1274
1275    #[inline]
1276    fn render<'w>(
1277        _item: &P,
1278        (view_uniform_offset, has_motion_vector_prepass, previous_view_uniform_offset): (
1279            &'_ ViewUniformOffset,
1280            bool,
1281            Option<&'_ PreviousViewUniformOffset>,
1282        ),
1283        _entity: Option<()>,
1284        prepass_view_bind_group: SystemParamItem<'w, '_, Self::Param>,
1285        pass: &mut TrackedRenderPass<'w>,
1286    ) -> RenderCommandResult {
1287        let prepass_view_bind_group = prepass_view_bind_group.into_inner();
1288
1289        match previous_view_uniform_offset {
1290            Some(previous_view_uniform_offset) if has_motion_vector_prepass => {
1291                pass.set_bind_group(
1292                    I,
1293                    prepass_view_bind_group.motion_vectors.as_ref().unwrap(),
1294                    &[
1295                        view_uniform_offset.offset,
1296                        previous_view_uniform_offset.offset,
1297                    ],
1298                );
1299            }
1300            _ => {
1301                pass.set_bind_group(
1302                    I,
1303                    prepass_view_bind_group.no_motion_vectors.as_ref().unwrap(),
1304                    &[view_uniform_offset.offset],
1305                );
1306            }
1307        }
1308
1309        RenderCommandResult::Success
1310    }
1311}
1312
1313pub type DrawPrepass<M> = (
1314    SetItemPipeline,
1315    SetPrepassViewBindGroup<0>,
1316    SetMeshBindGroup<1>,
1317    SetMaterialBindGroup<M, 2>,
1318    DrawMesh,
1319);