bevy
bevy copied to clipboard
RenderGraph Labelization
Objective
The whole Cow<'static, str>
naming for nodes and subgraphs in RenderGraph
is a mess.
Solution
Replaces hardcoded and potentially overlapping strings for nodes and subgraphs inside RenderGraph
with bevy's labelsystem.
Changelog
- Two new labels:
RenderLabel
andRenderSubGraph
. - Replaced all uses for hardcoded strings with those labels
- Moved
Taa
label from its own mod to all the otherLabels3d
-
add_render_graph_edges
now needs a tuple of labels - Moved
ScreenSpaceAmbientOcclusion
label from its own mod with theShadowPass
label toLabelsPbr
- Removed
NodeId
- Renamed
Edges.id()
toEdges.label()
- Removed
NodeLabel
- Changed examples according to the new label system
- Introduced new
RenderLabel
s:Labels2d
,Labels3d
,LabelsPbr
,LabelsUi
- Introduced new
RenderSubGraph
s:SubGraph2d
,SubGraph3d
,SubGraphUi
- Removed
Reflect
andDefault
derive fromCameraRenderGraph
component struct - Improved some error messages
Migration Guide
For Nodes and SubGraphs, instead of using hardcoded strings, you now pass labels, which can be derived with structs and enums.
// old
#[derive(Default)]
struct MyRenderNode;
impl MyRenderNode {
pub const NAME: &'static str = "my_render_node"
}
render_app
.add_render_graph_node::<ViewNodeRunner<MyRenderNode>>(
core_3d::graph::NAME,
MyRenderNode::NAME,
)
.add_render_graph_edges(
core_3d::graph::NAME,
&[
core_3d::graph::node::TONEMAPPING,
MyRenderNode::NAME,
core_3d::graph::node::END_MAIN_PASS_POST_PROCESSING,
],
);
// new
use bevy::core_pipeline::core_3d::graph::{Labels3d, SubGraph3d};
#[derive(Debug, Hash, PartialEq, Eq, Clone, RenderLabel)]
pub struct MyRenderLabel;
#[derive(Default)]
struct MyRenderNode;
render_app
.add_render_graph_node::<ViewNodeRunner<MyRenderNode>>(
SubGraph3d,
MyRenderLabel,
)
.add_render_graph_edges(
SubGraph3d,
(
Labels3d::Tonemapping,
MyRenderLabel,
Labels3d::EndMainPassPostProcessing,
),
);
SubGraphs
in bevy_core_pipeline::core_2d::graph
old string-based path | new label |
---|---|
NAME |
SubGraph2d |
in bevy_core_pipeline::core_3d::graph
old string-based path | new label |
---|---|
NAME |
SubGraph3d |
in bevy_ui::render
old string-based path | new label |
---|---|
draw_ui_graph::NAME |
graph::SubGraphUi |
Nodes
in bevy_core_pipeline::core_2d::graph
old string-based path | new label |
---|---|
node::MSAA_WRITEBACK |
Labels2d::MsaaWriteback |
node::MAIN_PASS |
Labels2d::MainPass |
node::BLOOM |
Labels2d::Bloom |
node::TONEMAPPING |
Labels2d::Tonemapping |
node::FXAA |
Labels2d::Fxaa |
node::UPSCALING |
Labels2d::Upscaling |
node::CONTRAST_ADAPTIVE_SHARPENING |
Labels2d::ConstrastAdaptiveSharpening |
node::END_MAIN_PASS_POST_PROCESSING |
Labels2d::EndMainPassPostProcessing |
in bevy_core_pipeline::core_3d::graph
old string-based path | new label |
---|---|
node::MSAA_WRITEBACK |
Labels3d::MsaaWriteback |
node::PREPASS |
Labels3d::Prepass |
node::DEFERRED_PREPASS |
Labels3d::DeferredPrepass |
node::COPY_DEFERRED_LIGHTING_ID |
Labels3d::CopyDeferredLightingId |
node::END_PREPASSES |
Labels3d::EndPrepasses |
node::START_MAIN_PASS |
Labels3d::StartMainPass |
node::MAIN_OPAQUE_PASS |
Labels3d::MainOpaquePass |
node::MAIN_TRANSMISSIVE_PASS |
Labels3d::MainTransmissivePass |
node::MAIN_TRANSPARENT_PASS |
Labels3d::MainTransparentPass |
node::END_MAIN_PASS |
Labels3d::EndMainPass |
node::BLOOM |
Labels3d::Bloom |
node::TONEMAPPING |
Labels3d::Tonemapping |
node::FXAA |
Labels3d::Fxaa |
node::UPSCALING |
Labels3d::Upscaling |
node::CONTRAST_ADAPTIVE_SHARPENING |
Labels3d::ContrastAdaptiveSharpening |
node::END_MAIN_PASS_POST_PROCESSING |
Labels3d::EndMainPassPostProcessing |
in bevy_core_pipeline
old string-based path | new label |
---|---|
taa::draw_3d_graph::node::TAA |
Labels3d::Taa |
in bevy_pbr
old string-based path | new label |
---|---|
draw_3d_graph::node::SHADOW_PASS |
LabelsPbr::ShadowPass |
ssao::draw_3d_graph::node::SCREEN_SPACE_AMBIENT_OCCLUSION |
LabelsPbr::ScreenSpaceAmbientOcclusion |
deferred::DEFFERED_LIGHTING_PASS |
LabelsPbr::DeferredLightingPass |
in bevy_render
old string-based path | new label |
---|---|
main_graph::node::CAMERA_DRIVER |
graph::CameraDriverLabel |
in bevy_ui::render
old string-based path | new label |
---|---|
draw_ui_graph::node::UI_PASS |
graph::LabelsUi::UiPass |
Future work
- Make
NodeSlot
s also use types. Ideally, we have an enum with unit variants where every variant resembles one slot. Then to make sure you are using the right slot enum and make rust-analyzer play nicely with it, we should make an associated type in theNode
trait. With today's system, we can introduce 3rd party slots to a node, and i wasnt sure if this was used, so I didn't do this in this PR.
Unresolved Questions
When looking at the post_processing
example, we have a struct for the label and a struct for the node, this seems like boilerplate and on discord, @IceSentry (sowy for the ping) asked if a node could automatically introduce a label (or i completely misunderstood that). The problem with that is, that nodes like EmptyNode
exist multiple times inside the same (sub)graph, so there we need extern labels to distinguish between those. Hopefully we can find a way to reduce boilerplate and still have everything unique. For EmptyNode, we could maybe make a macro which implements an "empty node" for a type, but for nodes which contain code and need to be present multiple times, this could get nasty...
In favor of this! Stringly typed labels are bad, and we shouldn't use them here either :)
I also want to make benchmarks, im highly interested if this is a small regression or small improvement, since we now no longer hash strings. Is there a simple tutorial on how to benchmark bevy? also some people are using tracy, how can i get to that neat view where the two... mountains... overlap? 😅
Yeah, I hit the same EmptyNode
issue last time I looked into it. This PR is already a big improvement as it is so we can figure out that issue in the future. Doesn't have to be in this PR.
As for benchmarks, while it wouldn't hurt, I would be highly surprised if it made any difference but here's the docs on profiling. It explains how to use tracy https://github.com/bevyengine/bevy/blob/main/docs/profiling.md
Please add a code snippet in the migration guide.
Something like
// 0.12
#[derive(Default)]
struct PostProcessNode;
impl PostProcessNode {
pub const NAME: &'static str = "post_process";
}
// 0.13
#[derive(Default)]
struct PostProcessNode;
#[derive(Debug, Hash, PartialEq, Eq, Clone, RenderLabel)]
pub struct PostProcessLabel;
Yeah, I hit the same
EmptyNode
issue last time I looked into it. This PR is already a big improvement as it is so we can figure out that issue in the future. Doesn't have to be in this PR.As for benchmarks, while it wouldn't hurt, I would be highly surprised if it made any difference but here's the docs on profiling. It explains how to use tracy https://github.com/bevyengine/bevy/blob/main/docs/profiling.md
That markdown file is exactly what i was searching for
Please add a code snippet in the migration guide.
Something like
// 0.12 #[derive(Default)] struct PostProcessNode; impl PostProcessNode { pub const NAME: &'static str = "post_process"; } // 0.13 #[derive(Default)] struct PostProcessNode; #[derive(Debug, Hash, PartialEq, Eq, Clone, RenderLabel)] pub struct PostProcessLabel;
done :>
If you're feeling particularly nice, it would be good to have a markdown table showing how to migrate all of the existing labels to their type equivalent.
@alice-i-cecile Done! :D (also funny, there was a UI_PASS_DRIVER
which was never used)
- I'm a bit unsure about the naming of
CameraDriverLabel
. I named all the enumsLabels<topic>
because I couldn't put the3
of3d
at the front, so I am asking if i should name itLabelCameraDriver
? - Then there was
draw_ui_graph::node::UI_PASS
, which was the only "label" for Ui, but since it was in a module, I put it in a enumLabelsUi
with only one variant. Im unsure, should I also convert it toLabelUiPass
? - Last but not least, there are many inconsistent namings, like in
Labels3d
:Prepass
,EndPrepasses
,StartMainPass
, is it with or without start and end and is it plural or singular? Now is the best chance to clean those up. An additional Breaking change later after a release would be annoying.
This = PR, External = main
Running
main_graph
seems to have a bigger spike. (what does the spike mean?)
This = PR, External = main
Running
SubGraphUi
(formerly draw_ui
) however, it seems faster for whatever reason
Last but not least, there are many inconsistent namings, like in Labels3d: Prepass, EndPrepasses, StartMainPass, is it with or without start and end and is it plural or singular? Now is the best chance to clean those up. An additional Breaking change later after a release would be annoying.
Prepass was singular because at the time of creation only 1 prepass existed. EndPrepasses is plural because it waits for the prepass and the deferred_prepass to finish. The main pass has a start and an end label because there are many nodes used in the main pass. So my opinion would be to keep it as is.
Last but not least, there are many inconsistent namings, like in Labels3d: Prepass, EndPrepasses, StartMainPass, is it with or without start and end and is it plural or singular? Now is the best chance to clean those up. An additional Breaking change later after a release would be annoying.
Prepass was singular because at the time of creation only 1 prepass existed. EndPrepasses is plural because it waits for the prepass and the deferred_prepass to finish. The main pass has a start and an end label because there are many nodes used in the main pass. So my opinion would be to keep it as is.
Ok, then i would like to document them. Any idea how to call the Normal Prepass?
Well, there's the "normal prepass" as in the prepass that generates a normals texture and there's the "depth prepass", but both are in the same Prepass node
Well, there's the "normal prepass" as in the prepass that generates a normals texture and there's the "depth prepass", but both are in the same Prepass node
Oh wait forgot about normals.. I meant when differentiating between Prepass
and DeferredPrepass
My opinion is that calling it Prepass
is good enough. That or maybe DepthNormalPrepass
, but I'd prefer just calling it Prepass
. Currently most rendering contributors are already familiar with this terminology.
My opinion is that calling it
Prepass
is good enough. That or maybeDepthNormalPrepass
, but I'd prefer just calling itPrepass
. Currently most rendering contributors are already familiar with this terminology.
then I would like to document them right. So Prepass
is for the forward rendering prepasses and DeferredPrepasses
are for deferred prepasses and EndPrepasses
waits for both or what?
Running main_graph seems to have a bigger spike. (what does the spike mean?)
Each vertical line represents the count of events within that timespan. Having a larger can mean a few different things. In this case it's probably because the variation (standard deviation) is smaller in this pr. Including the numbers that are above the graph could help with interpretation. Those have the mean, median, and standard deviation numbers.
I wonder what impact this has on the ability to have 3rd party plugins replace nodes. Currently if someone makes an alternate TAA or Bloom crate, for example, if they use the same name for the node, it will run in the right order around whatever other nodes the user or other 3rd party plugins have added relative to those labels. I mention this because I already make use of this property of the current system and this would mean I need to require users to replace the whole Render Graph for their cameras in some cases.
I agree however that stringly typed labels are lame, not sure what the best way to handle this is.
I guess one option would be to add even more marker nodes. Like bloom start, bloom end and then orient around those. But that also maybe sounds like a mess idk
@DGriffin91 if you want to replace bloom, you can still use the Labels3d::Bloom node. The only thing I've done is preventing unintended overlapping
@DasLixou Gotcha, I see now. I should have looked at the impl first.
@DasLixou I want to include this in 0.13: can you resolve merge conflicts?
@DasLixou I want to include this in 0.13: can you resolve merge conflicts?
Sure! I'll try to fix them later!
Yay @alice-i-cecile looks like CI is passing.
I've encountered some challenges. Could you please advise on how to convert dynamic hardcoded strings into RenderLabel
s? For example, in the bevy_egui
parts for updating them to be compatible with Bevy 0.13.
/// Sets up the pipeline for newly created windows.
pub fn setup_new_windows_render_system(
windows: Extract<Query<Entity, Added<Window>>>,
mut render_graph: ResMut<RenderGraph>,
) {
for window in windows.iter() {
let egui_pass = format!("egui-{}-{}", window.index(), window.generation());
let new_node = EguiNode::new(window);
render_graph.add_node(egui_pass.clone(), new_node);
render_graph.add_node_edge(
bevy::render::graph::CameraDriverLabel,
egui_pass.to_string(),
);
}
}
@Shute052 im not 100% sure but I think it should be fine to make a tuple struct like
#[derive(..., RenderLabel)]
struct EguiPass(&'static str);
@Shute052 im not 100% sure but I think it should be fine to make a tuple struct like
#[derive(..., RenderLabel)] struct EguiPass(&'static str);
Thanks! It works and successfully passed all of four tests in bevy_egui, which have limited coverage. I'm not entirely certain that all behaviors are error-free.
@Shute052 im not 100% sure but I think it should be fine to make a tuple struct like
#[derive(..., RenderLabel)] struct EguiPass(&'static str);
Thanks! It works and successfully passed all of four tests in bevy_egui, which have limited coverage. I'm not entirely certain that all behaviors are error-free.
Well it shouldn't differ from the old one except when you have two different structs, because now wouldn't collide anymore, means you could also strip out the egui-
from the format! macro
Could you update the migration guide to include advice on dynamic labels? :)
#[derive(Debug, Hash, PartialEq, Eq, Clone, RenderLabel)]
struct EguiPass(u32, u32);
let egui_pass = EguiPass(window.index(), window.generation());
This passed the compilation, would it cause any problems? Stores two u32 might be better than a String
Could you update the migration guide to include advice on dynamic labels? :)
sure, ill also add it to the blog PR
#[derive(Debug, Hash, PartialEq, Eq, Clone, RenderLabel)] struct EguiPass(u32, u32); let egui_pass = EguiPass(window.index(), window.generation());
This passed the compilation, would it cause any problems? Stores two u32 might be better than a String
Already proposed that in the bevy_egui pr 👍