From 614de3019c28e819a0b1beb154d29e89bad8ca69 Mon Sep 17 00:00:00 2001 From: IceSentry Date: Mon, 3 Apr 2023 20:50:22 -0400 Subject: [PATCH] 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 --- crates/bevy_core_pipeline/src/bloom/mod.rs | 64 ++++----- .../src/contrast_adaptive_sharpening/mod.rs | 58 ++++---- .../src/contrast_adaptive_sharpening/node.rs | 4 +- crates/bevy_core_pipeline/src/core_2d/mod.rs | 11 +- crates/bevy_core_pipeline/src/core_3d/mod.rs | 18 +-- crates/bevy_core_pipeline/src/fxaa/mod.rs | 53 +++----- crates/bevy_core_pipeline/src/fxaa/node.rs | 4 +- crates/bevy_core_pipeline/src/taa/mod.rs | 34 ++--- crates/bevy_render/src/render_graph/app.rs | 73 ++++++++++ crates/bevy_render/src/render_graph/graph.rs | 126 +++++++++++++++--- crates/bevy_render/src/render_graph/mod.rs | 2 + examples/shader/post_process_pass.rs | 70 +++++----- 12 files changed, 318 insertions(+), 199 deletions(-) create mode 100644 crates/bevy_render/src/render_graph/app.rs diff --git a/crates/bevy_core_pipeline/src/bloom/mod.rs b/crates/bevy_core_pipeline/src/bloom/mod.rs index e408480b52..7125a0510c 100644 --- a/crates/bevy_core_pipeline/src/bloom/mod.rs +++ b/crates/bevy_core_pipeline/src/bloom/mod.rs @@ -16,7 +16,7 @@ use bevy_render::{ ComponentUniforms, DynamicUniformIndex, ExtractComponentPlugin, UniformComponentPlugin, }, prelude::Color, - render_graph::{Node, NodeRunError, RenderGraph, RenderGraphContext}, + render_graph::{Node, NodeRunError, RenderGraphApp, RenderGraphContext}, render_resource::*, renderer::{RenderContext, RenderDevice}, texture::{CachedTexture, TextureCache}, @@ -71,45 +71,27 @@ impl Plugin for BloomPlugin { prepare_upsampling_pipeline.in_set(RenderSet::Prepare), queue_bloom_bind_groups.in_set(RenderSet::Queue), ), + ) + // Add bloom to the 3d render graph + .add_render_graph_node::(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::(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::(); - 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::(); - 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 { - pub fn new(world: &mut World) -> Self { +impl FromWorld for BloomNode { + fn from_world(world: &mut World) -> Self { Self { view_query: QueryState::new(world), } diff --git a/crates/bevy_core_pipeline/src/contrast_adaptive_sharpening/mod.rs b/crates/bevy_core_pipeline/src/contrast_adaptive_sharpening/mod.rs index 1f189f15aa..973b4cbee5 100644 --- a/crates/bevy_core_pipeline/src/contrast_adaptive_sharpening/mod.rs +++ b/crates/bevy_core_pipeline/src/contrast_adaptive_sharpening/mod.rs @@ -6,7 +6,7 @@ use bevy_reflect::{Reflect, TypeUuid}; use bevy_render::{ extract_component::{ExtractComponent, ExtractComponentPlugin, UniformComponentPlugin}, prelude::Camera, - render_graph::RenderGraph, + render_graph::RenderGraphApp, render_resource::*, renderer::RenderDevice, texture::BevyDefault, @@ -114,47 +114,47 @@ impl Plugin for CASPlugin { render_app .init_resource::() .init_resource::>() - .add_systems(Render, prepare_cas_pipelines.in_set(RenderSet::Prepare)); - { - let cas_node = CASNode::new(&mut render_app.world); - let mut binding = render_app.world.resource_mut::(); - let graph = binding.get_sub_graph_mut(core_3d::graph::NAME).unwrap(); - - graph.add_node(core_3d::graph::node::CONTRAST_ADAPTIVE_SHARPENING, cas_node); - - graph.add_node_edge( + .add_systems(Render, prepare_cas_pipelines.in_set(RenderSet::Prepare)) + // 3d + .add_render_graph_node::( + core_3d::graph::NAME, + core_3d::graph::node::CONTRAST_ADAPTIVE_SHARPENING, + ) + .add_render_graph_edge( + core_3d::graph::NAME, core_3d::graph::node::TONEMAPPING, 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::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::END_MAIN_PASS_POST_PROCESSING, - ); - } - { - let cas_node = CASNode::new(&mut render_app.world); - let mut binding = render_app.world.resource_mut::(); - let graph = binding.get_sub_graph_mut(core_2d::graph::NAME).unwrap(); - - graph.add_node(core_2d::graph::node::CONTRAST_ADAPTIVE_SHARPENING, cas_node); - - graph.add_node_edge( + ) + // 2d + .add_render_graph_node::( + core_2d::graph::NAME, + core_2d::graph::node::CONTRAST_ADAPTIVE_SHARPENING, + ) + .add_render_graph_edge( + core_2d::graph::NAME, core_2d::graph::node::TONEMAPPING, 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::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::END_MAIN_PASS_POST_PROCESSING, ); - } } } diff --git a/crates/bevy_core_pipeline/src/contrast_adaptive_sharpening/node.rs b/crates/bevy_core_pipeline/src/contrast_adaptive_sharpening/node.rs index 7a459cfee2..c49d876f22 100644 --- a/crates/bevy_core_pipeline/src/contrast_adaptive_sharpening/node.rs +++ b/crates/bevy_core_pipeline/src/contrast_adaptive_sharpening/node.rs @@ -28,8 +28,8 @@ pub struct CASNode { cached_bind_group: Mutex>, } -impl CASNode { - pub fn new(world: &mut World) -> Self { +impl FromWorld for CASNode { + fn from_world(world: &mut World) -> Self { Self { query: QueryState::new(world), cached_bind_group: Mutex::new(None), diff --git a/crates/bevy_core_pipeline/src/core_2d/mod.rs b/crates/bevy_core_pipeline/src/core_2d/mod.rs index 5a2250696d..11843e149d 100644 --- a/crates/bevy_core_pipeline/src/core_2d/mod.rs +++ b/crates/bevy_core_pipeline/src/core_2d/mod.rs @@ -74,15 +74,14 @@ impl Plugin for Core2dPlugin { 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::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::END_MAIN_PASS_POST_PROCESSING, - ); - draw_2d_graph.add_node_edge( - graph::node::END_MAIN_PASS_POST_PROCESSING, graph::node::UPSCALING, - ); + ]); + graph.add_sub_graph(graph::NAME, draw_2d_graph); } } diff --git a/crates/bevy_core_pipeline/src/core_3d/mod.rs b/crates/bevy_core_pipeline/src/core_3d/mod.rs index 4a76b710d7..d4f86ce562 100644 --- a/crates/bevy_core_pipeline/src/core_3d/mod.rs +++ b/crates/bevy_core_pipeline/src/core_3d/mod.rs @@ -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::UPSCALING, upscaling); - draw_3d_graph.add_node_edge(graph::node::PREPASS, graph::node::START_MAIN_PASS); - draw_3d_graph.add_node_edge(graph::node::START_MAIN_PASS, graph::node::MAIN_OPAQUE_PASS); - draw_3d_graph.add_node_edge( + draw_3d_graph.add_node_edges(&[ + graph::node::PREPASS, + graph::node::START_MAIN_PASS, graph::node::MAIN_OPAQUE_PASS, graph::node::MAIN_TRANSPARENT_PASS, - ); - draw_3d_graph.add_node_edge( - graph::node::MAIN_TRANSPARENT_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::END_MAIN_PASS_POST_PROCESSING, - ); - draw_3d_graph.add_node_edge( - graph::node::END_MAIN_PASS_POST_PROCESSING, graph::node::UPSCALING, - ); + ]); + graph.add_sub_graph(graph::NAME, draw_3d_graph); } } diff --git a/crates/bevy_core_pipeline/src/fxaa/mod.rs b/crates/bevy_core_pipeline/src/fxaa/mod.rs index 6c4181d1f4..4342e61983 100644 --- a/crates/bevy_core_pipeline/src/fxaa/mod.rs +++ b/crates/bevy_core_pipeline/src/fxaa/mod.rs @@ -9,7 +9,7 @@ use bevy_reflect::{ use bevy_render::{ extract_component::{ExtractComponent, ExtractComponentPlugin}, prelude::Camera, - render_graph::RenderGraph, + render_graph::RenderGraphApp, render_resource::*, renderer::RenderDevice, texture::BevyDefault, @@ -90,40 +90,25 @@ impl Plugin for FxaaPlugin { render_app .init_resource::() .init_resource::>() - .add_systems(Render, prepare_fxaa_pipelines.in_set(RenderSet::Prepare)); - - { - let fxaa_node = FxaaNode::new(&mut render_app.world); - let mut binding = render_app.world.resource_mut::(); - let graph = binding.get_sub_graph_mut(core_3d::graph::NAME).unwrap(); - - graph.add_node(core_3d::graph::node::FXAA, fxaa_node); - - graph.add_node_edge( - core_3d::graph::node::TONEMAPPING, - core_3d::graph::node::FXAA, + .add_systems(Render, prepare_fxaa_pipelines.in_set(RenderSet::Prepare)) + .add_render_graph_node::(core_3d::graph::NAME, core_3d::graph::node::FXAA) + .add_render_graph_edges( + core_3d::graph::NAME, + &[ + core_3d::graph::node::TONEMAPPING, + core_3d::graph::node::FXAA, + core_3d::graph::node::END_MAIN_PASS_POST_PROCESSING, + ], + ) + .add_render_graph_node::(core_2d::graph::NAME, core_2d::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::(); - 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, - ); - } } } diff --git a/crates/bevy_core_pipeline/src/fxaa/node.rs b/crates/bevy_core_pipeline/src/fxaa/node.rs index 71d66ca27a..2e0aeba454 100644 --- a/crates/bevy_core_pipeline/src/fxaa/node.rs +++ b/crates/bevy_core_pipeline/src/fxaa/node.rs @@ -27,8 +27,8 @@ pub struct FxaaNode { cached_texture_bind_group: Mutex>, } -impl FxaaNode { - pub fn new(world: &mut World) -> Self { +impl FromWorld for FxaaNode { + fn from_world(world: &mut World) -> Self { Self { query: QueryState::new(world), cached_texture_bind_group: Mutex::new(None), diff --git a/crates/bevy_core_pipeline/src/taa/mod.rs b/crates/bevy_core_pipeline/src/taa/mod.rs index 19d7d56347..6b2e8730c4 100644 --- a/crates/bevy_core_pipeline/src/taa/mod.rs +++ b/crates/bevy_core_pipeline/src/taa/mod.rs @@ -1,4 +1,5 @@ use crate::{ + core_3d, fullscreen_vertex_shader::fullscreen_shader_vertex_state, prelude::Camera3d, prepass::{DepthPrepass, MotionVectorPrepass, ViewPrepassTextures}, @@ -18,7 +19,7 @@ use bevy_reflect::{Reflect, TypeUuid}; use bevy_render::{ camera::{ExtractedCamera, TemporalJitter}, prelude::{Camera, Projection}, - render_graph::{Node, NodeRunError, RenderGraph, RenderGraphContext}, + render_graph::{Node, NodeRunError, RenderGraphApp, RenderGraphContext}, render_resource::{ BindGroupDescriptor, BindGroupEntry, BindGroupLayout, BindGroupLayoutDescriptor, BindGroupLayoutEntry, BindingResource, BindingType, CachedRenderPipelineId, @@ -71,24 +72,17 @@ impl Plugin for TemporalAntiAliasPlugin { prepare_taa_history_textures.in_set(RenderSet::Prepare), prepare_taa_pipelines.in_set(RenderSet::Prepare), ), + ) + .add_render_graph_node::(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::(); - 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 { - fn new(world: &mut World) -> Self { +impl FromWorld for TAANode { + fn from_world(world: &mut World) -> Self { Self { view_query: QueryState::new(world), } diff --git a/crates/bevy_render/src/render_graph/app.rs b/crates/bevy_render/src/render_graph/app.rs new file mode 100644 index 0000000000..7cc13fb3c4 --- /dev/null +++ b/crates/bevy_render/src/render_graph/app.rs @@ -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( + &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( + &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::().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::().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::().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 + } +} diff --git a/crates/bevy_render/src/render_graph/graph.rs b/crates/bevy_render/src/render_graph/graph.rs index e034f345f4..87fc063ee1 100644 --- a/crates/bevy_render/src/render_graph/graph.rs +++ b/crates/bevy_render/src/render_graph/graph.rs @@ -119,6 +119,24 @@ impl RenderGraph { 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. /// If the name is does not exist, nothing happens. pub fn remove_node( @@ -583,6 +601,36 @@ impl RenderGraph { pub fn get_sub_graph_mut(&mut self, name: impl AsRef) -> Option<&mut RenderGraph> { 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) -> &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) -> &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 { @@ -635,7 +683,7 @@ mod tests { }, renderer::RenderContext, }; - use bevy_ecs::world::World; + use bevy_ecs::world::{FromWorld, World}; use bevy_utils::HashSet; #[derive(Debug)] @@ -676,6 +724,22 @@ mod tests { } } + fn input_nodes(name: &'static str, graph: &RenderGraph) -> HashSet { + graph + .iter_node_inputs(name) + .unwrap() + .map(|(_edge, node)| node.id) + .collect::>() + } + + fn output_nodes(name: &'static str, graph: &RenderGraph) -> HashSet { + graph + .iter_node_outputs(name) + .unwrap() + .map(|(_edge, node)| node.id) + .collect::>() + } + #[test] fn test_graph_edges() { let mut graph = RenderGraph::default(); @@ -688,22 +752,6 @@ mod tests { graph.add_node_edge("B", "C"); graph.add_slot_edge("C", 0, "D", 0); - fn input_nodes(name: &'static str, graph: &RenderGraph) -> HashSet { - graph - .iter_node_inputs(name) - .unwrap() - .map(|(_edge, node)| node.id) - .collect::>() - } - - fn output_nodes(name: &'static str, graph: &RenderGraph) -> HashSet { - graph - .iter_node_outputs(name) - .unwrap() - .map(|(_edge, node)| node.id) - .collect::>() - } - assert!(input_nodes("A", &graph).is_empty(), "A has no inputs"); assert!( 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" ); } + + #[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" + ); + } } diff --git a/crates/bevy_render/src/render_graph/mod.rs b/crates/bevy_render/src/render_graph/mod.rs index 9f5a5b7c71..8ba77ec245 100644 --- a/crates/bevy_render/src/render_graph/mod.rs +++ b/crates/bevy_render/src/render_graph/mod.rs @@ -1,9 +1,11 @@ +mod app; mod context; mod edge; mod graph; mod node; mod node_slot; +pub use app::*; pub use context::*; pub use edge::*; pub use graph::*; diff --git a/examples/shader/post_process_pass.rs b/examples/shader/post_process_pass.rs index cc4e3275cb..8080a5ab00 100644 --- a/examples/shader/post_process_pass.rs +++ b/examples/shader/post_process_pass.rs @@ -15,7 +15,7 @@ use bevy::{ extract_component::{ ComponentUniforms, ExtractComponent, ExtractComponentPlugin, UniformComponentPlugin, }, - render_graph::{Node, NodeRunError, RenderGraph, RenderGraphContext}, + render_graph::{Node, NodeRunError, RenderGraphApp, RenderGraphContext}, render_resource::{ BindGroupDescriptor, BindGroupEntry, BindGroupLayout, BindGroupLayoutDescriptor, BindGroupLayoutEntry, BindingResource, BindingType, CachedRenderPipelineId, @@ -53,7 +53,8 @@ impl Plugin for PostProcessPlugin { // be extracted to the render world every frame. // This makes it possible to control the effect from the main world. // 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::::default()) // 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 @@ -65,38 +66,35 @@ impl Plugin for PostProcessPlugin { return; }; - // Initialize the pipeline - render_app.init_resource::(); - - // 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. - - // Create the node with the render world - let node = PostProcessNode::new(&mut render_app.world); - - // Get the render graph for the entire app - let mut graph = render_app.world.resource_mut::(); - - // Get the render graph for 3d cameras/views - let core_3d_graph = graph.get_sub_graph_mut(core_3d::graph::NAME).unwrap(); - - // Register the post process node in the 3d render graph - core_3d_graph.add_node(PostProcessNode::NAME, node); - - // 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. - // - // Here we want our effect to run after tonemapping and before the end of the 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, - ); + render_app + // Initialize the pipeline + .init_resource::() + // 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. + // Add a [`Node`] to the [`RenderGraph`] + // The Node needs to impl FromWorld + .add_render_graph_node::( + // Specifiy the name of the graph, in this case we want the graph for 3d + core_3d::graph::NAME, + // It also needs the name of the node + PostProcessNode::NAME, + ) + .add_render_graph_edges( + core_3d::graph::NAME, + // Specify the node ordering. + // This will automatically create all required node edges to enforce the given ordering. + &[ + core_3d::graph::node::TONEMAPPING, + PostProcessNode::NAME, + core_3d::graph::node::END_MAIN_PASS_POST_PROCESSING, + ], + ); } } @@ -109,8 +107,10 @@ struct PostProcessNode { impl PostProcessNode { pub const NAME: &str = "post_process"; +} - fn new(world: &mut World) -> Self { +impl FromWorld for PostProcessNode { + fn from_world(world: &mut World) -> Self { Self { query: QueryState::new(world), }