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 component::Tick,
18 query::ROQueryItem,
19 system::{lifetimeless::SRes, SystemParamItem},
20 },
21 mesh::VertexBufferLayout,
22 prelude::*,
23 render::{
24 extract_component::{ExtractComponent, ExtractComponentPlugin},
25 render_phase::{
26 AddRenderCommand, BinnedRenderPhaseType, DrawFunctions, InputUniformIndex, PhaseItem,
27 RenderCommand, RenderCommandResult, SetItemPipeline, TrackedRenderPass,
28 ViewBinnedRenderPhases,
29 },
30 render_resource::{
31 BufferUsages, Canonical, ColorTargetState, ColorWrites, CompareFunction,
32 DepthStencilState, FragmentState, IndexFormat, PipelineCache, RawBufferVec,
33 RenderPipeline, RenderPipelineDescriptor, Specializer, SpecializerKey, TextureFormat,
34 Variants, VertexAttribute, VertexFormat, VertexState, VertexStepMode,
35 },
36 renderer::{RenderDevice, RenderQueue},
37 view::{ExtractedView, RenderVisibleEntities},
38 Render, RenderApp, RenderSystems,
39 },
40};
41use bytemuck::{Pod, Zeroable};
42
43#[derive(Clone, Component, ExtractComponent)]
51#[require(VisibilityClass)]
52#[component(on_add = visibility::add_visibility_class::<CustomRenderedEntity>)]
53struct CustomRenderedEntity;
54
55struct DrawCustomPhaseItem;
58
59impl<P> RenderCommand<P> for DrawCustomPhaseItem
60where
61 P: PhaseItem,
62{
63 type Param = SRes<CustomPhaseItemBuffers>;
64
65 type ViewQuery = ();
66
67 type ItemQuery = ();
68
69 fn render<'w>(
70 _: &P,
71 _: ROQueryItem<'w, '_, Self::ViewQuery>,
72 _: Option<ROQueryItem<'w, '_, Self::ItemQuery>>,
73 custom_phase_item_buffers: SystemParamItem<'w, '_, Self::Param>,
74 pass: &mut TrackedRenderPass<'w>,
75 ) -> RenderCommandResult {
76 let custom_phase_item_buffers = custom_phase_item_buffers.into_inner();
78
79 pass.set_vertex_buffer(
81 0,
82 custom_phase_item_buffers
83 .vertices
84 .buffer()
85 .unwrap()
86 .slice(..),
87 );
88
89 pass.set_index_buffer(
91 custom_phase_item_buffers
92 .indices
93 .buffer()
94 .unwrap()
95 .slice(..),
96 0,
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 .add_render_command::<Opaque3d, DrawCustomPhaseItemCommands>()
174 .add_systems(
175 Render,
176 prepare_custom_phase_item_buffers.in_set(RenderSystems::Prepare),
177 )
178 .add_systems(Render, queue_custom_phase_item.in_set(RenderSystems::Queue));
179
180 app.run();
181}
182
183fn setup(mut commands: Commands) {
185 commands.spawn((
188 Visibility::default(),
189 Transform::default(),
190 Aabb {
192 center: Vec3A::ZERO,
193 half_extents: Vec3A::splat(0.5),
194 },
195 CustomRenderedEntity,
196 ));
197
198 commands.spawn((
200 Camera3d::default(),
201 Transform::from_xyz(0.0, 0.0, 1.0).looking_at(Vec3::ZERO, Vec3::Y),
202 ));
203}
204
205fn prepare_custom_phase_item_buffers(mut commands: Commands) {
210 commands.init_resource::<CustomPhaseItemBuffers>();
211}
212
213fn queue_custom_phase_item(
216 pipeline_cache: Res<PipelineCache>,
217 mut pipeline: ResMut<CustomPhasePipeline>,
218 mut opaque_render_phases: ResMut<ViewBinnedRenderPhases<Opaque3d>>,
219 opaque_draw_functions: Res<DrawFunctions<Opaque3d>>,
220 views: Query<(&ExtractedView, &RenderVisibleEntities, &Msaa)>,
221 mut next_tick: Local<Tick>,
222) {
223 let draw_custom_phase_item = opaque_draw_functions
224 .read()
225 .id::<DrawCustomPhaseItemCommands>();
226
227 for (view, view_visible_entities, msaa) in views.iter() {
231 let Some(opaque_phase) = opaque_render_phases.get_mut(&view.retained_view_entity) else {
232 continue;
233 };
234
235 for &entity in view_visible_entities.get::<CustomRenderedEntity>().iter() {
238 let Ok(pipeline_id) = pipeline
243 .variants
244 .specialize(&pipeline_cache, CustomPhaseKey(*msaa))
245 else {
246 continue;
247 };
248
249 let this_tick = next_tick.get() + 1;
251 next_tick.set(this_tick);
252
253 opaque_phase.add(
262 Opaque3dBatchSetKey {
263 draw_function: draw_custom_phase_item,
264 pipeline: pipeline_id,
265 material_bind_group_index: None,
266 lightmap_slab: None,
267 vertex_slab: default(),
268 index_slab: None,
269 },
270 Opaque3dBinKey {
271 asset_id: AssetId::<Mesh>::invalid().untyped(),
272 },
273 entity,
274 InputUniformIndex::default(),
275 BinnedRenderPhaseType::NonMesh,
276 *next_tick,
277 );
278 }
279 }
280}
281
282struct CustomPhaseSpecializer;
283
284#[derive(Resource)]
285struct CustomPhasePipeline {
286 variants: Variants<RenderPipeline, CustomPhaseSpecializer>,
288}
289
290impl FromWorld for CustomPhasePipeline {
291 fn from_world(world: &mut World) -> Self {
292 let asset_server = world.resource::<AssetServer>();
293 let shader = asset_server.load("shaders/custom_phase_item.wgsl");
294
295 let base_descriptor = RenderPipelineDescriptor {
296 label: Some("custom render pipeline".into()),
297 vertex: VertexState {
298 shader: shader.clone(),
299 buffers: vec![VertexBufferLayout {
300 array_stride: size_of::<Vertex>() as u64,
301 step_mode: VertexStepMode::Vertex,
302 attributes: vec![
304 VertexAttribute {
305 format: VertexFormat::Float32x3,
306 offset: 0,
307 shader_location: 0,
308 },
309 VertexAttribute {
310 format: VertexFormat::Float32x3,
311 offset: 16,
312 shader_location: 1,
313 },
314 ],
315 }],
316 ..default()
317 },
318 fragment: Some(FragmentState {
319 shader: shader.clone(),
320 targets: vec![Some(ColorTargetState {
321 format: TextureFormat::bevy_default(),
325 blend: None,
326 write_mask: ColorWrites::ALL,
327 })],
328 ..default()
329 }),
330 depth_stencil: Some(DepthStencilState {
333 format: CORE_3D_DEPTH_FORMAT,
334 depth_write_enabled: false,
335 depth_compare: CompareFunction::Always,
336 stencil: default(),
337 bias: default(),
338 }),
339 ..default()
340 };
341
342 let variants = Variants::new(CustomPhaseSpecializer, base_descriptor);
343
344 Self { variants }
345 }
346}
347
348#[derive(Copy, Clone, PartialEq, Eq, Hash, SpecializerKey)]
349struct CustomPhaseKey(Msaa);
350
351impl Specializer<RenderPipeline> for CustomPhaseSpecializer {
352 type Key = CustomPhaseKey;
353
354 fn specialize(
355 &self,
356 key: Self::Key,
357 descriptor: &mut RenderPipelineDescriptor,
358 ) -> Result<Canonical<Self::Key>, BevyError> {
359 descriptor.multisample.count = key.0.samples();
360 Ok(key)
361 }
362}
363
364impl FromWorld for CustomPhaseItemBuffers {
365 fn from_world(world: &mut World) -> Self {
366 let render_device = world.resource::<RenderDevice>();
367 let render_queue = world.resource::<RenderQueue>();
368
369 let mut vbo = RawBufferVec::new(BufferUsages::VERTEX);
371 let mut ibo = RawBufferVec::new(BufferUsages::INDEX);
372
373 for vertex in &VERTICES {
374 vbo.push(*vertex);
375 }
376 for index in 0..3 {
377 ibo.push(index);
378 }
379
380 vbo.write_buffer(render_device, render_queue);
382 ibo.write_buffer(render_device, render_queue);
383
384 CustomPhaseItemBuffers {
385 vertices: vbo,
386 indices: ibo,
387 }
388 }
389}