Add ViewNode to simplify render node management (#8118)

# Objective

- When writing render nodes that need a view, you always need to define
a `Query` on the associated view and make sure to update it manually and
query it manually. This is verbose and error prone.

## Solution

- Introduce a new `ViewNode` trait and `ViewNodeRunner` `Node` that will
take care of managing the associated view query automatically.
- The trait is currently a passthrough of the `Node` trait. So it still
has the update/run with all the same data passed in.
- The `ViewNodeRunner` is the actual node that is added to the render
graph and it contains the custom node. This is necessary because it's
the one that takes care of updating the node.

---

## Changelog

- Add `ViewNode`
- Add `ViewNodeRunner`

## Notes

Currently, this only handles the view query, but it could probably have
a ReadOnlySystemState that would also simplify querying all the readonly
resources that most render nodes currently query manually. The issue is
that I don't know how to do that without a `&mut self`.

At first, I tried making this a default feature of all `Node`, but I
kept hitting errors related to traits and generics and stuff I'm not
super comfortable with. This implementations is much simpler and keeps
the default Node behaviour so isn't a breaking change

## Reviewer Notes

The PR looks quite big, but the core of the PR is the changes in
`render_graph/node.rs`. Every other change is simply updating existing
nodes to use this new feature.

## Open questions

~~- Naming is not final, I'm opened to anything. I named it
ViewQueryNode because it's a node with a managed Query on a View.~~
~~- What to do when the query fails? All nodes using this pattern
currently just `return Ok(())` when it fails, so I chose that, but
should it be more flexible?~~
~~- Is the ViewQueryFilter actually necessary? All view queries run on
the entity that is already guaranteed to be a view. Filtering won't do
much, but maybe someone wants to control an effect with the presence of
a component instead of a flag.~~
~~- What to do with Nodes that are empty struct? Implementing
`FromWorld` is pretty verbose but not implementing it means there's 2
ways to create a `ViewNodeRunner` which seems less ideal. This is an
issue now because most node simply existed to hold the query, but now
that they don't hold the query state we are left with a bunch of empty
structs.~~
- Should we have a `RenderGraphApp::add_render_graph_view_node()`, this
isn't necessary, but it could make the code a bit shorter.

---------

Co-authored-by: Carter Anderson <mcanders1@gmail.com>
This commit is contained in:
IceSentry 2023-05-08 15:42:23 -04:00 committed by GitHub
parent fe57b9f744
commit 613b5a69ae
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 230 additions and 274 deletions

View File

@ -10,7 +10,7 @@ use crate::{
}; };
use bevy_app::{App, Plugin}; use bevy_app::{App, Plugin};
use bevy_asset::{load_internal_asset, HandleUntyped}; use bevy_asset::{load_internal_asset, HandleUntyped};
use bevy_ecs::prelude::*; use bevy_ecs::{prelude::*, query::QueryItem};
use bevy_math::UVec2; use bevy_math::UVec2;
use bevy_reflect::TypeUuid; use bevy_reflect::TypeUuid;
use bevy_render::{ use bevy_render::{
@ -19,7 +19,7 @@ use bevy_render::{
ComponentUniforms, DynamicUniformIndex, ExtractComponentPlugin, UniformComponentPlugin, ComponentUniforms, DynamicUniformIndex, ExtractComponentPlugin, UniformComponentPlugin,
}, },
prelude::Color, prelude::Color,
render_graph::{Node, NodeRunError, RenderGraphApp, RenderGraphContext}, render_graph::{NodeRunError, RenderGraphApp, RenderGraphContext, ViewNode, ViewNodeRunner},
render_resource::*, render_resource::*,
renderer::{RenderContext, RenderDevice}, renderer::{RenderContext, RenderDevice},
texture::{CachedTexture, TextureCache}, texture::{CachedTexture, TextureCache},
@ -73,7 +73,10 @@ impl Plugin for BloomPlugin {
), ),
) )
// Add bloom to the 3d render graph // Add bloom to the 3d render graph
.add_render_graph_node::<BloomNode>(CORE_3D, core_3d::graph::node::BLOOM) .add_render_graph_node::<ViewNodeRunner<BloomNode>>(
CORE_3D,
core_3d::graph::node::BLOOM,
)
.add_render_graph_edges( .add_render_graph_edges(
CORE_3D, CORE_3D,
&[ &[
@ -83,7 +86,10 @@ impl Plugin for BloomPlugin {
], ],
) )
// Add bloom to the 2d render graph // Add bloom to the 2d render graph
.add_render_graph_node::<BloomNode>(CORE_2D, core_2d::graph::node::BLOOM) .add_render_graph_node::<ViewNodeRunner<BloomNode>>(
CORE_2D,
core_2d::graph::node::BLOOM,
)
.add_render_graph_edges( .add_render_graph_edges(
CORE_2D, CORE_2D,
&[ &[
@ -106,8 +112,10 @@ impl Plugin for BloomPlugin {
} }
} }
pub struct BloomNode { #[derive(Default)]
view_query: QueryState<( struct BloomNode;
impl ViewNode for BloomNode {
type ViewQuery = (
&'static ExtractedCamera, &'static ExtractedCamera,
&'static ViewTarget, &'static ViewTarget,
&'static BloomTexture, &'static BloomTexture,
@ -116,36 +124,16 @@ pub struct BloomNode {
&'static BloomSettings, &'static BloomSettings,
&'static UpsamplingPipelineIds, &'static UpsamplingPipelineIds,
&'static BloomDownsamplingPipelineIds, &'static BloomDownsamplingPipelineIds,
)>, );
}
impl FromWorld for BloomNode {
fn from_world(world: &mut World) -> Self {
Self {
view_query: QueryState::new(world),
}
}
}
impl Node for BloomNode {
fn update(&mut self, world: &mut World) {
self.view_query.update_archetypes(world);
}
// Atypically for a post-processing effect, we do not need to // Atypically for a post-processing effect, we do not need to
// use a secondary texture normally provided by view_target.post_process_write(), // use a secondary texture normally provided by view_target.post_process_write(),
// instead we write into our own bloom texture and then directly back onto main. // instead we write into our own bloom texture and then directly back onto main.
fn run( fn run(
&self, &self,
graph: &mut RenderGraphContext, _graph: &mut RenderGraphContext,
render_context: &mut RenderContext, render_context: &mut RenderContext,
world: &World, (
) -> Result<(), NodeRunError> {
let downsampling_pipeline_res = world.resource::<BloomDownsamplingPipeline>();
let pipeline_cache = world.resource::<PipelineCache>();
let uniforms = world.resource::<ComponentUniforms<BloomUniforms>>();
let view_entity = graph.view_entity();
let Ok((
camera, camera,
view_target, view_target,
bloom_texture, bloom_texture,
@ -154,8 +142,12 @@ impl Node for BloomNode {
bloom_settings, bloom_settings,
upsampling_pipeline_ids, upsampling_pipeline_ids,
downsampling_pipeline_ids, downsampling_pipeline_ids,
)) = self.view_query.get_manual(world, view_entity) ): QueryItem<Self::ViewQuery>,
else { return Ok(()) }; world: &World,
) -> Result<(), NodeRunError> {
let downsampling_pipeline_res = world.resource::<BloomDownsamplingPipeline>();
let pipeline_cache = world.resource::<PipelineCache>();
let uniforms = world.resource::<ComponentUniforms<BloomUniforms>>();
let ( let (
Some(uniforms), Some(uniforms),

View File

@ -27,7 +27,7 @@ use bevy_ecs::prelude::*;
use bevy_render::{ use bevy_render::{
camera::Camera, camera::Camera,
extract_component::ExtractComponentPlugin, extract_component::ExtractComponentPlugin,
render_graph::{EmptyNode, RenderGraphApp}, render_graph::{EmptyNode, RenderGraphApp, ViewNodeRunner},
render_phase::{ render_phase::{
batch_phase_system, sort_phase_system, BatchedPhaseItem, CachedRenderPipelinePhaseItem, batch_phase_system, sort_phase_system, BatchedPhaseItem, CachedRenderPipelinePhaseItem,
DrawFunctionId, DrawFunctions, PhaseItem, RenderPhase, DrawFunctionId, DrawFunctions, PhaseItem, RenderPhase,
@ -65,24 +65,22 @@ impl Plugin for Core2dPlugin {
), ),
); );
{ use graph::node::*;
use graph::node::*; render_app
render_app .add_render_sub_graph(CORE_2D)
.add_render_sub_graph(CORE_2D) .add_render_graph_node::<MainPass2dNode>(CORE_2D, MAIN_PASS)
.add_render_graph_node::<MainPass2dNode>(CORE_2D, MAIN_PASS) .add_render_graph_node::<ViewNodeRunner<TonemappingNode>>(CORE_2D, TONEMAPPING)
.add_render_graph_node::<TonemappingNode>(CORE_2D, TONEMAPPING) .add_render_graph_node::<EmptyNode>(CORE_2D, END_MAIN_PASS_POST_PROCESSING)
.add_render_graph_node::<EmptyNode>(CORE_2D, END_MAIN_PASS_POST_PROCESSING) .add_render_graph_node::<ViewNodeRunner<UpscalingNode>>(CORE_2D, UPSCALING)
.add_render_graph_node::<UpscalingNode>(CORE_2D, UPSCALING) .add_render_graph_edges(
.add_render_graph_edges( CORE_2D,
CORE_2D, &[
&[ MAIN_PASS,
MAIN_PASS, TONEMAPPING,
TONEMAPPING, END_MAIN_PASS_POST_PROCESSING,
END_MAIN_PASS_POST_PROCESSING, UPSCALING,
UPSCALING, ],
], );
);
}
} }
} }

View File

@ -4,64 +4,46 @@ use crate::{
prepass::{DepthPrepass, MotionVectorPrepass, NormalPrepass}, prepass::{DepthPrepass, MotionVectorPrepass, NormalPrepass},
skybox::{SkyboxBindGroup, SkyboxPipelineId}, skybox::{SkyboxBindGroup, SkyboxPipelineId},
}; };
use bevy_ecs::prelude::*; use bevy_ecs::{prelude::*, query::QueryItem};
use bevy_render::{ use bevy_render::{
camera::ExtractedCamera, camera::ExtractedCamera,
render_graph::{Node, NodeRunError, RenderGraphContext}, render_graph::{NodeRunError, RenderGraphContext, ViewNode},
render_phase::RenderPhase, render_phase::RenderPhase,
render_resource::{ render_resource::{
LoadOp, Operations, PipelineCache, RenderPassDepthStencilAttachment, RenderPassDescriptor, LoadOp, Operations, PipelineCache, RenderPassDepthStencilAttachment, RenderPassDescriptor,
}, },
renderer::RenderContext, renderer::RenderContext,
view::{ExtractedView, ViewDepthTexture, ViewTarget, ViewUniformOffset}, view::{ViewDepthTexture, ViewTarget, ViewUniformOffset},
}; };
#[cfg(feature = "trace")] #[cfg(feature = "trace")]
use bevy_utils::tracing::info_span; use bevy_utils::tracing::info_span;
use super::{AlphaMask3d, Camera3dDepthLoadOp}; use super::{AlphaMask3d, Camera3dDepthLoadOp};
/// A [`Node`] that runs the [`Opaque3d`] and [`AlphaMask3d`] [`RenderPhase`]. /// A [`bevy_render::render_graph::Node`] that runs the [`Opaque3d`] and [`AlphaMask3d`] [`RenderPhase`].
pub struct MainOpaquePass3dNode { #[derive(Default)]
query: QueryState< pub struct MainOpaquePass3dNode;
( impl ViewNode for MainOpaquePass3dNode {
&'static ExtractedCamera, type ViewQuery = (
&'static RenderPhase<Opaque3d>, &'static ExtractedCamera,
&'static RenderPhase<AlphaMask3d>, &'static RenderPhase<Opaque3d>,
&'static Camera3d, &'static RenderPhase<AlphaMask3d>,
&'static ViewTarget, &'static Camera3d,
&'static ViewDepthTexture, &'static ViewTarget,
Option<&'static DepthPrepass>, &'static ViewDepthTexture,
Option<&'static NormalPrepass>, Option<&'static DepthPrepass>,
Option<&'static MotionVectorPrepass>, Option<&'static NormalPrepass>,
Option<&'static SkyboxPipelineId>, Option<&'static MotionVectorPrepass>,
Option<&'static SkyboxBindGroup>, Option<&'static SkyboxPipelineId>,
&'static ViewUniformOffset, Option<&'static SkyboxBindGroup>,
), &'static ViewUniformOffset,
With<ExtractedView>, );
>,
}
impl FromWorld for MainOpaquePass3dNode {
fn from_world(world: &mut World) -> Self {
Self {
query: world.query_filtered(),
}
}
}
impl Node for MainOpaquePass3dNode {
fn update(&mut self, world: &mut World) {
self.query.update_archetypes(world);
}
fn run( fn run(
&self, &self,
graph: &mut RenderGraphContext, graph: &mut RenderGraphContext,
render_context: &mut RenderContext, render_context: &mut RenderContext,
world: &World, (
) -> Result<(), NodeRunError> {
let view_entity = graph.view_entity();
let Ok((
camera, camera,
opaque_phase, opaque_phase,
alpha_mask_phase, alpha_mask_phase,
@ -74,11 +56,9 @@ impl Node for MainOpaquePass3dNode {
skybox_pipeline, skybox_pipeline,
skybox_bind_group, skybox_bind_group,
view_uniform_offset, view_uniform_offset,
)) = self.query.get_manual(world, view_entity) else { ): QueryItem<Self::ViewQuery>,
// No window world: &World,
return Ok(()); ) -> Result<(), NodeRunError> {
};
// Run the opaque pass, sorted front-to-back // Run the opaque pass, sorted front-to-back
// NOTE: Scoped to drop the mutable borrow of render_context // NOTE: Scoped to drop the mutable borrow of render_context
#[cfg(feature = "trace")] #[cfg(feature = "trace")]
@ -125,6 +105,8 @@ impl Node for MainOpaquePass3dNode {
render_pass.set_camera_viewport(viewport); render_pass.set_camera_viewport(viewport);
} }
let view_entity = graph.view_entity();
// Opaque draws // Opaque draws
opaque_phase.render(&mut render_pass, world, view_entity); opaque_phase.render(&mut render_pass, world, view_entity);

View File

@ -1,58 +1,35 @@
use crate::core_3d::Transparent3d; use crate::core_3d::Transparent3d;
use bevy_ecs::prelude::*; use bevy_ecs::{prelude::*, query::QueryItem};
use bevy_render::{ use bevy_render::{
camera::ExtractedCamera, camera::ExtractedCamera,
render_graph::{Node, NodeRunError, RenderGraphContext}, render_graph::{NodeRunError, RenderGraphContext, ViewNode},
render_phase::RenderPhase, render_phase::RenderPhase,
render_resource::{LoadOp, Operations, RenderPassDepthStencilAttachment, RenderPassDescriptor}, render_resource::{LoadOp, Operations, RenderPassDepthStencilAttachment, RenderPassDescriptor},
renderer::RenderContext, renderer::RenderContext,
view::{ExtractedView, ViewDepthTexture, ViewTarget}, view::{ViewDepthTexture, ViewTarget},
}; };
#[cfg(feature = "trace")] #[cfg(feature = "trace")]
use bevy_utils::tracing::info_span; use bevy_utils::tracing::info_span;
/// A [`Node`] that runs the [`Transparent3d`] [`RenderPhase`]. /// A [`bevy_render::render_graph::Node`] that runs the [`Transparent3d`] [`RenderPhase`].
pub struct MainTransparentPass3dNode { #[derive(Default)]
query: QueryState< pub struct MainTransparentPass3dNode;
(
&'static ExtractedCamera,
&'static RenderPhase<Transparent3d>,
&'static ViewTarget,
&'static ViewDepthTexture,
),
With<ExtractedView>,
>,
}
impl FromWorld for MainTransparentPass3dNode {
fn from_world(world: &mut World) -> Self {
Self {
query: world.query_filtered(),
}
}
}
impl Node for MainTransparentPass3dNode {
fn update(&mut self, world: &mut World) {
self.query.update_archetypes(world);
}
impl ViewNode for MainTransparentPass3dNode {
type ViewQuery = (
&'static ExtractedCamera,
&'static RenderPhase<Transparent3d>,
&'static ViewTarget,
&'static ViewDepthTexture,
);
fn run( fn run(
&self, &self,
graph: &mut RenderGraphContext, graph: &mut RenderGraphContext,
render_context: &mut RenderContext, render_context: &mut RenderContext,
(camera, transparent_phase, target, depth): QueryItem<Self::ViewQuery>,
world: &World, world: &World,
) -> Result<(), NodeRunError> { ) -> Result<(), NodeRunError> {
let view_entity = graph.view_entity(); let view_entity = graph.view_entity();
let Ok((
camera,
transparent_phase,
target,
depth,
)) = self.query.get_manual(world, view_entity) else {
// No window
return Ok(());
};
if !transparent_phase.items.is_empty() { if !transparent_phase.items.is_empty() {
// Run the transparent pass, sorted back-to-front // Run the transparent pass, sorted back-to-front

View File

@ -36,7 +36,7 @@ use bevy_render::{
camera::{Camera, ExtractedCamera}, camera::{Camera, ExtractedCamera},
extract_component::ExtractComponentPlugin, extract_component::ExtractComponentPlugin,
prelude::Msaa, prelude::Msaa,
render_graph::{EmptyNode, RenderGraphApp}, render_graph::{EmptyNode, RenderGraphApp, ViewNodeRunner},
render_phase::{ render_phase::{
sort_phase_system, CachedRenderPipelinePhaseItem, DrawFunctionId, DrawFunctions, PhaseItem, sort_phase_system, CachedRenderPipelinePhaseItem, DrawFunctionId, DrawFunctions, PhaseItem,
RenderPhase, RenderPhase,
@ -93,14 +93,20 @@ impl Plugin for Core3dPlugin {
use graph::node::*; use graph::node::*;
render_app render_app
.add_render_sub_graph(CORE_3D) .add_render_sub_graph(CORE_3D)
.add_render_graph_node::<PrepassNode>(CORE_3D, PREPASS) .add_render_graph_node::<ViewNodeRunner<PrepassNode>>(CORE_3D, PREPASS)
.add_render_graph_node::<EmptyNode>(CORE_3D, START_MAIN_PASS) .add_render_graph_node::<EmptyNode>(CORE_3D, START_MAIN_PASS)
.add_render_graph_node::<MainOpaquePass3dNode>(CORE_3D, MAIN_OPAQUE_PASS) .add_render_graph_node::<ViewNodeRunner<MainOpaquePass3dNode>>(
.add_render_graph_node::<MainTransparentPass3dNode>(CORE_3D, MAIN_TRANSPARENT_PASS) CORE_3D,
MAIN_OPAQUE_PASS,
)
.add_render_graph_node::<ViewNodeRunner<MainTransparentPass3dNode>>(
CORE_3D,
MAIN_TRANSPARENT_PASS,
)
.add_render_graph_node::<EmptyNode>(CORE_3D, END_MAIN_PASS) .add_render_graph_node::<EmptyNode>(CORE_3D, END_MAIN_PASS)
.add_render_graph_node::<TonemappingNode>(CORE_3D, TONEMAPPING) .add_render_graph_node::<ViewNodeRunner<TonemappingNode>>(CORE_3D, TONEMAPPING)
.add_render_graph_node::<EmptyNode>(CORE_3D, END_MAIN_PASS_POST_PROCESSING) .add_render_graph_node::<EmptyNode>(CORE_3D, END_MAIN_PASS_POST_PROCESSING)
.add_render_graph_node::<UpscalingNode>(CORE_3D, UPSCALING) .add_render_graph_node::<ViewNodeRunner<UpscalingNode>>(CORE_3D, UPSCALING)
.add_render_graph_edges( .add_render_graph_edges(
CORE_3D, CORE_3D,
&[ &[

View File

@ -14,6 +14,7 @@ use bevy_render::{
extract_component::{ExtractComponent, ExtractComponentPlugin}, extract_component::{ExtractComponent, ExtractComponentPlugin},
prelude::Camera, prelude::Camera,
render_graph::RenderGraphApp, render_graph::RenderGraphApp,
render_graph::ViewNodeRunner,
render_resource::*, render_resource::*,
renderer::RenderDevice, renderer::RenderDevice,
texture::BevyDefault, texture::BevyDefault,
@ -94,7 +95,7 @@ impl Plugin for FxaaPlugin {
render_app render_app
.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, core_3d::graph::node::FXAA) .add_render_graph_node::<ViewNodeRunner<FxaaNode>>(CORE_3D, core_3d::graph::node::FXAA)
.add_render_graph_edges( .add_render_graph_edges(
CORE_3D, CORE_3D,
&[ &[
@ -103,7 +104,7 @@ impl Plugin for FxaaPlugin {
core_3d::graph::node::END_MAIN_PASS_POST_PROCESSING, core_3d::graph::node::END_MAIN_PASS_POST_PROCESSING,
], ],
) )
.add_render_graph_node::<FxaaNode>(CORE_2D, core_2d::graph::node::FXAA) .add_render_graph_node::<ViewNodeRunner<FxaaNode>>(CORE_2D, core_2d::graph::node::FXAA)
.add_render_graph_edges( .add_render_graph_edges(
CORE_2D, CORE_2D,
&[ &[

View File

@ -2,60 +2,41 @@ use std::sync::Mutex;
use crate::fxaa::{CameraFxaaPipeline, Fxaa, FxaaPipeline}; use crate::fxaa::{CameraFxaaPipeline, Fxaa, FxaaPipeline};
use bevy_ecs::prelude::*; use bevy_ecs::prelude::*;
use bevy_ecs::query::QueryState; use bevy_ecs::query::QueryItem;
use bevy_render::{ use bevy_render::{
render_graph::{Node, NodeRunError, RenderGraphContext}, render_graph::{NodeRunError, RenderGraphContext, ViewNode},
render_resource::{ render_resource::{
BindGroup, BindGroupDescriptor, BindGroupEntry, BindingResource, FilterMode, Operations, BindGroup, BindGroupDescriptor, BindGroupEntry, BindingResource, FilterMode, Operations,
PipelineCache, RenderPassColorAttachment, RenderPassDescriptor, SamplerDescriptor, PipelineCache, RenderPassColorAttachment, RenderPassDescriptor, SamplerDescriptor,
TextureViewId, TextureViewId,
}, },
renderer::RenderContext, renderer::RenderContext,
view::{ExtractedView, ViewTarget}, view::ViewTarget,
}; };
use bevy_utils::default; use bevy_utils::default;
#[derive(Default)]
pub struct FxaaNode { pub struct FxaaNode {
query: QueryState<
(
&'static ViewTarget,
&'static CameraFxaaPipeline,
&'static Fxaa,
),
With<ExtractedView>,
>,
cached_texture_bind_group: Mutex<Option<(TextureViewId, BindGroup)>>, cached_texture_bind_group: Mutex<Option<(TextureViewId, BindGroup)>>,
} }
impl FromWorld for FxaaNode { impl ViewNode for FxaaNode {
fn from_world(world: &mut World) -> Self { type ViewQuery = (
Self { &'static ViewTarget,
query: QueryState::new(world), &'static CameraFxaaPipeline,
cached_texture_bind_group: Mutex::new(None), &'static Fxaa,
} );
}
}
impl Node for FxaaNode {
fn update(&mut self, world: &mut World) {
self.query.update_archetypes(world);
}
fn run( fn run(
&self, &self,
graph: &mut RenderGraphContext, _graph: &mut RenderGraphContext,
render_context: &mut RenderContext, render_context: &mut RenderContext,
(target, pipeline, fxaa): QueryItem<Self::ViewQuery>,
world: &World, world: &World,
) -> Result<(), NodeRunError> { ) -> Result<(), NodeRunError> {
let view_entity = graph.view_entity();
let pipeline_cache = world.resource::<PipelineCache>(); let pipeline_cache = world.resource::<PipelineCache>();
let fxaa_pipeline = world.resource::<FxaaPipeline>(); let fxaa_pipeline = world.resource::<FxaaPipeline>();
let (target, pipeline, fxaa) = match self.query.get_manual(world, view_entity) {
Ok(result) => result,
Err(_) => return Ok(()),
};
if !fxaa.enabled { if !fxaa.enabled {
return Ok(()); return Ok(());
}; };

View File

@ -1,16 +1,17 @@
use bevy_ecs::prelude::*; use bevy_ecs::prelude::*;
use bevy_ecs::query::QueryState; use bevy_ecs::query::QueryItem;
use bevy_render::render_graph::ViewNode;
use bevy_render::{ use bevy_render::{
camera::ExtractedCamera, camera::ExtractedCamera,
prelude::Color, prelude::Color,
render_graph::{Node, NodeRunError, RenderGraphContext}, render_graph::{NodeRunError, RenderGraphContext},
render_phase::RenderPhase, render_phase::RenderPhase,
render_resource::{ render_resource::{
LoadOp, Operations, RenderPassColorAttachment, RenderPassDepthStencilAttachment, LoadOp, Operations, RenderPassColorAttachment, RenderPassDepthStencilAttachment,
RenderPassDescriptor, RenderPassDescriptor,
}, },
renderer::RenderContext, renderer::RenderContext,
view::{ExtractedView, ViewDepthTexture}, view::ViewDepthTexture,
}; };
#[cfg(feature = "trace")] #[cfg(feature = "trace")]
use bevy_utils::tracing::info_span; use bevy_utils::tracing::info_span;
@ -20,48 +21,32 @@ use super::{AlphaMask3dPrepass, Opaque3dPrepass, ViewPrepassTextures};
/// Render node used by the prepass. /// Render node used by the prepass.
/// ///
/// By default, inserted before the main pass in the render graph. /// By default, inserted before the main pass in the render graph.
pub struct PrepassNode { #[derive(Default)]
main_view_query: QueryState< pub struct PrepassNode;
(
&'static ExtractedCamera,
&'static RenderPhase<Opaque3dPrepass>,
&'static RenderPhase<AlphaMask3dPrepass>,
&'static ViewDepthTexture,
&'static ViewPrepassTextures,
),
With<ExtractedView>,
>,
}
impl FromWorld for PrepassNode { impl ViewNode for PrepassNode {
fn from_world(world: &mut World) -> Self { type ViewQuery = (
Self { &'static ExtractedCamera,
main_view_query: QueryState::new(world), &'static RenderPhase<Opaque3dPrepass>,
} &'static RenderPhase<AlphaMask3dPrepass>,
} &'static ViewDepthTexture,
} &'static ViewPrepassTextures,
);
impl Node for PrepassNode {
fn update(&mut self, world: &mut World) {
self.main_view_query.update_archetypes(world);
}
fn run( fn run(
&self, &self,
graph: &mut RenderGraphContext, graph: &mut RenderGraphContext,
render_context: &mut RenderContext, render_context: &mut RenderContext,
world: &World, (
) -> Result<(), NodeRunError> {
let view_entity = graph.view_entity();
let Ok((
camera, camera,
opaque_prepass_phase, opaque_prepass_phase,
alpha_mask_prepass_phase, alpha_mask_prepass_phase,
view_depth_texture, view_depth_texture,
view_prepass_textures, view_prepass_textures,
)) = self.main_view_query.get_manual(world, view_entity) else { ): QueryItem<Self::ViewQuery>,
return Ok(()); world: &World,
}; ) -> Result<(), NodeRunError> {
let view_entity = graph.view_entity();
let mut color_attachments = vec![]; let mut color_attachments = vec![];
color_attachments.push( color_attachments.push(

View File

@ -2,11 +2,10 @@ use std::sync::Mutex;
use crate::tonemapping::{TonemappingLuts, TonemappingPipeline, ViewTonemappingPipeline}; use crate::tonemapping::{TonemappingLuts, TonemappingPipeline, ViewTonemappingPipeline};
use bevy_ecs::prelude::*; use bevy_ecs::{prelude::*, query::QueryItem};
use bevy_ecs::query::QueryState;
use bevy_render::{ use bevy_render::{
render_asset::RenderAssets, render_asset::RenderAssets,
render_graph::{Node, NodeRunError, RenderGraphContext}, render_graph::{NodeRunError, RenderGraphContext, ViewNode},
render_resource::{ render_resource::{
BindGroup, BindGroupDescriptor, BindGroupEntry, BindingResource, BufferId, LoadOp, BindGroup, BindGroupDescriptor, BindGroupEntry, BindingResource, BufferId, LoadOp,
Operations, PipelineCache, RenderPassColorAttachment, RenderPassDescriptor, Operations, PipelineCache, RenderPassColorAttachment, RenderPassDescriptor,
@ -14,47 +13,34 @@ use bevy_render::{
}, },
renderer::RenderContext, renderer::RenderContext,
texture::Image, texture::Image,
view::{ExtractedView, ViewTarget, ViewUniformOffset, ViewUniforms}, view::{ViewTarget, ViewUniformOffset, ViewUniforms},
}; };
use super::{get_lut_bindings, Tonemapping}; use super::{get_lut_bindings, Tonemapping};
#[derive(Default)]
pub struct TonemappingNode { pub struct TonemappingNode {
query: QueryState<
(
&'static ViewUniformOffset,
&'static ViewTarget,
&'static ViewTonemappingPipeline,
&'static Tonemapping,
),
With<ExtractedView>,
>,
cached_bind_group: Mutex<Option<(BufferId, TextureViewId, BindGroup)>>, cached_bind_group: Mutex<Option<(BufferId, TextureViewId, BindGroup)>>,
last_tonemapping: Mutex<Option<Tonemapping>>, last_tonemapping: Mutex<Option<Tonemapping>>,
} }
impl FromWorld for TonemappingNode { impl ViewNode for TonemappingNode {
fn from_world(world: &mut World) -> Self { type ViewQuery = (
Self { &'static ViewUniformOffset,
query: QueryState::new(world), &'static ViewTarget,
cached_bind_group: Mutex::new(None), &'static ViewTonemappingPipeline,
last_tonemapping: Mutex::new(None), &'static Tonemapping,
} );
}
}
impl Node for TonemappingNode {
fn update(&mut self, world: &mut World) {
self.query.update_archetypes(world);
}
fn run( fn run(
&self, &self,
graph: &mut RenderGraphContext, _graph: &mut RenderGraphContext,
render_context: &mut RenderContext, render_context: &mut RenderContext,
(view_uniform_offset, target, view_tonemapping_pipeline, tonemapping): QueryItem<
Self::ViewQuery,
>,
world: &World, world: &World,
) -> Result<(), NodeRunError> { ) -> Result<(), NodeRunError> {
let view_entity = graph.view_entity();
let pipeline_cache = world.resource::<PipelineCache>(); let pipeline_cache = world.resource::<PipelineCache>();
let tonemapping_pipeline = world.resource::<TonemappingPipeline>(); let tonemapping_pipeline = world.resource::<TonemappingPipeline>();
let gpu_images = world.get_resource::<RenderAssets<Image>>().unwrap(); let gpu_images = world.get_resource::<RenderAssets<Image>>().unwrap();
@ -62,12 +48,6 @@ impl Node for TonemappingNode {
let view_uniforms = &view_uniforms_resource.uniforms; let view_uniforms = &view_uniforms_resource.uniforms;
let view_uniforms_id = view_uniforms.buffer().unwrap().id(); let view_uniforms_id = view_uniforms.buffer().unwrap().id();
let (view_uniform_offset, target, view_tonemapping_pipeline, tonemapping) =
match self.query.get_manual(world, view_entity) {
Ok(result) => result,
Err(_) => return Ok(()),
};
if !target.is_hdr() { if !target.is_hdr() {
return Ok(()); return Ok(());
} }

View File

@ -1,61 +1,40 @@
use crate::{blit::BlitPipeline, upscaling::ViewUpscalingPipeline}; use crate::{blit::BlitPipeline, upscaling::ViewUpscalingPipeline};
use bevy_ecs::prelude::*; use bevy_ecs::{prelude::*, query::QueryItem};
use bevy_ecs::query::QueryState;
use bevy_render::{ use bevy_render::{
camera::{CameraOutputMode, ExtractedCamera}, camera::{CameraOutputMode, ExtractedCamera},
render_graph::{Node, NodeRunError, RenderGraphContext}, render_graph::{NodeRunError, RenderGraphContext, ViewNode},
render_resource::{ render_resource::{
BindGroup, BindGroupDescriptor, BindGroupEntry, BindingResource, LoadOp, Operations, BindGroup, BindGroupDescriptor, BindGroupEntry, BindingResource, LoadOp, Operations,
PipelineCache, RenderPassColorAttachment, RenderPassDescriptor, SamplerDescriptor, PipelineCache, RenderPassColorAttachment, RenderPassDescriptor, SamplerDescriptor,
TextureViewId, TextureViewId,
}, },
renderer::RenderContext, renderer::RenderContext,
view::{ExtractedView, ViewTarget}, view::ViewTarget,
}; };
use std::sync::Mutex; use std::sync::Mutex;
#[derive(Default)]
pub struct UpscalingNode { pub struct UpscalingNode {
query: QueryState<
(
&'static ViewTarget,
&'static ViewUpscalingPipeline,
Option<&'static ExtractedCamera>,
),
With<ExtractedView>,
>,
cached_texture_bind_group: Mutex<Option<(TextureViewId, BindGroup)>>, cached_texture_bind_group: Mutex<Option<(TextureViewId, BindGroup)>>,
} }
impl FromWorld for UpscalingNode { impl ViewNode for UpscalingNode {
fn from_world(world: &mut World) -> Self { type ViewQuery = (
Self { &'static ViewTarget,
query: QueryState::new(world), &'static ViewUpscalingPipeline,
cached_texture_bind_group: Mutex::new(None), Option<&'static ExtractedCamera>,
} );
}
}
impl Node for UpscalingNode {
fn update(&mut self, world: &mut World) {
self.query.update_archetypes(world);
}
fn run( fn run(
&self, &self,
graph: &mut RenderGraphContext, _graph: &mut RenderGraphContext,
render_context: &mut RenderContext, render_context: &mut RenderContext,
(target, upscaling_target, camera): QueryItem<Self::ViewQuery>,
world: &World, world: &World,
) -> Result<(), NodeRunError> { ) -> Result<(), NodeRunError> {
let view_entity = graph.view_entity();
let pipeline_cache = world.get_resource::<PipelineCache>().unwrap(); let pipeline_cache = world.get_resource::<PipelineCache>().unwrap();
let blit_pipeline = world.get_resource::<BlitPipeline>().unwrap(); let blit_pipeline = world.get_resource::<BlitPipeline>().unwrap();
let (target, upscaling_target, camera) = match self.query.get_manual(world, view_entity) {
Ok(query) => query,
Err(_) => return Ok(()),
};
let color_attachment_load_op = if let Some(camera) = camera { let color_attachment_load_op = if let Some(camera) = camera {
match camera.output_mode { match camera.output_mode {
CameraOutputMode::Write { CameraOutputMode::Write {

View File

@ -6,7 +6,10 @@ use crate::{
}, },
renderer::RenderContext, renderer::RenderContext,
}; };
use bevy_ecs::world::World; use bevy_ecs::{
query::{QueryItem, QueryState, ReadOnlyWorldQuery},
world::{FromWorld, World},
};
use downcast_rs::{impl_downcast, Downcast}; use downcast_rs::{impl_downcast, Downcast};
use std::{borrow::Cow, fmt::Debug}; use std::{borrow::Cow, fmt::Debug};
use thiserror::Error; use thiserror::Error;
@ -332,3 +335,75 @@ impl Node for RunGraphOnViewNode {
Ok(()) Ok(())
} }
} }
/// This trait should be used instead of the [`Node`] trait when making a render node that runs on a view.
///
/// It is intended to be used with [`ViewNodeRunner`]
pub trait ViewNode {
/// The query that will be used on the view entity.
/// It is guaranteed to run on the view entity, so there's no need for a filter
type ViewQuery: ReadOnlyWorldQuery;
/// Updates internal node state using the current render [`World`] prior to the run method.
fn update(&mut self, _world: &mut World) {}
/// Runs the graph node logic, issues draw calls, updates the output slots and
/// optionally queues up subgraphs for execution. The graph data, input and output values are
/// passed via the [`RenderGraphContext`].
fn run(
&self,
graph: &mut RenderGraphContext,
render_context: &mut RenderContext,
view_query: QueryItem<Self::ViewQuery>,
world: &World,
) -> Result<(), NodeRunError>;
}
/// This [`Node`] can be used to run any [`ViewNode`].
/// It will take care of updating the view query in `update()` and running the query in `run()`.
///
/// This [`Node`] exists to help reduce boilerplate when making a render node that runs on a view.
pub struct ViewNodeRunner<N: ViewNode> {
view_query: QueryState<N::ViewQuery>,
node: N,
}
impl<N: ViewNode> ViewNodeRunner<N> {
pub fn new(node: N, world: &mut World) -> Self {
Self {
view_query: world.query_filtered(),
node,
}
}
}
impl<N: ViewNode + FromWorld> FromWorld for ViewNodeRunner<N> {
fn from_world(world: &mut World) -> Self {
Self::new(N::from_world(world), world)
}
}
impl<T> Node for ViewNodeRunner<T>
where
T: ViewNode + Send + Sync + 'static,
{
fn update(&mut self, world: &mut World) {
self.view_query.update_archetypes(world);
self.node.update(world);
}
fn run(
&self,
graph: &mut RenderGraphContext,
render_context: &mut RenderContext,
world: &World,
) -> Result<(), NodeRunError> {
let Ok(view) = self
.view_query
.get_manual(world, graph.view_entity())
else { return Ok(()); };
ViewNode::run(&self.node, graph, render_context, view, world)?;
Ok(())
}
}