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::{Indices, MeshVertexBufferLayoutRef, PrimitiveTopology},
16 pbr::{
17 DrawMesh, MeshPipeline, MeshPipelineKey, MeshPipelineSystems, MeshPipelineViewLayoutKey,
18 RenderMeshInstances, SetMeshBindGroup, SetMeshViewBindGroup, SetMeshViewEmptyBindGroup,
19 ViewKeyCache,
20 },
21 prelude::*,
22 render::{
23 batching::gpu_preprocessing::GpuPreprocessingSupport,
24 camera::{DirtySpecializations, PendingQueues},
25 extract_component::{ExtractComponent, ExtractComponentPlugin},
26 mesh::{allocator::MeshAllocator, RenderMesh},
27 render_asset::RenderAssets,
28 render_phase::{
29 AddRenderCommand, BinnedRenderPhaseType, DrawFunctions, SetItemPipeline,
30 ViewBinnedRenderPhases,
31 },
32 render_resource::{
33 ColorTargetState, ColorWrites, CompareFunction, DepthStencilState, Face, FragmentState,
34 FrontFace, MultisampleState, PipelineCache, PolygonMode, PrimitiveState,
35 RenderPipelineDescriptor, SpecializedMeshPipeline, SpecializedMeshPipelineError,
36 SpecializedMeshPipelines, VertexState,
37 },
38 view::{ExtractedView, RenderVisibleEntities},
39 Render, RenderApp, RenderStartup, RenderSystems,
40 },
41};
42
43const SHADER_ASSET_PATH: &str = "shaders/specialized_mesh_pipeline.wgsl";
44
45fn main() {
46 App::new()
47 .add_plugins(DefaultPlugins)
48 .add_plugins(CustomRenderedMeshPipelinePlugin)
49 .add_systems(Startup, setup)
50 .run();
51}
52
53fn setup(mut commands: Commands, mut meshes: ResMut<Assets<Mesh>>) {
55 let mesh = Mesh::new(
59 PrimitiveTopology::TriangleList,
60 RenderAssetUsages::default(),
61 )
62 .with_inserted_indices(Indices::U32(vec![0, 1, 2]))
63 .with_inserted_attribute(
64 Mesh::ATTRIBUTE_POSITION,
65 vec![
66 vec3(-0.5, -0.5, 0.0),
67 vec3(0.5, -0.5, 0.0),
68 vec3(0.0, 0.25, 0.0),
69 ],
70 )
71 .with_inserted_attribute(
72 Mesh::ATTRIBUTE_COLOR,
73 vec![
74 vec4(1.0, 0.0, 0.0, 1.0),
75 vec4(0.0, 1.0, 0.0, 1.0),
76 vec4(0.0, 0.0, 1.0, 1.0),
77 ],
78 );
79
80 for (x, y) in [-0.5, 0.0, 0.5].into_iter().zip([-0.25, 0.5, -0.25]) {
82 commands.spawn((
84 CustomRenderedEntity,
87 Mesh3d(meshes.add(mesh.clone())),
89 Transform::from_xyz(x, y, 0.0),
90 ));
91 }
92
93 commands.spawn((
95 Camera3d::default(),
96 Transform::from_xyz(0.0, 0.0, 3.0).looking_at(Vec3::ZERO, Vec3::Y),
98 ));
99}
100
101struct CustomRenderedMeshPipelinePlugin;
105impl Plugin for CustomRenderedMeshPipelinePlugin {
106 fn build(&self, app: &mut App) {
107 app.add_plugins(ExtractComponentPlugin::<CustomRenderedEntity>::default());
108
109 let Some(render_app) = app.get_sub_app_mut(RenderApp) else {
111 return;
112 };
113 render_app
114 .init_resource::<SpecializedMeshPipelines<CustomMeshPipeline>>()
116 .init_resource::<PendingCustomMeshQueues>()
117 .add_render_command::<Opaque3d, DrawSpecializedPipelineCommands>()
119 .add_systems(
120 RenderStartup,
121 init_custom_mesh_pipeline.after(MeshPipelineSystems),
122 )
123 .add_systems(
124 Render,
125 queue_custom_mesh_pipeline.in_set(RenderSystems::Queue),
126 );
127 }
128}
129
130#[derive(Clone, Component, ExtractComponent)]
138#[require(VisibilityClass)]
139#[component(on_add = visibility::add_visibility_class::<CustomRenderedEntity>)]
140struct CustomRenderedEntity;
141
142type DrawSpecializedPipelineCommands = (
145 SetItemPipeline,
147 SetMeshViewBindGroup<0>,
149 SetMeshViewEmptyBindGroup<1>,
151 SetMeshBindGroup<2>,
153 DrawMesh,
155);
156
157#[derive(Resource)]
159struct CustomMeshPipeline {
160 mesh_pipeline: MeshPipeline,
165 shader_handle: Handle<Shader>,
168}
169
170fn init_custom_mesh_pipeline(
171 mut commands: Commands,
172 asset_server: Res<AssetServer>,
173 mesh_pipeline: Res<MeshPipeline>,
174) {
175 let shader_handle: Handle<Shader> = asset_server.load(SHADER_ASSET_PATH);
177 commands.insert_resource(CustomMeshPipeline {
178 mesh_pipeline: mesh_pipeline.clone(),
179 shader_handle,
180 });
181}
182
183impl SpecializedMeshPipeline for CustomMeshPipeline {
184 type Key = MeshPipelineKey;
191
192 fn specialize(
193 &self,
194 mesh_key: Self::Key,
195 layout: &MeshVertexBufferLayoutRef,
196 ) -> Result<RenderPipelineDescriptor, SpecializedMeshPipelineError> {
197 let mut vertex_attributes = Vec::new();
199 if layout.0.contains(Mesh::ATTRIBUTE_POSITION) {
200 vertex_attributes.push(Mesh::ATTRIBUTE_POSITION.at_shader_location(0));
202 }
203 if layout.0.contains(Mesh::ATTRIBUTE_COLOR) {
204 vertex_attributes.push(Mesh::ATTRIBUTE_COLOR.at_shader_location(1));
206 }
207 let vertex_buffer_layout = layout.0.get_layout(&vertex_attributes)?;
209
210 let view_layout = self
211 .mesh_pipeline
212 .get_view_layout(MeshPipelineViewLayoutKey::from(mesh_key));
213
214 Ok(RenderPipelineDescriptor {
215 label: Some("Specialized Mesh Pipeline".into()),
216 layout: vec![
217 view_layout.main_layout,
218 view_layout.empty_layout,
219 self.mesh_pipeline.mesh_layouts.model_only.clone(),
220 ],
221 vertex: VertexState {
222 shader: self.shader_handle.clone(),
223 buffers: vec![vertex_buffer_layout],
225 ..default()
226 },
227 fragment: Some(FragmentState {
228 shader: self.shader_handle.clone(),
229 targets: vec![Some(ColorTargetState {
230 format: mesh_key.target_format(),
233 blend: None,
236 write_mask: ColorWrites::ALL,
237 })],
238 ..default()
239 }),
240 primitive: PrimitiveState {
241 topology: mesh_key.primitive_topology(),
242 strip_index_format: mesh_key.strip_index_format(),
243 front_face: FrontFace::Ccw,
244 cull_mode: Some(Face::Back),
245 polygon_mode: PolygonMode::Fill,
246 ..default()
247 },
248 depth_stencil: Some(DepthStencilState {
251 format: CORE_3D_DEPTH_FORMAT,
252 depth_write_enabled: Some(true),
253 depth_compare: Some(CompareFunction::GreaterEqual),
254 stencil: default(),
255 bias: default(),
256 }),
257 multisample: MultisampleState {
260 count: mesh_key.msaa_samples(),
261 ..default()
262 },
263 ..default()
264 })
265 }
266}
267
268#[derive(Default, Deref, DerefMut, Resource)]
273struct PendingCustomMeshQueues(pub PendingQueues);
274
275fn queue_custom_mesh_pipeline(
278 pipeline_cache: Res<PipelineCache>,
279 custom_mesh_pipeline: Res<CustomMeshPipeline>,
280 (mut opaque_render_phases, opaque_draw_functions): (
281 ResMut<ViewBinnedRenderPhases<Opaque3d>>,
282 Res<DrawFunctions<Opaque3d>>,
283 ),
284 mut specialized_mesh_pipelines: ResMut<SpecializedMeshPipelines<CustomMeshPipeline>>,
285 views: Query<(&RenderVisibleEntities, &ExtractedView)>,
286 view_key_cache: Res<ViewKeyCache>,
287 (render_meshes, render_mesh_instances): (
288 Res<RenderAssets<RenderMesh>>,
289 Res<RenderMeshInstances>,
290 ),
291 mut change_tick: Local<Tick>,
292 mesh_allocator: Res<MeshAllocator>,
293 gpu_preprocessing_support: Res<GpuPreprocessingSupport>,
294 dirty_specializations: Res<DirtySpecializations>,
295 mut pending_custom_mesh_queues: ResMut<PendingCustomMeshQueues>,
296) {
297 let draw_function = opaque_draw_functions
299 .read()
300 .id::<DrawSpecializedPipelineCommands>();
301
302 for (view_visible_entities, view) in views.iter() {
306 let Some(opaque_phase) = opaque_render_phases.get_mut(&view.retained_view_entity) else {
307 continue;
308 };
309
310 let Some(&view_key) = view_key_cache.get(&view.retained_view_entity) else {
311 continue;
312 };
313
314 let Some(render_visible_mesh_entities) =
315 view_visible_entities.get::<CustomRenderedEntity>()
316 else {
317 continue;
318 };
319
320 let view_pending_custom_mesh_queues =
322 pending_custom_mesh_queues.prepare_for_new_frame(view.retained_view_entity);
323
324 for &main_entity in dirty_specializations
326 .iter_to_dequeue(view.retained_view_entity, render_visible_mesh_entities)
327 {
328 opaque_phase.remove(main_entity);
329 }
330
331 for (render_entity, visible_entity) in dirty_specializations.iter_to_queue(
334 view.retained_view_entity,
335 render_visible_mesh_entities,
336 &view_pending_custom_mesh_queues.prev_frame,
337 ) {
338 let Some(mesh_instance) = render_mesh_instances.render_mesh_queue_data(*visible_entity)
340 else {
341 view_pending_custom_mesh_queues
345 .current_frame
346 .insert((*render_entity, *visible_entity));
347 continue;
348 };
349
350 let Some(mesh) = render_meshes.get(mesh_instance.mesh_asset_id()) else {
352 continue;
353 };
354
355 let Some(mesh_slabs) = mesh_allocator.mesh_slabs(&mesh_instance.mesh_asset_id()) else {
356 continue;
357 };
358
359 let mut mesh_key = view_key;
363 mesh_key |= MeshPipelineKey::from_primitive_topology_and_strip_index(
364 mesh.primitive_topology(),
365 mesh.index_format(),
366 );
367
368 let pipeline_id = specialized_mesh_pipelines
370 .specialize(
371 &pipeline_cache,
372 &custom_mesh_pipeline,
373 mesh_key,
374 &mesh.layout,
375 )
376 .expect("Failed to specialize mesh pipeline");
379
380 let next_change_tick = change_tick.get() + 1;
382 change_tick.set(next_change_tick);
383
384 opaque_phase.add(
386 Opaque3dBatchSetKey {
387 draw_function,
388 pipeline: pipeline_id,
389 material_bind_group_index: None,
390 slabs: mesh_slabs,
391 lightmap_slab: None,
392 },
393 Opaque3dBinKey {
396 asset_id: mesh_instance.mesh_asset_id().into(),
397 },
398 (*render_entity, *visible_entity),
399 mesh_instance.current_uniform_index,
400 BinnedRenderPhaseType::mesh(
404 mesh_instance.should_batch(),
405 &gpu_preprocessing_support,
406 ),
407 );
408 }
409 }
410}