Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add RenderGraphApp to simplify adding render nodes #8007

Merged
merged 6 commits into from Apr 4, 2023

Conversation

IceSentry
Copy link
Contributor

@IceSentry IceSentry commented Mar 9, 2023

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 #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

@IceSentry IceSentry requested a review from JMS55 March 9, 2023 20:55
@IceSentry IceSentry added A-Rendering Drawing game state to the screen C-Usability A simple quality-of-life change that makes Bevy easier to use labels Mar 9, 2023
@alice-i-cecile alice-i-cecile added this to the 0.11 milestone Mar 9, 2023
Copy link
Contributor

@JMS55 JMS55 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A solid step towards more ergonomic render nodes :)

@@ -676,6 +714,22 @@ mod tests {
}
}

fn input_nodes(name: &'static str, graph: &RenderGraph) -> HashSet<NodeId> {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This code was simply moved out of test_graph_edges because I used it in the new test.

cart pushed a commit that referenced this pull request Mar 21, 2023
# Objective

- Currently, the render graph slots are only used to pass the
view_entity around. This introduces significant boilerplate for very
little value. Instead of using slots for this, make the view_entity part
of the `RenderGraphContext`. This also means we won't need to have
`IN_VIEW` on every node and and we'll be able to use the default impl of
`Node::input()`.

## Solution

- Add `view_entity: Option<Entity>` to the `RenderGraphContext`
- Update all nodes to use this instead of entity slot input

---

## Changelog

- Add optional `view_entity` to `RenderGraphContext`

## Migration Guide

You can now get the view_entity directly from the `RenderGraphContext`. 

When implementing the Node:

```rust
// 0.10
struct FooNode;
impl FooNode {
    const IN_VIEW: &'static str = "view";
}
impl Node for FooNode {
    fn input(&self) -> Vec<SlotInfo> {
        vec![SlotInfo::new(Self::IN_VIEW, SlotType::Entity)]
    }
    fn run(
        &self,
        graph: &mut RenderGraphContext,
        // ... 
    ) -> Result<(), NodeRunError> {
        let view_entity = graph.get_input_entity(Self::IN_VIEW)?;
        // ...
        Ok(())
    }
}

// 0.11
struct FooNode;
impl Node for FooNode {
    fn run(
        &self,
        graph: &mut RenderGraphContext,
        // ... 
    ) -> Result<(), NodeRunError> {
        let view_entity = graph.view_entity();
        // ...
        Ok(())
    }
}
```

When adding the node to the graph, you don't need to specify a slot_edge
for the view_entity.

```rust
// 0.10
let mut graph = RenderGraph::default();
graph.add_node(FooNode::NAME, node);
let input_node_id = draw_2d_graph.set_input(vec![SlotInfo::new(
    graph::input::VIEW_ENTITY,
    SlotType::Entity,
)]);
graph.add_slot_edge(
    input_node_id,
    graph::input::VIEW_ENTITY,
    FooNode::NAME,
    FooNode::IN_VIEW,
);
// add_node_edge ...

// 0.11
let mut graph = RenderGraph::default();
graph.add_node(FooNode::NAME, node);
// add_node_edge ...
```

## Notes

This PR paired with #8007 will help reduce a lot of annoying boilerplate
with the render nodes. Depending on which one gets merged first. It will
require a bit of clean up work to make both compatible.

I tagged this as a breaking change, because using the old system to get
the view_entity will break things because it's not a node input slot
anymore.

## Notes for reviewers

A lot of the diffs are just removing the slots in every nodes and graph
creation. The important part is mostly in the
graph_runner/CameraDriverNode.
Copy link
Contributor

@kurtkuehnert kurtkuehnert left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A nice change.
Why don't we store the name of each node on the node itself?

struct MyNode: Node {}

impl Node for MyNode {
  fn name() -> &'static str {
    "MyNode"
  }
}

@IceSentry
Copy link
Contributor Author

IceSentry commented Mar 25, 2023

I've been considering doing that because it would make a few of those patterns even simpler. The issue is that it wouldn't work with EmptyNode that is used in a few places like post processing, but having a way to automatically label a node would be nice a bit like how you can use systems as labels.

So for now, I'd prefer keeping this PR simpler and consider better labels in a future PR

@IceSentry IceSentry force-pushed the render-graph-utils branch 2 times, most recently from 18f9a3d to 5849c15 Compare March 27, 2023 23:03
@IceSentry
Copy link
Contributor Author

While updating this for TAA, I noticed that adding the same edge multiple time would cause a panic so I updated the code to not panic in that case since it doesn't seem like something that is inherently wrong. You can just not add it again if it already exist

@JMS55
Copy link
Contributor

JMS55 commented Mar 28, 2023

I've been considering doing that because it would make a few of those patterns even simpler. The issue is that it wouldn't work with EmptyNode that is used in a few places like post processing, but having a way to automatically label a node would be nice a bit like how you can use systems as labels.

Would something like this work?

struct EmptyNode { name: &'static str }

impl Node for EmptyNode {
  fn name(&self) -> &'static str {
      self.name
  }
} 

