Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
bevyengine
GitHub Repository: bevyengine/bevy
Path: blob/main/examples/shader_advanced/custom_phase_item.rs
6849 views
1
//! Demonstrates how to enqueue custom draw commands in a render phase.
2
//!
3
//! This example shows how to use the built-in
4
//! [`bevy_render::render_phase::BinnedRenderPhase`] functionality with a
5
//! custom [`RenderCommand`] to allow inserting arbitrary GPU drawing logic
6
//! into Bevy's pipeline. This is not the only way to add custom rendering code
7
//! into Bevy—render nodes are another, lower-level method—but it does allow
8
//! for better reuse of parts of Bevy's built-in mesh rendering logic.
9
10
use 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
};
41
use bytemuck::{Pod, Zeroable};
42
43
/// A marker component that represents an entity that is to be rendered using
44
/// our custom phase item.
45
///
46
/// Note the [`ExtractComponent`] trait implementation: this is necessary to
47
/// tell Bevy that this object should be pulled into the render world. Also note
48
/// the `on_add` hook, which is needed to tell Bevy's `check_visibility` system
49
/// that entities with this component need to be examined for visibility.
50
#[derive(Clone, Component, ExtractComponent)]
51
#[require(VisibilityClass)]
52
#[component(on_add = visibility::add_visibility_class::<CustomRenderedEntity>)]
53
struct CustomRenderedEntity;
54
55
/// A [`RenderCommand`] that binds the vertex and index buffers and issues the
56
/// draw command for our custom phase item.
57
struct DrawCustomPhaseItem;
58
59
impl<P> RenderCommand<P> for DrawCustomPhaseItem
60
where
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
// Borrow check workaround.
77
let custom_phase_item_buffers = custom_phase_item_buffers.into_inner();
78
79
// Tell the GPU where the vertices are.
80
pass.set_vertex_buffer(
81
0,
82
custom_phase_item_buffers
83
.vertices
84
.buffer()
85
.unwrap()
86
.slice(..),
87
);
88
89
// Tell the GPU where the indices are.
90
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
// Draw one triangle (3 vertices).
101
pass.draw_indexed(0..3, 0, 0..1);
102
103
RenderCommandResult::Success
104
}
105
}
106
107
/// The GPU vertex and index buffers for our custom phase item.
108
///
109
/// As the custom phase item is a single triangle, these are uploaded once and
110
/// then left alone.
111
#[derive(Resource)]
112
struct CustomPhaseItemBuffers {
113
/// The vertices for the single triangle.
114
///
115
/// This is a [`RawBufferVec`] because that's the simplest and fastest type
116
/// of GPU buffer, and [`Vertex`] objects are simple.
117
vertices: RawBufferVec<Vertex>,
118
119
/// The indices of the single triangle.
120
///
121
/// As above, this is a [`RawBufferVec`] because `u32` values have trivial
122
/// size and alignment.
123
indices: RawBufferVec<u32>,
124
}
125
126
/// The CPU-side structure that describes a single vertex of the triangle.
127
#[derive(Clone, Copy, Pod, Zeroable)]
128
#[repr(C)]
129
struct Vertex {
130
/// The 3D position of the triangle vertex.
131
position: Vec3,
132
/// Padding.
133
pad0: u32,
134
/// The color of the triangle vertex.
135
color: Vec3,
136
/// Padding.
137
pad1: u32,
138
}
139
140
impl Vertex {
141
/// Creates a new vertex structure.
142
const fn new(position: Vec3, color: Vec3) -> Vertex {
143
Vertex {
144
position,
145
color,
146
pad0: 0,
147
pad1: 0,
148
}
149
}
150
}
151
152
/// The custom draw commands that Bevy executes for each entity we enqueue into
153
/// the render phase.
154
type DrawCustomPhaseItemCommands = (SetItemPipeline, DrawCustomPhaseItem);
155
156
/// A single triangle's worth of vertices, for demonstration purposes.
157
static 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
163
/// The entry point.
164
fn 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
// We make sure to add these to the render app, not the main app.
171
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
183
/// Spawns the objects in the scene.
184
fn setup(mut commands: Commands) {
185
// Spawn a single entity that has custom rendering. It'll be extracted into
186
// the render world via [`ExtractComponent`].
187
commands.spawn((
188
Visibility::default(),
189
Transform::default(),
190
// This `Aabb` is necessary for the visibility checks to work.
191
Aabb {
192
center: Vec3A::ZERO,
193
half_extents: Vec3A::splat(0.5),
194
},
195
CustomRenderedEntity,
196
));
197
198
// Spawn the camera.
199
commands.spawn((
200
Camera3d::default(),
201
Transform::from_xyz(0.0, 0.0, 1.0).looking_at(Vec3::ZERO, Vec3::Y),
202
));
203
}
204
205
/// Creates the [`CustomPhaseItemBuffers`] resource.
206
///
207
/// This must be done in a startup system because it needs the [`RenderDevice`]
208
/// and [`RenderQueue`] to exist, and they don't until [`App::run`] is called.
209
fn prepare_custom_phase_item_buffers(mut commands: Commands) {
210
commands.init_resource::<CustomPhaseItemBuffers>();
211
}
212
213
/// A render-world system that enqueues the entity with custom rendering into
214
/// the opaque render phases of each view.
215
fn 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
// Render phases are per-view, so we need to iterate over all views so that
228
// the entity appears in them. (In this example, we have only one view, but
229
// it's good practice to loop over all views anyway.)
230
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
// Find all the custom rendered entities that are visible from this
236
// view.
237
for &entity in view_visible_entities.get::<CustomRenderedEntity>().iter() {
238
// Ordinarily, the [`SpecializedRenderPipeline::Key`] would contain
239
// some per-view settings, such as whether the view is HDR, but for
240
// simplicity's sake we simply hard-code the view's characteristics,
241
// with the exception of number of MSAA samples.
242
let Ok(pipeline_id) = pipeline
243
.variants
244
.specialize(&pipeline_cache, CustomPhaseKey(*msaa))
245
else {
246
continue;
247
};
248
249
// Bump the change tick in order to force Bevy to rebuild the bin.
250
let this_tick = next_tick.get() + 1;
251
next_tick.set(this_tick);
252
253
// Add the custom render item. We use the
254
// [`BinnedRenderPhaseType::NonMesh`] type to skip the special
255
// handling that Bevy has for meshes (preprocessing, indirect
256
// draws, etc.)
257
//
258
// The asset ID is arbitrary; we simply use [`AssetId::invalid`],
259
// but you can use anything you like. Note that the asset ID need
260
// not be the ID of a [`Mesh`].
261
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
282
struct CustomPhaseSpecializer;
283
284
#[derive(Resource)]
285
struct CustomPhasePipeline {
286
/// the `variants` collection holds onto the shader handle through the base descriptor
287
variants: Variants<RenderPipeline, CustomPhaseSpecializer>,
288
}
289
290
impl 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
// This needs to match the layout of [`Vertex`].
303
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
// Ordinarily, you'd want to check whether the view has the
322
// HDR format and substitute the appropriate texture format
323
// here, but we omit that for simplicity.
324
format: TextureFormat::bevy_default(),
325
blend: None,
326
write_mask: ColorWrites::ALL,
327
})],
328
..default()
329
}),
330
// Note that if your view has no depth buffer this will need to be
331
// changed.
332
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)]
349
struct CustomPhaseKey(Msaa);
350
351
impl 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
364
impl 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
// Create the vertex and index buffers.
370
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
// These two lines are required in order to trigger the upload to GPU.
381
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
}
390
391