Skip to content

Conversation

JMS55
Copy link
Contributor

@JMS55 JMS55 commented Oct 19, 2025

Objective

  • Reduce boilerplate for render nodes, building on my experience writing SSAO, TAA, virtual geometry, Solari, etc
  • Provide the missing low-level render API helper:
    • High level - Material and FullscreenMaterial
    • Mid level - Phase stuff
    • Low level - Missing until now

Solution

  • Provide a new RenderTask trait to handle all the boilerplate you would otherwise have to write manually:
    • Checking GPU features/limits when setting up the plugin
    • Setting up a render graph node and edge ordering
    • Preparing and caching textures and buffers
    • Preparing and caching pipelines
    • Preparing and caching bind groups/layouts
    • Setting up wgpu compute/render passes and setting state between dispatches/draws
    • Inserting profiling spans

Testing

  • Did you test these changes? If so, how?
  • Are there any parts that need more testing?
  • How can other people (reviewers) test your changes? Is there anything specific they need to know?
  • If relevant, what platforms did you test these changes on, and are there any important ones you can't test?

Showcase

impl RenderTask for SolariLighting {
    type RenderNodeLabel = node::graph::SolariLightingNode;
    type RenderNodeSubGraph = Core3d;
    fn render_node_ordering() -> impl IntoRenderNodeArray {
        (
            Node3d::EndPrepasses,
            node::graph::SolariLightingNode,
            Node3d::EndMainPass,
        )
    }

    fn encode_commands(&self, mut encoder: RenderTaskEncoder, entity: Entity, world: &World) {
        let Some((
            solari_lighting_resources,
            view_target,
            view_prepass_textures,
            view_uniform_offset,
            previous_view_uniform_offset,
        )) = world.entity(entity).get_components::<(
            &SolariLightingResources,
            &ViewTarget,
            &ViewPrepassTextures,
            &ViewUniformOffset,
            &PreviousViewUniformOffset,
        )>()
        else {
            return;
        };

        let frame_count = world.resource::<FrameCount>();
        let frame_index = frame_count.0.wrapping_mul(5782582);

        let push_constants = &[frame_index, self.reset as u32];

        encoder
            .compute_pass("presample_light_tiles")
            .shader(load_embedded_asset!(world, "presample_light_tiles.wgsl"))
            .push_constants(push_constants)
            .dispatch_1d(LIGHT_TILE_BLOCKS as u32);
    }
}

This section is optional. If this PR does not include a visual change or does not add a new feature, you can delete this section.

  • Help others understand the result of this PR by showcasing your awesome work!
  • If this PR adds a new feature or public API, consider adding a brief pseudo-code snippet of it in action
  • If this PR includes a visual change, consider adding a screenshot, GIF, or video
    • If you want, you could even include a before/after comparison!
  • If the Migration Guide adequately covers the changes, you can delete this section

While a showcase should aim to be brief and digestible, you can use a toggleable section to save space on longer showcases:

Click to view showcase
println!("My super cool code.");

@JMS55 JMS55 added A-Rendering Drawing game state to the screen C-Code-Quality A section of code that is hard to understand or change C-Usability A targeted quality-of-life change that makes Bevy easier to use D-Modest A "normal" level of difficulty; suitable for simple features or challenging fixes labels Oct 19, 2025
@torsteingrindvik
Copy link
Contributor

Is ordering always (before, myself, after)? If so is it enough to specify before and after?

@JMS55
Copy link
Contributor Author

JMS55 commented Oct 19, 2025

Is ordering always (before, myself, after)? If so is it enough to specify before and after?

I suppose yeah, but before/after can be more than 1 thing.

@atlv24
Copy link
Contributor

atlv24 commented Oct 19, 2025

I think @torsteingrindvik was suggesting having

    type AfterNodeLabel = Node3d::EndPrepasses;
    type RenderNodeLabel = node::graph::SolariLightingNode;
    type BeforeNodeLabel = Node3d::EndMainPass;
    
    // this is the same for all RenderTasks
    fn render_node_ordering() -> impl IntoRenderNodeArray {
        (
            Self::AfterNodeLabel,
            Self::RenderNodeLabel,
            Self::BeforeNodeLabel,
        )
    }

@JMS55
Copy link
Contributor Author

JMS55 commented Oct 19, 2025

No yeah I get the idea. I just don't think we can do that because you could want more than 1 thing before/after.

@torsteingrindvik
Copy link
Contributor

About the ordering I suppose

    // Ordering defined by a (before) tuple and an (after) tuple
    fn render_node_ordering() -> (impl IntoRenderNodeArray, impl IntoRenderNodeArray) {
        (
            (Node3d::Before0),
            (Node3d::After0, Node3d::After1),
        )
    }

could work, alternatively splitting it up

    fn render_nodes_before() -> impl IntoRenderNodeArray {
        Node3d::Before0
    }

    fn render_nodes_after() -> impl IntoRenderNodeArray {
        (Node3d::After0, Node3d::After1)
    }

not a huge ergonomics win but perhaps worth considering to not have to name the "self node" an extra time.

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-Code-Quality A section of code that is hard to understand or change C-Usability A targeted quality-of-life change that makes Bevy easier to use D-Modest A "normal" level of difficulty; suitable for simple features or challenging fixes

Projects

Status: No status

Development

Successfully merging this pull request may close these issues.

4 participants