move mesh uniform construction out of Core3d (#16188)

# Objective

gpu based mesh uniform construction in the `GpuPreprocessNode` is
currently in `Core3d`. The node iterates all views and schedules the
uniform construction for each. so
- when there are multiple 3d cameras, it runs multiple times on each
view
- if a view wants to render meshes but doesn't use the `Core3d` graph,
the camera must run later than at least one `Core3d`-based camera (or
add the node to its own graph, duplicating the work)
- If views want to share mesh uniforms there is no way to avoid running
the preprocessing for every view

## Solution

- move the node to the top level of the rendergraph, before the camera
driver node
- make the `PreprocessBindGroup` `clone`able, and add a
`SkipGpuPreprocessing` component to allow opting out per view
This commit is contained in:
robtfm 2024-11-04 15:29:11 +00:00 committed by GitHub
parent cd2d14c0fd
commit 262f471934
No known key found for this signature in database
GPG key ID: B5690EEEBB952194

View file

@ -10,11 +10,10 @@ use core::num::NonZero;
use bevy_app::{App, Plugin}; use bevy_app::{App, Plugin};
use bevy_asset::{load_internal_asset, Handle}; use bevy_asset::{load_internal_asset, Handle};
use bevy_core_pipeline::core_3d::graph::{Core3d, Node3d};
use bevy_ecs::{ use bevy_ecs::{
component::Component, component::Component,
entity::Entity, entity::Entity,
query::{Has, QueryState}, query::{Has, QueryState, Without},
schedule::{common_conditions::resource_exists, IntoSystemConfigs as _}, schedule::{common_conditions::resource_exists, IntoSystemConfigs as _},
system::{lifetimeless::Read, Commands, Res, ResMut, Resource}, system::{lifetimeless::Read, Commands, Res, ResMut, Resource},
world::{FromWorld, World}, world::{FromWorld, World},
@ -24,7 +23,8 @@ use bevy_render::{
BatchedInstanceBuffers, GpuPreprocessingSupport, IndirectParameters, BatchedInstanceBuffers, GpuPreprocessingSupport, IndirectParameters,
IndirectParametersBuffer, PreprocessWorkItem, IndirectParametersBuffer, PreprocessWorkItem,
}, },
render_graph::{Node, NodeRunError, RenderGraphApp, RenderGraphContext}, graph::CameraDriverLabel,
render_graph::{Node, NodeRunError, RenderGraph, RenderGraphContext},
render_resource::{ render_resource::{
binding_types::{storage_buffer, storage_buffer_read_only, uniform_buffer}, binding_types::{storage_buffer, storage_buffer_read_only, uniform_buffer},
BindGroup, BindGroupEntries, BindGroupLayout, BindingResource, BufferBinding, BindGroup, BindGroupEntries, BindGroupLayout, BindingResource, BufferBinding,
@ -65,12 +65,15 @@ pub struct GpuMeshPreprocessPlugin {
/// The render node for the mesh uniform building pass. /// The render node for the mesh uniform building pass.
pub struct GpuPreprocessNode { pub struct GpuPreprocessNode {
view_query: QueryState<( view_query: QueryState<
(
Entity, Entity,
Read<PreprocessBindGroup>, Read<PreprocessBindGroup>,
Read<ViewUniformOffset>, Read<ViewUniformOffset>,
Has<GpuCulling>, Has<GpuCulling>,
)>, ),
Without<SkipGpuPreprocess>,
>,
} }
/// The compute shader pipelines for the mesh uniform building pass. /// The compute shader pipelines for the mesh uniform building pass.
@ -108,9 +111,14 @@ bitflags! {
/// The compute shader bind group for the mesh uniform building pass. /// The compute shader bind group for the mesh uniform building pass.
/// ///
/// This goes on the view. /// This goes on the view.
#[derive(Component)] #[derive(Component, Clone)]
pub struct PreprocessBindGroup(BindGroup); pub struct PreprocessBindGroup(BindGroup);
/// Stops the `GpuPreprocessNode` attempting to generate the buffer for this view
/// useful to avoid duplicating effort if the bind group is shared between views
#[derive(Component)]
pub struct SkipGpuPreprocess;
impl Plugin for GpuMeshPreprocessPlugin { impl Plugin for GpuMeshPreprocessPlugin {
fn build(&self, app: &mut App) { fn build(&self, app: &mut App) {
load_internal_asset!( load_internal_asset!(
@ -136,10 +144,12 @@ impl Plugin for GpuMeshPreprocessPlugin {
} }
// Stitch the node in. // Stitch the node in.
let gpu_preprocess_node = GpuPreprocessNode::from_world(render_app.world_mut());
let mut render_graph = render_app.world_mut().resource_mut::<RenderGraph>();
render_graph.add_node(NodePbr::GpuPreprocess, gpu_preprocess_node);
render_graph.add_node_edge(NodePbr::GpuPreprocess, CameraDriverLabel);
render_app render_app
.add_render_graph_node::<GpuPreprocessNode>(Core3d, NodePbr::GpuPreprocess)
.add_render_graph_edges(Core3d, (NodePbr::GpuPreprocess, Node3d::Prepass))
.add_render_graph_edges(Core3d, (NodePbr::GpuPreprocess, NodePbr::ShadowPass))
.init_resource::<PreprocessPipelines>() .init_resource::<PreprocessPipelines>()
.init_resource::<SpecializedComputePipelines<PreprocessPipeline>>() .init_resource::<SpecializedComputePipelines<PreprocessPipeline>>()
.add_systems( .add_systems(
@ -200,7 +210,7 @@ impl Node for GpuPreprocessNode {
// Grab the index buffer for this view. // Grab the index buffer for this view.
let Some(index_buffer) = index_buffers.get(&view) else { let Some(index_buffer) = index_buffers.get(&view) else {
warn!("The preprocessing index buffer wasn't present"); warn!("The preprocessing index buffer wasn't present");
return Ok(()); continue;
}; };
// Select the right pipeline, depending on whether GPU culling is in // Select the right pipeline, depending on whether GPU culling is in