specialized_mesh_pipeline/
specialized_mesh_pipeline.rs1use bevy::{
10 asset::RenderAssetUsages,
11 camera::visibility::{self, VisibilityClass},
12 core_pipeline::core_3d::{Opaque3d, Opaque3dBatchSetKey, Opaque3dBinKey, CORE_3D_DEPTH_FORMAT},
13 ecs::change_detection::Tick,
14 math::{vec3, vec4},
15 mesh::{
16 Indices, MeshAttributeCompressionFlags, MeshCompressionArgs, MeshVertexBufferLayoutRef,
17 PrimitiveTopology,
18 },
19 pbr::{
20 DrawMesh, MeshPipeline, MeshPipelineKey, MeshPipelineSystems, MeshPipelineViewLayoutKey,
21 RenderMeshInstances, SetMeshBindGroup, SetMeshViewBindGroup, SetMeshViewEmptyBindGroup,
22 ViewKeyCache,
23 },
24 prelude::*,
25 render::{
26 batching::gpu_preprocessing::GpuPreprocessingSupport,
27 camera::{DirtySpecializations, PendingQueues},
28 extract_component::{ExtractComponent, ExtractComponentPlugin},
29 mesh::{allocator::MeshAllocator, RenderMesh},
30 render_asset::RenderAssets,
31 render_phase::{
32 AddRenderCommand, BinnedRenderPhaseType, DrawFunctions, SetItemPipeline,
33 ViewBinnedRenderPhases,
34 },
35 render_resource::{
36 ColorTargetState, ColorWrites, CompareFunction, DepthStencilState, Face, FragmentState,
37 FrontFace, MultisampleState, PipelineCache, PolygonMode, PrimitiveState,
38 RenderPipelineDescriptor, SpecializedMeshPipeline, SpecializedMeshPipelineError,
39 SpecializedMeshPipelines, VertexState,
40 },
41 sync_world::MainEntityHashSet,
42 view::{ExtractedView, RenderVisibleEntities},
43 Render, RenderApp, RenderStartup, RenderSystems,
44 },
45};
46
47const SHADER_ASSET_PATH: &str = "shaders/specialized_mesh_pipeline.wesl";
48
49fn main() {
50 App::new()
51 .add_plugins(DefaultPlugins)
52 .add_plugins(CustomRenderedMeshPipelinePlugin)
53 .add_systems(Startup, setup)
54 .run();
55}
56
57fn setup(mut commands: Commands, mut meshes: ResMut<Assets<Mesh>>) {
59 let mesh = Mesh::new(
63 PrimitiveTopology::TriangleList,
64 RenderAssetUsages::default(),
65 )
66 .with_inserted_indices(Indices::U32(vec![0, 1, 2]))
67 .with_inserted_attribute(
68 Mesh::ATTRIBUTE_POSITION,
69 vec![
70 vec3(-0.5, -0.5, 0.0),
71 vec3(0.5, -0.5, 0.0),
72 vec3(0.0, 0.25, 0.0),
73 ],
74 )
75 .with_inserted_attribute(
76 Mesh::ATTRIBUTE_COLOR,
77 vec![
78 vec4(1.0, 0.0, 0.0, 1.0),
79 vec4(0.0, 1.0, 0.0, 1.0),
80 vec4(0.0, 0.0, 1.0, 1.0),
81 ],
82 );
83
84 for (x, y) in [-0.5, 0.0, 0.5].into_iter().zip([-0.25, 0.5, -0.25]) {
86 commands.spawn((
88 CustomRenderedEntity,
91 Mesh3d(meshes.add(mesh.clone())),
93 Transform::from_xyz(x, y, 0.0),
94 ));
95 }
96 commands.spawn((
97 CustomRenderedEntity,
100 Mesh3d(
102 meshes.add(
103 Rectangle::new(0.5, 0.5)
104 .mesh()
105 .build()
106 .with_removed_attribute(Mesh::ATTRIBUTE_NORMAL)
107 .with_removed_attribute(Mesh::ATTRIBUTE_UV_0)
108 .compressed_mesh(&MeshCompressionArgs::regular())
109 .unwrap(),
110 ),
111 ),
112 Transform::from_xyz(0.0, -1.0 / 3.0, -1.0),
113 ));
114
115 commands.spawn((
117 Camera3d::default(),
118 Transform::from_xyz(0.0, 0.0, 3.0).looking_at(Vec3::ZERO, Vec3::Y),
120 ));
121}
122
123struct CustomRenderedMeshPipelinePlugin;
127impl Plugin for CustomRenderedMeshPipelinePlugin {
128 fn build(&self, app: &mut App) {
129 app.add_plugins(ExtractComponentPlugin::<CustomRenderedEntity>::default());
130
131 let Some(render_app) = app.get_sub_app_mut(RenderApp) else {
133 return;
134 };
135 render_app
136 .init_resource::<SpecializedMeshPipelines<CustomMeshPipeline>>()
138 .init_resource::<PendingCustomMeshQueues>()
139 .add_render_command::<Opaque3d, DrawSpecializedPipelineCommands>()
141 .add_systems(
142 RenderStartup,
143 init_custom_mesh_pipeline.after(MeshPipelineSystems),
144 )
145 .add_systems(
146 Render,
147 queue_custom_mesh_pipeline.in_set(RenderSystems::Queue),
148 );
149 }
150}
151
152#[derive(Clone, Component, ExtractComponent)]
160#[require(VisibilityClass)]
161#[component(on_add = visibility::add_visibility_class::<CustomRenderedEntity>)]
162#[extract_app(RenderApp)]
163struct CustomRenderedEntity;
164
165type DrawSpecializedPipelineCommands = (
168 SetItemPipeline,
170 SetMeshViewBindGroup<0>,
172 SetMeshViewEmptyBindGroup<1>,
174 SetMeshBindGroup<2>,
176 DrawMesh,
178);
179
180#[derive(Resource)]
182struct CustomMeshPipeline {
183 mesh_pipeline: MeshPipeline,
188 shader_handle: Handle<Shader>,
191}
192
193fn init_custom_mesh_pipeline(
194 mut commands: Commands,
195 asset_server: Res<AssetServer>,
196 mesh_pipeline: Res<MeshPipeline>,
197) {
198 let shader_handle: Handle<Shader> = asset_server.load(SHADER_ASSET_PATH);
200 commands.insert_resource(CustomMeshPipeline {
201 mesh_pipeline: mesh_pipeline.clone(),
202 shader_handle,
203 });
204}
205
206impl SpecializedMeshPipeline for CustomMeshPipeline {
207 type Key = MeshPipelineKey;
214
215 fn specialize(
216 &self,
217 mesh_key: Self::Key,
218 layout: &MeshVertexBufferLayoutRef,
219 ) -> Result<RenderPipelineDescriptor, SpecializedMeshPipelineError> {
220 let mut shader_defs = Vec::new();
221 let mut vertex_attributes = Vec::new();
223 if layout.0.contains(Mesh::ATTRIBUTE_POSITION) {
224 if layout
226 .0
227 .get_attribute_compression()
228 .contains(MeshAttributeCompressionFlags::COMPRESS_POSITION)
229 {
230 shader_defs.push("VERTEX_POSITIONS_COMPRESSED".into());
231 }
232 vertex_attributes.push(Mesh::ATTRIBUTE_POSITION.at_shader_location(0));
234 }
235 if layout.0.contains(Mesh::ATTRIBUTE_COLOR) {
236 shader_defs.push("VERTEX_COLORS".into());
237 vertex_attributes.push(Mesh::ATTRIBUTE_COLOR.at_shader_location(1));
239 }
240 let vertex_buffer_layout = layout.0.get_layout(&vertex_attributes)?;
242
243 let view_layout = self
244 .mesh_pipeline
245 .get_view_layout(MeshPipelineViewLayoutKey::from(mesh_key));
246
247 Ok(RenderPipelineDescriptor {
248 label: Some("Specialized Mesh Pipeline".into()),
249 layout: vec![
250 view_layout.main_layout,
251 view_layout.empty_layout,
252 self.mesh_pipeline.mesh_layouts.model_only.clone(),
253 ],
254 vertex: VertexState {
255 shader: self.shader_handle.clone(),
256 shader_defs: shader_defs.clone(),
257 buffers: vec![vertex_buffer_layout],
259 ..default()
260 },
261 fragment: Some(FragmentState {
262 shader: self.shader_handle.clone(),
263 shader_defs,
264 targets: vec![Some(ColorTargetState {
265 format: mesh_key.target_format(),
268 blend: None,
271 write_mask: ColorWrites::ALL,
272 })],
273 ..default()
274 }),
275 primitive: PrimitiveState {
276 topology: mesh_key.primitive_topology(),
277 strip_index_format: mesh_key.strip_index_format(),
278 front_face: FrontFace::Ccw,
279 cull_mode: Some(Face::Back),
280 polygon_mode: PolygonMode::Fill,
281 ..default()
282 },
283 depth_stencil: Some(DepthStencilState {
286 format: CORE_3D_DEPTH_FORMAT,
287 depth_write_enabled: Some(true),
288 depth_compare: Some(CompareFunction::GreaterEqual),
289 stencil: default(),
290 bias: default(),
291 }),
292 multisample: MultisampleState {
295 count: mesh_key.msaa_samples(),
296 ..default()
297 },
298 ..default()
299 })
300 }
301}
302
303#[derive(Default, Deref, DerefMut, Resource)]
308struct PendingCustomMeshQueues(pub PendingQueues);
309
310fn queue_custom_mesh_pipeline(
313 pipeline_cache: Res<PipelineCache>,
314 custom_mesh_pipeline: Res<CustomMeshPipeline>,
315 (mut opaque_render_phases, opaque_draw_functions): (
316 ResMut<ViewBinnedRenderPhases<Opaque3d>>,
317 Res<DrawFunctions<Opaque3d>>,
318 ),
319 mut specialized_mesh_pipelines: ResMut<SpecializedMeshPipelines<CustomMeshPipeline>>,
320 views: Query<(&RenderVisibleEntities, &ExtractedView)>,
321 view_key_cache: Res<ViewKeyCache>,
322 (render_meshes, render_mesh_instances): (
323 Res<RenderAssets<RenderMesh>>,
324 Res<RenderMeshInstances>,
325 ),
326 mut change_tick: Local<Tick>,
327 mesh_allocator: Res<MeshAllocator>,
328 gpu_preprocessing_support: Res<GpuPreprocessingSupport>,
329 dirty_specializations: Res<DirtySpecializations>,
330 mut pending_custom_mesh_queues: ResMut<PendingCustomMeshQueues>,
331 mut mesh_instances_queued_this_iteration_scratch_space: Local<MainEntityHashSet>,
332) {
333 let draw_function = opaque_draw_functions
335 .read()
336 .id::<DrawSpecializedPipelineCommands>();
337
338 for (view_visible_entities, view) in views.iter() {
342 let Some(opaque_phase) = opaque_render_phases.get_mut(&view.retained_view_entity) else {
343 continue;
344 };
345
346 let Some(&view_key) = view_key_cache.get(&view.retained_view_entity) else {
347 continue;
348 };
349
350 let Some(render_visible_mesh_entities) =
351 view_visible_entities.get::<CustomRenderedEntity>()
352 else {
353 continue;
354 };
355
356 let view_pending_custom_mesh_queues =
358 pending_custom_mesh_queues.prepare_for_new_frame(view.retained_view_entity);
359
360 for &main_entity in dirty_specializations
362 .iter_to_dequeue(view.retained_view_entity, render_visible_mesh_entities)
363 {
364 opaque_phase.remove(main_entity);
365 }
366
367 for (render_entity, visible_entity) in dirty_specializations.iter_to_queue(
370 view.retained_view_entity,
371 render_visible_mesh_entities,
372 &view_pending_custom_mesh_queues.prev_frame,
373 &mut mesh_instances_queued_this_iteration_scratch_space,
374 ) {
375 let Some(mesh_instance) = render_mesh_instances.render_mesh_queue_data(*visible_entity)
377 else {
378 view_pending_custom_mesh_queues
382 .current_frame
383 .insert((*render_entity, *visible_entity));
384 continue;
385 };
386
387 let Some(mesh) = render_meshes.get(mesh_instance.mesh_asset_id()) else {
389 continue;
390 };
391
392 let Some(mesh_slabs) = mesh_allocator.mesh_slabs(&mesh_instance.mesh_asset_id()) else {
393 continue;
394 };
395
396 let mut mesh_key = view_key;
400 mesh_key |= MeshPipelineKey::from_primitive_topology_and_strip_index(
401 mesh.primitive_topology(),
402 mesh.index_format(),
403 );
404
405 let pipeline_id = specialized_mesh_pipelines
407 .specialize(
408 &pipeline_cache,
409 &custom_mesh_pipeline,
410 mesh_key,
411 &mesh.layout,
412 )
413 .expect("Failed to specialize mesh pipeline");
416
417 let next_change_tick = change_tick.get() + 1;
419 change_tick.set(next_change_tick);
420
421 opaque_phase.add(
423 Opaque3dBatchSetKey {
424 draw_function,
425 pipeline: pipeline_id,
426 material_bind_group_index: None,
427 slabs: mesh_slabs,
428 lightmap_slab: None,
429 },
430 Opaque3dBinKey {
433 asset_id: mesh_instance.mesh_asset_id().into(),
434 },
435 (*render_entity, *visible_entity),
436 mesh_instance.current_uniform_index,
437 BinnedRenderPhaseType::mesh(
441 mesh_instance.should_batch(),
442 &gpu_preprocessing_support,
443 ),
444 );
445 }
446 }
447}