Add RenderGraphApp to simplify adding render nodes (#8007)

# Objective

- Adding a node to the render_graph can be quite verbose and error prone
because there's a lot of moving parts to it.

## Solution

- Encapsulate this in a simple utility method
	- Mostly intended for optional nodes that have specific ordering
- Requires that the `Node` impl `FromWorld`, but every internal node is
built using a new function taking a `&mut World` so it was essentially
already `FromWorld`
- Use it for the bloom, fxaa and taa, nodes. 
- The main nodes don't use it because they rely more on the order of
many nodes being added

---

## Changelog

- Impl `FromWorld` for `BloomNode`, `FxaaNode` and `TaaNode`
- Added `RenderGraph::add_node_edges()`
- Added `RenderGraph::sub_graph()`
- Added `RenderGraph::sub_graph_mut()`
- Added `RenderGraphApp`, `RenderGraphApp::add_render_graph_node`,
`RenderGraphApp::add_render_graph_edges`,
`RenderGraphApp::add_render_graph_edge`

## Notes

~~This was taken out of https://github.com/bevyengine/bevy/pull/7995
because it works on it's own. Once the linked PR is done, the new
`add_node()` will be simplified a bit since the input/output params
won't be necessary.~~

This feature will be useful in most of the upcoming render nodes so it's
impact will be more relevant at that point.

Partially fixes #7985 

## Future work

* Add a way to automatically label nodes or at least make it part of the
trait. This would remove one more field from the functions added in this
PR
* Use it in the main pass 2d/3d

---------

Co-authored-by: Carter Anderson <mcanders1@gmail.com>
This commit is contained in:
IceSentry 2023-04-03 20:50:22 -04:00 committed by GitHub
parent 5c7abb0579
commit 614de3019c
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
12 changed files with 318 additions and 199 deletions

View file

@ -16,7 +16,7 @@ use bevy_render::{
ComponentUniforms, DynamicUniformIndex, ExtractComponentPlugin, UniformComponentPlugin, ComponentUniforms, DynamicUniformIndex, ExtractComponentPlugin, UniformComponentPlugin,
}, },
prelude::Color, prelude::Color,
render_graph::{Node, NodeRunError, RenderGraph, RenderGraphContext}, render_graph::{Node, NodeRunError, RenderGraphApp, RenderGraphContext},
render_resource::*, render_resource::*,
renderer::{RenderContext, RenderDevice}, renderer::{RenderContext, RenderDevice},
texture::{CachedTexture, TextureCache}, texture::{CachedTexture, TextureCache},
@ -71,45 +71,27 @@ impl Plugin for BloomPlugin {
prepare_upsampling_pipeline.in_set(RenderSet::Prepare), prepare_upsampling_pipeline.in_set(RenderSet::Prepare),
queue_bloom_bind_groups.in_set(RenderSet::Queue), queue_bloom_bind_groups.in_set(RenderSet::Queue),
), ),
)
// Add bloom to the 3d render graph
.add_render_graph_node::<BloomNode>(core_3d::graph::NAME, core_3d::graph::node::BLOOM)
.add_render_graph_edges(
core_3d::graph::NAME,
&[
core_3d::graph::node::END_MAIN_PASS,
core_3d::graph::node::BLOOM,
core_3d::graph::node::TONEMAPPING,
],
)
// Add bloom to the 2d render graph
.add_render_graph_node::<BloomNode>(core_2d::graph::NAME, core_2d::graph::node::BLOOM)
.add_render_graph_edges(
core_2d::graph::NAME,
&[
core_2d::graph::node::MAIN_PASS,
core_2d::graph::node::BLOOM,
core_2d::graph::node::TONEMAPPING,
],
); );
// Add bloom to the 3d render graph
{
let bloom_node = BloomNode::new(&mut render_app.world);
let mut graph = render_app.world.resource_mut::<RenderGraph>();
let draw_3d_graph = graph
.get_sub_graph_mut(crate::core_3d::graph::NAME)
.unwrap();
draw_3d_graph.add_node(core_3d::graph::node::BLOOM, bloom_node);
// MAIN_PASS -> BLOOM -> TONEMAPPING
draw_3d_graph.add_node_edge(
crate::core_3d::graph::node::END_MAIN_PASS,
core_3d::graph::node::BLOOM,
);
draw_3d_graph.add_node_edge(
core_3d::graph::node::BLOOM,
crate::core_3d::graph::node::TONEMAPPING,
);
}
// Add bloom to the 2d render graph
{
let bloom_node = BloomNode::new(&mut render_app.world);
let mut graph = render_app.world.resource_mut::<RenderGraph>();
let draw_2d_graph = graph
.get_sub_graph_mut(crate::core_2d::graph::NAME)
.unwrap();
draw_2d_graph.add_node(core_2d::graph::node::BLOOM, bloom_node);
// MAIN_PASS -> BLOOM -> TONEMAPPING
draw_2d_graph.add_node_edge(
crate::core_2d::graph::node::MAIN_PASS,
core_2d::graph::node::BLOOM,
);
draw_2d_graph.add_node_edge(
core_2d::graph::node::BLOOM,
crate::core_2d::graph::node::TONEMAPPING,
);
}
} }
} }
@ -126,8 +108,8 @@ pub struct BloomNode {
)>, )>,
} }
impl BloomNode { impl FromWorld for BloomNode {
pub fn new(world: &mut World) -> Self { fn from_world(world: &mut World) -> Self {
Self { Self {
view_query: QueryState::new(world), view_query: QueryState::new(world),
} }

View file

@ -6,7 +6,7 @@ use bevy_reflect::{Reflect, TypeUuid};
use bevy_render::{ use bevy_render::{
extract_component::{ExtractComponent, ExtractComponentPlugin, UniformComponentPlugin}, extract_component::{ExtractComponent, ExtractComponentPlugin, UniformComponentPlugin},
prelude::Camera, prelude::Camera,
render_graph::RenderGraph, render_graph::RenderGraphApp,
render_resource::*, render_resource::*,
renderer::RenderDevice, renderer::RenderDevice,
texture::BevyDefault, texture::BevyDefault,
@ -114,47 +114,47 @@ impl Plugin for CASPlugin {
render_app render_app
.init_resource::<CASPipeline>() .init_resource::<CASPipeline>()
.init_resource::<SpecializedRenderPipelines<CASPipeline>>() .init_resource::<SpecializedRenderPipelines<CASPipeline>>()
.add_systems(Render, prepare_cas_pipelines.in_set(RenderSet::Prepare)); .add_systems(Render, prepare_cas_pipelines.in_set(RenderSet::Prepare))
{ // 3d
let cas_node = CASNode::new(&mut render_app.world); .add_render_graph_node::<CASNode>(
let mut binding = render_app.world.resource_mut::<RenderGraph>(); core_3d::graph::NAME,
let graph = binding.get_sub_graph_mut(core_3d::graph::NAME).unwrap(); core_3d::graph::node::CONTRAST_ADAPTIVE_SHARPENING,
)
graph.add_node(core_3d::graph::node::CONTRAST_ADAPTIVE_SHARPENING, cas_node); .add_render_graph_edge(
core_3d::graph::NAME,
graph.add_node_edge(
core_3d::graph::node::TONEMAPPING, core_3d::graph::node::TONEMAPPING,
core_3d::graph::node::CONTRAST_ADAPTIVE_SHARPENING, core_3d::graph::node::CONTRAST_ADAPTIVE_SHARPENING,
); )
graph.add_node_edge( .add_render_graph_edge(
core_3d::graph::NAME,
core_3d::graph::node::FXAA, core_3d::graph::node::FXAA,
core_3d::graph::node::CONTRAST_ADAPTIVE_SHARPENING, core_3d::graph::node::CONTRAST_ADAPTIVE_SHARPENING,
); )
graph.add_node_edge( .add_render_graph_edge(
core_3d::graph::NAME,
core_3d::graph::node::CONTRAST_ADAPTIVE_SHARPENING, core_3d::graph::node::CONTRAST_ADAPTIVE_SHARPENING,
core_3d::graph::node::END_MAIN_PASS_POST_PROCESSING, core_3d::graph::node::END_MAIN_PASS_POST_PROCESSING,
); )
} // 2d
{ .add_render_graph_node::<CASNode>(
let cas_node = CASNode::new(&mut render_app.world); core_2d::graph::NAME,
let mut binding = render_app.world.resource_mut::<RenderGraph>(); core_2d::graph::node::CONTRAST_ADAPTIVE_SHARPENING,
let graph = binding.get_sub_graph_mut(core_2d::graph::NAME).unwrap(); )
.add_render_graph_edge(
graph.add_node(core_2d::graph::node::CONTRAST_ADAPTIVE_SHARPENING, cas_node); core_2d::graph::NAME,
graph.add_node_edge(
core_2d::graph::node::TONEMAPPING, core_2d::graph::node::TONEMAPPING,
core_2d::graph::node::CONTRAST_ADAPTIVE_SHARPENING, core_2d::graph::node::CONTRAST_ADAPTIVE_SHARPENING,
); )
graph.add_node_edge( .add_render_graph_edge(
core_2d::graph::NAME,
core_2d::graph::node::FXAA, core_2d::graph::node::FXAA,
core_2d::graph::node::CONTRAST_ADAPTIVE_SHARPENING, core_2d::graph::node::CONTRAST_ADAPTIVE_SHARPENING,
); )
graph.add_node_edge( .add_render_graph_edge(
core_2d::graph::NAME,
core_2d::graph::node::CONTRAST_ADAPTIVE_SHARPENING, core_2d::graph::node::CONTRAST_ADAPTIVE_SHARPENING,
core_2d::graph::node::END_MAIN_PASS_POST_PROCESSING, core_2d::graph::node::END_MAIN_PASS_POST_PROCESSING,
); );
}
} }
} }

View file

@ -28,8 +28,8 @@ pub struct CASNode {
cached_bind_group: Mutex<Option<(BufferId, TextureViewId, BindGroup)>>, cached_bind_group: Mutex<Option<(BufferId, TextureViewId, BindGroup)>>,
} }
impl CASNode { impl FromWorld for CASNode {
pub fn new(world: &mut World) -> Self { fn from_world(world: &mut World) -> Self {
Self { Self {
query: QueryState::new(world), query: QueryState::new(world),
cached_bind_group: Mutex::new(None), cached_bind_group: Mutex::new(None),

View file

@ -74,15 +74,14 @@ impl Plugin for Core2dPlugin {
draw_2d_graph.add_node(graph::node::TONEMAPPING, tonemapping); draw_2d_graph.add_node(graph::node::TONEMAPPING, tonemapping);
draw_2d_graph.add_node(graph::node::END_MAIN_PASS_POST_PROCESSING, EmptyNode); draw_2d_graph.add_node(graph::node::END_MAIN_PASS_POST_PROCESSING, EmptyNode);
draw_2d_graph.add_node(graph::node::UPSCALING, upscaling); draw_2d_graph.add_node(graph::node::UPSCALING, upscaling);
draw_2d_graph.add_node_edge(graph::node::MAIN_PASS, graph::node::TONEMAPPING);
draw_2d_graph.add_node_edge( draw_2d_graph.add_node_edges(&[
graph::node::MAIN_PASS,
graph::node::TONEMAPPING, graph::node::TONEMAPPING,
graph::node::END_MAIN_PASS_POST_PROCESSING, graph::node::END_MAIN_PASS_POST_PROCESSING,
);
draw_2d_graph.add_node_edge(
graph::node::END_MAIN_PASS_POST_PROCESSING,
graph::node::UPSCALING, graph::node::UPSCALING,
); ]);
graph.add_sub_graph(graph::NAME, draw_2d_graph); graph.add_sub_graph(graph::NAME, draw_2d_graph);
} }
} }

View file

@ -106,25 +106,17 @@ impl Plugin for Core3dPlugin {
draw_3d_graph.add_node(graph::node::END_MAIN_PASS_POST_PROCESSING, EmptyNode); draw_3d_graph.add_node(graph::node::END_MAIN_PASS_POST_PROCESSING, EmptyNode);
draw_3d_graph.add_node(graph::node::UPSCALING, upscaling); draw_3d_graph.add_node(graph::node::UPSCALING, upscaling);
draw_3d_graph.add_node_edge(graph::node::PREPASS, graph::node::START_MAIN_PASS); draw_3d_graph.add_node_edges(&[
draw_3d_graph.add_node_edge(graph::node::START_MAIN_PASS, graph::node::MAIN_OPAQUE_PASS); graph::node::PREPASS,
draw_3d_graph.add_node_edge( graph::node::START_MAIN_PASS,
graph::node::MAIN_OPAQUE_PASS, graph::node::MAIN_OPAQUE_PASS,
graph::node::MAIN_TRANSPARENT_PASS, graph::node::MAIN_TRANSPARENT_PASS,
);
draw_3d_graph.add_node_edge(
graph::node::MAIN_TRANSPARENT_PASS,
graph::node::END_MAIN_PASS, graph::node::END_MAIN_PASS,
);
draw_3d_graph.add_node_edge(graph::node::END_MAIN_PASS, graph::node::TONEMAPPING);
draw_3d_graph.add_node_edge(
graph::node::TONEMAPPING, graph::node::TONEMAPPING,
graph::node::END_MAIN_PASS_POST_PROCESSING, graph::node::END_MAIN_PASS_POST_PROCESSING,
);
draw_3d_graph.add_node_edge(
graph::node::END_MAIN_PASS_POST_PROCESSING,
graph::node::UPSCALING, graph::node::UPSCALING,
); ]);
graph.add_sub_graph(graph::NAME, draw_3d_graph); graph.add_sub_graph(graph::NAME, draw_3d_graph);
} }
} }

View file

@ -9,7 +9,7 @@ use bevy_reflect::{
use bevy_render::{ use bevy_render::{
extract_component::{ExtractComponent, ExtractComponentPlugin}, extract_component::{ExtractComponent, ExtractComponentPlugin},
prelude::Camera, prelude::Camera,
render_graph::RenderGraph, render_graph::RenderGraphApp,
render_resource::*, render_resource::*,
renderer::RenderDevice, renderer::RenderDevice,
texture::BevyDefault, texture::BevyDefault,
@ -90,40 +90,25 @@ impl Plugin for FxaaPlugin {
render_app render_app
.init_resource::<FxaaPipeline>() .init_resource::<FxaaPipeline>()
.init_resource::<SpecializedRenderPipelines<FxaaPipeline>>() .init_resource::<SpecializedRenderPipelines<FxaaPipeline>>()
.add_systems(Render, prepare_fxaa_pipelines.in_set(RenderSet::Prepare)); .add_systems(Render, prepare_fxaa_pipelines.in_set(RenderSet::Prepare))
.add_render_graph_node::<FxaaNode>(core_3d::graph::NAME, core_3d::graph::node::FXAA)
{ .add_render_graph_edges(
let fxaa_node = FxaaNode::new(&mut render_app.world); core_3d::graph::NAME,
let mut binding = render_app.world.resource_mut::<RenderGraph>(); &[
let graph = binding.get_sub_graph_mut(core_3d::graph::NAME).unwrap(); core_3d::graph::node::TONEMAPPING,
core_3d::graph::node::FXAA,
graph.add_node(core_3d::graph::node::FXAA, fxaa_node); core_3d::graph::node::END_MAIN_PASS_POST_PROCESSING,
],
graph.add_node_edge( )
core_3d::graph::node::TONEMAPPING, .add_render_graph_node::<FxaaNode>(core_2d::graph::NAME, core_2d::graph::node::FXAA)
core_3d::graph::node::FXAA, .add_render_graph_edges(
core_2d::graph::NAME,
&[
core_2d::graph::node::TONEMAPPING,
core_2d::graph::node::FXAA,
core_2d::graph::node::END_MAIN_PASS_POST_PROCESSING,
],
); );
graph.add_node_edge(
core_3d::graph::node::FXAA,
core_3d::graph::node::END_MAIN_PASS_POST_PROCESSING,
);
}
{
let fxaa_node = FxaaNode::new(&mut render_app.world);
let mut binding = render_app.world.resource_mut::<RenderGraph>();
let graph = binding.get_sub_graph_mut(core_2d::graph::NAME).unwrap();
graph.add_node(core_2d::graph::node::FXAA, fxaa_node);
graph.add_node_edge(
core_2d::graph::node::TONEMAPPING,
core_2d::graph::node::FXAA,
);
graph.add_node_edge(
core_2d::graph::node::FXAA,
core_2d::graph::node::END_MAIN_PASS_POST_PROCESSING,
);
}
} }
} }

View file

@ -27,8 +27,8 @@ pub struct FxaaNode {
cached_texture_bind_group: Mutex<Option<(TextureViewId, BindGroup)>>, cached_texture_bind_group: Mutex<Option<(TextureViewId, BindGroup)>>,
} }
impl FxaaNode { impl FromWorld for FxaaNode {
pub fn new(world: &mut World) -> Self { fn from_world(world: &mut World) -> Self {
Self { Self {
query: QueryState::new(world), query: QueryState::new(world),
cached_texture_bind_group: Mutex::new(None), cached_texture_bind_group: Mutex::new(None),

View file

@ -1,4 +1,5 @@
use crate::{ use crate::{
core_3d,
fullscreen_vertex_shader::fullscreen_shader_vertex_state, fullscreen_vertex_shader::fullscreen_shader_vertex_state,
prelude::Camera3d, prelude::Camera3d,
prepass::{DepthPrepass, MotionVectorPrepass, ViewPrepassTextures}, prepass::{DepthPrepass, MotionVectorPrepass, ViewPrepassTextures},
@ -18,7 +19,7 @@ use bevy_reflect::{Reflect, TypeUuid};
use bevy_render::{ use bevy_render::{
camera::{ExtractedCamera, TemporalJitter}, camera::{ExtractedCamera, TemporalJitter},
prelude::{Camera, Projection}, prelude::{Camera, Projection},
render_graph::{Node, NodeRunError, RenderGraph, RenderGraphContext}, render_graph::{Node, NodeRunError, RenderGraphApp, RenderGraphContext},
render_resource::{ render_resource::{
BindGroupDescriptor, BindGroupEntry, BindGroupLayout, BindGroupLayoutDescriptor, BindGroupDescriptor, BindGroupEntry, BindGroupLayout, BindGroupLayoutDescriptor,
BindGroupLayoutEntry, BindingResource, BindingType, CachedRenderPipelineId, BindGroupLayoutEntry, BindingResource, BindingType, CachedRenderPipelineId,
@ -71,24 +72,17 @@ impl Plugin for TemporalAntiAliasPlugin {
prepare_taa_history_textures.in_set(RenderSet::Prepare), prepare_taa_history_textures.in_set(RenderSet::Prepare),
prepare_taa_pipelines.in_set(RenderSet::Prepare), prepare_taa_pipelines.in_set(RenderSet::Prepare),
), ),
)
.add_render_graph_node::<TAANode>(core_3d::graph::NAME, draw_3d_graph::node::TAA)
.add_render_graph_edges(
core_3d::graph::NAME,
&[
core_3d::graph::node::END_MAIN_PASS,
draw_3d_graph::node::TAA,
core_3d::graph::node::BLOOM,
core_3d::graph::node::TONEMAPPING,
],
); );
let taa_node = TAANode::new(&mut render_app.world);
let mut graph = render_app.world.resource_mut::<RenderGraph>();
let draw_3d_graph = graph
.get_sub_graph_mut(crate::core_3d::graph::NAME)
.unwrap();
draw_3d_graph.add_node(draw_3d_graph::node::TAA, taa_node);
// MAIN_PASS -> TAA -> BLOOM -> TONEMAPPING
draw_3d_graph.add_node_edge(
crate::core_3d::graph::node::END_MAIN_PASS,
draw_3d_graph::node::TAA,
);
draw_3d_graph.add_node_edge(draw_3d_graph::node::TAA, crate::core_3d::graph::node::BLOOM);
draw_3d_graph.add_node_edge(
draw_3d_graph::node::TAA,
crate::core_3d::graph::node::TONEMAPPING,
);
} }
} }
@ -168,8 +162,8 @@ struct TAANode {
)>, )>,
} }
impl TAANode { impl FromWorld for TAANode {
fn new(world: &mut World) -> Self { fn from_world(world: &mut World) -> Self {
Self { Self {
view_query: QueryState::new(world), view_query: QueryState::new(world),
} }

View file

@ -0,0 +1,73 @@
use bevy_app::App;
use bevy_ecs::world::FromWorld;
use super::{Node, RenderGraph};
/// Adds common [`RenderGraph`] operations to [`App`].
pub trait RenderGraphApp {
/// Add a [`Node`] to the [`RenderGraph`]:
/// * Create the [`Node`] using the [`FromWorld`] implementation
/// * Add it to the graph
fn add_render_graph_node<T: Node + FromWorld>(
&mut self,
sub_graph_name: &'static str,
node_name: &'static str,
) -> &mut Self;
/// Automatically add the required node edges based on the given ordering
fn add_render_graph_edges(
&mut self,
sub_graph_name: &'static str,
edges: &[&'static str],
) -> &mut Self;
/// Add node edge to the specified graph
fn add_render_graph_edge(
&mut self,
sub_graph_name: &'static str,
output_edge: &'static str,
input_edge: &'static str,
) -> &mut Self;
}
impl RenderGraphApp for App {
fn add_render_graph_node<T: Node + FromWorld>(
&mut self,
sub_graph_name: &'static str,
node_name: &'static str,
) -> &mut Self {
let node = T::from_world(&mut self.world);
let mut render_graph = self.world.get_resource_mut::<RenderGraph>().expect(
"RenderGraph not found. Make sure you are using add_render_graph_node on the RenderApp",
);
let graph = render_graph.sub_graph_mut(sub_graph_name);
graph.add_node(node_name, node);
self
}
fn add_render_graph_edges(
&mut self,
sub_graph_name: &'static str,
edges: &[&'static str],
) -> &mut Self {
let mut render_graph = self.world.get_resource_mut::<RenderGraph>().expect(
"RenderGraph not found. Make sure you are using add_render_graph_node on the RenderApp",
);
let graph = render_graph.sub_graph_mut(sub_graph_name);
graph.add_node_edges(edges);
self
}
fn add_render_graph_edge(
&mut self,
sub_graph_name: &'static str,
output_edge: &'static str,
input_edge: &'static str,
) -> &mut Self {
let mut render_graph = self.world.get_resource_mut::<RenderGraph>().expect(
"RenderGraph not found. Make sure you are using add_render_graph_node on the RenderApp",
);
let graph = render_graph.sub_graph_mut(sub_graph_name);
graph.add_node_edge(output_edge, input_edge);
self
}
}

View file

@ -119,6 +119,24 @@ impl RenderGraph {
id id
} }
/// Add `node_edge`s based on the order of the given `edges` array.
///
/// Defining an edge that already exists is not considered an error with this api.
/// It simply won't create a new edge.
pub fn add_node_edges(&mut self, edges: &[&'static str]) {
for window in edges.windows(2) {
let [a, b] = window else { break; };
if let Err(err) = self.try_add_node_edge(*a, *b) {
match err {
// Already existing edges are very easy to produce with this api
// and shouldn't cause a panic
RenderGraphError::EdgeAlreadyExists(_) => {}
_ => panic!("{err:?}"),
}
}
}
}
/// Removes the `node` with the `name` from the graph. /// Removes the `node` with the `name` from the graph.
/// If the name is does not exist, nothing happens. /// If the name is does not exist, nothing happens.
pub fn remove_node( pub fn remove_node(
@ -583,6 +601,36 @@ impl RenderGraph {
pub fn get_sub_graph_mut(&mut self, name: impl AsRef<str>) -> Option<&mut RenderGraph> { pub fn get_sub_graph_mut(&mut self, name: impl AsRef<str>) -> Option<&mut RenderGraph> {
self.sub_graphs.get_mut(name.as_ref()) self.sub_graphs.get_mut(name.as_ref())
} }
/// Retrieves the sub graph corresponding to the `name`.
///
/// # Panics
///
/// Panics if any invalid node name is given.
///
/// # See also
///
/// - [`get_sub_graph`](Self::get_sub_graph) for a fallible version.
pub fn sub_graph(&self, name: impl AsRef<str>) -> &RenderGraph {
self.sub_graphs
.get(name.as_ref())
.unwrap_or_else(|| panic!("Node {} not found in sub_graph", name.as_ref()))
}
/// Retrieves the sub graph corresponding to the `name` mutably.
///
/// # Panics
///
/// Panics if any invalid node name is given.
///
/// # See also
///
/// - [`get_sub_graph_mut`](Self::get_sub_graph_mut) for a fallible version.
pub fn sub_graph_mut(&mut self, name: impl AsRef<str>) -> &mut RenderGraph {
self.sub_graphs
.get_mut(name.as_ref())
.unwrap_or_else(|| panic!("Node {} not found in sub_graph", name.as_ref()))
}
} }
impl Debug for RenderGraph { impl Debug for RenderGraph {
@ -635,7 +683,7 @@ mod tests {
}, },
renderer::RenderContext, renderer::RenderContext,
}; };
use bevy_ecs::world::World; use bevy_ecs::world::{FromWorld, World};
use bevy_utils::HashSet; use bevy_utils::HashSet;
#[derive(Debug)] #[derive(Debug)]
@ -676,6 +724,22 @@ mod tests {
} }
} }
fn input_nodes(name: &'static str, graph: &RenderGraph) -> HashSet<NodeId> {
graph
.iter_node_inputs(name)
.unwrap()
.map(|(_edge, node)| node.id)
.collect::<HashSet<NodeId>>()
}
fn output_nodes(name: &'static str, graph: &RenderGraph) -> HashSet<NodeId> {
graph
.iter_node_outputs(name)
.unwrap()
.map(|(_edge, node)| node.id)
.collect::<HashSet<NodeId>>()
}
#[test] #[test]
fn test_graph_edges() { fn test_graph_edges() {
let mut graph = RenderGraph::default(); let mut graph = RenderGraph::default();
@ -688,22 +752,6 @@ mod tests {
graph.add_node_edge("B", "C"); graph.add_node_edge("B", "C");
graph.add_slot_edge("C", 0, "D", 0); graph.add_slot_edge("C", 0, "D", 0);
fn input_nodes(name: &'static str, graph: &RenderGraph) -> HashSet<NodeId> {
graph
.iter_node_inputs(name)
.unwrap()
.map(|(_edge, node)| node.id)
.collect::<HashSet<NodeId>>()
}
fn output_nodes(name: &'static str, graph: &RenderGraph) -> HashSet<NodeId> {
graph
.iter_node_outputs(name)
.unwrap()
.map(|(_edge, node)| node.id)
.collect::<HashSet<NodeId>>()
}
assert!(input_nodes("A", &graph).is_empty(), "A has no inputs"); assert!(input_nodes("A", &graph).is_empty(), "A has no inputs");
assert!( assert!(
output_nodes("A", &graph) == HashSet::from_iter(vec![c_id]), output_nodes("A", &graph) == HashSet::from_iter(vec![c_id]),
@ -803,4 +851,48 @@ mod tests {
"Adding to a duplicate edge should return an error" "Adding to a duplicate edge should return an error"
); );
} }
#[test]
fn test_add_node_edges() {
struct SimpleNode;
impl Node for SimpleNode {
fn run(
&self,
_graph: &mut RenderGraphContext,
_render_context: &mut RenderContext,
_world: &World,
) -> Result<(), NodeRunError> {
Ok(())
}
}
impl FromWorld for SimpleNode {
fn from_world(_world: &mut World) -> Self {
Self
}
}
let mut graph = RenderGraph::default();
let a_id = graph.add_node("A", SimpleNode);
let b_id = graph.add_node("B", SimpleNode);
let c_id = graph.add_node("C", SimpleNode);
graph.add_node_edges(&["A", "B", "C"]);
assert!(
output_nodes("A", &graph) == HashSet::from_iter(vec![b_id]),
"A -> B"
);
assert!(
input_nodes("B", &graph) == HashSet::from_iter(vec![a_id]),
"A -> B"
);
assert!(
output_nodes("B", &graph) == HashSet::from_iter(vec![c_id]),
"B -> C"
);
assert!(
input_nodes("C", &graph) == HashSet::from_iter(vec![b_id]),
"B -> C"
);
}
} }

View file

@ -1,9 +1,11 @@
mod app;
mod context; mod context;
mod edge; mod edge;
mod graph; mod graph;
mod node; mod node;
mod node_slot; mod node_slot;
pub use app::*;
pub use context::*; pub use context::*;
pub use edge::*; pub use edge::*;
pub use graph::*; pub use graph::*;

View file

@ -15,7 +15,7 @@ use bevy::{
extract_component::{ extract_component::{
ComponentUniforms, ExtractComponent, ExtractComponentPlugin, UniformComponentPlugin, ComponentUniforms, ExtractComponent, ExtractComponentPlugin, UniformComponentPlugin,
}, },
render_graph::{Node, NodeRunError, RenderGraph, RenderGraphContext}, render_graph::{Node, NodeRunError, RenderGraphApp, RenderGraphContext},
render_resource::{ render_resource::{
BindGroupDescriptor, BindGroupEntry, BindGroupLayout, BindGroupLayoutDescriptor, BindGroupDescriptor, BindGroupEntry, BindGroupLayout, BindGroupLayoutDescriptor,
BindGroupLayoutEntry, BindingResource, BindingType, CachedRenderPipelineId, BindGroupLayoutEntry, BindingResource, BindingType, CachedRenderPipelineId,
@ -53,7 +53,8 @@ impl Plugin for PostProcessPlugin {
// be extracted to the render world every frame. // be extracted to the render world every frame.
// This makes it possible to control the effect from the main world. // This makes it possible to control the effect from the main world.
// This plugin will take care of extracting it automatically. // This plugin will take care of extracting it automatically.
// It's important to derive [`ExtractComponent`] on [`PostProcessingSettings`] for this plugin to work correctly. // It's important to derive [`ExtractComponent`] on [`PostProcessingSettings`]
// for this plugin to work correctly.
.add_plugin(ExtractComponentPlugin::<PostProcessSettings>::default()) .add_plugin(ExtractComponentPlugin::<PostProcessSettings>::default())
// The settings will also be the data used in the shader. // The settings will also be the data used in the shader.
// This plugin will prepare the component for the GPU by creating a uniform buffer // This plugin will prepare the component for the GPU by creating a uniform buffer
@ -65,38 +66,35 @@ impl Plugin for PostProcessPlugin {
return; return;
}; };
// Initialize the pipeline render_app
render_app.init_resource::<PostProcessPipeline>(); // Initialize the pipeline
.init_resource::<PostProcessPipeline>()
// Bevy's renderer uses a render graph which is a collection of nodes in a directed acyclic graph. // 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 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. // 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 // Each node can execute arbitrary work, but it generally runs at least one render pass.
// you need to extract it manually or with the plugin like above. // 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.
// Create the node with the render world // Add a [`Node`] to the [`RenderGraph`]
let node = PostProcessNode::new(&mut render_app.world); // The Node needs to impl FromWorld
.add_render_graph_node::<PostProcessNode>(
// Get the render graph for the entire app // Specifiy the name of the graph, in this case we want the graph for 3d
let mut graph = render_app.world.resource_mut::<RenderGraph>(); core_3d::graph::NAME,
// It also needs the name of the node
// Get the render graph for 3d cameras/views PostProcessNode::NAME,
let core_3d_graph = graph.get_sub_graph_mut(core_3d::graph::NAME).unwrap(); )
.add_render_graph_edges(
// Register the post process node in the 3d render graph core_3d::graph::NAME,
core_3d_graph.add_node(PostProcessNode::NAME, node); // Specify the node ordering.
// This will automatically create all required node edges to enforce the given ordering.
// We now need to add an edge between our node and the nodes from bevy &[
// to make sure our node is ordered correctly relative to other nodes. core_3d::graph::node::TONEMAPPING,
// PostProcessNode::NAME,
// Here we want our effect to run after tonemapping and before the end of the main pass post processing core_3d::graph::node::END_MAIN_PASS_POST_PROCESSING,
core_3d_graph.add_node_edge(core_3d::graph::node::TONEMAPPING, PostProcessNode::NAME); ],
core_3d_graph.add_node_edge( );
PostProcessNode::NAME,
core_3d::graph::node::END_MAIN_PASS_POST_PROCESSING,
);
} }
} }
@ -109,8 +107,10 @@ struct PostProcessNode {
impl PostProcessNode { impl PostProcessNode {
pub const NAME: &str = "post_process"; pub const NAME: &str = "post_process";
}
fn new(world: &mut World) -> Self { impl FromWorld for PostProcessNode {
fn from_world(world: &mut World) -> Self {
Self { Self {
query: QueryState::new(world), query: QueryState::new(world),
} }