Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
bevyengine
GitHub Repository: bevyengine/bevy
Path: blob/main/examples/shader_advanced/custom_phase_item.rs
9345 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
change_detection::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
IndexFormat::Uint32,
97
);
98
99
// Draw one triangle (3 vertices).
100
pass.draw_indexed(0..3, 0, 0..1);
101
102
RenderCommandResult::Success
103
}
104
}
105
106
/// The GPU vertex and index buffers for our custom phase item.
107
///
108
/// As the custom phase item is a single triangle, these are uploaded once and
109
/// then left alone.
110
#[derive(Resource)]
111
struct CustomPhaseItemBuffers {
112
/// The vertices for the single triangle.
113
///
114
/// This is a [`RawBufferVec`] because that's the simplest and fastest type
115
/// of GPU buffer, and [`Vertex`] objects are simple.
116
vertices: RawBufferVec<Vertex>,
117
118
/// The indices of the single triangle.
119
///
120
/// As above, this is a [`RawBufferVec`] because `u32` values have trivial
121
/// size and alignment.
122
indices: RawBufferVec<u32>,
123
}
124
125
/// The CPU-side structure that describes a single vertex of the triangle.
126
#[derive(Clone, Copy, Pod, Zeroable)]
127
#[repr(C)]
128
struct Vertex {
129
/// The 3D position of the triangle vertex.
130
position: Vec3,
131
/// Padding.
132
pad0: u32,
133
/// The color of the triangle vertex.
134
color: Vec3,
135
/// Padding.
136
pad1: u32,
137
}
138
139
impl Vertex {
140
/// Creates a new vertex structure.
141
const fn new(position: Vec3, color: Vec3) -> Vertex {
142
Vertex {
143
position,
144
color,
145
pad0: 0,
146
pad1: 0,
147
}
148
}
149
}
150
151
/// The custom draw commands that Bevy executes for each entity we enqueue into
152
/// the render phase.
153
type DrawCustomPhaseItemCommands = (SetItemPipeline, DrawCustomPhaseItem);
154
155
/// A single triangle's worth of vertices, for demonstration purposes.
156
static VERTICES: [Vertex; 3] = [
157
Vertex::new(vec3(-0.866, -0.5, 0.5), vec3(1.0, 0.0, 0.0)),
158
Vertex::new(vec3(0.866, -0.5, 0.5), vec3(0.0, 1.0, 0.0)),
159
Vertex::new(vec3(0.0, 1.0, 0.5), vec3(0.0, 0.0, 1.0)),
160
];
161
162
/// The entry point.
163
fn main() {
164
let mut app = App::new();
165
app.add_plugins(DefaultPlugins)
166
.add_plugins(ExtractComponentPlugin::<CustomRenderedEntity>::default())
167
.add_systems(Startup, setup);
168
169
// We make sure to add these to the render app, not the main app.
170
app.sub_app_mut(RenderApp)
171
.init_resource::<CustomPhasePipeline>()
172
.add_render_command::<Opaque3d, DrawCustomPhaseItemCommands>()
173
.add_systems(
174
Render,
175
prepare_custom_phase_item_buffers.in_set(RenderSystems::Prepare),
176
)
177
.add_systems(Render, queue_custom_phase_item.in_set(RenderSystems::Queue));
178
179
app.run();
180
}
181
182
/// Spawns the objects in the scene.
183
fn setup(mut commands: Commands) {
184
// Spawn a single entity that has custom rendering. It'll be extracted into
185
// the render world via [`ExtractComponent`].
186
commands.spawn((
187
Visibility::default(),
188
Transform::default(),
189
// This `Aabb` is necessary for the visibility checks to work.
190
Aabb {
191
center: Vec3A::ZERO,
192
half_extents: Vec3A::splat(0.5),
193
},
194
CustomRenderedEntity,
195
));
196
197
// Spawn the camera.
198
commands.spawn((
199
Camera3d::default(),
200
Transform::from_xyz(0.0, 0.0, 1.0).looking_at(Vec3::ZERO, Vec3::Y),
201
));
202
}
203
204
/// Creates the [`CustomPhaseItemBuffers`] resource.
205
///
206
/// This must be done in a startup system because it needs the [`RenderDevice`]
207
/// and [`RenderQueue`] to exist, and they don't until [`App::run`] is called.
208
fn prepare_custom_phase_item_buffers(mut commands: Commands) {
209
commands.init_resource::<CustomPhaseItemBuffers>();
210
}
211
212
/// A render-world system that enqueues the entity with custom rendering into
213
/// the opaque render phases of each view.
214
fn queue_custom_phase_item(
215
pipeline_cache: Res<PipelineCache>,
216
mut pipeline: ResMut<CustomPhasePipeline>,
217
mut opaque_render_phases: ResMut<ViewBinnedRenderPhases<Opaque3d>>,
218
opaque_draw_functions: Res<DrawFunctions<Opaque3d>>,
219
views: Query<(&ExtractedView, &RenderVisibleEntities, &Msaa)>,
220
mut next_tick: Local<Tick>,
221
) {
222
let draw_custom_phase_item = opaque_draw_functions
223
.read()
224
.id::<DrawCustomPhaseItemCommands>();
225
226
// Render phases are per-view, so we need to iterate over all views so that
227
// the entity appears in them. (In this example, we have only one view, but
228
// it's good practice to loop over all views anyway.)
229
for (view, view_visible_entities, msaa) in views.iter() {
230
let Some(opaque_phase) = opaque_render_phases.get_mut(&view.retained_view_entity) else {
231
continue;
232
};
233
234
// Find all the custom rendered entities that are visible from this
235
// view.
236
for &entity in view_visible_entities.get::<CustomRenderedEntity>().iter() {
237
// Ordinarily, the [`SpecializedRenderPipeline::Key`] would contain
238
// some per-view settings, such as whether the view is HDR, but for
239
// simplicity's sake we simply hard-code the view's characteristics,
240
// with the exception of number of MSAA samples.
241
let Ok(pipeline_id) = pipeline
242
.variants
243
.specialize(&pipeline_cache, CustomPhaseKey(*msaa))
244
else {
245
continue;
246
};
247
248
// Bump the change tick in order to force Bevy to rebuild the bin.
249
let this_tick = next_tick.get() + 1;
250
next_tick.set(this_tick);
251
252
// Add the custom render item. We use the
253
// [`BinnedRenderPhaseType::NonMesh`] type to skip the special
254
// handling that Bevy has for meshes (preprocessing, indirect
255
// draws, etc.)
256
//
257
// The asset ID is arbitrary; we simply use [`AssetId::invalid`],
258
// but you can use anything you like. Note that the asset ID need
259
// not be the ID of a [`Mesh`].
260
opaque_phase.add(
261
Opaque3dBatchSetKey {
262
draw_function: draw_custom_phase_item,
263
pipeline: pipeline_id,
264
material_bind_group_index: None,
265
lightmap_slab: None,
266
vertex_slab: default(),
267
index_slab: None,
268
},
269
Opaque3dBinKey {
270
asset_id: AssetId::<Mesh>::invalid().untyped(),
271
},
272
entity,
273
InputUniformIndex::default(),
274
BinnedRenderPhaseType::NonMesh,
275
*next_tick,
276
);
277
}
278
}
279
}
280
281
struct CustomPhaseSpecializer;
282
283
#[derive(Resource)]
284
struct CustomPhasePipeline {
285
/// the `variants` collection holds onto the shader handle through the base descriptor
286
variants: Variants<RenderPipeline, CustomPhaseSpecializer>,
287
}
288
289
impl FromWorld for CustomPhasePipeline {
290
fn from_world(world: &mut World) -> Self {
291
let asset_server = world.resource::<AssetServer>();
292
let shader = asset_server.load("shaders/custom_phase_item.wgsl");
293
294
let base_descriptor = RenderPipelineDescriptor {
295
label: Some("custom render pipeline".into()),
296
vertex: VertexState {
297
shader: shader.clone(),
298
buffers: vec![VertexBufferLayout {
299
array_stride: size_of::<Vertex>() as u64,
300
step_mode: VertexStepMode::Vertex,
301
// This needs to match the layout of [`Vertex`].
302
attributes: vec![
303
VertexAttribute {
304
format: VertexFormat::Float32x3,
305
offset: 0,
306
shader_location: 0,
307
},
308
VertexAttribute {
309
format: VertexFormat::Float32x3,
310
offset: 16,
311
shader_location: 1,
312
},
313
],
314
}],
315
..default()
316
},
317
fragment: Some(FragmentState {
318
shader: shader.clone(),
319
targets: vec![Some(ColorTargetState {
320
// Ordinarily, you'd want to check whether the view has the
321
// HDR format and substitute the appropriate texture format
322
// here, but we omit that for simplicity.
323
format: TextureFormat::bevy_default(),
324
blend: None,
325
write_mask: ColorWrites::ALL,
326
})],
327
..default()
328
}),
329
// Note that if your view has no depth buffer this will need to be
330
// changed.
331
depth_stencil: Some(DepthStencilState {
332
format: CORE_3D_DEPTH_FORMAT,
333
depth_write_enabled: false,
334
depth_compare: CompareFunction::Always,
335
stencil: default(),
336
bias: default(),
337
}),
338
..default()
339
};
340
341
let variants = Variants::new(CustomPhaseSpecializer, base_descriptor);
342
343
Self { variants }
344
}
345
}
346
347
#[derive(Copy, Clone, PartialEq, Eq, Hash, SpecializerKey)]
348
struct CustomPhaseKey(Msaa);
349
350
impl Specializer<RenderPipeline> for CustomPhaseSpecializer {
351
type Key = CustomPhaseKey;
352
353
fn specialize(
354
&self,
355
key: Self::Key,
356
descriptor: &mut RenderPipelineDescriptor,
357
) -> Result<Canonical<Self::Key>, BevyError> {
358
descriptor.multisample.count = key.0.samples();
359
Ok(key)
360
}
361
}
362
363
impl FromWorld for CustomPhaseItemBuffers {
364
fn from_world(world: &mut World) -> Self {
365
let render_device = world.resource::<RenderDevice>();
366
let render_queue = world.resource::<RenderQueue>();
367
368
// Create the vertex and index buffers.
369
let mut vbo = RawBufferVec::new(BufferUsages::VERTEX);
370
let mut ibo = RawBufferVec::new(BufferUsages::INDEX);
371
372
for vertex in &VERTICES {
373
vbo.push(*vertex);
374
}
375
for index in 0..3 {
376
ibo.push(index);
377
}
378
379
// These two lines are required in order to trigger the upload to GPU.
380
vbo.write_buffer(render_device, render_queue);
381
ibo.write_buffer(render_device, render_queue);
382
383
CustomPhaseItemBuffers {
384
vertices: vbo,
385
indices: ibo,
386
}
387
}
388
}
389
390