1use bevy::{
11 camera::{
12 primitives::Aabb,
13 visibility::{self, VisibilityClass},
14 },
15 core_pipeline::core_3d::{Opaque3d, Opaque3dBatchSetKey, Opaque3dBinKey, CORE_3D_DEPTH_FORMAT},
16 ecs::{
17 query::ROQueryItem,
18 system::{lifetimeless::SRes, SystemParamItem},
19 },
20 mesh::VertexBufferLayout,
21 prelude::*,
22 render::{
23 camera::{DirtySpecializations, PendingQueues},
24 extract_component::{ExtractComponent, ExtractComponentPlugin},
25 mesh::allocator::MeshSlabs,
26 render_phase::{
27 AddRenderCommand, BinnedRenderPhaseType, DrawFunctions, InputUniformIndex, PhaseItem,
28 RenderCommand, RenderCommandResult, SetItemPipeline, TrackedRenderPass,
29 ViewBinnedRenderPhases,
30 },
31 render_resource::{
32 BufferUsages, Canonical, ColorTargetState, ColorWrites, CompareFunction,
33 DepthStencilState, FragmentState, IndexFormat, PipelineCache, RawBufferVec,
34 RenderPipeline, RenderPipelineDescriptor, Specializer, SpecializerKey, TextureFormat,
35 Variants, VertexAttribute, VertexFormat, VertexState, VertexStepMode,
36 },
37 renderer::{RenderDevice, RenderQueue},
38 view::{ExtractedView, RenderVisibleEntities},
39 Render, RenderApp, RenderSystems,
40 },
41};
42use bytemuck::{Pod, Zeroable};
43
44#[derive(Clone, Component, ExtractComponent)]
52#[require(VisibilityClass)]
53#[component(on_add = visibility::add_visibility_class::<CustomRenderedEntity>)]
54struct CustomRenderedEntity;
55
56struct DrawCustomPhaseItem;
59
60impl<P> RenderCommand<P> for DrawCustomPhaseItem
61where
62 P: PhaseItem,
63{
64 type Param = SRes<CustomPhaseItemBuffers>;
65
66 type ViewQuery = ();
67
68 type ItemQuery = ();
69
70 fn render<'w>(
71 _: &P,
72 _: ROQueryItem<'w, '_, Self::ViewQuery>,
73 _: Option<ROQueryItem<'w, '_, Self::ItemQuery>>,
74 custom_phase_item_buffers: SystemParamItem<'w, '_, Self::Param>,
75 pass: &mut TrackedRenderPass<'w>,
76 ) -> RenderCommandResult {
77 let custom_phase_item_buffers = custom_phase_item_buffers.into_inner();
79
80 pass.set_vertex_buffer(
82 0,
83 custom_phase_item_buffers
84 .vertices
85 .buffer()
86 .unwrap()
87 .slice(..),
88 );
89
90 pass.set_index_buffer(
92 custom_phase_item_buffers
93 .indices
94 .buffer()
95 .unwrap()
96 .slice(..),
97 IndexFormat::Uint32,
98 );
99
100 pass.draw_indexed(0..3, 0, 0..1);
102
103 RenderCommandResult::Success
104 }
105}
106
107#[derive(Resource)]
112struct CustomPhaseItemBuffers {
113 vertices: RawBufferVec<Vertex>,
118
119 indices: RawBufferVec<u32>,
124}
125
126#[derive(Clone, Copy, Pod, Zeroable)]
128#[repr(C)]
129struct Vertex {
130 position: Vec3,
132 pad0: u32,
134 color: Vec3,
136 pad1: u32,
138}
139
140impl Vertex {
141 const fn new(position: Vec3, color: Vec3) -> Vertex {
143 Vertex {
144 position,
145 color,
146 pad0: 0,
147 pad1: 0,
148 }
149 }
150}
151
152type DrawCustomPhaseItemCommands = (SetItemPipeline, DrawCustomPhaseItem);
155
156static VERTICES: [Vertex; 3] = [
158 Vertex::new(vec3(-0.866, -0.5, 0.5), vec3(1.0, 0.0, 0.0)),
159 Vertex::new(vec3(0.866, -0.5, 0.5), vec3(0.0, 1.0, 0.0)),
160 Vertex::new(vec3(0.0, 1.0, 0.5), vec3(0.0, 0.0, 1.0)),
161];
162
163fn main() {
165 let mut app = App::new();
166 app.add_plugins(DefaultPlugins)
167 .add_plugins(ExtractComponentPlugin::<CustomRenderedEntity>::default())
168 .add_systems(Startup, setup);
169
170 app.sub_app_mut(RenderApp)
172 .init_resource::<CustomPhasePipeline>()
173 .init_resource::<PendingCustomPhaseItemQueues>()
174 .add_render_command::<Opaque3d, DrawCustomPhaseItemCommands>()
175 .add_systems(
176 Render,
177 prepare_custom_phase_item_buffers.in_set(RenderSystems::Prepare),
178 )
179 .add_systems(Render, queue_custom_phase_item.in_set(RenderSystems::Queue));
180
181 app.run();
182}
183
184fn setup(mut commands: Commands) {
186 commands.spawn((
189 Visibility::default(),
190 Transform::default(),
191 Aabb {
193 center: Vec3A::ZERO,
194 half_extents: Vec3A::splat(0.5),
195 },
196 CustomRenderedEntity,
197 ));
198
199 commands.spawn((
201 Camera3d::default(),
202 Transform::from_xyz(0.0, 0.0, 1.0).looking_at(Vec3::ZERO, Vec3::Y),
203 ));
204}
205
206fn prepare_custom_phase_item_buffers(mut commands: Commands) {
211 commands.init_resource::<CustomPhaseItemBuffers>();
212}
213
214#[derive(Default, Deref, DerefMut, Resource)]
223pub struct PendingCustomPhaseItemQueues(pub PendingQueues);
224
225fn queue_custom_phase_item(
228 pipeline_cache: Res<PipelineCache>,
229 mut pipeline: ResMut<CustomPhasePipeline>,
230 mut opaque_render_phases: ResMut<ViewBinnedRenderPhases<Opaque3d>>,
231 opaque_draw_functions: Res<DrawFunctions<Opaque3d>>,
232 views: Query<(&ExtractedView, &RenderVisibleEntities, &Msaa)>,
233 dirty_specializations: Res<DirtySpecializations>,
234 mut pending_custom_phase_item_queues: ResMut<PendingCustomPhaseItemQueues>,
235) {
236 let draw_custom_phase_item = opaque_draw_functions
237 .read()
238 .id::<DrawCustomPhaseItemCommands>();
239
240 for (view, view_visible_entities, msaa) in views.iter() {
244 let Some(opaque_phase) = opaque_render_phases.get_mut(&view.retained_view_entity) else {
245 continue;
246 };
247
248 let Some(render_visible_mesh_entities) =
252 view_visible_entities.get::<CustomRenderedEntity>()
253 else {
254 continue;
255 };
256
257 let view_pending_custom_phase_item_queues =
258 pending_custom_phase_item_queues.prepare_for_new_frame(view.retained_view_entity);
259
260 for &main_entity in dirty_specializations
263 .iter_to_dequeue(view.retained_view_entity, render_visible_mesh_entities)
264 {
265 opaque_phase.remove(main_entity);
266 }
267
268 for (render_entity, main_entity) in dirty_specializations.iter_to_queue(
271 view.retained_view_entity,
272 render_visible_mesh_entities,
273 &view_pending_custom_phase_item_queues.prev_frame,
274 ) {
275 let Ok(pipeline_id) = pipeline
280 .variants
281 .specialize(&pipeline_cache, CustomPhaseKey(*msaa))
282 else {
283 continue;
284 };
285
286 opaque_phase.add(
295 Opaque3dBatchSetKey {
296 draw_function: draw_custom_phase_item,
297 pipeline: pipeline_id,
298 material_bind_group_index: None,
299 lightmap_slab: None,
300 slabs: MeshSlabs::default(),
301 },
302 Opaque3dBinKey {
303 asset_id: AssetId::<Mesh>::invalid().untyped(),
304 },
305 (*render_entity, *main_entity),
306 InputUniformIndex::default(),
307 BinnedRenderPhaseType::NonMesh,
308 );
309 }
310 }
311}
312
313struct CustomPhaseSpecializer;
314
315#[derive(Resource)]
316struct CustomPhasePipeline {
317 variants: Variants<RenderPipeline, CustomPhaseSpecializer>,
319}
320
321impl FromWorld for CustomPhasePipeline {
322 fn from_world(world: &mut World) -> Self {
323 let asset_server = world.resource::<AssetServer>();
324 let shader = asset_server.load("shaders/custom_phase_item.wgsl");
325
326 let base_descriptor = RenderPipelineDescriptor {
327 label: Some("custom render pipeline".into()),
328 vertex: VertexState {
329 shader: shader.clone(),
330 buffers: vec![VertexBufferLayout {
331 array_stride: size_of::<Vertex>() as u64,
332 step_mode: VertexStepMode::Vertex,
333 attributes: vec![
335 VertexAttribute {
336 format: VertexFormat::Float32x3,
337 offset: 0,
338 shader_location: 0,
339 },
340 VertexAttribute {
341 format: VertexFormat::Float32x3,
342 offset: 16,
343 shader_location: 1,
344 },
345 ],
346 }],
347 ..default()
348 },
349 fragment: Some(FragmentState {
350 shader: shader.clone(),
351 targets: vec![Some(ColorTargetState {
352 format: TextureFormat::Rgba8UnormSrgb,
356 blend: None,
357 write_mask: ColorWrites::ALL,
358 })],
359 ..default()
360 }),
361 depth_stencil: Some(DepthStencilState {
364 format: CORE_3D_DEPTH_FORMAT,
365 depth_write_enabled: Some(false),
366 depth_compare: Some(CompareFunction::Always),
367 stencil: default(),
368 bias: default(),
369 }),
370 ..default()
371 };
372
373 let variants = Variants::new(CustomPhaseSpecializer, base_descriptor);
374
375 Self { variants }
376 }
377}
378
379#[derive(Copy, Clone, PartialEq, Eq, Hash, SpecializerKey)]
380struct CustomPhaseKey(Msaa);
381
382impl Specializer<RenderPipeline> for CustomPhaseSpecializer {
383 type Key = CustomPhaseKey;
384
385 fn specialize(
386 &self,
387 key: Self::Key,
388 descriptor: &mut RenderPipelineDescriptor,
389 ) -> Result<Canonical<Self::Key>, BevyError> {
390 descriptor.multisample.count = key.0.samples();
391 Ok(key)
392 }
393}
394
395impl FromWorld for CustomPhaseItemBuffers {
396 fn from_world(world: &mut World) -> Self {
397 let render_device = world.resource::<RenderDevice>();
398 let render_queue = world.resource::<RenderQueue>();
399
400 let mut vbo = RawBufferVec::new(BufferUsages::VERTEX);
402 let mut ibo = RawBufferVec::new(BufferUsages::INDEX);
403
404 for vertex in &VERTICES {
405 vbo.push(*vertex);
406 }
407 for index in 0..3 {
408 ibo.push(index);
409 }
410
411 vbo.write_buffer(render_device, render_queue);
413 ibo.write_buffer(render_device, render_queue);
414
415 CustomPhaseItemBuffers {
416 vertices: vbo,
417 indices: ibo,
418 }
419 }
420}