docs/bevy/examples/2d/mesh2d_manual.rs 30 K · 828 lines · raw · history

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
8 use 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 };
56 use indexmap::IndexMap;
57 use std::{f32::consts::PI, ops::Range};
58
59 fn main() {
60 App::new()
61 .add_plugins((DefaultPlugins, ColoredMesh2dPlugin))
62 .add_systems(Startup, star)
63 .run();
64 }
65
66 fn 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)]
143 pub struct ColoredMesh2d;
144
145 impl SyncComponent<RenderApp> for ColoredMesh2d {
146 type Target = Self;
147 }
148
149 /// Custom pipeline for 2d meshes with vertex colors
150 #[derive(Resource)]
151 pub struct ColoredMesh2dPipeline {
152 /// This pipeline wraps the standard [`Mesh2dPipeline`]
153 mesh2d_pipeline: Mesh2dPipeline,
154 /// The shader asset handle.
155 shader: Handle<Shader>,
156 }
157
158 fn 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`
171 impl 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
249 type 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()`.
262 const COLORED_MESH2D_SHADER: &str = r"
263 // Import the standard 2d mesh uniforms and set their bind groups
264 import bevy_sprite_render::mesh2d::functions as mesh2d_functions;
265
266 // The structure of the vertex buffer is as specified in `specialize()`
267 struct Vertex {
268 @builtin(instance_index) instance_index: u32,
269 @location(0) position: vec3<f32>,
270 @location(1) color: u32,
271 };
272
273 struct 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
282 fn 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
293 struct 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
300 fn fragment(in: FragmentInput) -> @location(0) vec4<f32> {
301 return in.color;
302 }
303 ";
304
305 /// Plugin that renders [`ColoredMesh2d`]s
306 pub 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)]
311 struct ColoredMesh2dShader(Handle<Shader>);
312
313 /// Our custom pipeline needs its own instance storage
314 #[derive(Resource, Deref, DerefMut, Default)]
315 pub struct RenderColoredMesh2dInstances(MainEntityHashMap<RenderMesh2dInstance>);
316
317 impl 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.
378 struct 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
390 impl 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
427 impl 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
452 impl CachedRenderPipelinePhaseItem for TransparentColoredMesh2d {
453 #[inline]
454 fn cached_pipeline(&self) -> CachedRenderPipelineId {
455 self.pipeline
456 }
457 }
458
459 impl 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.
492 fn 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
517 pub 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
565 fn 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.
637 fn 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.
704 struct SetColoredMesh2dBindGroup<const I: usize>;
705
706 impl<P, const I: usize> RenderCommand<P> for SetColoredMesh2dBindGroup<I>
707 where
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.
766 struct DrawColoredMesh2d;
767
768 impl<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 }