From bed44dffb7382479c585dae95983e6b78083d9da Mon Sep 17 00:00:00 2001 From: icesentry Date: Tue, 28 Mar 2023 14:57:05 -0400 Subject: [PATCH] render graph utils --- crates/bevy_core_pipeline/src/bloom/mod.rs | 56 +++----- 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 | 41 +++--- crates/bevy_core_pipeline/src/fxaa/node.rs | 4 +- crates/bevy_core_pipeline/src/taa/mod.rs | 30 ++--- crates/bevy_render/src/render_graph/graph.rs | 126 +++++++++++++++--- .../bevy_render/src/render_graph/helpers.rs | 22 +++ crates/bevy_render/src/render_graph/mod.rs | 2 + examples/shader/post_process_pass.rs | 45 +++---- 10 files changed, 214 insertions(+), 141 deletions(-) create mode 100644 crates/bevy_render/src/render_graph/helpers.rs diff --git a/crates/bevy_core_pipeline/src/bloom/mod.rs b/crates/bevy_core_pipeline/src/bloom/mod.rs index e408480b52e53..c16de41be4345 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::{add_node, Node, NodeRunError, RenderGraphContext}, render_resource::*, renderer::{RenderContext, RenderDevice}, texture::{CachedTexture, TextureCache}, @@ -74,42 +74,28 @@ impl Plugin for BloomPlugin { ); // 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( + add_node::( + render_app, + core_3d::graph::NAME, + core_3d::graph::node::BLOOM, + &[ + core_3d::graph::node::END_MAIN_PASS, core_3d::graph::node::BLOOM, - crate::core_3d::graph::node::TONEMAPPING, - ); - } + 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( + add_node::( + render_app, + core_2d::graph::NAME, + core_2d::graph::node::BLOOM, + &[ + core_2d::graph::node::MAIN_PASS, core_2d::graph::node::BLOOM, - crate::core_2d::graph::node::TONEMAPPING, - ); - } + core_2d::graph::node::TONEMAPPING, + ], + ); } } @@ -126,8 +112,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/core_2d/mod.rs b/crates/bevy_core_pipeline/src/core_2d/mod.rs index 5f866715fbb57..7097712b72409 100644 --- a/crates/bevy_core_pipeline/src/core_2d/mod.rs +++ b/crates/bevy_core_pipeline/src/core_2d/mod.rs @@ -73,15 +73,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 4f1f807477a26..1da4f66b8b25b 100644 --- a/crates/bevy_core_pipeline/src/core_3d/mod.rs +++ b/crates/bevy_core_pipeline/src/core_3d/mod.rs @@ -103,25 +103,13 @@ 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( - graph::node::MAIN_OPAQUE_PASS, - graph::node::MAIN_TRANSPARENT_PASS, - ); - draw_3d_graph.add_node_edge( - graph::node::MAIN_TRANSPARENT_PASS, + draw_3d_graph.add_node_edges(&[ 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 6c4181d1f4350..07e99d74f7619 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::add_node, render_resource::*, renderer::RenderDevice, texture::BevyDefault, @@ -92,38 +92,27 @@ impl Plugin for FxaaPlugin { .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( + add_node::( + render_app, + core_3d::graph::NAME, + core_3d::graph::node::FXAA, + &[ core_3d::graph::node::TONEMAPPING, core_3d::graph::node::FXAA, - ); - 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( + add_node::( + render_app, + core_2d::graph::NAME, + core_2d::graph::node::FXAA, + &[ 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 71d66ca27a34f..2e0aeba454418 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 19d7d56347ef2..2e6e969b20d69 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::{add_node, Node, NodeRunError, RenderGraphContext}, render_resource::{ BindGroupDescriptor, BindGroupEntry, BindGroupLayout, BindGroupLayoutDescriptor, BindGroupLayoutEntry, BindingResource, BindingType, CachedRenderPipelineId, @@ -73,21 +74,16 @@ impl Plugin for TemporalAntiAliasPlugin { ), ); - 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, + add_node::( + render_app, + core_3d::graph::NAME, 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, + &[ + core_3d::graph::node::END_MAIN_PASS, + draw_3d_graph::node::TAA, + core_3d::graph::node::BLOOM, + core_3d::graph::node::TONEMAPPING, + ], ); } } @@ -168,8 +164,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/graph.rs b/crates/bevy_render/src/render_graph/graph.rs index e034f345f4e67..95433a5862db6 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]), + "B -> C" + ); + 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/helpers.rs b/crates/bevy_render/src/render_graph/helpers.rs new file mode 100644 index 0000000000000..250409c32dc9a --- /dev/null +++ b/crates/bevy_render/src/render_graph/helpers.rs @@ -0,0 +1,22 @@ +use bevy_app::App; +use bevy_ecs::world::FromWorld; + +use super::{Node, RenderGraph}; + +/// Utility function to add a [`Node`] to the [`RenderGraph`] +/// * Create the [`Node`] using the [`FromWorld`] implementation +/// * Add it to the graph +/// * Automatically add the required node edges based on the given ordering +pub fn add_node( + render_app: &mut App, + sub_graph_name: &'static str, + node_name: &'static str, + edges: &[&'static str], +) { + let node = T::from_world(&mut render_app.world); + let mut render_graph = render_app.world.resource_mut::(); + + let graph = render_graph.get_sub_graph_mut(sub_graph_name).unwrap(); + graph.add_node(node_name, node); + graph.add_node_edges(edges); +} diff --git a/crates/bevy_render/src/render_graph/mod.rs b/crates/bevy_render/src/render_graph/mod.rs index 9f5a5b7c712fb..32cc1b008804d 100644 --- a/crates/bevy_render/src/render_graph/mod.rs +++ b/crates/bevy_render/src/render_graph/mod.rs @@ -1,12 +1,14 @@ mod context; mod edge; mod graph; +mod helpers; mod node; mod node_slot; pub use context::*; pub use edge::*; pub use graph::*; +pub use helpers::*; pub use node::*; pub use node_slot::*; diff --git a/examples/shader/post_process_pass.rs b/examples/shader/post_process_pass.rs index cc4e3275cbb55..0e70948924f12 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::{add_node, Node, NodeRunError, 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 @@ -70,32 +71,28 @@ impl Plugin for PostProcessPlugin { // 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. + // 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( + // Utility function to add a [`Node`] to the [`RenderGraph`] + // The Node needs to impl FromWorld + add_node::( + render_app, + // 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, - core_3d::graph::node::END_MAIN_PASS_POST_PROCESSING, + // 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 +106,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), }