We could also maybe replace empty nodes with subgraphs? Idk, haven't looked at how subgraphs work.

@IceSentry
Copy link
Contributor Author

Would something like this work?

Yeah, that's exactly what I thought of after my original comment.

I still think I'd prefer doing this in a separate PR just because big PRs tend to take a long time to merge.

@IceSentry IceSentry force-pushed the render-graph-utils branch 3 times, most recently from bed44df to 00c857f Compare March 31, 2023 18:34
@IceSentry IceSentry changed the title Add utility function to add render nodes Add utility functions to simplify adding render nodes Mar 31, 2023
@IceSentry IceSentry force-pushed the render-graph-utils branch 2 times, most recently from 8e14d99 to 959e7f2 Compare April 1, 2023 19:27
@IceSentry IceSentry added the S-Ready-For-Final-Review This PR has been approved by the community. It's ready for a maintainer to consider merging it label Apr 1, 2023
Copy link
Member

@cart cart left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm very on board for this (love the improved legibility and ergonomics), but I have a few suggestions:

  1. I think this helper should be an "app builder" method. It is already effectively written as one (by taking a mutable app as the first param). It makes this more "standard" (we use this pattern for every other app operation), and it allows us to add it to other app ops.
  2. I think we should split out adding edges and nodes. They are technically orthogonal operations. I know we basically always call them back to back, but I think calling separate named methods helps make it clearer what is going on (rather than having a big list of variables as input that result in a variety of outputs). Combine that with (1) and this actually ends up taking up fewer lines anyway.
  3. Because (1) makes this an App extension method, add_node isn't descriptive enough in a global context. So these should be add_render_graph_X not add_X. (I probably would have pushed for this anyway for the original helper method).

I've pushed a commit that does all of this (as a proposal to make the conversation easier ... we can always revert or change things). I personally think it reads quite well. Check out the diff:
image

Also as a side note: not a fan of "helper" naming generally and I try to avoid it in Bevy. "helper" is a grab bag term and there is almost always a more descriptive and useful name.

@IceSentry
Copy link
Contributor Author

IceSentry commented Apr 3, 2023

I think this helper should be an "app builder" method

I didn't even consider this, I was just trying to encapsulate this common pattern but that diff looks really good.

I think we should split out adding edges and nodes.

That's fair, I liked the idea of bundling everything so it's hard to forget to set a node edge, but I do agree that they are separate concepts. My approach also made it impossible to use add_node in the main_pass_2d/3d which is why I ended up adding the add_node_edges function to the render_graph.

not a fan of "helper" naming generally

Completely agree here, I just really didn't know how to name it/where to put this.

In general, I'm on board with the app extension. My only concern here is that people might end up calling it on the App and not RenderApp. I'm not sure how to handle that case. To be fair, it was also an issue with the original approach.

@IceSentry
Copy link
Contributor Author

Since I'm fully on board with this, I updated the description to match this new api

@IceSentry IceSentry changed the title Add utility functions to simplify adding render nodes Add RenderGraphApp to simplify adding render nodes Apr 3, 2023
@JMS55
Copy link
Contributor

JMS55 commented Apr 3, 2023

In general, I'm on board with the app extension. My only concern here is that people might end up calling it on the App and not RenderApp. I'm not sure how to handle that case. To be fair, it was also an issue with the original approach.

I feel that we should have a RenderAppExt trait implemented on App. Users won't be able to access the methods unless they import the trait, which should hopefully be less error prone.

@IceSentry
Copy link
Contributor Author

IceSentry commented Apr 3, 2023

RenderAppExt trait

I don't think this is possible because RenderApp is just a marker for a sub app. Maybe Apps could have a concept of labels to identify you are using the correct App. Definitely out of scope here though.

I reread your comment, the new feature is implemented like that. It's an extension trait implemented on App. It's just that if it's in scope people could still end up using it on the wrong app.

@IceSentry
Copy link
Contributor Author

IceSentry commented Apr 3, 2023

I added a check in the function to warn the user if it doesn't find the RenderGraph and to remind them to run it on the RenderApp.

@JMS55
Copy link
Contributor

JMS55 commented Apr 3, 2023

Alternative: Make RenderApp a type wrapping the render App, and implement the methods there. Then, instead of get_sub_app(), add get_render_app() which wraps the app in RenderApp.

Users could still call get_render_app() on the wrong app, if they had a custom subapp, but it's much more unlikely.

@IceSentry
Copy link
Contributor Author

I think for now, with the improved panic message and considering this api is most likely only used by more advanced user we'll be fine for a bit. But I'll add it to my list of potential improvements.

@cart cart enabled auto-merge April 4, 2023 00:48
@cart cart added this pull request to the merge queue Apr 4, 2023
Merged via the queue into bevyengine:main with commit 614de30 Apr 4, 2023
20 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
A-Rendering Drawing game state to the screen C-Usability A simple quality-of-life change that makes Bevy easier to use S-Ready-For-Final-Review This PR has been approved by the community. It's ready for a maintainer to consider merging it
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Reduce render Node boilerplate
5 participants