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_asset::{load_internal_asset, HandleUntyped};
use bevy_ecs::prelude::*;
use bevy_ecs::{prelude::*, query::QueryItem};
use bevy_math::UVec2;
use bevy_reflect::TypeUuid;
use bevy_render::{
@ -19,7 +19,7 @@ use bevy_render::{
ComponentUniforms, DynamicUniformIndex, ExtractComponentPlugin, UniformComponentPlugin,
},
prelude::Color,
render_graph::{Node, NodeRunError, RenderGraphApp, RenderGraphContext},
render_graph::{NodeRunError, RenderGraphApp, RenderGraphContext, ViewNode, ViewNodeRunner},
render_resource::*,
renderer::{RenderContext, RenderDevice},
texture::{CachedTexture, TextureCache},
@ -73,7 +73,10 @@ impl Plugin for BloomPlugin {
),
)
// 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(
CORE_3D,
&[
@ -83,7 +86,10 @@ impl Plugin for BloomPlugin {
],
)
// 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(
CORE_2D,
&[
@ -106,8 +112,10 @@ impl Plugin for BloomPlugin {
}
}
pub struct BloomNode {
view_query: QueryState<(
#[derive(Default)]
struct BloomNode;
impl ViewNode for BloomNode {
type ViewQuery = (
&'static ExtractedCamera,
&'static ViewTarget,
&'static BloomTexture,
@ -116,36 +124,16 @@ pub struct BloomNode {
&'static BloomSettings,
&'static UpsamplingPipelineIds,
&'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
// 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.
fn run(
&self,
graph: &mut RenderGraphContext,
_graph: &mut RenderGraphContext,
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,
view_target,
bloom_texture,
@ -154,8 +142,12 @@ impl Node for BloomNode {
bloom_settings,
upsampling_pipeline_ids,
downsampling_pipeline_ids,
)) = self.view_query.get_manual(world, view_entity)
else { return Ok(()) };
): QueryItem<Self::ViewQuery>,
world: &World,
) -> Result<(), NodeRunError> {
let downsampling_pipeline_res = world.resource::<BloomDownsamplingPipeline>();
let pipeline_cache = world.resource::<PipelineCache>();
let uniforms = world.resource::<ComponentUniforms<BloomUniforms>>();
let (
Some(uniforms),

View File

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

View File

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

View File

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

View File

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

View File

@ -14,6 +14,7 @@ use bevy_render::{
extract_component::{ExtractComponent, ExtractComponentPlugin},
prelude::Camera,
render_graph::RenderGraphApp,
render_graph::ViewNodeRunner,
render_resource::*,
renderer::RenderDevice,
texture::BevyDefault,
@ -94,7 +95,7 @@ impl Plugin for FxaaPlugin {
render_app
.init_resource::<SpecializedRenderPipelines<FxaaPipeline>>()
.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(
CORE_3D,
&[
@ -103,7 +104,7 @@ impl Plugin for FxaaPlugin {
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(
CORE_2D,
&[

View File

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

View File

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

View File

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

View File

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

View File

@ -6,7 +6,10 @@ use crate::{
},
renderer::RenderContext,
};
use bevy_ecs::world::World;
use bevy_ecs::{
query::{QueryItem, QueryState, ReadOnlyWorldQuery},
world::{FromWorld, World},
};
use downcast_rs::{impl_downcast, Downcast};
use std::{borrow::Cow, fmt::Debug};
use thiserror::Error;
@ -332,3 +335,75 @@ impl Node for RunGraphOnViewNode {
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(())
}
}