Skip to content

Commit

Permalink
more docs
Browse files Browse the repository at this point in the history
  • Loading branch information
IceSentry committed Jul 12, 2023
1 parent cb2f373 commit b9635d5
Showing 1 changed file with 82 additions and 43 deletions.
125 changes: 82 additions & 43 deletions examples/shader/custom_render_phase.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
//! An example showcasing how to set up a custom draw command and render phase
//!
//! The example shader is a very basic shader that just outputs a color at the correct position.
//!
//! This is a fairly low level example and assumes some familiarity with rendering concepts and wgpu.

use bevy::{
core_pipeline::core_3d::{self, CORE_3D},
Expand Down Expand Up @@ -49,12 +53,15 @@ fn main() {
}

fn setup(mut commands: Commands, mut meshes: ResMut<Assets<Mesh>>) {
// camera
commands.spawn(Camera3dBundle {
transform: Transform::from_xyz(0.0, 1.0, 5.0),
..default()
});

// Spawn 3 cubes that use the custom draw command
// Each cube is at a different depth to show that the sorting works correctly

commands.spawn(CustomMaterialMeshBundle {
mesh: meshes.add(Mesh::from(shape::Cube { size: 1.0 })),
material: CustomMaterial {
Expand All @@ -81,25 +88,34 @@ fn setup(mut commands: Commands, mut meshes: ResMut<Assets<Mesh>>) {
});
}

const CUSTOM_NODE: &str = "custom_node";

// Initializing various parts of the render pipeline can be quite complex so it's easier to do it in a separate plugin
pub struct CustomRenderPhasePlugin;
impl Plugin for CustomRenderPhasePlugin {
fn build(&self, app: &mut App) {
// The UniformComponentPlugin will set up the necessary system to
// automatically extract and prepare the given uniform component
app.add_plugins(UniformComponentPlugin::<CustomMaterialUniform>::default());

// We need to get the render app from the main app
let Ok(render_app) = app.get_sub_app_mut(RenderApp) else {
return;
};

render_app
// The CustomPipeline is a specialized mesh pipeline so we need to initialize it
.init_resource::<SpecializedMeshPipelines<CustomPipeline>>()
// When making a custom phase you need to initialize the DrawFunctions resource for that phase
.init_resource::<DrawFunctions<CustomPhaseItem>>()
// You also need to add the custom render command to that phase
.add_render_command::<CustomPhaseItem, DrawCustom>()
.add_render_command::<CustomPhaseItem, DrawCustom>();

// The render world
render_app
// The extract schedule is the only sync point between the main world and the render world
// When you need to send data to the render world you need to extract
// that data and you can only do it in the ExtractSchedule.
// Some common extract scenarios have plugins that will do this automatically.
// For the purpose of the example we will do it manually.
.add_systems(
ExtractSchedule,
(extract_render_phase, extract_custom_material_uniform),
Expand All @@ -112,14 +128,29 @@ impl Plugin for CustomRenderPhasePlugin {
queue_mesh_custom_phase.in_set(RenderSet::Queue),
queue_custom_bind_group.in_set(RenderSet::Queue),
),
)
);

// Bevy's renderer uses a render graph which is a collection of nodes in a directed acyclic graph.
// It currently runs on each view/camera and executes each node in the specified order.
// It will make sure that any node that needs a dependency from another node
// only runs when that dependency is done.
//
// Each node can execute arbitrary work, but it generally runs at least one render pass.
// A node only has access to the render world, so if you need data from the main world
// you need to extract it manually or with the plugin like above.
render_app
// Add the node that will render the custom phase
.add_render_graph_node::<ViewNodeRunner<CustomNode>>(CORE_3D, CUSTOM_NODE)
.add_render_graph_node::<ViewNodeRunner<CustomNode>>(CORE_3D, CustomNode::NAME)
// This will schedule the custom node to run after the main opaque pass
.add_render_graph_edge(CORE_3D, core_3d::graph::node::MAIN_OPAQUE_PASS, CUSTOM_NODE);
.add_render_graph_edge(
CORE_3D,
core_3d::graph::node::MAIN_OPAQUE_PASS,
CustomNode::NAME,
);
}

fn finish(&self, app: &mut App) {
// We need to get the render app from the main app
let Ok(render_app) = app.get_sub_app_mut(RenderApp) else {
return;
};
Expand All @@ -129,45 +160,13 @@ impl Plugin for CustomRenderPhasePlugin {
}
}

#[derive(Resource, Deref)]
pub struct CustomMaterialBindGroup(BindGroup);

pub struct SetMaterialBindGroup<const I: usize>;
impl<P: PhaseItem, const I: usize> RenderCommand<P> for SetMaterialBindGroup<I> {
type Param = SRes<CustomMaterialBindGroup>;
type ViewWorldQuery = ();
type ItemWorldQuery = Read<DynamicUniformIndex<CustomMaterialUniform>>;

#[inline]
fn render<'w>(
_item: &P,
_view: (),
mesh_index: ROQueryItem<'w, Self::ItemWorldQuery>,
bind_group: SystemParamItem<'w, '_, Self::Param>,
pass: &mut TrackedRenderPass<'w>,
) -> RenderCommandResult {
pass.set_bind_group(I, bind_group.into_inner(), &[mesh_index.index()]);
RenderCommandResult::Success
}
}

// The custom draw command
type DrawCustom = (
// Sets the render pipeline for the draw
SetItemPipeline,
// Sets the mesh view bind group at index 0
SetMeshViewBindGroup<0>,
// Sets the custom material bind group at index 1
SetMaterialBindGroup<1>,
// Sets the mesh bind group at index 2
SetMeshBindGroup<2>,
// Draws the mesh with the specified pipeline and bind groups
DrawMesh,
);

// The render node that will render the custom phase
#[derive(Default)]
pub struct CustomNode;
impl CustomNode {
const NAME: &str = "custom_node";
}

impl ViewNode for CustomNode {
type ViewQuery = (
&'static RenderPhase<CustomPhaseItem>,
Expand Down Expand Up @@ -211,6 +210,46 @@ impl ViewNode for CustomNode {
}
}

// The bind group of the custom material
#[derive(Resource, Deref)]
pub struct CustomMaterialBindGroup(BindGroup);

// TODO document render command
pub struct SetMaterialBindGroup<const I: usize>;
impl<P: PhaseItem, const I: usize> RenderCommand<P> for SetMaterialBindGroup<I> {
type Param = SRes<CustomMaterialBindGroup>;
type ViewWorldQuery = ();
type ItemWorldQuery = Read<DynamicUniformIndex<CustomMaterialUniform>>;

#[inline]
fn render<'w>(
_item: &P,
_view: (),
mesh_index: ROQueryItem<'w, Self::ItemWorldQuery>,
bind_group: SystemParamItem<'w, '_, Self::Param>,
pass: &mut TrackedRenderPass<'w>,
) -> RenderCommandResult {
pass.set_bind_group(I, bind_group.into_inner(), &[mesh_index.index()]);
RenderCommandResult::Success
}
}

// The custom draw command
// TODO explain a bit more why it's a type alias
type DrawCustom = (
// Sets the render pipeline for the draw
SetItemPipeline,
// Sets the mesh view bind group at index 0
SetMeshViewBindGroup<0>,
// Sets the custom material bind group at index 1
SetMaterialBindGroup<1>,
// Sets the mesh bind group at index 2
SetMeshBindGroup<2>,
// Draws the mesh with the specified pipeline and bind groups
DrawMesh,
);

// TODO explain what a PhaseItem is
pub struct CustomPhaseItem {
pub distance: f32,
pub pipeline: CachedRenderPipelineId,
Expand Down Expand Up @@ -284,9 +323,9 @@ impl FromWorld for CustomPipeline {
}
}

// TODO explain what SpecializedMeshPipeline are
impl SpecializedMeshPipeline for CustomPipeline {
type Key = MeshPipelineKey;

fn specialize(
&self,
key: Self::Key,
Expand Down

0 comments on commit b9635d5

Please sign in to comment.