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);
    }
}

@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.

@alice-i-cecile alice-i-cecile added the M-Release-Note Work that should be called out in the blog due to impact label Oct 27, 2025
@github-actions
Copy link
Contributor

It looks like your PR has been selected for a highlight in the next release blog post, but you didn't provide a release note.

Please review the instructions for writing release notes, then expand or revise the content in the release notes directory to showcase your changes.

@alice-i-cecile alice-i-cecile added the S-Waiting-on-Author The author needs to make changes or address concerns before this can be merged label Oct 27, 2025
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 M-Release-Note Work that should be called out in the blog due to impact S-Waiting-on-Author The author needs to make changes or address concerns before this can be merged

Projects

Status: No status

Development

Successfully merging this pull request may close these issues.

5 participants