specialized_mesh_pipeline/
specialized_mesh_pipeline.rs1use bevy::{
10 core_pipeline::core_3d::{Opaque3d, Opaque3dBatchSetKey, Opaque3dBinKey, CORE_3D_DEPTH_FORMAT},
11 ecs::{component::Tick, system::StaticSystemParam},
12 math::{vec3, vec4},
13 pbr::{
14 DrawMesh, MeshPipeline, MeshPipelineKey, MeshPipelineViewLayoutKey, RenderMeshInstances,
15 SetMeshBindGroup, SetMeshViewBindGroup,
16 },
17 prelude::*,
18 render::{
19 batching::{
20 gpu_preprocessing::{
21 self, PhaseBatchedInstanceBuffers, PhaseIndirectParametersBuffers,
22 PreprocessWorkItem, UntypedPhaseBatchedInstanceBuffers,
23 },
24 GetBatchData, GetFullBatchData,
25 },
26 experimental::occlusion_culling::OcclusionCulling,
27 extract_component::{ExtractComponent, ExtractComponentPlugin},
28 mesh::{Indices, MeshVertexBufferLayoutRef, PrimitiveTopology, RenderMesh},
29 render_asset::{RenderAssetUsages, RenderAssets},
30 render_phase::{
31 AddRenderCommand, BinnedRenderPhaseType, DrawFunctions, SetItemPipeline,
32 ViewBinnedRenderPhases,
33 },
34 render_resource::{
35 ColorTargetState, ColorWrites, CompareFunction, DepthStencilState, Face, FragmentState,
36 FrontFace, MultisampleState, PipelineCache, PolygonMode, PrimitiveState,
37 RenderPipelineDescriptor, SpecializedMeshPipeline, SpecializedMeshPipelineError,
38 SpecializedMeshPipelines, TextureFormat, VertexState,
39 },
40 view::NoIndirectDrawing,
41 view::{self, ExtractedView, RenderVisibleEntities, ViewTarget, VisibilityClass},
42 Render, RenderApp, RenderSet,
43 },
44};
45
46const SHADER_ASSET_PATH: &str = "shaders/specialized_mesh_pipeline.wgsl";
47
48fn main() {
49 App::new()
50 .add_plugins(DefaultPlugins)
51 .add_plugins(CustomRenderedMeshPipelinePlugin)
52 .add_systems(Startup, setup)
53 .run();
54}
55
56fn setup(mut commands: Commands, mut meshes: ResMut<Assets<Mesh>>) {
58 let mesh = Mesh::new(
62 PrimitiveTopology::TriangleList,
63 RenderAssetUsages::default(),
64 )
65 .with_inserted_indices(Indices::U32(vec![0, 1, 2]))
66 .with_inserted_attribute(
67 Mesh::ATTRIBUTE_POSITION,
68 vec![
69 vec3(-0.5, -0.5, 0.0),
70 vec3(0.5, -0.5, 0.0),
71 vec3(0.0, 0.25, 0.0),
72 ],
73 )
74 .with_inserted_attribute(
75 Mesh::ATTRIBUTE_COLOR,
76 vec![
77 vec4(1.0, 0.0, 0.0, 1.0),
78 vec4(0.0, 1.0, 0.0, 1.0),
79 vec4(0.0, 0.0, 1.0, 1.0),
80 ],
81 );
82
83 for (x, y) in [-0.5, 0.0, 0.5].into_iter().zip([-0.25, 0.5, -0.25]) {
85 commands.spawn((
87 CustomRenderedEntity,
90 Mesh3d(meshes.add(mesh.clone())),
92 Transform::from_xyz(x, y, 0.0),
93 ));
94 }
95
96 commands.spawn((
98 Camera3d::default(),
99 Transform::from_xyz(0.0, 0.0, 3.0).looking_at(Vec3::ZERO, Vec3::Y),
101 ));
102}
103
104struct CustomRenderedMeshPipelinePlugin;
108impl Plugin for CustomRenderedMeshPipelinePlugin {
109 fn build(&self, app: &mut App) {
110 app.add_plugins(ExtractComponentPlugin::<CustomRenderedEntity>::default());
111
112 let Some(render_app) = app.get_sub_app_mut(RenderApp) else {
114 return;
115 };
116 render_app
117 .init_resource::<SpecializedMeshPipelines<CustomMeshPipeline>>()
119 .add_render_command::<Opaque3d, DrawSpecializedPipelineCommands>()
121 .add_systems(Render, queue_custom_mesh_pipeline.in_set(RenderSet::Queue));
122 }
123
124 fn finish(&self, app: &mut App) {
125 let Some(render_app) = app.get_sub_app_mut(RenderApp) else {
126 return;
127 };
128 render_app.init_resource::<CustomMeshPipeline>();
131 }
132}
133
134#[derive(Clone, Component, ExtractComponent)]
142#[require(VisibilityClass)]
143#[component(on_add = view::add_visibility_class::<CustomRenderedEntity>)]
144struct CustomRenderedEntity;
145
146type DrawSpecializedPipelineCommands = (
149 SetItemPipeline,
151 SetMeshViewBindGroup<0>,
153 SetMeshBindGroup<1>,
155 DrawMesh,
157);
158
159#[derive(Resource)]
161struct CustomMeshPipeline {
162 mesh_pipeline: MeshPipeline,
167 shader_handle: Handle<Shader>,
170}
171impl FromWorld for CustomMeshPipeline {
172 fn from_world(world: &mut World) -> Self {
173 let shader_handle: Handle<Shader> = world.resource::<AssetServer>().load(SHADER_ASSET_PATH);
175 Self {
176 mesh_pipeline: MeshPipeline::from_world(world),
177 shader_handle,
178 }
179 }
180}
181
182impl SpecializedMeshPipeline for CustomMeshPipeline {
183 type Key = MeshPipelineKey;
190
191 fn specialize(
192 &self,
193 mesh_key: Self::Key,
194 layout: &MeshVertexBufferLayoutRef,
195 ) -> Result<RenderPipelineDescriptor, SpecializedMeshPipelineError> {
196 let mut vertex_attributes = Vec::new();
198 if layout.0.contains(Mesh::ATTRIBUTE_POSITION) {
199 vertex_attributes.push(Mesh::ATTRIBUTE_POSITION.at_shader_location(0));
201 }
202 if layout.0.contains(Mesh::ATTRIBUTE_COLOR) {
203 vertex_attributes.push(Mesh::ATTRIBUTE_COLOR.at_shader_location(1));
205 }
206 let vertex_buffer_layout = layout.0.get_layout(&vertex_attributes)?;
208
209 Ok(RenderPipelineDescriptor {
210 label: Some("Specialized Mesh Pipeline".into()),
211 layout: vec![
212 self.mesh_pipeline
214 .get_view_layout(MeshPipelineViewLayoutKey::from(mesh_key))
215 .clone(),
216 self.mesh_pipeline.mesh_layouts.model_only.clone(),
218 ],
219 push_constant_ranges: vec![],
220 vertex: VertexState {
221 shader: self.shader_handle.clone(),
222 shader_defs: vec![],
223 entry_point: "vertex".into(),
224 buffers: vec![vertex_buffer_layout],
226 },
227 fragment: Some(FragmentState {
228 shader: self.shader_handle.clone(),
229 shader_defs: vec![],
230 entry_point: "fragment".into(),
231 targets: vec![Some(ColorTargetState {
232 format: if mesh_key.contains(MeshPipelineKey::HDR) {
235 ViewTarget::TEXTURE_FORMAT_HDR
236 } else {
237 TextureFormat::bevy_default()
238 },
239 blend: None,
242 write_mask: ColorWrites::ALL,
243 })],
244 }),
245 primitive: PrimitiveState {
246 topology: mesh_key.primitive_topology(),
247 front_face: FrontFace::Ccw,
248 cull_mode: Some(Face::Back),
249 polygon_mode: PolygonMode::Fill,
250 ..default()
251 },
252 depth_stencil: Some(DepthStencilState {
255 format: CORE_3D_DEPTH_FORMAT,
256 depth_write_enabled: true,
257 depth_compare: CompareFunction::GreaterEqual,
258 stencil: default(),
259 bias: default(),
260 }),
261 multisample: MultisampleState {
264 count: mesh_key.msaa_samples(),
265 ..MultisampleState::default()
266 },
267 zero_initialize_workgroup_memory: false,
268 })
269 }
270}
271
272fn queue_custom_mesh_pipeline(
275 pipeline_cache: Res<PipelineCache>,
276 custom_mesh_pipeline: Res<CustomMeshPipeline>,
277 (mut opaque_render_phases, opaque_draw_functions): (
278 ResMut<ViewBinnedRenderPhases<Opaque3d>>,
279 Res<DrawFunctions<Opaque3d>>,
280 ),
281 mut specialized_mesh_pipelines: ResMut<SpecializedMeshPipelines<CustomMeshPipeline>>,
282 views: Query<(
283 &RenderVisibleEntities,
284 &ExtractedView,
285 &Msaa,
286 Has<NoIndirectDrawing>,
287 Has<OcclusionCulling>,
288 )>,
289 (render_meshes, render_mesh_instances): (
290 Res<RenderAssets<RenderMesh>>,
291 Res<RenderMeshInstances>,
292 ),
293 param: StaticSystemParam<<MeshPipeline as GetBatchData>::Param>,
294 mut phase_batched_instance_buffers: ResMut<
295 PhaseBatchedInstanceBuffers<Opaque3d, <MeshPipeline as GetBatchData>::BufferData>,
296 >,
297 mut phase_indirect_parameters_buffers: ResMut<PhaseIndirectParametersBuffers<Opaque3d>>,
298 mut change_tick: Local<Tick>,
299) {
300 let system_param_item = param.into_inner();
301
302 let UntypedPhaseBatchedInstanceBuffers {
303 ref mut data_buffer,
304 ref mut work_item_buffers,
305 ref mut late_indexed_indirect_parameters_buffer,
306 ref mut late_non_indexed_indirect_parameters_buffer,
307 ..
308 } = phase_batched_instance_buffers.buffers;
309
310 let draw_function_id = opaque_draw_functions
312 .read()
313 .id::<DrawSpecializedPipelineCommands>();
314
315 for (view_visible_entities, view, msaa, no_indirect_drawing, gpu_occlusion_culling) in
319 views.iter()
320 {
321 let Some(opaque_phase) = opaque_render_phases.get_mut(&view.retained_view_entity) else {
322 continue;
323 };
324
325 let work_item_buffer = gpu_preprocessing::get_or_create_work_item_buffer::<Opaque3d>(
329 work_item_buffers,
330 view.retained_view_entity,
331 no_indirect_drawing,
332 gpu_occlusion_culling,
333 );
334
335 gpu_preprocessing::init_work_item_buffers(
337 work_item_buffer,
338 late_indexed_indirect_parameters_buffer,
339 late_non_indexed_indirect_parameters_buffer,
340 );
341
342 let view_key = MeshPipelineKey::from_msaa_samples(msaa.samples())
344 | MeshPipelineKey::from_hdr(view.hdr);
345
346 let mut mesh_batch_set_info = None;
350
351 for &(render_entity, visible_entity) in
354 view_visible_entities.get::<CustomRenderedEntity>().iter()
355 {
356 let Some(mesh_instance) = render_mesh_instances.render_mesh_queue_data(visible_entity)
358 else {
359 continue;
360 };
361
362 let Some(mesh) = render_meshes.get(mesh_instance.mesh_asset_id) else {
364 continue;
365 };
366
367 let mut mesh_key = view_key;
371 mesh_key |= MeshPipelineKey::from_primitive_topology(mesh.primitive_topology());
372
373 if mesh_batch_set_info.is_none() {
377 mesh_batch_set_info = Some(MeshBatchSetInfo {
378 indirect_parameters_index: phase_indirect_parameters_buffers
379 .buffers
380 .allocate(mesh.indexed(), 1),
381 is_indexed: mesh.indexed(),
382 });
383 }
384 let mesh_info = mesh_batch_set_info.unwrap();
385
386 let Some(input_index) =
389 MeshPipeline::get_binned_index(&system_param_item, visible_entity)
390 else {
391 continue;
392 };
393 let output_index = data_buffer.add() as u32;
394
395 let pipeline_id = specialized_mesh_pipelines
397 .specialize(
398 &pipeline_cache,
399 &custom_mesh_pipeline,
400 mesh_key,
401 &mesh.layout,
402 )
403 .expect("Failed to specialize mesh pipeline");
406
407 let next_change_tick = change_tick.get() + 1;
409 change_tick.set(next_change_tick);
410
411 opaque_phase.add(
413 Opaque3dBatchSetKey {
414 draw_function: draw_function_id,
415 pipeline: pipeline_id,
416 material_bind_group_index: None,
417 vertex_slab: default(),
418 index_slab: None,
419 lightmap_slab: None,
420 },
421 Opaque3dBinKey {
425 asset_id: AssetId::<Mesh>::invalid().untyped(),
426 },
427 (render_entity, visible_entity),
428 mesh_instance.current_uniform_index,
429 BinnedRenderPhaseType::BatchableMesh,
432 *change_tick,
433 );
434
435 work_item_buffer.push(
438 mesh.indexed(),
439 PreprocessWorkItem {
440 input_index: input_index.into(),
441 output_or_indirect_parameters_index: if no_indirect_drawing {
442 output_index
443 } else {
444 mesh_info.indirect_parameters_index
445 },
446 },
447 );
448 }
449
450 if let Some(mesh_info) = mesh_batch_set_info {
454 phase_indirect_parameters_buffers
455 .buffers
456 .add_batch_set(mesh_info.is_indexed, mesh_info.indirect_parameters_index);
457 }
458 }
459}
460
461#[derive(Clone, Copy)]
464struct MeshBatchSetInfo {
465 indirect_parameters_index: u32,
467 is_indexed: bool,
469}