Skip to main content

mesh2d_manual/
mesh2d_manual.rs

1//! This example shows how to manually render 2d items using "mid level render apis" with a custom
2//! pipeline for 2d meshes.
3//! It doesn't use the [`Material2d`] abstraction, but changes the vertex buffer to include vertex color.
4//! Check out the "mesh2d" example for simpler / higher level 2d meshes.
5//!
6//! [`Material2d`]: bevy::sprite_render::Material2d
7
8use bevy::{
9    asset::RenderAssetUsages,
10    color::palettes::basic::YELLOW,
11    core_pipeline::{core_2d::CORE_2D_DEPTH_FORMAT, Core2d, Core2dSystems},
12    ecs::{
13        entity::EntityHash,
14        system::{lifetimeless::SRes, SystemParamItem},
15    },
16    math::{ops, FloatOrd},
17    mesh::{BaseMeshPipelineKey, Indices, MeshVertexAttribute, VertexBufferLayout},
18    platform::collections::HashSet,
19    prelude::*,
20    render::{
21        batching::{no_gpu_preprocessing::batch_and_prepare_sorted_render_phase, GetBatchData},
22        camera::ExtractedCamera,
23        diagnostic::RecordDiagnostics as _,
24        material_bind_groups::{MaterialBindGroupIndex, MaterialBindGroupSlot, MaterialBindingId},
25        mesh::{
26            allocator::MeshAllocator, MeshMetadataFallbackBuffer, RenderMesh, RenderMeshBufferInfo,
27        },
28        render_asset::RenderAssets,
29        render_phase::{
30            sort_phase_system, AddRenderCommand, CachedRenderPipelinePhaseItem, DrawFunctionId,
31            DrawFunctions, PhaseItem, PhaseItemExtraIndex, RenderCommand, RenderCommandResult,
32            SetItemPipeline, SortedPhaseItem, TrackedRenderPass, ViewSortedRenderPhases,
33        },
34        render_resource::{
35            BlendState, CachedRenderPipelineId, ColorTargetState, ColorWrites, CompareFunction,
36            DepthBiasState, DepthStencilState, Face, FragmentState, MultisampleState,
37            PipelineCache, PrimitiveState, PrimitiveTopology, RenderPassDescriptor,
38            RenderPipelineDescriptor, SpecializedRenderPipeline, SpecializedRenderPipelines,
39            StencilFaceState, StencilState, StoreOp, VertexFormat, VertexState, VertexStepMode,
40        },
41        renderer::{RenderContext, ViewQuery},
42        sync_component::{SyncComponent, SyncComponentPlugin},
43        sync_world::{MainEntity, MainEntityHashMap, RenderEntity},
44        view::{
45            ExtractedView, RenderVisibleEntities, RetainedViewEntity, ViewDepthStencilTexture,
46            ViewTarget,
47        },
48        Extract, Render, RenderApp, RenderStartup, RenderSystems,
49    },
50    sprite_render::{
51        extract_2d_meshes, init_mesh_2d_pipeline, Mesh2dBindGroup, Mesh2dPipeline,
52        Mesh2dPipelineKey, Mesh2dTransforms, Mesh2dUniform, MeshFlags, RenderMesh2dInstance,
53        SetMesh2dViewBindGroup,
54    },
55};
56use indexmap::IndexMap;
57use std::{f32::consts::PI, ops::Range};
58
59fn main() {
60    App::new()
61        .add_plugins((DefaultPlugins, ColoredMesh2dPlugin))
62        .add_systems(Startup, star)
63        .run();
64}
65
66fn star(
67    mut commands: Commands,
68    // We will add a new Mesh for the star being created
69    mut meshes: ResMut<Assets<Mesh>>,
70) {
71    // Let's define the mesh for the object we want to draw: a nice star.
72    // We will specify here what kind of topology is used to define the mesh,
73    // that is, how triangles are built from the vertices. We will use a
74    // triangle list, meaning that each vertex of the triangle has to be
75    // specified. We set `RenderAssetUsages::RENDER_WORLD`, meaning this mesh
76    // will not be accessible in future frames from the `meshes` resource, in
77    // order to save on memory once it has been uploaded to the GPU.
78    let mut star = Mesh::new(
79        PrimitiveTopology::TriangleList,
80        RenderAssetUsages::RENDER_WORLD,
81    );
82
83    // Vertices need to have a position attribute. We will use the following
84    // vertices (I hope you can spot the star in the schema).
85    //
86    //        1
87    //
88    //     10   2
89    // 9      0      3
90    //     8     4
91    //        6
92    //   7        5
93    //
94    // These vertices are specified in 3D space.
95    let mut v_pos = vec![[0.0, 0.0, 0.0]];
96    for i in 0..10 {
97        // The angle between each vertex is 1/10 of a full rotation.
98        let a = i as f32 * PI / 5.0;
99        // The radius of inner vertices (even indices) is 100. For outer vertices (odd indices) it's 200.
100        let r = (1 - i % 2) as f32 * 100.0 + 100.0;
101        // Add the vertex position.
102        v_pos.push([r * ops::sin(a), r * ops::cos(a), 0.0]);
103    }
104    // Set the position attribute
105    star.insert_attribute(Mesh::ATTRIBUTE_POSITION, v_pos);
106    // And a RGB color attribute as well. A built-in `Mesh::ATTRIBUTE_COLOR` exists, but we
107    // use a custom vertex attribute here for demonstration purposes.
108    let mut v_color: Vec<u32> = vec![LinearRgba::BLACK.as_u32()];
109    v_color.extend_from_slice(&[LinearRgba::from(YELLOW).as_u32(); 10]);
110    star.insert_attribute(
111        MeshVertexAttribute::new("Vertex_Color", 1, VertexFormat::Uint32),
112        v_color,
113    );
114
115    // Now, we specify the indices of the vertex that are going to compose the
116    // triangles in our star. Vertices in triangles have to be specified in CCW
117    // winding (that will be the front face, colored). Since we are using
118    // triangle list, we will specify each triangle as 3 vertices
119    //   First triangle: 0, 2, 1
120    //   Second triangle: 0, 3, 2
121    //   Third triangle: 0, 4, 3
122    //   etc
123    //   Last triangle: 0, 1, 10
124    let mut indices = vec![0, 1, 10];
125    for i in 2..=10 {
126        indices.extend_from_slice(&[0, i, i - 1]);
127    }
128    star.insert_indices(Indices::U32(indices));
129
130    // We can now spawn the entities for the star and the camera
131    commands.spawn((
132        // We use a marker component to identify the custom colored meshes
133        ColoredMesh2d,
134        // The `Handle<Mesh>` needs to be wrapped in a `Mesh2d` for 2D rendering
135        Mesh2d(meshes.add(star)),
136    ));
137
138    commands.spawn(Camera2d);
139}
140
141/// A marker component for colored 2d meshes
142#[derive(Component, Default)]
143pub struct ColoredMesh2d;
144
145impl SyncComponent<RenderApp> for ColoredMesh2d {
146    type Target = Self;
147}
148
149/// Custom pipeline for 2d meshes with vertex colors
150#[derive(Resource)]
151pub struct ColoredMesh2dPipeline {
152    /// This pipeline wraps the standard [`Mesh2dPipeline`]
153    mesh2d_pipeline: Mesh2dPipeline,
154    /// The shader asset handle.
155    shader: Handle<Shader>,
156}
157
158fn init_colored_mesh_2d_pipeline(
159    mut commands: Commands,
160    mesh2d_pipeline: Res<Mesh2dPipeline>,
161    colored_mesh2d_shader: Res<ColoredMesh2dShader>,
162) {
163    commands.insert_resource(ColoredMesh2dPipeline {
164        mesh2d_pipeline: mesh2d_pipeline.clone(),
165        // Clone the shader from the shader resource we inserted in the plugin.
166        shader: colored_mesh2d_shader.0.clone(),
167    });
168}
169
170// We implement `SpecializedPipeline` to customize the default rendering from `Mesh2dPipeline`
171impl SpecializedRenderPipeline for ColoredMesh2dPipeline {
172    type Key = Mesh2dPipelineKey;
173
174    fn specialize(&self, key: Self::Key) -> RenderPipelineDescriptor {
175        // Customize how to store the meshes' vertex attributes in the vertex buffer
176        // Our meshes only have position and color
177        let formats = vec![
178            // Position
179            VertexFormat::Float32x3,
180            // Color
181            VertexFormat::Uint32,
182        ];
183
184        let vertex_layout =
185            VertexBufferLayout::from_vertex_formats(VertexStepMode::Vertex, formats);
186
187        let format = key.target_format();
188
189        RenderPipelineDescriptor {
190            vertex: VertexState {
191                // Use our custom shader
192                shader: self.shader.clone(),
193                // Use our custom vertex buffer
194                buffers: vec![vertex_layout],
195                ..default()
196            },
197            fragment: Some(FragmentState {
198                // Use our custom shader
199                shader: self.shader.clone(),
200                targets: vec![Some(ColorTargetState {
201                    format,
202                    blend: Some(BlendState::ALPHA_BLENDING),
203                    write_mask: ColorWrites::ALL,
204                })],
205                ..default()
206            }),
207            // Use the two standard uniforms for 2d meshes
208            layout: vec![
209                // Bind group 0 is the view uniform
210                self.mesh2d_pipeline.view_layout.clone(),
211                // Bind group 1 is the mesh uniform
212                self.mesh2d_pipeline.mesh_layout.clone(),
213            ],
214            primitive: PrimitiveState {
215                cull_mode: Some(Face::Back),
216                topology: BaseMeshPipelineKey::from_bits_retain(key.bits()).primitive_topology(),
217                strip_index_format: BaseMeshPipelineKey::from_bits_retain(key.bits())
218                    .strip_index_format(),
219                ..default()
220            },
221            depth_stencil: Some(DepthStencilState {
222                format: CORE_2D_DEPTH_FORMAT,
223                depth_write_enabled: Some(false),
224                depth_compare: Some(CompareFunction::GreaterEqual),
225                stencil: StencilState {
226                    front: StencilFaceState::IGNORE,
227                    back: StencilFaceState::IGNORE,
228                    read_mask: 0,
229                    write_mask: 0,
230                },
231                bias: DepthBiasState {
232                    constant: 0,
233                    slope_scale: 0.0,
234                    clamp: 0.0,
235                },
236            }),
237            multisample: MultisampleState {
238                count: key.msaa_samples(),
239                mask: !0,
240                alpha_to_coverage_enabled: false,
241            },
242            label: Some("colored_mesh2d_pipeline".into()),
243            ..default()
244        }
245    }
246}
247
248// This specifies how to render a colored 2d mesh
249type DrawTransparentColoredMesh2d = (
250    // Set the pipeline
251    SetItemPipeline,
252    // Set the view uniform as bind group 0
253    SetMesh2dViewBindGroup<0>,
254    // Set the mesh uniform as bind group 1
255    SetColoredMesh2dBindGroup<1>,
256    // Draw the mesh
257    DrawColoredMesh2d,
258);
259
260// The custom shader can be inline like here, included from another file at build time
261// using `include_str!()`, or loaded like any other asset with `asset_server.load()`.
262const COLORED_MESH2D_SHADER: &str = r"
263// Import the standard 2d mesh uniforms and set their bind groups
264import bevy_sprite_render::mesh2d::functions as mesh2d_functions;
265
266// The structure of the vertex buffer is as specified in `specialize()`
267struct Vertex {
268    @builtin(instance_index) instance_index: u32,
269    @location(0) position: vec3<f32>,
270    @location(1) color: u32,
271};
272
273struct VertexOutput {
274    // The vertex shader must set the on-screen position of the vertex
275    @builtin(position) clip_position: vec4<f32>,
276    // We pass the vertex color to the fragment shader in location 0
277    @location(0) color: vec4<f32>,
278};
279
280/// Entry point for the vertex shader
281@vertex
282fn vertex(vertex: Vertex) -> VertexOutput {
283    var out: VertexOutput;
284    // Project the world position of the mesh into screen position
285    let model = mesh2d_functions::get_world_from_local(vertex.instance_index);
286    out.clip_position = mesh2d_functions::mesh2d_position_local_to_clip(model, vec4<f32>(vertex.position, 1.0));
287    // Unpack the `u32` from the vertex buffer into the `vec4<f32>` used by the fragment shader
288    out.color = vec4<f32>((vec4<u32>(vertex.color) >> vec4<u32>(0u, 8u, 16u, 24u)) & vec4<u32>(255u)) / 255.0;
289    return out;
290}
291
292// The input of the fragment shader must correspond to the output of the vertex shader for all `location`s
293struct FragmentInput {
294    // The color is interpolated between vertices by default
295    @location(0) color: vec4<f32>,
296};
297
298/// Entry point for the fragment shader
299@fragment
300fn fragment(in: FragmentInput) -> @location(0) vec4<f32> {
301    return in.color;
302}
303";
304
305/// Plugin that renders [`ColoredMesh2d`]s
306pub struct ColoredMesh2dPlugin;
307
308/// A resource holding the shader asset handle for the pipeline to take. There are many ways to get
309/// the shader into the pipeline - this is just one option.
310#[derive(Resource)]
311struct ColoredMesh2dShader(Handle<Shader>);
312
313/// Our custom pipeline needs its own instance storage
314#[derive(Resource, Deref, DerefMut, Default)]
315pub struct RenderColoredMesh2dInstances(MainEntityHashMap<RenderMesh2dInstance>);
316
317impl Plugin for ColoredMesh2dPlugin {
318    fn build(&self, app: &mut App) {
319        // Load our custom shader
320        let mut shaders = app.world_mut().resource_mut::<Assets<Shader>>();
321        // Here, we construct and add the shader asset manually. There are many ways to load this
322        // shader, including `embedded_asset`/`load_embedded_asset`.
323        let shader = shaders.add(Shader::from_wesl(COLORED_MESH2D_SHADER, file!()));
324
325        app.add_plugins(SyncComponentPlugin::<ColoredMesh2d>::default());
326
327        // Register our custom draw function, and add our render systems
328        app.get_sub_app_mut(RenderApp)
329            .unwrap()
330            .init_resource::<DrawFunctions<TransparentColoredMesh2d>>()
331            // Declare a render phase, `TransparentColoredMesh2d`, to go with
332            // our pipeline.
333            .init_resource::<ViewSortedRenderPhases<TransparentColoredMesh2d>>()
334            // Declare the pipeline itself.
335            .init_resource::<SpecializedRenderPipelines<ColoredMesh2dPipeline>>()
336            // Declare the render-world resource that will hold the instances.
337            .init_resource::<RenderColoredMesh2dInstances>()
338            .insert_resource(ColoredMesh2dShader(shader))
339            // Declare a new render command.
340            .add_render_command::<TransparentColoredMesh2d, DrawTransparentColoredMesh2d>()
341            .add_systems(
342                RenderStartup,
343                init_colored_mesh_2d_pipeline.after(init_mesh_2d_pipeline),
344            )
345            .add_systems(
346                ExtractSchedule,
347                (
348                    extract_colored_mesh2d.after(extract_2d_meshes),
349                    extract_colored_mesh2d_camera_phases,
350                ),
351            )
352            .add_systems(
353                Render,
354                (
355                    sort_phase_system::<TransparentColoredMesh2d>.in_set(RenderSystems::PhaseSort),
356                    queue_colored_mesh2d.in_set(RenderSystems::QueueMeshes),
357                    // Make sure to prepare the render phase.
358                    batch_and_prepare_sorted_render_phase::<
359                        TransparentColoredMesh2d,
360                        ColoredMesh2dPipeline,
361                    >
362                        .in_set(RenderSystems::PrepareResources),
363                ),
364            )
365            .add_systems(
366                Core2d,
367                // Add the draw command to draw the items in our custom phase.
368                main_colored_transparent_pass_2d.in_set(Core2dSystems::MainPass),
369            );
370    }
371}
372
373/// Our own [`PhaseItem`].
374///
375/// Every render phase must be in 1:1 correspondence with a pipeline. Since we
376/// have our own custom pipeline, we must also declare a custom render phase to
377/// go with it.
378struct TransparentColoredMesh2d {
379    sort_key: FloatOrd,
380    entity: (Entity, MainEntity),
381    pipeline: CachedRenderPipelineId,
382    draw_function: DrawFunctionId,
383    batch_range: Range<u32>,
384    extra_index: PhaseItemExtraIndex,
385    /// Whether the mesh in question is indexed (uses an index buffer in
386    /// addition to its vertex buffer).
387    indexed: bool,
388}
389
390impl PhaseItem for TransparentColoredMesh2d {
391    #[inline]
392    fn entity(&self) -> Entity {
393        self.entity.0
394    }
395
396    #[inline]
397    fn main_entity(&self) -> MainEntity {
398        self.entity.1
399    }
400
401    #[inline]
402    fn draw_function(&self) -> DrawFunctionId {
403        self.draw_function
404    }
405
406    #[inline]
407    fn batch_range(&self) -> &Range<u32> {
408        &self.batch_range
409    }
410
411    #[inline]
412    fn batch_range_mut(&mut self) -> &mut Range<u32> {
413        &mut self.batch_range
414    }
415
416    #[inline]
417    fn extra_index(&self) -> PhaseItemExtraIndex {
418        self.extra_index.clone()
419    }
420
421    #[inline]
422    fn batch_range_and_extra_index_mut(&mut self) -> (&mut Range<u32>, &mut PhaseItemExtraIndex) {
423        (&mut self.batch_range, &mut self.extra_index)
424    }
425}
426
427impl SortedPhaseItem for TransparentColoredMesh2d {
428    type SortKey = FloatOrd;
429
430    #[inline]
431    fn sort_key(&self) -> Self::SortKey {
432        self.sort_key
433    }
434
435    #[inline]
436    fn sort(items: &mut IndexMap<(Entity, MainEntity), TransparentColoredMesh2d, EntityHash>) {
437        items.sort_by_key(|_, item| item.sort_key());
438    }
439
440    fn recalculate_sort_keys(
441        _: &mut IndexMap<(Entity, MainEntity), Self, EntityHash>,
442        _: &ExtractedView,
443    ) {
444        // Sort keys are precalculated for 2D phase items.
445    }
446
447    fn indexed(&self) -> bool {
448        self.indexed
449    }
450}
451
452impl CachedRenderPipelinePhaseItem for TransparentColoredMesh2d {
453    #[inline]
454    fn cached_pipeline(&self) -> CachedRenderPipelineId {
455        self.pipeline
456    }
457}
458
459impl GetBatchData for ColoredMesh2dPipeline {
460    type Param = (SRes<RenderColoredMesh2dInstances>, SRes<MeshAllocator>);
461    type BatchSetCompareData = AssetId<Mesh>;
462    type BatchCompareData = Option<MaterialBindGroupIndex>;
463    type BufferData = Mesh2dUniform;
464
465    fn get_batch_data(
466        (mesh_instances, mesh_allocator): &SystemParamItem<Self::Param>,
467        (_entity, main_entity): (Entity, MainEntity),
468    ) -> Option<(
469        Self::BufferData,
470        Option<(Self::BatchSetCompareData, Self::BatchCompareData)>,
471    )> {
472        let mesh_instance = mesh_instances.get(&main_entity)?;
473        let metadata_index = mesh_allocator
474            .mesh_metadata_slice(&mesh_instance.mesh_asset_id)
475            .map(|mesh_metadata_slice| mesh_metadata_slice.range.start);
476
477        Some((
478            Mesh2dUniform::from_components(
479                &mesh_instance.transforms,
480                MaterialBindGroupSlot(0),
481                mesh_instance.tag,
482                metadata_index,
483            ),
484            mesh_instance
485                .automatic_batching
486                .then_some((mesh_instance.mesh_asset_id, None)),
487        ))
488    }
489}
490
491/// Prepares our custom render phase for a new frame.
492fn extract_colored_mesh2d_camera_phases(
493    mut colored_mesh2d_render_phases: ResMut<ViewSortedRenderPhases<TransparentColoredMesh2d>>,
494    cameras_2d: Extract<Query<(Entity, &Camera), With<Camera2d>>>,
495    mut live_entities: Local<HashSet<RetainedViewEntity>>,
496) {
497    live_entities.clear();
498
499    for (main_entity, camera) in &cameras_2d {
500        if !camera.is_active {
501            continue;
502        }
503
504        // This is the main 2D camera, so we use the first subview index (0).
505        let retained_view_entity = RetainedViewEntity::new(main_entity.into(), None, 0);
506
507        colored_mesh2d_render_phases.prepare_for_new_frame(retained_view_entity);
508
509        live_entities.insert(retained_view_entity);
510    }
511
512    // Clear out all dead views.
513    colored_mesh2d_render_phases.retain(|camera_entity, _| live_entities.contains(camera_entity));
514}
515
516/// Extract the [`ColoredMesh2d`] marker component into the render app
517pub fn extract_colored_mesh2d(
518    mut commands: Commands,
519    mut previous_len: Local<usize>,
520    // When extracting, you must use `Extract` to mark the `SystemParam`s
521    // which should be taken from the main world.
522    query: Extract<
523        Query<
524            (
525                Entity,
526                RenderEntity,
527                &ViewVisibility,
528                &GlobalTransform,
529                &Mesh2d,
530            ),
531            With<ColoredMesh2d>,
532        >,
533    >,
534    mut render_mesh_instances: ResMut<RenderColoredMesh2dInstances>,
535) {
536    let mut values = Vec::with_capacity(*previous_len);
537    for (entity, render_entity, view_visibility, transform, handle) in &query {
538        if !view_visibility.get() {
539            continue;
540        }
541
542        let transforms = Mesh2dTransforms {
543            world_from_local: transform.affine().into(),
544            flags: MeshFlags::empty().bits(),
545        };
546
547        values.push((render_entity, ColoredMesh2d));
548        render_mesh_instances.insert(
549            entity.into(),
550            RenderMesh2dInstance {
551                mesh_asset_id: handle.0.id(),
552                transforms,
553                // This is unused here.
554                material_bindings_index: MaterialBindingId::default(),
555                automatic_batching: false,
556                tag: 0,
557            },
558        );
559    }
560    *previous_len = values.len();
561    commands.try_insert_batch(values);
562}
563
564/// Queue the 2d meshes marked with [`ColoredMesh2d`] using our custom pipeline and draw function
565fn queue_colored_mesh2d(
566    transparent_draw_functions: Res<DrawFunctions<TransparentColoredMesh2d>>,
567    colored_mesh2d_pipeline: Res<ColoredMesh2dPipeline>,
568    mut pipelines: ResMut<SpecializedRenderPipelines<ColoredMesh2dPipeline>>,
569    pipeline_cache: Res<PipelineCache>,
570    render_meshes: Res<RenderAssets<RenderMesh>>,
571    render_mesh_instances: Res<RenderColoredMesh2dInstances>,
572    mut transparent_render_phases: ResMut<ViewSortedRenderPhases<TransparentColoredMesh2d>>,
573    views: Query<(&RenderVisibleEntities, &ExtractedView, &Msaa)>,
574) {
575    if render_mesh_instances.is_empty() {
576        return;
577    }
578
579    // Iterate each view (a camera is a view)
580    for (visible_entities, view, msaa) in &views {
581        let Some(transparent_phase) = transparent_render_phases.get_mut(&view.retained_view_entity)
582        else {
583            continue;
584        };
585
586        let draw_colored_mesh2d = transparent_draw_functions
587            .read()
588            .id::<DrawTransparentColoredMesh2d>();
589
590        let mesh_key = Mesh2dPipelineKey::from_msaa_samples(msaa.samples())
591            | Mesh2dPipelineKey::from_target_format(view.target_format);
592
593        // Queue all entities visible to that view
594        let Some(visible_entities) = visible_entities.get::<Mesh2d>() else {
595            continue;
596        };
597        for (render_entity, visible_entity) in visible_entities.iter_visible() {
598            if let Some(mesh_instance) = render_mesh_instances.get(visible_entity) {
599                let mesh2d_handle = mesh_instance.mesh_asset_id;
600                let mesh2d_transforms = &mesh_instance.transforms;
601                // Get our specialized pipeline
602                let mut mesh2d_key = mesh_key;
603                let Some(mesh) = render_meshes.get(mesh2d_handle) else {
604                    continue;
605                };
606                mesh2d_key |= Mesh2dPipelineKey::from(
607                    BaseMeshPipelineKey::from_primitive_topology_and_strip_index(
608                        mesh.primitive_topology(),
609                        mesh.index_format(),
610                    )
611                    .bits(),
612                );
613
614                let pipeline_id =
615                    pipelines.specialize(&pipeline_cache, &colored_mesh2d_pipeline, mesh2d_key);
616
617                let mesh_z = mesh2d_transforms.world_from_local.translation.z;
618                transparent_phase.add_retained(TransparentColoredMesh2d {
619                    entity: (*render_entity, *visible_entity),
620                    draw_function: draw_colored_mesh2d,
621                    pipeline: pipeline_id,
622                    // The 2d render items are sorted according to their z value before rendering,
623                    // in order to get correct transparency
624                    sort_key: FloatOrd(mesh_z),
625                    // This material is not batched
626                    batch_range: 0..1,
627                    extra_index: PhaseItemExtraIndex::None,
628                    indexed: mesh.indexed(),
629                });
630            }
631        }
632    }
633}
634
635/// The render node system that draws all items in the
636/// [`TransparentColoredMesh2d`] phase.
637fn main_colored_transparent_pass_2d(
638    world: &World,
639    view: ViewQuery<(
640        &ExtractedCamera,
641        &ExtractedView,
642        &ViewTarget,
643        &ViewDepthStencilTexture,
644    )>,
645    transparent_phases: Res<ViewSortedRenderPhases<TransparentColoredMesh2d>>,
646    mut ctx: RenderContext,
647) {
648    let view_entity = view.entity();
649    let (camera, extracted_view, target, depth) = view.into_inner();
650
651    let Some(transparent_phase) = transparent_phases.get(&extracted_view.retained_view_entity)
652    else {
653        return;
654    };
655
656    #[cfg(feature = "trace")]
657    let _span = info_span!("main_colored_transparent_pass_2d").entered();
658
659    let diagnostics = ctx.diagnostic_recorder();
660    let diagnostics = diagnostics.as_deref();
661
662    let color_attachments = [Some(target.get_color_attachment())];
663    // NOTE: For the transparent pass we load the depth buffer. There should be no
664    // need to write to it, but store is set to `true` as a workaround for issue #3776,
665    // https://github.com/bevyengine/bevy/issues/3776
666    // so that wgpu does not clear the depth buffer.
667    // As the opaque and alpha mask passes run first, opaque meshes can occlude
668    // transparent ones.
669    let depth_stencil_attachment = Some(depth.get_attachment(StoreOp::Store));
670
671    {
672        let mut render_pass = ctx.begin_tracked_render_pass(RenderPassDescriptor {
673            label: Some("main_colored_transparent_pass_2d"),
674            color_attachments: &color_attachments,
675            depth_stencil_attachment,
676            timestamp_writes: None,
677            occlusion_query_set: None,
678            multiview_mask: None,
679        });
680        let pass_span = diagnostics.pass_span(&mut render_pass, "main_colored_transparent_pass_2d");
681
682        if let Some(viewport) = camera.viewport.as_ref() {
683            render_pass.set_camera_viewport(viewport);
684        }
685
686        if !transparent_phase.items.is_empty() {
687            #[cfg(feature = "trace")]
688            let _transparent_span = info_span!("colored_transparent_main_pass_2d").entered();
689            if let Err(err) = transparent_phase.render(&mut render_pass, world, view_entity) {
690                error!(
691                    "Error encountered while rendering the colored transparent 2D phase {err:?}"
692                );
693            }
694        }
695
696        pass_span.end(&mut render_pass);
697    }
698}
699
700/// The render command that sets the right bind group.
701///
702/// Since the normal `SetMesh2dBindGroup` render command is hardwired to use
703/// `RenderMesh2dInstances`, we need to replace it with our own render command.
704struct SetColoredMesh2dBindGroup<const I: usize>;
705
706impl<P, const I: usize> RenderCommand<P> for SetColoredMesh2dBindGroup<I>
707where
708    P: PhaseItem,
709{
710    type Param = (
711        SRes<Mesh2dBindGroup>,
712        SRes<RenderColoredMesh2dInstances>,
713        SRes<MeshAllocator>,
714        SRes<MeshMetadataFallbackBuffer>,
715    );
716    type ViewQuery = ();
717    type ItemQuery = ();
718
719    #[inline]
720    fn render<'w>(
721        item: &P,
722        _view: (),
723        _item_query: Option<()>,
724        (mesh2d_bind_group, render_mesh2d_instances, mesh_allocator,metadata_fallback_buffer): SystemParamItem<
725            'w,
726            '_,
727            Self::Param,
728        >,
729        pass: &mut TrackedRenderPass<'w>,
730    ) -> RenderCommandResult {
731        let render_mesh2d_instances = render_mesh2d_instances.into_inner();
732        let mesh_allocator = mesh_allocator.into_inner();
733        let mesh2d_bind_group = mesh2d_bind_group.into_inner();
734
735        let Some(RenderMesh2dInstance { mesh_asset_id, .. }) =
736            render_mesh2d_instances.get(&item.main_entity())
737        else {
738            return RenderCommandResult::Skip;
739        };
740        let metadata_slab_id = mesh_allocator
741            .key_to_slab
742            .get(&bevy_render::mesh::allocator::MeshAllocationKey::new(
743                *mesh_asset_id,
744                bevy_render::mesh::allocator::ElementClass::Metadata,
745            ))
746            .cloned()
747            .unwrap_or(metadata_fallback_buffer.slab_id);
748        let Some(bind_group) = &mesh2d_bind_group.value.get(&metadata_slab_id) else {
749            return RenderCommandResult::Failure(
750                "The mesh2d bind group wasn't set in the render phase.",
751            );
752        };
753
754        let mut dynamic_offsets: [u32; 1] = Default::default();
755        let mut offset_count = 0;
756        if let PhaseItemExtraIndex::DynamicOffset(dynamic_offset) = item.extra_index() {
757            dynamic_offsets[offset_count] = dynamic_offset;
758            offset_count += 1;
759        }
760        pass.set_bind_group(I, bind_group, &dynamic_offsets[..offset_count]);
761        RenderCommandResult::Success
762    }
763}
764
765/// The render command that draws all the meshes in our custom render phase.
766struct DrawColoredMesh2d;
767
768impl<P: PhaseItem> RenderCommand<P> for DrawColoredMesh2d {
769    type Param = (
770        SRes<RenderAssets<RenderMesh>>,
771        SRes<RenderColoredMesh2dInstances>,
772        SRes<MeshAllocator>,
773    );
774    type ViewQuery = ();
775    type ItemQuery = ();
776
777    #[inline]
778    fn render<'w>(
779        item: &P,
780        _view: (),
781        _item_query: Option<()>,
782        (meshes, render_mesh2d_instances, mesh_allocator): SystemParamItem<'w, '_, Self::Param>,
783        pass: &mut TrackedRenderPass<'w>,
784    ) -> RenderCommandResult {
785        let meshes = meshes.into_inner();
786        let render_mesh2d_instances = render_mesh2d_instances.into_inner();
787        let mesh_allocator = mesh_allocator.into_inner();
788
789        let Some(RenderMesh2dInstance { mesh_asset_id, .. }) =
790            render_mesh2d_instances.get(&item.main_entity())
791        else {
792            return RenderCommandResult::Skip;
793        };
794        let Some(gpu_mesh) = meshes.get(*mesh_asset_id) else {
795            return RenderCommandResult::Skip;
796        };
797        let Some(vertex_buffer_slice) = mesh_allocator.mesh_vertex_slice(mesh_asset_id) else {
798            return RenderCommandResult::Skip;
799        };
800
801        pass.set_vertex_buffer(0, vertex_buffer_slice.buffer.slice(..));
802
803        let batch_range = item.batch_range();
804        match &gpu_mesh.buffer_info {
805            RenderMeshBufferInfo::Indexed {
806                index_format,
807                count,
808            } => {
809                let Some(index_buffer_slice) = mesh_allocator.mesh_index_slice(mesh_asset_id)
810                else {
811                    return RenderCommandResult::Skip;
812                };
813
814                pass.set_index_buffer(index_buffer_slice.buffer.slice(..), *index_format);
815
816                pass.draw_indexed(
817                    index_buffer_slice.range.start..(index_buffer_slice.range.start + count),
818                    vertex_buffer_slice.range.start as i32,
819                    batch_range.clone(),
820                );
821            }
822            RenderMeshBufferInfo::NonIndexed => {
823                pass.draw(vertex_buffer_slice.range, batch_range.clone());
824            }
825        }
826        RenderCommandResult::Success
827    }
828}