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 sync_world::MainEntityHashSet,
39 view::{ExtractedView, RenderVisibleEntities},
40 Render, RenderApp, RenderSystems,
41 },
42};
43use bytemuck::{Pod, Zeroable};
44
45#[derive(Clone, Component, ExtractComponent)]
53#[require(VisibilityClass)]
54#[component(on_add = visibility::add_visibility_class::<CustomRenderedEntity>)]
55#[extract_app(RenderApp)]
56struct CustomRenderedEntity;
57
58struct DrawCustomPhaseItem;
61
62impl<P> RenderCommand<P> for DrawCustomPhaseItem
63where
64 P: PhaseItem,
65{
66 type Param = SRes<CustomPhaseItemBuffers>;
67
68 type ViewQuery = ();
69
70 type ItemQuery = ();
71
72 fn render<'w>(
73 _: &P,
74 _: ROQueryItem<'w, '_, Self::ViewQuery>,
75 _: Option<ROQueryItem<'w, '_, Self::ItemQuery>>,
76 custom_phase_item_buffers: SystemParamItem<'w, '_, Self::Param>,
77 pass: &mut TrackedRenderPass<'w>,
78 ) -> RenderCommandResult {
79 let custom_phase_item_buffers = custom_phase_item_buffers.into_inner();
81
82 pass.set_vertex_buffer(
84 0,
85 custom_phase_item_buffers
86 .vertices
87 .buffer()
88 .unwrap()
89 .slice(..),
90 );
91
92 pass.set_index_buffer(
94 custom_phase_item_buffers
95 .indices
96 .buffer()
97 .unwrap()
98 .slice(..),
99 IndexFormat::Uint32,
100 );
101
102 pass.draw_indexed(0..3, 0, 0..1);
104
105 RenderCommandResult::Success
106 }
107}
108
109#[derive(Resource)]
114struct CustomPhaseItemBuffers {
115 vertices: RawBufferVec<Vertex>,
120
121 indices: RawBufferVec<u32>,
126}
127
128#[derive(Clone, Copy, Pod, Zeroable)]
130#[repr(C)]
131struct Vertex {
132 position: Vec3,
134 pad0: u32,
136 color: Vec3,
138 pad1: u32,
140}
141
142impl Vertex {
143 const fn new(position: Vec3, color: Vec3) -> Vertex {
145 Vertex {
146 position,
147 color,
148 pad0: 0,
149 pad1: 0,
150 }
151 }
152}
153
154type DrawCustomPhaseItemCommands = (SetItemPipeline, DrawCustomPhaseItem);
157
158static VERTICES: [Vertex; 3] = [
160 Vertex::new(vec3(-0.866, -0.5, 0.5), vec3(1.0, 0.0, 0.0)),
161 Vertex::new(vec3(0.866, -0.5, 0.5), vec3(0.0, 1.0, 0.0)),
162 Vertex::new(vec3(0.0, 1.0, 0.5), vec3(0.0, 0.0, 1.0)),
163];
164
165fn main() {
167 let mut app = App::new();
168 app.add_plugins(DefaultPlugins)
169 .add_plugins(ExtractComponentPlugin::<CustomRenderedEntity>::default())
170 .add_systems(Startup, setup);
171
172 app.sub_app_mut(RenderApp)
174 .init_resource::<CustomPhasePipeline>()
175 .init_resource::<PendingCustomPhaseItemQueues>()
176 .add_render_command::<Opaque3d, DrawCustomPhaseItemCommands>()
177 .add_systems(
178 Render,
179 prepare_custom_phase_item_buffers.in_set(RenderSystems::Prepare),
180 )
181 .add_systems(Render, queue_custom_phase_item.in_set(RenderSystems::Queue));
182
183 app.run();
184}
185
186fn setup(mut commands: Commands) {
188 commands.spawn((
191 Visibility::default(),
192 Transform::default(),
193 Aabb {
195 center: Vec3A::ZERO,
196 half_extents: Vec3A::splat(0.5),
197 },
198 CustomRenderedEntity,
199 ));
200
201 commands.spawn((
203 Camera3d::default(),
204 Transform::from_xyz(0.0, 0.0, 1.0).looking_at(Vec3::ZERO, Vec3::Y),
205 ));
206}
207
208fn prepare_custom_phase_item_buffers(mut commands: Commands) {
213 commands.init_resource::<CustomPhaseItemBuffers>();
214}
215
216#[derive(Default, Deref, DerefMut, Resource)]
225pub struct PendingCustomPhaseItemQueues(pub PendingQueues);
226
227fn queue_custom_phase_item(
230 pipeline_cache: Res<PipelineCache>,
231 mut pipeline: ResMut<CustomPhasePipeline>,
232 mut opaque_render_phases: ResMut<ViewBinnedRenderPhases<Opaque3d>>,
233 opaque_draw_functions: Res<DrawFunctions<Opaque3d>>,
234 views: Query<(&ExtractedView, &RenderVisibleEntities, &Msaa)>,
235 dirty_specializations: Res<DirtySpecializations>,
236 mut pending_custom_phase_item_queues: ResMut<PendingCustomPhaseItemQueues>,
237 mut mesh_instances_queued_this_iteration_scratch_space: Local<MainEntityHashSet>,
238) {
239 let draw_custom_phase_item = opaque_draw_functions
240 .read()
241 .id::<DrawCustomPhaseItemCommands>();
242
243 for (view, view_visible_entities, msaa) in views.iter() {
247 let Some(opaque_phase) = opaque_render_phases.get_mut(&view.retained_view_entity) else {
248 continue;
249 };
250
251 let Some(render_visible_mesh_entities) =
255 view_visible_entities.get::<CustomRenderedEntity>()
256 else {
257 continue;
258 };
259
260 let view_pending_custom_phase_item_queues =
261 pending_custom_phase_item_queues.prepare_for_new_frame(view.retained_view_entity);
262
263 for &main_entity in dirty_specializations
266 .iter_to_dequeue(view.retained_view_entity, render_visible_mesh_entities)
267 {
268 opaque_phase.remove(main_entity);
269 }
270
271 for (render_entity, main_entity) in dirty_specializations.iter_to_queue(
274 view.retained_view_entity,
275 render_visible_mesh_entities,
276 &view_pending_custom_phase_item_queues.prev_frame,
277 &mut mesh_instances_queued_this_iteration_scratch_space,
278 ) {
279 let Ok(pipeline_id) = pipeline
284 .variants
285 .specialize(&pipeline_cache, CustomPhaseKey(*msaa))
286 else {
287 continue;
288 };
289
290 opaque_phase.add(
299 Opaque3dBatchSetKey {
300 draw_function: draw_custom_phase_item,
301 pipeline: pipeline_id,
302 material_bind_group_index: None,
303 lightmap_slab: None,
304 slabs: MeshSlabs::default(),
305 },
306 Opaque3dBinKey {
307 asset_id: AssetId::<Mesh>::default().untyped(),
308 },
309 (*render_entity, *main_entity),
310 InputUniformIndex::default(),
311 BinnedRenderPhaseType::NonMesh,
312 );
313 }
314 }
315}
316
317struct CustomPhaseSpecializer;
318
319#[derive(Resource)]
320struct CustomPhasePipeline {
321 variants: Variants<RenderPipeline, CustomPhaseSpecializer>,
323}
324
325impl FromWorld for CustomPhasePipeline {
326 fn from_world(world: &mut World) -> Self {
327 let asset_server = world.resource::<AssetServer>();
328 let shader = asset_server.load("shaders/custom_phase_item.wesl");
329
330 let base_descriptor = RenderPipelineDescriptor {
331 label: Some("custom render pipeline".into()),
332 vertex: VertexState {
333 shader: shader.clone(),
334 buffers: vec![VertexBufferLayout {
335 array_stride: size_of::<Vertex>() as u64,
336 step_mode: VertexStepMode::Vertex,
337 attributes: vec![
339 VertexAttribute {
340 format: VertexFormat::Float32x3,
341 offset: 0,
342 shader_location: 0,
343 },
344 VertexAttribute {
345 format: VertexFormat::Float32x3,
346 offset: 16,
347 shader_location: 1,
348 },
349 ],
350 }],
351 ..default()
352 },
353 fragment: Some(FragmentState {
354 shader: shader.clone(),
355 targets: vec![Some(ColorTargetState {
356 format: TextureFormat::Rgba8UnormSrgb,
360 blend: None,
361 write_mask: ColorWrites::ALL,
362 })],
363 ..default()
364 }),
365 depth_stencil: Some(DepthStencilState {
368 format: CORE_3D_DEPTH_FORMAT,
369 depth_write_enabled: Some(false),
370 depth_compare: Some(CompareFunction::Always),
371 stencil: default(),
372 bias: default(),
373 }),
374 ..default()
375 };
376
377 let variants = Variants::new(CustomPhaseSpecializer, base_descriptor);
378
379 Self { variants }
380 }
381}
382
383#[derive(Copy, Clone, PartialEq, Eq, Hash, SpecializerKey)]
384struct CustomPhaseKey(Msaa);
385
386impl Specializer<RenderPipeline> for CustomPhaseSpecializer {
387 type Key = CustomPhaseKey;
388
389 fn specialize(
390 &self,
391 key: Self::Key,
392 descriptor: &mut RenderPipelineDescriptor,
393 ) -> Result<Canonical<Self::Key>, BevyError> {
394 descriptor.multisample.count = key.0.samples();
395 Ok(key)
396 }
397}
398
399impl FromWorld for CustomPhaseItemBuffers {
400 fn from_world(world: &mut World) -> Self {
401 let render_device = world.resource::<RenderDevice>();
402 let render_queue = world.resource::<RenderQueue>();
403
404 let mut vbo = RawBufferVec::new(BufferUsages::VERTEX);
406 let mut ibo = RawBufferVec::new(BufferUsages::INDEX);
407
408 for vertex in &VERTICES {
409 vbo.push(*vertex);
410 }
411 for index in 0..3 {
412 ibo.push(index);
413 }
414
415 vbo.write_buffer(render_device, render_queue);
417 ibo.write_buffer(render_device, render_queue);
418
419 CustomPhaseItemBuffers {
420 vertices: vbo,
421 indices: ibo,
422 }
423 }
424}