From fa703f0cf7c128db680049940266268c047c3b13 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?charlotte=20=F0=9F=8C=B8?= Date: Mon, 15 Dec 2025 13:37:51 -0800 Subject: [PATCH 01/55] Rewrite RenderGraph to use Schedule. --- .../src/contrast_adaptive_sharpening/mod.rs | 54 +- .../src/contrast_adaptive_sharpening/node.rs | 170 +-- crates/bevy_anti_alias/src/dlss/mod.rs | 34 +- crates/bevy_anti_alias/src/dlss/node.rs | 273 ++-- crates/bevy_anti_alias/src/fxaa/mod.rs | 27 +- crates/bevy_anti_alias/src/fxaa/node.rs | 119 +- crates/bevy_anti_alias/src/smaa/mod.rs | 277 ++-- crates/bevy_anti_alias/src/taa/mod.rs | 190 ++- .../src/core_2d/main_opaque_pass_2d_node.rs | 143 +-- .../core_2d/main_transparent_pass_2d_node.rs | 171 +-- crates/bevy_core_pipeline/src/core_2d/mod.rs | 79 +- .../src/core_3d/main_opaque_pass_3d_node.rs | 199 ++- .../core_3d/main_transmissive_pass_3d_node.rs | 225 ++-- .../core_3d/main_transparent_pass_3d_node.rs | 143 +-- crates/bevy_core_pipeline/src/core_3d/mod.rs | 173 +-- .../src/deferred/copy_lighting_id.rs | 128 +- .../bevy_core_pipeline/src/deferred/node.rs | 293 ++--- .../src/experimental/mip_generation/mod.rs | 279 ++-- .../src/fullscreen_material.rs | 292 ++--- crates/bevy_core_pipeline/src/lib.rs | 8 +- crates/bevy_core_pipeline/src/oit/mod.rs | 27 +- .../src/oit/resolve/node.rs | 122 +- crates/bevy_core_pipeline/src/prepass/node.rs | 355 +++--- crates/bevy_core_pipeline/src/schedule.rs | 213 ++++ .../bevy_core_pipeline/src/tonemapping/mod.rs | 2 +- .../src/tonemapping/node.rs | 218 ++-- .../bevy_core_pipeline/src/upscaling/mod.rs | 2 +- .../bevy_core_pipeline/src/upscaling/node.rs | 136 +- crates/bevy_pbr/src/atmosphere/environment.rs | 135 +- crates/bevy_pbr/src/atmosphere/mod.rs | 53 +- crates/bevy_pbr/src/atmosphere/node.rs | 383 +++--- crates/bevy_pbr/src/deferred/mod.rs | 198 ++- crates/bevy_pbr/src/lib.rs | 61 +- crates/bevy_pbr/src/light_probe/generate.rs | 329 ++--- .../src/meshlet/material_shade_nodes.rs | 638 +++++---- crates/bevy_pbr/src/meshlet/mod.rs | 74 +- .../meshlet/visibility_buffer_raster_node.rs | 551 ++++---- crates/bevy_pbr/src/render/gpu_preprocess.rs | 1061 ++++++--------- crates/bevy_pbr/src/render/light.rs | 215 ++-- crates/bevy_pbr/src/ssao/mod.rs | 203 ++- crates/bevy_pbr/src/ssr/mod.rs | 219 ++-- crates/bevy_pbr/src/volumetric_fog/mod.rs | 27 +- crates/bevy_pbr/src/volumetric_fog/render.rs | 377 +++--- crates/bevy_pbr/src/wireframe.rs | 94 +- .../src/auto_exposure/mod.rs | 19 +- .../src/auto_exposure/node.rs | 204 ++- crates/bevy_post_process/src/bloom/mod.rs | 421 +++--- crates/bevy_post_process/src/dof/mod.rs | 314 +++-- .../bevy_post_process/src/effect_stack/mod.rs | 194 ++- .../bevy_post_process/src/motion_blur/mod.rs | 23 +- .../bevy_post_process/src/motion_blur/node.rs | 151 ++- .../bevy_post_process/src/msaa_writeback.rs | 147 +-- crates/bevy_render/src/camera.rs | 26 +- crates/bevy_render/src/diagnostic/mod.rs | 31 +- crates/bevy_render/src/gpu_readback.rs | 7 +- crates/bevy_render/src/lib.rs | 15 +- crates/bevy_render/src/render_graph/app.rs | 174 --- .../src/render_graph/camera_driver_node.rs | 107 -- .../bevy_render/src/render_graph/context.rs | 286 ----- crates/bevy_render/src/render_graph/edge.rs | 57 - crates/bevy_render/src/render_graph/graph.rs | 918 ------------- crates/bevy_render/src/render_graph/mod.rs | 56 - crates/bevy_render/src/render_graph/node.rs | 426 ------- .../bevy_render/src/render_graph/node_slot.rs | 165 --- .../bevy_render/src/renderer/graph_runner.rs | 292 ----- crates/bevy_render/src/renderer/mod.rs | 275 +--- .../src/renderer/render_context.rs | 315 +++++ crates/bevy_solari/src/pathtracer/mod.rs | 18 +- crates/bevy_solari/src/pathtracer/node.rs | 192 ++- crates/bevy_solari/src/realtime/mod.rs | 25 +- crates/bevy_solari/src/realtime/node.rs | 1135 ++++++++++------- .../src/mesh2d/wireframe2d.rs | 93 +- crates/bevy_ui_render/src/lib.rs | 88 +- crates/bevy_ui_render/src/render_pass.rs | 125 +- .../shader_advanced/custom_post_processing.rs | 241 ++-- .../shader_advanced/custom_render_phase.rs | 117 +- .../shader_advanced/fullscreen_material.rs | 44 +- .../render_depth_to_texture.rs | 160 +-- 78 files changed, 6015 insertions(+), 9816 deletions(-) create mode 100644 crates/bevy_core_pipeline/src/schedule.rs delete mode 100644 crates/bevy_render/src/render_graph/app.rs delete mode 100644 crates/bevy_render/src/render_graph/camera_driver_node.rs delete mode 100644 crates/bevy_render/src/render_graph/context.rs delete mode 100644 crates/bevy_render/src/render_graph/edge.rs delete mode 100644 crates/bevy_render/src/render_graph/graph.rs delete mode 100644 crates/bevy_render/src/render_graph/mod.rs delete mode 100644 crates/bevy_render/src/render_graph/node.rs delete mode 100644 crates/bevy_render/src/render_graph/node_slot.rs delete mode 100644 crates/bevy_render/src/renderer/graph_runner.rs create mode 100644 crates/bevy_render/src/renderer/render_context.rs diff --git a/crates/bevy_anti_alias/src/contrast_adaptive_sharpening/mod.rs b/crates/bevy_anti_alias/src/contrast_adaptive_sharpening/mod.rs index 83aa35e94e8bd..f3519fba12752 100644 --- a/crates/bevy_anti_alias/src/contrast_adaptive_sharpening/mod.rs +++ b/crates/bevy_anti_alias/src/contrast_adaptive_sharpening/mod.rs @@ -2,16 +2,15 @@ use bevy_app::prelude::*; use bevy_asset::{embedded_asset, load_embedded_asset, AssetServer, Handle}; use bevy_camera::Camera; use bevy_core_pipeline::{ - core_2d::graph::{Core2d, Node2d}, - core_3d::graph::{Core3d, Node3d}, + schedule::{Core2d, Core2dSystems, Core3d, Core3dSystems}, FullscreenShader, }; +use crate::fxaa::fxaa; use bevy_ecs::{prelude::*, query::QueryItem}; use bevy_image::BevyDefault as _; use bevy_reflect::{std_traits::ReflectDefault, Reflect}; use bevy_render::{ extract_component::{ExtractComponent, ExtractComponentPlugin, UniformComponentPlugin}, - render_graph::RenderGraphExt, render_resource::{ binding_types::{sampler, texture_2d, uniform_buffer}, *, @@ -25,7 +24,7 @@ use bevy_utils::default; mod node; -pub use node::CasNode; +pub(crate) use node::cas; /// Applies a contrast adaptive sharpening (CAS) filter to the camera. /// @@ -116,42 +115,17 @@ impl Plugin for CasPlugin { render_app .init_resource::>() .add_systems(RenderStartup, init_cas_pipeline) - .add_systems(Render, prepare_cas_pipelines.in_set(RenderSystems::Prepare)); - - { - render_app - .add_render_graph_node::(Core3d, Node3d::ContrastAdaptiveSharpening) - .add_render_graph_edge( - Core3d, - Node3d::Tonemapping, - Node3d::ContrastAdaptiveSharpening, - ) - .add_render_graph_edges( - Core3d, - ( - Node3d::Fxaa, - Node3d::ContrastAdaptiveSharpening, - Node3d::EndMainPassPostProcessing, - ), - ); - } - { - render_app - .add_render_graph_node::(Core2d, Node2d::ContrastAdaptiveSharpening) - .add_render_graph_edge( - Core2d, - Node2d::Tonemapping, - Node2d::ContrastAdaptiveSharpening, - ) - .add_render_graph_edges( - Core2d, - ( - Node2d::Fxaa, - Node2d::ContrastAdaptiveSharpening, - Node2d::EndMainPassPostProcessing, - ), - ); - } + .add_systems(Render, prepare_cas_pipelines.in_set(RenderSystems::Prepare)) + .add_systems( + Core3d, + cas.after(fxaa) + .before(Core3dSystems::EndMainPassPostProcessing), + ) + .add_systems( + Core2d, + cas.after(fxaa) + .before(Core2dSystems::EndMainPassPostProcessing), + ); } } diff --git a/crates/bevy_anti_alias/src/contrast_adaptive_sharpening/node.rs b/crates/bevy_anti_alias/src/contrast_adaptive_sharpening/node.rs index 8356a44659b63..897c41787330d 100644 --- a/crates/bevy_anti_alias/src/contrast_adaptive_sharpening/node.rs +++ b/crates/bevy_anti_alias/src/contrast_adaptive_sharpening/node.rs @@ -1,126 +1,94 @@ -use std::sync::Mutex; - use crate::contrast_adaptive_sharpening::ViewCasPipeline; use bevy_ecs::prelude::*; use bevy_render::{ diagnostic::RecordDiagnostics, extract_component::{ComponentUniforms, DynamicUniformIndex}, - render_graph::{Node, NodeRunError, RenderGraphContext}, render_resource::{ BindGroup, BindGroupEntries, BufferId, Operations, PipelineCache, RenderPassColorAttachment, RenderPassDescriptor, TextureViewId, }, - renderer::RenderContext, + renderer::{RenderContext, ViewQuery}, view::{ExtractedView, ViewTarget}, }; use super::{CasPipeline, CasUniform}; -pub struct CasNode { - query: QueryState< +pub(crate) fn cas( + view: ViewQuery< ( - &'static ViewTarget, - &'static ViewCasPipeline, - &'static DynamicUniformIndex, + &ViewTarget, + &ViewCasPipeline, + &DynamicUniformIndex, ), With, >, - cached_bind_group: Mutex>, -} - -impl FromWorld for CasNode { - fn from_world(world: &mut World) -> Self { - Self { - query: QueryState::new(world), - cached_bind_group: Mutex::new(None), + sharpening_pipeline: Res, + pipeline_cache: Res, + uniforms: Res>, + mut ctx: RenderContext, + mut cached_bind_group: Local>, +) { + let (target, pipeline, uniform_index) = view.into_inner(); + + let uniforms_id = uniforms.buffer().unwrap().id(); + let Some(uniforms_binding) = uniforms.binding() else { + return; + }; + + let Some(pipeline) = pipeline_cache.get_render_pipeline(pipeline.0) else { + return; + }; + + let view_target = target.post_process_write(); + let source = view_target.source; + let destination = view_target.destination; + + let bind_group = match &mut *cached_bind_group { + Some((buffer_id, texture_id, bind_group)) + if source.id() == *texture_id && uniforms_id == *buffer_id => + { + bind_group } - } -} - -impl Node for CasNode { - fn update(&mut self, world: &mut World) { - self.query.update_archetypes(world); - } - - fn run( - &self, - graph: &mut RenderGraphContext, - render_context: &mut RenderContext, - world: &World, - ) -> Result<(), NodeRunError> { - let view_entity = graph.view_entity(); - let pipeline_cache = world.resource::(); - let sharpening_pipeline = world.resource::(); - let uniforms = world.resource::>(); - - let Ok((target, pipeline, uniform_index)) = self.query.get_manual(world, view_entity) - else { - return Ok(()); - }; - - let uniforms_id = uniforms.buffer().unwrap().id(); - let Some(uniforms) = uniforms.binding() else { - return Ok(()); - }; - - let Some(pipeline) = pipeline_cache.get_render_pipeline(pipeline.0) else { - return Ok(()); - }; - - let diagnostics = render_context.diagnostic_recorder(); - - let view_target = target.post_process_write(); - let source = view_target.source; - let destination = view_target.destination; - - let mut cached_bind_group = self.cached_bind_group.lock().unwrap(); - let bind_group = match &mut *cached_bind_group { - Some((buffer_id, texture_id, bind_group)) - if source.id() == *texture_id && uniforms_id == *buffer_id => - { - bind_group - } - cached_bind_group => { - let bind_group = render_context.render_device().create_bind_group( - "cas_bind_group", - &pipeline_cache.get_bind_group_layout(&sharpening_pipeline.texture_bind_group), - &BindGroupEntries::sequential(( - view_target.source, - &sharpening_pipeline.sampler, - uniforms, - )), - ); - - let (_, _, bind_group) = - cached_bind_group.insert((uniforms_id, source.id(), bind_group)); - bind_group - } - }; - - let pass_descriptor = RenderPassDescriptor { - label: Some("contrast_adaptive_sharpening"), - color_attachments: &[Some(RenderPassColorAttachment { - view: destination, - depth_slice: None, - resolve_target: None, - ops: Operations::default(), - })], - depth_stencil_attachment: None, - timestamp_writes: None, - occlusion_query_set: None, - }; - - let mut render_pass = render_context - .command_encoder() - .begin_render_pass(&pass_descriptor); - let pass_span = diagnostics.pass_span(&mut render_pass, "contrast_adaptive_sharpening"); + cached => { + let bind_group = ctx.render_device().create_bind_group( + "cas_bind_group", + &pipeline_cache.get_bind_group_layout(&sharpening_pipeline.texture_bind_group), + &BindGroupEntries::sequential(( + view_target.source, + &sharpening_pipeline.sampler, + uniforms_binding, + )), + ); + + let (_, _, bind_group) = cached.insert((uniforms_id, source.id(), bind_group)); + bind_group + } + }; + + let pass_descriptor = RenderPassDescriptor { + label: Some("contrast_adaptive_sharpening"), + color_attachments: &[Some(RenderPassColorAttachment { + view: destination, + depth_slice: None, + resolve_target: None, + ops: Operations::default(), + })], + depth_stencil_attachment: None, + timestamp_writes: None, + occlusion_query_set: None, + }; + + let diagnostics = ctx.diagnostic_recorder(); + let diagnostics = diagnostics.as_deref(); + let time_span = diagnostics.time_span(ctx.command_encoder(), "contrast_adaptive_sharpening"); + + { + let mut render_pass = ctx.command_encoder().begin_render_pass(&pass_descriptor); render_pass.set_pipeline(pipeline); render_pass.set_bind_group(0, bind_group, &[uniform_index.index()]); render_pass.draw(0..3, 0..1); - - pass_span.end(&mut render_pass); - - Ok(()) } + + time_span.end(ctx.command_encoder()); } diff --git a/crates/bevy_anti_alias/src/dlss/mod.rs b/crates/bevy_anti_alias/src/dlss/mod.rs index 50586f5d03d02..c993734b6a675 100644 --- a/crates/bevy_anti_alias/src/dlss/mod.rs +++ b/crates/bevy_anti_alias/src/dlss/mod.rs @@ -22,15 +22,14 @@ pub use dlss_wgpu::DlssPerfQualityMode; use bevy_app::{App, Plugin}; use bevy_core_pipeline::{ - core_3d::graph::{Core3d, Node3d}, prepass::{DepthPrepass, MotionVectorPrepass}, + schedule::{Core3d, Core3dSystems}, }; use bevy_ecs::prelude::*; use bevy_math::{UVec2, Vec2}; use bevy_reflect::{reflect_remote, Reflect}; use bevy_render::{ camera::{MipBias, TemporalJitter}, - render_graph::{RenderGraphExt, ViewNodeRunner}, renderer::{ raw_vulkan_init::{AdditionalVulkanFeatures, RawVulkanInitSettings}, RenderDevice, RenderQueue, @@ -187,26 +186,19 @@ impl Plugin for DlssPlugin { ) .in_set(RenderSystems::ManageViews) .before(prepare_view_targets), - ) - .add_render_graph_node::>>( - Core3d, - Node3d::DlssSuperResolution, - ) - .add_render_graph_node::>>( - Core3d, - Node3d::DlssRayReconstruction, - ) - .add_render_graph_edges( - Core3d, - ( - Node3d::EndMainPass, - Node3d::MotionBlur, // Running before DLSS reduces edge artifacts and noise - Node3d::DlssSuperResolution, - Node3d::DlssRayReconstruction, - Node3d::Bloom, - Node3d::Tonemapping, - ), ); + + app.sub_app_mut(RenderApp).add_systems( + Core3d, + ( + node::dlss_super_resolution + .after(Core3dSystems::StartMainPassPostProcessing) + .before(Core3dSystems::PostProcessing), + node::dlss_ray_reconstruction + .after(node::dlss_super_resolution) + .before(Core3dSystems::PostProcessing), + ), + ); } } diff --git a/crates/bevy_anti_alias/src/dlss/node.rs b/crates/bevy_anti_alias/src/dlss/node.rs index aae59fa95b821..f44c55a9a5ddd 100644 --- a/crates/bevy_anti_alias/src/dlss/node.rs +++ b/crates/bevy_anti_alias/src/dlss/node.rs @@ -1,15 +1,14 @@ use super::{ - prepare::DlssRenderContext, Dlss, DlssFeature, DlssRayReconstructionFeature, - DlssSuperResolutionFeature, ViewDlssRayReconstructionTextures, + prepare::DlssRenderContext, Dlss, DlssRayReconstructionFeature, DlssSuperResolutionFeature, + ViewDlssRayReconstructionTextures, }; use bevy_camera::MainPassResolutionOverride; use bevy_core_pipeline::prepass::ViewPrepassTextures; -use bevy_ecs::{query::QueryItem, world::World}; +use bevy_ecs::system::{Query, Res}; use bevy_render::{ camera::TemporalJitter, diagnostic::RecordDiagnostics, - render_graph::{NodeRunError, RenderGraphContext, ViewNode}, - renderer::{RenderAdapter, RenderContext}, + renderer::{RenderAdapter, RenderContext, ViewQuery}, view::ViewTarget, }; use dlss_wgpu::{ @@ -18,148 +17,128 @@ use dlss_wgpu::{ }, super_resolution::{DlssSuperResolutionExposure, DlssSuperResolutionRenderParameters}, }; -use std::marker::PhantomData; - -#[derive(Default)] -pub struct DlssNode(PhantomData); - -impl ViewNode for DlssNode { - type ViewQuery = ( - &'static Dlss, - &'static DlssRenderContext, - &'static MainPassResolutionOverride, - &'static TemporalJitter, - &'static ViewTarget, - &'static ViewPrepassTextures, - ); - - fn run( - &self, - _graph: &mut RenderGraphContext, - render_context: &mut RenderContext, - ( - dlss, - dlss_context, - resolution_override, - temporal_jitter, - view_target, - prepass_textures, - ): QueryItem, - world: &World, - ) -> Result<(), NodeRunError> { - let adapter = world.resource::(); - let (Some(prepass_depth_texture), Some(prepass_motion_vectors_texture)) = - (&prepass_textures.depth, &prepass_textures.motion_vectors) - else { - return Ok(()); - }; - - let view_target = view_target.post_process_write(); - - let render_resolution = resolution_override.0; - let render_parameters = DlssSuperResolutionRenderParameters { - color: &view_target.source, - depth: &prepass_depth_texture.texture.default_view, - motion_vectors: &prepass_motion_vectors_texture.texture.default_view, - exposure: DlssSuperResolutionExposure::Automatic, // TODO - bias: None, // TODO - dlss_output: &view_target.destination, - reset: dlss.reset, - jitter_offset: -temporal_jitter.offset, - partial_texture_size: Some(render_resolution), - motion_vector_scale: Some(-render_resolution.as_vec2()), - }; - - let diagnostics = render_context.diagnostic_recorder(); - let command_encoder = render_context.command_encoder(); - let mut dlss_context = dlss_context.context.lock().unwrap(); - - command_encoder.push_debug_group("dlss_super_resolution"); - let time_span = diagnostics.time_span(command_encoder, "dlss_super_resolution"); - - dlss_context - .render(render_parameters, command_encoder, &adapter) - .expect("Failed to render DLSS Super Resolution"); - - time_span.end(command_encoder); - command_encoder.pop_debug_group(); - - Ok(()) - } + +pub fn dlss_super_resolution( + view: ViewQuery<( + &Dlss, + &DlssRenderContext, + &MainPassResolutionOverride, + &TemporalJitter, + &ViewTarget, + &ViewPrepassTextures, + )>, + adapter: Res, + mut ctx: RenderContext, +) { + let (dlss, dlss_context, resolution_override, temporal_jitter, view_target, prepass_textures) = + view.into_inner(); + + let (Some(prepass_depth_texture), Some(prepass_motion_vectors_texture)) = + (&prepass_textures.depth, &prepass_textures.motion_vectors) + else { + return; + }; + + let view_target = view_target.post_process_write(); + + let render_resolution = resolution_override.0; + let render_parameters = DlssSuperResolutionRenderParameters { + color: &view_target.source, + depth: &prepass_depth_texture.texture.default_view, + motion_vectors: &prepass_motion_vectors_texture.texture.default_view, + exposure: DlssSuperResolutionExposure::Automatic, // TODO + bias: None, // TODO + dlss_output: &view_target.destination, + reset: dlss.reset, + jitter_offset: -temporal_jitter.offset, + partial_texture_size: Some(render_resolution), + motion_vector_scale: Some(-render_resolution.as_vec2()), + }; + + let diagnostics = ctx.diagnostic_recorder(); + let diagnostics = diagnostics.as_deref(); + let time_span = diagnostics.time_span(ctx.command_encoder(), "dlss_super_resolution"); + + let command_encoder = ctx.command_encoder(); + let mut dlss_context = dlss_context.context.lock().unwrap(); + + command_encoder.push_debug_group("dlss_super_resolution"); + + dlss_context + .render(render_parameters, command_encoder, &adapter) + .expect("Failed to render DLSS Super Resolution"); + + command_encoder.pop_debug_group(); + time_span.end(ctx.command_encoder()); } -impl ViewNode for DlssNode { - type ViewQuery = ( - &'static Dlss, - &'static DlssRenderContext, - &'static MainPassResolutionOverride, - &'static TemporalJitter, - &'static ViewTarget, - &'static ViewPrepassTextures, - &'static ViewDlssRayReconstructionTextures, - ); - - fn run( - &self, - _graph: &mut RenderGraphContext, - render_context: &mut RenderContext, - ( - dlss, - dlss_context, - resolution_override, - temporal_jitter, - view_target, - prepass_textures, - ray_reconstruction_textures, - ): QueryItem, - world: &World, - ) -> Result<(), NodeRunError> { - let adapter = world.resource::(); - let (Some(prepass_depth_texture), Some(prepass_motion_vectors_texture)) = - (&prepass_textures.depth, &prepass_textures.motion_vectors) - else { - return Ok(()); - }; - - let view_target = view_target.post_process_write(); - - let render_resolution = resolution_override.0; - let render_parameters = DlssRayReconstructionRenderParameters { - diffuse_albedo: &ray_reconstruction_textures.diffuse_albedo.default_view, - specular_albedo: &ray_reconstruction_textures.specular_albedo.default_view, - normals: &ray_reconstruction_textures.normal_roughness.default_view, - roughness: None, - color: &view_target.source, - depth: &prepass_depth_texture.texture.default_view, - motion_vectors: &prepass_motion_vectors_texture.texture.default_view, - specular_guide: DlssRayReconstructionSpecularGuide::SpecularMotionVectors( - &ray_reconstruction_textures - .specular_motion_vectors - .default_view, - ), - screen_space_subsurface_scattering_guide: None, // TODO - bias: None, // TODO - dlss_output: &view_target.destination, - reset: dlss.reset, - jitter_offset: -temporal_jitter.offset, - partial_texture_size: Some(render_resolution), - motion_vector_scale: Some(-render_resolution.as_vec2()), - }; - - let diagnostics = render_context.diagnostic_recorder(); - let command_encoder = render_context.command_encoder(); - let mut dlss_context = dlss_context.context.lock().unwrap(); - - command_encoder.push_debug_group("dlss_ray_reconstruction"); - let time_span = diagnostics.time_span(command_encoder, "dlss_ray_reconstruction"); - - dlss_context - .render(render_parameters, command_encoder, &adapter) - .expect("Failed to render DLSS Ray Reconstruction"); - - time_span.end(command_encoder); - command_encoder.pop_debug_group(); - - Ok(()) - } +pub fn dlss_ray_reconstruction( + view: ViewQuery<( + &Dlss, + &DlssRenderContext, + &MainPassResolutionOverride, + &TemporalJitter, + &ViewTarget, + &ViewPrepassTextures, + &ViewDlssRayReconstructionTextures, + )>, + adapter: Res, + mut ctx: RenderContext, +) { + let ( + dlss, + dlss_context, + resolution_override, + temporal_jitter, + view_target, + prepass_textures, + ray_reconstruction_textures, + ) = view.into_inner(); + + let (Some(prepass_depth_texture), Some(prepass_motion_vectors_texture)) = + (&prepass_textures.depth, &prepass_textures.motion_vectors) + else { + return; + }; + + let view_target = view_target.post_process_write(); + + let render_resolution = resolution_override.0; + let render_parameters = DlssRayReconstructionRenderParameters { + diffuse_albedo: &ray_reconstruction_textures.diffuse_albedo.default_view, + specular_albedo: &ray_reconstruction_textures.specular_albedo.default_view, + normals: &ray_reconstruction_textures.normal_roughness.default_view, + roughness: None, + color: &view_target.source, + depth: &prepass_depth_texture.texture.default_view, + motion_vectors: &prepass_motion_vectors_texture.texture.default_view, + specular_guide: DlssRayReconstructionSpecularGuide::SpecularMotionVectors( + &ray_reconstruction_textures + .specular_motion_vectors + .default_view, + ), + screen_space_subsurface_scattering_guide: None, // TODO + bias: None, // TODO + dlss_output: &view_target.destination, + reset: dlss.reset, + jitter_offset: -temporal_jitter.offset, + partial_texture_size: Some(render_resolution), + motion_vector_scale: Some(-render_resolution.as_vec2()), + }; + + let diagnostics = ctx.diagnostic_recorder(); + let diagnostics = diagnostics.as_deref(); + let time_span = diagnostics.time_span(ctx.command_encoder(), "dlss_ray_reconstruction"); + + let command_encoder = ctx.command_encoder(); + let mut dlss_context = dlss_context.context.lock().unwrap(); + + command_encoder.push_debug_group("dlss_ray_reconstruction"); + + dlss_context + .render(render_parameters, command_encoder, &adapter) + .expect("Failed to render DLSS Ray Reconstruction"); + + command_encoder.pop_debug_group(); + time_span.end(ctx.command_encoder()); } diff --git a/crates/bevy_anti_alias/src/fxaa/mod.rs b/crates/bevy_anti_alias/src/fxaa/mod.rs index 302fa572cf0a6..1a9ef28e199e1 100644 --- a/crates/bevy_anti_alias/src/fxaa/mod.rs +++ b/crates/bevy_anti_alias/src/fxaa/mod.rs @@ -2,8 +2,8 @@ use bevy_app::prelude::*; use bevy_asset::{embedded_asset, load_embedded_asset, AssetServer, Handle}; use bevy_camera::Camera; use bevy_core_pipeline::{ - core_2d::graph::{Core2d, Node2d}, - core_3d::graph::{Core3d, Node3d}, + schedule::{Core2d, Core2dSystems, Core3d, Core3dSystems}, + tonemapping::tonemapping, FullscreenShader, }; use bevy_ecs::prelude::*; @@ -11,7 +11,6 @@ use bevy_image::BevyDefault as _; use bevy_reflect::{std_traits::ReflectDefault, Reflect}; use bevy_render::{ extract_component::{ExtractComponent, ExtractComponentPlugin}, - render_graph::{RenderGraphExt, ViewNodeRunner}, render_resource::{ binding_types::{sampler, texture_2d}, *, @@ -25,7 +24,7 @@ use bevy_utils::default; mod node; -pub use node::FxaaNode; +pub(crate) use node::fxaa; #[derive(Debug, Reflect, Eq, PartialEq, Hash, Clone, Copy)] #[reflect(PartialEq, Hash, Clone)] @@ -100,23 +99,15 @@ impl Plugin for FxaaPlugin { Render, prepare_fxaa_pipelines.in_set(RenderSystems::Prepare), ) - .add_render_graph_node::>(Core3d, Node3d::Fxaa) - .add_render_graph_edges( + .add_systems( Core3d, - ( - Node3d::Tonemapping, - Node3d::Fxaa, - Node3d::EndMainPassPostProcessing, - ), + fxaa.after(tonemapping) + .before(Core3dSystems::EndMainPassPostProcessing), ) - .add_render_graph_node::>(Core2d, Node2d::Fxaa) - .add_render_graph_edges( + .add_systems( Core2d, - ( - Node2d::Tonemapping, - Node2d::Fxaa, - Node2d::EndMainPassPostProcessing, - ), + fxaa.after(tonemapping) + .before(Core2dSystems::EndMainPassPostProcessing), ); } } diff --git a/crates/bevy_anti_alias/src/fxaa/node.rs b/crates/bevy_anti_alias/src/fxaa/node.rs index bf520b776c4c5..9611cf2094c26 100644 --- a/crates/bevy_anti_alias/src/fxaa/node.rs +++ b/crates/bevy_anti_alias/src/fxaa/node.rs @@ -1,92 +1,73 @@ -use std::sync::Mutex; - use crate::fxaa::{CameraFxaaPipeline, Fxaa, FxaaPipeline}; -use bevy_ecs::{prelude::*, query::QueryItem}; +use bevy_ecs::prelude::*; use bevy_render::{ diagnostic::RecordDiagnostics, - render_graph::{NodeRunError, RenderGraphContext, ViewNode}, render_resource::{ BindGroup, BindGroupEntries, Operations, PipelineCache, RenderPassColorAttachment, RenderPassDescriptor, TextureViewId, }, - renderer::RenderContext, + renderer::{RenderContext, ViewQuery}, view::ViewTarget, }; -#[derive(Default)] -pub struct FxaaNode { - cached_texture_bind_group: Mutex>, -} - -impl ViewNode for FxaaNode { - type ViewQuery = ( - &'static ViewTarget, - &'static CameraFxaaPipeline, - &'static Fxaa, - ); - - fn run( - &self, - _graph: &mut RenderGraphContext, - render_context: &mut RenderContext, - (target, pipeline, fxaa): QueryItem, - world: &World, - ) -> Result<(), NodeRunError> { - let pipeline_cache = world.resource::(); - let fxaa_pipeline = world.resource::(); +pub(crate) fn fxaa( + view: ViewQuery<(&ViewTarget, &CameraFxaaPipeline, &Fxaa)>, + fxaa_pipeline: Res, + pipeline_cache: Res, + mut ctx: RenderContext, + mut cached_bind_group: Local>, +) { + let (target, pipeline, fxaa) = view.into_inner(); - if !fxaa.enabled { - return Ok(()); - }; + if !fxaa.enabled { + return; + } - let Some(pipeline) = pipeline_cache.get_render_pipeline(pipeline.pipeline_id) else { - return Ok(()); - }; + let Some(pipeline) = pipeline_cache.get_render_pipeline(pipeline.pipeline_id) else { + return; + }; - let diagnostics = render_context.diagnostic_recorder(); + let post_process = target.post_process_write(); + let source = post_process.source; + let destination = post_process.destination; + let bind_group = match &mut *cached_bind_group { + Some((id, bind_group)) if source.id() == *id => bind_group, + cached => { + let bind_group = ctx.render_device().create_bind_group( + None, + &pipeline_cache.get_bind_group_layout(&fxaa_pipeline.texture_bind_group), + &BindGroupEntries::sequential((source, &fxaa_pipeline.sampler)), + ); - let post_process = target.post_process_write(); - let source = post_process.source; - let destination = post_process.destination; - let mut cached_bind_group = self.cached_texture_bind_group.lock().unwrap(); - let bind_group = match &mut *cached_bind_group { - Some((id, bind_group)) if source.id() == *id => bind_group, - cached_bind_group => { - let bind_group = render_context.render_device().create_bind_group( - None, - &pipeline_cache.get_bind_group_layout(&fxaa_pipeline.texture_bind_group), - &BindGroupEntries::sequential((source, &fxaa_pipeline.sampler)), - ); + let (_, bind_group) = cached.insert((source.id(), bind_group)); + bind_group + } + }; - let (_, bind_group) = cached_bind_group.insert((source.id(), bind_group)); - bind_group - } - }; + let pass_descriptor = RenderPassDescriptor { + label: Some("fxaa"), + color_attachments: &[Some(RenderPassColorAttachment { + view: destination, + depth_slice: None, + resolve_target: None, + ops: Operations::default(), + })], + depth_stencil_attachment: None, + timestamp_writes: None, + occlusion_query_set: None, + }; - let pass_descriptor = RenderPassDescriptor { - label: Some("fxaa"), - color_attachments: &[Some(RenderPassColorAttachment { - view: destination, - depth_slice: None, - resolve_target: None, - ops: Operations::default(), - })], - depth_stencil_attachment: None, - timestamp_writes: None, - occlusion_query_set: None, - }; + let diagnostics = ctx.diagnostic_recorder(); + let diagnostics = diagnostics.as_deref(); + let time_span = diagnostics.time_span(ctx.command_encoder(), "fxaa"); - let mut render_pass = render_context - .command_encoder() - .begin_render_pass(&pass_descriptor); - let pass_span = diagnostics.pass_span(&mut render_pass, "fxaa"); + { + let mut render_pass = ctx.command_encoder().begin_render_pass(&pass_descriptor); render_pass.set_pipeline(pipeline); render_pass.set_bind_group(0, bind_group, &[]); render_pass.draw(0..3, 0..1); - - pass_span.end(&mut render_pass); - - Ok(()) } + + time_span.end(ctx.command_encoder()); } diff --git a/crates/bevy_anti_alias/src/smaa/mod.rs b/crates/bevy_anti_alias/src/smaa/mod.rs index ee28af6b69bb7..3b0275a378e69 100644 --- a/crates/bevy_anti_alias/src/smaa/mod.rs +++ b/crates/bevy_anti_alias/src/smaa/mod.rs @@ -34,19 +34,18 @@ use bevy_asset::{embedded_asset, load_embedded_asset, AssetServer, Handle}; #[cfg(not(feature = "smaa_luts"))] use bevy_core_pipeline::tonemapping::lut_placeholder; use bevy_core_pipeline::{ - core_2d::graph::{Core2d, Node2d}, - core_3d::graph::{Core3d, Node3d}, + schedule::{Core2d, Core2dSystems, Core3d, Core3dSystems}, + tonemapping::tonemapping, }; use bevy_derive::{Deref, DerefMut}; use bevy_ecs::{ component::Component, entity::Entity, - query::{QueryItem, With}, + query::With, reflect::ReflectComponent, resource::Resource, schedule::IntoScheduleConfigs as _, - system::{lifetimeless::Read, Commands, Query, Res, ResMut}, - world::World, + system::{Commands, Query, Res, ResMut}, }; use bevy_image::{BevyDefault, Image, ToExtents}; use bevy_math::{vec4, Vec4}; @@ -56,9 +55,6 @@ use bevy_render::{ diagnostic::RecordDiagnostics, extract_component::{ExtractComponent, ExtractComponentPlugin}, render_asset::RenderAssets, - render_graph::{ - NodeRunError, RenderGraphContext, RenderGraphExt as _, ViewNode, ViewNodeRunner, - }, render_resource::{ binding_types::{sampler, texture_2d, uniform_buffer}, AddressMode, BindGroup, BindGroupEntries, BindGroupLayoutDescriptor, @@ -71,7 +67,7 @@ use bevy_render::{ StencilState, StoreOp, TextureDescriptor, TextureDimension, TextureFormat, TextureSampleType, TextureUsages, TextureView, VertexState, }, - renderer::{RenderContext, RenderDevice, RenderQueue}, + renderer::{RenderContext, RenderDevice, RenderQueue, ViewQuery}, texture::{CachedTexture, GpuImage, TextureCache}, view::{ExtractedView, ViewTarget}, Render, RenderApp, RenderStartup, RenderSystems, @@ -193,10 +189,6 @@ pub struct ViewSmaaPipelines { neighborhood_blending_pipeline_id: CachedRenderPipelineId, } -/// The render graph node that performs subpixel morphological antialiasing -/// (SMAA). -#[derive(Default)] -pub struct SmaaNode; /// Values supplied to the GPU for SMAA. /// @@ -352,23 +344,15 @@ impl Plugin for SmaaPlugin { prepare_smaa_bind_groups.in_set(RenderSystems::PrepareBindGroups), ), ) - .add_render_graph_node::>(Core3d, Node3d::Smaa) - .add_render_graph_edges( + .add_systems( Core3d, - ( - Node3d::Tonemapping, - Node3d::Smaa, - Node3d::EndMainPassPostProcessing, - ), + smaa.after(tonemapping) + .before(Core3dSystems::EndMainPassPostProcessing), ) - .add_render_graph_node::>(Core2d, Node2d::Smaa) - .add_render_graph_edges( + .add_systems( Core2d, - ( - Node2d::Tonemapping, - Node2d::Smaa, - Node2d::EndMainPassPostProcessing, - ), + smaa.after(tonemapping) + .before(Core2dSystems::EndMainPassPostProcessing), ); } } @@ -805,109 +789,100 @@ fn prepare_smaa_bind_groups( } } -impl ViewNode for SmaaNode { - type ViewQuery = ( - Read, - Read, - Read, - Read, - Read, - ); - - fn run<'w>( - &self, - _: &mut RenderGraphContext, - render_context: &mut RenderContext<'w>, - ( - view_target, - view_pipelines, - view_smaa_uniform_offset, - smaa_textures, - view_smaa_bind_groups, - ): QueryItem<'w, '_, Self::ViewQuery>, - world: &'w World, - ) -> Result<(), NodeRunError> { - let pipeline_cache = world.resource::(); - let smaa_pipelines = world.resource::(); - let smaa_info_uniform_buffer = world.resource::(); - - // Fetch the render pipelines. - let ( - Some(edge_detection_pipeline), - Some(blending_weight_calculation_pipeline), - Some(neighborhood_blending_pipeline), - ) = ( - pipeline_cache.get_render_pipeline(view_pipelines.edge_detection_pipeline_id), - pipeline_cache - .get_render_pipeline(view_pipelines.blending_weight_calculation_pipeline_id), - pipeline_cache.get_render_pipeline(view_pipelines.neighborhood_blending_pipeline_id), - ) - else { - return Ok(()); - }; +impl SmaaPreset { + /// Returns the `#define` in the shader corresponding to this quality + /// preset. + fn shader_def(&self) -> ShaderDefVal { + match *self { + SmaaPreset::Low => "SMAA_PRESET_LOW".into(), + SmaaPreset::Medium => "SMAA_PRESET_MEDIUM".into(), + SmaaPreset::High => "SMAA_PRESET_HIGH".into(), + SmaaPreset::Ultra => "SMAA_PRESET_ULTRA".into(), + } + } +} - let diagnostics = render_context.diagnostic_recorder(); - render_context.command_encoder().push_debug_group("smaa"); - let time_span = diagnostics.time_span(render_context.command_encoder(), "smaa"); - - // Fetch the framebuffer textures. - let postprocess = view_target.post_process_write(); - let (source, destination) = (postprocess.source, postprocess.destination); - - // Stage 1: Edge detection pass. - perform_edge_detection( - render_context, - pipeline_cache, - smaa_pipelines, - smaa_textures, - view_smaa_bind_groups, - smaa_info_uniform_buffer, - view_smaa_uniform_offset, - edge_detection_pipeline, - source, - ); +pub(crate) fn smaa( + view: ViewQuery<( + &ViewTarget, + &ViewSmaaPipelines, + &SmaaInfoUniformOffset, + &SmaaTextures, + &SmaaBindGroups, + )>, + smaa_pipelines: Res, + smaa_info_uniform_buffer: Res, + pipeline_cache: Res, + mut ctx: RenderContext, +) { + let (view_target, view_pipelines, view_smaa_uniform_offset, smaa_textures, view_smaa_bind_groups) = + view.into_inner(); + + let ( + Some(edge_detection_pipeline), + Some(blending_weight_calculation_pipeline), + Some(neighborhood_blending_pipeline), + ) = ( + pipeline_cache.get_render_pipeline(view_pipelines.edge_detection_pipeline_id), + pipeline_cache.get_render_pipeline(view_pipelines.blending_weight_calculation_pipeline_id), + pipeline_cache.get_render_pipeline(view_pipelines.neighborhood_blending_pipeline_id), + ) + else { + return; + }; - // Stage 2: Blending weight calculation pass. - perform_blending_weight_calculation( - render_context, - pipeline_cache, - smaa_pipelines, - smaa_textures, - view_smaa_bind_groups, - smaa_info_uniform_buffer, - view_smaa_uniform_offset, - blending_weight_calculation_pipeline, - source, - ); + let diagnostics = ctx.diagnostic_recorder(); + let diagnostics = diagnostics.as_deref(); + let time_span = diagnostics.time_span(ctx.command_encoder(), "smaa"); + + ctx.command_encoder().push_debug_group("smaa"); + + let postprocess = view_target.post_process_write(); + let (source, destination) = (postprocess.source, postprocess.destination); + + perform_edge_detection( + &mut ctx, + &pipeline_cache, + &smaa_pipelines, + smaa_textures, + view_smaa_bind_groups, + &smaa_info_uniform_buffer, + view_smaa_uniform_offset, + edge_detection_pipeline, + source, + ); - // Stage 3: Neighborhood blending pass. - perform_neighborhood_blending( - render_context, - pipeline_cache, - smaa_pipelines, - view_smaa_bind_groups, - smaa_info_uniform_buffer, - view_smaa_uniform_offset, - neighborhood_blending_pipeline, - source, - destination, - ); + perform_blending_weight_calculation( + &mut ctx, + &pipeline_cache, + &smaa_pipelines, + smaa_textures, + view_smaa_bind_groups, + &smaa_info_uniform_buffer, + view_smaa_uniform_offset, + blending_weight_calculation_pipeline, + source, + ); - time_span.end(render_context.command_encoder()); - render_context.command_encoder().pop_debug_group(); + perform_neighborhood_blending( + &mut ctx, + &pipeline_cache, + &smaa_pipelines, + view_smaa_bind_groups, + &smaa_info_uniform_buffer, + view_smaa_uniform_offset, + neighborhood_blending_pipeline, + source, + destination, + ); - Ok(()) - } + ctx.command_encoder().pop_debug_group(); + time_span.end(ctx.command_encoder()); } /// Performs edge detection (phase 1). -/// -/// This runs as part of the [`SmaaNode`]. It reads from the source texture and -/// writes to the two-channel RG edges texture. Additionally, it ensures that -/// all pixels it didn't touch are stenciled out so that phase 2 won't have to -/// examine them. fn perform_edge_detection( - render_context: &mut RenderContext, + ctx: &mut RenderContext, pipeline_cache: &PipelineCache, smaa_pipelines: &SmaaPipelines, smaa_textures: &SmaaTextures, @@ -917,15 +892,13 @@ fn perform_edge_detection( edge_detection_pipeline: &RenderPipeline, source: &TextureView, ) { - // Create the edge detection bind group. - let postprocess_bind_group = render_context.render_device().create_bind_group( + let postprocess_bind_group = ctx.render_device().create_bind_group( None, &pipeline_cache .get_bind_group_layout(&smaa_pipelines.edge_detection.postprocess_bind_group_layout), &BindGroupEntries::sequential((source, &**smaa_info_uniform_buffer)), ); - // Create the edge detection pass descriptor. let pass_descriptor = RenderPassDescriptor { label: Some("SMAA edge detection pass"), color_attachments: &[Some(RenderPassColorAttachment { @@ -946,10 +919,7 @@ fn perform_edge_detection( occlusion_query_set: None, }; - // Run the actual render pass. - let mut render_pass = render_context - .command_encoder() - .begin_render_pass(&pass_descriptor); + let mut render_pass = ctx.command_encoder().begin_render_pass(&pass_descriptor); render_pass.set_pipeline(edge_detection_pipeline); render_pass.set_bind_group(0, &postprocess_bind_group, &[**view_smaa_uniform_offset]); render_pass.set_bind_group(1, &view_smaa_bind_groups.edge_detection_bind_group, &[]); @@ -958,12 +928,8 @@ fn perform_edge_detection( } /// Performs blending weight calculation (phase 2). -/// -/// This runs as part of the [`SmaaNode`]. It reads the edges texture and writes -/// to the blend weight texture, using the stencil buffer to avoid processing -/// pixels it doesn't need to examine. fn perform_blending_weight_calculation( - render_context: &mut RenderContext, + ctx: &mut RenderContext, pipeline_cache: &PipelineCache, smaa_pipelines: &SmaaPipelines, smaa_textures: &SmaaTextures, @@ -973,8 +939,7 @@ fn perform_blending_weight_calculation( blending_weight_calculation_pipeline: &RenderPipeline, source: &TextureView, ) { - // Create the blending weight calculation bind group. - let postprocess_bind_group = render_context.render_device().create_bind_group( + let postprocess_bind_group = ctx.render_device().create_bind_group( None, &pipeline_cache.get_bind_group_layout( &smaa_pipelines @@ -984,7 +949,6 @@ fn perform_blending_weight_calculation( &BindGroupEntries::sequential((source, &**smaa_info_uniform_buffer)), ); - // Create the blending weight calculation pass descriptor. let pass_descriptor = RenderPassDescriptor { label: Some("SMAA blending weight calculation pass"), color_attachments: &[Some(RenderPassColorAttachment { @@ -1005,10 +969,7 @@ fn perform_blending_weight_calculation( occlusion_query_set: None, }; - // Run the actual render pass. - let mut render_pass = render_context - .command_encoder() - .begin_render_pass(&pass_descriptor); + let mut render_pass = ctx.command_encoder().begin_render_pass(&pass_descriptor); render_pass.set_pipeline(blending_weight_calculation_pipeline); render_pass.set_bind_group(0, &postprocess_bind_group, &[**view_smaa_uniform_offset]); render_pass.set_bind_group( @@ -1021,11 +982,8 @@ fn perform_blending_weight_calculation( } /// Performs blending weight calculation (phase 3). -/// -/// This runs as part of the [`SmaaNode`]. It reads from the blend weight -/// texture. It's the only phase that writes to the postprocessing destination. fn perform_neighborhood_blending( - render_context: &mut RenderContext, + ctx: &mut RenderContext, pipeline_cache: &PipelineCache, smaa_pipelines: &SmaaPipelines, view_smaa_bind_groups: &SmaaBindGroups, @@ -1035,7 +993,7 @@ fn perform_neighborhood_blending( source: &TextureView, destination: &TextureView, ) { - let postprocess_bind_group = render_context.render_device().create_bind_group( + let postprocess_bind_group = ctx.render_device().create_bind_group( None, &pipeline_cache.get_bind_group_layout( &smaa_pipelines @@ -1058,32 +1016,13 @@ fn perform_neighborhood_blending( occlusion_query_set: None, }; - let mut neighborhood_blending_render_pass = render_context - .command_encoder() - .begin_render_pass(&pass_descriptor); - neighborhood_blending_render_pass.set_pipeline(neighborhood_blending_pipeline); - neighborhood_blending_render_pass.set_bind_group( - 0, - &postprocess_bind_group, - &[**view_smaa_uniform_offset], - ); - neighborhood_blending_render_pass.set_bind_group( + let mut render_pass = ctx.command_encoder().begin_render_pass(&pass_descriptor); + render_pass.set_pipeline(neighborhood_blending_pipeline); + render_pass.set_bind_group(0, &postprocess_bind_group, &[**view_smaa_uniform_offset]); + render_pass.set_bind_group( 1, &view_smaa_bind_groups.neighborhood_blending_bind_group, &[], ); - neighborhood_blending_render_pass.draw(0..3, 0..1); -} - -impl SmaaPreset { - /// Returns the `#define` in the shader corresponding to this quality - /// preset. - fn shader_def(&self) -> ShaderDefVal { - match *self { - SmaaPreset::Low => "SMAA_PRESET_LOW".into(), - SmaaPreset::Medium => "SMAA_PRESET_MEDIUM".into(), - SmaaPreset::High => "SMAA_PRESET_HIGH".into(), - SmaaPreset::Ultra => "SMAA_PRESET_ULTRA".into(), - } - } + render_pass.draw(0..3, 0..1); } diff --git a/crates/bevy_anti_alias/src/taa/mod.rs b/crates/bevy_anti_alias/src/taa/mod.rs index 377de519c1560..995f98db9dea8 100644 --- a/crates/bevy_anti_alias/src/taa/mod.rs +++ b/crates/bevy_anti_alias/src/taa/mod.rs @@ -2,19 +2,18 @@ use bevy_app::{App, Plugin}; use bevy_asset::{embedded_asset, load_embedded_asset, AssetServer}; use bevy_camera::{Camera, Camera3d}; use bevy_core_pipeline::{ - core_3d::graph::{Core3d, Node3d}, prepass::{DepthPrepass, MotionVectorPrepass, ViewPrepassTextures}, + schedule::{Core3d, Core3dSystems}, FullscreenShader, }; use bevy_diagnostic::FrameCount; use bevy_ecs::{ error::BevyError, prelude::{Component, Entity, ReflectComponent}, - query::{QueryItem, With}, + query::With, resource::Resource, schedule::IntoScheduleConfigs, system::{Commands, Query, Res, ResMut}, - world::World, }; use bevy_image::{BevyDefault as _, ToExtents}; use bevy_math::vec2; @@ -22,7 +21,6 @@ use bevy_reflect::{std_traits::ReflectDefault, Reflect}; use bevy_render::{ camera::{ExtractedCamera, MipBias, TemporalJitter}, diagnostic::RecordDiagnostics, - render_graph::{NodeRunError, RenderGraphContext, RenderGraphExt, ViewNode, ViewNodeRunner}, render_resource::{ binding_types::{sampler, texture_2d, texture_depth_2d}, BindGroupEntries, BindGroupLayoutDescriptor, BindGroupLayoutEntries, @@ -32,7 +30,7 @@ use bevy_render::{ ShaderStages, Specializer, SpecializerKey, TextureDescriptor, TextureDimension, TextureFormat, TextureSampleType, TextureUsages, Variants, }, - renderer::{RenderContext, RenderDevice}, + renderer::{RenderContext, RenderDevice, ViewQuery}, sync_component::SyncComponentPlugin, sync_world::RenderEntity, texture::{CachedTexture, TextureCache}, @@ -67,18 +65,14 @@ impl Plugin for TemporalAntiAliasPlugin { prepare_taa_pipelines.in_set(RenderSystems::Prepare), prepare_taa_history_textures.in_set(RenderSystems::PrepareResources), ), - ) - .add_render_graph_node::>(Core3d, Node3d::Taa) - .add_render_graph_edges( - Core3d, - ( - Node3d::StartMainPassPostProcessing, - Node3d::MotionBlur, // Running before TAA reduces edge artifacts and noise - Node3d::Taa, - Node3d::Bloom, - Node3d::Tonemapping, - ), ); + + render_app.add_systems( + Core3d, + temporal_anti_alias + .after(Core3dSystems::StartMainPassPostProcessing) + .before(Core3dSystems::PostProcessing), + ); } } @@ -137,100 +131,86 @@ impl Default for TemporalAntiAliasing { } } -/// Render [`bevy_render::render_graph::Node`] used by temporal anti-aliasing. -#[derive(Default)] -pub struct TemporalAntiAliasNode; - -impl ViewNode for TemporalAntiAliasNode { - type ViewQuery = ( - &'static ExtractedCamera, - &'static ViewTarget, - &'static TemporalAntiAliasHistoryTextures, - &'static ViewPrepassTextures, - &'static TemporalAntiAliasPipelineId, - &'static Msaa, - ); - - fn run( - &self, - _graph: &mut RenderGraphContext, - render_context: &mut RenderContext, - (camera, view_target, taa_history_textures, prepass_textures, taa_pipeline_id, msaa): QueryItem< - Self::ViewQuery, - >, - world: &World, - ) -> Result<(), NodeRunError> { - if *msaa != Msaa::Off { - warn!("Temporal anti-aliasing requires MSAA to be disabled"); - return Ok(()); - } +fn temporal_anti_alias( + view: ViewQuery<( + &ExtractedCamera, + &ViewTarget, + &TemporalAntiAliasHistoryTextures, + &ViewPrepassTextures, + &TemporalAntiAliasPipelineId, + &Msaa, + )>, + pipelines: Option>, + pipeline_cache: Res, + mut ctx: RenderContext, +) { + let (camera, view_target, taa_history_textures, prepass_textures, taa_pipeline_id, msaa) = + view.into_inner(); - let (Some(pipelines), Some(pipeline_cache)) = ( - world.get_resource::(), - world.get_resource::(), - ) else { - return Ok(()); - }; - let (Some(taa_pipeline), Some(prepass_motion_vectors_texture), Some(prepass_depth_texture)) = ( - pipeline_cache.get_render_pipeline(taa_pipeline_id.0), - &prepass_textures.motion_vectors, - &prepass_textures.depth, - ) else { - return Ok(()); - }; + if *msaa != Msaa::Off { + warn!("Temporal anti-aliasing requires MSAA to be disabled"); + return; + } - let diagnostics = render_context.diagnostic_recorder(); - - let view_target = view_target.post_process_write(); - - let taa_bind_group = render_context.render_device().create_bind_group( - "taa_bind_group", - &pipeline_cache.get_bind_group_layout(&pipelines.taa_bind_group_layout), - &BindGroupEntries::sequential(( - view_target.source, - &taa_history_textures.read.default_view, - &prepass_motion_vectors_texture.texture.default_view, - &prepass_depth_texture.texture.default_view, - &pipelines.nearest_sampler, - &pipelines.linear_sampler, - )), - ); + let Some(pipelines) = pipelines else { + return; + }; + let (Some(taa_pipeline), Some(prepass_motion_vectors_texture), Some(prepass_depth_texture)) = ( + pipeline_cache.get_render_pipeline(taa_pipeline_id.0), + &prepass_textures.motion_vectors, + &prepass_textures.depth, + ) else { + return; + }; + + let view_target = view_target.post_process_write(); + + let taa_bind_group = ctx.render_device().create_bind_group( + "taa_bind_group", + &pipeline_cache.get_bind_group_layout(&pipelines.taa_bind_group_layout), + &BindGroupEntries::sequential(( + view_target.source, + &taa_history_textures.read.default_view, + &prepass_motion_vectors_texture.texture.default_view, + &prepass_depth_texture.texture.default_view, + &pipelines.nearest_sampler, + &pipelines.linear_sampler, + )), + ); - { - let mut taa_pass = render_context.begin_tracked_render_pass(RenderPassDescriptor { - label: Some("taa"), - color_attachments: &[ - Some(RenderPassColorAttachment { - view: view_target.destination, - depth_slice: None, - resolve_target: None, - ops: Operations::default(), - }), - Some(RenderPassColorAttachment { - view: &taa_history_textures.write.default_view, - depth_slice: None, - resolve_target: None, - ops: Operations::default(), - }), - ], - depth_stencil_attachment: None, - timestamp_writes: None, - occlusion_query_set: None, - }); - let pass_span = diagnostics.pass_span(&mut taa_pass, "taa"); - - taa_pass.set_render_pipeline(taa_pipeline); - taa_pass.set_bind_group(0, &taa_bind_group, &[]); - if let Some(viewport) = camera.viewport.as_ref() { - taa_pass.set_camera_viewport(viewport); - } - taa_pass.draw(0..3, 0..1); - - pass_span.end(&mut taa_pass); - } + let diagnostics = ctx.diagnostic_recorder(); + let diagnostics = diagnostics.as_deref(); + + let mut taa_pass = ctx.begin_tracked_render_pass(RenderPassDescriptor { + label: Some("taa"), + color_attachments: &[ + Some(RenderPassColorAttachment { + view: view_target.destination, + depth_slice: None, + resolve_target: None, + ops: Operations::default(), + }), + Some(RenderPassColorAttachment { + view: &taa_history_textures.write.default_view, + depth_slice: None, + resolve_target: None, + ops: Operations::default(), + }), + ], + depth_stencil_attachment: None, + timestamp_writes: None, + occlusion_query_set: None, + }); + let pass_span = diagnostics.pass_span(&mut taa_pass, "taa"); - Ok(()) + taa_pass.set_render_pipeline(taa_pipeline); + taa_pass.set_bind_group(0, &taa_bind_group, &[]); + if let Some(viewport) = camera.viewport.as_ref() { + taa_pass.set_camera_viewport(viewport); } + taa_pass.draw(0..3, 0..1); + + pass_span.end(&mut taa_pass); } #[derive(Resource)] diff --git a/crates/bevy_core_pipeline/src/core_2d/main_opaque_pass_2d_node.rs b/crates/bevy_core_pipeline/src/core_2d/main_opaque_pass_2d_node.rs index e8cd0c65c6888..369345ae9134b 100644 --- a/crates/bevy_core_pipeline/src/core_2d/main_opaque_pass_2d_node.rs +++ b/crates/bevy_core_pipeline/src/core_2d/main_opaque_pass_2d_node.rs @@ -1,12 +1,11 @@ use crate::core_2d::Opaque2d; -use bevy_ecs::{prelude::World, query::QueryItem}; +use bevy_ecs::prelude::*; use bevy_render::{ camera::ExtractedCamera, diagnostic::RecordDiagnostics, - render_graph::{NodeRunError, RenderGraphContext, ViewNode}, - render_phase::{TrackedRenderPass, ViewBinnedRenderPhases}, - render_resource::{CommandEncoderDescriptor, RenderPassDescriptor, StoreOp}, - renderer::RenderContext, + render_phase::ViewBinnedRenderPhases, + render_resource::{RenderPassDescriptor, StoreOp}, + renderer::{RenderContext, ViewQuery}, view::{ExtractedView, ViewDepthTexture, ViewTarget}, }; use tracing::error; @@ -15,92 +14,70 @@ use tracing::info_span; use super::AlphaMask2d; -/// A [`bevy_render::render_graph::Node`] that runs the -/// [`Opaque2d`] [`ViewBinnedRenderPhases`] and [`AlphaMask2d`] [`ViewBinnedRenderPhases`] -#[derive(Default)] -pub struct MainOpaquePass2dNode; -impl ViewNode for MainOpaquePass2dNode { - type ViewQuery = ( - &'static ExtractedCamera, - &'static ExtractedView, - &'static ViewTarget, - &'static ViewDepthTexture, - ); +pub fn main_opaque_pass_2d( + world: &World, + view: ViewQuery<( + &ExtractedCamera, + &ExtractedView, + &ViewTarget, + &ViewDepthTexture, + )>, + opaque_phases: Res>, + alpha_mask_phases: Res>, + mut ctx: RenderContext, +) { + let view_entity = view.entity(); + let (camera, extracted_view, target, depth) = view.into_inner(); - fn run<'w>( - &self, - graph: &mut RenderGraphContext, - render_context: &mut RenderContext<'w>, - (camera, view, target, depth): QueryItem<'w, '_, Self::ViewQuery>, - world: &'w World, - ) -> Result<(), NodeRunError> { - let (Some(opaque_phases), Some(alpha_mask_phases)) = ( - world.get_resource::>(), - world.get_resource::>(), - ) else { - return Ok(()); - }; + let (Some(opaque_phase), Some(alpha_mask_phase)) = ( + opaque_phases.get(&extracted_view.retained_view_entity), + alpha_mask_phases.get(&extracted_view.retained_view_entity), + ) else { + return; + }; - let diagnostics = render_context.diagnostic_recorder(); - - let color_attachments = [Some(target.get_color_attachment())]; - let depth_stencil_attachment = Some(depth.get_attachment(StoreOp::Store)); - - let view_entity = graph.view_entity(); - let (Some(opaque_phase), Some(alpha_mask_phase)) = ( - opaque_phases.get(&view.retained_view_entity), - alpha_mask_phases.get(&view.retained_view_entity), - ) else { - return Ok(()); - }; - render_context.add_command_buffer_generation_task(move |render_device| { - #[cfg(feature = "trace")] - let _main_opaque_pass_2d_span = info_span!("main_opaque_pass_2d").entered(); + if opaque_phase.is_empty() && alpha_mask_phase.is_empty() { + return; + } - // Command encoder setup - let mut command_encoder = - render_device.create_command_encoder(&CommandEncoderDescriptor { - label: Some("main_opaque_pass_2d_command_encoder"), - }); + #[cfg(feature = "trace")] + let _span = info_span!("main_opaque_pass_2d").entered(); - // Render pass setup - let render_pass = command_encoder.begin_render_pass(&RenderPassDescriptor { - label: Some("main_opaque_pass_2d"), - color_attachments: &color_attachments, - depth_stencil_attachment, - timestamp_writes: None, - occlusion_query_set: None, - }); - let mut render_pass = TrackedRenderPass::new(&render_device, render_pass); - let pass_span = diagnostics.pass_span(&mut render_pass, "main_opaque_pass_2d"); + let diagnostics = ctx.diagnostic_recorder(); + let diagnostics = diagnostics.as_deref(); - if let Some(viewport) = camera.viewport.as_ref() { - render_pass.set_camera_viewport(viewport); - } + let color_attachments = [Some(target.get_color_attachment())]; + let depth_stencil_attachment = Some(depth.get_attachment(StoreOp::Store)); - // Opaque draws - if !opaque_phase.is_empty() { - #[cfg(feature = "trace")] - let _opaque_main_pass_2d_span = info_span!("opaque_main_pass_2d").entered(); - if let Err(err) = opaque_phase.render(&mut render_pass, world, view_entity) { - error!("Error encountered while rendering the 2d opaque phase {err:?}"); - } - } + let mut render_pass = ctx.begin_tracked_render_pass(RenderPassDescriptor { + label: Some("main_opaque_pass_2d"), + color_attachments: &color_attachments, + depth_stencil_attachment, + timestamp_writes: None, + occlusion_query_set: None, + }); + let pass_span = diagnostics.pass_span(&mut render_pass, "main_opaque_pass_2d"); - // Alpha mask draws - if !alpha_mask_phase.is_empty() { - #[cfg(feature = "trace")] - let _alpha_mask_main_pass_2d_span = info_span!("alpha_mask_main_pass_2d").entered(); - if let Err(err) = alpha_mask_phase.render(&mut render_pass, world, view_entity) { - error!("Error encountered while rendering the 2d alpha mask phase {err:?}"); - } - } + if let Some(viewport) = camera.viewport.as_ref() { + render_pass.set_camera_viewport(viewport); + } - pass_span.end(&mut render_pass); - drop(render_pass); - command_encoder.finish() - }); + if !opaque_phase.is_empty() { + #[cfg(feature = "trace")] + let _opaque_span = info_span!("opaque_main_pass_2d").entered(); + if let Err(err) = opaque_phase.render(&mut render_pass, world, view_entity) { + error!("Error encountered while rendering the 2d opaque phase {err:?}"); + } + } - Ok(()) + if !alpha_mask_phase.is_empty() { + #[cfg(feature = "trace")] + let _alpha_mask_span = info_span!("alpha_mask_main_pass_2d").entered(); + if let Err(err) = alpha_mask_phase.render(&mut render_pass, world, view_entity) { + error!("Error encountered while rendering the 2d alpha mask phase {err:?}"); + } } + + pass_span.end(&mut render_pass); } + diff --git a/crates/bevy_core_pipeline/src/core_2d/main_transparent_pass_2d_node.rs b/crates/bevy_core_pipeline/src/core_2d/main_transparent_pass_2d_node.rs index 4054283a5738a..a0a099f9a2eb4 100644 --- a/crates/bevy_core_pipeline/src/core_2d/main_transparent_pass_2d_node.rs +++ b/crates/bevy_core_pipeline/src/core_2d/main_transparent_pass_2d_node.rs @@ -3,118 +3,87 @@ use bevy_ecs::prelude::*; use bevy_render::{ camera::ExtractedCamera, diagnostic::RecordDiagnostics, - render_graph::{NodeRunError, RenderGraphContext, ViewNode}, - render_phase::{TrackedRenderPass, ViewSortedRenderPhases}, - render_resource::{CommandEncoderDescriptor, RenderPassDescriptor, StoreOp}, - renderer::RenderContext, + render_phase::ViewSortedRenderPhases, + render_resource::{RenderPassDescriptor, StoreOp}, + renderer::{RenderContext, ViewQuery}, view::{ExtractedView, ViewDepthTexture, ViewTarget}, }; use tracing::error; #[cfg(feature = "trace")] use tracing::info_span; -#[derive(Default)] -pub struct MainTransparentPass2dNode {} - -impl ViewNode for MainTransparentPass2dNode { - type ViewQuery = ( - &'static ExtractedCamera, - &'static ExtractedView, - &'static ViewTarget, - &'static ViewDepthTexture, - ); - - fn run<'w>( - &self, - graph: &mut RenderGraphContext, - render_context: &mut RenderContext<'w>, - (camera, view, target, depth): bevy_ecs::query::QueryItem<'w, '_, Self::ViewQuery>, - world: &'w World, - ) -> Result<(), NodeRunError> { - let Some(transparent_phases) = - world.get_resource::>() - else { - return Ok(()); - }; - - let view_entity = graph.view_entity(); - let Some(transparent_phase) = transparent_phases.get(&view.retained_view_entity) else { - return Ok(()); - }; - - let diagnostics = render_context.diagnostic_recorder(); - - let color_attachments = [Some(target.get_color_attachment())]; - // NOTE: For the transparent pass we load the depth buffer. There should be no - // need to write to it, but store is set to `true` as a workaround for issue #3776, - // https://github.com/bevyengine/bevy/issues/3776 - // so that wgpu does not clear the depth buffer. - // As the opaque and alpha mask passes run first, opaque meshes can occlude - // transparent ones. - let depth_stencil_attachment = Some(depth.get_attachment(StoreOp::Store)); - - render_context.add_command_buffer_generation_task(move |render_device| { - // Command encoder setup - let mut command_encoder = - render_device.create_command_encoder(&CommandEncoderDescriptor { - label: Some("main_transparent_pass_2d_command_encoder"), - }); - - // This needs to run at least once to clear the background color, even if there are no items to render - { - #[cfg(feature = "trace")] - let _main_pass_2d = info_span!("main_transparent_pass_2d").entered(); - - let render_pass = command_encoder.begin_render_pass(&RenderPassDescriptor { - label: Some("main_transparent_pass_2d"), - color_attachments: &color_attachments, - depth_stencil_attachment, - timestamp_writes: None, - occlusion_query_set: None, - }); - let mut render_pass = TrackedRenderPass::new(&render_device, render_pass); - - let pass_span = diagnostics.pass_span(&mut render_pass, "main_transparent_pass_2d"); - - if let Some(viewport) = camera.viewport.as_ref() { - render_pass.set_camera_viewport(viewport); - } +pub fn main_transparent_pass_2d( + world: &World, + view: ViewQuery<( + &ExtractedCamera, + &ExtractedView, + &ViewTarget, + &ViewDepthTexture, + )>, + transparent_phases: Res>, + mut ctx: RenderContext, +) { + let view_entity = view.entity(); + let (camera, extracted_view, target, depth) = view.into_inner(); + + let Some(transparent_phase) = transparent_phases.get(&extracted_view.retained_view_entity) + else { + return; + }; + + #[cfg(feature = "trace")] + let _span = info_span!("main_transparent_pass_2d").entered(); + + let diagnostics = ctx.diagnostic_recorder(); + let diagnostics = diagnostics.as_deref(); + + let color_attachments = [Some(target.get_color_attachment())]; + // NOTE: For the transparent pass we load the depth buffer. There should be no + // need to write to it, but store is set to `true` as a workaround for issue #3776, + // https://github.com/bevyengine/bevy/issues/3776 + // so that wgpu does not clear the depth buffer. + // As the opaque and alpha mask passes run first, opaque meshes can occlude + // transparent ones. + let depth_stencil_attachment = Some(depth.get_attachment(StoreOp::Store)); + + { + let mut render_pass = ctx.begin_tracked_render_pass(RenderPassDescriptor { + label: Some("main_transparent_pass_2d"), + color_attachments: &color_attachments, + depth_stencil_attachment, + timestamp_writes: None, + occlusion_query_set: None, + }); + let pass_span = diagnostics.pass_span(&mut render_pass, "main_transparent_pass_2d"); - if !transparent_phase.items.is_empty() { - #[cfg(feature = "trace")] - let _transparent_main_pass_2d_span = - info_span!("transparent_main_pass_2d").entered(); - if let Err(err) = transparent_phase.render(&mut render_pass, world, view_entity) - { - error!( - "Error encountered while rendering the transparent 2D phase {err:?}" - ); - } - } + if let Some(viewport) = camera.viewport.as_ref() { + render_pass.set_camera_viewport(viewport); + } - pass_span.end(&mut render_pass); + if !transparent_phase.items.is_empty() { + #[cfg(feature = "trace")] + let _transparent_span = info_span!("transparent_main_pass_2d").entered(); + if let Err(err) = transparent_phase.render(&mut render_pass, world, view_entity) { + error!("Error encountered while rendering the transparent 2D phase {err:?}"); } + } - // WebGL2 quirk: if ending with a render pass with a custom viewport, the viewport isn't - // reset for the next render pass so add an empty render pass without a custom viewport - #[cfg(all(feature = "webgl", target_arch = "wasm32", not(feature = "webgpu")))] - if camera.viewport.is_some() { - #[cfg(feature = "trace")] - let _reset_viewport_pass_2d = info_span!("reset_viewport_pass_2d").entered(); - let pass_descriptor = RenderPassDescriptor { - label: Some("reset_viewport_pass_2d"), - color_attachments: &[Some(target.get_color_attachment())], - depth_stencil_attachment: None, - timestamp_writes: None, - occlusion_query_set: None, - }; - - command_encoder.begin_render_pass(&pass_descriptor); - } + pass_span.end(&mut render_pass); + } - command_encoder.finish() + // WebGL2 quirk: if ending with a render pass with a custom viewport, the viewport isn't + // reset for the next render pass so add an empty render pass without a custom viewport + #[cfg(all(feature = "webgl", target_arch = "wasm32", not(feature = "webgpu")))] + if camera.viewport.is_some() { + #[cfg(feature = "trace")] + let _reset_viewport_pass_2d = info_span!("reset_viewport_pass_2d").entered(); + + let _reset_pass = ctx.begin_tracked_render_pass(RenderPassDescriptor { + label: Some("reset_viewport_pass_2d"), + color_attachments: &[Some(target.get_color_attachment())], + depth_stencil_attachment: None, + timestamp_writes: None, + occlusion_query_set: None, }); - - Ok(()) } } diff --git a/crates/bevy_core_pipeline/src/core_2d/mod.rs b/crates/bevy_core_pipeline/src/core_2d/mod.rs index dee095185d44b..f1eb40546690d 100644 --- a/crates/bevy_core_pipeline/src/core_2d/mod.rs +++ b/crates/bevy_core_pipeline/src/core_2d/mod.rs @@ -1,36 +1,6 @@ mod main_opaque_pass_2d_node; mod main_transparent_pass_2d_node; -pub mod graph { - use bevy_render::render_graph::{RenderLabel, RenderSubGraph}; - - #[derive(Debug, Hash, PartialEq, Eq, Clone, RenderSubGraph)] - pub struct Core2d; - - pub mod input { - pub const VIEW_ENTITY: &str = "view_entity"; - } - - #[derive(Debug, Hash, PartialEq, Eq, Clone, RenderLabel)] - pub enum Node2d { - MsaaWriteback, - StartMainPass, - MainOpaquePass, - MainTransparentPass, - EndMainPass, - Wireframe, - StartMainPassPostProcessing, - Bloom, - PostProcessing, - Tonemapping, - Fxaa, - Smaa, - Upscaling, - ContrastAdaptiveSharpening, - EndMainPassPostProcessing, - } -} - use core::ops::Range; use bevy_asset::UntypedAssetId; @@ -46,17 +16,16 @@ use bevy_render::{ pub use main_opaque_pass_2d_node::*; pub use main_transparent_pass_2d_node::*; -use crate::{ - tonemapping::{DebandDither, Tonemapping, TonemappingNode}, - upscaling::UpscalingNode, -}; +use crate::schedule::Core2d; +use crate::tonemapping::{tonemapping, DebandDither, Tonemapping}; +use crate::upscaling::upscaling; +use crate::Core2dSystems; use bevy_app::{App, Plugin}; use bevy_ecs::prelude::*; use bevy_math::FloatOrd; use bevy_render::{ camera::ExtractedCamera, extract_component::ExtractComponentPlugin, - render_graph::{EmptyNode, RenderGraphExt, ViewNodeRunner}, render_phase::{ sort_phase_system, BinnedPhaseItem, CachedRenderPipelinePhaseItem, DrawFunctionId, DrawFunctions, PhaseItem, PhaseItemExtraIndex, SortedPhaseItem, ViewBinnedRenderPhases, @@ -73,8 +42,6 @@ use bevy_render::{ Extract, ExtractSchedule, Render, RenderApp, RenderSystems, }; -use self::graph::{Core2d, Node2d}; - pub const CORE_2D_DEPTH_FORMAT: TextureFormat = TextureFormat::Depth32Float; pub struct Core2dPlugin; @@ -105,35 +72,21 @@ impl Plugin for Core2dPlugin { sort_phase_system::.in_set(RenderSystems::PhaseSort), prepare_core_2d_depth_textures.in_set(RenderSystems::PrepareResources), ), - ); - - render_app - .add_render_sub_graph(Core2d) - .add_render_graph_node::(Core2d, Node2d::StartMainPass) - .add_render_graph_node::>( - Core2d, - Node2d::MainOpaquePass, ) - .add_render_graph_node::>( - Core2d, - Node2d::MainTransparentPass, - ) - .add_render_graph_node::(Core2d, Node2d::EndMainPass) - .add_render_graph_node::(Core2d, Node2d::StartMainPassPostProcessing) - .add_render_graph_node::>(Core2d, Node2d::Tonemapping) - .add_render_graph_node::(Core2d, Node2d::EndMainPassPostProcessing) - .add_render_graph_node::>(Core2d, Node2d::Upscaling) - .add_render_graph_edges( + .add_schedule(Core2d::base_schedule()) + .add_systems( Core2d, ( - Node2d::StartMainPass, - Node2d::MainOpaquePass, - Node2d::MainTransparentPass, - Node2d::EndMainPass, - Node2d::StartMainPassPostProcessing, - Node2d::Tonemapping, - Node2d::EndMainPassPostProcessing, - Node2d::Upscaling, + main_opaque_pass_2d + .after(Core2dSystems::StartMainPass) + .before(Core2dSystems::EndMainPass), + main_transparent_pass_2d + .after(main_opaque_pass_2d) + .before(Core2dSystems::EndMainPass), + tonemapping + .after(Core2dSystems::StartMainPassPostProcessing) + .before(Core2dSystems::PostProcessing), + upscaling.after(Core2dSystems::EndMainPassPostProcessing), ), ); } diff --git a/crates/bevy_core_pipeline/src/core_3d/main_opaque_pass_3d_node.rs b/crates/bevy_core_pipeline/src/core_3d/main_opaque_pass_3d_node.rs index 25cf2ac75c58e..fd367ff6d4ae1 100644 --- a/crates/bevy_core_pipeline/src/core_3d/main_opaque_pass_3d_node.rs +++ b/crates/bevy_core_pipeline/src/core_3d/main_opaque_pass_3d_node.rs @@ -3,14 +3,13 @@ use crate::{ skybox::{SkyboxBindGroup, SkyboxPipelineId}, }; use bevy_camera::{MainPassResolutionOverride, Viewport}; -use bevy_ecs::{prelude::World, query::QueryItem}; +use bevy_ecs::prelude::*; use bevy_render::{ camera::ExtractedCamera, diagnostic::RecordDiagnostics, - render_graph::{NodeRunError, RenderGraphContext, ViewNode}, - render_phase::{TrackedRenderPass, ViewBinnedRenderPhases}, - render_resource::{CommandEncoderDescriptor, PipelineCache, RenderPassDescriptor, StoreOp}, - renderer::RenderContext, + render_phase::ViewBinnedRenderPhases, + render_resource::{PipelineCache, RenderPassDescriptor, StoreOp}, + renderer::{RenderContext, ViewQuery}, view::{ExtractedView, ViewDepthTexture, ViewTarget, ViewUniformOffset}, }; use tracing::error; @@ -19,124 +18,96 @@ use tracing::info_span; use super::AlphaMask3d; -/// A [`bevy_render::render_graph::Node`] that runs the [`Opaque3d`] and [`AlphaMask3d`] -/// [`ViewBinnedRenderPhases`]s. -#[derive(Default)] -pub struct MainOpaquePass3dNode; -impl ViewNode for MainOpaquePass3dNode { - type ViewQuery = ( - &'static ExtractedCamera, - &'static ExtractedView, - &'static ViewTarget, - &'static ViewDepthTexture, - Option<&'static SkyboxPipelineId>, - Option<&'static SkyboxBindGroup>, - &'static ViewUniformOffset, - Option<&'static MainPassResolutionOverride>, - ); +pub fn main_opaque_pass_3d( + world: &World, + view: ViewQuery<( + &ExtractedCamera, + &ExtractedView, + &ViewTarget, + &ViewDepthTexture, + Option<&SkyboxPipelineId>, + Option<&SkyboxBindGroup>, + &ViewUniformOffset, + Option<&MainPassResolutionOverride>, + )>, + opaque_phases: Res>, + alpha_mask_phases: Res>, + pipeline_cache: Res, + mut ctx: RenderContext, +) { + let view_entity = view.entity(); - fn run<'w>( - &self, - graph: &mut RenderGraphContext, - render_context: &mut RenderContext<'w>, - ( - camera, - extracted_view, - target, - depth, - skybox_pipeline, - skybox_bind_group, - view_uniform_offset, - resolution_override, - ): QueryItem<'w, '_, Self::ViewQuery>, - world: &'w World, - ) -> Result<(), NodeRunError> { - let (Some(opaque_phases), Some(alpha_mask_phases)) = ( - world.get_resource::>(), - world.get_resource::>(), - ) else { - return Ok(()); - }; + let ( + camera, + extracted_view, + target, + depth, + skybox_pipeline, + skybox_bind_group, + view_uniform_offset, + resolution_override, + ) = view.into_inner(); - let (Some(opaque_phase), Some(alpha_mask_phase)) = ( - opaque_phases.get(&extracted_view.retained_view_entity), - alpha_mask_phases.get(&extracted_view.retained_view_entity), - ) else { - return Ok(()); - }; + let (Some(opaque_phase), Some(alpha_mask_phase)) = ( + opaque_phases.get(&extracted_view.retained_view_entity), + alpha_mask_phases.get(&extracted_view.retained_view_entity), + ) else { + return; + }; - let diagnostics = render_context.diagnostic_recorder(); + #[cfg(feature = "trace")] + let _main_opaque_pass_3d_span = info_span!("main_opaque_pass_3d").entered(); - let color_attachments = [Some(target.get_color_attachment())]; - let depth_stencil_attachment = Some(depth.get_attachment(StoreOp::Store)); + let diagnostics = ctx.diagnostic_recorder(); + let diagnostics = diagnostics.as_deref(); - let view_entity = graph.view_entity(); - render_context.add_command_buffer_generation_task(move |render_device| { - #[cfg(feature = "trace")] - let _main_opaque_pass_3d_span = info_span!("main_opaque_pass_3d").entered(); + let color_attachments = [Some(target.get_color_attachment())]; + let depth_stencil_attachment = Some(depth.get_attachment(StoreOp::Store)); - // Command encoder setup - let mut command_encoder = - render_device.create_command_encoder(&CommandEncoderDescriptor { - label: Some("main_opaque_pass_3d_command_encoder"), - }); + let mut render_pass = ctx.begin_tracked_render_pass(RenderPassDescriptor { + label: Some("main_opaque_pass_3d"), + color_attachments: &color_attachments, + depth_stencil_attachment, + timestamp_writes: None, + occlusion_query_set: None, + }); + let pass_span = diagnostics.pass_span(&mut render_pass, "main_opaque_pass_3d"); - // Render pass setup - let render_pass = command_encoder.begin_render_pass(&RenderPassDescriptor { - label: Some("main_opaque_pass_3d"), - color_attachments: &color_attachments, - depth_stencil_attachment, - timestamp_writes: None, - occlusion_query_set: None, - }); - let mut render_pass = TrackedRenderPass::new(&render_device, render_pass); - let pass_span = diagnostics.pass_span(&mut render_pass, "main_opaque_pass_3d"); - - if let Some(viewport) = - Viewport::from_viewport_and_override(camera.viewport.as_ref(), resolution_override) - { - render_pass.set_camera_viewport(&viewport); - } - - // Opaque draws - if !opaque_phase.is_empty() { - #[cfg(feature = "trace")] - let _opaque_main_pass_3d_span = info_span!("opaque_main_pass_3d").entered(); - if let Err(err) = opaque_phase.render(&mut render_pass, world, view_entity) { - error!("Error encountered while rendering the opaque phase {err:?}"); - } - } - - // Alpha draws - if !alpha_mask_phase.is_empty() { - #[cfg(feature = "trace")] - let _alpha_mask_main_pass_3d_span = info_span!("alpha_mask_main_pass_3d").entered(); - if let Err(err) = alpha_mask_phase.render(&mut render_pass, world, view_entity) { - error!("Error encountered while rendering the alpha mask phase {err:?}"); - } - } + if let Some(viewport) = + Viewport::from_viewport_and_override(camera.viewport.as_ref(), resolution_override) + { + render_pass.set_camera_viewport(&viewport); + } - // Skybox draw using a fullscreen triangle - if let (Some(skybox_pipeline), Some(SkyboxBindGroup(skybox_bind_group))) = - (skybox_pipeline, skybox_bind_group) - { - let pipeline_cache = world.resource::(); - if let Some(pipeline) = pipeline_cache.get_render_pipeline(skybox_pipeline.0) { - render_pass.set_render_pipeline(pipeline); - render_pass.set_bind_group( - 0, - &skybox_bind_group.0, - &[view_uniform_offset.offset, skybox_bind_group.1], - ); - render_pass.draw(0..3, 0..1); - } - } + if !opaque_phase.is_empty() { + #[cfg(feature = "trace")] + let _opaque_main_pass_3d_span = info_span!("opaque_main_pass_3d").entered(); + if let Err(err) = opaque_phase.render(&mut render_pass, world, view_entity) { + error!("Error encountered while rendering the opaque phase {err:?}"); + } + } - pass_span.end(&mut render_pass); - drop(render_pass); - command_encoder.finish() - }); + if !alpha_mask_phase.is_empty() { + #[cfg(feature = "trace")] + let _alpha_mask_main_pass_3d_span = info_span!("alpha_mask_main_pass_3d").entered(); + if let Err(err) = alpha_mask_phase.render(&mut render_pass, world, view_entity) { + error!("Error encountered while rendering the alpha mask phase {err:?}"); + } + } - Ok(()) + if let (Some(skybox_pipeline), Some(SkyboxBindGroup(skybox_bind_group))) = + (skybox_pipeline, skybox_bind_group) + { + if let Some(pipeline) = pipeline_cache.get_render_pipeline(skybox_pipeline.0) { + render_pass.set_render_pipeline(pipeline); + render_pass.set_bind_group( + 0, + &skybox_bind_group.0, + &[view_uniform_offset.offset, skybox_bind_group.1], + ); + render_pass.draw(0..3, 0..1); + } } + + pass_span.end(&mut render_pass); } diff --git a/crates/bevy_core_pipeline/src/core_3d/main_transmissive_pass_3d_node.rs b/crates/bevy_core_pipeline/src/core_3d/main_transmissive_pass_3d_node.rs index 1319534bf3971..9346a4f83b2ec 100644 --- a/crates/bevy_core_pipeline/src/core_3d/main_transmissive_pass_3d_node.rs +++ b/crates/bevy_core_pipeline/src/core_3d/main_transmissive_pass_3d_node.rs @@ -1,15 +1,14 @@ use super::ViewTransmissionTexture; use crate::core_3d::Transmissive3d; use bevy_camera::{Camera3d, MainPassResolutionOverride, Viewport}; -use bevy_ecs::{prelude::*, query::QueryItem}; +use bevy_ecs::prelude::*; use bevy_image::ToExtents; use bevy_render::{ camera::ExtractedCamera, diagnostic::RecordDiagnostics, - render_graph::{NodeRunError, RenderGraphContext, ViewNode}, render_phase::ViewSortedRenderPhases, render_resource::{RenderPassDescriptor, StoreOp}, - renderer::RenderContext, + renderer::{RenderContext, ViewQuery}, view::{ExtractedView, ViewDepthTexture, ViewTarget}, }; use core::ops::Range; @@ -17,128 +16,6 @@ use tracing::error; #[cfg(feature = "trace")] use tracing::info_span; -/// A [`bevy_render::render_graph::Node`] that runs the [`Transmissive3d`] -/// [`ViewSortedRenderPhases`]. -#[derive(Default)] -pub struct MainTransmissivePass3dNode; - -impl ViewNode for MainTransmissivePass3dNode { - type ViewQuery = ( - &'static ExtractedCamera, - &'static ExtractedView, - &'static Camera3d, - &'static ViewTarget, - Option<&'static ViewTransmissionTexture>, - &'static ViewDepthTexture, - Option<&'static MainPassResolutionOverride>, - ); - - fn run( - &self, - graph: &mut RenderGraphContext, - render_context: &mut RenderContext, - (camera, view, camera_3d, target, transmission, depth, resolution_override): QueryItem< - Self::ViewQuery, - >, - world: &World, - ) -> Result<(), NodeRunError> { - let view_entity = graph.view_entity(); - - let Some(transmissive_phases) = - world.get_resource::>() - else { - return Ok(()); - }; - - let Some(transmissive_phase) = transmissive_phases.get(&view.retained_view_entity) else { - return Ok(()); - }; - - let diagnostics = render_context.diagnostic_recorder(); - - let physical_target_size = camera.physical_target_size.unwrap(); - - let render_pass_descriptor = RenderPassDescriptor { - label: Some("main_transmissive_pass_3d"), - color_attachments: &[Some(target.get_color_attachment())], - depth_stencil_attachment: Some(depth.get_attachment(StoreOp::Store)), - timestamp_writes: None, - occlusion_query_set: None, - }; - - // Run the transmissive pass, sorted back-to-front - // NOTE: Scoped to drop the mutable borrow of render_context - #[cfg(feature = "trace")] - let _main_transmissive_pass_3d_span = info_span!("main_transmissive_pass_3d").entered(); - - if !transmissive_phase.items.is_empty() { - let screen_space_specular_transmission_steps = - camera_3d.screen_space_specular_transmission_steps; - if screen_space_specular_transmission_steps > 0 { - let transmission = - transmission.expect("`ViewTransmissionTexture` should exist at this point"); - - // `transmissive_phase.items` are depth sorted, so we split them into N = `screen_space_specular_transmission_steps` - // ranges, rendering them back-to-front in multiple steps, allowing multiple levels of transparency. - // - // Note: For the sake of simplicity, we currently split items evenly among steps. In the future, we - // might want to use a more sophisticated heuristic (e.g. based on view bounds, or with an exponential - // falloff so that nearby objects have more levels of transparency available to them) - for range in split_range( - 0..transmissive_phase.items.len(), - screen_space_specular_transmission_steps, - ) { - // Copy the main texture to the transmission texture, allowing to use the color output of the - // previous step (or of the `Opaque3d` phase, for the first step) as a transmissive color input - render_context.command_encoder().copy_texture_to_texture( - target.main_texture().as_image_copy(), - transmission.texture.as_image_copy(), - physical_target_size.to_extents(), - ); - - let mut render_pass = - render_context.begin_tracked_render_pass(render_pass_descriptor.clone()); - let pass_span = - diagnostics.pass_span(&mut render_pass, "main_transmissive_pass_3d"); - - if let Some(viewport) = camera.viewport.as_ref() { - render_pass.set_camera_viewport(viewport); - } - - // render items in range - if let Err(err) = - transmissive_phase.render_range(&mut render_pass, world, view_entity, range) - { - error!("Error encountered while rendering the transmissive phase {err:?}"); - } - - pass_span.end(&mut render_pass); - } - } else { - let mut render_pass = - render_context.begin_tracked_render_pass(render_pass_descriptor); - let pass_span = - diagnostics.pass_span(&mut render_pass, "main_transmissive_pass_3d"); - - if let Some(viewport) = Viewport::from_viewport_and_override( - camera.viewport.as_ref(), - resolution_override, - ) { - render_pass.set_camera_viewport(&viewport); - } - - if let Err(err) = transmissive_phase.render(&mut render_pass, world, view_entity) { - error!("Error encountered while rendering the transmissive phase {err:?}"); - } - - pass_span.end(&mut render_pass); - } - } - - Ok(()) - } -} - /// Splits a [`Range`] into at most `max_num_splits` sub-ranges without overlaps /// /// Properly takes into account remainders of inexact divisions (by adding extra @@ -165,3 +42,101 @@ fn split_range(range: Range, max_num_splits: usize) -> impl Iterator, + &ViewDepthTexture, + Option<&MainPassResolutionOverride>, + )>, + transmissive_phases: Res>, + mut ctx: RenderContext, +) { + let view_entity = view.entity(); + + let (camera, extracted_view, camera_3d, target, transmission, depth, resolution_override) = + view.into_inner(); + + let Some(transmissive_phase) = transmissive_phases.get(&extracted_view.retained_view_entity) + else { + return; + }; + + let Some(physical_target_size) = camera.physical_target_size else { + return; + }; + + #[cfg(feature = "trace")] + let _main_transmissive_pass_3d_span = info_span!("main_transmissive_pass_3d").entered(); + + let diagnostics = ctx.diagnostic_recorder(); + let diagnostics = diagnostics.as_deref(); + + let render_pass_descriptor = RenderPassDescriptor { + label: Some("main_transmissive_pass_3d"), + color_attachments: &[Some(target.get_color_attachment())], + depth_stencil_attachment: Some(depth.get_attachment(StoreOp::Store)), + timestamp_writes: None, + occlusion_query_set: None, + }; + + if !transmissive_phase.items.is_empty() { + let screen_space_specular_transmission_steps = + camera_3d.screen_space_specular_transmission_steps; + if screen_space_specular_transmission_steps > 0 { + let transmission = + transmission.expect("`ViewTransmissionTexture` should exist at this point"); + + // `transmissive_phase.items` are depth sorted, so we split them into N = `screen_space_specular_transmission_steps` + // ranges, rendering them back-to-front in multiple steps, allowing multiple levels of transparency. + for range in split_range( + 0..transmissive_phase.items.len(), + screen_space_specular_transmission_steps, + ) { + // Copy the main texture to the transmission texture + ctx.command_encoder().copy_texture_to_texture( + target.main_texture().as_image_copy(), + transmission.texture.as_image_copy(), + physical_target_size.to_extents(), + ); + + let mut render_pass = + ctx.begin_tracked_render_pass(render_pass_descriptor.clone()); + let pass_span = + diagnostics.pass_span(&mut render_pass, "main_transmissive_pass_3d"); + + if let Some(viewport) = camera.viewport.as_ref() { + render_pass.set_camera_viewport(viewport); + } + + if let Err(err) = + transmissive_phase.render_range(&mut render_pass, world, view_entity, range) + { + error!("Error encountered while rendering the transmissive phase {err:?}"); + } + + pass_span.end(&mut render_pass); + } + } else { + let mut render_pass = ctx.begin_tracked_render_pass(render_pass_descriptor); + let pass_span = diagnostics.pass_span(&mut render_pass, "main_transmissive_pass_3d"); + + if let Some(viewport) = + Viewport::from_viewport_and_override(camera.viewport.as_ref(), resolution_override) + { + render_pass.set_camera_viewport(&viewport); + } + + if let Err(err) = transmissive_phase.render(&mut render_pass, world, view_entity) { + error!("Error encountered while rendering the transmissive phase {err:?}"); + } + + pass_span.end(&mut render_pass); + } + } +} diff --git a/crates/bevy_core_pipeline/src/core_3d/main_transparent_pass_3d_node.rs b/crates/bevy_core_pipeline/src/core_3d/main_transparent_pass_3d_node.rs index bd54b7849e9c5..f9c5db533c9a1 100644 --- a/crates/bevy_core_pipeline/src/core_3d/main_transparent_pass_3d_node.rs +++ b/crates/bevy_core_pipeline/src/core_3d/main_transparent_pass_3d_node.rs @@ -1,107 +1,88 @@ use crate::core_3d::Transparent3d; use bevy_camera::{MainPassResolutionOverride, Viewport}; -use bevy_ecs::{prelude::*, query::QueryItem}; +use bevy_ecs::prelude::*; use bevy_render::{ camera::ExtractedCamera, diagnostic::RecordDiagnostics, - render_graph::{NodeRunError, RenderGraphContext, ViewNode}, render_phase::ViewSortedRenderPhases, render_resource::{RenderPassDescriptor, StoreOp}, - renderer::RenderContext, + renderer::{RenderContext, ViewQuery}, view::{ExtractedView, ViewDepthTexture, ViewTarget}, }; use tracing::error; #[cfg(feature = "trace")] use tracing::info_span; -/// A [`bevy_render::render_graph::Node`] that runs the [`Transparent3d`] -/// [`ViewSortedRenderPhases`]. -#[derive(Default)] -pub struct MainTransparentPass3dNode; +pub fn main_transparent_pass_3d( + world: &World, + view: ViewQuery<( + &ExtractedCamera, + &ExtractedView, + &ViewTarget, + &ViewDepthTexture, + Option<&MainPassResolutionOverride>, + )>, + transparent_phases: Res>, + mut ctx: RenderContext, +) { + let view_entity = view.entity(); -impl ViewNode for MainTransparentPass3dNode { - type ViewQuery = ( - &'static ExtractedCamera, - &'static ExtractedView, - &'static ViewTarget, - &'static ViewDepthTexture, - Option<&'static MainPassResolutionOverride>, - ); - fn run( - &self, - graph: &mut RenderGraphContext, - render_context: &mut RenderContext, - (camera, view, target, depth, resolution_override): QueryItem, - world: &World, - ) -> Result<(), NodeRunError> { - let view_entity = graph.view_entity(); + let (camera, extracted_view, target, depth, resolution_override) = view.into_inner(); - let Some(transparent_phases) = - world.get_resource::>() - else { - return Ok(()); - }; - - let Some(transparent_phase) = transparent_phases.get(&view.retained_view_entity) else { - return Ok(()); - }; - - if !transparent_phase.items.is_empty() { - // Run the transparent pass, sorted back-to-front - // NOTE: Scoped to drop the mutable borrow of render_context - #[cfg(feature = "trace")] - let _main_transparent_pass_3d_span = info_span!("main_transparent_pass_3d").entered(); - - let diagnostics = render_context.diagnostic_recorder(); + let Some(transparent_phase) = transparent_phases.get(&extracted_view.retained_view_entity) + else { + return; + }; - let mut render_pass = render_context.begin_tracked_render_pass(RenderPassDescriptor { - label: Some("main_transparent_pass_3d"), - color_attachments: &[Some(target.get_color_attachment())], - // NOTE: For the transparent pass we load the depth buffer. There should be no - // need to write to it, but store is set to `true` as a workaround for issue #3776, - // https://github.com/bevyengine/bevy/issues/3776 - // so that wgpu does not clear the depth buffer. - // As the opaque and alpha mask passes run first, opaque meshes can occlude - // transparent ones. - depth_stencil_attachment: Some(depth.get_attachment(StoreOp::Store)), - timestamp_writes: None, - occlusion_query_set: None, - }); + if !transparent_phase.items.is_empty() { + #[cfg(feature = "trace")] + let _main_transparent_pass_3d_span = info_span!("main_transparent_pass_3d").entered(); - let pass_span = diagnostics.pass_span(&mut render_pass, "main_transparent_pass_3d"); + let diagnostics = ctx.diagnostic_recorder(); + let diagnostics = diagnostics.as_deref(); - if let Some(viewport) = - Viewport::from_viewport_and_override(camera.viewport.as_ref(), resolution_override) - { - render_pass.set_camera_viewport(&viewport); - } + let mut render_pass = ctx.begin_tracked_render_pass(RenderPassDescriptor { + label: Some("main_transparent_pass_3d"), + color_attachments: &[Some(target.get_color_attachment())], + // NOTE: For the transparent pass we load the depth buffer. There should be no + // need to write to it, but store is set to `true` as a workaround for issue #3776, + // https://github.com/bevyengine/bevy/issues/3776 + // so that wgpu does not clear the depth buffer. + // As the opaque and alpha mask passes run first, opaque meshes can occlude + // transparent ones. + depth_stencil_attachment: Some(depth.get_attachment(StoreOp::Store)), + timestamp_writes: None, + occlusion_query_set: None, + }); + let pass_span = diagnostics.pass_span(&mut render_pass, "main_transparent_pass_3d"); - if let Err(err) = transparent_phase.render(&mut render_pass, world, view_entity) { - error!("Error encountered while rendering the transparent phase {err:?}"); - } + if let Some(viewport) = + Viewport::from_viewport_and_override(camera.viewport.as_ref(), resolution_override) + { + render_pass.set_camera_viewport(&viewport); + } - pass_span.end(&mut render_pass); + if let Err(err) = transparent_phase.render(&mut render_pass, world, view_entity) { + error!("Error encountered while rendering the transparent phase {err:?}"); } - // WebGL2 quirk: if ending with a render pass with a custom viewport, the viewport isn't - // reset for the next render pass so add an empty render pass without a custom viewport - #[cfg(all(feature = "webgl", target_arch = "wasm32", not(feature = "webgpu")))] - if camera.viewport.is_some() { - #[cfg(feature = "trace")] - let _reset_viewport_pass_3d = info_span!("reset_viewport_pass_3d").entered(); - let pass_descriptor = RenderPassDescriptor { - label: Some("reset_viewport_pass_3d"), - color_attachments: &[Some(target.get_color_attachment())], - depth_stencil_attachment: None, - timestamp_writes: None, - occlusion_query_set: None, - }; + pass_span.end(&mut render_pass); + } - render_context - .command_encoder() - .begin_render_pass(&pass_descriptor); - } + // WebGL2 quirk: if ending with a render pass with a custom viewport, the viewport isn't + // reset for the next render pass so add an empty render pass without a custom viewport + #[cfg(all(feature = "webgl", target_arch = "wasm32", not(feature = "webgpu")))] + if camera.viewport.is_some() { + #[cfg(feature = "trace")] + let _reset_viewport_pass_3d = info_span!("reset_viewport_pass_3d").entered(); + let pass_descriptor = RenderPassDescriptor { + label: Some("reset_viewport_pass_3d"), + color_attachments: &[Some(target.get_color_attachment())], + depth_stencil_attachment: None, + timestamp_writes: None, + occlusion_query_set: None, + }; - Ok(()) + ctx.command_encoder().begin_render_pass(&pass_descriptor); } } diff --git a/crates/bevy_core_pipeline/src/core_3d/mod.rs b/crates/bevy_core_pipeline/src/core_3d/mod.rs index 5a090b5da610a..77da3e4636068 100644 --- a/crates/bevy_core_pipeline/src/core_3d/mod.rs +++ b/crates/bevy_core_pipeline/src/core_3d/mod.rs @@ -2,51 +2,6 @@ mod main_opaque_pass_3d_node; mod main_transmissive_pass_3d_node; mod main_transparent_pass_3d_node; -pub mod graph { - use bevy_render::render_graph::{RenderLabel, RenderSubGraph}; - - #[derive(Debug, Hash, PartialEq, Eq, Clone, RenderSubGraph)] - pub struct Core3d; - - pub mod input { - pub const VIEW_ENTITY: &str = "view_entity"; - } - - #[derive(Debug, Hash, PartialEq, Eq, Clone, RenderLabel)] - pub enum Node3d { - MsaaWriteback, - EarlyPrepass, - EarlyDownsampleDepth, - LatePrepass, - EarlyDeferredPrepass, - LateDeferredPrepass, - CopyDeferredLightingId, - EndPrepasses, - StartMainPass, - MainOpaquePass, - MainTransmissivePass, - MainTransparentPass, - EndMainPass, - Wireframe, - StartMainPassPostProcessing, - LateDownsampleDepth, - MotionBlur, - Taa, - DlssSuperResolution, - DlssRayReconstruction, - Bloom, - AutoExposure, - DepthOfField, - PostProcessing, - Tonemapping, - Fxaa, - Smaa, - Upscaling, - ContrastAdaptiveSharpening, - EndMainPassPostProcessing, - } -} - // PERF: vulkan docs recommend using 24 bit depth for better performance pub const CORE_3D_DEPTH_FORMAT: TextureFormat = TextureFormat::Depth32Float; @@ -84,6 +39,7 @@ use bevy_render::{ view::{prepare_view_targets, NoIndirectDrawing, RetainedViewEntity}, }; pub use main_opaque_pass_3d_node::*; +pub use main_transmissive_pass_3d_node::*; pub use main_transparent_pass_3d_node::*; use bevy_app::{App, Plugin, PostUpdate}; @@ -97,7 +53,6 @@ use bevy_render::{ camera::ExtractedCamera, extract_component::ExtractComponentPlugin, prelude::Msaa, - render_graph::{EmptyNode, RenderGraphExt, ViewNodeRunner}, render_phase::{ sort_phase_system, BinnedPhaseItem, CachedRenderPipelinePhaseItem, DrawFunctionId, DrawFunctions, PhaseItem, PhaseItemExtraIndex, SortedPhaseItem, ViewBinnedRenderPhases, @@ -116,27 +71,20 @@ use bevy_render::{ use nonmax::NonMaxU32; use tracing::warn; -use crate::{ - core_3d::main_transmissive_pass_3d_node::MainTransmissivePass3dNode, - deferred::{ - copy_lighting_id::CopyDeferredLightingIdNode, - node::{EarlyDeferredGBufferPrepassNode, LateDeferredGBufferPrepassNode}, - AlphaMask3dDeferred, Opaque3dDeferred, DEFERRED_LIGHTING_PASS_ID_FORMAT, - DEFERRED_PREPASS_FORMAT, - }, - prepass::{ - node::{EarlyPrepassNode, LatePrepassNode}, - AlphaMask3dPrepass, DeferredPrepass, DeferredPrepassDoubleBuffer, DepthPrepass, - DepthPrepassDoubleBuffer, MotionVectorPrepass, NormalPrepass, Opaque3dPrepass, - OpaqueNoLightmap3dBatchSetKey, OpaqueNoLightmap3dBinKey, ViewPrepassTextures, - MOTION_VECTOR_PREPASS_FORMAT, NORMAL_PREPASS_FORMAT, - }, - skybox::SkyboxPlugin, - tonemapping::{DebandDither, Tonemapping, TonemappingNode}, - upscaling::UpscalingNode, -}; - -use self::graph::{Core3d, Node3d}; +use crate::{deferred::{ + AlphaMask3dDeferred, Opaque3dDeferred, DEFERRED_LIGHTING_PASS_ID_FORMAT, + DEFERRED_PREPASS_FORMAT, +}, prepass::{ + AlphaMask3dPrepass, DeferredPrepass, DeferredPrepassDoubleBuffer, DepthPrepass, + DepthPrepassDoubleBuffer, MotionVectorPrepass, NormalPrepass, Opaque3dPrepass, + OpaqueNoLightmap3dBatchSetKey, OpaqueNoLightmap3dBinKey, ViewPrepassTextures, + MOTION_VECTOR_PREPASS_FORMAT, NORMAL_PREPASS_FORMAT, +}, schedule::Core3d, skybox::SkyboxPlugin, tonemapping::{DebandDither, Tonemapping}, Core3dSystems}; +use crate::deferred::copy_lighting_id::copy_deferred_lighting_id; +use crate::deferred::node::{early_deferred_prepass, late_deferred_prepass}; +use crate::prepass::node::{early_prepass, late_prepass}; +use crate::tonemapping::tonemapping; +use crate::upscaling::upscaling; pub struct Core3dPlugin; @@ -184,63 +132,42 @@ impl Plugin for Core3dPlugin { prepare_core_3d_transmission_textures.in_set(RenderSystems::PrepareResources), prepare_prepass_textures.in_set(RenderSystems::PrepareResources), ), - ); - - render_app - .add_render_sub_graph(Core3d) - .add_render_graph_node::>(Core3d, Node3d::EarlyPrepass) - .add_render_graph_node::>(Core3d, Node3d::LatePrepass) - .add_render_graph_node::>( - Core3d, - Node3d::EarlyDeferredPrepass, - ) - .add_render_graph_node::>( - Core3d, - Node3d::LateDeferredPrepass, - ) - .add_render_graph_node::>( - Core3d, - Node3d::CopyDeferredLightingId, - ) - .add_render_graph_node::(Core3d, Node3d::EndPrepasses) - .add_render_graph_node::(Core3d, Node3d::StartMainPass) - .add_render_graph_node::>( - Core3d, - Node3d::MainOpaquePass, - ) - .add_render_graph_node::>( - Core3d, - Node3d::MainTransmissivePass, - ) - .add_render_graph_node::>( - Core3d, - Node3d::MainTransparentPass, ) - .add_render_graph_node::(Core3d, Node3d::EndMainPass) - .add_render_graph_node::(Core3d, Node3d::StartMainPassPostProcessing) - .add_render_graph_node::>(Core3d, Node3d::Tonemapping) - .add_render_graph_node::(Core3d, Node3d::EndMainPassPostProcessing) - .add_render_graph_node::>(Core3d, Node3d::Upscaling) - .add_render_graph_edges( - Core3d, - ( - Node3d::EarlyPrepass, - Node3d::EarlyDeferredPrepass, - Node3d::LatePrepass, - Node3d::LateDeferredPrepass, - Node3d::CopyDeferredLightingId, - Node3d::EndPrepasses, - Node3d::StartMainPass, - Node3d::MainOpaquePass, - Node3d::MainTransmissivePass, - Node3d::MainTransparentPass, - Node3d::EndMainPass, - Node3d::StartMainPassPostProcessing, - Node3d::Tonemapping, - Node3d::EndMainPassPostProcessing, - Node3d::Upscaling, - ), - ); + .add_schedule(Core3d::base_schedule()) + .add_systems( + Core3d, + ( + // Prepasses + early_prepass.before(Core3dSystems::EndPrepasses), + early_deferred_prepass + .after(early_prepass) + .before(Core3dSystems::EndPrepasses), + late_prepass + .after(early_deferred_prepass) + .before(Core3dSystems::EndPrepasses), + late_deferred_prepass + .after(late_prepass) + .before(Core3dSystems::EndPrepasses), + copy_deferred_lighting_id + .after(late_deferred_prepass) + .before(Core3dSystems::EndPrepasses), + // Main passes + main_opaque_pass_3d + .after(Core3dSystems::StartMainPass) + .before(Core3dSystems::EndMainPass), + main_transmissive_pass_3d + .after(main_opaque_pass_3d) + .before(Core3dSystems::EndMainPass), + main_transparent_pass_3d + .after(main_transmissive_pass_3d) + .before(Core3dSystems::EndMainPass), + // Post-processing + tonemapping + .after(Core3dSystems::StartMainPassPostProcessing) + .before(Core3dSystems::PostProcessing), + upscaling.after(Core3dSystems::EndMainPassPostProcessing), + ), + ); } } diff --git a/crates/bevy_core_pipeline/src/deferred/copy_lighting_id.rs b/crates/bevy_core_pipeline/src/deferred/copy_lighting_id.rs index 1889fc33ce6d9..c57bcf2d55e04 100644 --- a/crates/bevy_core_pipeline/src/deferred/copy_lighting_id.rs +++ b/crates/bevy_core_pipeline/src/deferred/copy_lighting_id.rs @@ -8,7 +8,6 @@ use bevy_ecs::prelude::*; use bevy_image::ToExtents; use bevy_render::{ camera::ExtractedCamera, - diagnostic::RecordDiagnostics, render_resource::{binding_types::texture_2d, *}, renderer::RenderDevice, texture::{CachedTexture, TextureCache}, @@ -17,11 +16,7 @@ use bevy_render::{ }; use super::DEFERRED_LIGHTING_PASS_ID_DEPTH_FORMAT; -use bevy_ecs::query::QueryItem; -use bevy_render::{ - render_graph::{NodeRunError, RenderGraphContext, ViewNode}, - renderer::RenderContext, -}; +use bevy_render::renderer::{RenderContext, ViewQuery}; use bevy_utils::default; pub struct CopyDeferredLightingIdPlugin; @@ -41,80 +36,8 @@ impl Plugin for CopyDeferredLightingIdPlugin { } } -#[derive(Default)] -pub struct CopyDeferredLightingIdNode; -impl CopyDeferredLightingIdNode { - pub const NAME: &'static str = "copy_deferred_lighting_id"; -} - -impl ViewNode for CopyDeferredLightingIdNode { - type ViewQuery = ( - &'static ViewTarget, - &'static ViewPrepassTextures, - &'static DeferredLightingIdDepthTexture, - ); - - fn run( - &self, - _graph: &mut RenderGraphContext, - render_context: &mut RenderContext, - (_view_target, view_prepass_textures, deferred_lighting_id_depth_texture): QueryItem< - Self::ViewQuery, - >, - world: &World, - ) -> Result<(), NodeRunError> { - let copy_deferred_lighting_id_pipeline = world.resource::(); - - let pipeline_cache = world.resource::(); - - let Some(pipeline) = - pipeline_cache.get_render_pipeline(copy_deferred_lighting_id_pipeline.pipeline_id) - else { - return Ok(()); - }; - let Some(deferred_lighting_pass_id_texture) = - &view_prepass_textures.deferred_lighting_pass_id - else { - return Ok(()); - }; - - let diagnostics = render_context.diagnostic_recorder(); - - let bind_group = render_context.render_device().create_bind_group( - "copy_deferred_lighting_id_bind_group", - &pipeline_cache.get_bind_group_layout(©_deferred_lighting_id_pipeline.layout), - &BindGroupEntries::single(&deferred_lighting_pass_id_texture.texture.default_view), - ); - - let mut render_pass = render_context.begin_tracked_render_pass(RenderPassDescriptor { - label: Some("copy_deferred_lighting_id"), - color_attachments: &[], - depth_stencil_attachment: Some(RenderPassDepthStencilAttachment { - view: &deferred_lighting_id_depth_texture.texture.default_view, - depth_ops: Some(Operations { - load: LoadOp::Clear(0.0), - store: StoreOp::Store, - }), - stencil_ops: None, - }), - timestamp_writes: None, - occlusion_query_set: None, - }); - - let pass_span = diagnostics.pass_span(&mut render_pass, "copy_deferred_lighting_id"); - - render_pass.set_render_pipeline(pipeline); - render_pass.set_bind_group(0, &bind_group, &[]); - render_pass.draw(0..3, 0..1); - - pass_span.end(&mut render_pass); - - Ok(()) - } -} - #[derive(Resource)] -struct CopyDeferredLightingIdPipeline { +pub(crate) struct CopyDeferredLightingIdPipeline { layout: BindGroupLayoutDescriptor, pipeline_id: CachedRenderPipelineId, } @@ -190,3 +113,50 @@ fn prepare_deferred_lighting_id_textures( } } } + +pub(crate) fn copy_deferred_lighting_id( + view: ViewQuery<( + &ViewTarget, + &ViewPrepassTextures, + &DeferredLightingIdDepthTexture, + )>, + copy_pipeline: Res, + pipeline_cache: Res, + mut ctx: RenderContext, +) { + let (_view_target, view_prepass_textures, deferred_lighting_id_depth_texture) = + view.into_inner(); + + let Some(pipeline) = pipeline_cache.get_render_pipeline(copy_pipeline.pipeline_id) else { + return; + }; + let Some(deferred_lighting_pass_id_texture) = &view_prepass_textures.deferred_lighting_pass_id + else { + return; + }; + + let bind_group = ctx.render_device().create_bind_group( + "copy_deferred_lighting_id_bind_group", + &pipeline_cache.get_bind_group_layout(©_pipeline.layout), + &BindGroupEntries::single(&deferred_lighting_pass_id_texture.texture.default_view), + ); + + let mut render_pass = ctx.begin_tracked_render_pass(RenderPassDescriptor { + label: Some("copy_deferred_lighting_id"), + color_attachments: &[], + depth_stencil_attachment: Some(RenderPassDepthStencilAttachment { + view: &deferred_lighting_id_depth_texture.texture.default_view, + depth_ops: Some(Operations { + load: LoadOp::Clear(0.0), + store: StoreOp::Store, + }), + stencil_ops: None, + }), + timestamp_writes: None, + occlusion_query_set: None, + }); + + render_pass.set_render_pipeline(pipeline); + render_pass.set_bind_group(0, &bind_group, &[]); + render_pass.draw(0..3, 0..1); +} diff --git a/crates/bevy_core_pipeline/src/deferred/node.rs b/crates/bevy_core_pipeline/src/deferred/node.rs index 49bf3dc5adf19..4a41ed34cfe43 100644 --- a/crates/bevy_core_pipeline/src/deferred/node.rs +++ b/crates/bevy_core_pipeline/src/deferred/node.rs @@ -1,16 +1,14 @@ use bevy_camera::{MainPassResolutionOverride, Viewport}; -use bevy_ecs::{prelude::*, query::QueryItem}; +use bevy_ecs::prelude::*; use bevy_render::experimental::occlusion_culling::OcclusionCulling; -use bevy_render::render_graph::ViewNode; use bevy_render::view::{ExtractedView, NoIndirectDrawing}; use bevy_render::{ camera::ExtractedCamera, diagnostic::RecordDiagnostics, - render_graph::{NodeRunError, RenderGraphContext}, - render_phase::{TrackedRenderPass, ViewBinnedRenderPhases}, - render_resource::{CommandEncoderDescriptor, RenderPassDescriptor, StoreOp}, - renderer::RenderContext, + render_phase::ViewBinnedRenderPhases, + render_resource::{RenderPassDescriptor, StoreOp}, + renderer::{RenderContext, ViewQuery}, view::ViewDepthTexture, }; use tracing::error; @@ -21,117 +19,109 @@ use crate::prepass::ViewPrepassTextures; use super::{AlphaMask3dDeferred, Opaque3dDeferred}; -/// The phase of the deferred prepass that draws meshes that were visible last -/// frame. -/// -/// If occlusion culling isn't in use, this prepass simply draws all meshes. -/// -/// Like all prepass nodes, this is inserted before the main pass in the render -/// graph. -#[derive(Default)] -pub struct EarlyDeferredGBufferPrepassNode; - -impl ViewNode for EarlyDeferredGBufferPrepassNode { - type ViewQuery = ::ViewQuery; - - fn run<'w>( - &self, - graph: &mut RenderGraphContext, - render_context: &mut RenderContext<'w>, - view_query: QueryItem<'w, '_, Self::ViewQuery>, - world: &'w World, - ) -> Result<(), NodeRunError> { - run_deferred_prepass( - graph, - render_context, - view_query, - false, - world, - "early deferred prepass", - ) - } +/// Type alias for the deferred prepass view query. +type DeferredPrepassViewQueryData = ( + &'static ExtractedCamera, + &'static ExtractedView, + &'static ViewDepthTexture, + &'static ViewPrepassTextures, + Option<&'static MainPassResolutionOverride>, + Has, + Has, +); + +pub(crate) fn early_deferred_prepass( + world: &World, + view: ViewQuery, + opaque_deferred_phases: Res>, + alpha_mask_deferred_phases: Res>, + mut ctx: RenderContext, +) { + let view_entity = view.entity(); + let (camera, extracted_view, view_depth_texture, view_prepass_textures, resolution_override, _, _) = + view.into_inner(); + + run_deferred_prepass_system( + world, + view_entity, + camera, + extracted_view, + view_depth_texture, + view_prepass_textures, + resolution_override, + false, + &opaque_deferred_phases, + &alpha_mask_deferred_phases, + &mut ctx, + "early deferred prepass", + ); } -/// The phase of the prepass that runs after occlusion culling against the -/// meshes that were visible last frame. -/// -/// If occlusion culling isn't in use, this is a no-op. -/// -/// Like all prepass nodes, this is inserted before the main pass in the render -/// graph. -#[derive(Default)] -pub struct LateDeferredGBufferPrepassNode; +pub(crate) fn late_deferred_prepass( + world: &World, + view: ViewQuery, + opaque_deferred_phases: Res>, + alpha_mask_deferred_phases: Res>, + mut ctx: RenderContext, +) { + let view_entity = view.entity(); + let ( + camera, + extracted_view, + view_depth_texture, + view_prepass_textures, + resolution_override, + occlusion_culling, + no_indirect_drawing, + ) = view.into_inner(); + + if !occlusion_culling || no_indirect_drawing { + return; + } -impl ViewNode for LateDeferredGBufferPrepassNode { - type ViewQuery = ( - &'static ExtractedCamera, - &'static ExtractedView, - &'static ViewDepthTexture, - &'static ViewPrepassTextures, - Option<&'static MainPassResolutionOverride>, - Has, - Has, + run_deferred_prepass_system( + world, + view_entity, + camera, + extracted_view, + view_depth_texture, + view_prepass_textures, + resolution_override, + true, + &opaque_deferred_phases, + &alpha_mask_deferred_phases, + &mut ctx, + "late deferred prepass", ); - - fn run<'w>( - &self, - graph: &mut RenderGraphContext, - render_context: &mut RenderContext<'w>, - view_query: QueryItem<'w, '_, Self::ViewQuery>, - world: &'w World, - ) -> Result<(), NodeRunError> { - let (.., occlusion_culling, no_indirect_drawing) = view_query; - if !occlusion_culling || no_indirect_drawing { - return Ok(()); - } - - run_deferred_prepass( - graph, - render_context, - view_query, - true, - world, - "late deferred prepass", - ) - } } -/// Runs the deferred prepass that draws all meshes to the depth buffer and -/// G-buffers. -/// -/// If occlusion culling isn't in use, and a prepass is enabled, then there's -/// only one prepass. If occlusion culling is in use, then any prepass is split -/// into two: an *early* prepass and a *late* prepass. The early prepass draws -/// what was visible last frame, and the last prepass performs occlusion culling -/// against a conservative hierarchical Z buffer before drawing unoccluded -/// meshes. -fn run_deferred_prepass<'w>( - graph: &mut RenderGraphContext, - render_context: &mut RenderContext<'w>, - (camera, extracted_view, view_depth_texture, view_prepass_textures, resolution_override, _, _): QueryItem< - 'w, - '_, - ::ViewQuery, - >, +#[allow(clippy::too_many_arguments)] +fn run_deferred_prepass_system( + world: &World, + view_entity: Entity, + camera: &ExtractedCamera, + extracted_view: &ExtractedView, + view_depth_texture: &ViewDepthTexture, + view_prepass_textures: &ViewPrepassTextures, + resolution_override: Option<&MainPassResolutionOverride>, is_late: bool, - world: &'w World, + opaque_deferred_phases: &ViewBinnedRenderPhases, + alpha_mask_deferred_phases: &ViewBinnedRenderPhases, + ctx: &mut RenderContext, label: &'static str, -) -> Result<(), NodeRunError> { - let (Some(opaque_deferred_phases), Some(alpha_mask_deferred_phases)) = ( - world.get_resource::>(), - world.get_resource::>(), - ) else { - return Ok(()); - }; - +) { let (Some(opaque_deferred_phase), Some(alpha_mask_deferred_phase)) = ( opaque_deferred_phases.get(&extracted_view.retained_view_entity), alpha_mask_deferred_phases.get(&extracted_view.retained_view_entity), ) else { - return Ok(()); + return; }; - let diagnostic = render_context.diagnostic_recorder(); + #[cfg(feature = "trace")] + let _deferred_span = info_span!("deferred_prepass").entered(); + + let diagnostics = ctx.diagnostic_recorder(); + let diagnostics = diagnostics.as_deref(); let mut color_attachments = vec![]; color_attachments.push( @@ -155,7 +145,7 @@ fn run_deferred_prepass<'w>( #[cfg(all(feature = "webgl", target_arch = "wasm32", not(feature = "webgpu")))] if !is_late { if let Some(deferred_texture) = &view_prepass_textures.deferred { - render_context.command_encoder().clear_texture( + ctx.command_encoder().clear_texture( &deferred_texture.texture.texture, &bevy_render::render_resource::ImageSubresourceRange::default(), ); @@ -206,68 +196,49 @@ fn run_deferred_prepass<'w>( let depth_stencil_attachment = Some(view_depth_texture.get_attachment(StoreOp::Store)); - let view_entity = graph.view_entity(); - render_context.add_command_buffer_generation_task(move |render_device| { - #[cfg(feature = "trace")] - let _deferred_span = info_span!("deferred_prepass").entered(); - - // Command encoder setup - let mut command_encoder = render_device.create_command_encoder(&CommandEncoderDescriptor { - label: Some("deferred_prepass_command_encoder"), - }); - - // Render pass setup - let render_pass = command_encoder.begin_render_pass(&RenderPassDescriptor { - label: Some(label), - color_attachments: &color_attachments, - depth_stencil_attachment, - timestamp_writes: None, - occlusion_query_set: None, - }); - let mut render_pass = TrackedRenderPass::new(&render_device, render_pass); - let pass_span = diagnostic.pass_span(&mut render_pass, label); - if let Some(viewport) = - Viewport::from_viewport_and_override(camera.viewport.as_ref(), resolution_override) - { - render_pass.set_camera_viewport(&viewport); - } + let mut render_pass = ctx.begin_tracked_render_pass(RenderPassDescriptor { + label: Some(label), + color_attachments: &color_attachments, + depth_stencil_attachment, + timestamp_writes: None, + occlusion_query_set: None, + }); + let pass_span = diagnostics.pass_span(&mut render_pass, label); - // Opaque draws - if !opaque_deferred_phase.multidrawable_meshes.is_empty() - || !opaque_deferred_phase.batchable_meshes.is_empty() - || !opaque_deferred_phase.unbatchable_meshes.is_empty() - { - #[cfg(feature = "trace")] - let _opaque_prepass_span = info_span!("opaque_deferred_prepass").entered(); - if let Err(err) = opaque_deferred_phase.render(&mut render_pass, world, view_entity) { - error!("Error encountered while rendering the opaque deferred phase {err:?}"); - } - } + if let Some(viewport) = + Viewport::from_viewport_and_override(camera.viewport.as_ref(), resolution_override) + { + render_pass.set_camera_viewport(&viewport); + } - // Alpha masked draws - if !alpha_mask_deferred_phase.is_empty() { - #[cfg(feature = "trace")] - let _alpha_mask_deferred_span = info_span!("alpha_mask_deferred_prepass").entered(); - if let Err(err) = alpha_mask_deferred_phase.render(&mut render_pass, world, view_entity) - { - error!("Error encountered while rendering the alpha mask deferred phase {err:?}"); - } + if !opaque_deferred_phase.multidrawable_meshes.is_empty() + || !opaque_deferred_phase.batchable_meshes.is_empty() + || !opaque_deferred_phase.unbatchable_meshes.is_empty() + { + #[cfg(feature = "trace")] + let _opaque_prepass_span = info_span!("opaque_deferred_prepass").entered(); + if let Err(err) = opaque_deferred_phase.render(&mut render_pass, world, view_entity) { + error!("Error encountered while rendering the opaque deferred phase {err:?}"); } + } - pass_span.end(&mut render_pass); - drop(render_pass); - - // After rendering to the view depth texture, copy it to the prepass depth texture - if let Some(prepass_depth_texture) = &view_prepass_textures.depth { - command_encoder.copy_texture_to_texture( - view_depth_texture.texture.as_image_copy(), - prepass_depth_texture.texture.texture.as_image_copy(), - view_prepass_textures.size, - ); + if !alpha_mask_deferred_phase.is_empty() { + #[cfg(feature = "trace")] + let _alpha_mask_deferred_span = info_span!("alpha_mask_deferred_prepass").entered(); + if let Err(err) = alpha_mask_deferred_phase.render(&mut render_pass, world, view_entity) { + error!("Error encountered while rendering the alpha mask deferred phase {err:?}"); } + } - command_encoder.finish() - }); + pass_span.end(&mut render_pass); + drop(render_pass); - Ok(()) + // After rendering to the view depth texture, copy it to the prepass depth texture + if let Some(prepass_depth_texture) = &view_prepass_textures.depth { + ctx.command_encoder().copy_texture_to_texture( + view_depth_texture.texture.as_image_copy(), + prepass_depth_texture.texture.texture.as_image_copy(), + view_prepass_textures.size, + ); + } } diff --git a/crates/bevy_core_pipeline/src/experimental/mip_generation/mod.rs b/crates/bevy_core_pipeline/src/experimental/mip_generation/mod.rs index 25e4e04ce84e8..32e83d83d3e05 100644 --- a/crates/bevy_core_pipeline/src/experimental/mip_generation/mod.rs +++ b/crates/bevy_core_pipeline/src/experimental/mip_generation/mod.rs @@ -7,9 +7,11 @@ use core::array; -use crate::core_3d::{ - graph::{Core3d, Node3d}, - prepare_core_3d_depth_textures, +use crate::{ + core_3d::prepare_core_3d_depth_textures, + deferred::node::early_deferred_prepass, + prepass::node::late_prepass, + schedule::{Core3d, Core3dSystems}, }; use bevy_app::{App, Plugin}; use bevy_asset::{embedded_asset, load_embedded_asset, Handle}; @@ -18,11 +20,10 @@ use bevy_ecs::{ component::Component, entity::Entity, prelude::{resource_exists, Without}, - query::{Or, QueryState, With}, + query::{Or, With}, resource::Resource, schedule::IntoScheduleConfigs as _, - system::{lifetimeless::Read, Commands, Local, Query, Res, ResMut}, - world::{FromWorld, World}, + system::{Commands, Local, Query, Res, ResMut}, }; use bevy_math::{uvec2, UVec2, Vec4Swizzles as _}; use bevy_render::{ @@ -33,7 +34,6 @@ use bevy_render::{ experimental::occlusion_culling::{ OcclusionCulling, OcclusionCullingSubview, OcclusionCullingSubviewEntities, }, - render_graph::{Node, NodeRunError, RenderGraphContext, RenderGraphExt}, render_resource::{ binding_types::{sampler, texture_2d, texture_2d_multisampled, texture_storage_2d}, BindGroup, BindGroupEntries, BindGroupLayout, BindGroupLayoutEntries, @@ -43,7 +43,7 @@ use bevy_render::{ StorageTextureAccess, TextureAspect, TextureDescriptor, TextureDimension, TextureFormat, TextureSampleType, TextureUsages, TextureView, TextureViewDescriptor, TextureViewDimension, }, - renderer::{RenderContext, RenderDevice}, + renderer::{RenderContext, RenderDevice, ViewQuery}, texture::TextureCache, view::{ExtractedView, NoIndirectDrawing, ViewDepthTexture}, Render, RenderApp, RenderSystems, @@ -83,26 +83,6 @@ impl Plugin for MipGenerationPlugin { render_app .insert_resource(DownsampleDepthShader(downsample_depth_shader)) .init_resource::>() - .add_render_graph_node::(Core3d, Node3d::EarlyDownsampleDepth) - .add_render_graph_node::(Core3d, Node3d::LateDownsampleDepth) - .add_render_graph_edges( - Core3d, - ( - Node3d::EarlyPrepass, - Node3d::EarlyDeferredPrepass, - Node3d::EarlyDownsampleDepth, - Node3d::LatePrepass, - Node3d::LateDeferredPrepass, - ), - ) - .add_render_graph_edges( - Core3d, - ( - Node3d::StartMainPassPostProcessing, - Node3d::LateDownsampleDepth, - Node3d::EndMainPassPostProcessing, - ), - ) .add_systems(RenderStartup, init_depth_pyramid_dummy_texture) .add_systems( Render, @@ -119,127 +99,150 @@ impl Plugin for MipGenerationPlugin { .run_if(resource_exists::) .after(prepare_core_3d_depth_textures), ); + + render_app.add_systems( + Core3d, + ( + early_downsample_depth + .after(early_deferred_prepass) + .before(late_prepass), + late_downsample_depth + .after(Core3dSystems::StartMainPassPostProcessing) + .before(Core3dSystems::EndMainPassPostProcessing), + ), + ); } } -/// The nodes that produce a hierarchical Z-buffer, also known as a depth -/// pyramid. -/// -/// This runs the single-pass downsampling (SPD) shader with the *min* filter in -/// order to generate a series of mipmaps for the Z buffer. The resulting -/// hierarchical Z-buffer can be used for occlusion culling. -/// -/// There are two instances of this node. The *early* downsample depth pass is -/// the first hierarchical Z-buffer stage, which runs after the early prepass -/// and before the late prepass. It prepares the Z-buffer for the bounding box -/// tests that the late mesh preprocessing stage will perform. The *late* -/// downsample depth pass runs at the end of the main phase. It prepares the -/// Z-buffer for the occlusion culling that the early mesh preprocessing phase -/// of the *next* frame will perform. -/// -/// This node won't do anything if occlusion culling isn't on. -pub struct DownsampleDepthNode { - /// The query that we use to find views that need occlusion culling for - /// their Z-buffer. - main_view_query: QueryState<( - Read, - Read, - Read, - Option>, +pub fn early_downsample_depth( + view: ViewQuery<( + &ViewDepthPyramid, + &ViewDownsampleDepthBindGroup, + &ViewDepthTexture, + Option<&OcclusionCullingSubviewEntities>, )>, - /// The query that we use to find shadow maps that need occlusion culling. - shadow_view_query: QueryState<( - Read, - Read, - Read, + shadow_view_query: Query<( + &ViewDepthPyramid, + &ViewDownsampleDepthBindGroup, + &OcclusionCullingSubview, )>, + downsample_depth_pipelines: Option>, + pipeline_cache: Res, + mut ctx: RenderContext, +) { + run_downsample_depth_system( + "early_downsample_depth", + view.into_inner(), + &shadow_view_query, + downsample_depth_pipelines.as_deref(), + &pipeline_cache, + &mut ctx, + ); } -impl FromWorld for DownsampleDepthNode { - fn from_world(world: &mut World) -> Self { - Self { - main_view_query: QueryState::new(world), - shadow_view_query: QueryState::new(world), - } - } +pub fn late_downsample_depth( + view: ViewQuery<( + &ViewDepthPyramid, + &ViewDownsampleDepthBindGroup, + &ViewDepthTexture, + Option<&OcclusionCullingSubviewEntities>, + )>, + shadow_view_query: Query<( + &ViewDepthPyramid, + &ViewDownsampleDepthBindGroup, + &OcclusionCullingSubview, + )>, + downsample_depth_pipelines: Option>, + pipeline_cache: Res, + mut ctx: RenderContext, +) { + run_downsample_depth_system( + "late_downsample_depth", + view.into_inner(), + &shadow_view_query, + downsample_depth_pipelines.as_deref(), + &pipeline_cache, + &mut ctx, + ); } -impl Node for DownsampleDepthNode { - fn update(&mut self, world: &mut World) { - self.main_view_query.update_archetypes(world); - self.shadow_view_query.update_archetypes(world); - } +fn run_downsample_depth_system( + label: &'static str, + main_view_data: ( + &ViewDepthPyramid, + &ViewDownsampleDepthBindGroup, + &ViewDepthTexture, + Option<&OcclusionCullingSubviewEntities>, + ), + shadow_view_query: &Query<( + &ViewDepthPyramid, + &ViewDownsampleDepthBindGroup, + &OcclusionCullingSubview, + )>, + downsample_depth_pipelines: Option<&DownsampleDepthPipelines>, + pipeline_cache: &PipelineCache, + ctx: &mut RenderContext, +) { + let Some(downsample_depth_pipelines) = downsample_depth_pipelines else { + return; + }; - fn run<'w>( - &self, - render_graph_context: &mut RenderGraphContext, - render_context: &mut RenderContext<'w>, - world: &'w World, - ) -> Result<(), NodeRunError> { - let Ok(( - view_depth_pyramid, - view_downsample_depth_bind_group, - view_depth_texture, - maybe_view_light_entities, - )) = self - .main_view_query - .get_manual(world, render_graph_context.view_entity()) - else { - return Ok(()); - }; + let ( + view_depth_pyramid, + view_downsample_depth_bind_group, + view_depth_texture, + maybe_view_light_entities, + ) = main_view_data; + + // Downsample depth for the main Z-buffer. + downsample_depth( + label, + ctx, + downsample_depth_pipelines, + pipeline_cache, + view_depth_pyramid, + view_downsample_depth_bind_group, + uvec2( + view_depth_texture.texture.width(), + view_depth_texture.texture.height(), + ), + view_depth_texture.texture.sample_count(), + ); - // Downsample depth for the main Z-buffer. - downsample_depth( - render_graph_context, - render_context, - world, - view_depth_pyramid, - view_downsample_depth_bind_group, - uvec2( - view_depth_texture.texture.width(), - view_depth_texture.texture.height(), - ), - view_depth_texture.texture.sample_count(), - )?; - - // Downsample depth for shadow maps that have occlusion culling enabled. - if let Some(view_light_entities) = maybe_view_light_entities { - for &view_light_entity in &view_light_entities.0 { - let Ok((view_depth_pyramid, view_downsample_depth_bind_group, occlusion_culling)) = - self.shadow_view_query.get_manual(world, view_light_entity) - else { - continue; - }; - downsample_depth( - render_graph_context, - render_context, - world, - view_depth_pyramid, - view_downsample_depth_bind_group, - UVec2::splat(occlusion_culling.depth_texture_size), - 1, - )?; - } + // Downsample depth for shadow maps that have occlusion culling enabled. + if let Some(view_light_entities) = maybe_view_light_entities { + for &view_light_entity in &view_light_entities.0 { + let Ok((view_depth_pyramid, view_downsample_depth_bind_group, occlusion_culling)) = + shadow_view_query.get(view_light_entity) + else { + continue; + }; + downsample_depth( + label, + ctx, + downsample_depth_pipelines, + pipeline_cache, + view_depth_pyramid, + view_downsample_depth_bind_group, + UVec2::splat(occlusion_culling.depth_texture_size), + 1, + ); } - - Ok(()) } } /// Produces a depth pyramid from the current depth buffer for a single view. /// The resulting depth pyramid can be used for occlusion testing. -fn downsample_depth<'w>( - render_graph_context: &mut RenderGraphContext, - render_context: &mut RenderContext<'w>, - world: &'w World, +fn downsample_depth( + label: &str, + ctx: &mut RenderContext, + downsample_depth_pipelines: &DownsampleDepthPipelines, + pipeline_cache: &PipelineCache, view_depth_pyramid: &ViewDepthPyramid, view_downsample_depth_bind_group: &ViewDownsampleDepthBindGroup, view_size: UVec2, sample_count: u32, -) -> Result<(), NodeRunError> { - let downsample_depth_pipelines = world.resource::(); - let pipeline_cache = world.resource::(); - +) { // Despite the name "single-pass downsampling", we actually need two // passes because of the lack of `coherent` buffers in WGPU/WGSL. // Between each pass, there's an implicit synchronization barrier. @@ -259,7 +262,7 @@ fn downsample_depth<'w>( ) }) else { - return Ok(()); + return; }; // Fetch the pipelines for the two passes. @@ -267,19 +270,18 @@ fn downsample_depth<'w>( pipeline_cache.get_compute_pipeline(first_downsample_depth_pipeline_id), pipeline_cache.get_compute_pipeline(second_downsample_depth_pipeline_id), ) else { - return Ok(()); + return; }; // Run the depth downsampling. - view_depth_pyramid.downsample_depth( - &format!("{:?}", render_graph_context.label()), - render_context, + view_depth_pyramid.downsample_depth_with_ctx( + label, + ctx, view_size, view_downsample_depth_bind_group, first_downsample_depth_pipeline, second_downsample_depth_pipeline, ); - Ok(()) } /// A single depth downsample pipeline. @@ -675,19 +677,16 @@ impl ViewDepthPyramid { ) } - /// Invokes the shaders to generate the hierarchical Z-buffer. - /// - /// This is intended to be invoked as part of a render node. - pub fn downsample_depth( + pub fn downsample_depth_with_ctx( &self, label: &str, - render_context: &mut RenderContext, + ctx: &mut RenderContext, view_size: UVec2, downsample_depth_bind_group: &BindGroup, downsample_depth_first_pipeline: &ComputePipeline, downsample_depth_second_pipeline: &ComputePipeline, ) { - let command_encoder = render_context.command_encoder(); + let command_encoder = ctx.command_encoder(); let mut downsample_pass = command_encoder.begin_compute_pass(&ComputePassDescriptor { label: Some(label), timestamp_writes: None, diff --git a/crates/bevy_core_pipeline/src/fullscreen_material.rs b/crates/bevy_core_pipeline/src/fullscreen_material.rs index 4de57f54cbfc9..2845df722fc3a 100644 --- a/crates/bevy_core_pipeline/src/fullscreen_material.rs +++ b/crates/bevy_core_pipeline/src/fullscreen_material.rs @@ -1,23 +1,20 @@ //! This is mostly a pluginified version of the `custom_post_processing` example //! -//! The plugin will create a new node that runs a fullscreen triangle. +//! The plugin will create a new system that runs a fullscreen triangle. //! -//! Users need to use the [`FullscreenMaterial`] trait to define the parameters like the graph label or the graph ordering. +//! Users need to use the [`FullscreenMaterial`] trait to define the parameters like ordering. use core::any::type_name; use core::marker::PhantomData; -use crate::{core_2d::graph::Core2d, core_3d::graph::Core3d, FullscreenShader}; +use crate::{schedule::Core3d, Core3dSystems, FullscreenShader}; use bevy_app::{App, Plugin}; use bevy_asset::AssetServer; -use bevy_camera::{Camera2d, Camera3d}; use bevy_ecs::{ component::Component, - entity::Entity, - query::{Added, Has, QueryItem}, resource::Resource, - system::{Commands, Res}, - world::{FromWorld, World}, + schedule::{IntoScheduleConfigs, ScheduleLabel, SystemSet}, + system::{Commands, Local, Res}, }; use bevy_image::BevyDefault; use bevy_render::{ @@ -25,31 +22,27 @@ use bevy_render::{ ComponentUniforms, DynamicUniformIndex, ExtractComponent, ExtractComponentPlugin, UniformComponentPlugin, }, - render_graph::{ - InternedRenderLabel, InternedRenderSubGraph, NodeRunError, RenderGraph, RenderGraphContext, - RenderGraphError, RenderGraphExt, RenderLabel, ViewNode, ViewNodeRunner, - }, render_resource::{ binding_types::{sampler, texture_2d, uniform_buffer}, encase::internal::WriteInto, - BindGroupEntries, BindGroupLayoutDescriptor, BindGroupLayoutEntries, + BindGroup, BindGroupEntries, BindGroupLayoutDescriptor, BindGroupLayoutEntries, CachedRenderPipelineId, ColorTargetState, ColorWrites, FragmentState, Operations, PipelineCache, RenderPassColorAttachment, RenderPassDescriptor, RenderPipelineDescriptor, Sampler, SamplerBindingType, SamplerDescriptor, ShaderStages, ShaderType, TextureFormat, - TextureSampleType, + TextureSampleType, TextureViewId, }, - renderer::{RenderContext, RenderDevice}, + renderer::{RenderContext, RenderDevice, ViewQuery}, view::ViewTarget, - ExtractSchedule, MainWorld, RenderApp, RenderStartup, + RenderApp, RenderStartup, }; use bevy_shader::ShaderRef; use bevy_utils::default; -use tracing::warn; #[derive(Default)] pub struct FullscreenMaterialPlugin { _marker: PhantomData, } + impl Plugin for FullscreenMaterialPlugin { fn build(&self, app: &mut App) { app.add_plugins(( @@ -63,137 +56,51 @@ impl Plugin for FullscreenMaterialPlugin { render_app.add_systems(RenderStartup, init_pipeline::); - if let Some(sub_graph) = T::sub_graph() { - render_app.add_render_graph_node::>>( - sub_graph, - T::node_label(), - ); - - // We can't use add_render_graph_edges because it doesn't accept a Vec - if let Some(mut render_graph) = render_app.world_mut().get_resource_mut::() - && let Some(graph) = render_graph.get_sub_graph_mut(sub_graph) - { - for window in T::node_edges().windows(2) { - let [a, b] = window else { - break; - }; - let Err(err) = graph.try_add_node_edge(*a, *b) else { - continue; - }; - match err { - // Already existing edges are very easy to produce with this api - // and shouldn't cause a panic - RenderGraphError::EdgeAlreadyExists(_) => {} - _ => panic!("{err:?}"), - } - } - } else { - warn!("Failed to add edges for FullscreenMaterial"); - }; - } else { - // If there was no sub_graph specified we try to determine the graph based on the camera - // it gets added to. - render_app.add_systems(ExtractSchedule, extract_on_add::); - } + render_app.add_systems( + T::schedule(), + fullscreen_material_system:: + .after(T::run_after()) + .before(T::run_before()), + ); } } -fn extract_on_add(world: &mut World) { - world.resource_scope::(|world, mut main_world| { - // Extract the material from the main world - let mut query = - main_world.query_filtered::<(Entity, Has, Has), Added>(); - - // Create the node and add it to the render graph - world.resource_scope::(|world, mut render_graph| { - for (_entity, is_3d, is_2d) in query.iter(&main_world) { - let graph = if is_3d && let Some(graph) = render_graph.get_sub_graph_mut(Core3d) { - graph - } else if is_2d && let Some(graph) = render_graph.get_sub_graph_mut(Core2d) { - graph - } else { - warn!("FullscreenMaterial was added to an entity that isn't a camera"); - continue; - }; - - let node = ViewNodeRunner::>::from_world(world); - graph.add_node(T::node_label(), node); - - for window in T::node_edges().windows(2) { - let [a, b] = window else { - break; - }; - let Err(err) = graph.try_add_node_edge(*a, *b) else { - continue; - }; - match err { - // Already existing edges are very easy to produce with this api - // and shouldn't cause a panic - RenderGraphError::EdgeAlreadyExists(_) => {} - _ => panic!("{err:?}"), - } - } - } - }); - }); -} - -/// A trait to define a material that will render to the entire screen using a fullscrene triangle +/// A trait to define a material that will render to the entire screen using a fullscreen triangle. pub trait FullscreenMaterial: - Component + ExtractComponent + Clone + Copy + ShaderType + WriteInto + Default + Component + ExtractComponent + Clone + Copy + ShaderType + WriteInto + Default + Send + Sync + 'static { - /// The shader that will run on the entire screen using a fullscreen triangle + /// The shader that will run on the entire screen using a fullscreen triangle. fn fragment_shader() -> ShaderRef; - /// The list of `node_edges`. In 3d, for a post processing effect, it would look like this: - /// - /// ```compile_fail - /// # use bevy_core_pipeline::core_3d::graph::Node3d; - /// # use bevy_render::render_graph::RenderLabel; - /// vec![ - /// Node3d::Tonemapping.intern(), - /// // Self::sub_graph().intern(), // <--- your own label here - /// Node3d::EndMainPassPostProcessing.intern(), - /// ] - /// ``` - /// - /// This tell the render graph to run your fullscreen effect after the tonemapping pass but - /// before the end of post processing. For 2d, it would be the same but using Node2d. You can - /// specify any edges you want but make sure to include your own label. - fn node_edges() -> Vec; - - /// The [`bevy_render::render_graph::RenderSubGraph`] the effect will run in + /// The schedule this effect runs in. /// - /// For 2d this is generally [`crate::core_2d::graph::Core2d`] and for 3d it's - /// [`crate::core_3d::graph::Core3d`] - fn sub_graph() -> Option { - None + /// Defaults to [`Core3d`] for 3D post-processing effects. + fn schedule() -> impl ScheduleLabel + Clone { + Core3d } - /// The label used to represent the render node that will run the pass - fn node_label() -> impl RenderLabel { - FullscreenMaterialLabel(type_name::()) + /// The system set this effect runs after. + /// + /// Defaults to [`Core3dSystems::PostProcessing`]. + fn run_after() -> impl SystemSet { + Core3dSystems::PostProcessing } -} - -#[derive(Debug, Hash, PartialEq, Eq, Clone)] -struct FullscreenMaterialLabel(&'static str); -impl RenderLabel for FullscreenMaterialLabel -where - Self: 'static + Send + Sync + Clone + Eq + ::core::fmt::Debug + ::core::hash::Hash, -{ - fn dyn_clone(&self) -> Box { - Box::new(::core::clone::Clone::clone(self)) + /// The system set this effect runs before. + /// + /// Defaults to [`Core3dSystems::EndMainPassPostProcessing`]. + fn run_before() -> impl SystemSet { + Core3dSystems::EndMainPassPostProcessing } } #[derive(Resource)] -struct FullscreenMaterialPipeline { +struct FullscreenMaterialPipeline { layout: BindGroupLayoutDescriptor, sampler: Sampler, pipeline_id: CachedRenderPipelineId, pipeline_id_hdr: CachedRenderPipelineId, + _marker: PhantomData, } fn init_pipeline( @@ -204,16 +111,12 @@ fn init_pipeline( pipeline_cache: Res, ) { let layout = BindGroupLayoutDescriptor::new( - "post_process_bind_group_layout", + "fullscreen_material_bind_group_layout", &BindGroupLayoutEntries::sequential( ShaderStages::FRAGMENT, ( - // The screen texture texture_2d(TextureSampleType::Float { filterable: true }), - // The sampler that will be used to sample the screen texture sampler(SamplerBindingType::Filtering), - // We use a uniform buffer so users can pass some data to the effect - // Eventually we should just use a separate bind group for user data uniform_buffer::(true), ), ), @@ -221,8 +124,6 @@ fn init_pipeline( let sampler = render_device.create_sampler(&SamplerDescriptor::default()); let shader = match T::fragment_shader() { ShaderRef::Default => { - // TODO not sure what an actual fallback should be. An empty shader or output a solid - // color to indicate a missing shader? unimplemented!( "FullscreenMaterial::fragment_shader() must not return ShaderRef::Default" ) @@ -230,10 +131,10 @@ fn init_pipeline( ShaderRef::Handle(handle) => handle, ShaderRef::Path(path) => asset_server.load(path), }; - // Setup a fullscreen triangle for the vertex state. + let vertex_state = fullscreen_shader.to_vertex_state(); let mut desc = RenderPipelineDescriptor { - label: Some("post_process_pipeline".into()), + label: Some(format!("fullscreen_material_pipeline<{}>", type_name::()).into()), layout: vec![layout.clone()], vertex: vertex_state, fragment: Some(FragmentState { @@ -253,77 +154,88 @@ fn init_pipeline( .unwrap() .format = ViewTarget::TEXTURE_FORMAT_HDR; let pipeline_id_hdr = pipeline_cache.queue_render_pipeline(desc); - commands.insert_resource(FullscreenMaterialPipeline { + + commands.insert_resource(FullscreenMaterialPipeline:: { layout, sampler, pipeline_id, pipeline_id_hdr, + _marker: PhantomData, }); } #[derive(Default)] -struct FullscreenMaterialNode { - _marker: PhantomData, +struct FullscreenMaterialBindGroupCache { + cached: Option<(TextureViewId, BindGroup)>, } -impl ViewNode for FullscreenMaterialNode { - // TODO we should expose the depth buffer and the gbuffer if using deferred - type ViewQuery = (&'static ViewTarget, &'static DynamicUniformIndex); +fn fullscreen_material_system( + view: ViewQuery<(&ViewTarget, &DynamicUniformIndex)>, + fullscreen_pipeline: Option>>, + pipeline_cache: Res, + data_uniforms: Res>, + mut cache: Local, + mut ctx: RenderContext, +) { + let Some(fullscreen_pipeline) = fullscreen_pipeline else { + return; + }; - fn run<'w>( - &self, - _graph: &mut RenderGraphContext, - render_context: &mut RenderContext, - (view_target, settings_index): QueryItem, - world: &World, - ) -> Result<(), NodeRunError> { - let fullscreen_pipeline = world.resource::(); + let (view_target, settings_index) = view.into_inner(); - let pipeline_cache = world.resource::(); - let pipeline_id = if view_target.is_hdr() { - fullscreen_pipeline.pipeline_id_hdr - } else { - fullscreen_pipeline.pipeline_id - }; + let pipeline_id = if view_target.is_hdr() { + fullscreen_pipeline.pipeline_id_hdr + } else { + fullscreen_pipeline.pipeline_id + }; - let Some(pipeline) = pipeline_cache.get_render_pipeline(pipeline_id) else { - return Ok(()); - }; + let Some(pipeline) = pipeline_cache.get_render_pipeline(pipeline_id) else { + return; + }; - let data_uniforms = world.resource::>(); - let Some(settings_binding) = data_uniforms.uniforms().binding() else { - return Ok(()); - }; + let Some(settings_binding) = data_uniforms.uniforms().binding() else { + return; + }; - let post_process = view_target.post_process_write(); + let post_process = view_target.post_process_write(); + let source = post_process.source; + let destination = post_process.destination; + + let bind_group = match &mut cache.cached { + Some((texture_id, bind_group)) if source.id() == *texture_id => bind_group, + cached => { + let bind_group = ctx.render_device().create_bind_group( + "fullscreen_material_bind_group", + &pipeline_cache.get_bind_group_layout(&fullscreen_pipeline.layout), + &BindGroupEntries::sequential(( + source, + &fullscreen_pipeline.sampler, + settings_binding.clone(), + )), + ); - let bind_group = render_context.render_device().create_bind_group( - "post_process_bind_group", - &pipeline_cache.get_bind_group_layout(&fullscreen_pipeline.layout), - &BindGroupEntries::sequential(( - post_process.source, - &fullscreen_pipeline.sampler, - settings_binding.clone(), - )), - ); + let (_, bind_group) = cached.insert((source.id(), bind_group)); + bind_group + } + }; - let mut render_pass = render_context.begin_tracked_render_pass(RenderPassDescriptor { - label: Some("post_process_pass"), - color_attachments: &[Some(RenderPassColorAttachment { - view: post_process.destination, - depth_slice: None, - resolve_target: None, - ops: Operations::default(), - })], - depth_stencil_attachment: None, - timestamp_writes: None, - occlusion_query_set: None, - }); + let pass_descriptor = RenderPassDescriptor { + label: Some("fullscreen_material_pass"), + color_attachments: &[Some(RenderPassColorAttachment { + view: destination, + depth_slice: None, + resolve_target: None, + ops: Operations::default(), + })], + depth_stencil_attachment: None, + timestamp_writes: None, + occlusion_query_set: None, + }; - render_pass.set_render_pipeline(pipeline); - render_pass.set_bind_group(0, &bind_group, &[settings_index.index()]); + { + let mut render_pass = ctx.command_encoder().begin_render_pass(&pass_descriptor); + render_pass.set_pipeline(pipeline); + render_pass.set_bind_group(0, bind_group, &[settings_index.index()]); render_pass.draw(0..3, 0..1); - - Ok(()) } } diff --git a/crates/bevy_core_pipeline/src/lib.rs b/crates/bevy_core_pipeline/src/lib.rs index 6037e89cc325d..2c2d40d3e9cc8 100644 --- a/crates/bevy_core_pipeline/src/lib.rs +++ b/crates/bevy_core_pipeline/src/lib.rs @@ -14,10 +14,12 @@ pub mod experimental; pub mod fullscreen_material; pub mod oit; pub mod prepass; +pub mod schedule; pub mod tonemapping; pub mod upscaling; pub use fullscreen_vertex_shader::FullscreenShader; +pub use schedule::{Core2d, Core2dSystems, Core3d, Core3dSystems}; pub use skybox::Skybox; mod fullscreen_vertex_shader; @@ -32,7 +34,9 @@ use crate::{ use bevy_app::{App, Plugin}; use bevy_asset::embedded_asset; use bevy_render::RenderApp; +use bevy_render::renderer::RenderGraph; use oit::OrderIndependentTransparencyPlugin; +use crate::schedule::camera_driver; #[derive(Default)] pub struct CorePipelinePlugin; @@ -52,6 +56,8 @@ impl Plugin for CorePipelinePlugin { let Some(render_app) = app.get_sub_app_mut(RenderApp) else { return; }; - render_app.init_resource::(); + render_app + .init_resource::() + .add_systems(RenderGraph, camera_driver); } } diff --git a/crates/bevy_core_pipeline/src/oit/mod.rs b/crates/bevy_core_pipeline/src/oit/mod.rs index 51c07dff717c6..62d958ee46a2d 100644 --- a/crates/bevy_core_pipeline/src/oit/mod.rs +++ b/crates/bevy_core_pipeline/src/oit/mod.rs @@ -10,7 +10,6 @@ use bevy_reflect::{std_traits::ReflectDefault, Reflect}; use bevy_render::{ camera::ExtractedCamera, extract_component::{ExtractComponent, ExtractComponentPlugin}, - render_graph::{RenderGraphExt, ViewNodeRunner}, render_resource::{BufferUsages, BufferVec, DynamicUniformBuffer, ShaderType, TextureUsages}, renderer::{RenderDevice, RenderQueue}, view::Msaa, @@ -18,13 +17,13 @@ use bevy_render::{ }; use bevy_shader::load_shader_library; use bevy_window::PrimaryWindow; -use resolve::{ - node::{OitResolveNode, OitResolvePass}, - OitResolvePlugin, -}; +use resolve::{node::oit_resolve, OitResolvePlugin}; use tracing::{trace, warn}; -use crate::core_3d::graph::{Core3d, Node3d}; +use crate::{ + core_3d::main_transparent_pass_3d, + schedule::{Core3d, Core3dSystems}, +}; /// Module that defines the necessary systems to resolve the OIT buffer and render it to the screen. pub mod resolve; @@ -117,16 +116,12 @@ impl Plugin for OrderIndependentTransparencyPlugin { prepare_oit_buffers.in_set(RenderSystems::PrepareResources), ); - render_app - .add_render_graph_node::>(Core3d, OitResolvePass) - .add_render_graph_edges( - Core3d, - ( - Node3d::MainTransparentPass, - OitResolvePass, - Node3d::EndMainPass, - ), - ); + render_app.add_systems( + Core3d, + oit_resolve + .after(main_transparent_pass_3d) + .before(Core3dSystems::EndMainPass), + ); } } diff --git a/crates/bevy_core_pipeline/src/oit/resolve/node.rs b/crates/bevy_core_pipeline/src/oit/resolve/node.rs index 4aa1ee201c8f6..0532c2d7bad0d 100644 --- a/crates/bevy_core_pipeline/src/oit/resolve/node.rs +++ b/crates/bevy_core_pipeline/src/oit/resolve/node.rs @@ -1,89 +1,71 @@ use bevy_camera::{MainPassResolutionOverride, Viewport}; -use bevy_ecs::{prelude::*, query::QueryItem}; +use bevy_ecs::prelude::*; use bevy_render::{ camera::ExtractedCamera, diagnostic::RecordDiagnostics, - render_graph::{NodeRunError, RenderGraphContext, RenderLabel, ViewNode}, render_resource::{BindGroupEntries, PipelineCache, RenderPassDescriptor}, - renderer::RenderContext, + renderer::{RenderContext, ViewQuery}, view::{ViewDepthTexture, ViewTarget, ViewUniformOffset}, }; use super::{OitResolveBindGroup, OitResolvePipeline, OitResolvePipelineId}; -/// Render label for the OIT resolve pass. -#[derive(RenderLabel, Debug, Clone, Hash, PartialEq, Eq)] -pub struct OitResolvePass; +pub fn oit_resolve( + view: ViewQuery<( + &ExtractedCamera, + &ViewTarget, + &ViewUniformOffset, + &OitResolvePipelineId, + &ViewDepthTexture, + Option<&MainPassResolutionOverride>, + )>, + resolve_pipeline: Option>, + bind_group: Option>, + pipeline_cache: Res, + mut ctx: RenderContext, +) { + let (camera, view_target, view_uniform, oit_resolve_pipeline_id, depth, resolution_override) = + view.into_inner(); -/// The node that executes the OIT resolve pass. -#[derive(Default)] -pub struct OitResolveNode; -impl ViewNode for OitResolveNode { - type ViewQuery = ( - &'static ExtractedCamera, - &'static ViewTarget, - &'static ViewUniformOffset, - &'static OitResolvePipelineId, - &'static ViewDepthTexture, - Option<&'static MainPassResolutionOverride>, - ); - - fn run( - &self, - _graph: &mut RenderGraphContext, - render_context: &mut RenderContext, - (camera, view_target, view_uniform, oit_resolve_pipeline_id, depth, resolution_override): QueryItem< - Self::ViewQuery, - >, - world: &World, - ) -> Result<(), NodeRunError> { - let Some(resolve_pipeline) = world.get_resource::() else { - return Ok(()); - }; - - // resolve oit - // sorts the layers and renders the final blended color to the screen - { - let pipeline_cache = world.resource::(); - let bind_group = world.resource::(); - let Some(pipeline) = pipeline_cache.get_render_pipeline(oit_resolve_pipeline_id.0) - else { - return Ok(()); - }; - - let diagnostics = render_context.diagnostic_recorder(); + let Some(resolve_pipeline) = resolve_pipeline else { + return; + }; + let Some(bind_group) = bind_group else { + return; + }; + let Some(pipeline) = pipeline_cache.get_render_pipeline(oit_resolve_pipeline_id.0) else { + return; + }; - let depth_bind_group = render_context.render_device().create_bind_group( - "oit_resolve_depth_bind_group", - &pipeline_cache - .get_bind_group_layout(&resolve_pipeline.oit_depth_bind_group_layout), - &BindGroupEntries::single(depth.view()), - ); + let depth_bind_group = ctx.render_device().create_bind_group( + "oit_resolve_depth_bind_group", + &pipeline_cache.get_bind_group_layout(&resolve_pipeline.oit_depth_bind_group_layout), + &BindGroupEntries::single(depth.view()), + ); - let mut render_pass = render_context.begin_tracked_render_pass(RenderPassDescriptor { - label: Some("oit_resolve"), - color_attachments: &[Some(view_target.get_color_attachment())], - depth_stencil_attachment: None, - timestamp_writes: None, - occlusion_query_set: None, - }); - let pass_span = diagnostics.pass_span(&mut render_pass, "oit_resolve"); + let diagnostics = ctx.diagnostic_recorder(); + let diagnostics = diagnostics.as_deref(); - if let Some(viewport) = - Viewport::from_viewport_and_override(camera.viewport.as_ref(), resolution_override) - { - render_pass.set_camera_viewport(&viewport); - } + let mut render_pass = ctx.begin_tracked_render_pass(RenderPassDescriptor { + label: Some("oit_resolve"), + color_attachments: &[Some(view_target.get_color_attachment())], + depth_stencil_attachment: None, + timestamp_writes: None, + occlusion_query_set: None, + }); + let pass_span = diagnostics.pass_span(&mut render_pass, "oit_resolve"); - render_pass.set_render_pipeline(pipeline); - render_pass.set_bind_group(0, bind_group, &[view_uniform.offset]); - render_pass.set_bind_group(1, &depth_bind_group, &[]); + if let Some(viewport) = + Viewport::from_viewport_and_override(camera.viewport.as_ref(), resolution_override) + { + render_pass.set_camera_viewport(&viewport); + } - render_pass.draw(0..3, 0..1); + render_pass.set_render_pipeline(pipeline); + render_pass.set_bind_group(0, &bind_group, &[view_uniform.offset]); + render_pass.set_bind_group(1, &depth_bind_group, &[]); - pass_span.end(&mut render_pass); - } + render_pass.draw(0..3, 0..1); - Ok(()) - } + pass_span.end(&mut render_pass); } diff --git a/crates/bevy_core_pipeline/src/prepass/node.rs b/crates/bevy_core_pipeline/src/prepass/node.rs index d429fc9289d76..5be8090d79784 100644 --- a/crates/bevy_core_pipeline/src/prepass/node.rs +++ b/crates/bevy_core_pipeline/src/prepass/node.rs @@ -1,13 +1,12 @@ use bevy_camera::{MainPassResolutionOverride, Viewport}; -use bevy_ecs::{prelude::*, query::QueryItem}; +use bevy_ecs::prelude::*; use bevy_render::{ camera::ExtractedCamera, diagnostic::RecordDiagnostics, experimental::occlusion_culling::OcclusionCulling, - render_graph::{NodeRunError, RenderGraphContext, ViewNode}, - render_phase::{TrackedRenderPass, ViewBinnedRenderPhases}, - render_resource::{CommandEncoderDescriptor, PipelineCache, RenderPassDescriptor, StoreOp}, - renderer::RenderContext, + render_phase::ViewBinnedRenderPhases, + render_resource::{PipelineCache, RenderPassDescriptor, StoreOp}, + renderer::{RenderContext, ViewQuery}, view::{ExtractedView, NoIndirectDrawing, ViewDepthTexture, ViewUniformOffset}, }; use tracing::error; @@ -21,93 +20,82 @@ use super::{ ViewPrepassTextures, }; -/// The phase of the prepass that draws meshes that were visible last frame. -/// -/// If occlusion culling isn't in use, this prepass simply draws all meshes. -/// -/// Like all prepass nodes, this is inserted before the main pass in the render -/// graph. -#[derive(Default)] -pub struct EarlyPrepassNode; - -impl ViewNode for EarlyPrepassNode { - type ViewQuery = ::ViewQuery; - - fn run<'w>( - &self, - graph: &mut RenderGraphContext, - render_context: &mut RenderContext<'w>, - view_query: QueryItem<'w, '_, Self::ViewQuery>, - world: &'w World, - ) -> Result<(), NodeRunError> { - run_prepass(graph, render_context, view_query, world, "early prepass") - } -} +/// Type alias for the prepass view query. +type PrepassViewQueryData = ( + ( + &'static ExtractedCamera, + &'static ExtractedView, + &'static ViewDepthTexture, + &'static ViewPrepassTextures, + &'static ViewUniformOffset, + ), + ( + Option<&'static DeferredPrepass>, + Option<&'static RenderSkyboxPrepassPipeline>, + Option<&'static SkyboxPrepassBindGroup>, + Option<&'static PreviousViewUniformOffset>, + Option<&'static MainPassResolutionOverride>, + ), + ( + Has, + Has, + Has, + ), +); -/// The phase of the prepass that runs after occlusion culling against the -/// meshes that were visible last frame. -/// -/// If occlusion culling isn't in use, this is a no-op. -/// -/// Like all prepass nodes, this is inserted before the main pass in the render -/// graph. -#[derive(Default)] -pub struct LatePrepassNode; - -impl ViewNode for LatePrepassNode { - type ViewQuery = ( - ( - &'static ExtractedCamera, - &'static ExtractedView, - &'static ViewDepthTexture, - &'static ViewPrepassTextures, - &'static ViewUniformOffset, - ), - ( - Option<&'static DeferredPrepass>, - Option<&'static RenderSkyboxPrepassPipeline>, - Option<&'static SkyboxPrepassBindGroup>, - Option<&'static PreviousViewUniformOffset>, - Option<&'static MainPassResolutionOverride>, - ), +pub fn early_prepass( + world: &World, + view: ViewQuery, + opaque_prepass_phases: Res>, + alpha_mask_prepass_phases: Res>, + pipeline_cache: Res, + mut ctx: RenderContext, +) { + let view_entity = view.entity(); + let ( + (camera, extracted_view, view_depth_texture, view_prepass_textures, view_uniform_offset), ( - Has, - Has, - Has, + deferred_prepass, + skybox_prepass_pipeline, + skybox_prepass_bind_group, + view_prev_uniform_offset, + resolution_override, ), - ); - - fn run<'w>( - &self, - graph: &mut RenderGraphContext, - render_context: &mut RenderContext<'w>, - query: QueryItem<'w, '_, Self::ViewQuery>, - world: &'w World, - ) -> Result<(), NodeRunError> { - // We only need a late prepass if we have occlusion culling and indirect - // drawing. - let (_, _, (occlusion_culling, no_indirect_drawing, _)) = query; - if !occlusion_culling || no_indirect_drawing { - return Ok(()); - } + (_, _, has_deferred), + ) = view.into_inner(); - run_prepass(graph, render_context, query, world, "late prepass") - } + run_prepass_system( + world, + view_entity, + camera, + extracted_view, + view_depth_texture, + view_prepass_textures, + view_uniform_offset, + deferred_prepass, + skybox_prepass_pipeline, + skybox_prepass_bind_group, + view_prev_uniform_offset, + resolution_override, + has_deferred, + &opaque_prepass_phases, + &alpha_mask_prepass_phases, + &pipeline_cache, + &mut ctx, + "early prepass", + ); } -/// Runs a prepass that draws all meshes to the depth buffer, and possibly -/// normal and motion vector buffers as well. -/// -/// If occlusion culling isn't in use, and a prepass is enabled, then there's -/// only one prepass. If occlusion culling is in use, then any prepass is split -/// into two: an *early* prepass and a *late* prepass. The early prepass draws -/// what was visible last frame, and the last prepass performs occlusion culling -/// against a conservative hierarchical Z buffer before drawing unoccluded -/// meshes. -fn run_prepass<'w>( - graph: &mut RenderGraphContext, - render_context: &mut RenderContext<'w>, - ( +pub fn late_prepass( + world: &World, + view: ViewQuery, + opaque_prepass_phases: Res>, + alpha_mask_prepass_phases: Res>, + pipeline_cache: Res, + mut ctx: RenderContext, +) { + let view_entity = view.entity(); + let ( (camera, extracted_view, view_depth_texture, view_prepass_textures, view_uniform_offset), ( deferred_prepass, @@ -116,33 +104,76 @@ fn run_prepass<'w>( view_prev_uniform_offset, resolution_override, ), - (_, _, has_deferred), - ): QueryItem<'w, '_, ::ViewQuery>, - world: &'w World, + (occlusion_culling, no_indirect_drawing, has_deferred), + ) = view.into_inner(); + + if !occlusion_culling || no_indirect_drawing { + return; + } + + run_prepass_system( + world, + view_entity, + camera, + extracted_view, + view_depth_texture, + view_prepass_textures, + view_uniform_offset, + deferred_prepass, + skybox_prepass_pipeline, + skybox_prepass_bind_group, + view_prev_uniform_offset, + resolution_override, + has_deferred, + &opaque_prepass_phases, + &alpha_mask_prepass_phases, + &pipeline_cache, + &mut ctx, + "late prepass", + ); +} + +/// Shared implementation for prepass systems. +#[allow(clippy::too_many_arguments)] +fn run_prepass_system( + world: &World, + view_entity: Entity, + camera: &ExtractedCamera, + extracted_view: &ExtractedView, + view_depth_texture: &ViewDepthTexture, + view_prepass_textures: &ViewPrepassTextures, + view_uniform_offset: &ViewUniformOffset, + deferred_prepass: Option<&DeferredPrepass>, + skybox_prepass_pipeline: Option<&RenderSkyboxPrepassPipeline>, + skybox_prepass_bind_group: Option<&SkyboxPrepassBindGroup>, + view_prev_uniform_offset: Option<&PreviousViewUniformOffset>, + resolution_override: Option<&MainPassResolutionOverride>, + has_deferred: bool, + opaque_prepass_phases: &ViewBinnedRenderPhases, + alpha_mask_prepass_phases: &ViewBinnedRenderPhases, + pipeline_cache: &PipelineCache, + ctx: &mut RenderContext, label: &'static str, -) -> Result<(), NodeRunError> { +) { // If we're using deferred rendering, there will be a deferred prepass // instead of this one. Just bail out so we don't have to bother looking at // the empty bins. if has_deferred { - return Ok(()); + return; } - let (Some(opaque_prepass_phases), Some(alpha_mask_prepass_phases)) = ( - world.get_resource::>(), - world.get_resource::>(), - ) else { - return Ok(()); - }; - let (Some(opaque_prepass_phase), Some(alpha_mask_prepass_phase)) = ( opaque_prepass_phases.get(&extracted_view.retained_view_entity), alpha_mask_prepass_phases.get(&extracted_view.retained_view_entity), ) else { - return Ok(()); + return; }; - let diagnostics = render_context.diagnostic_recorder(); + #[cfg(feature = "trace")] + let _prepass_span = info_span!("prepass").entered(); + + let diagnostics = ctx.diagnostic_recorder(); + let diagnostics = diagnostics.as_deref(); let mut color_attachments = vec![ view_prepass_textures @@ -165,91 +196,67 @@ fn run_prepass<'w>( let depth_stencil_attachment = Some(view_depth_texture.get_attachment(StoreOp::Store)); - let view_entity = graph.view_entity(); - render_context.add_command_buffer_generation_task(move |render_device| { - #[cfg(feature = "trace")] - let _prepass_span = info_span!("prepass").entered(); - - // Command encoder setup - let mut command_encoder = render_device.create_command_encoder(&CommandEncoderDescriptor { - label: Some("prepass_command_encoder"), - }); - - // Render pass setup - let render_pass = command_encoder.begin_render_pass(&RenderPassDescriptor { - label: Some(label), - color_attachments: &color_attachments, - depth_stencil_attachment, - timestamp_writes: None, - occlusion_query_set: None, - }); - - let mut render_pass = TrackedRenderPass::new(&render_device, render_pass); - let pass_span = diagnostics.pass_span(&mut render_pass, label); - - if let Some(viewport) = - Viewport::from_viewport_and_override(camera.viewport.as_ref(), resolution_override) - { - render_pass.set_camera_viewport(&viewport); - } + let mut render_pass = ctx.begin_tracked_render_pass(RenderPassDescriptor { + label: Some(label), + color_attachments: &color_attachments, + depth_stencil_attachment, + timestamp_writes: None, + occlusion_query_set: None, + }); + let pass_span = diagnostics.pass_span(&mut render_pass, label); - // Opaque draws - if !opaque_prepass_phase.is_empty() { - #[cfg(feature = "trace")] - let _opaque_prepass_span = info_span!("opaque_prepass").entered(); - if let Err(err) = opaque_prepass_phase.render(&mut render_pass, world, view_entity) { - error!("Error encountered while rendering the opaque prepass phase {err:?}"); - } + if let Some(viewport) = + Viewport::from_viewport_and_override(camera.viewport.as_ref(), resolution_override) + { + render_pass.set_camera_viewport(&viewport); + } + + if !opaque_prepass_phase.is_empty() { + #[cfg(feature = "trace")] + let _opaque_prepass_span = info_span!("opaque_prepass").entered(); + if let Err(err) = opaque_prepass_phase.render(&mut render_pass, world, view_entity) { + error!("Error encountered while rendering the opaque prepass phase {err:?}"); } + } - // Alpha masked draws - if !alpha_mask_prepass_phase.is_empty() { - #[cfg(feature = "trace")] - let _alpha_mask_prepass_span = info_span!("alpha_mask_prepass").entered(); - if let Err(err) = alpha_mask_prepass_phase.render(&mut render_pass, world, view_entity) - { - error!("Error encountered while rendering the alpha mask prepass phase {err:?}"); - } + if !alpha_mask_prepass_phase.is_empty() { + #[cfg(feature = "trace")] + let _alpha_mask_prepass_span = info_span!("alpha_mask_prepass").entered(); + if let Err(err) = alpha_mask_prepass_phase.render(&mut render_pass, world, view_entity) { + error!("Error encountered while rendering the alpha mask prepass phase {err:?}"); } + } - // Skybox draw using a fullscreen triangle - if let ( - Some(skybox_prepass_pipeline), - Some(skybox_prepass_bind_group), - Some(view_prev_uniform_offset), - ) = ( - skybox_prepass_pipeline, - skybox_prepass_bind_group, - view_prev_uniform_offset, - ) { - let pipeline_cache = world.resource::(); - if let Some(pipeline) = pipeline_cache.get_render_pipeline(skybox_prepass_pipeline.0) { - render_pass.set_render_pipeline(pipeline); - render_pass.set_bind_group( - 0, - &skybox_prepass_bind_group.0, - &[view_uniform_offset.offset, view_prev_uniform_offset.offset], - ); - render_pass.draw(0..3, 0..1); - } + if let ( + Some(skybox_prepass_pipeline), + Some(skybox_prepass_bind_group), + Some(view_prev_uniform_offset), + ) = ( + skybox_prepass_pipeline, + skybox_prepass_bind_group, + view_prev_uniform_offset, + ) { + if let Some(pipeline) = pipeline_cache.get_render_pipeline(skybox_prepass_pipeline.0) { + render_pass.set_render_pipeline(pipeline); + render_pass.set_bind_group( + 0, + &skybox_prepass_bind_group.0, + &[view_uniform_offset.offset, view_prev_uniform_offset.offset], + ); + render_pass.draw(0..3, 0..1); } + } - pass_span.end(&mut render_pass); - drop(render_pass); + pass_span.end(&mut render_pass); + drop(render_pass); - // After rendering to the view depth texture, copy it to the prepass depth texture if deferred isn't going to - if deferred_prepass.is_none() - && let Some(prepass_depth_texture) = &view_prepass_textures.depth - { - command_encoder.copy_texture_to_texture( + if deferred_prepass.is_none() { + if let Some(prepass_depth_texture) = &view_prepass_textures.depth { + ctx.command_encoder().copy_texture_to_texture( view_depth_texture.texture.as_image_copy(), prepass_depth_texture.texture.texture.as_image_copy(), view_prepass_textures.size, ); } - - command_encoder.finish() - }); - - Ok(()) + } } diff --git a/crates/bevy_core_pipeline/src/schedule.rs b/crates/bevy_core_pipeline/src/schedule.rs new file mode 100644 index 0000000000000..098e2920667d9 --- /dev/null +++ b/crates/bevy_core_pipeline/src/schedule.rs @@ -0,0 +1,213 @@ +use bevy_ecs::{ + prelude::*, + schedule::{IntoScheduleConfigs, Schedule, ScheduleLabel, SystemSet}, +}; +use bevy_render::{ + camera::{ExtractedCamera, SortedCameras}, + render_resource::{ + CommandEncoderDescriptor, LoadOp, Operations, RenderPassColorAttachment, + RenderPassDescriptor, StoreOp, + }, + renderer::{ + CurrentViewEntity, PendingCommandBuffers, RenderDevice, + RenderQueue, + }, + view::ExtractedWindows, +}; +use bevy_camera::{ClearColor, NormalizedRenderTarget}; +use bevy_platform::collections::HashSet; + +/// Schedule label for the Core 3D rendering pipeline. +#[derive(ScheduleLabel, Debug, Clone, PartialEq, Eq, Hash, Default)] +pub struct Core3d; + +#[derive(SystemSet, Debug, Clone, PartialEq, Eq, Hash)] +pub enum Core3dSystems { + EndPrepasses, + StartMainPass, + EndMainPass, + StartMainPassPostProcessing, + PostProcessing, + EndMainPassPostProcessing, +} + +impl Core3d { + pub fn base_schedule() -> Schedule { + use bevy_ecs::schedule::ScheduleBuildSettings; + use Core3dSystems::*; + + let mut schedule = Schedule::new(Self); + + schedule.set_build_settings(ScheduleBuildSettings { + // TODO: yes/no? + auto_insert_apply_deferred: false, + ..Default::default() + }); + + schedule.configure_sets( + ( + EndPrepasses, + StartMainPass, + EndMainPass, + StartMainPassPostProcessing, + PostProcessing, + EndMainPassPostProcessing, + ) + .chain(), + ); + + schedule + } +} + +#[derive(ScheduleLabel, Debug, Clone, PartialEq, Eq, Hash, Default)] +pub struct Core2d; + +#[derive(SystemSet, Debug, Clone, PartialEq, Eq, Hash)] +pub enum Core2dSystems { + StartMainPass, + EndMainPass, + StartMainPassPostProcessing, + PostProcessing, + EndMainPassPostProcessing, +} + +impl Core2d { + pub fn base_schedule() -> Schedule { + use bevy_ecs::schedule::ScheduleBuildSettings; + use Core2dSystems::*; + + let mut schedule = Schedule::new(Self); + + schedule.set_build_settings(ScheduleBuildSettings { + auto_insert_apply_deferred: false, + ..Default::default() + }); + + schedule.configure_sets( + ( + StartMainPass, + EndMainPass, + StartMainPassPostProcessing, + PostProcessing, + EndMainPassPostProcessing, + ) + .chain(), + ); + + schedule + } +} + +pub fn camera_driver(world: &mut World) { + let sorted_cameras: Vec<_> = { + let sorted = world.resource::(); + sorted.0.iter().map(|c| (c.entity, c.order)).collect() + }; + + let mut camera_windows = HashSet::default(); + + for (camera_entity, order) in sorted_cameras { + let Some(camera) = world.get::(camera_entity) else { + continue; + }; + + let schedule = camera.schedule; + let target = camera.target.clone(); + + let mut run_schedule = true; + if let Some(NormalizedRenderTarget::Window(window_ref)) = &target { + let window_entity = window_ref.entity(); + let windows = world.resource::(); + if windows + .windows + .get(&window_entity) + .is_some_and(|w| w.physical_width > 0 && w.physical_height > 0) + { + camera_windows.insert(window_entity); + } else { + run_schedule = false; + } + } + + if run_schedule { + world.insert_resource(CurrentViewEntity::new(camera_entity)); + + #[cfg(feature = "trace")] + let _span = tracing::info_span!( + "camera_schedule", + camera = format!("Camera {} ({:?})", order, camera_entity) + ) + .entered(); + + world.run_schedule(schedule); + submit_pending_command_buffers(world); + } + } + + world.remove_resource::(); + handle_uncovered_swap_chains(world, &camera_windows); +} + +fn submit_pending_command_buffers(world: &mut World) { + let mut pending = world.resource_mut::(); + let buffers = pending.take(); + + if !buffers.is_empty() { + let queue = world.resource::(); + queue.submit(buffers); + } +} + +fn handle_uncovered_swap_chains(world: &mut World, camera_windows: &HashSet) { + let windows_to_clear: Vec<_> = { + let clear_color = world.resource::().0.to_linear(); + let windows = world.resource::(); + + windows + .iter() + .filter_map(|(window_entity, window)| { + if camera_windows.contains(window_entity) { + return None; + } + let swap_chain_texture = window.swap_chain_texture_view.as_ref()?; + Some((swap_chain_texture.clone(), clear_color)) + }) + .collect() + }; + + if windows_to_clear.is_empty() { + return; + } + + let render_device = world.resource::(); + let render_queue = world.resource::(); + + let mut encoder = + render_device.create_command_encoder(&CommandEncoderDescriptor::default()); + + for (swap_chain_texture, clear_color) in &windows_to_clear { + #[cfg(feature = "trace")] + let _span = tracing::info_span!("no_camera_clear_pass").entered(); + + let pass_descriptor = RenderPassDescriptor { + label: Some("no_camera_clear_pass"), + color_attachments: &[Some(RenderPassColorAttachment { + view: swap_chain_texture, + depth_slice: None, + resolve_target: None, + ops: Operations { + load: LoadOp::Clear((*clear_color).into()), + store: StoreOp::Store, + }, + })], + depth_stencil_attachment: None, + timestamp_writes: None, + occlusion_query_set: None, + }; + + encoder.begin_render_pass(&pass_descriptor); + } + + render_queue.submit([encoder.finish()]); +} diff --git a/crates/bevy_core_pipeline/src/tonemapping/mod.rs b/crates/bevy_core_pipeline/src/tonemapping/mod.rs index 93681a5c199f4..9aa1e519ce105 100644 --- a/crates/bevy_core_pipeline/src/tonemapping/mod.rs +++ b/crates/bevy_core_pipeline/src/tonemapping/mod.rs @@ -27,7 +27,7 @@ use tracing::error; mod node; use bevy_utils::default; -pub use node::TonemappingNode; +pub use node::tonemapping; use crate::FullscreenShader; diff --git a/crates/bevy_core_pipeline/src/tonemapping/node.rs b/crates/bevy_core_pipeline/src/tonemapping/node.rs index 19fb63b6347e8..0f9a980826db7 100644 --- a/crates/bevy_core_pipeline/src/tonemapping/node.rs +++ b/crates/bevy_core_pipeline/src/tonemapping/node.rs @@ -1,148 +1,136 @@ -use std::sync::Mutex; - use crate::tonemapping::{TonemappingLuts, TonemappingPipeline, ViewTonemappingPipeline}; -use bevy_ecs::{prelude::*, query::QueryItem}; +use bevy_ecs::prelude::*; use bevy_render::{ diagnostic::RecordDiagnostics, render_asset::RenderAssets, - render_graph::{NodeRunError, RenderGraphContext, ViewNode}, render_resource::{ BindGroup, BindGroupEntries, BufferId, LoadOp, Operations, PipelineCache, RenderPassColorAttachment, RenderPassDescriptor, StoreOp, TextureViewId, }, - renderer::RenderContext, + renderer::{RenderContext, ViewQuery}, texture::{FallbackImage, GpuImage}, view::{ViewTarget, ViewUniformOffset, ViewUniforms}, }; use super::{get_lut_bindings, Tonemapping}; +/// Cached bind group state for tonemapping. #[derive(Default)] -pub struct TonemappingNode { - cached_bind_group: Mutex>, - last_tonemapping: Mutex>, +pub struct TonemappingBindGroupCache { + cached: Option<(BufferId, TextureViewId, TextureViewId, BindGroup)>, + last_tonemapping: Option, } -impl ViewNode for TonemappingNode { - type ViewQuery = ( - &'static ViewUniformOffset, - &'static ViewTarget, - &'static ViewTonemappingPipeline, - &'static Tonemapping, - ); - - fn run( - &self, - _graph: &mut RenderGraphContext, - render_context: &mut RenderContext, - (view_uniform_offset, target, view_tonemapping_pipeline, tonemapping): QueryItem< - Self::ViewQuery, - >, - world: &World, - ) -> Result<(), NodeRunError> { - let pipeline_cache = world.resource::(); - let tonemapping_pipeline = world.resource::(); - let gpu_images = world.get_resource::>().unwrap(); - let fallback_image = world.resource::(); - let view_uniforms_resource = world.resource::(); - let view_uniforms = &view_uniforms_resource.uniforms; - let view_uniforms_id = view_uniforms.buffer().unwrap().id(); - - if *tonemapping == Tonemapping::None { - return Ok(()); - } +pub fn tonemapping( + view: ViewQuery<( + &ViewUniformOffset, + &ViewTarget, + &ViewTonemappingPipeline, + &Tonemapping, + )>, + pipeline_cache: Res, + tonemapping_pipeline: Res, + gpu_images: Res>, + fallback_image: Res, + view_uniforms: Res, + tonemapping_luts: Res, + mut cache: Local, + mut ctx: RenderContext, +) { + let (view_uniform_offset, target, view_tonemapping_pipeline, tonemapping) = view.into_inner(); + + if *tonemapping == Tonemapping::None { + return; + } - if !target.is_hdr() { - return Ok(()); - } + if !target.is_hdr() { + return; + } - let Some(pipeline) = pipeline_cache.get_render_pipeline(view_tonemapping_pipeline.0) else { - return Ok(()); - }; + let Some(pipeline) = pipeline_cache.get_render_pipeline(view_tonemapping_pipeline.0) else { + return; + }; - let diagnostics = render_context.diagnostic_recorder(); + let view_uniforms_buffer = &view_uniforms.uniforms; + let view_uniforms_id = view_uniforms_buffer.buffer().unwrap().id(); - let post_process = target.post_process_write(); - let source = post_process.source; - let destination = post_process.destination; + let post_process = target.post_process_write(); + let source = post_process.source; + let destination = post_process.destination; - let mut last_tonemapping = self.last_tonemapping.lock().unwrap(); + let tonemapping_changed = cache + .last_tonemapping + .map_or(true, |last| *tonemapping != last); + if tonemapping_changed { + cache.last_tonemapping = Some(*tonemapping); + } - let tonemapping_changed = if let Some(last_tonemapping) = &*last_tonemapping { - tonemapping != last_tonemapping - } else { - true - }; - if tonemapping_changed { - *last_tonemapping = Some(*tonemapping); + let bind_group = match &mut cache.cached { + Some((buffer_id, texture_id, lut_id, bind_group)) + if view_uniforms_id == *buffer_id + && source.id() == *texture_id + && *lut_id != fallback_image.d3.texture_view.id() + && !tonemapping_changed => + { + bind_group } - - let mut cached_bind_group = self.cached_bind_group.lock().unwrap(); - let bind_group = match &mut *cached_bind_group { - Some((buffer_id, texture_id, lut_id, bind_group)) - if view_uniforms_id == *buffer_id - && source.id() == *texture_id - && *lut_id != fallback_image.d3.texture_view.id() - && !tonemapping_changed => - { - bind_group - } - cached_bind_group => { - let tonemapping_luts = world.resource::(); - - let lut_bindings = - get_lut_bindings(gpu_images, tonemapping_luts, tonemapping, fallback_image); - - let bind_group = render_context.render_device().create_bind_group( - None, - &pipeline_cache.get_bind_group_layout(&tonemapping_pipeline.texture_bind_group), - &BindGroupEntries::sequential(( - view_uniforms, - source, - &tonemapping_pipeline.sampler, - lut_bindings.0, - lut_bindings.1, - )), - ); - - let (_, _, _, bind_group) = cached_bind_group.insert(( - view_uniforms_id, - source.id(), - lut_bindings.0.id(), - bind_group, - )); - bind_group - } - }; - - let pass_descriptor = RenderPassDescriptor { - label: Some("tonemapping"), - color_attachments: &[Some(RenderPassColorAttachment { - view: destination, - depth_slice: None, - resolve_target: None, - ops: Operations { - load: LoadOp::Clear(Default::default()), // TODO shouldn't need to be cleared - store: StoreOp::Store, - }, - })], - depth_stencil_attachment: None, - timestamp_writes: None, - occlusion_query_set: None, - }; - - let mut render_pass = render_context + cached => { + let lut_bindings = + get_lut_bindings(&gpu_images, &tonemapping_luts, tonemapping, &fallback_image); + + let bind_group = ctx.render_device().create_bind_group( + None, + &pipeline_cache.get_bind_group_layout(&tonemapping_pipeline.texture_bind_group), + &BindGroupEntries::sequential(( + view_uniforms_buffer, + source, + &tonemapping_pipeline.sampler, + lut_bindings.0, + lut_bindings.1, + )), + ); + + let (_, _, _, bind_group) = cached.insert(( + view_uniforms_id, + source.id(), + lut_bindings.0.id(), + bind_group, + )); + bind_group + } + }; + + let pass_descriptor = RenderPassDescriptor { + label: Some("tonemapping"), + color_attachments: &[Some(RenderPassColorAttachment { + view: destination, + depth_slice: None, + resolve_target: None, + ops: Operations { + load: LoadOp::Clear(Default::default()), // TODO shouldn't need to be cleared + store: StoreOp::Store, + }, + })], + depth_stencil_attachment: None, + timestamp_writes: None, + occlusion_query_set: None, + }; + + let diagnostics = ctx.diagnostic_recorder(); + let diagnostics = diagnostics.as_deref(); + let time_span = diagnostics.time_span(ctx.command_encoder(), "tonemapping"); + + { + let mut render_pass = ctx .command_encoder() .begin_render_pass(&pass_descriptor); - let pass_span = diagnostics.pass_span(&mut render_pass, "tonemapping"); render_pass.set_pipeline(pipeline); render_pass.set_bind_group(0, bind_group, &[view_uniform_offset.offset]); render_pass.draw(0..3, 0..1); - - pass_span.end(&mut render_pass); - - Ok(()) } + + time_span.end(ctx.command_encoder()); } + diff --git a/crates/bevy_core_pipeline/src/upscaling/mod.rs b/crates/bevy_core_pipeline/src/upscaling/mod.rs index 053ce5007a8a7..fe7d83298d27f 100644 --- a/crates/bevy_core_pipeline/src/upscaling/mod.rs +++ b/crates/bevy_core_pipeline/src/upscaling/mod.rs @@ -9,7 +9,7 @@ use bevy_render::{ mod node; -pub use node::UpscalingNode; +pub use node::upscaling; pub struct UpscalingPlugin; diff --git a/crates/bevy_core_pipeline/src/upscaling/node.rs b/crates/bevy_core_pipeline/src/upscaling/node.rs index 3d7b2c9905068..ea2337badf53b 100644 --- a/crates/bevy_core_pipeline/src/upscaling/node.rs +++ b/crates/bevy_core_pipeline/src/upscaling/node.rs @@ -1,92 +1,83 @@ use crate::{blit::BlitPipeline, upscaling::ViewUpscalingPipeline}; use bevy_camera::{CameraOutputMode, ClearColor, ClearColorConfig}; -use bevy_ecs::{prelude::*, query::QueryItem}; +use bevy_ecs::prelude::*; use bevy_render::{ camera::ExtractedCamera, diagnostic::RecordDiagnostics, - render_graph::{NodeRunError, RenderGraphContext, ViewNode}, render_resource::{BindGroup, PipelineCache, RenderPassDescriptor, TextureViewId}, - renderer::RenderContext, + renderer::{RenderContext, ViewQuery}, view::ViewTarget, }; -use std::sync::Mutex; #[derive(Default)] -pub struct UpscalingNode { - cached_texture_bind_group: Mutex>, +pub struct UpscalingBindGroupCache { + cached: Option<(TextureViewId, BindGroup)>, } -impl ViewNode for UpscalingNode { - type ViewQuery = ( - &'static ViewTarget, - &'static ViewUpscalingPipeline, - Option<&'static ExtractedCamera>, - ); +pub fn upscaling( + view: ViewQuery<( + &ViewTarget, + &ViewUpscalingPipeline, + Option<&ExtractedCamera>, + )>, + pipeline_cache: Res, + blit_pipeline: Res, + clear_color_global: Res, + mut cache: Local, + mut ctx: RenderContext, +) { + let (target, upscaling_target, camera) = view.into_inner(); - fn run( - &self, - _graph: &mut RenderGraphContext, - render_context: &mut RenderContext, - (target, upscaling_target, camera): QueryItem, - world: &World, - ) -> Result<(), NodeRunError> { - let pipeline_cache = world.resource::(); - let blit_pipeline = world.resource::(); - let clear_color_global = world.resource::(); + let clear_color = if let Some(camera) = camera { + match camera.output_mode { + CameraOutputMode::Write { clear_color, .. } => clear_color, + CameraOutputMode::Skip => return, + } + } else { + ClearColorConfig::Default + }; + let clear_color = match clear_color { + ClearColorConfig::Default => Some(clear_color_global.0), + ClearColorConfig::Custom(color) => Some(color), + ClearColorConfig::None => None, + }; + let converted_clear_color = clear_color.map(Into::into); - let diagnostics = render_context.diagnostic_recorder(); + // texture to be upscaled to the output texture + let main_texture_view = target.main_texture_view(); - let clear_color = if let Some(camera) = camera { - match camera.output_mode { - CameraOutputMode::Write { clear_color, .. } => clear_color, - CameraOutputMode::Skip => return Ok(()), - } - } else { - ClearColorConfig::Default - }; - let clear_color = match clear_color { - ClearColorConfig::Default => Some(clear_color_global.0), - ClearColorConfig::Custom(color) => Some(color), - ClearColorConfig::None => None, - }; - let converted_clear_color = clear_color.map(Into::into); - // texture to be upscaled to the output texture - let main_texture_view = target.main_texture_view(); + let bind_group = match &mut cache.cached { + Some((id, bind_group)) if main_texture_view.id() == *id => bind_group, + cached => { + let bind_group = blit_pipeline.create_bind_group( + ctx.render_device(), + main_texture_view, + &pipeline_cache, + ); - let mut cached_bind_group = self.cached_texture_bind_group.lock().unwrap(); - let bind_group = match &mut *cached_bind_group { - Some((id, bind_group)) if main_texture_view.id() == *id => bind_group, - cached_bind_group => { - let bind_group = blit_pipeline.create_bind_group( - render_context.render_device(), - main_texture_view, - pipeline_cache, - ); + let (_, bind_group) = cached.insert((main_texture_view.id(), bind_group)); + bind_group + } + }; - let (_, bind_group) = - cached_bind_group.insert((main_texture_view.id(), bind_group)); - bind_group - } - }; + let Some(pipeline) = pipeline_cache.get_render_pipeline(upscaling_target.0) else { + return; + }; - let Some(pipeline) = pipeline_cache.get_render_pipeline(upscaling_target.0) else { - return Ok(()); - }; + let pass_descriptor = RenderPassDescriptor { + label: Some("upscaling"), + color_attachments: &[Some(target.out_texture_color_attachment(converted_clear_color))], + depth_stencil_attachment: None, + timestamp_writes: None, + occlusion_query_set: None, + }; - let pass_descriptor = RenderPassDescriptor { - label: Some("upscaling"), - color_attachments: &[Some( - target.out_texture_color_attachment(converted_clear_color), - )], - depth_stencil_attachment: None, - timestamp_writes: None, - occlusion_query_set: None, - }; + let diagnostics = ctx.diagnostic_recorder(); + let diagnostics = diagnostics.as_deref(); + let time_span = diagnostics.time_span(ctx.command_encoder(), "upscaling"); - let mut render_pass = render_context - .command_encoder() - .begin_render_pass(&pass_descriptor); - let pass_span = diagnostics.pass_span(&mut render_pass, "upscaling"); + { + let mut render_pass = ctx.command_encoder().begin_render_pass(&pass_descriptor); if let Some(camera) = camera && let Some(viewport) = &camera.viewport @@ -99,9 +90,8 @@ impl ViewNode for UpscalingNode { render_pass.set_pipeline(pipeline); render_pass.set_bind_group(0, bind_group, &[]); render_pass.draw(0..3, 0..1); - - pass_span.end(&mut render_pass); - - Ok(()) } + + time_span.end(ctx.command_encoder()); } + diff --git a/crates/bevy_pbr/src/atmosphere/environment.rs b/crates/bevy_pbr/src/atmosphere/environment.rs index 29f9c7f30676c..3c2a5ad1315ce 100644 --- a/crates/bevy_pbr/src/atmosphere/environment.rs +++ b/crates/bevy_pbr/src/atmosphere/environment.rs @@ -9,10 +9,9 @@ use bevy_asset::{load_embedded_asset, AssetServer, Assets, Handle, RenderAssetUs use bevy_ecs::{ component::Component, entity::Entity, - query::{QueryState, With, Without}, + query::{With, Without}, resource::Resource, - system::{lifetimeless::Read, Commands, Query, Res, ResMut}, - world::{FromWorld, World}, + system::{Commands, Query, Res, ResMut}, }; use bevy_image::Image; use bevy_light::{AtmosphereEnvironmentMapLight, GeneratedEnvironmentMapLight}; @@ -20,9 +19,8 @@ use bevy_math::{Quat, UVec2}; use bevy_render::{ extract_component::{ComponentUniforms, DynamicUniformIndex, ExtractComponent}, render_asset::RenderAssets, - render_graph::{Node, NodeRunError, RenderGraphContext}, render_resource::{binding_types::*, *}, - renderer::{RenderContext, RenderDevice}, + renderer::{RenderContext, RenderDevice, ViewQuery}, texture::{CachedTexture, GpuImage}, view::{ViewUniform, ViewUniformOffset, ViewUniforms}, }; @@ -243,91 +241,56 @@ pub fn prepare_atmosphere_probe_components( }); } } - -pub(super) struct EnvironmentNode { - main_view_query: QueryState<( - Read>, - Read>, - Read, - Read, - Read, - )>, - probe_query: QueryState<( - Read, - Read, +pub fn atmosphere_environment( + view: ViewQuery<( + &DynamicUniformIndex, + &DynamicUniformIndex, + &AtmosphereTransformsOffset, + &ViewUniformOffset, + &ViewLightsUniformOffset, )>, -} - -impl FromWorld for EnvironmentNode { - fn from_world(world: &mut World) -> Self { - Self { - main_view_query: QueryState::new(world), - probe_query: QueryState::new(world), - } - } -} - -impl Node for EnvironmentNode { - fn update(&mut self, world: &mut World) { - self.main_view_query.update_archetypes(world); - self.probe_query.update_archetypes(world); - } - - fn run( - &self, - graph: &mut RenderGraphContext, - render_context: &mut RenderContext, - world: &World, - ) -> Result<(), NodeRunError> { - let pipeline_cache = world.resource::(); - let pipelines = world.resource::(); - let view_entity = graph.view_entity(); - - let Some(environment_pipeline) = pipeline_cache.get_compute_pipeline(pipelines.environment) - else { - return Ok(()); - }; - - let (Ok(( - atmosphere_uniforms_offset, - settings_uniforms_offset, - atmosphere_transforms_offset, - view_uniforms_offset, - lights_uniforms_offset, - )),) = (self.main_view_query.get_manual(world, view_entity),) - else { - return Ok(()); - }; + probe_query: Query<(&AtmosphereProbeBindGroups, &AtmosphereEnvironmentMap)>, + pipeline_cache: Res, + pipelines: Res, + mut ctx: RenderContext, +) { + let Some(environment_pipeline) = pipeline_cache.get_compute_pipeline(pipelines.environment) + else { + return; + }; - for (bind_groups, env_map_light) in self.probe_query.iter_manual(world) { - let mut pass = - render_context - .command_encoder() - .begin_compute_pass(&ComputePassDescriptor { - label: Some("environment_pass"), - timestamp_writes: None, - }); + let ( + atmosphere_uniforms_offset, + settings_uniforms_offset, + atmosphere_transforms_offset, + view_uniforms_offset, + lights_uniforms_offset, + ) = view.into_inner(); - pass.set_pipeline(environment_pipeline); - pass.set_bind_group( - 0, - &bind_groups.environment, - &[ - atmosphere_uniforms_offset.index(), - settings_uniforms_offset.index(), - atmosphere_transforms_offset.index(), - view_uniforms_offset.offset, - lights_uniforms_offset.offset, - ], - ); + for (bind_groups, env_map_light) in probe_query.iter() { + let command_encoder = ctx.command_encoder(); + let mut pass = command_encoder.begin_compute_pass(&ComputePassDescriptor { + label: Some("environment_pass"), + timestamp_writes: None, + }); - pass.dispatch_workgroups( - env_map_light.size.x / 8, - env_map_light.size.y / 8, - 6, // 6 cubemap faces - ); - } + pass.set_pipeline(environment_pipeline); + pass.set_bind_group( + 0, + &bind_groups.environment, + &[ + atmosphere_uniforms_offset.index(), + settings_uniforms_offset.index(), + atmosphere_transforms_offset.index(), + view_uniforms_offset.offset, + lights_uniforms_offset.offset, + ], + ); - Ok(()) + pass.dispatch_workgroups( + env_map_light.size.x / 8, + env_map_light.size.y / 8, + 6, // 6 cubemap faces + ); } } diff --git a/crates/bevy_pbr/src/atmosphere/mod.rs b/crates/bevy_pbr/src/atmosphere/mod.rs index 908f471f39d1d..e8fa9d793e55b 100644 --- a/crates/bevy_pbr/src/atmosphere/mod.rs +++ b/crates/bevy_pbr/src/atmosphere/mod.rs @@ -40,7 +40,10 @@ pub mod resources; use bevy_app::{App, Plugin, Update}; use bevy_asset::{embedded_asset, AssetId, Assets, Handle}; use bevy_camera::Camera3d; -use bevy_core_pipeline::core_3d::graph::Node3d; +use bevy_core_pipeline::{ + core_3d::{main_opaque_pass_3d, main_transparent_pass_3d}, + schedule::{Core3d, Core3dSystems}, +}; use bevy_ecs::{ component::Component, query::{Changed, QueryItem, With}, @@ -58,19 +61,18 @@ use bevy_render::{ }; use bevy_render::{ extract_component::{ExtractComponent, ExtractComponentPlugin}, - render_graph::{RenderGraphExt, ViewNodeRunner}, render_resource::{TextureFormat, TextureUsages}, renderer::RenderAdapter, Render, RenderApp, RenderSystems, }; -use bevy_core_pipeline::core_3d::graph::Core3d; use bevy_shader::load_shader_library; use environment::{ - init_atmosphere_probe_layout, init_atmosphere_probe_pipeline, + atmosphere_environment, init_atmosphere_probe_layout, init_atmosphere_probe_pipeline, prepare_atmosphere_probe_bind_groups, prepare_atmosphere_probe_components, - prepare_probe_textures, AtmosphereEnvironmentMap, EnvironmentNode, + prepare_probe_textures, AtmosphereEnvironmentMap, }; +use node::{atmosphere_luts, render_sky}; use resources::{ prepare_atmosphere_transforms, prepare_atmosphere_uniforms, queue_render_sky_pipelines, AtmosphereTransforms, GpuAtmosphere, RenderSkyBindGroupLayouts, @@ -82,12 +84,9 @@ use crate::{ resources::{init_atmosphere_buffer, write_atmosphere_buffer}, }; -use self::{ - node::{AtmosphereLutsNode, AtmosphereNode, RenderSkyNode}, - resources::{ - prepare_atmosphere_bind_groups, prepare_atmosphere_textures, AtmosphereBindGroupLayouts, - AtmosphereLutPipelines, AtmosphereSampler, - }, +use self::resources::{ + prepare_atmosphere_bind_groups, prepare_atmosphere_textures, AtmosphereBindGroupLayouts, + AtmosphereLutPipelines, AtmosphereSampler, }; #[doc(hidden)] @@ -182,30 +181,18 @@ impl Plugin for AtmospherePlugin { write_atmosphere_buffer.in_set(RenderSystems::PrepareResources), ), ) - .add_render_graph_node::>( - Core3d, - AtmosphereNode::RenderLuts, - ) - .add_render_graph_edges( - Core3d, - ( - // END_PRE_PASSES -> RENDER_LUTS -> MAIN_PASS - Node3d::EndPrepasses, - AtmosphereNode::RenderLuts, - Node3d::StartMainPass, - ), - ) - .add_render_graph_node::>( - Core3d, - AtmosphereNode::RenderSky, - ) - .add_render_graph_node::(Core3d, AtmosphereNode::Environment) - .add_render_graph_edges( + .add_systems( Core3d, ( - Node3d::MainOpaquePass, - AtmosphereNode::RenderSky, - Node3d::MainTransparentPass, + atmosphere_luts + .after(Core3dSystems::EndPrepasses) + .before(Core3dSystems::StartMainPass), + render_sky + .after(main_opaque_pass_3d) + .before(main_transparent_pass_3d), + atmosphere_environment + .after(atmosphere_luts) + .before(Core3dSystems::StartMainPass), ), ); } diff --git a/crates/bevy_pbr/src/atmosphere/node.rs b/crates/bevy_pbr/src/atmosphere/node.rs index 9f640b524b886..1dfa344dc37a7 100644 --- a/crates/bevy_pbr/src/atmosphere/node.rs +++ b/crates/bevy_pbr/src/atmosphere/node.rs @@ -1,13 +1,11 @@ use bevy_camera::{MainPassResolutionOverride, Viewport}; -use bevy_ecs::{query::QueryItem, system::lifetimeless::Read, world::World}; +use bevy_ecs::system::Res; use bevy_math::{UVec2, Vec3Swizzles}; use bevy_render::{ camera::ExtractedCamera, - diagnostic::RecordDiagnostics, extract_component::DynamicUniformIndex, - render_graph::{NodeRunError, RenderGraphContext, RenderLabel, ViewNode}, render_resource::{ComputePass, ComputePassDescriptor, PipelineCache, RenderPassDescriptor}, - renderer::RenderContext, + renderer::{RenderContext, ViewQuery}, view::{ViewTarget, ViewUniformOffset}, }; @@ -21,222 +19,193 @@ use super::{ GpuAtmosphereSettings, }; -#[derive(PartialEq, Eq, Debug, Copy, Clone, Hash, RenderLabel)] -pub enum AtmosphereNode { - RenderLuts, - RenderSky, - Environment, -} - -#[derive(Default)] -pub(super) struct AtmosphereLutsNode {} - -impl ViewNode for AtmosphereLutsNode { - type ViewQuery = ( - Read, - Read, - Read>, - Read>, - Read, - Read, - Read, - ); - - fn run( - &self, - _graph: &mut RenderGraphContext, - render_context: &mut RenderContext, - ( - settings, - bind_groups, - atmosphere_uniforms_offset, - settings_uniforms_offset, - atmosphere_transforms_offset, - view_uniforms_offset, - lights_uniforms_offset, - ): QueryItem, - world: &World, - ) -> Result<(), NodeRunError> { - let pipelines = world.resource::(); - let pipeline_cache = world.resource::(); - let ( - Some(transmittance_lut_pipeline), - Some(multiscattering_lut_pipeline), - Some(sky_view_lut_pipeline), - Some(aerial_view_lut_pipeline), - ) = ( - pipeline_cache.get_compute_pipeline(pipelines.transmittance_lut), - pipeline_cache.get_compute_pipeline(pipelines.multiscattering_lut), - pipeline_cache.get_compute_pipeline(pipelines.sky_view_lut), - pipeline_cache.get_compute_pipeline(pipelines.aerial_view_lut), - ) - else { - return Ok(()); - }; - - let diagnostics = render_context.diagnostic_recorder(); - - let command_encoder = render_context.command_encoder(); - - let mut luts_pass = command_encoder.begin_compute_pass(&ComputePassDescriptor { - label: Some("atmosphere_luts"), - timestamp_writes: None, - }); - let pass_span = diagnostics.pass_span(&mut luts_pass, "atmosphere_luts"); - - fn dispatch_2d(compute_pass: &mut ComputePass, size: UVec2) { - const WORKGROUP_SIZE: u32 = 16; - let workgroups_x = size.x.div_ceil(WORKGROUP_SIZE); - let workgroups_y = size.y.div_ceil(WORKGROUP_SIZE); - compute_pass.dispatch_workgroups(workgroups_x, workgroups_y, 1); - } - - // Transmittance LUT - - luts_pass.set_pipeline(transmittance_lut_pipeline); - luts_pass.set_bind_group( - 0, - &bind_groups.transmittance_lut, - &[ - atmosphere_uniforms_offset.index(), - settings_uniforms_offset.index(), - ], - ); +pub fn atmosphere_luts( + view: ViewQuery<( + &GpuAtmosphereSettings, + &AtmosphereBindGroups, + &DynamicUniformIndex, + &DynamicUniformIndex, + &AtmosphereTransformsOffset, + &ViewUniformOffset, + &ViewLightsUniformOffset, + )>, + pipelines: Res, + pipeline_cache: Res, + mut ctx: RenderContext, +) { + let ( + settings, + bind_groups, + atmosphere_uniforms_offset, + settings_uniforms_offset, + atmosphere_transforms_offset, + view_uniforms_offset, + lights_uniforms_offset, + ) = view.into_inner(); + + let ( + Some(transmittance_lut_pipeline), + Some(multiscattering_lut_pipeline), + Some(sky_view_lut_pipeline), + Some(aerial_view_lut_pipeline), + ) = ( + pipeline_cache.get_compute_pipeline(pipelines.transmittance_lut), + pipeline_cache.get_compute_pipeline(pipelines.multiscattering_lut), + pipeline_cache.get_compute_pipeline(pipelines.sky_view_lut), + pipeline_cache.get_compute_pipeline(pipelines.aerial_view_lut), + ) + else { + return; + }; + + let command_encoder = ctx.command_encoder(); + + let mut luts_pass = command_encoder.begin_compute_pass(&ComputePassDescriptor { + label: Some("atmosphere_luts"), + timestamp_writes: None, + }); + + fn dispatch_2d(compute_pass: &mut ComputePass, size: UVec2) { + const WORKGROUP_SIZE: u32 = 16; + let workgroups_x = size.x.div_ceil(WORKGROUP_SIZE); + let workgroups_y = size.y.div_ceil(WORKGROUP_SIZE); + compute_pass.dispatch_workgroups(workgroups_x, workgroups_y, 1); + } - dispatch_2d(&mut luts_pass, settings.transmittance_lut_size); + // Transmittance LUT - // Multiscattering LUT + luts_pass.set_pipeline(transmittance_lut_pipeline); + luts_pass.set_bind_group( + 0, + &bind_groups.transmittance_lut, + &[ + atmosphere_uniforms_offset.index(), + settings_uniforms_offset.index(), + ], + ); - luts_pass.set_pipeline(multiscattering_lut_pipeline); - luts_pass.set_bind_group( - 0, - &bind_groups.multiscattering_lut, - &[ - atmosphere_uniforms_offset.index(), - settings_uniforms_offset.index(), - ], - ); + dispatch_2d(&mut luts_pass, settings.transmittance_lut_size); - luts_pass.dispatch_workgroups( - settings.multiscattering_lut_size.x, - settings.multiscattering_lut_size.y, - 1, - ); + // Multiscattering LUT - // Sky View LUT - - luts_pass.set_pipeline(sky_view_lut_pipeline); - luts_pass.set_bind_group( - 0, - &bind_groups.sky_view_lut, - &[ - atmosphere_uniforms_offset.index(), - settings_uniforms_offset.index(), - atmosphere_transforms_offset.index(), - view_uniforms_offset.offset, - lights_uniforms_offset.offset, - ], - ); + luts_pass.set_pipeline(multiscattering_lut_pipeline); + luts_pass.set_bind_group( + 0, + &bind_groups.multiscattering_lut, + &[ + atmosphere_uniforms_offset.index(), + settings_uniforms_offset.index(), + ], + ); - dispatch_2d(&mut luts_pass, settings.sky_view_lut_size); + luts_pass.dispatch_workgroups( + settings.multiscattering_lut_size.x, + settings.multiscattering_lut_size.y, + 1, + ); - // Aerial View LUT + // Sky View LUT + + luts_pass.set_pipeline(sky_view_lut_pipeline); + luts_pass.set_bind_group( + 0, + &bind_groups.sky_view_lut, + &[ + atmosphere_uniforms_offset.index(), + settings_uniforms_offset.index(), + atmosphere_transforms_offset.index(), + view_uniforms_offset.offset, + lights_uniforms_offset.offset, + ], + ); - luts_pass.set_pipeline(aerial_view_lut_pipeline); - luts_pass.set_bind_group( - 0, - &bind_groups.aerial_view_lut, - &[ - atmosphere_uniforms_offset.index(), - settings_uniforms_offset.index(), - view_uniforms_offset.offset, - lights_uniforms_offset.offset, - ], - ); + dispatch_2d(&mut luts_pass, settings.sky_view_lut_size); - dispatch_2d(&mut luts_pass, settings.aerial_view_lut_size.xy()); + // Aerial View LUT - pass_span.end(&mut luts_pass); + luts_pass.set_pipeline(aerial_view_lut_pipeline); + luts_pass.set_bind_group( + 0, + &bind_groups.aerial_view_lut, + &[ + atmosphere_uniforms_offset.index(), + settings_uniforms_offset.index(), + view_uniforms_offset.offset, + lights_uniforms_offset.offset, + ], + ); - Ok(()) - } + dispatch_2d(&mut luts_pass, settings.aerial_view_lut_size.xy()); } -#[derive(Default)] -pub(super) struct RenderSkyNode; - -impl ViewNode for RenderSkyNode { - type ViewQuery = ( - Read, - Read, - Read, - Read>, - Read>, - Read, - Read, - Read, - Read, - Option>, - ); - - fn run<'w>( - &self, - _graph: &mut RenderGraphContext, - render_context: &mut RenderContext<'w>, - ( - camera, - atmosphere_bind_groups, - view_target, - atmosphere_uniforms_offset, - settings_uniforms_offset, - atmosphere_transforms_offset, - view_uniforms_offset, - lights_uniforms_offset, - render_sky_pipeline_id, - resolution_override, - ): QueryItem<'w, '_, Self::ViewQuery>, - world: &'w World, - ) -> Result<(), NodeRunError> { - let pipeline_cache = world.resource::(); - let Some(render_sky_pipeline) = - pipeline_cache.get_render_pipeline(render_sky_pipeline_id.0) - else { - return Ok(()); - }; //TODO: warning - - let diagnostics = render_context.diagnostic_recorder(); - - let mut render_sky_pass = render_context.begin_tracked_render_pass(RenderPassDescriptor { - label: Some("render_sky"), - color_attachments: &[Some(view_target.get_color_attachment())], - depth_stencil_attachment: None, - timestamp_writes: None, - occlusion_query_set: None, - }); - let pass_span = diagnostics.pass_span(&mut render_sky_pass, "render_sky"); - - if let Some(viewport) = - Viewport::from_viewport_and_override(camera.viewport.as_ref(), resolution_override) - { - render_sky_pass.set_camera_viewport(&viewport); - } - - render_sky_pass.set_render_pipeline(render_sky_pipeline); - render_sky_pass.set_bind_group( - 0, - &atmosphere_bind_groups.render_sky, - &[ - atmosphere_uniforms_offset.index(), - settings_uniforms_offset.index(), - atmosphere_transforms_offset.index(), - view_uniforms_offset.offset, - lights_uniforms_offset.offset, - ], +pub fn render_sky( + view: ViewQuery<( + &ExtractedCamera, + &AtmosphereBindGroups, + &ViewTarget, + &DynamicUniformIndex, + &DynamicUniformIndex, + &AtmosphereTransformsOffset, + &ViewUniformOffset, + &ViewLightsUniformOffset, + &RenderSkyPipelineId, + Option<&MainPassResolutionOverride>, + )>, + pipeline_cache: Res, + mut ctx: RenderContext, +) { + let ( + camera, + atmosphere_bind_groups, + view_target, + atmosphere_uniforms_offset, + settings_uniforms_offset, + atmosphere_transforms_offset, + view_uniforms_offset, + lights_uniforms_offset, + render_sky_pipeline_id, + resolution_override, + ) = view.into_inner(); + + let Some(render_sky_pipeline) = pipeline_cache.get_render_pipeline(render_sky_pipeline_id.0) + else { + return; + }; //TODO: warning + + let command_encoder = ctx.command_encoder(); + + let mut render_sky_pass = command_encoder.begin_render_pass(&RenderPassDescriptor { + label: Some("render_sky"), + color_attachments: &[Some(view_target.get_color_attachment())], + depth_stencil_attachment: None, + timestamp_writes: None, + occlusion_query_set: None, + }); + + if let Some(viewport) = + Viewport::from_viewport_and_override(camera.viewport.as_ref(), resolution_override) + { + render_sky_pass.set_viewport( + viewport.physical_position.x as f32, + viewport.physical_position.y as f32, + viewport.physical_size.x as f32, + viewport.physical_size.y as f32, + viewport.depth.start, + viewport.depth.end, ); - render_sky_pass.draw(0..3, 0..1); - - pass_span.end(&mut render_sky_pass); - - Ok(()) } + + render_sky_pass.set_pipeline(render_sky_pipeline); + render_sky_pass.set_bind_group( + 0, + &atmosphere_bind_groups.render_sky, + &[ + atmosphere_uniforms_offset.index(), + settings_uniforms_offset.index(), + atmosphere_transforms_offset.index(), + view_uniforms_offset.offset, + lights_uniforms_offset.offset, + ], + ); + render_sky_pass.draw(0..3, 0..1); } diff --git a/crates/bevy_pbr/src/deferred/mod.rs b/crates/bevy_pbr/src/deferred/mod.rs index 41766372d9f36..df9ccb5a54e2c 100644 --- a/crates/bevy_pbr/src/deferred/mod.rs +++ b/crates/bevy_pbr/src/deferred/mod.rs @@ -1,8 +1,8 @@ use crate::{ - graph::NodePbr, MeshPipeline, MeshViewBindGroup, RenderViewLightProbes, - ScreenSpaceAmbientOcclusion, ScreenSpaceReflectionsUniform, ViewEnvironmentMapUniformOffset, - ViewLightProbesUniformOffset, ViewScreenSpaceReflectionsUniformOffset, - TONEMAPPING_LUT_SAMPLER_BINDING_INDEX, TONEMAPPING_LUT_TEXTURE_BINDING_INDEX, + MeshPipeline, MeshViewBindGroup, RenderViewLightProbes, ScreenSpaceAmbientOcclusion, + ScreenSpaceReflectionsUniform, ViewEnvironmentMapUniformOffset, ViewLightProbesUniformOffset, + ViewScreenSpaceReflectionsUniformOffset, TONEMAPPING_LUT_SAMPLER_BINDING_INDEX, + TONEMAPPING_LUT_TEXTURE_BINDING_INDEX, }; use crate::{ DistanceFog, ExtractedAtmosphere, MeshPipelineKey, ViewFogUniformOffset, @@ -11,25 +11,24 @@ use crate::{ use bevy_app::prelude::*; use bevy_asset::{embedded_asset, load_embedded_asset, AssetServer, Handle}; use bevy_core_pipeline::{ - core_3d::graph::{Core3d, Node3d}, + core_3d::main_opaque_pass_3d, deferred::{ copy_lighting_id::DeferredLightingIdDepthTexture, DEFERRED_LIGHTING_PASS_ID_DEPTH_FORMAT, }, prepass::{DeferredPrepass, DepthPrepass, MotionVectorPrepass, NormalPrepass}, + schedule::{Core3d, Core3dSystems}, tonemapping::{DebandDither, Tonemapping}, }; -use bevy_ecs::{prelude::*, query::QueryItem}; +use bevy_ecs::prelude::*; use bevy_image::BevyDefault as _; use bevy_light::{EnvironmentMapLight, IrradianceVolume, ShadowFilteringMethod}; use bevy_render::RenderStartup; use bevy_render::{ - diagnostic::RecordDiagnostics, extract_component::{ ComponentUniforms, ExtractComponent, ExtractComponentPlugin, UniformComponentPlugin, }, - render_graph::{NodeRunError, RenderGraphContext, RenderGraphExt, ViewNode, ViewNodeRunner}, render_resource::{binding_types::uniform_buffer, *}, - renderer::RenderContext, + renderer::{RenderContext, ViewQuery}, view::{ExtractedView, ViewTarget, ViewUniformOffset}, Render, RenderApp, RenderSystems, }; @@ -114,118 +113,93 @@ impl Plugin for DeferredPbrLightingPlugin { Render, (prepare_deferred_lighting_pipelines.in_set(RenderSystems::Prepare),), ) - .add_render_graph_node::>( - Core3d, - NodePbr::DeferredLightingPass, - ) - .add_render_graph_edges( + .add_systems( Core3d, - ( - Node3d::StartMainPass, - NodePbr::DeferredLightingPass, - Node3d::MainOpaquePass, - ), + deferred_lighting + .after(Core3dSystems::StartMainPass) + .before(main_opaque_pass_3d), ); } } -#[derive(Default)] -pub struct DeferredOpaquePass3dPbrLightingNode; - -impl ViewNode for DeferredOpaquePass3dPbrLightingNode { - type ViewQuery = ( - &'static ViewUniformOffset, - &'static ViewLightsUniformOffset, - &'static ViewFogUniformOffset, - &'static ViewLightProbesUniformOffset, - &'static ViewScreenSpaceReflectionsUniformOffset, - &'static ViewEnvironmentMapUniformOffset, - &'static MeshViewBindGroup, - &'static ViewTarget, - &'static DeferredLightingIdDepthTexture, - &'static DeferredLightingPipeline, +pub fn deferred_lighting( + view: ViewQuery<( + &ViewUniformOffset, + &ViewLightsUniformOffset, + &ViewFogUniformOffset, + &ViewLightProbesUniformOffset, + &ViewScreenSpaceReflectionsUniformOffset, + &ViewEnvironmentMapUniformOffset, + &MeshViewBindGroup, + &ViewTarget, + &DeferredLightingIdDepthTexture, + &DeferredLightingPipeline, + )>, + pipeline_cache: Res, + deferred_lighting_layout: Res, + deferred_lighting_pass_id: Res>, + mut ctx: RenderContext, +) { + let ( + view_uniform_offset, + view_lights_offset, + view_fog_offset, + view_light_probes_offset, + view_ssr_offset, + view_environment_map_offset, + mesh_view_bind_group, + target, + deferred_lighting_id_depth_texture, + deferred_lighting_pipeline, + ) = view.into_inner(); + + let Some(pipeline) = pipeline_cache.get_render_pipeline(deferred_lighting_pipeline.pipeline_id) + else { + return; + }; + + let Some(deferred_lighting_pass_id_binding) = deferred_lighting_pass_id.uniforms().binding() + else { + return; + }; + + let bind_group_2 = ctx.render_device().create_bind_group( + "deferred_lighting_layout_group_2", + &pipeline_cache.get_bind_group_layout(&deferred_lighting_layout.bind_group_layout_2), + &BindGroupEntries::single(deferred_lighting_pass_id_binding), ); - fn run( - &self, - _graph_context: &mut RenderGraphContext, - render_context: &mut RenderContext, - ( - view_uniform_offset, - view_lights_offset, - view_fog_offset, - view_light_probes_offset, - view_ssr_offset, - view_environment_map_offset, - mesh_view_bind_group, - target, - deferred_lighting_id_depth_texture, - deferred_lighting_pipeline, - ): QueryItem, - world: &World, - ) -> Result<(), NodeRunError> { - let pipeline_cache = world.resource::(); - let deferred_lighting_layout = world.resource::(); - - let Some(pipeline) = - pipeline_cache.get_render_pipeline(deferred_lighting_pipeline.pipeline_id) - else { - return Ok(()); - }; - - let deferred_lighting_pass_id = - world.resource::>(); - let Some(deferred_lighting_pass_id_binding) = - deferred_lighting_pass_id.uniforms().binding() - else { - return Ok(()); - }; - - let diagnostics = render_context.diagnostic_recorder(); - - let bind_group_2 = render_context.render_device().create_bind_group( - "deferred_lighting_layout_group_2", - &pipeline_cache.get_bind_group_layout(&deferred_lighting_layout.bind_group_layout_2), - &BindGroupEntries::single(deferred_lighting_pass_id_binding), - ); - - let mut render_pass = render_context.begin_tracked_render_pass(RenderPassDescriptor { - label: Some("deferred_lighting"), - color_attachments: &[Some(target.get_color_attachment())], - depth_stencil_attachment: Some(RenderPassDepthStencilAttachment { - view: &deferred_lighting_id_depth_texture.texture.default_view, - depth_ops: Some(Operations { - load: LoadOp::Load, - store: StoreOp::Discard, - }), - stencil_ops: None, + let mut render_pass = ctx.begin_tracked_render_pass(RenderPassDescriptor { + label: Some("deferred_lighting"), + color_attachments: &[Some(target.get_color_attachment())], + depth_stencil_attachment: Some(RenderPassDepthStencilAttachment { + view: &deferred_lighting_id_depth_texture.texture.default_view, + depth_ops: Some(Operations { + load: LoadOp::Load, + store: StoreOp::Discard, }), - timestamp_writes: None, - occlusion_query_set: None, - }); - let pass_span = diagnostics.pass_span(&mut render_pass, "deferred_lighting"); - - render_pass.set_render_pipeline(pipeline); - render_pass.set_bind_group( - 0, - &mesh_view_bind_group.main, - &[ - view_uniform_offset.offset, - view_lights_offset.offset, - view_fog_offset.offset, - **view_light_probes_offset, - **view_ssr_offset, - **view_environment_map_offset, - ], - ); - render_pass.set_bind_group(1, &mesh_view_bind_group.binding_array, &[]); - render_pass.set_bind_group(2, &bind_group_2, &[]); - render_pass.draw(0..3, 0..1); - - pass_span.end(&mut render_pass); + stencil_ops: None, + }), + timestamp_writes: None, + occlusion_query_set: None, + }); - Ok(()) - } + render_pass.set_render_pipeline(pipeline); + render_pass.set_bind_group( + 0, + &mesh_view_bind_group.main, + &[ + view_uniform_offset.offset, + view_lights_offset.offset, + view_fog_offset.offset, + **view_light_probes_offset, + **view_ssr_offset, + **view_environment_map_offset, + ], + ); + render_pass.set_bind_group(1, &mesh_view_bind_group.binding_array, &[]); + render_pass.set_bind_group(2, &bind_group_2, &[]); + render_pass.draw(0..3, 0..1); } #[derive(Resource)] diff --git a/crates/bevy_pbr/src/lib.rs b/crates/bevy_pbr/src/lib.rs index 79163cb648134..e7fc420ca1366 100644 --- a/crates/bevy_pbr/src/lib.rs +++ b/crates/bevy_pbr/src/lib.rs @@ -88,48 +88,10 @@ pub mod prelude { }; } -pub mod graph { - use bevy_render::render_graph::RenderLabel; - - /// Render graph nodes specific to 3D PBR rendering. - #[derive(Debug, Hash, PartialEq, Eq, Clone, RenderLabel)] - pub enum NodePbr { - /// Label for the shadow pass node that draws meshes that were visible - /// from the light last frame. - EarlyShadowPass, - /// Label for the shadow pass node that draws meshes that became visible - /// from the light this frame. - LateShadowPass, - /// Label for the screen space ambient occlusion render node. - ScreenSpaceAmbientOcclusion, - DeferredLightingPass, - /// Label for the volumetric lighting pass. - VolumetricFog, - /// Label for the shader that transforms and culls meshes that were - /// visible last frame. - EarlyGpuPreprocess, - /// Label for the shader that transforms and culls meshes that became - /// visible this frame. - LateGpuPreprocess, - /// Label for the screen space reflections pass. - ScreenSpaceReflections, - /// Label for the node that builds indirect draw parameters for meshes - /// that were visible last frame. - EarlyPrepassBuildIndirectParameters, - /// Label for the node that builds indirect draw parameters for meshes - /// that became visible this frame. - LatePrepassBuildIndirectParameters, - /// Label for the node that builds indirect draw parameters for the main - /// rendering pass, containing all meshes that are visible this frame. - MainBuildIndirectParameters, - ClearIndirectParametersMetadata, - } -} - -use crate::{deferred::DeferredPbrLightingPlugin, graph::NodePbr}; +use crate::deferred::DeferredPbrLightingPlugin; use bevy_app::prelude::*; use bevy_asset::{AssetApp, AssetPath, Assets, Handle, RenderAssetUsages}; -use bevy_core_pipeline::core_3d::graph::{Core3d, Node3d}; +use bevy_core_pipeline::schedule::{Core3d, Core3dSystems}; use bevy_ecs::prelude::*; #[cfg(feature = "bluenoise_texture")] use bevy_image::{CompressedImageFormats, ImageType}; @@ -138,7 +100,6 @@ use bevy_render::{ alpha::AlphaMode, camera::sort_cameras, extract_resource::ExtractResourcePlugin, - render_graph::RenderGraph, render_resource::{ Extent3d, TextureDataOrder, TextureDescriptor, TextureDimension, TextureFormat, TextureUsages, @@ -348,17 +309,13 @@ impl Plugin for PbrPlugin { .add_observer(remove_light_view_entities); render_app.world_mut().add_observer(extracted_light_removed); - let early_shadow_pass_node = EarlyShadowPassNode::from_world(render_app.world_mut()); - let late_shadow_pass_node = LateShadowPassNode::from_world(render_app.world_mut()); - let mut graph = render_app.world_mut().resource_mut::(); - let draw_3d_graph = graph.get_sub_graph_mut(Core3d).unwrap(); - draw_3d_graph.add_node(NodePbr::EarlyShadowPass, early_shadow_pass_node); - draw_3d_graph.add_node(NodePbr::LateShadowPass, late_shadow_pass_node); - draw_3d_graph.add_node_edges(( - NodePbr::EarlyShadowPass, - NodePbr::LateShadowPass, - Node3d::StartMainPass, - )); + render_app.add_systems( + Core3d, + ( + early_shadow_pass.before(late_shadow_pass), + late_shadow_pass.before(Core3dSystems::StartMainPass), + ), + ); } fn finish(&self, app: &mut App) { diff --git a/crates/bevy_pbr/src/light_probe/generate.rs b/crates/bevy_pbr/src/light_probe/generate.rs index a944513bd3128..e0b41901624e7 100644 --- a/crates/bevy_pbr/src/light_probe/generate.rs +++ b/crates/bevy_pbr/src/light_probe/generate.rs @@ -13,22 +13,19 @@ //! These components are intended to be added to a camera. use bevy_app::{App, Plugin, Update}; use bevy_asset::{embedded_asset, load_embedded_asset, AssetServer, Assets, RenderAssetUsages}; -use bevy_core_pipeline::core_3d::graph::{Core3d, Node3d}; use bevy_ecs::{ component::Component, entity::Entity, - query::{QueryState, With, Without}, + query::{With, Without}, resource::Resource, schedule::IntoScheduleConfigs, - system::{lifetimeless::Read, Commands, Query, Res, ResMut}, - world::{FromWorld, World}, + system::{Commands, Query, Res, ResMut}, }; use bevy_image::Image; use bevy_math::{Quat, UVec2, Vec2}; use bevy_render::{ diagnostic::RecordDiagnostics, render_asset::RenderAssets, - render_graph::{Node, NodeRunError, RenderGraphContext, RenderGraphExt, RenderLabel}, render_resource::{ binding_types::*, AddressMode, BindGroup, BindGroupEntries, BindGroupLayoutDescriptor, BindGroupLayoutEntries, CachedComputePipelineId, ComputePassDescriptor, @@ -67,13 +64,6 @@ use tracing::info; use crate::Bluenoise; -/// Labels for the environment map generation nodes -#[derive(PartialEq, Eq, Debug, Copy, Clone, Hash, RenderLabel)] -pub enum GeneratorNode { - Downsampling, - Filtering, -} - /// Stores the bind group layouts for the environment map generation pipelines #[derive(Resource)] pub struct GeneratorBindGroupLayouts { @@ -146,30 +136,22 @@ impl Plugin for EnvironmentMapGenerationPlugin { }; render_app - .add_render_graph_node::(Core3d, GeneratorNode::Downsampling) - .add_render_graph_node::(Core3d, GeneratorNode::Filtering) - .add_render_graph_edges( - Core3d, - ( - Node3d::EndPrepasses, - GeneratorNode::Downsampling, - GeneratorNode::Filtering, - Node3d::StartMainPass, - ), - ) .add_systems( ExtractSchedule, extract_generated_environment_map_entities.after(generate_environment_map_light), ) .add_systems( Render, - prepare_generated_environment_map_bind_groups - .in_set(RenderSystems::PrepareBindGroups), - ) - .add_systems( - Render, - prepare_generated_environment_map_intermediate_textures - .in_set(RenderSystems::PrepareResources), + ( + prepare_generated_environment_map_bind_groups + .in_set(RenderSystems::PrepareBindGroups), + prepare_generated_environment_map_intermediate_textures + .in_set(RenderSystems::PrepareResources), + (downsampling_system, filtering_system) + .chain() + .after(RenderSystems::PrepareBindGroups) + .before(RenderSystems::Render), + ), ) .add_systems( RenderStartup, @@ -871,178 +853,132 @@ fn create_placeholder_storage_view(render_device: &RenderDevice) -> TextureView tex.create_view(&TextureViewDescriptor::default()) } -/// Downsampling node implementation that handles all parts of the mip chain -pub struct DownsamplingNode { - query: QueryState<( - Entity, - Read, - Read, - )>, -} - -impl FromWorld for DownsamplingNode { - fn from_world(world: &mut World) -> Self { - Self { - query: QueryState::new(world), - } - } -} - -impl Node for DownsamplingNode { - fn update(&mut self, world: &mut World) { - self.query.update_archetypes(world); - } - - fn run( - &self, - _graph: &mut RenderGraphContext, - render_context: &mut RenderContext, - world: &World, - ) -> Result<(), NodeRunError> { - let pipeline_cache = world.resource::(); - let pipelines = world.resource::(); - - let Some(downsample_first_pipeline) = - pipeline_cache.get_compute_pipeline(pipelines.downsample_first) - else { - return Ok(()); - }; - - let Some(downsample_second_pipeline) = - pipeline_cache.get_compute_pipeline(pipelines.downsample_second) - else { - return Ok(()); - }; - - let diagnostics = render_context.diagnostic_recorder(); +pub fn downsampling_system( + query: Query<(&GeneratorBindGroups, &RenderEnvironmentMap)>, + pipeline_cache: Res, + pipelines: Option>, + mut ctx: RenderContext, +) { + let Some(pipelines) = pipelines else { + return; + }; - for (_, bind_groups, env_map_light) in self.query.iter_manual(world) { - // Copy base mip using compute shader with pre-built bind group - let Some(copy_pipeline) = pipeline_cache.get_compute_pipeline(pipelines.copy) else { - return Ok(()); - }; + let Some(downsample_first_pipeline) = + pipeline_cache.get_compute_pipeline(pipelines.downsample_first) + else { + return; + }; - { - let mut compute_pass = - render_context - .command_encoder() - .begin_compute_pass(&ComputePassDescriptor { - label: Some("lightprobe_copy"), - timestamp_writes: None, - }); + let Some(downsample_second_pipeline) = + pipeline_cache.get_compute_pipeline(pipelines.downsample_second) + else { + return; + }; - let pass_span = diagnostics.pass_span(&mut compute_pass, "lightprobe_copy"); + let Some(copy_pipeline) = pipeline_cache.get_compute_pipeline(pipelines.copy) else { + return; + }; - compute_pass.set_pipeline(copy_pipeline); - compute_pass.set_bind_group(0, &bind_groups.copy, &[]); + let diagnostics = ctx.diagnostic_recorder(); + let diagnostics = diagnostics.as_deref(); - let tex_size = env_map_light.environment_map.size; - let wg_x = tex_size.width.div_ceil(8); - let wg_y = tex_size.height.div_ceil(8); - compute_pass.dispatch_workgroups(wg_x, wg_y, 6); + for (bind_groups, env_map_light) in &query { + // Copy base mip using compute shader with pre-built bind group + { + let mut compute_pass = + ctx.command_encoder() + .begin_compute_pass(&ComputePassDescriptor { + label: Some("lightprobe_copy"), + timestamp_writes: None, + }); - pass_span.end(&mut compute_pass); - } + let pass_span = diagnostics.pass_span(&mut compute_pass, "lightprobe_copy"); - // First pass - process mips 0-5 - { - let mut compute_pass = - render_context - .command_encoder() - .begin_compute_pass(&ComputePassDescriptor { - label: Some("lightprobe_downsampling_first_pass"), - timestamp_writes: None, - }); + compute_pass.set_pipeline(copy_pipeline); + compute_pass.set_bind_group(0, &bind_groups.copy, &[]); - let pass_span = - diagnostics.pass_span(&mut compute_pass, "lightprobe_downsampling_first_pass"); + let tex_size = env_map_light.environment_map.size; + let wg_x = tex_size.width.div_ceil(8); + let wg_y = tex_size.height.div_ceil(8); + compute_pass.dispatch_workgroups(wg_x, wg_y, 6); - compute_pass.set_pipeline(downsample_first_pipeline); - compute_pass.set_bind_group(0, &bind_groups.downsampling_first, &[]); + pass_span.end(&mut compute_pass); + } - let tex_size = env_map_light.environment_map.size; - let wg_x = tex_size.width.div_ceil(64); - let wg_y = tex_size.height.div_ceil(64); - compute_pass.dispatch_workgroups(wg_x, wg_y, 6); // 6 faces + // First pass - process mips 0-5 + { + let mut compute_pass = + ctx.command_encoder() + .begin_compute_pass(&ComputePassDescriptor { + label: Some("lightprobe_downsampling_first_pass"), + timestamp_writes: None, + }); - pass_span.end(&mut compute_pass); - } + let pass_span = + diagnostics.pass_span(&mut compute_pass, "lightprobe_downsampling_first_pass"); - // Second pass - process mips 6-12 - { - let mut compute_pass = - render_context - .command_encoder() - .begin_compute_pass(&ComputePassDescriptor { - label: Some("lightprobe_downsampling_second_pass"), - timestamp_writes: None, - }); + compute_pass.set_pipeline(downsample_first_pipeline); + compute_pass.set_bind_group(0, &bind_groups.downsampling_first, &[]); - let pass_span = - diagnostics.pass_span(&mut compute_pass, "lightprobe_downsampling_second_pass"); + let tex_size = env_map_light.environment_map.size; + let wg_x = tex_size.width.div_ceil(64); + let wg_y = tex_size.height.div_ceil(64); + compute_pass.dispatch_workgroups(wg_x, wg_y, 6); // 6 faces - compute_pass.set_pipeline(downsample_second_pipeline); - compute_pass.set_bind_group(0, &bind_groups.downsampling_second, &[]); + pass_span.end(&mut compute_pass); + } - let tex_size = env_map_light.environment_map.size; - let wg_x = tex_size.width.div_ceil(256); - let wg_y = tex_size.height.div_ceil(256); - compute_pass.dispatch_workgroups(wg_x, wg_y, 6); + // Second pass - process mips 6-12 + { + let mut compute_pass = + ctx.command_encoder() + .begin_compute_pass(&ComputePassDescriptor { + label: Some("lightprobe_downsampling_second_pass"), + timestamp_writes: None, + }); - pass_span.end(&mut compute_pass); - } - } + let pass_span = + diagnostics.pass_span(&mut compute_pass, "lightprobe_downsampling_second_pass"); - Ok(()) - } -} + compute_pass.set_pipeline(downsample_second_pipeline); + compute_pass.set_bind_group(0, &bind_groups.downsampling_second, &[]); -/// Radiance map node for generating specular environment maps -pub struct FilteringNode { - query: QueryState<( - Entity, - Read, - Read, - )>, -} + let tex_size = env_map_light.environment_map.size; + let wg_x = tex_size.width.div_ceil(256); + let wg_y = tex_size.height.div_ceil(256); + compute_pass.dispatch_workgroups(wg_x, wg_y, 6); -impl FromWorld for FilteringNode { - fn from_world(world: &mut World) -> Self { - Self { - query: QueryState::new(world), + pass_span.end(&mut compute_pass); } } } -impl Node for FilteringNode { - fn update(&mut self, world: &mut World) { - self.query.update_archetypes(world); - } +pub fn filtering_system( + query: Query<(&GeneratorBindGroups, &RenderEnvironmentMap)>, + pipeline_cache: Res, + pipelines: Option>, + mut ctx: RenderContext, +) { + let Some(pipelines) = pipelines else { + return; + }; - fn run( - &self, - _graph: &mut RenderGraphContext, - render_context: &mut RenderContext, - world: &World, - ) -> Result<(), NodeRunError> { - let pipeline_cache = world.resource::(); - let pipelines = world.resource::(); - - let Some(radiance_pipeline) = pipeline_cache.get_compute_pipeline(pipelines.radiance) - else { - return Ok(()); - }; - let Some(irradiance_pipeline) = pipeline_cache.get_compute_pipeline(pipelines.irradiance) - else { - return Ok(()); - }; + let Some(radiance_pipeline) = pipeline_cache.get_compute_pipeline(pipelines.radiance) else { + return; + }; + let Some(irradiance_pipeline) = pipeline_cache.get_compute_pipeline(pipelines.irradiance) + else { + return; + }; - let diagnostics = render_context.diagnostic_recorder(); + let diagnostics = ctx.diagnostic_recorder(); + let diagnostics = diagnostics.as_deref(); - for (_, bind_groups, env_map_light) in self.query.iter_manual(world) { + for (bind_groups, env_map_light) in &query { + // Radiance convolution pass + { let mut compute_pass = - render_context - .command_encoder() + ctx.command_encoder() .begin_compute_pass(&ComputePassDescriptor { label: Some("lightprobe_radiance_map"), timestamp_writes: None, @@ -1054,7 +990,6 @@ impl Node for FilteringNode { let base_size = env_map_light.specular_map.size.width; - // Radiance convolution pass // Process each mip at different roughness levels for (mip, bind_group) in bind_groups.radiance.iter().enumerate() { compute_pass.set_bind_group(0, bind_group, &[]); @@ -1066,35 +1001,31 @@ impl Node for FilteringNode { // Dispatch for all 6 faces compute_pass.dispatch_workgroups(workgroup_count, workgroup_count, 6); } + pass_span.end(&mut compute_pass); - // End the compute pass before starting the next one - drop(compute_pass); - - // Irradiance convolution pass - // Generate the diffuse environment map - { - let mut compute_pass = - render_context - .command_encoder() - .begin_compute_pass(&ComputePassDescriptor { - label: Some("lightprobe_irradiance_map"), - timestamp_writes: None, - }); + } + + // Irradiance convolution pass + // Generate the diffuse environment map + { + let mut compute_pass = + ctx.command_encoder() + .begin_compute_pass(&ComputePassDescriptor { + label: Some("lightprobe_irradiance_map"), + timestamp_writes: None, + }); - let irr_span = - diagnostics.pass_span(&mut compute_pass, "lightprobe_irradiance_map"); + let irr_span = + diagnostics.pass_span(&mut compute_pass, "lightprobe_irradiance_map"); - compute_pass.set_pipeline(irradiance_pipeline); - compute_pass.set_bind_group(0, &bind_groups.irradiance, &[]); + compute_pass.set_pipeline(irradiance_pipeline); + compute_pass.set_bind_group(0, &bind_groups.irradiance, &[]); - // 32×32 texture processed with 8×8 workgroups for all 6 faces - compute_pass.dispatch_workgroups(4, 4, 6); + // 32×32 texture processed with 8×8 workgroups for all 6 faces + compute_pass.dispatch_workgroups(4, 4, 6); - irr_span.end(&mut compute_pass); - } + irr_span.end(&mut compute_pass); } - - Ok(()) } } diff --git a/crates/bevy_pbr/src/meshlet/material_shade_nodes.rs b/crates/bevy_pbr/src/meshlet/material_shade_nodes.rs index 890df912f4e93..34a63ea8c70a9 100644 --- a/crates/bevy_pbr/src/meshlet/material_shade_nodes.rs +++ b/crates/bevy_pbr/src/meshlet/material_shade_nodes.rs @@ -16,406 +16,340 @@ use bevy_core_pipeline::prepass::{ MotionVectorPrepass, PreviousViewUniformOffset, ViewPrepassTextures, }; use bevy_ecs::{ - query::{Has, QueryItem}, - world::World, + prelude::*, + query::Has, }; use bevy_render::{ camera::ExtractedCamera, - diagnostic::RecordDiagnostics, - render_graph::{NodeRunError, RenderGraphContext, ViewNode}, render_resource::{ LoadOp, Operations, PipelineCache, RenderPassDepthStencilAttachment, RenderPassDescriptor, StoreOp, }, - renderer::RenderContext, + renderer::{RenderContext, ViewQuery}, view::{ViewTarget, ViewUniformOffset}, }; +/// /// Fullscreen shading pass based on the visibility buffer generated from rasterizing meshlets. -#[derive(Default)] -pub struct MeshletMainOpaquePass3dNode; -impl ViewNode for MeshletMainOpaquePass3dNode { - type ViewQuery = ( - &'static ExtractedCamera, - &'static ViewTarget, - &'static MeshViewBindGroup, - &'static ViewUniformOffset, - &'static ViewLightsUniformOffset, - &'static ViewFogUniformOffset, - &'static ViewLightProbesUniformOffset, - &'static ViewScreenSpaceReflectionsUniformOffset, - &'static ViewEnvironmentMapUniformOffset, - Option<&'static MainPassResolutionOverride>, - &'static MeshletViewMaterialsMainOpaquePass, - &'static MeshletViewBindGroups, - &'static MeshletViewResources, - ); - - fn run( - &self, - _graph: &mut RenderGraphContext, - render_context: &mut RenderContext, - ( - camera, - target, - mesh_view_bind_group, - view_uniform_offset, - view_lights_offset, - view_fog_offset, - view_light_probes_offset, - view_ssr_offset, - view_environment_map_offset, - resolution_override, - meshlet_view_materials, - meshlet_view_bind_groups, - meshlet_view_resources, - ): QueryItem, - world: &World, - ) -> Result<(), NodeRunError> { - if meshlet_view_materials.is_empty() { - return Ok(()); - } +pub fn meshlet_main_opaque_pass( + view: ViewQuery<( + &ExtractedCamera, + &ViewTarget, + &MeshViewBindGroup, + &ViewUniformOffset, + &ViewLightsUniformOffset, + &ViewFogUniformOffset, + &ViewLightProbesUniformOffset, + &ViewScreenSpaceReflectionsUniformOffset, + &ViewEnvironmentMapUniformOffset, + Option<&MainPassResolutionOverride>, + &MeshletViewMaterialsMainOpaquePass, + &MeshletViewBindGroups, + &MeshletViewResources, + )>, + instance_manager: Res, + pipeline_cache: Res, + mut ctx: RenderContext, +) { + let ( + camera, + target, + mesh_view_bind_group, + view_uniform_offset, + view_lights_offset, + view_fog_offset, + view_light_probes_offset, + view_ssr_offset, + view_environment_map_offset, + resolution_override, + meshlet_view_materials, + meshlet_view_bind_groups, + meshlet_view_resources, + ) = view.into_inner(); - let ( - Some(instance_manager), - Some(pipeline_cache), - Some(meshlet_material_depth), - Some(meshlet_material_shade_bind_group), - ) = ( - world.get_resource::(), - world.get_resource::(), - meshlet_view_resources.material_depth.as_ref(), - meshlet_view_bind_groups.material_shade.as_ref(), - ) - else { - return Ok(()); - }; + if meshlet_view_materials.is_empty() { + return; + } - let diagnostics = render_context.diagnostic_recorder(); + let (Some(meshlet_material_depth), Some(meshlet_material_shade_bind_group)) = ( + meshlet_view_resources.material_depth.as_ref(), + meshlet_view_bind_groups.material_shade.as_ref(), + ) else { + return; + }; - let mut render_pass = render_context.begin_tracked_render_pass(RenderPassDescriptor { - label: Some("meshlet_material_opaque_3d_pass"), - color_attachments: &[Some(target.get_color_attachment())], - depth_stencil_attachment: Some(RenderPassDepthStencilAttachment { - view: &meshlet_material_depth.default_view, - depth_ops: Some(Operations { - load: LoadOp::Load, - store: StoreOp::Store, - }), - stencil_ops: None, + let mut render_pass = ctx.begin_tracked_render_pass(RenderPassDescriptor { + label: Some("meshlet_material_opaque_3d_pass"), + color_attachments: &[Some(target.get_color_attachment())], + depth_stencil_attachment: Some(RenderPassDepthStencilAttachment { + view: &meshlet_material_depth.default_view, + depth_ops: Some(Operations { + load: LoadOp::Load, + store: StoreOp::Store, }), - timestamp_writes: None, - occlusion_query_set: None, - }); - let pass_span = diagnostics.pass_span(&mut render_pass, "meshlet_material_opaque_3d_pass"); - if let Some(viewport) = - Viewport::from_viewport_and_override(camera.viewport.as_ref(), resolution_override) - { - render_pass.set_camera_viewport(&viewport); - } + stencil_ops: None, + }), + timestamp_writes: None, + occlusion_query_set: None, + }); - render_pass.set_bind_group( - 0, - &mesh_view_bind_group.main, - &[ - view_uniform_offset.offset, - view_lights_offset.offset, - view_fog_offset.offset, - **view_light_probes_offset, - **view_ssr_offset, - **view_environment_map_offset, - ], - ); - render_pass.set_bind_group(1, &mesh_view_bind_group.binding_array, &[]); - render_pass.set_bind_group(2, meshlet_material_shade_bind_group, &[]); + if let Some(viewport) = + Viewport::from_viewport_and_override(camera.viewport.as_ref(), resolution_override) + { + render_pass.set_camera_viewport(&viewport); + } + + render_pass.set_bind_group( + 0, + &mesh_view_bind_group.main, + &[ + view_uniform_offset.offset, + view_lights_offset.offset, + view_fog_offset.offset, + **view_light_probes_offset, + **view_ssr_offset, + **view_environment_map_offset, + ], + ); + render_pass.set_bind_group(1, &mesh_view_bind_group.binding_array, &[]); + render_pass.set_bind_group(2, meshlet_material_shade_bind_group, &[]); - // 1 fullscreen triangle draw per material - for (material_id, material_pipeline_id, material_bind_group) in - meshlet_view_materials.iter() + // 1 fullscreen triangle draw per material + for (material_id, material_pipeline_id, material_bind_group) in meshlet_view_materials.iter() { + if instance_manager.material_present_in_scene(material_id) + && let Some(material_pipeline) = + pipeline_cache.get_render_pipeline(*material_pipeline_id) { - if instance_manager.material_present_in_scene(material_id) - && let Some(material_pipeline) = - pipeline_cache.get_render_pipeline(*material_pipeline_id) - { - let x = *material_id * 3; - render_pass.set_render_pipeline(material_pipeline); - render_pass.set_bind_group(3, material_bind_group, &[]); - render_pass.draw(x..(x + 3), 0..1); - } + let x = *material_id * 3; + render_pass.set_render_pipeline(material_pipeline); + render_pass.set_bind_group(3, material_bind_group, &[]); + render_pass.draw(x..(x + 3), 0..1); } - - pass_span.end(&mut render_pass); - - Ok(()) } } +/// /// Fullscreen pass to generate prepass textures based on the visibility buffer generated from rasterizing meshlets. -#[derive(Default)] -pub struct MeshletPrepassNode; -impl ViewNode for MeshletPrepassNode { - type ViewQuery = ( - &'static ExtractedCamera, - &'static ViewPrepassTextures, - &'static ViewUniformOffset, - &'static PreviousViewUniformOffset, - Option<&'static MainPassResolutionOverride>, +pub fn meshlet_prepass( + view: ViewQuery<( + &ExtractedCamera, + &ViewPrepassTextures, + &ViewUniformOffset, + &PreviousViewUniformOffset, + Option<&MainPassResolutionOverride>, Has, - &'static MeshletViewMaterialsPrepass, - &'static MeshletViewBindGroups, - &'static MeshletViewResources, - ); - - fn run( - &self, - _graph: &mut RenderGraphContext, - render_context: &mut RenderContext, - ( - camera, - view_prepass_textures, - view_uniform_offset, - previous_view_uniform_offset, - resolution_override, - view_has_motion_vector_prepass, - meshlet_view_materials, - meshlet_view_bind_groups, - meshlet_view_resources, - ): QueryItem, - world: &World, - ) -> Result<(), NodeRunError> { - if meshlet_view_materials.is_empty() { - return Ok(()); - } + &MeshletViewMaterialsPrepass, + &MeshletViewBindGroups, + &MeshletViewResources, + )>, + prepass_view_bind_group: Res, + instance_manager: Res, + pipeline_cache: Res, + mut ctx: RenderContext, +) { + let ( + camera, + view_prepass_textures, + view_uniform_offset, + previous_view_uniform_offset, + resolution_override, + view_has_motion_vector_prepass, + meshlet_view_materials, + meshlet_view_bind_groups, + meshlet_view_resources, + ) = view.into_inner(); - let ( - Some(prepass_view_bind_group), - Some(instance_manager), - Some(pipeline_cache), - Some(meshlet_material_depth), - Some(meshlet_material_shade_bind_group), - ) = ( - world.get_resource::(), - world.get_resource::(), - world.get_resource::(), - meshlet_view_resources.material_depth.as_ref(), - meshlet_view_bind_groups.material_shade.as_ref(), - ) - else { - return Ok(()); - }; + if meshlet_view_materials.is_empty() { + return; + } - let diagnostics = render_context.diagnostic_recorder(); + let (Some(meshlet_material_depth), Some(meshlet_material_shade_bind_group)) = ( + meshlet_view_resources.material_depth.as_ref(), + meshlet_view_bind_groups.material_shade.as_ref(), + ) else { + return; + }; - let color_attachments = vec![ - view_prepass_textures - .normal - .as_ref() - .map(|normals_texture| normals_texture.get_attachment()), - view_prepass_textures - .motion_vectors - .as_ref() - .map(|motion_vectors_texture| motion_vectors_texture.get_attachment()), - // Use None in place of Deferred attachments - None, - None, - ]; + let color_attachments = vec![ + view_prepass_textures + .normal + .as_ref() + .map(|normals_texture| normals_texture.get_attachment()), + view_prepass_textures + .motion_vectors + .as_ref() + .map(|motion_vectors_texture| motion_vectors_texture.get_attachment()), + // Use None in place of Deferred attachments + None, + None, + ]; - let mut render_pass = render_context.begin_tracked_render_pass(RenderPassDescriptor { - label: Some("meshlet_material_prepass"), - color_attachments: &color_attachments, - depth_stencil_attachment: Some(RenderPassDepthStencilAttachment { - view: &meshlet_material_depth.default_view, - depth_ops: Some(Operations { - load: LoadOp::Load, - store: StoreOp::Store, - }), - stencil_ops: None, + let mut render_pass = ctx.begin_tracked_render_pass(RenderPassDescriptor { + label: Some("meshlet_material_prepass"), + color_attachments: &color_attachments, + depth_stencil_attachment: Some(RenderPassDepthStencilAttachment { + view: &meshlet_material_depth.default_view, + depth_ops: Some(Operations { + load: LoadOp::Load, + store: StoreOp::Store, }), - timestamp_writes: None, - occlusion_query_set: None, - }); - let pass_span = diagnostics.pass_span(&mut render_pass, "meshlet_material_prepass"); - if let Some(viewport) = - Viewport::from_viewport_and_override(camera.viewport.as_ref(), resolution_override) - { - render_pass.set_camera_viewport(&viewport); - } + stencil_ops: None, + }), + timestamp_writes: None, + occlusion_query_set: None, + }); - if view_has_motion_vector_prepass { - render_pass.set_bind_group( - 0, - prepass_view_bind_group.motion_vectors.as_ref().unwrap(), - &[ - view_uniform_offset.offset, - previous_view_uniform_offset.offset, - ], - ); - } else { - render_pass.set_bind_group( - 0, - prepass_view_bind_group.no_motion_vectors.as_ref().unwrap(), - &[view_uniform_offset.offset], - ); - } + if let Some(viewport) = + Viewport::from_viewport_and_override(camera.viewport.as_ref(), resolution_override) + { + render_pass.set_camera_viewport(&viewport); + } - render_pass.set_bind_group(1, &prepass_view_bind_group.empty_bind_group, &[]); - render_pass.set_bind_group(2, meshlet_material_shade_bind_group, &[]); + if view_has_motion_vector_prepass { + render_pass.set_bind_group( + 0, + prepass_view_bind_group.motion_vectors.as_ref().unwrap(), + &[ + view_uniform_offset.offset, + previous_view_uniform_offset.offset, + ], + ); + } else { + render_pass.set_bind_group( + 0, + prepass_view_bind_group.no_motion_vectors.as_ref().unwrap(), + &[view_uniform_offset.offset], + ); + } + + render_pass.set_bind_group(1, &prepass_view_bind_group.empty_bind_group, &[]); + render_pass.set_bind_group(2, meshlet_material_shade_bind_group, &[]); - // 1 fullscreen triangle draw per material - for (material_id, material_pipeline_id, material_bind_group) in - meshlet_view_materials.iter() + // 1 fullscreen triangle draw per material + for (material_id, material_pipeline_id, material_bind_group) in meshlet_view_materials.iter() { + if instance_manager.material_present_in_scene(material_id) + && let Some(material_pipeline) = + pipeline_cache.get_render_pipeline(*material_pipeline_id) { - if instance_manager.material_present_in_scene(material_id) - && let Some(material_pipeline) = - pipeline_cache.get_render_pipeline(*material_pipeline_id) - { - let x = *material_id * 3; - render_pass.set_render_pipeline(material_pipeline); - render_pass.set_bind_group(2, material_bind_group, &[]); - render_pass.draw(x..(x + 3), 0..1); - } + let x = *material_id * 3; + render_pass.set_render_pipeline(material_pipeline); + render_pass.set_bind_group(2, material_bind_group, &[]); + render_pass.draw(x..(x + 3), 0..1); } - - pass_span.end(&mut render_pass); - - Ok(()) } } /// Fullscreen pass to generate a gbuffer based on the visibility buffer generated from rasterizing meshlets. -#[derive(Default)] -pub struct MeshletDeferredGBufferPrepassNode; -impl ViewNode for MeshletDeferredGBufferPrepassNode { - type ViewQuery = ( - &'static ExtractedCamera, - &'static ViewPrepassTextures, - &'static ViewUniformOffset, - &'static PreviousViewUniformOffset, - Option<&'static MainPassResolutionOverride>, +pub fn meshlet_deferred_gbuffer_prepass( + view: ViewQuery<( + &ExtractedCamera, + &ViewPrepassTextures, + &ViewUniformOffset, + &PreviousViewUniformOffset, + Option<&MainPassResolutionOverride>, Has, - &'static MeshletViewMaterialsDeferredGBufferPrepass, - &'static MeshletViewBindGroups, - &'static MeshletViewResources, - ); - - fn run( - &self, - _graph: &mut RenderGraphContext, - render_context: &mut RenderContext, - ( - camera, - view_prepass_textures, - view_uniform_offset, - previous_view_uniform_offset, - resolution_override, - view_has_motion_vector_prepass, - meshlet_view_materials, - meshlet_view_bind_groups, - meshlet_view_resources, - ): QueryItem, - world: &World, - ) -> Result<(), NodeRunError> { - if meshlet_view_materials.is_empty() { - return Ok(()); - } + &MeshletViewMaterialsDeferredGBufferPrepass, + &MeshletViewBindGroups, + &MeshletViewResources, + )>, + prepass_view_bind_group: Res, + instance_manager: Res, + pipeline_cache: Res, + mut ctx: RenderContext, +) { + let ( + camera, + view_prepass_textures, + view_uniform_offset, + previous_view_uniform_offset, + resolution_override, + view_has_motion_vector_prepass, + meshlet_view_materials, + meshlet_view_bind_groups, + meshlet_view_resources, + ) = view.into_inner(); - let ( - Some(prepass_view_bind_group), - Some(instance_manager), - Some(pipeline_cache), - Some(meshlet_material_depth), - Some(meshlet_material_shade_bind_group), - ) = ( - world.get_resource::(), - world.get_resource::(), - world.get_resource::(), - meshlet_view_resources.material_depth.as_ref(), - meshlet_view_bind_groups.material_shade.as_ref(), - ) - else { - return Ok(()); - }; + if meshlet_view_materials.is_empty() { + return; + } - let color_attachments = vec![ - view_prepass_textures - .normal - .as_ref() - .map(|normals_texture| normals_texture.get_attachment()), - view_prepass_textures - .motion_vectors - .as_ref() - .map(|motion_vectors_texture| motion_vectors_texture.get_attachment()), - view_prepass_textures - .deferred - .as_ref() - .map(|deferred_texture| deferred_texture.get_attachment()), - view_prepass_textures - .deferred_lighting_pass_id - .as_ref() - .map(|deferred_lighting_pass_id| deferred_lighting_pass_id.get_attachment()), - ]; + let (Some(meshlet_material_depth), Some(meshlet_material_shade_bind_group)) = ( + meshlet_view_resources.material_depth.as_ref(), + meshlet_view_bind_groups.material_shade.as_ref(), + ) else { + return; + }; - let diagnostics = render_context.diagnostic_recorder(); + let color_attachments = vec![ + view_prepass_textures + .normal + .as_ref() + .map(|normals_texture| normals_texture.get_attachment()), + view_prepass_textures + .motion_vectors + .as_ref() + .map(|motion_vectors_texture| motion_vectors_texture.get_attachment()), + view_prepass_textures + .deferred + .as_ref() + .map(|deferred_texture| deferred_texture.get_attachment()), + view_prepass_textures + .deferred_lighting_pass_id + .as_ref() + .map(|deferred_lighting_pass_id| deferred_lighting_pass_id.get_attachment()), + ]; - let mut render_pass = render_context.begin_tracked_render_pass(RenderPassDescriptor { - label: Some("meshlet_material_deferred_prepass"), - color_attachments: &color_attachments, - depth_stencil_attachment: Some(RenderPassDepthStencilAttachment { - view: &meshlet_material_depth.default_view, - depth_ops: Some(Operations { - load: LoadOp::Load, - store: StoreOp::Store, - }), - stencil_ops: None, + let mut render_pass = ctx.begin_tracked_render_pass(RenderPassDescriptor { + label: Some("meshlet_material_deferred_prepass"), + color_attachments: &color_attachments, + depth_stencil_attachment: Some(RenderPassDepthStencilAttachment { + view: &meshlet_material_depth.default_view, + depth_ops: Some(Operations { + load: LoadOp::Load, + store: StoreOp::Store, }), - timestamp_writes: None, - occlusion_query_set: None, - }); - let pass_span = - diagnostics.pass_span(&mut render_pass, "meshlet_material_deferred_prepass"); - if let Some(viewport) = - Viewport::from_viewport_and_override(camera.viewport.as_ref(), resolution_override) - { - render_pass.set_camera_viewport(&viewport); - } + stencil_ops: None, + }), + timestamp_writes: None, + occlusion_query_set: None, + }); - if view_has_motion_vector_prepass { - render_pass.set_bind_group( - 0, - prepass_view_bind_group.motion_vectors.as_ref().unwrap(), - &[ - view_uniform_offset.offset, - previous_view_uniform_offset.offset, - ], - ); - } else { - render_pass.set_bind_group( - 0, - prepass_view_bind_group.no_motion_vectors.as_ref().unwrap(), - &[view_uniform_offset.offset], - ); - } + if let Some(viewport) = + Viewport::from_viewport_and_override(camera.viewport.as_ref(), resolution_override) + { + render_pass.set_camera_viewport(&viewport); + } + + if view_has_motion_vector_prepass { + render_pass.set_bind_group( + 0, + prepass_view_bind_group.motion_vectors.as_ref().unwrap(), + &[ + view_uniform_offset.offset, + previous_view_uniform_offset.offset, + ], + ); + } else { + render_pass.set_bind_group( + 0, + prepass_view_bind_group.no_motion_vectors.as_ref().unwrap(), + &[view_uniform_offset.offset], + ); + } - render_pass.set_bind_group(1, &prepass_view_bind_group.empty_bind_group, &[]); - render_pass.set_bind_group(2, meshlet_material_shade_bind_group, &[]); + render_pass.set_bind_group(1, &prepass_view_bind_group.empty_bind_group, &[]); + render_pass.set_bind_group(2, meshlet_material_shade_bind_group, &[]); - // 1 fullscreen triangle draw per material - for (material_id, material_pipeline_id, material_bind_group) in - meshlet_view_materials.iter() + // 1 fullscreen triangle draw per material + for (material_id, material_pipeline_id, material_bind_group) in meshlet_view_materials.iter() { + if instance_manager.material_present_in_scene(material_id) + && let Some(material_pipeline) = + pipeline_cache.get_render_pipeline(*material_pipeline_id) { - if instance_manager.material_present_in_scene(material_id) - && let Some(material_pipeline) = - pipeline_cache.get_render_pipeline(*material_pipeline_id) - { - let x = *material_id * 3; - render_pass.set_render_pipeline(material_pipeline); - render_pass.set_bind_group(2, material_bind_group, &[]); - render_pass.draw(x..(x + 3), 0..1); - } + let x = *material_id * 3; + render_pass.set_render_pipeline(material_pipeline); + render_pass.set_bind_group(2, material_bind_group, &[]); + render_pass.draw(x..(x + 3), 0..1); } - - pass_span.end(&mut render_pass); - - Ok(()) } } diff --git a/crates/bevy_pbr/src/meshlet/mod.rs b/crates/bevy_pbr/src/meshlet/mod.rs index 13ec1444c8f0a..abcfb04b0b67c 100644 --- a/crates/bevy_pbr/src/meshlet/mod.rs +++ b/crates/bevy_pbr/src/meshlet/mod.rs @@ -13,18 +13,6 @@ mod pipelines; mod resource_manager; mod visibility_buffer_raster_node; -pub mod graph { - use bevy_render::render_graph::RenderLabel; - - #[derive(Debug, Hash, PartialEq, Eq, Clone, RenderLabel)] - pub enum NodeMeshlet { - VisibilityBufferRasterPass, - Prepass, - DeferredPrepass, - MainOpaquePass, - } -} - pub(crate) use self::{ instance_manager::{queue_material_meshlet_meshes, InstanceManager}, material_pipeline_prepare::{ @@ -40,32 +28,29 @@ pub use self::from_mesh::{ MeshToMeshletMeshConversionError, MESHLET_DEFAULT_VERTEX_POSITION_QUANTIZATION_FACTOR, }; use self::{ - graph::NodeMeshlet, instance_manager::extract_meshlet_mesh_entities, material_pipeline_prepare::{ MeshletViewMaterialsDeferredGBufferPrepass, MeshletViewMaterialsMainOpaquePass, MeshletViewMaterialsPrepass, }, material_shade_nodes::{ - MeshletDeferredGBufferPrepassNode, MeshletMainOpaquePass3dNode, MeshletPrepassNode, + meshlet_deferred_gbuffer_prepass, meshlet_main_opaque_pass, meshlet_prepass, }, meshlet_mesh_manager::perform_pending_meshlet_mesh_writes, pipelines::*, resource_manager::{ prepare_meshlet_per_frame_resources, prepare_meshlet_view_bind_groups, ResourceManager, }, - visibility_buffer_raster_node::MeshletVisibilityBufferRasterPassNode, -}; -use crate::{ - graph::NodePbr, meshlet::meshlet_mesh_manager::init_meshlet_mesh_manager, - PreviousGlobalTransform, + visibility_buffer_raster_node::meshlet_visibility_buffer_raster, }; +use crate::{meshlet::meshlet_mesh_manager::init_meshlet_mesh_manager, PreviousGlobalTransform}; +use crate::render::early_shadow_pass; use bevy_app::{App, Plugin}; use bevy_asset::{embedded_asset, AssetApp, AssetId, Handle}; use bevy_camera::visibility::{self, Visibility, VisibilityClass}; use bevy_core_pipeline::{ - core_3d::graph::{Core3d, Node3d}, prepass::{DeferredPrepass, MotionVectorPrepass, NormalPrepass}, + schedule::{Core3d, Core3dSystems}, }; use bevy_derive::{Deref, DerefMut}; use bevy_ecs::{ @@ -78,7 +63,6 @@ use bevy_ecs::{ }; use bevy_reflect::{std_traits::ReflectDefault, Reflect}; use bevy_render::{ - render_graph::{RenderGraphExt, ViewNodeRunner}, renderer::RenderDevice, settings::WgpuFeatures, view::{prepare_view_targets, Msaa}, @@ -181,39 +165,6 @@ impl Plugin for MeshletPlugin { }; render_app - .add_render_graph_node::( - Core3d, - NodeMeshlet::VisibilityBufferRasterPass, - ) - .add_render_graph_node::>( - Core3d, - NodeMeshlet::Prepass, - ) - .add_render_graph_node::>( - Core3d, - NodeMeshlet::DeferredPrepass, - ) - .add_render_graph_node::>( - Core3d, - NodeMeshlet::MainOpaquePass, - ) - .add_render_graph_edges( - Core3d, - ( - NodeMeshlet::VisibilityBufferRasterPass, - NodePbr::EarlyShadowPass, - // - NodeMeshlet::Prepass, - // - NodeMeshlet::DeferredPrepass, - Node3d::EndPrepasses, - // - Node3d::StartMainPass, - NodeMeshlet::MainOpaquePass, - Node3d::MainOpaquePass, - Node3d::EndMainPass, - ), - ) .insert_resource(InstanceManager::new()) .add_systems( RenderStartup, @@ -241,6 +192,21 @@ impl Plugin for MeshletPlugin { .in_set(RenderSystems::QueueMeshes) .before(queue_material_meshlet_meshes), ), + ) + .add_systems( + Core3d, + ( + meshlet_visibility_buffer_raster.before(early_shadow_pass), + meshlet_prepass + .after(early_shadow_pass) + .before(Core3dSystems::EndPrepasses), + meshlet_deferred_gbuffer_prepass + .after(meshlet_prepass) + .before(Core3dSystems::EndPrepasses), + meshlet_main_opaque_pass + .after(Core3dSystems::StartMainPass) + .before(Core3dSystems::EndMainPass), + ), ); } } diff --git a/crates/bevy_pbr/src/meshlet/visibility_buffer_raster_node.rs b/crates/bevy_pbr/src/meshlet/visibility_buffer_raster_node.rs index fee4741308b85..2701579829f53 100644 --- a/crates/bevy_pbr/src/meshlet/visibility_buffer_raster_node.rs +++ b/crates/bevy_pbr/src/meshlet/visibility_buffer_raster_node.rs @@ -1,139 +1,225 @@ use super::{ pipelines::MeshletPipelines, - resource_manager::{MeshletViewBindGroups, MeshletViewResources}, -}; -use crate::{ - meshlet::resource_manager::ResourceManager, LightEntity, ShadowView, ViewLightEntities, + resource_manager::{MeshletViewBindGroups, MeshletViewResources, ResourceManager}, }; +use crate::{LightEntity, ShadowView, ViewLightEntities}; use bevy_color::LinearRgba; use bevy_core_pipeline::prepass::PreviousViewUniformOffset; -use bevy_ecs::{ - query::QueryState, - world::{FromWorld, World}, -}; +use bevy_ecs::prelude::*; use bevy_math::UVec2; use bevy_render::{ camera::ExtractedCamera, - diagnostic::RecordDiagnostics, - render_graph::{Node, NodeRunError, RenderGraphContext}, render_resource::*, - renderer::RenderContext, + renderer::{RenderContext, ViewQuery}, view::{ViewDepthTexture, ViewUniformOffset}, }; +/// /// Rasterize meshlets into a depth buffer, and optional visibility buffer + material depth buffer for shading passes. -pub struct MeshletVisibilityBufferRasterPassNode { - main_view_query: QueryState<( - &'static ExtractedCamera, - &'static ViewDepthTexture, - &'static ViewUniformOffset, - &'static PreviousViewUniformOffset, - &'static MeshletViewBindGroups, - &'static MeshletViewResources, - &'static ViewLightEntities, +// TODO: Reuse compute/render passes between logical passes where possible, as they're expensive +pub fn meshlet_visibility_buffer_raster( + world: &World, + view: ViewQuery<( + &ExtractedCamera, + &ViewDepthTexture, + &ViewUniformOffset, + &PreviousViewUniformOffset, + &MeshletViewBindGroups, + &MeshletViewResources, + &ViewLightEntities, )>, - view_light_query: QueryState<( - &'static ShadowView, - &'static LightEntity, - &'static ViewUniformOffset, - &'static PreviousViewUniformOffset, - &'static MeshletViewBindGroups, - &'static MeshletViewResources, + view_light_query: Query<( + &ShadowView, + &LightEntity, + &ViewUniformOffset, + &PreviousViewUniformOffset, + &MeshletViewBindGroups, + &MeshletViewResources, )>, -} + resource_manager: Res, + mut ctx: RenderContext, +) { + let ( + camera, + view_depth, + view_offset, + previous_view_offset, + meshlet_view_bind_groups, + meshlet_view_resources, + lights, + ) = view.into_inner(); + + let Some(( + clear_visibility_buffer_pipeline, + clear_visibility_buffer_shadow_view_pipeline, + first_instance_cull_pipeline, + second_instance_cull_pipeline, + first_bvh_cull_pipeline, + second_bvh_cull_pipeline, + first_meshlet_cull_pipeline, + second_meshlet_cull_pipeline, + downsample_depth_first_pipeline, + downsample_depth_second_pipeline, + downsample_depth_first_shadow_view_pipeline, + downsample_depth_second_shadow_view_pipeline, + visibility_buffer_software_raster_pipeline, + visibility_buffer_software_raster_shadow_view_pipeline, + visibility_buffer_hardware_raster_pipeline, + visibility_buffer_hardware_raster_shadow_view_pipeline, + visibility_buffer_hardware_raster_shadow_view_unclipped_pipeline, + resolve_depth_pipeline, + resolve_depth_shadow_view_pipeline, + resolve_material_depth_pipeline, + remap_1d_to_2d_dispatch_pipeline, + fill_counts_pipeline, + )) = MeshletPipelines::get(world) + else { + return; + }; + + ctx.command_encoder() + .push_debug_group("meshlet_visibility_buffer_raster"); + + ctx.command_encoder().clear_buffer( + &resource_manager.visibility_buffer_raster_cluster_prev_counts, + 0, + None, + ); -impl FromWorld for MeshletVisibilityBufferRasterPassNode { - fn from_world(world: &mut World) -> Self { - Self { - main_view_query: QueryState::new(world), - view_light_query: QueryState::new(world), - } - } -} + clear_visibility_buffer_pass( + &mut ctx, + &meshlet_view_bind_groups.clear_visibility_buffer, + clear_visibility_buffer_pipeline, + meshlet_view_resources.view_size, + ); -impl Node for MeshletVisibilityBufferRasterPassNode { - fn update(&mut self, world: &mut World) { - self.main_view_query.update_archetypes(world); - self.view_light_query.update_archetypes(world); - } + ctx.command_encoder() + .push_debug_group("meshlet_first_pass"); + first_cull( + &mut ctx, + meshlet_view_bind_groups, + meshlet_view_resources, + view_offset, + previous_view_offset, + first_instance_cull_pipeline, + first_bvh_cull_pipeline, + first_meshlet_cull_pipeline, + remap_1d_to_2d_dispatch_pipeline, + ); + raster_pass( + true, + &mut ctx, + &meshlet_view_resources.visibility_buffer_software_raster_indirect_args, + &meshlet_view_resources.visibility_buffer_hardware_raster_indirect_args, + &meshlet_view_resources.dummy_render_target.default_view, + meshlet_view_bind_groups, + view_offset, + visibility_buffer_software_raster_pipeline, + visibility_buffer_hardware_raster_pipeline, + fill_counts_pipeline, + Some(camera), + meshlet_view_resources.rightmost_slot, + ); + ctx.command_encoder().pop_debug_group(); + + meshlet_view_resources.depth_pyramid.downsample_depth_with_ctx( + "downsample_depth", + &mut ctx, + meshlet_view_resources.view_size, + &meshlet_view_bind_groups.downsample_depth, + downsample_depth_first_pipeline, + downsample_depth_second_pipeline, + ); + + ctx.command_encoder() + .push_debug_group("meshlet_second_pass"); + second_cull( + &mut ctx, + meshlet_view_bind_groups, + meshlet_view_resources, + view_offset, + previous_view_offset, + second_instance_cull_pipeline, + second_bvh_cull_pipeline, + second_meshlet_cull_pipeline, + remap_1d_to_2d_dispatch_pipeline, + ); + raster_pass( + false, + &mut ctx, + &meshlet_view_resources.visibility_buffer_software_raster_indirect_args, + &meshlet_view_resources.visibility_buffer_hardware_raster_indirect_args, + &meshlet_view_resources.dummy_render_target.default_view, + meshlet_view_bind_groups, + view_offset, + visibility_buffer_software_raster_pipeline, + visibility_buffer_hardware_raster_pipeline, + fill_counts_pipeline, + Some(camera), + meshlet_view_resources.rightmost_slot, + ); + ctx.command_encoder().pop_debug_group(); + + resolve_depth( + &mut ctx, + view_depth.get_attachment(StoreOp::Store), + meshlet_view_bind_groups, + resolve_depth_pipeline, + camera, + ); + resolve_material_depth( + &mut ctx, + meshlet_view_resources, + meshlet_view_bind_groups, + resolve_material_depth_pipeline, + camera, + ); + meshlet_view_resources.depth_pyramid.downsample_depth_with_ctx( + "downsample_depth", + &mut ctx, + meshlet_view_resources.view_size, + &meshlet_view_bind_groups.downsample_depth, + downsample_depth_first_pipeline, + downsample_depth_second_pipeline, + ); + ctx.command_encoder().pop_debug_group(); - // TODO: Reuse compute/render passes between logical passes where possible, as they're expensive - fn run( - &self, - graph: &mut RenderGraphContext, - render_context: &mut RenderContext, - world: &World, - ) -> Result<(), NodeRunError> { + for light_entity in &lights.lights { let Ok(( - camera, - view_depth, + shadow_view, + light_type, view_offset, previous_view_offset, meshlet_view_bind_groups, meshlet_view_resources, - lights, - )) = self.main_view_query.get_manual(world, graph.view_entity()) + )) = view_light_query.get(*light_entity) else { - return Ok(()); + continue; }; - let Some(( - clear_visibility_buffer_pipeline, - clear_visibility_buffer_shadow_view_pipeline, - first_instance_cull_pipeline, - second_instance_cull_pipeline, - first_bvh_cull_pipeline, - second_bvh_cull_pipeline, - first_meshlet_cull_pipeline, - second_meshlet_cull_pipeline, - downsample_depth_first_pipeline, - downsample_depth_second_pipeline, - downsample_depth_first_shadow_view_pipeline, - downsample_depth_second_shadow_view_pipeline, - visibility_buffer_software_raster_pipeline, - visibility_buffer_software_raster_shadow_view_pipeline, - visibility_buffer_hardware_raster_pipeline, - visibility_buffer_hardware_raster_shadow_view_pipeline, - visibility_buffer_hardware_raster_shadow_view_unclipped_pipeline, - resolve_depth_pipeline, - resolve_depth_shadow_view_pipeline, - resolve_material_depth_pipeline, - remap_1d_to_2d_dispatch_pipeline, - fill_counts_pipeline, - )) = MeshletPipelines::get(world) - else { - return Ok(()); - }; - - let diagnostics = render_context.diagnostic_recorder(); + let shadow_visibility_buffer_hardware_raster_pipeline = + if let LightEntity::Directional { .. } = light_type { + visibility_buffer_hardware_raster_shadow_view_unclipped_pipeline + } else { + visibility_buffer_hardware_raster_shadow_view_pipeline + }; - render_context - .command_encoder() - .push_debug_group("meshlet_visibility_buffer_raster"); - let time_span = diagnostics.time_span( - render_context.command_encoder(), - "meshlet_visibility_buffer_raster", - ); - - let resource_manager = world.get_resource::().unwrap(); - render_context.command_encoder().clear_buffer( - &resource_manager.visibility_buffer_raster_cluster_prev_counts, - 0, - None, - ); + ctx.command_encoder().push_debug_group(&format!( + "meshlet_visibility_buffer_raster: {}", + shadow_view.pass_name + )); clear_visibility_buffer_pass( - render_context, + &mut ctx, &meshlet_view_bind_groups.clear_visibility_buffer, - clear_visibility_buffer_pipeline, + clear_visibility_buffer_shadow_view_pipeline, meshlet_view_resources.view_size, ); - render_context - .command_encoder() + ctx.command_encoder() .push_debug_group("meshlet_first_pass"); first_cull( - render_context, + &mut ctx, meshlet_view_bind_groups, meshlet_view_resources, view_offset, @@ -145,34 +231,33 @@ impl Node for MeshletVisibilityBufferRasterPassNode { ); raster_pass( true, - render_context, + &mut ctx, &meshlet_view_resources.visibility_buffer_software_raster_indirect_args, &meshlet_view_resources.visibility_buffer_hardware_raster_indirect_args, &meshlet_view_resources.dummy_render_target.default_view, meshlet_view_bind_groups, view_offset, - visibility_buffer_software_raster_pipeline, - visibility_buffer_hardware_raster_pipeline, + visibility_buffer_software_raster_shadow_view_pipeline, + shadow_visibility_buffer_hardware_raster_pipeline, fill_counts_pipeline, - Some(camera), + None, meshlet_view_resources.rightmost_slot, ); - render_context.command_encoder().pop_debug_group(); + ctx.command_encoder().pop_debug_group(); - meshlet_view_resources.depth_pyramid.downsample_depth( + meshlet_view_resources.depth_pyramid.downsample_depth_with_ctx( "downsample_depth", - render_context, + &mut ctx, meshlet_view_resources.view_size, &meshlet_view_bind_groups.downsample_depth, - downsample_depth_first_pipeline, - downsample_depth_second_pipeline, + downsample_depth_first_shadow_view_pipeline, + downsample_depth_second_shadow_view_pipeline, ); - render_context - .command_encoder() + ctx.command_encoder() .push_debug_group("meshlet_second_pass"); second_cull( - render_context, + &mut ctx, meshlet_view_bind_groups, meshlet_view_resources, view_offset, @@ -184,181 +269,47 @@ impl Node for MeshletVisibilityBufferRasterPassNode { ); raster_pass( false, - render_context, + &mut ctx, &meshlet_view_resources.visibility_buffer_software_raster_indirect_args, &meshlet_view_resources.visibility_buffer_hardware_raster_indirect_args, &meshlet_view_resources.dummy_render_target.default_view, meshlet_view_bind_groups, view_offset, - visibility_buffer_software_raster_pipeline, - visibility_buffer_hardware_raster_pipeline, + visibility_buffer_software_raster_shadow_view_pipeline, + shadow_visibility_buffer_hardware_raster_pipeline, fill_counts_pipeline, - Some(camera), + None, meshlet_view_resources.rightmost_slot, ); - render_context.command_encoder().pop_debug_group(); + ctx.command_encoder().pop_debug_group(); resolve_depth( - render_context, - view_depth.get_attachment(StoreOp::Store), - meshlet_view_bind_groups, - resolve_depth_pipeline, - camera, - ); - resolve_material_depth( - render_context, - meshlet_view_resources, + &mut ctx, + shadow_view.depth_attachment.get_attachment(StoreOp::Store), meshlet_view_bind_groups, - resolve_material_depth_pipeline, + resolve_depth_shadow_view_pipeline, camera, ); - meshlet_view_resources.depth_pyramid.downsample_depth( + meshlet_view_resources.depth_pyramid.downsample_depth_with_ctx( "downsample_depth", - render_context, + &mut ctx, meshlet_view_resources.view_size, &meshlet_view_bind_groups.downsample_depth, - downsample_depth_first_pipeline, - downsample_depth_second_pipeline, + downsample_depth_first_shadow_view_pipeline, + downsample_depth_second_shadow_view_pipeline, ); - render_context.command_encoder().pop_debug_group(); - - for light_entity in &lights.lights { - let Ok(( - shadow_view, - light_type, - view_offset, - previous_view_offset, - meshlet_view_bind_groups, - meshlet_view_resources, - )) = self.view_light_query.get_manual(world, *light_entity) - else { - continue; - }; - - let shadow_visibility_buffer_hardware_raster_pipeline = - if let LightEntity::Directional { .. } = light_type { - visibility_buffer_hardware_raster_shadow_view_unclipped_pipeline - } else { - visibility_buffer_hardware_raster_shadow_view_pipeline - }; - - render_context.command_encoder().push_debug_group(&format!( - "meshlet_visibility_buffer_raster: {}", - shadow_view.pass_name - )); - let time_span_shadow = diagnostics.time_span( - render_context.command_encoder(), - shadow_view.pass_name.clone(), - ); - clear_visibility_buffer_pass( - render_context, - &meshlet_view_bind_groups.clear_visibility_buffer, - clear_visibility_buffer_shadow_view_pipeline, - meshlet_view_resources.view_size, - ); - - render_context - .command_encoder() - .push_debug_group("meshlet_first_pass"); - first_cull( - render_context, - meshlet_view_bind_groups, - meshlet_view_resources, - view_offset, - previous_view_offset, - first_instance_cull_pipeline, - first_bvh_cull_pipeline, - first_meshlet_cull_pipeline, - remap_1d_to_2d_dispatch_pipeline, - ); - raster_pass( - true, - render_context, - &meshlet_view_resources.visibility_buffer_software_raster_indirect_args, - &meshlet_view_resources.visibility_buffer_hardware_raster_indirect_args, - &meshlet_view_resources.dummy_render_target.default_view, - meshlet_view_bind_groups, - view_offset, - visibility_buffer_software_raster_shadow_view_pipeline, - shadow_visibility_buffer_hardware_raster_pipeline, - fill_counts_pipeline, - None, - meshlet_view_resources.rightmost_slot, - ); - render_context.command_encoder().pop_debug_group(); - - meshlet_view_resources.depth_pyramid.downsample_depth( - "downsample_depth", - render_context, - meshlet_view_resources.view_size, - &meshlet_view_bind_groups.downsample_depth, - downsample_depth_first_shadow_view_pipeline, - downsample_depth_second_shadow_view_pipeline, - ); - - render_context - .command_encoder() - .push_debug_group("meshlet_second_pass"); - second_cull( - render_context, - meshlet_view_bind_groups, - meshlet_view_resources, - view_offset, - previous_view_offset, - second_instance_cull_pipeline, - second_bvh_cull_pipeline, - second_meshlet_cull_pipeline, - remap_1d_to_2d_dispatch_pipeline, - ); - raster_pass( - false, - render_context, - &meshlet_view_resources.visibility_buffer_software_raster_indirect_args, - &meshlet_view_resources.visibility_buffer_hardware_raster_indirect_args, - &meshlet_view_resources.dummy_render_target.default_view, - meshlet_view_bind_groups, - view_offset, - visibility_buffer_software_raster_shadow_view_pipeline, - shadow_visibility_buffer_hardware_raster_pipeline, - fill_counts_pipeline, - None, - meshlet_view_resources.rightmost_slot, - ); - render_context.command_encoder().pop_debug_group(); - - resolve_depth( - render_context, - shadow_view.depth_attachment.get_attachment(StoreOp::Store), - meshlet_view_bind_groups, - resolve_depth_shadow_view_pipeline, - camera, - ); - meshlet_view_resources.depth_pyramid.downsample_depth( - "downsample_depth", - render_context, - meshlet_view_resources.view_size, - &meshlet_view_bind_groups.downsample_depth, - downsample_depth_first_shadow_view_pipeline, - downsample_depth_second_shadow_view_pipeline, - ); - render_context.command_encoder().pop_debug_group(); - time_span_shadow.end(render_context.command_encoder()); - } - - time_span.end(render_context.command_encoder()); - - Ok(()) + ctx.command_encoder().pop_debug_group(); } } // TODO: Replace this with vkCmdClearColorImage once wgpu supports it fn clear_visibility_buffer_pass( - render_context: &mut RenderContext, + ctx: &mut RenderContext, clear_visibility_buffer_bind_group: &BindGroup, clear_visibility_buffer_pipeline: &ComputePipeline, view_size: UVec2, ) { - let command_encoder = render_context.command_encoder(); + let command_encoder = ctx.command_encoder(); let mut clear_visibility_buffer_pass = command_encoder.begin_compute_pass(&ComputePassDescriptor { label: Some("clear_visibility_buffer"), @@ -375,7 +326,7 @@ fn clear_visibility_buffer_pass( } fn first_cull( - render_context: &mut RenderContext, + ctx: &mut RenderContext, meshlet_view_bind_groups: &MeshletViewBindGroups, meshlet_view_resources: &MeshletViewResources, view_offset: &ViewUniformOffset, @@ -388,7 +339,7 @@ fn first_cull( let workgroups = meshlet_view_resources.scene_instance_count.div_ceil(128); cull_pass( "meshlet_first_instance_cull", - render_context, + ctx, &meshlet_view_bind_groups.first_instance_cull, view_offset, previous_view_offset, @@ -397,14 +348,13 @@ fn first_cull( ) .dispatch_workgroups(workgroups, 1, 1); - render_context - .command_encoder() + ctx.command_encoder() .push_debug_group("meshlet_first_bvh_cull"); let mut ping = true; for _ in 0..meshlet_view_resources.max_bvh_depth { cull_pass( "meshlet_first_bvh_cull_dispatch", - render_context, + ctx, if ping { &meshlet_view_bind_groups.first_bvh_cull_ping } else { @@ -423,7 +373,7 @@ fn first_cull( }, 0, ); - render_context.command_encoder().clear_buffer( + ctx.command_encoder().clear_buffer( if ping { &meshlet_view_resources.first_bvh_cull_count_front } else { @@ -432,7 +382,7 @@ fn first_cull( 0, Some(4), ); - render_context.command_encoder().clear_buffer( + ctx.command_encoder().clear_buffer( if ping { &meshlet_view_resources.first_bvh_cull_dispatch_front } else { @@ -443,11 +393,11 @@ fn first_cull( ); ping = !ping; } - render_context.command_encoder().pop_debug_group(); + ctx.command_encoder().pop_debug_group(); let mut pass = cull_pass( "meshlet_first_meshlet_cull", - render_context, + ctx, &meshlet_view_bind_groups.first_meshlet_cull, view_offset, previous_view_offset, @@ -463,7 +413,7 @@ fn first_cull( } fn second_cull( - render_context: &mut RenderContext, + ctx: &mut RenderContext, meshlet_view_bind_groups: &MeshletViewBindGroups, meshlet_view_resources: &MeshletViewResources, view_offset: &ViewUniformOffset, @@ -475,7 +425,7 @@ fn second_cull( ) { cull_pass( "meshlet_second_instance_cull", - render_context, + ctx, &meshlet_view_bind_groups.second_instance_cull, view_offset, previous_view_offset, @@ -484,14 +434,13 @@ fn second_cull( ) .dispatch_workgroups_indirect(&meshlet_view_resources.second_pass_dispatch, 0); - render_context - .command_encoder() + ctx.command_encoder() .push_debug_group("meshlet_second_bvh_cull"); let mut ping = true; for _ in 0..meshlet_view_resources.max_bvh_depth { cull_pass( "meshlet_second_bvh_cull_dispatch", - render_context, + ctx, if ping { &meshlet_view_bind_groups.second_bvh_cull_ping } else { @@ -512,11 +461,11 @@ fn second_cull( ); ping = !ping; } - render_context.command_encoder().pop_debug_group(); + ctx.command_encoder().pop_debug_group(); let mut pass = cull_pass( "meshlet_second_meshlet_cull", - render_context, + ctx, &meshlet_view_bind_groups.second_meshlet_cull, view_offset, previous_view_offset, @@ -533,14 +482,14 @@ fn second_cull( fn cull_pass<'a>( label: &'static str, - render_context: &'a mut RenderContext, + ctx: &'a mut RenderContext, bind_group: &'a BindGroup, view_offset: &'a ViewUniformOffset, previous_view_offset: &'a PreviousViewUniformOffset, pipeline: &'a ComputePipeline, push_constants: &[u32], ) -> ComputePass<'a> { - let command_encoder = render_context.command_encoder(); + let command_encoder = ctx.command_encoder(); let mut pass = command_encoder.begin_compute_pass(&ComputePassDescriptor { label: Some(label), timestamp_writes: None, @@ -569,7 +518,7 @@ fn remap_1d_to_2d( fn raster_pass( first_pass: bool, - render_context: &mut RenderContext, + ctx: &mut RenderContext, visibility_buffer_software_raster_indirect_args: &Buffer, visibility_buffer_hardware_raster_indirect_args: &Buffer, dummy_render_target: &TextureView, @@ -581,17 +530,14 @@ fn raster_pass( camera: Option<&ExtractedCamera>, raster_cluster_rightmost_slot: u32, ) { - let mut software_pass = - render_context - .command_encoder() - .begin_compute_pass(&ComputePassDescriptor { - label: Some(if first_pass { - "raster_software_first" - } else { - "raster_software_second" - }), - timestamp_writes: None, - }); + let mut software_pass = ctx.command_encoder().begin_compute_pass(&ComputePassDescriptor { + label: Some(if first_pass { + "raster_software_first" + } else { + "raster_software_second" + }), + timestamp_writes: None, + }); software_pass.set_pipeline(visibility_buffer_software_raster_pipeline); software_pass.set_bind_group( 0, @@ -601,7 +547,7 @@ fn raster_pass( software_pass.dispatch_workgroups_indirect(visibility_buffer_software_raster_indirect_args, 0); drop(software_pass); - let mut hardware_pass = render_context.begin_tracked_render_pass(RenderPassDescriptor { + let mut hardware_pass = ctx.begin_tracked_render_pass(RenderPassDescriptor { label: Some(if first_pass { "raster_hardware_first" } else { @@ -637,26 +583,23 @@ fn raster_pass( hardware_pass.draw_indirect(visibility_buffer_hardware_raster_indirect_args, 0); drop(hardware_pass); - let mut fill_counts_pass = - render_context - .command_encoder() - .begin_compute_pass(&ComputePassDescriptor { - label: Some("fill_counts"), - timestamp_writes: None, - }); + let mut fill_counts_pass = ctx.command_encoder().begin_compute_pass(&ComputePassDescriptor { + label: Some("fill_counts"), + timestamp_writes: None, + }); fill_counts_pass.set_pipeline(fill_counts_pipeline); fill_counts_pass.set_bind_group(0, &meshlet_view_bind_groups.fill_counts, &[]); fill_counts_pass.dispatch_workgroups(1, 1, 1); } fn resolve_depth( - render_context: &mut RenderContext, + ctx: &mut RenderContext, depth_stencil_attachment: RenderPassDepthStencilAttachment, meshlet_view_bind_groups: &MeshletViewBindGroups, resolve_depth_pipeline: &RenderPipeline, camera: &ExtractedCamera, ) { - let mut resolve_pass = render_context.begin_tracked_render_pass(RenderPassDescriptor { + let mut resolve_pass = ctx.begin_tracked_render_pass(RenderPassDescriptor { label: Some("resolve_depth"), color_attachments: &[], depth_stencil_attachment: Some(depth_stencil_attachment), @@ -672,7 +615,7 @@ fn resolve_depth( } fn resolve_material_depth( - render_context: &mut RenderContext, + ctx: &mut RenderContext, meshlet_view_resources: &MeshletViewResources, meshlet_view_bind_groups: &MeshletViewBindGroups, resolve_material_depth_pipeline: &RenderPipeline, @@ -682,7 +625,7 @@ fn resolve_material_depth( meshlet_view_resources.material_depth.as_ref(), meshlet_view_bind_groups.resolve_material_depth.as_ref(), ) { - let mut resolve_pass = render_context.begin_tracked_render_pass(RenderPassDescriptor { + let mut resolve_pass = ctx.begin_tracked_render_pass(RenderPassDescriptor { label: Some("resolve_material_depth"), color_attachments: &[], depth_stencil_attachment: Some(RenderPassDepthStencilAttachment { diff --git a/crates/bevy_pbr/src/render/gpu_preprocess.rs b/crates/bevy_pbr/src/render/gpu_preprocess.rs index d25b41eac80f1..8bcf0d5fce5f3 100644 --- a/crates/bevy_pbr/src/render/gpu_preprocess.rs +++ b/crates/bevy_pbr/src/render/gpu_preprocess.rs @@ -11,19 +11,22 @@ use core::num::{NonZero, NonZeroU64}; use bevy_app::{App, Plugin}; use bevy_asset::{embedded_asset, load_embedded_asset, Handle}; use bevy_core_pipeline::{ - core_3d::graph::{Core3d, Node3d}, - experimental::mip_generation::ViewDepthPyramid, - prepass::{DepthPrepass, PreviousViewData, PreviousViewUniformOffset, PreviousViewUniforms}, + experimental::mip_generation::{early_downsample_depth, ViewDepthPyramid}, + prepass::{ + node::{early_prepass, late_prepass}, + DepthPrepass, PreviousViewData, PreviousViewUniformOffset, PreviousViewUniforms, + }, + schedule::{Core3d, Core3dSystems}, }; use bevy_derive::{Deref, DerefMut}; use bevy_ecs::{ component::Component, entity::Entity, prelude::resource_exists, - query::{Has, Or, QueryState, With, Without}, + query::{Has, Or, With, Without}, resource::Resource, schedule::IntoScheduleConfigs as _, - system::{lifetimeless::Read, Commands, Query, Res, ResMut}, + system::{Commands, Query, Res, ResMut}, world::{FromWorld, World}, }; use bevy_render::{ @@ -35,9 +38,7 @@ use bevy_render::{ PreprocessWorkItemBuffers, UntypedPhaseBatchedInstanceBuffers, UntypedPhaseIndirectParametersBuffers, }, - diagnostic::RecordDiagnostics, experimental::occlusion_culling::OcclusionCulling, - render_graph::{Node, NodeRunError, RenderGraphContext, RenderGraphExt}, render_resource::{ binding_types::{storage_buffer, storage_buffer_read_only, texture_2d, uniform_buffer}, BindGroup, BindGroupEntries, BindGroupLayoutDescriptor, BindingResource, Buffer, @@ -46,7 +47,7 @@ use bevy_render::{ ShaderStages, ShaderType, SpecializedComputePipeline, SpecializedComputePipelines, TextureSampleType, UninitBufferVec, }, - renderer::{RenderContext, RenderDevice, RenderQueue}, + renderer::{RenderContext, RenderDevice, RenderQueue, ViewQuery}, settings::WgpuFeatures, view::{ExtractedView, NoIndirectDrawing, ViewUniform, ViewUniformOffset, ViewUniforms}, Render, RenderApp, RenderSystems, @@ -57,9 +58,7 @@ use bitflags::bitflags; use smallvec::{smallvec, SmallVec}; use tracing::warn; -use crate::{ - graph::NodePbr, MeshCullingData, MeshCullingDataBuffer, MeshInputUniform, MeshUniform, -}; +use crate::{MeshCullingData, MeshCullingDataBuffer, MeshInputUniform, MeshUniform}; use super::{ShadowView, ViewLightEntities}; @@ -78,108 +77,6 @@ pub struct GpuMeshPreprocessPlugin { pub use_gpu_instance_buffer_builder: bool, } -/// The render node that clears out the GPU-side indirect metadata buffers. -/// -/// This is only used when indirect drawing is enabled. -#[derive(Default)] -pub struct ClearIndirectParametersMetadataNode; - -/// The render node for the first mesh preprocessing pass. -/// -/// This pass runs a compute shader to cull meshes outside the view frustum (if -/// that wasn't done by the CPU), cull meshes that weren't visible last frame -/// (if occlusion culling is on), transform them, and, if indirect drawing is -/// on, populate indirect draw parameter metadata for the subsequent -/// [`EarlyPrepassBuildIndirectParametersNode`]. -pub struct EarlyGpuPreprocessNode { - view_query: QueryState< - ( - Read, - Option>, - Option>, - Has, - Has, - ), - Without, - >, - main_view_query: QueryState>, -} - -/// The render node for the second mesh preprocessing pass. -/// -/// This pass runs a compute shader to cull meshes outside the view frustum (if -/// that wasn't done by the CPU), cull meshes that were neither visible last -/// frame nor visible this frame (if occlusion culling is on), transform them, -/// and, if indirect drawing is on, populate the indirect draw parameter -/// metadata for the subsequent [`LatePrepassBuildIndirectParametersNode`]. -pub struct LateGpuPreprocessNode { - view_query: QueryState< - ( - Read, - Read, - Read, - ), - ( - Without, - Without, - With, - With, - ), - >, -} - -/// The render node for the part of the indirect parameter building pass that -/// draws the meshes visible from the previous frame. -/// -/// This node runs a compute shader on the output of the -/// [`EarlyGpuPreprocessNode`] in order to transform the -/// [`IndirectParametersGpuMetadata`] into properly-formatted -/// [`IndirectParametersIndexed`] and [`IndirectParametersNonIndexed`]. -pub struct EarlyPrepassBuildIndirectParametersNode { - view_query: QueryState< - Read, - ( - Without, - Without, - Or<(With, With)>, - ), - >, -} - -/// The render node for the part of the indirect parameter building pass that -/// draws the meshes that are potentially visible on this frame but weren't -/// visible on the previous frame. -/// -/// This node runs a compute shader on the output of the -/// [`LateGpuPreprocessNode`] in order to transform the -/// [`IndirectParametersGpuMetadata`] into properly-formatted -/// [`IndirectParametersIndexed`] and [`IndirectParametersNonIndexed`]. -pub struct LatePrepassBuildIndirectParametersNode { - view_query: QueryState< - Read, - ( - Without, - Without, - Or<(With, With)>, - With, - ), - >, -} - -/// The render node for the part of the indirect parameter building pass that -/// draws all meshes, both those that are newly-visible on this frame and those -/// that were visible last frame. -/// -/// This node runs a compute shader on the output of the -/// [`EarlyGpuPreprocessNode`] and [`LateGpuPreprocessNode`] in order to -/// transform the [`IndirectParametersGpuMetadata`] into properly-formatted -/// [`IndirectParametersIndexed`] and [`IndirectParametersNonIndexed`]. -pub struct MainBuildIndirectParametersNode { - view_query: QueryState< - Read, - (Without, Without), - >, -} /// The compute shader pipelines for the GPU mesh preprocessing and indirect /// parameter building passes. @@ -462,601 +359,485 @@ impl Plugin for GpuMeshPreprocessPlugin { write_mesh_culling_data_buffer.in_set(RenderSystems::PrepareResourcesFlush), ), ) - .add_render_graph_node::( - Core3d, - NodePbr::ClearIndirectParametersMetadata - ) - .add_render_graph_node::(Core3d, NodePbr::EarlyGpuPreprocess) - .add_render_graph_node::(Core3d, NodePbr::LateGpuPreprocess) - .add_render_graph_node::( - Core3d, - NodePbr::EarlyPrepassBuildIndirectParameters, - ) - .add_render_graph_node::( - Core3d, - NodePbr::LatePrepassBuildIndirectParameters, - ) - .add_render_graph_node::( - Core3d, - NodePbr::MainBuildIndirectParameters, - ) - .add_render_graph_edges( + .add_systems( Core3d, ( - NodePbr::ClearIndirectParametersMetadata, - NodePbr::EarlyGpuPreprocess, - NodePbr::EarlyPrepassBuildIndirectParameters, - Node3d::EarlyPrepass, - Node3d::EarlyDeferredPrepass, - Node3d::EarlyDownsampleDepth, - NodePbr::LateGpuPreprocess, - NodePbr::LatePrepassBuildIndirectParameters, - Node3d::LatePrepass, - Node3d::LateDeferredPrepass, - NodePbr::MainBuildIndirectParameters, - Node3d::StartMainPass, + // Clear indirect parameters metadata first, before early prepass + clear_indirect_parameters_metadata.before(early_prepass), + // Early GPU preprocess runs before early prepasses + early_gpu_preprocess + .after(clear_indirect_parameters_metadata) + .before(early_prepass), + // Early prepass build indirect parameters + early_prepass_build_indirect_parameters + .after(early_gpu_preprocess) + .before(early_prepass), + // Late GPU preprocess runs after early prepasses, before late prepasses + late_gpu_preprocess + .after(early_downsample_depth) + .before(late_prepass), + // Late prepass build indirect parameters + late_prepass_build_indirect_parameters + .after(late_gpu_preprocess) + .before(late_prepass), + // Main build indirect parameters runs before main pass + main_build_indirect_parameters + .after(late_prepass_build_indirect_parameters) + .before(Core3dSystems::StartMainPass), ), - ).add_render_graph_edges( - Core3d, - ( - NodePbr::EarlyPrepassBuildIndirectParameters, - NodePbr::EarlyShadowPass, - Node3d::EarlyDownsampleDepth, - ) - ).add_render_graph_edges( - Core3d, - ( - NodePbr::LatePrepassBuildIndirectParameters, - NodePbr::LateShadowPass, - NodePbr::MainBuildIndirectParameters, - ) ); } } -impl Node for ClearIndirectParametersMetadataNode { - fn run<'w>( - &self, - _: &mut RenderGraphContext, - render_context: &mut RenderContext<'w>, - world: &'w World, - ) -> Result<(), NodeRunError> { - let Some(indirect_parameters_buffers) = world.get_resource::() - else { - return Ok(()); - }; - - // Clear out each indexed and non-indexed GPU-side buffer. - for phase_indirect_parameters_buffers in indirect_parameters_buffers.values() { - if let Some(indexed_gpu_metadata_buffer) = phase_indirect_parameters_buffers - .indexed - .gpu_metadata_buffer() - { - render_context.command_encoder().clear_buffer( - indexed_gpu_metadata_buffer, - 0, - Some( - phase_indirect_parameters_buffers.indexed.batch_count() as u64 - * size_of::() as u64, - ), - ); - } +pub fn clear_indirect_parameters_metadata( + indirect_parameters_buffers: Option>, + mut ctx: RenderContext, +) { + let Some(indirect_parameters_buffers) = indirect_parameters_buffers else { + return; + }; - if let Some(non_indexed_gpu_metadata_buffer) = phase_indirect_parameters_buffers - .non_indexed - .gpu_metadata_buffer() - { - render_context.command_encoder().clear_buffer( - non_indexed_gpu_metadata_buffer, - 0, - Some( - phase_indirect_parameters_buffers.non_indexed.batch_count() as u64 - * size_of::() as u64, - ), - ); - } + // Clear out each indexed and non-indexed GPU-side buffer. + for phase_indirect_parameters_buffers in indirect_parameters_buffers.values() { + if let Some(indexed_gpu_metadata_buffer) = phase_indirect_parameters_buffers + .indexed + .gpu_metadata_buffer() + { + ctx.command_encoder().clear_buffer( + indexed_gpu_metadata_buffer, + 0, + Some( + phase_indirect_parameters_buffers.indexed.batch_count() as u64 + * size_of::() as u64, + ), + ); } - Ok(()) - } -} - -impl FromWorld for EarlyGpuPreprocessNode { - fn from_world(world: &mut World) -> Self { - Self { - view_query: QueryState::new(world), - main_view_query: QueryState::new(world), + if let Some(non_indexed_gpu_metadata_buffer) = phase_indirect_parameters_buffers + .non_indexed + .gpu_metadata_buffer() + { + ctx.command_encoder().clear_buffer( + non_indexed_gpu_metadata_buffer, + 0, + Some( + phase_indirect_parameters_buffers.non_indexed.batch_count() as u64 + * size_of::() as u64, + ), + ); } } } -impl Node for EarlyGpuPreprocessNode { - fn update(&mut self, world: &mut World) { - self.view_query.update_archetypes(world); - self.main_view_query.update_archetypes(world); +pub fn early_gpu_preprocess( + current_view: ViewQuery, Without>, + view_query: Query< + ( + &ExtractedView, + Option<&PreprocessBindGroups>, + Option<&ViewUniformOffset>, + Has, + Has, + ), + Without, + >, + batched_instance_buffers: Res>, + pipeline_cache: Res, + preprocess_pipelines: Res, + mut ctx: RenderContext, +) { + let command_encoder = ctx.command_encoder(); + + let mut compute_pass = command_encoder.begin_compute_pass(&ComputePassDescriptor { + label: Some("early_mesh_preprocessing"), + timestamp_writes: None, + }); + + let view_entity = current_view.entity(); + let shadow_cascade_views = current_view.into_inner(); + let mut all_views: SmallVec<[_; 8]> = SmallVec::new(); + all_views.push(view_entity); + if let Some(shadow_cascade_views) = shadow_cascade_views { + all_views.extend(shadow_cascade_views.lights.iter().copied()); } - fn run<'w>( - &self, - graph: &mut RenderGraphContext, - render_context: &mut RenderContext<'w>, - world: &'w World, - ) -> Result<(), NodeRunError> { - let diagnostics = render_context.diagnostic_recorder(); - - // Grab the [`BatchedInstanceBuffers`]. - let batched_instance_buffers = - world.resource::>(); - - let pipeline_cache = world.resource::(); - let preprocess_pipelines = world.resource::(); - - let mut compute_pass = - render_context - .command_encoder() - .begin_compute_pass(&ComputePassDescriptor { - label: Some("early_mesh_preprocessing"), - timestamp_writes: None, - }); - let pass_span = diagnostics.pass_span(&mut compute_pass, "early_mesh_preprocessing"); - - let mut all_views: SmallVec<[_; 8]> = SmallVec::new(); - all_views.push(graph.view_entity()); - if let Ok(shadow_cascade_views) = - self.main_view_query.get_manual(world, graph.view_entity()) - { - all_views.extend(shadow_cascade_views.lights.iter().copied()); - } + // Run the compute passes. + for view_entity in all_views { + let Ok((view, bind_groups, view_uniform_offset, no_indirect_drawing, occlusion_culling)) = + view_query.get(view_entity) + else { + continue; + }; - // Run the compute passes. + let Some(bind_groups) = bind_groups else { + continue; + }; + let Some(view_uniform_offset) = view_uniform_offset else { + continue; + }; - for view_entity in all_views { - let Ok(( - view, - bind_groups, - view_uniform_offset, - no_indirect_drawing, - occlusion_culling, - )) = self.view_query.get_manual(world, view_entity) - else { - continue; - }; + // Select the right pipeline, depending on whether GPU culling is in + // use. + let maybe_pipeline_id = if no_indirect_drawing { + preprocess_pipelines.direct_preprocess.pipeline_id + } else if occlusion_culling { + preprocess_pipelines + .early_gpu_occlusion_culling_preprocess + .pipeline_id + } else { + preprocess_pipelines + .gpu_frustum_culling_preprocess + .pipeline_id + }; - let Some(bind_groups) = bind_groups else { - continue; - }; - let Some(view_uniform_offset) = view_uniform_offset else { - continue; - }; + // Fetch the pipeline. + let Some(preprocess_pipeline_id) = maybe_pipeline_id else { + warn!("The build mesh uniforms pipeline wasn't ready"); + continue; + }; - // Select the right pipeline, depending on whether GPU culling is in - // use. - let maybe_pipeline_id = if no_indirect_drawing { - preprocess_pipelines.direct_preprocess.pipeline_id - } else if occlusion_culling { - preprocess_pipelines - .early_gpu_occlusion_culling_preprocess - .pipeline_id - } else { - preprocess_pipelines - .gpu_frustum_culling_preprocess - .pipeline_id - }; + let Some(preprocess_pipeline) = + pipeline_cache.get_compute_pipeline(preprocess_pipeline_id) + else { + // This will happen while the pipeline is being compiled and is fine. + continue; + }; + + compute_pass.set_pipeline(preprocess_pipeline); - // Fetch the pipeline. - let Some(preprocess_pipeline_id) = maybe_pipeline_id else { - warn!("The build mesh uniforms pipeline wasn't ready"); + // Loop over each render phase. + for (phase_type_id, batched_phase_instance_buffers) in + &batched_instance_buffers.phase_instance_buffers + { + // Grab the work item buffers for this view. + let Some(work_item_buffers) = batched_phase_instance_buffers + .work_item_buffers + .get(&view.retained_view_entity) + else { continue; }; - let Some(preprocess_pipeline) = - pipeline_cache.get_compute_pipeline(preprocess_pipeline_id) - else { - // This will happen while the pipeline is being compiled and is fine. + // Fetch the bind group for the render phase. + let Some(phase_bind_groups) = bind_groups.get(phase_type_id) else { continue; }; - compute_pass.set_pipeline(preprocess_pipeline); - - // Loop over each render phase. - for (phase_type_id, batched_phase_instance_buffers) in - &batched_instance_buffers.phase_instance_buffers - { - // Grab the work item buffers for this view. - let Some(work_item_buffers) = batched_phase_instance_buffers - .work_item_buffers - .get(&view.retained_view_entity) - else { - continue; - }; - - // Fetch the bind group for the render phase. - let Some(phase_bind_groups) = bind_groups.get(phase_type_id) else { - continue; - }; - - // Make sure the mesh preprocessing shader has access to the - // view info it needs to do culling and motion vector - // computation. - let dynamic_offsets = [view_uniform_offset.offset]; - - // Are we drawing directly or indirectly? - match *phase_bind_groups { - PhasePreprocessBindGroups::Direct(ref bind_group) => { - // Invoke the mesh preprocessing shader to transform - // meshes only, but not cull. - let PreprocessWorkItemBuffers::Direct(work_item_buffer) = work_item_buffers - else { - continue; - }; - compute_pass.set_bind_group(0, bind_group, &dynamic_offsets); - let workgroup_count = work_item_buffer.len().div_ceil(WORKGROUP_SIZE); - if workgroup_count > 0 { - compute_pass.dispatch_workgroups(workgroup_count as u32, 1, 1); - } + // Make sure the mesh preprocessing shader has access to the + // view info it needs to do culling and motion vector + // computation. + let dynamic_offsets = [view_uniform_offset.offset]; + + // Are we drawing directly or indirectly? + match *phase_bind_groups { + PhasePreprocessBindGroups::Direct(ref bind_group) => { + // Invoke the mesh preprocessing shader to transform + // meshes only, but not cull. + let PreprocessWorkItemBuffers::Direct(work_item_buffer) = work_item_buffers + else { + continue; + }; + compute_pass.set_bind_group(0, bind_group, &dynamic_offsets); + let workgroup_count = work_item_buffer.len().div_ceil(WORKGROUP_SIZE); + if workgroup_count > 0 { + compute_pass.dispatch_workgroups(workgroup_count as u32, 1, 1); } + } - PhasePreprocessBindGroups::IndirectFrustumCulling { - indexed: ref maybe_indexed_bind_group, - non_indexed: ref maybe_non_indexed_bind_group, - } - | PhasePreprocessBindGroups::IndirectOcclusionCulling { - early_indexed: ref maybe_indexed_bind_group, - early_non_indexed: ref maybe_non_indexed_bind_group, + PhasePreprocessBindGroups::IndirectFrustumCulling { + indexed: ref maybe_indexed_bind_group, + non_indexed: ref maybe_non_indexed_bind_group, + } + | PhasePreprocessBindGroups::IndirectOcclusionCulling { + early_indexed: ref maybe_indexed_bind_group, + early_non_indexed: ref maybe_non_indexed_bind_group, + .. + } => { + // Invoke the mesh preprocessing shader to transform and + // cull the meshes. + let PreprocessWorkItemBuffers::Indirect { + indexed: indexed_buffer, + non_indexed: non_indexed_buffer, .. - } => { - // Invoke the mesh preprocessing shader to transform and - // cull the meshes. - let PreprocessWorkItemBuffers::Indirect { - indexed: indexed_buffer, - non_indexed: non_indexed_buffer, + } = work_item_buffers + else { + continue; + }; + + // Transform and cull indexed meshes if there are any. + if let Some(indexed_bind_group) = maybe_indexed_bind_group { + if let PreprocessWorkItemBuffers::Indirect { + gpu_occlusion_culling: + Some(GpuOcclusionCullingWorkItemBuffers { + late_indirect_parameters_indexed_offset, + .. + }), .. - } = work_item_buffers - else { - continue; - }; - - // Transform and cull indexed meshes if there are any. - if let Some(indexed_bind_group) = maybe_indexed_bind_group { - if let PreprocessWorkItemBuffers::Indirect { - gpu_occlusion_culling: - Some(GpuOcclusionCullingWorkItemBuffers { - late_indirect_parameters_indexed_offset, - .. - }), - .. - } = *work_item_buffers - { - compute_pass.set_push_constants( - 0, - bytemuck::bytes_of(&late_indirect_parameters_indexed_offset), - ); - } - - compute_pass.set_bind_group(0, indexed_bind_group, &dynamic_offsets); - let workgroup_count = indexed_buffer.len().div_ceil(WORKGROUP_SIZE); - if workgroup_count > 0 { - compute_pass.dispatch_workgroups(workgroup_count as u32, 1, 1); - } + } = *work_item_buffers + { + compute_pass.set_push_constants( + 0, + bytemuck::bytes_of(&late_indirect_parameters_indexed_offset), + ); } - // Transform and cull non-indexed meshes if there are any. - if let Some(non_indexed_bind_group) = maybe_non_indexed_bind_group { - if let PreprocessWorkItemBuffers::Indirect { - gpu_occlusion_culling: - Some(GpuOcclusionCullingWorkItemBuffers { - late_indirect_parameters_non_indexed_offset, - .. - }), - .. - } = *work_item_buffers - { - compute_pass.set_push_constants( - 0, - bytemuck::bytes_of( - &late_indirect_parameters_non_indexed_offset, - ), - ); - } + compute_pass.set_bind_group(0, indexed_bind_group, &dynamic_offsets); + let workgroup_count = indexed_buffer.len().div_ceil(WORKGROUP_SIZE); + if workgroup_count > 0 { + compute_pass.dispatch_workgroups(workgroup_count as u32, 1, 1); + } + } - compute_pass.set_bind_group( + // Transform and cull non-indexed meshes if there are any. + if let Some(non_indexed_bind_group) = maybe_non_indexed_bind_group { + if let PreprocessWorkItemBuffers::Indirect { + gpu_occlusion_culling: + Some(GpuOcclusionCullingWorkItemBuffers { + late_indirect_parameters_non_indexed_offset, + .. + }), + .. + } = *work_item_buffers + { + compute_pass.set_push_constants( 0, - non_indexed_bind_group, - &dynamic_offsets, + bytemuck::bytes_of(&late_indirect_parameters_non_indexed_offset), ); - let workgroup_count = non_indexed_buffer.len().div_ceil(WORKGROUP_SIZE); - if workgroup_count > 0 { - compute_pass.dispatch_workgroups(workgroup_count as u32, 1, 1); - } + } + + compute_pass.set_bind_group(0, non_indexed_bind_group, &dynamic_offsets); + let workgroup_count = non_indexed_buffer.len().div_ceil(WORKGROUP_SIZE); + if workgroup_count > 0 { + compute_pass.dispatch_workgroups(workgroup_count as u32, 1, 1); } } } } } - - pass_span.end(&mut compute_pass); - - Ok(()) } } -impl FromWorld for EarlyPrepassBuildIndirectParametersNode { - fn from_world(world: &mut World) -> Self { - Self { - view_query: QueryState::new(world), - } - } -} +pub fn late_gpu_preprocess( + current_view: ViewQuery< + ( + &ExtractedView, + &PreprocessBindGroups, + &ViewUniformOffset, + ), + ( + Without, + Without, + With, + With, + ), + >, + batched_instance_buffers: Res>, + pipeline_cache: Res, + preprocess_pipelines: Res, + mut ctx: RenderContext, +) { + let (view, bind_groups, view_uniform_offset) = current_view.into_inner(); -impl FromWorld for LatePrepassBuildIndirectParametersNode { - fn from_world(world: &mut World) -> Self { - Self { - view_query: QueryState::new(world), - } - } -} + let command_encoder = ctx.command_encoder(); -impl FromWorld for MainBuildIndirectParametersNode { - fn from_world(world: &mut World) -> Self { - Self { - view_query: QueryState::new(world), - } - } -} + let mut compute_pass = command_encoder.begin_compute_pass(&ComputePassDescriptor { + label: Some("late_mesh_preprocessing"), + timestamp_writes: None, + }); -impl FromWorld for LateGpuPreprocessNode { - fn from_world(world: &mut World) -> Self { - Self { - view_query: QueryState::new(world), - } - } -} + let maybe_pipeline_id = preprocess_pipelines + .late_gpu_occlusion_culling_preprocess + .pipeline_id; -impl Node for LateGpuPreprocessNode { - fn update(&mut self, world: &mut World) { - self.view_query.update_archetypes(world); - } + // Fetch the pipeline. + let Some(preprocess_pipeline_id) = maybe_pipeline_id else { + warn!("The build mesh uniforms pipeline wasn't ready"); + return; + }; - fn run<'w>( - &self, - _: &mut RenderGraphContext, - render_context: &mut RenderContext<'w>, - world: &'w World, - ) -> Result<(), NodeRunError> { - let diagnostics = render_context.diagnostic_recorder(); - - // Grab the [`BatchedInstanceBuffers`]. - let batched_instance_buffers = - world.resource::>(); - - let pipeline_cache = world.resource::(); - let preprocess_pipelines = world.resource::(); - - let mut compute_pass = - render_context - .command_encoder() - .begin_compute_pass(&ComputePassDescriptor { - label: Some("late_mesh_preprocessing"), - timestamp_writes: None, - }); - let pass_span = diagnostics.pass_span(&mut compute_pass, "late_mesh_preprocessing"); - - // Run the compute passes. - for (view, bind_groups, view_uniform_offset) in self.view_query.iter_manual(world) { - let maybe_pipeline_id = preprocess_pipelines - .late_gpu_occlusion_culling_preprocess - .pipeline_id; - - // Fetch the pipeline. - let Some(preprocess_pipeline_id) = maybe_pipeline_id else { - warn!("The build mesh uniforms pipeline wasn't ready"); - return Ok(()); - }; + let Some(preprocess_pipeline) = pipeline_cache.get_compute_pipeline(preprocess_pipeline_id) + else { + // This will happen while the pipeline is being compiled and is fine. + return; + }; - let Some(preprocess_pipeline) = - pipeline_cache.get_compute_pipeline(preprocess_pipeline_id) - else { - // This will happen while the pipeline is being compiled and is fine. - return Ok(()); - }; + compute_pass.set_pipeline(preprocess_pipeline); - compute_pass.set_pipeline(preprocess_pipeline); - - // Loop over each phase. Because we built the phases in parallel, - // each phase has a separate set of instance buffers. - for (phase_type_id, batched_phase_instance_buffers) in - &batched_instance_buffers.phase_instance_buffers - { - let UntypedPhaseBatchedInstanceBuffers { - ref work_item_buffers, - ref late_indexed_indirect_parameters_buffer, - ref late_non_indexed_indirect_parameters_buffer, - .. - } = *batched_phase_instance_buffers; - - // Grab the work item buffers for this view. - let Some(phase_work_item_buffers) = - work_item_buffers.get(&view.retained_view_entity) - else { - continue; - }; - - let ( - PreprocessWorkItemBuffers::Indirect { - gpu_occlusion_culling: - Some(GpuOcclusionCullingWorkItemBuffers { - late_indirect_parameters_indexed_offset, - late_indirect_parameters_non_indexed_offset, - .. - }), - .. - }, - Some(PhasePreprocessBindGroups::IndirectOcclusionCulling { - late_indexed: maybe_late_indexed_bind_group, - late_non_indexed: maybe_late_non_indexed_bind_group, + // Loop over each phase. Because we built the phases in parallel, + // each phase has a separate set of instance buffers. + for (phase_type_id, batched_phase_instance_buffers) in + &batched_instance_buffers.phase_instance_buffers + { + let UntypedPhaseBatchedInstanceBuffers { + ref work_item_buffers, + ref late_indexed_indirect_parameters_buffer, + ref late_non_indexed_indirect_parameters_buffer, + .. + } = *batched_phase_instance_buffers; + + // Grab the work item buffers for this view. + let Some(phase_work_item_buffers) = work_item_buffers.get(&view.retained_view_entity) + else { + continue; + }; + + let ( + PreprocessWorkItemBuffers::Indirect { + gpu_occlusion_culling: + Some(GpuOcclusionCullingWorkItemBuffers { + late_indirect_parameters_indexed_offset, + late_indirect_parameters_non_indexed_offset, .. }), - Some(late_indexed_indirect_parameters_buffer), - Some(late_non_indexed_indirect_parameters_buffer), - ) = ( - phase_work_item_buffers, - bind_groups.get(phase_type_id), - late_indexed_indirect_parameters_buffer.buffer(), - late_non_indexed_indirect_parameters_buffer.buffer(), - ) - else { - continue; - }; - - let mut dynamic_offsets: SmallVec<[u32; 1]> = smallvec![]; - dynamic_offsets.push(view_uniform_offset.offset); - - // If there's no space reserved for work items, then don't - // bother doing the dispatch, as there can't possibly be any - // meshes of the given class (indexed or non-indexed) in this - // phase. - - // Transform and cull indexed meshes if there are any. - if let Some(late_indexed_bind_group) = maybe_late_indexed_bind_group { - compute_pass.set_push_constants( - 0, - bytemuck::bytes_of(late_indirect_parameters_indexed_offset), - ); - - compute_pass.set_bind_group(0, late_indexed_bind_group, &dynamic_offsets); - compute_pass.dispatch_workgroups_indirect( - late_indexed_indirect_parameters_buffer, - (*late_indirect_parameters_indexed_offset as u64) - * (size_of::() as u64), - ); - } - - // Transform and cull non-indexed meshes if there are any. - if let Some(late_non_indexed_bind_group) = maybe_late_non_indexed_bind_group { - compute_pass.set_push_constants( - 0, - bytemuck::bytes_of(late_indirect_parameters_non_indexed_offset), - ); - - compute_pass.set_bind_group(0, late_non_indexed_bind_group, &dynamic_offsets); - compute_pass.dispatch_workgroups_indirect( - late_non_indexed_indirect_parameters_buffer, - (*late_indirect_parameters_non_indexed_offset as u64) - * (size_of::() as u64), - ); - } - } - } + .. + }, + Some(PhasePreprocessBindGroups::IndirectOcclusionCulling { + late_indexed: maybe_late_indexed_bind_group, + late_non_indexed: maybe_late_non_indexed_bind_group, + .. + }), + Some(late_indexed_indirect_parameters_buffer), + Some(late_non_indexed_indirect_parameters_buffer), + ) = ( + phase_work_item_buffers, + bind_groups.get(phase_type_id), + late_indexed_indirect_parameters_buffer.buffer(), + late_non_indexed_indirect_parameters_buffer.buffer(), + ) + else { + continue; + }; - pass_span.end(&mut compute_pass); + let mut dynamic_offsets: SmallVec<[u32; 1]> = smallvec![]; + dynamic_offsets.push(view_uniform_offset.offset); - Ok(()) - } -} + // If there's no space reserved for work items, then don't + // bother doing the dispatch, as there can't possibly be any + // meshes of the given class (indexed or non-indexed) in this + // phase. -impl Node for EarlyPrepassBuildIndirectParametersNode { - fn update(&mut self, world: &mut World) { - self.view_query.update_archetypes(world); - } + // Transform and cull indexed meshes if there are any. + if let Some(late_indexed_bind_group) = maybe_late_indexed_bind_group { + compute_pass.set_push_constants( + 0, + bytemuck::bytes_of(late_indirect_parameters_indexed_offset), + ); - fn run<'w>( - &self, - _: &mut RenderGraphContext, - render_context: &mut RenderContext<'w>, - world: &'w World, - ) -> Result<(), NodeRunError> { - let preprocess_pipelines = world.resource::(); - - // If there are no views with a depth prepass enabled, we don't need to - // run this. - if self.view_query.iter_manual(world).next().is_none() { - return Ok(()); + compute_pass.set_bind_group(0, late_indexed_bind_group, &dynamic_offsets); + compute_pass.dispatch_workgroups_indirect( + late_indexed_indirect_parameters_buffer, + (*late_indirect_parameters_indexed_offset as u64) + * (size_of::() as u64), + ); } - run_build_indirect_parameters_node( - render_context, - world, - &preprocess_pipelines.early_phase, - "early_prepass_indirect_parameters_building", - ) - } -} - -impl Node for LatePrepassBuildIndirectParametersNode { - fn update(&mut self, world: &mut World) { - self.view_query.update_archetypes(world); - } + // Transform and cull non-indexed meshes if there are any. + if let Some(late_non_indexed_bind_group) = maybe_late_non_indexed_bind_group { + compute_pass.set_push_constants( + 0, + bytemuck::bytes_of(late_indirect_parameters_non_indexed_offset), + ); - fn run<'w>( - &self, - _: &mut RenderGraphContext, - render_context: &mut RenderContext<'w>, - world: &'w World, - ) -> Result<(), NodeRunError> { - let preprocess_pipelines = world.resource::(); - - // If there are no views with occlusion culling enabled, we don't need - // to run this. - if self.view_query.iter_manual(world).next().is_none() { - return Ok(()); + compute_pass.set_bind_group(0, late_non_indexed_bind_group, &dynamic_offsets); + compute_pass.dispatch_workgroups_indirect( + late_non_indexed_indirect_parameters_buffer, + (*late_indirect_parameters_non_indexed_offset as u64) + * (size_of::() as u64), + ); } - - run_build_indirect_parameters_node( - render_context, - world, - &preprocess_pipelines.late_phase, - "late_prepass_indirect_parameters_building", - ) } } -impl Node for MainBuildIndirectParametersNode { - fn update(&mut self, world: &mut World) { - self.view_query.update_archetypes(world); - } +pub fn early_prepass_build_indirect_parameters( + _view: ViewQuery< + (), + ( + Without, + Without, + Or<(With, With)>, + ), + >, + preprocess_pipelines: Res, + build_indirect_params_bind_groups: Option>, + pipeline_cache: Res, + indirect_parameters_buffers: Option>, + mut ctx: RenderContext, +) { + run_build_indirect_parameters( + &mut ctx, + build_indirect_params_bind_groups.as_deref(), + &pipeline_cache, + indirect_parameters_buffers.as_deref(), + &preprocess_pipelines.early_phase, + "early_prepass_indirect_parameters_building", + ); +} - fn run<'w>( - &self, - _: &mut RenderGraphContext, - render_context: &mut RenderContext<'w>, - world: &'w World, - ) -> Result<(), NodeRunError> { - let preprocess_pipelines = world.resource::(); - - run_build_indirect_parameters_node( - render_context, - world, - &preprocess_pipelines.main_phase, - "main_indirect_parameters_building", - ) - } +pub fn late_prepass_build_indirect_parameters( + _view: ViewQuery< + (), + ( + Without, + Without, + Or<(With, With)>, + With, + ), + >, + preprocess_pipelines: Res, + build_indirect_params_bind_groups: Option>, + pipeline_cache: Res, + indirect_parameters_buffers: Option>, + mut ctx: RenderContext, +) { + run_build_indirect_parameters( + &mut ctx, + build_indirect_params_bind_groups.as_deref(), + &pipeline_cache, + indirect_parameters_buffers.as_deref(), + &preprocess_pipelines.late_phase, + "late_prepass_indirect_parameters_building", + ); } -fn run_build_indirect_parameters_node( - render_context: &mut RenderContext, - world: &World, +pub fn main_build_indirect_parameters( + _view: ViewQuery<(), (Without, Without)>, + preprocess_pipelines: Res, + build_indirect_params_bind_groups: Option>, + pipeline_cache: Res, + indirect_parameters_buffers: Option>, + mut ctx: RenderContext, +) { + run_build_indirect_parameters( + &mut ctx, + build_indirect_params_bind_groups.as_deref(), + &pipeline_cache, + indirect_parameters_buffers.as_deref(), + &preprocess_pipelines.main_phase, + "main_indirect_parameters_building", + ); +} + +fn run_build_indirect_parameters( + ctx: &mut RenderContext, + build_indirect_params_bind_groups: Option<&BuildIndirectParametersBindGroups>, + pipeline_cache: &PipelineCache, + indirect_parameters_buffers: Option<&IndirectParametersBuffers>, preprocess_phase_pipelines: &PreprocessPhasePipelines, label: &'static str, -) -> Result<(), NodeRunError> { - let Some(build_indirect_params_bind_groups) = - world.get_resource::() - else { - return Ok(()); +) { + let Some(build_indirect_params_bind_groups) = build_indirect_params_bind_groups else { + return; }; - let diagnostics = render_context.diagnostic_recorder(); + let Some(indirect_parameters_buffers) = indirect_parameters_buffers else { + return; + }; - let pipeline_cache = world.resource::(); - let indirect_parameters_buffers = world.resource::(); + let command_encoder = ctx.command_encoder(); - let mut compute_pass = - render_context - .command_encoder() - .begin_compute_pass(&ComputePassDescriptor { - label: Some(label), - timestamp_writes: None, - }); - let pass_span = diagnostics.pass_span(&mut compute_pass, label); + let mut compute_pass = command_encoder.begin_compute_pass(&ComputePassDescriptor { + label: Some(label), + timestamp_writes: None, + }); // Fetch the pipeline. let ( @@ -1076,8 +857,7 @@ fn run_build_indirect_parameters_node( ) else { warn!("The build indirect parameters pipelines weren't ready"); - pass_span.end(&mut compute_pass); - return Ok(()); + return; }; let ( @@ -1091,8 +871,7 @@ fn run_build_indirect_parameters_node( ) else { // This will happen while the pipeline is being compiled and is fine. - pass_span.end(&mut compute_pass); - return Ok(()); + return; }; // Loop over each phase. As each has as separate set of buffers, we need to @@ -1162,10 +941,6 @@ fn run_build_indirect_parameters_node( } } } - - pass_span.end(&mut compute_pass); - - Ok(()) } impl PreprocessPipelines { diff --git a/crates/bevy_pbr/src/render/light.rs b/crates/bevy_pbr/src/render/light.rs index 9c95f0711493d..45355c5bc3c78 100644 --- a/crates/bevy_pbr/src/render/light.rs +++ b/crates/bevy_pbr/src/render/light.rs @@ -17,7 +17,6 @@ use bevy_ecs::system::SystemChangeTick; use bevy_ecs::{ entity::{EntityHashMap, EntityHashSet}, prelude::*, - system::lifetimeless::Read, }; use bevy_light::cascade::Cascade; use bevy_light::cluster::assign::{calculate_cluster_factors, ClusterableObjectType}; @@ -43,13 +42,11 @@ use bevy_render::{ view::{NoIndirectDrawing, RetainedViewEntity}, }; use bevy_render::{ - diagnostic::RecordDiagnostics, mesh::RenderMesh, render_asset::RenderAssets, - render_graph::{Node, NodeRunError, RenderGraphContext}, render_phase::*, render_resource::*, - renderer::{RenderContext, RenderDevice, RenderQueue}, + renderer::{RenderContext, RenderDevice, RenderQueue, ViewQuery}, texture::*, view::ExtractedView, Extract, @@ -2200,161 +2197,91 @@ impl CachedRenderPipelinePhaseItem for Shadow { } } -/// The rendering node that renders meshes that were "visible" (so to speak) -/// from a light last frame. -/// -/// If occlusion culling for a light is disabled, then this node simply renders +/// This renders meshes that were "visible" (so to speak) from a light last frame. +/// If occlusion culling for a light is disabled, then this simply renders /// all meshes in range of the light. -#[derive(Deref, DerefMut)] -pub struct EarlyShadowPassNode(ShadowPassNode); - -/// The rendering node that renders meshes that became newly "visible" (so to -/// speak) from a light this frame. -/// -/// If occlusion culling for a light is disabled, then this node does nothing. -#[derive(Deref, DerefMut)] -pub struct LateShadowPassNode(ShadowPassNode); - -/// Encapsulates rendering logic shared between the early and late shadow pass -/// nodes. -pub struct ShadowPassNode { - /// The query that finds cameras in which shadows are visible. - main_view_query: QueryState>, - /// The query that finds shadow cascades. - view_light_query: QueryState<(Read, Read, Has)>, +pub fn early_shadow_pass( + world: &World, + view: ViewQuery<&ViewLightEntities>, + view_light_query: Query<(&ShadowView, &ExtractedView, Has)>, + shadow_render_phases: Res>, + mut ctx: RenderContext, +) { + run_shadow_pass( + world, + view.into_inner(), + &view_light_query, + &shadow_render_phases, + &mut ctx, + false, + ); } -impl FromWorld for EarlyShadowPassNode { - fn from_world(world: &mut World) -> Self { - Self(ShadowPassNode::from_world(world)) - } +/// This renders meshes that became newly "visible" (so to speak) from a light this frame. +/// If occlusion culling for a light is disabled, then this does nothing. +pub fn late_shadow_pass( + world: &World, + view: ViewQuery<&ViewLightEntities>, + view_light_query: Query<(&ShadowView, &ExtractedView, Has)>, + shadow_render_phases: Res>, + mut ctx: RenderContext, +) { + run_shadow_pass( + world, + view.into_inner(), + &view_light_query, + &shadow_render_phases, + &mut ctx, + true, + ); } -impl FromWorld for LateShadowPassNode { - fn from_world(world: &mut World) -> Self { - Self(ShadowPassNode::from_world(world)) - } -} +/// Shared implementation for early and late shadow passes. +/// +/// `is_late` is true if this is the late shadow pass or false if this is +/// the early shadow pass. +fn run_shadow_pass( + world: &World, + view_lights: &ViewLightEntities, + view_light_query: &Query<(&ShadowView, &ExtractedView, Has)>, + shadow_render_phases: &ViewBinnedRenderPhases, + ctx: &mut RenderContext, + is_late: bool, +) { + for view_light_entity in view_lights.lights.iter().copied() { + let Ok((view_light, extracted_light_view, occlusion_culling)) = + view_light_query.get(view_light_entity) + else { + continue; + }; -impl FromWorld for ShadowPassNode { - fn from_world(world: &mut World) -> Self { - Self { - main_view_query: QueryState::new(world), - view_light_query: QueryState::new(world), + if is_late && !occlusion_culling { + continue; } - } -} -impl Node for EarlyShadowPassNode { - fn update(&mut self, world: &mut World) { - self.0.update(world); - } - - fn run<'w>( - &self, - graph: &mut RenderGraphContext, - render_context: &mut RenderContext<'w>, - world: &'w World, - ) -> Result<(), NodeRunError> { - self.0.run(graph, render_context, world, false) - } -} - -impl Node for LateShadowPassNode { - fn update(&mut self, world: &mut World) { - self.0.update(world); - } - - fn run<'w>( - &self, - graph: &mut RenderGraphContext, - render_context: &mut RenderContext<'w>, - world: &'w World, - ) -> Result<(), NodeRunError> { - self.0.run(graph, render_context, world, true) - } -} - -impl ShadowPassNode { - fn update(&mut self, world: &mut World) { - self.main_view_query.update_archetypes(world); - self.view_light_query.update_archetypes(world); - } - - /// Runs the node logic. - /// - /// `is_late` is true if this is the late shadow pass or false if this is - /// the early shadow pass. - fn run<'w>( - &self, - graph: &mut RenderGraphContext, - render_context: &mut RenderContext<'w>, - world: &'w World, - is_late: bool, - ) -> Result<(), NodeRunError> { - let Some(shadow_render_phases) = world.get_resource::>() + let Some(shadow_phase) = + shadow_render_phases.get(&extracted_light_view.retained_view_entity) else { - return Ok(()); + continue; }; - if let Ok(view_lights) = self.main_view_query.get_manual(world, graph.view_entity()) { - for view_light_entity in view_lights.lights.iter().copied() { - let Ok((view_light, extracted_light_view, occlusion_culling)) = - self.view_light_query.get_manual(world, view_light_entity) - else { - continue; - }; + #[cfg(feature = "trace")] + let _shadow_pass_span = info_span!("", "{}", view_light.pass_name).entered(); - // There's no need for a late shadow pass if the light isn't - // using occlusion culling. - if is_late && !occlusion_culling { - continue; - } + let depth_stencil_attachment = + Some(view_light.depth_attachment.get_attachment(StoreOp::Store)); - let Some(shadow_phase) = - shadow_render_phases.get(&extracted_light_view.retained_view_entity) - else { - continue; - }; - - let depth_stencil_attachment = - Some(view_light.depth_attachment.get_attachment(StoreOp::Store)); - - let diagnostics = render_context.diagnostic_recorder(); - render_context.add_command_buffer_generation_task(move |render_device| { - #[cfg(feature = "trace")] - let _shadow_pass_span = info_span!("", "{}", view_light.pass_name).entered(); - let mut command_encoder = - render_device.create_command_encoder(&CommandEncoderDescriptor { - label: Some("shadow_pass_command_encoder"), - }); - - let render_pass = command_encoder.begin_render_pass(&RenderPassDescriptor { - label: Some(&view_light.pass_name), - color_attachments: &[], - depth_stencil_attachment, - timestamp_writes: None, - occlusion_query_set: None, - }); - - let mut render_pass = TrackedRenderPass::new(&render_device, render_pass); - let pass_span = - diagnostics.pass_span(&mut render_pass, view_light.pass_name.clone()); - - if let Err(err) = - shadow_phase.render(&mut render_pass, world, view_light_entity) - { - error!("Error encountered while rendering the shadow phase {err:?}"); - } + let mut render_pass = ctx.begin_tracked_render_pass(RenderPassDescriptor { + label: Some(&view_light.pass_name), + color_attachments: &[], + depth_stencil_attachment, + timestamp_writes: None, + occlusion_query_set: None, + }); - pass_span.end(&mut render_pass); - drop(render_pass); - command_encoder.finish() - }); - } + if let Err(err) = shadow_phase.render(&mut render_pass, world, view_light_entity) { + error!("Error encountered while rendering the shadow phase {err:?}"); } - - Ok(()) } } diff --git a/crates/bevy_pbr/src/ssao/mod.rs b/crates/bevy_pbr/src/ssao/mod.rs index 81aa5aa4aed65..d23793d58371c 100644 --- a/crates/bevy_pbr/src/ssao/mod.rs +++ b/crates/bevy_pbr/src/ssao/mod.rs @@ -1,14 +1,13 @@ -use crate::NodePbr; use bevy_app::{App, Plugin}; use bevy_asset::{embedded_asset, load_embedded_asset, Handle}; use bevy_camera::{Camera, Camera3d}; use bevy_core_pipeline::{ - core_3d::graph::{Core3d, Node3d}, prepass::{DepthPrepass, NormalPrepass, ViewPrepassTextures}, + schedule::{Core3d, Core3dSystems}, }; use bevy_ecs::{ prelude::{Component, Entity}, - query::{Has, QueryItem, With}, + query::{Has, With}, reflect::ReflectComponent, resource::Resource, schedule::IntoScheduleConfigs, @@ -22,14 +21,13 @@ use bevy_render::{ diagnostic::RecordDiagnostics, extract_component::ExtractComponent, globals::{GlobalsBuffer, GlobalsUniform}, - render_graph::{NodeRunError, RenderGraphContext, RenderGraphExt, ViewNode, ViewNodeRunner}, render_resource::{ binding_types::{ sampler, texture_2d, texture_depth_2d, texture_storage_2d, uniform_buffer, }, *, }, - renderer::{RenderAdapter, RenderContext, RenderDevice, RenderQueue}, + renderer::{RenderAdapter, RenderContext, RenderDevice, RenderQueue, ViewQuery}, sync_component::SyncComponentPlugin, sync_world::RenderEntity, texture::{CachedTexture, TextureCache}, @@ -82,20 +80,13 @@ impl Plugin for ScreenSpaceAmbientOcclusionPlugin { prepare_ssao_textures.in_set(RenderSystems::PrepareResources), prepare_ssao_bind_groups.in_set(RenderSystems::PrepareBindGroups), ), - ) - .add_render_graph_node::>( - Core3d, - NodePbr::ScreenSpaceAmbientOcclusion, - ) - .add_render_graph_edges( - Core3d, - ( - // END_PRE_PASSES -> SCREEN_SPACE_AMBIENT_OCCLUSION -> MAIN_PASS - Node3d::EndPrepasses, - NodePbr::ScreenSpaceAmbientOcclusion, - Node3d::StartMainPass, - ), ); + + render_app.add_systems( + Core3d, + ssao.after(Core3dSystems::EndPrepasses) + .before(Core3dSystems::StartMainPass), + ); } } @@ -171,106 +162,98 @@ impl ScreenSpaceAmbientOcclusionQualityLevel { } } -#[derive(Default)] -struct SsaoNode {} - -impl ViewNode for SsaoNode { - type ViewQuery = ( - &'static ExtractedCamera, - &'static SsaoPipelineId, - &'static SsaoBindGroups, - &'static ViewUniformOffset, - ); - - fn run( - &self, - _graph: &mut RenderGraphContext, - render_context: &mut RenderContext, - (camera, pipeline_id, bind_groups, view_uniform_offset): QueryItem, - world: &World, - ) -> Result<(), NodeRunError> { - let pipelines = world.resource::(); - let pipeline_cache = world.resource::(); - let ( - Some(camera_size), - Some(preprocess_depth_pipeline), - Some(spatial_denoise_pipeline), - Some(ssao_pipeline), - ) = ( - camera.physical_viewport_size, - pipeline_cache.get_compute_pipeline(pipelines.preprocess_depth_pipeline), - pipeline_cache.get_compute_pipeline(pipelines.spatial_denoise_pipeline), - pipeline_cache.get_compute_pipeline(pipeline_id.0), - ) - else { - return Ok(()); - }; - - let diagnostics = render_context.diagnostic_recorder(); +fn ssao( + view: ViewQuery<( + &ExtractedCamera, + &SsaoPipelineId, + &SsaoBindGroups, + &ViewUniformOffset, + )>, + pipelines: Res, + pipeline_cache: Res, + mut ctx: RenderContext, +) { + let (camera, pipeline_id, bind_groups, view_uniform_offset) = view.into_inner(); + + let ( + Some(camera_size), + Some(preprocess_depth_pipeline), + Some(spatial_denoise_pipeline), + Some(ssao_pipeline), + ) = ( + camera.physical_viewport_size, + pipeline_cache.get_compute_pipeline(pipelines.preprocess_depth_pipeline), + pipeline_cache.get_compute_pipeline(pipelines.spatial_denoise_pipeline), + pipeline_cache.get_compute_pipeline(pipeline_id.0), + ) + else { + return; + }; - let command_encoder = render_context.command_encoder(); - command_encoder.push_debug_group("ssao"); - let time_span = diagnostics.time_span(command_encoder, "ssao"); + let diagnostics = ctx.diagnostic_recorder(); + let diagnostics = diagnostics.as_deref(); + let time_span = diagnostics.time_span(ctx.command_encoder(), "ssao"); - { - let mut preprocess_depth_pass = - command_encoder.begin_compute_pass(&ComputePassDescriptor { - label: Some("ssao_preprocess_depth"), - timestamp_writes: None, - }); - preprocess_depth_pass.set_pipeline(preprocess_depth_pipeline); - preprocess_depth_pass.set_bind_group(0, &bind_groups.preprocess_depth_bind_group, &[]); - preprocess_depth_pass.set_bind_group( - 1, - &bind_groups.common_bind_group, - &[view_uniform_offset.offset], - ); - preprocess_depth_pass.dispatch_workgroups( - camera_size.x.div_ceil(16), - camera_size.y.div_ceil(16), - 1, - ); - } + let command_encoder = ctx.command_encoder(); + command_encoder.push_debug_group("ssao"); - { - let mut ssao_pass = command_encoder.begin_compute_pass(&ComputePassDescriptor { - label: Some("ssao"), + { + let mut preprocess_depth_pass = + command_encoder.begin_compute_pass(&ComputePassDescriptor { + label: Some("ssao_preprocess_depth"), timestamp_writes: None, }); - ssao_pass.set_pipeline(ssao_pipeline); - ssao_pass.set_bind_group(0, &bind_groups.ssao_bind_group, &[]); - ssao_pass.set_bind_group( - 1, - &bind_groups.common_bind_group, - &[view_uniform_offset.offset], - ); - ssao_pass.dispatch_workgroups(camera_size.x.div_ceil(8), camera_size.y.div_ceil(8), 1); - } + preprocess_depth_pass.set_pipeline(preprocess_depth_pipeline); + preprocess_depth_pass.set_bind_group(0, &bind_groups.preprocess_depth_bind_group, &[]); + preprocess_depth_pass.set_bind_group( + 1, + &bind_groups.common_bind_group, + &[view_uniform_offset.offset], + ); + preprocess_depth_pass.dispatch_workgroups( + camera_size.x.div_ceil(16), + camera_size.y.div_ceil(16), + 1, + ); + } - { - let mut spatial_denoise_pass = - command_encoder.begin_compute_pass(&ComputePassDescriptor { - label: Some("ssao_spatial_denoise"), - timestamp_writes: None, - }); - spatial_denoise_pass.set_pipeline(spatial_denoise_pipeline); - spatial_denoise_pass.set_bind_group(0, &bind_groups.spatial_denoise_bind_group, &[]); - spatial_denoise_pass.set_bind_group( - 1, - &bind_groups.common_bind_group, - &[view_uniform_offset.offset], - ); - spatial_denoise_pass.dispatch_workgroups( - camera_size.x.div_ceil(8), - camera_size.y.div_ceil(8), - 1, - ); - } + { + let mut ssao_pass = command_encoder.begin_compute_pass(&ComputePassDescriptor { + label: Some("ssao"), + timestamp_writes: None, + }); + ssao_pass.set_pipeline(ssao_pipeline); + ssao_pass.set_bind_group(0, &bind_groups.ssao_bind_group, &[]); + ssao_pass.set_bind_group( + 1, + &bind_groups.common_bind_group, + &[view_uniform_offset.offset], + ); + ssao_pass.dispatch_workgroups(camera_size.x.div_ceil(8), camera_size.y.div_ceil(8), 1); + } - time_span.end(command_encoder); - command_encoder.pop_debug_group(); - Ok(()) + { + let mut spatial_denoise_pass = + command_encoder.begin_compute_pass(&ComputePassDescriptor { + label: Some("ssao_spatial_denoise"), + timestamp_writes: None, + }); + spatial_denoise_pass.set_pipeline(spatial_denoise_pipeline); + spatial_denoise_pass.set_bind_group(0, &bind_groups.spatial_denoise_bind_group, &[]); + spatial_denoise_pass.set_bind_group( + 1, + &bind_groups.common_bind_group, + &[view_uniform_offset.offset], + ); + spatial_denoise_pass.dispatch_workgroups( + camera_size.x.div_ceil(8), + camera_size.y.div_ceil(8), + 1, + ); } + + command_encoder.pop_debug_group(); + time_span.end(ctx.command_encoder()); } #[derive(Resource)] diff --git a/crates/bevy_pbr/src/ssr/mod.rs b/crates/bevy_pbr/src/ssr/mod.rs index 40b84f3d733f7..dc32b03ee3838 100644 --- a/crates/bevy_pbr/src/ssr/mod.rs +++ b/crates/bevy_pbr/src/ssr/mod.rs @@ -3,11 +3,9 @@ use bevy_app::{App, Plugin}; use bevy_asset::{load_embedded_asset, AssetServer, Handle}; use bevy_core_pipeline::{ - core_3d::{ - graph::{Core3d, Node3d}, - DEPTH_TEXTURE_SAMPLING_SUPPORTED, - }, + core_3d::{main_opaque_pass_3d, DEPTH_TEXTURE_SAMPLING_SUPPORTED}, prepass::{DeferredPrepass, DepthPrepass, MotionVectorPrepass, NormalPrepass}, + schedule::Core3d, FullscreenShader, }; use bevy_derive::{Deref, DerefMut}; @@ -19,7 +17,6 @@ use bevy_ecs::{ resource::Resource, schedule::IntoScheduleConfigs as _, system::{lifetimeless::Read, Commands, Query, Res, ResMut}, - world::World, }; use bevy_image::BevyDefault as _; use bevy_light::EnvironmentMapLight; @@ -27,9 +24,6 @@ use bevy_reflect::{std_traits::ReflectDefault, Reflect}; use bevy_render::{ diagnostic::RecordDiagnostics, extract_component::{ExtractComponent, ExtractComponentPlugin}, - render_graph::{ - NodeRunError, RenderGraph, RenderGraphContext, RenderGraphExt, ViewNode, ViewNodeRunner, - }, render_resource::{ binding_types, AddressMode, BindGroupEntries, BindGroupLayoutDescriptor, BindGroupLayoutEntries, CachedRenderPipelineId, ColorTargetState, ColorWrites, @@ -38,7 +32,7 @@ use bevy_render::{ SamplerBindingType, SamplerDescriptor, ShaderStages, ShaderType, SpecializedRenderPipeline, SpecializedRenderPipelines, TextureFormat, TextureSampleType, }, - renderer::{RenderAdapter, RenderContext, RenderDevice, RenderQueue}, + renderer::{RenderAdapter, RenderContext, RenderDevice, RenderQueue, ViewQuery}, view::{ExtractedView, Msaa, ViewTarget, ViewUniformOffset}, Render, RenderApp, RenderStartup, RenderSystems, }; @@ -47,8 +41,8 @@ use bevy_utils::{once, prelude::default}; use tracing::info; use crate::{ - binding_arrays_are_usable, graph::NodePbr, ExtractedAtmosphere, MeshPipelineViewLayoutKey, - MeshPipelineViewLayouts, MeshViewBindGroup, RenderViewLightProbes, + binding_arrays_are_usable, deferred::deferred_lighting, ExtractedAtmosphere, + MeshPipelineViewLayoutKey, MeshPipelineViewLayouts, MeshViewBindGroup, RenderViewLightProbes, ViewEnvironmentMapUniformOffset, ViewFogUniformOffset, ViewLightProbesUniformOffset, ViewLightsUniformOffset, }; @@ -196,42 +190,21 @@ impl Plugin for ScreenSpaceReflectionsPlugin { render_app .init_resource::() .init_resource::>() - .add_systems( - RenderStartup, - ( - init_screen_space_reflections_pipeline, - add_screen_space_reflections_render_graph_edges, - ), - ) + .add_systems(RenderStartup, init_screen_space_reflections_pipeline) .add_systems(Render, prepare_ssr_pipelines.in_set(RenderSystems::Prepare)) .add_systems( Render, prepare_ssr_settings.in_set(RenderSystems::PrepareResources), ) - // Note: we add this node here but then we add edges in - // `add_screen_space_reflections_render_graph_edges`. - .add_render_graph_node::>( + .add_systems( Core3d, - NodePbr::ScreenSpaceReflections, + screen_space_reflections + .after(deferred_lighting) + .before(main_opaque_pass_3d), ); } } -fn add_screen_space_reflections_render_graph_edges(mut render_graph: ResMut) { - let subgraph = render_graph.sub_graph_mut(Core3d); - - subgraph.add_node_edge(NodePbr::ScreenSpaceReflections, Node3d::MainOpaquePass); - - if subgraph - .get_node_state(NodePbr::DeferredLightingPass) - .is_ok() - { - subgraph.add_node_edge( - NodePbr::DeferredLightingPass, - NodePbr::ScreenSpaceReflections, - ); - } -} impl Default for ScreenSpaceReflections { // Reasonable default values. @@ -250,99 +223,93 @@ impl Default for ScreenSpaceReflections { } } -impl ViewNode for ScreenSpaceReflectionsNode { - type ViewQuery = ( - Read, - Read, - Read, - Read, - Read, - Read, - Read, - Read, - Read, - ); - - fn run<'w>( - &self, - _: &mut RenderGraphContext, - render_context: &mut RenderContext<'w>, - ( - view_target, - view_uniform_offset, - view_lights_offset, - view_fog_offset, - view_light_probes_offset, - view_ssr_offset, - view_environment_map_offset, - view_bind_group, - ssr_pipeline_id, - ): QueryItem<'w, '_, Self::ViewQuery>, - world: &'w World, - ) -> Result<(), NodeRunError> { - // Grab the render pipeline. - let pipeline_cache = world.resource::(); - let Some(render_pipeline) = pipeline_cache.get_render_pipeline(**ssr_pipeline_id) else { - return Ok(()); - }; - - let diagnostics = render_context.diagnostic_recorder(); - - // Set up a standard pair of postprocessing textures. - let postprocess = view_target.post_process_write(); - - // Create the bind group for this view. - let ssr_pipeline = world.resource::(); - let ssr_bind_group = render_context.render_device().create_bind_group( - "SSR bind group", - &pipeline_cache.get_bind_group_layout(&ssr_pipeline.bind_group_layout), - &BindGroupEntries::sequential(( - postprocess.source, - &ssr_pipeline.color_sampler, - &ssr_pipeline.depth_linear_sampler, - &ssr_pipeline.depth_nearest_sampler, - )), - ); +pub fn screen_space_reflections( + view: ViewQuery<( + &ViewTarget, + &ViewUniformOffset, + &ViewLightsUniformOffset, + &ViewFogUniformOffset, + &ViewLightProbesUniformOffset, + &ViewScreenSpaceReflectionsUniformOffset, + &ViewEnvironmentMapUniformOffset, + &MeshViewBindGroup, + &ScreenSpaceReflectionsPipelineId, + )>, + pipeline_cache: Res, + ssr_pipeline: Res, + mut ctx: RenderContext, +) { + let ( + view_target, + view_uniform_offset, + view_lights_offset, + view_fog_offset, + view_light_probes_offset, + view_ssr_offset, + view_environment_map_offset, + view_bind_group, + ssr_pipeline_id, + ) = view.into_inner(); + + // Grab the render pipeline. + let Some(render_pipeline) = pipeline_cache.get_render_pipeline(**ssr_pipeline_id) else { + return; + }; - // Build the SSR render pass. - let mut render_pass = render_context.begin_tracked_render_pass(RenderPassDescriptor { - label: Some("ssr"), - color_attachments: &[Some(RenderPassColorAttachment { - view: postprocess.destination, - depth_slice: None, - resolve_target: None, - ops: Operations::default(), - })], - depth_stencil_attachment: None, - timestamp_writes: None, - occlusion_query_set: None, - }); - let pass_span = diagnostics.pass_span(&mut render_pass, "ssr"); - - // Set bind groups. - render_pass.set_render_pipeline(render_pipeline); - render_pass.set_bind_group( - 0, - &view_bind_group.main, - &[ - view_uniform_offset.offset, - view_lights_offset.offset, - view_fog_offset.offset, - **view_light_probes_offset, - **view_ssr_offset, - **view_environment_map_offset, - ], - ); - render_pass.set_bind_group(1, &view_bind_group.binding_array, &[]); + // Set up a standard pair of postprocessing textures. + let postprocess = view_target.post_process_write(); + + // Create the bind group for this view. + let ssr_bind_group = ctx.render_device().create_bind_group( + "SSR bind group", + &pipeline_cache.get_bind_group_layout(&ssr_pipeline.bind_group_layout), + &BindGroupEntries::sequential(( + postprocess.source, + &ssr_pipeline.color_sampler, + &ssr_pipeline.depth_linear_sampler, + &ssr_pipeline.depth_nearest_sampler, + )), + ); - // Perform the SSR render pass. - render_pass.set_bind_group(2, &ssr_bind_group, &[]); - render_pass.draw(0..3, 0..1); + let diagnostics = ctx.diagnostic_recorder(); + let diagnostics = diagnostics.as_deref(); + + // Build the SSR render pass. + let mut render_pass = ctx.begin_tracked_render_pass(RenderPassDescriptor { + label: Some("ssr"), + color_attachments: &[Some(RenderPassColorAttachment { + view: postprocess.destination, + depth_slice: None, + resolve_target: None, + ops: Operations::default(), + })], + depth_stencil_attachment: None, + timestamp_writes: None, + occlusion_query_set: None, + }); + let pass_span = diagnostics.pass_span(&mut render_pass, "ssr"); + + // Set bind groups. + render_pass.set_render_pipeline(render_pipeline); + render_pass.set_bind_group( + 0, + &view_bind_group.main, + &[ + view_uniform_offset.offset, + view_lights_offset.offset, + view_fog_offset.offset, + **view_light_probes_offset, + **view_ssr_offset, + **view_environment_map_offset, + ], + ); + render_pass.set_bind_group(1, &view_bind_group.binding_array, &[]); - pass_span.end(&mut render_pass); + // Perform the SSR render pass. + render_pass.set_bind_group(2, &ssr_bind_group, &[]); + render_pass.draw(0..3, 0..1); - Ok(()) - } + pass_span.end(&mut render_pass); } pub fn init_screen_space_reflections_pipeline( diff --git a/crates/bevy_pbr/src/volumetric_fog/mod.rs b/crates/bevy_pbr/src/volumetric_fog/mod.rs index 57cbd7bb1722c..4146a2f1c7008 100644 --- a/crates/bevy_pbr/src/volumetric_fog/mod.rs +++ b/crates/bevy_pbr/src/volumetric_fog/mod.rs @@ -31,9 +31,9 @@ use bevy_app::{App, Plugin}; use bevy_asset::{embedded_asset, Assets, Handle}; -use bevy_core_pipeline::core_3d::{ - graph::{Core3d, Node3d}, - prepare_core_3d_depth_textures, +use bevy_core_pipeline::{ + core_3d::prepare_core_3d_depth_textures, + schedule::{Core3d, Core3dSystems}, }; use bevy_ecs::{resource::Resource, schedule::IntoScheduleConfigs as _}; use bevy_light::FogVolume; @@ -43,14 +43,13 @@ use bevy_math::{ }; use bevy_mesh::{Mesh, Meshable}; use bevy_render::{ - render_graph::{RenderGraphExt, ViewNodeRunner}, render_resource::SpecializedRenderPipelines, sync_component::SyncComponentPlugin, ExtractSchedule, Render, RenderApp, RenderStartup, RenderSystems, }; -use render::{VolumetricFogNode, VolumetricFogPipeline, VolumetricFogUniformBuffer}; +use render::{volumetric_fog, VolumetricFogPipeline, VolumetricFogUniformBuffer}; -use crate::{graph::NodePbr, volumetric_fog::render::init_volumetric_fog_pipeline}; +use crate::volumetric_fog::render::init_volumetric_fog_pipeline; pub mod render; @@ -96,19 +95,11 @@ impl Plugin for VolumetricFogPlugin { .before(prepare_core_3d_depth_textures), ), ) - .add_render_graph_node::>( - Core3d, - NodePbr::VolumetricFog, - ) - .add_render_graph_edges( + .add_systems( Core3d, - // Volumetric fog should run after the main pass but before bloom, so - // we order if at the start of post processing. - ( - Node3d::EndMainPass, - NodePbr::VolumetricFog, - Node3d::StartMainPassPostProcessing, - ), + volumetric_fog + .after(Core3dSystems::EndMainPass) + .before(Core3dSystems::StartMainPassPostProcessing), ); } } diff --git a/crates/bevy_pbr/src/volumetric_fog/render.rs b/crates/bevy_pbr/src/volumetric_fog/render.rs index e0ddc3ccefe74..e4bba273d2c0f 100644 --- a/crates/bevy_pbr/src/volumetric_fog/render.rs +++ b/crates/bevy_pbr/src/volumetric_fog/render.rs @@ -12,20 +12,17 @@ use bevy_derive::{Deref, DerefMut}; use bevy_ecs::{ component::Component, entity::Entity, - query::{Has, QueryItem, With}, + query::{Has, With}, resource::Resource, - system::{lifetimeless::Read, Commands, Local, Query, Res, ResMut}, - world::World, + system::{Commands, Local, Query, Res, ResMut}, }; use bevy_image::{BevyDefault, Image}; use bevy_light::{FogVolume, VolumetricFog, VolumetricLight}; use bevy_math::{vec4, Affine3A, Mat4, Vec3, Vec3A, Vec4}; use bevy_mesh::{Mesh, MeshVertexBufferLayoutRef}; use bevy_render::{ - diagnostic::RecordDiagnostics, mesh::{allocator::MeshAllocator, RenderMesh, RenderMeshBufferInfo}, render_asset::RenderAssets, - render_graph::{NodeRunError, RenderGraphContext, ViewNode}, render_resource::{ binding_types::{ sampler, texture_3d, texture_depth_2d, texture_depth_2d_multisampled, uniform_buffer, @@ -38,7 +35,7 @@ use bevy_render::{ SpecializedRenderPipeline, SpecializedRenderPipelines, StoreOp, TextureFormat, TextureSampleType, TextureUsages, VertexState, }, - renderer::{RenderContext, RenderDevice, RenderQueue}, + renderer::{RenderContext, RenderDevice, RenderQueue, ViewQuery}, sync_world::RenderEntity, texture::GpuImage, view::{ExtractedView, Msaa, ViewDepthTexture, ViewTarget, ViewUniformOffset}, @@ -123,10 +120,6 @@ pub struct ViewVolumetricFogPipelines { pub textured: CachedRenderPipelineId, } -/// The node in the render graph, part of the postprocessing stack, that -/// implements volumetric fog. -#[derive(Default)] -pub struct VolumetricFogNode; /// Identifies a single specialization of the volumetric fog shader. #[derive(PartialEq, Eq, Hash, Clone)] @@ -303,209 +296,191 @@ pub fn extract_volumetric_fog( } } -impl ViewNode for VolumetricFogNode { - type ViewQuery = ( - Read, - Read, - Read, - Read, - Read, - Read, - Read, - Read, - Read, - Read, - Read, - Read, - ); +pub fn volumetric_fog( + view: ViewQuery<( + &ViewTarget, + &ViewDepthTexture, + &ViewVolumetricFogPipelines, + &ViewUniformOffset, + &ViewLightsUniformOffset, + &ViewFogUniformOffset, + &ViewLightProbesUniformOffset, + &ViewVolumetricFog, + &MeshViewBindGroup, + &ViewScreenSpaceReflectionsUniformOffset, + &Msaa, + &ViewEnvironmentMapUniformOffset, + )>, + pipeline_cache: Res, + volumetric_lighting_pipeline: Res, + volumetric_lighting_uniform_buffers: Res, + image_assets: Res>, + mesh_allocator: Res, + fog_assets: Res, + render_meshes: Res>, + mut ctx: RenderContext, +) { + let ( + view_target, + view_depth_texture, + view_volumetric_lighting_pipelines, + view_uniform_offset, + view_lights_offset, + view_fog_offset, + view_light_probes_offset, + view_fog_volumes, + view_bind_group, + view_ssr_offset, + msaa, + view_environment_map_offset, + ) = view.into_inner(); + + // Fetch the uniform buffer and binding. + let ( + Some(textureless_pipeline), + Some(textured_pipeline), + Some(volumetric_lighting_uniform_buffer_binding), + ) = ( + pipeline_cache.get_render_pipeline(view_volumetric_lighting_pipelines.textureless), + pipeline_cache.get_render_pipeline(view_volumetric_lighting_pipelines.textured), + volumetric_lighting_uniform_buffers.binding(), + ) + else { + return; + }; - fn run<'w>( - &self, - _: &mut RenderGraphContext, - render_context: &mut RenderContext<'w>, - ( - view_target, - view_depth_texture, - view_volumetric_lighting_pipelines, - view_uniform_offset, - view_lights_offset, - view_fog_offset, - view_light_probes_offset, - view_fog_volumes, - view_bind_group, - view_ssr_offset, - msaa, - view_environment_map_offset, - ): QueryItem<'w, '_, Self::ViewQuery>, - world: &'w World, - ) -> Result<(), NodeRunError> { - let pipeline_cache = world.resource::(); - let volumetric_lighting_pipeline = world.resource::(); - let volumetric_lighting_uniform_buffers = world.resource::(); - let image_assets = world.resource::>(); - let mesh_allocator = world.resource::(); - - // Fetch the uniform buffer and binding. - let ( - Some(textureless_pipeline), - Some(textured_pipeline), - Some(volumetric_lighting_uniform_buffer_binding), - ) = ( - pipeline_cache.get_render_pipeline(view_volumetric_lighting_pipelines.textureless), - pipeline_cache.get_render_pipeline(view_volumetric_lighting_pipelines.textured), - volumetric_lighting_uniform_buffers.binding(), - ) - else { - return Ok(()); + let command_encoder = ctx.command_encoder(); + command_encoder.push_debug_group("volumetric_lighting"); + + for view_fog_volume in view_fog_volumes.iter() { + // If the camera is outside the fog volume, pick the cube mesh; + // otherwise, pick the plane mesh. In the latter case we'll be + // effectively rendering a full-screen quad. + let mesh_handle = if view_fog_volume.exterior { + fog_assets.cube_mesh.clone() + } else { + fog_assets.plane_mesh.clone() }; - let diagnostics = render_context.diagnostic_recorder(); - render_context - .command_encoder() - .push_debug_group("volumetric_lighting"); - let time_span = - diagnostics.time_span(render_context.command_encoder(), "volumetric_lighting"); - - let fog_assets = world.resource::(); - let render_meshes = world.resource::>(); - - for view_fog_volume in view_fog_volumes.iter() { - // If the camera is outside the fog volume, pick the cube mesh; - // otherwise, pick the plane mesh. In the latter case we'll be - // effectively rendering a full-screen quad. - let mesh_handle = if view_fog_volume.exterior { - fog_assets.cube_mesh.clone() - } else { - fog_assets.plane_mesh.clone() - }; - - let Some(vertex_buffer_slice) = mesh_allocator.mesh_vertex_slice(&mesh_handle.id()) - else { - continue; - }; - - let density_image = view_fog_volume - .density_texture - .and_then(|density_texture| image_assets.get(density_texture)); - - // Pick the right pipeline, depending on whether a density texture - // is present or not. - let pipeline = if density_image.is_some() { - textured_pipeline - } else { - textureless_pipeline - }; - - // This should always succeed, but if the asset was unloaded don't - // panic. - let Some(render_mesh) = render_meshes.get(&mesh_handle) else { - return Ok(()); - }; - - // Create the bind group for the view. - // - // TODO: Cache this. - - let mut bind_group_layout_key = VolumetricFogBindGroupLayoutKey::empty(); - bind_group_layout_key.set( - VolumetricFogBindGroupLayoutKey::MULTISAMPLED, - !matches!(*msaa, Msaa::Off), - ); + let Some(vertex_buffer_slice) = mesh_allocator.mesh_vertex_slice(&mesh_handle.id()) else { + continue; + }; - // Create the bind group entries. The ones relating to the density - // texture will only be filled in if that texture is present. - let mut bind_group_entries = DynamicBindGroupEntries::sequential(( - volumetric_lighting_uniform_buffer_binding.clone(), - BindingResource::TextureView(view_depth_texture.view()), - )); - if let Some(density_image) = density_image { - bind_group_layout_key.insert(VolumetricFogBindGroupLayoutKey::DENSITY_TEXTURE); - bind_group_entries = bind_group_entries.extend_sequential(( - BindingResource::TextureView(&density_image.texture_view), - BindingResource::Sampler(&density_image.sampler), - )); - } + let density_image = view_fog_volume + .density_texture + .and_then(|density_texture| image_assets.get(density_texture)); - let volumetric_view_bind_group_layout = &volumetric_lighting_pipeline - .volumetric_view_bind_group_layouts[bind_group_layout_key.bits() as usize]; + // Pick the right pipeline, depending on whether a density texture + // is present or not. + let pipeline = if density_image.is_some() { + textured_pipeline + } else { + textureless_pipeline + }; - let volumetric_view_bind_group = render_context.render_device().create_bind_group( - None, - &pipeline_cache.get_bind_group_layout(volumetric_view_bind_group_layout), - &bind_group_entries, - ); + // This should always succeed, but if the asset was unloaded don't + // panic. + let Some(render_mesh) = render_meshes.get(&mesh_handle) else { + return; + }; - let render_pass_descriptor = RenderPassDescriptor { - label: Some("volumetric lighting pass"), - color_attachments: &[Some(RenderPassColorAttachment { - view: view_target.main_texture_view(), - depth_slice: None, - resolve_target: None, - ops: Operations { - load: LoadOp::Load, - store: StoreOp::Store, - }, - })], - depth_stencil_attachment: None, - timestamp_writes: None, - occlusion_query_set: None, - }; - - let mut render_pass = render_context - .command_encoder() - .begin_render_pass(&render_pass_descriptor); - - render_pass.set_vertex_buffer(0, *vertex_buffer_slice.buffer.slice(..)); - render_pass.set_pipeline(pipeline); - render_pass.set_bind_group( - 0, - &view_bind_group.main, - &[ - view_uniform_offset.offset, - view_lights_offset.offset, - view_fog_offset.offset, - **view_light_probes_offset, - **view_ssr_offset, - **view_environment_map_offset, - ], - ); - render_pass.set_bind_group( - 1, - &volumetric_view_bind_group, - &[view_fog_volume.uniform_buffer_offset], - ); + // Create the bind group for the view. + // + // TODO: Cache this. - // Draw elements or arrays, as appropriate. - match &render_mesh.buffer_info { - RenderMeshBufferInfo::Indexed { - index_format, - count, - } => { - let Some(index_buffer_slice) = - mesh_allocator.mesh_index_slice(&mesh_handle.id()) - else { - continue; - }; - - render_pass - .set_index_buffer(*index_buffer_slice.buffer.slice(..), *index_format); - render_pass.draw_indexed( - index_buffer_slice.range.start..(index_buffer_slice.range.start + count), - vertex_buffer_slice.range.start as i32, - 0..1, - ); - } - RenderMeshBufferInfo::NonIndexed => { - render_pass.draw(vertex_buffer_slice.range, 0..1); - } - } + let mut bind_group_layout_key = VolumetricFogBindGroupLayoutKey::empty(); + bind_group_layout_key.set( + VolumetricFogBindGroupLayoutKey::MULTISAMPLED, + !matches!(*msaa, Msaa::Off), + ); + + // Create the bind group entries. The ones relating to the density + // texture will only be filled in if that texture is present. + let mut bind_group_entries = DynamicBindGroupEntries::sequential(( + volumetric_lighting_uniform_buffer_binding.clone(), + BindingResource::TextureView(view_depth_texture.view()), + )); + if let Some(density_image) = density_image { + bind_group_layout_key.insert(VolumetricFogBindGroupLayoutKey::DENSITY_TEXTURE); + bind_group_entries = bind_group_entries.extend_sequential(( + BindingResource::TextureView(&density_image.texture_view), + BindingResource::Sampler(&density_image.sampler), + )); } - time_span.end(render_context.command_encoder()); - render_context.command_encoder().pop_debug_group(); + let volumetric_view_bind_group_layout = &volumetric_lighting_pipeline + .volumetric_view_bind_group_layouts[bind_group_layout_key.bits() as usize]; + + let volumetric_view_bind_group = ctx.render_device().create_bind_group( + None, + &pipeline_cache.get_bind_group_layout(volumetric_view_bind_group_layout), + &bind_group_entries, + ); + + let render_pass_descriptor = RenderPassDescriptor { + label: Some("volumetric lighting pass"), + color_attachments: &[Some(RenderPassColorAttachment { + view: view_target.main_texture_view(), + depth_slice: None, + resolve_target: None, + ops: Operations { + load: LoadOp::Load, + store: StoreOp::Store, + }, + })], + depth_stencil_attachment: None, + timestamp_writes: None, + occlusion_query_set: None, + }; - Ok(()) + let command_encoder = ctx.command_encoder(); + let mut render_pass = command_encoder.begin_render_pass(&render_pass_descriptor); + + render_pass.set_vertex_buffer(0, *vertex_buffer_slice.buffer.slice(..)); + render_pass.set_pipeline(pipeline); + render_pass.set_bind_group( + 0, + &view_bind_group.main, + &[ + view_uniform_offset.offset, + view_lights_offset.offset, + view_fog_offset.offset, + **view_light_probes_offset, + **view_ssr_offset, + **view_environment_map_offset, + ], + ); + render_pass.set_bind_group( + 1, + &volumetric_view_bind_group, + &[view_fog_volume.uniform_buffer_offset], + ); + + // Draw elements or arrays, as appropriate. + match &render_mesh.buffer_info { + RenderMeshBufferInfo::Indexed { + index_format, + count, + } => { + let Some(index_buffer_slice) = mesh_allocator.mesh_index_slice(&mesh_handle.id()) + else { + continue; + }; + + render_pass.set_index_buffer(*index_buffer_slice.buffer.slice(..), *index_format); + render_pass.draw_indexed( + index_buffer_slice.range.start..(index_buffer_slice.range.start + count), + vertex_buffer_slice.range.start as i32, + 0..1, + ); + } + RenderMeshBufferInfo::NonIndexed => { + render_pass.draw(vertex_buffer_slice.range, 0..1); + } + } } + + ctx.command_encoder().pop_debug_group(); } impl SpecializedRenderPipeline for VolumetricFogPipeline { diff --git a/crates/bevy_pbr/src/wireframe.rs b/crates/bevy_pbr/src/wireframe.rs index c4b19df63f09f..a184531109fd3 100644 --- a/crates/bevy_pbr/src/wireframe.rs +++ b/crates/bevy_pbr/src/wireframe.rs @@ -10,12 +10,11 @@ use bevy_asset::{ }; use bevy_camera::{visibility::ViewVisibility, Camera, Camera3d}; use bevy_color::{Color, ColorToComponents}; -use bevy_core_pipeline::core_3d::graph::{Core3d, Node3d}; +use bevy_core_pipeline::schedule::{Core3d, Core3dSystems}; use bevy_derive::{Deref, DerefMut}; use bevy_ecs::{ change_detection::Tick, prelude::*, - query::QueryItem, system::{lifetimeless::SRes, SystemChangeTick, SystemParamItem}, }; use bevy_mesh::{Mesh3d, MeshVertexBufferLayoutRef}; @@ -27,7 +26,6 @@ use bevy_reflect::{std_traits::ReflectDefault, Reflect}; use bevy_render::{ batching::gpu_preprocessing::{GpuPreprocessingMode, GpuPreprocessingSupport}, camera::{extract_cameras, ExtractedCamera}, - diagnostic::RecordDiagnostics, extract_resource::ExtractResource, mesh::{ allocator::{MeshAllocator, SlabId}, @@ -37,7 +35,6 @@ use bevy_render::{ render_asset::{ prepare_assets, PrepareAssetError, RenderAsset, RenderAssetPlugin, RenderAssets, }, - render_graph::{NodeRunError, RenderGraphContext, RenderGraphExt, ViewNode, ViewNodeRunner}, render_phase::{ AddRenderCommand, BinnedPhaseItem, BinnedRenderPhasePlugin, BinnedRenderPhaseType, CachedRenderPipelinePhaseItem, DrawFunctionId, DrawFunctions, PhaseItem, @@ -45,7 +42,7 @@ use bevy_render::{ SetItemPipeline, TrackedRenderPass, ViewBinnedRenderPhases, }, render_resource::*, - renderer::{RenderContext, RenderDevice}, + renderer::{RenderContext, RenderDevice, ViewQuery}, sync_world::{MainEntity, MainEntityHashMap}, view::{ ExtractedView, NoIndirectDrawing, RenderVisibilityRanges, RenderVisibleEntities, @@ -132,16 +129,13 @@ impl Plugin for WireframePlugin { .add_render_command::() .init_resource::() .init_resource::>() - .add_render_graph_node::>(Core3d, Node3d::Wireframe) - .add_render_graph_edges( + .add_systems(RenderStartup, init_wireframe_3d_pipeline) + .add_systems( Core3d, - ( - Node3d::EndMainPass, - Node3d::Wireframe, - Node3d::PostProcessing, - ), + wireframe_3d + .after(Core3dSystems::EndMainPass) + .before(Core3dSystems::StartMainPassPostProcessing), ) - .add_systems(RenderStartup, init_wireframe_3d_pipeline) .add_systems( ExtractSchedule, ( @@ -367,55 +361,43 @@ impl SpecializedMeshPipeline for Wireframe3dPipeline { } } -#[derive(Default)] -struct Wireframe3dNode; -impl ViewNode for Wireframe3dNode { - type ViewQuery = ( - &'static ExtractedCamera, - &'static ExtractedView, - &'static ViewTarget, - &'static ViewDepthTexture, - ); - - fn run<'w>( - &self, - graph: &mut RenderGraphContext, - render_context: &mut RenderContext<'w>, - (camera, view, target, depth): QueryItem<'w, '_, Self::ViewQuery>, - world: &'w World, - ) -> Result<(), NodeRunError> { - let Some(wireframe_phase) = world.get_resource::>() - else { - return Ok(()); - }; - - let Some(wireframe_phase) = wireframe_phase.get(&view.retained_view_entity) else { - return Ok(()); - }; +pub fn wireframe_3d( + world: &World, + view: ViewQuery<( + &ExtractedCamera, + &ExtractedView, + &ViewTarget, + &ViewDepthTexture, + )>, + wireframe_phases: Res>, + mut ctx: RenderContext, +) { + let view_entity = view.entity(); - let diagnostics = render_context.diagnostic_recorder(); + let (camera, extracted_view, target, depth) = view.into_inner(); - let mut render_pass = render_context.begin_tracked_render_pass(RenderPassDescriptor { - label: Some("wireframe_3d"), - color_attachments: &[Some(target.get_color_attachment())], - depth_stencil_attachment: Some(depth.get_attachment(StoreOp::Store)), - timestamp_writes: None, - occlusion_query_set: None, - }); - let pass_span = diagnostics.pass_span(&mut render_pass, "wireframe_3d"); + let Some(wireframe_phase) = wireframe_phases.get(&extracted_view.retained_view_entity) else { + return; + }; - if let Some(viewport) = camera.viewport.as_ref() { - render_pass.set_camera_viewport(viewport); - } + if wireframe_phase.is_empty() { + return; + } - if let Err(err) = wireframe_phase.render(&mut render_pass, world, graph.view_entity()) { - error!("Error encountered while rendering the stencil phase {err:?}"); - return Err(NodeRunError::DrawError(err)); - } + let mut render_pass = ctx.begin_tracked_render_pass(RenderPassDescriptor { + label: Some("wireframe_3d"), + color_attachments: &[Some(target.get_color_attachment())], + depth_stencil_attachment: Some(depth.get_attachment(StoreOp::Store)), + timestamp_writes: None, + occlusion_query_set: None, + }); - pass_span.end(&mut render_pass); + if let Some(viewport) = camera.viewport.as_ref() { + render_pass.set_camera_viewport(viewport); + } - Ok(()) + if let Err(err) = wireframe_phase.render(&mut render_pass, world, view_entity) { + error!("Error encountered while rendering the wireframe phase {err:?}"); } } diff --git a/crates/bevy_post_process/src/auto_exposure/mod.rs b/crates/bevy_post_process/src/auto_exposure/mod.rs index f5b090621be02..4b1b64c3eb7b1 100644 --- a/crates/bevy_post_process/src/auto_exposure/mod.rs +++ b/crates/bevy_post_process/src/auto_exposure/mod.rs @@ -4,7 +4,6 @@ use bevy_ecs::prelude::*; use bevy_render::{ extract_component::ExtractComponentPlugin, render_asset::RenderAssetPlugin, - render_graph::RenderGraphExt, render_resource::{ Buffer, BufferDescriptor, BufferUsages, PipelineCache, SpecializedComputePipelines, }, @@ -20,14 +19,16 @@ mod settings; use buffers::{extract_buffers, prepare_buffers, AutoExposureBuffers}; pub use compensation_curve::{AutoExposureCompensationCurve, AutoExposureCompensationCurveError}; -use node::AutoExposureNode; use pipeline::{AutoExposurePass, AutoExposurePipeline, ViewAutoExposurePipeline}; pub use settings::AutoExposure; use crate::auto_exposure::{ compensation_curve::GpuAutoExposureCompensationCurve, pipeline::init_auto_exposure_pipeline, }; -use bevy_core_pipeline::core_3d::graph::{Core3d, Node3d}; +use bevy_core_pipeline::{ + schedule::{Core3d, Core3dSystems}, + tonemapping::tonemapping, +}; /// Plugin for the auto exposure feature. /// @@ -72,14 +73,12 @@ impl Plugin for AutoExposurePlugin { queue_view_auto_exposure_pipelines.in_set(RenderSystems::Queue), ), ) - .add_render_graph_node::(Core3d, node::AutoExposure) - .add_render_graph_edges( + // Add auto_exposure to the 3d schedule + .add_systems( Core3d, - ( - Node3d::StartMainPassPostProcessing, - node::AutoExposure, - Node3d::Tonemapping, - ), + node::auto_exposure + .after(Core3dSystems::StartMainPassPostProcessing) + .before(tonemapping), ); } } diff --git a/crates/bevy_post_process/src/auto_exposure/node.rs b/crates/bevy_post_process/src/auto_exposure/node.rs index 125c300241ccb..994b8510fb5f7 100644 --- a/crates/bevy_post_process/src/auto_exposure/node.rs +++ b/crates/bevy_post_process/src/auto_exposure/node.rs @@ -4,144 +4,106 @@ use super::{ pipeline::{AutoExposurePipeline, ViewAutoExposurePipeline}, AutoExposureResources, }; -use bevy_ecs::{ - query::QueryState, - system::lifetimeless::Read, - world::{FromWorld, World}, -}; +use bevy_ecs::prelude::*; use bevy_render::{ diagnostic::RecordDiagnostics, globals::GlobalsBuffer, render_asset::RenderAssets, - render_graph::*, render_resource::*, - renderer::RenderContext, + renderer::{RenderContext, ViewQuery}, texture::{FallbackImage, GpuImage}, view::{ExtractedView, ViewTarget, ViewUniform, ViewUniformOffset, ViewUniforms}, }; -#[derive(RenderLabel, Debug, Clone, Hash, PartialEq, Eq)] -pub struct AutoExposure; - -pub struct AutoExposureNode { - query: QueryState<( - Read, - Read, - Read, - Read, +pub(crate) fn auto_exposure( + view: ViewQuery<( + &ViewUniformOffset, + &ViewTarget, + &ViewAutoExposurePipeline, + &ExtractedView, )>, -} - -impl FromWorld for AutoExposureNode { - fn from_world(world: &mut World) -> Self { - Self { - query: QueryState::new(world), - } - } -} - -impl Node for AutoExposureNode { - fn update(&mut self, world: &mut World) { - self.query.update_archetypes(world); - } - - fn run( - &self, - graph: &mut RenderGraphContext, - render_context: &mut RenderContext, - world: &World, - ) -> Result<(), NodeRunError> { - let view_entity = graph.view_entity(); - let pipeline_cache = world.resource::(); - let pipeline = world.resource::(); - let resources = world.resource::(); - - let view_uniforms_resource = world.resource::(); - let view_uniforms = &view_uniforms_resource.uniforms; - let view_uniforms_buffer = view_uniforms.buffer().unwrap(); - - let globals_buffer = world.resource::(); - - let auto_exposure_buffers = world.resource::(); - - let ( - Ok((view_uniform_offset, view_target, auto_exposure, view)), - Some(auto_exposure_buffers), - ) = ( - self.query.get_manual(world, view_entity), - auto_exposure_buffers.buffers.get(&view_entity), - ) - else { - return Ok(()); - }; - - let (Some(histogram_pipeline), Some(average_pipeline)) = ( - pipeline_cache.get_compute_pipeline(auto_exposure.histogram_pipeline), - pipeline_cache.get_compute_pipeline(auto_exposure.mean_luminance_pipeline), - ) else { - return Ok(()); - }; - - let source = view_target.main_texture_view(); - - let fallback = world.resource::(); - let mask = world - .resource::>() - .get(&auto_exposure.metering_mask); - let mask = mask - .map(|i| &i.texture_view) - .unwrap_or(&fallback.d2.texture_view); - - let Some(compensation_curve) = world - .resource::>() - .get(&auto_exposure.compensation_curve) - else { - return Ok(()); - }; - - let diagnostics = render_context.diagnostic_recorder(); - - let compute_bind_group = render_context.render_device().create_bind_group( - None, - &pipeline_cache.get_bind_group_layout(&pipeline.histogram_layout), - &BindGroupEntries::sequential(( - &globals_buffer.buffer, - &auto_exposure_buffers.settings, - source, - mask, - &compensation_curve.texture_view, - &compensation_curve.extents, - resources.histogram.as_entire_buffer_binding(), - &auto_exposure_buffers.state, - BufferBinding { - buffer: view_uniforms_buffer, - size: Some(ViewUniform::min_size()), - offset: 0, - }, - )), - ); - - let mut compute_pass = - render_context - .command_encoder() - .begin_compute_pass(&ComputePassDescriptor { - label: Some("auto_exposure"), - timestamp_writes: None, - }); - let pass_span = diagnostics.pass_span(&mut compute_pass, "auto_exposure"); + pipeline_cache: Res, + pipeline: Res, + resources: Res, + view_uniforms: Res, + globals_buffer: Res, + auto_exposure_buffers: Res, + fallback: Res, + gpu_images: Res>, + compensation_curves: Res>, + mut ctx: RenderContext, +) { + let view_entity = view.entity(); + let (view_uniform_offset, view_target, auto_exposure_pipeline, extracted_view) = + view.into_inner(); + + let Some(auto_exposure_buffer) = auto_exposure_buffers.buffers.get(&view_entity) else { + return; + }; + + let (Some(histogram_pipeline), Some(average_pipeline)) = ( + pipeline_cache.get_compute_pipeline(auto_exposure_pipeline.histogram_pipeline), + pipeline_cache.get_compute_pipeline(auto_exposure_pipeline.mean_luminance_pipeline), + ) else { + return; + }; + + let view_uniforms_buffer = view_uniforms.uniforms.buffer().unwrap(); + let source = view_target.main_texture_view(); + + let mask = gpu_images + .get(&auto_exposure_pipeline.metering_mask) + .map(|i| &i.texture_view) + .unwrap_or(&fallback.d2.texture_view); + + let Some(compensation_curve) = + compensation_curves.get(&auto_exposure_pipeline.compensation_curve) + else { + return; + }; + + let compute_bind_group = ctx.render_device().create_bind_group( + None, + &pipeline_cache.get_bind_group_layout(&pipeline.histogram_layout), + &BindGroupEntries::sequential(( + &globals_buffer.buffer, + &auto_exposure_buffer.settings, + source, + mask, + &compensation_curve.texture_view, + &compensation_curve.extents, + resources.histogram.as_entire_buffer_binding(), + &auto_exposure_buffer.state, + BufferBinding { + buffer: view_uniforms_buffer, + size: Some(ViewUniform::min_size()), + offset: 0, + }, + )), + ); + + let diagnostics = ctx.diagnostic_recorder(); + let diagnostics = diagnostics.as_deref(); + let time_span = diagnostics.time_span(ctx.command_encoder(), "auto_exposure"); + + { + let mut compute_pass = ctx + .command_encoder() + .begin_compute_pass(&ComputePassDescriptor { + label: Some("auto_exposure"), + timestamp_writes: None, + }); compute_pass.set_bind_group(0, &compute_bind_group, &[view_uniform_offset.offset]); compute_pass.set_pipeline(histogram_pipeline); compute_pass.dispatch_workgroups( - view.viewport.z.div_ceil(16), - view.viewport.w.div_ceil(16), + extracted_view.viewport.z.div_ceil(16), + extracted_view.viewport.w.div_ceil(16), 1, ); compute_pass.set_pipeline(average_pipeline); compute_pass.dispatch_workgroups(1, 1, 1); - - pass_span.end(&mut compute_pass); - - Ok(()) } + + time_span.end(ctx.command_encoder()); } diff --git a/crates/bevy_post_process/src/bloom/mod.rs b/crates/bevy_post_process/src/bloom/mod.rs index 07eb83571e187..3bc7becfb58a2 100644 --- a/crates/bevy_post_process/src/bloom/mod.rs +++ b/crates/bevy_post_process/src/bloom/mod.rs @@ -13,10 +13,10 @@ use bevy_app::{App, Plugin}; use bevy_asset::embedded_asset; use bevy_color::{Gray, LinearRgba}; use bevy_core_pipeline::{ - core_2d::graph::{Core2d, Node2d}, - core_3d::graph::{Core3d, Node3d}, + schedule::{Core2d, Core2dSystems, Core3d, Core3dSystems}, + tonemapping::tonemapping, }; -use bevy_ecs::{prelude::*, query::QueryItem}; +use bevy_ecs::prelude::*; use bevy_math::{ops, UVec2}; use bevy_render::{ camera::ExtractedCamera, @@ -24,9 +24,8 @@ use bevy_render::{ extract_component::{ ComponentUniforms, DynamicUniformIndex, ExtractComponentPlugin, UniformComponentPlugin, }, - render_graph::{NodeRunError, RenderGraphContext, RenderGraphExt, ViewNode, ViewNodeRunner}, render_resource::*, - renderer::{RenderContext, RenderDevice}, + renderer::{RenderContext, RenderDevice, ViewQuery}, texture::{CachedTexture, TextureCache}, view::ViewTarget, Render, RenderApp, RenderStartup, RenderSystems, @@ -77,244 +76,212 @@ impl Plugin for BloomPlugin { prepare_bloom_bind_groups.in_set(RenderSystems::PrepareBindGroups), ), ) - // Add bloom to the 3d render graph - .add_render_graph_node::>(Core3d, Node3d::Bloom) - .add_render_graph_edges( + // Add bloom to the 3d schedule + .add_systems( Core3d, - ( - Node3d::StartMainPassPostProcessing, - Node3d::Bloom, - Node3d::Tonemapping, - ), + bloom + .after(Core3dSystems::StartMainPassPostProcessing) + .before(tonemapping), ) - // Add bloom to the 2d render graph - .add_render_graph_node::>(Core2d, Node2d::Bloom) - .add_render_graph_edges( + // Add bloom to the 2d schedule + .add_systems( Core2d, - ( - Node2d::StartMainPassPostProcessing, - Node2d::Bloom, - Node2d::Tonemapping, - ), + bloom + .after(Core2dSystems::StartMainPassPostProcessing) + .before(tonemapping), ); } } -#[derive(Default)] -struct BloomNode; -impl ViewNode for BloomNode { - type ViewQuery = ( - &'static ExtractedCamera, - &'static ViewTarget, - &'static BloomTexture, - &'static BloomBindGroups, - &'static DynamicUniformIndex, - &'static Bloom, - &'static UpsamplingPipelineIds, - &'static BloomDownsamplingPipelineIds, +pub(crate) fn bloom( + view: ViewQuery<( + &ExtractedCamera, + &ViewTarget, + &BloomTexture, + &BloomBindGroups, + &DynamicUniformIndex, + &Bloom, + &UpsamplingPipelineIds, + &BloomDownsamplingPipelineIds, + )>, + downsampling_pipeline_res: Res, + pipeline_cache: Res, + uniforms: Res>, + mut ctx: RenderContext, +) { + let ( + camera, + view_target, + bloom_texture, + bind_groups, + uniform_index, + bloom_settings, + upsampling_pipeline_ids, + downsampling_pipeline_ids, + ) = view.into_inner(); + + if bloom_settings.intensity == 0.0 { + return; + } + + let ( + Some(uniforms_binding), + Some(downsampling_first_pipeline), + Some(downsampling_pipeline), + Some(upsampling_pipeline), + Some(upsampling_final_pipeline), + ) = ( + uniforms.binding(), + pipeline_cache.get_render_pipeline(downsampling_pipeline_ids.first), + pipeline_cache.get_render_pipeline(downsampling_pipeline_ids.main), + pipeline_cache.get_render_pipeline(upsampling_pipeline_ids.id_main), + pipeline_cache.get_render_pipeline(upsampling_pipeline_ids.id_final), + ) + else { + return; + }; + + let view_texture = view_target.main_texture_view(); + let view_texture_unsampled = view_target.get_unsampled_color_attachment(); + + // Create the first downsampling bind group (reads from main texture) + let downsampling_first_bind_group = ctx.render_device().create_bind_group( + "bloom_downsampling_first_bind_group", + &pipeline_cache.get_bind_group_layout(&downsampling_pipeline_res.bind_group_layout), + &BindGroupEntries::sequential(( + view_texture, + &bind_groups.sampler, + uniforms_binding.clone(), + )), ); - // Atypically for a post-processing effect, we do not need to - // use a secondary texture normally provided by view_target.post_process_write(), - // instead we write into our own bloom texture and then directly back onto main. - fn run<'w>( - &self, - _graph: &mut RenderGraphContext, - render_context: &mut RenderContext<'w>, - ( - camera, - view_target, - bloom_texture, - bind_groups, - uniform_index, - bloom_settings, - upsampling_pipeline_ids, - downsampling_pipeline_ids, - ): QueryItem<'w, '_, Self::ViewQuery>, - world: &'w World, - ) -> Result<(), NodeRunError> { - if bloom_settings.intensity == 0.0 { - return Ok(()); - } + let diagnostics = ctx.diagnostic_recorder(); + let diagnostics = diagnostics.as_deref(); + let time_span = diagnostics.time_span(ctx.command_encoder(), "bloom"); + + let command_encoder = ctx.command_encoder(); + command_encoder.push_debug_group("bloom"); + + // First downsample pass + { + let view = &bloom_texture.view(0); + let mut downsampling_first_pass = + command_encoder.begin_render_pass(&RenderPassDescriptor { + label: Some("bloom_downsampling_first_pass"), + color_attachments: &[Some(RenderPassColorAttachment { + view, + depth_slice: None, + resolve_target: None, + ops: Operations::default(), + })], + depth_stencil_attachment: None, + timestamp_writes: None, + occlusion_query_set: None, + }); + downsampling_first_pass.set_pipeline(downsampling_first_pipeline); + downsampling_first_pass.set_bind_group( + 0, + &downsampling_first_bind_group, + &[uniform_index.index()], + ); + downsampling_first_pass.draw(0..3, 0..1); + } - let downsampling_pipeline_res = world.resource::(); - let pipeline_cache = world.resource::(); - let uniforms = world.resource::>(); - - let ( - Some(uniforms), - Some(downsampling_first_pipeline), - Some(downsampling_pipeline), - Some(upsampling_pipeline), - Some(upsampling_final_pipeline), - ) = ( - uniforms.binding(), - pipeline_cache.get_render_pipeline(downsampling_pipeline_ids.first), - pipeline_cache.get_render_pipeline(downsampling_pipeline_ids.main), - pipeline_cache.get_render_pipeline(upsampling_pipeline_ids.id_main), - pipeline_cache.get_render_pipeline(upsampling_pipeline_ids.id_final), - ) - else { - return Ok(()); - }; + // Other downsample passes + for mip in 1..bloom_texture.mip_count { + let view = &bloom_texture.view(mip); + let mut downsampling_pass = command_encoder.begin_render_pass(&RenderPassDescriptor { + label: Some("bloom_downsampling_pass"), + color_attachments: &[Some(RenderPassColorAttachment { + view, + depth_slice: None, + resolve_target: None, + ops: Operations::default(), + })], + depth_stencil_attachment: None, + timestamp_writes: None, + occlusion_query_set: None, + }); + downsampling_pass.set_pipeline(downsampling_pipeline); + downsampling_pass.set_bind_group( + 0, + &bind_groups.downsampling_bind_groups[mip as usize - 1], + &[uniform_index.index()], + ); + downsampling_pass.draw(0..3, 0..1); + } - let view_texture = view_target.main_texture_view(); - let view_texture_unsampled = view_target.get_unsampled_color_attachment(); - let diagnostics = render_context.diagnostic_recorder(); - - render_context.add_command_buffer_generation_task(move |render_device| { - #[cfg(feature = "trace")] - let _bloom_span = info_span!("bloom").entered(); - - let mut command_encoder = - render_device.create_command_encoder(&CommandEncoderDescriptor { - label: Some("bloom_command_encoder"), - }); - command_encoder.push_debug_group("bloom"); - let time_span = diagnostics.time_span(&mut command_encoder, "bloom"); - - // First downsample pass - { - let downsampling_first_bind_group = render_device.create_bind_group( - "bloom_downsampling_first_bind_group", - &pipeline_cache - .get_bind_group_layout(&downsampling_pipeline_res.bind_group_layout), - &BindGroupEntries::sequential(( - // Read from main texture directly - view_texture, - &bind_groups.sampler, - uniforms.clone(), - )), - ); - - let view = &bloom_texture.view(0); - let mut downsampling_first_pass = - command_encoder.begin_render_pass(&RenderPassDescriptor { - label: Some("bloom_downsampling_first_pass"), - color_attachments: &[Some(RenderPassColorAttachment { - view, - depth_slice: None, - resolve_target: None, - ops: Operations::default(), - })], - depth_stencil_attachment: None, - timestamp_writes: None, - occlusion_query_set: None, - }); - downsampling_first_pass.set_pipeline(downsampling_first_pipeline); - downsampling_first_pass.set_bind_group( - 0, - &downsampling_first_bind_group, - &[uniform_index.index()], - ); - downsampling_first_pass.draw(0..3, 0..1); - } - - // Other downsample passes - for mip in 1..bloom_texture.mip_count { - let view = &bloom_texture.view(mip); - let mut downsampling_pass = - command_encoder.begin_render_pass(&RenderPassDescriptor { - label: Some("bloom_downsampling_pass"), - color_attachments: &[Some(RenderPassColorAttachment { - view, - depth_slice: None, - resolve_target: None, - ops: Operations::default(), - })], - depth_stencil_attachment: None, - timestamp_writes: None, - occlusion_query_set: None, - }); - downsampling_pass.set_pipeline(downsampling_pipeline); - downsampling_pass.set_bind_group( - 0, - &bind_groups.downsampling_bind_groups[mip as usize - 1], - &[uniform_index.index()], - ); - downsampling_pass.draw(0..3, 0..1); - } - - // Upsample passes except the final one - for mip in (1..bloom_texture.mip_count).rev() { - let view = &bloom_texture.view(mip - 1); - let mut upsampling_pass = - command_encoder.begin_render_pass(&RenderPassDescriptor { - label: Some("bloom_upsampling_pass"), - color_attachments: &[Some(RenderPassColorAttachment { - view, - depth_slice: None, - resolve_target: None, - ops: Operations { - load: LoadOp::Load, - store: StoreOp::Store, - }, - })], - depth_stencil_attachment: None, - timestamp_writes: None, - occlusion_query_set: None, - }); - upsampling_pass.set_pipeline(upsampling_pipeline); - upsampling_pass.set_bind_group( - 0, - &bind_groups.upsampling_bind_groups - [(bloom_texture.mip_count - mip - 1) as usize], - &[uniform_index.index()], - ); - let blend = compute_blend_factor( - bloom_settings, - mip as f32, - (bloom_texture.mip_count - 1) as f32, - ); - upsampling_pass.set_blend_constant(LinearRgba::gray(blend).into()); - upsampling_pass.draw(0..3, 0..1); - } - - // Final upsample pass - // This is very similar to the above upsampling passes with the only difference - // being the pipeline (which itself is barely different) and the color attachment - { - let mut upsampling_final_pass = - command_encoder.begin_render_pass(&RenderPassDescriptor { - label: Some("bloom_upsampling_final_pass"), - color_attachments: &[Some(view_texture_unsampled)], - depth_stencil_attachment: None, - timestamp_writes: None, - occlusion_query_set: None, - }); - upsampling_final_pass.set_pipeline(upsampling_final_pipeline); - upsampling_final_pass.set_bind_group( - 0, - &bind_groups.upsampling_bind_groups[(bloom_texture.mip_count - 1) as usize], - &[uniform_index.index()], - ); - if let Some(viewport) = camera.viewport.as_ref() { - upsampling_final_pass.set_viewport( - viewport.physical_position.x as f32, - viewport.physical_position.y as f32, - viewport.physical_size.x as f32, - viewport.physical_size.y as f32, - viewport.depth.start, - viewport.depth.end, - ); - } - let blend = - compute_blend_factor(bloom_settings, 0.0, (bloom_texture.mip_count - 1) as f32); - upsampling_final_pass.set_blend_constant(LinearRgba::gray(blend).into()); - upsampling_final_pass.draw(0..3, 0..1); - } - - time_span.end(&mut command_encoder); - command_encoder.pop_debug_group(); - command_encoder.finish() + // Upsample passes except the final one + for mip in (1..bloom_texture.mip_count).rev() { + let view = &bloom_texture.view(mip - 1); + let mut upsampling_pass = command_encoder.begin_render_pass(&RenderPassDescriptor { + label: Some("bloom_upsampling_pass"), + color_attachments: &[Some(RenderPassColorAttachment { + view, + depth_slice: None, + resolve_target: None, + ops: Operations { + load: LoadOp::Load, + store: StoreOp::Store, + }, + })], + depth_stencil_attachment: None, + timestamp_writes: None, + occlusion_query_set: None, }); + upsampling_pass.set_pipeline(upsampling_pipeline); + upsampling_pass.set_bind_group( + 0, + &bind_groups.upsampling_bind_groups[(bloom_texture.mip_count - mip - 1) as usize], + &[uniform_index.index()], + ); + let blend = compute_blend_factor( + bloom_settings, + mip as f32, + (bloom_texture.mip_count - 1) as f32, + ); + upsampling_pass.set_blend_constant(LinearRgba::gray(blend).into()); + upsampling_pass.draw(0..3, 0..1); + } - Ok(()) + // Final upsample pass + { + let mut upsampling_final_pass = + command_encoder.begin_render_pass(&RenderPassDescriptor { + label: Some("bloom_upsampling_final_pass"), + color_attachments: &[Some(view_texture_unsampled)], + depth_stencil_attachment: None, + timestamp_writes: None, + occlusion_query_set: None, + }); + upsampling_final_pass.set_pipeline(upsampling_final_pipeline); + upsampling_final_pass.set_bind_group( + 0, + &bind_groups.upsampling_bind_groups[(bloom_texture.mip_count - 1) as usize], + &[uniform_index.index()], + ); + if let Some(viewport) = camera.viewport.as_ref() { + upsampling_final_pass.set_viewport( + viewport.physical_position.x as f32, + viewport.physical_position.y as f32, + viewport.physical_size.x as f32, + viewport.physical_size.y as f32, + viewport.depth.start, + viewport.depth.end, + ); + } + let blend = + compute_blend_factor(bloom_settings, 0.0, (bloom_texture.mip_count - 1) as f32); + upsampling_final_pass.set_blend_constant(LinearRgba::gray(blend).into()); + upsampling_final_pass.draw(0..3, 0..1); } + + command_encoder.pop_debug_group(); + time_span.end(ctx.command_encoder()); } #[derive(Component)] -struct BloomTexture { +pub(crate) struct BloomTexture { // First mip is half the screen resolution, successive mips are half the previous #[cfg(any( not(feature = "webgl"), @@ -416,7 +383,7 @@ fn prepare_bloom_textures( } #[derive(Component)] -struct BloomBindGroups { +pub(crate) struct BloomBindGroups { cache_key: (TextureId, BufferId), downsampling_bind_groups: Box<[BindGroup]>, upsampling_bind_groups: Box<[BindGroup]>, diff --git a/crates/bevy_post_process/src/dof/mod.rs b/crates/bevy_post_process/src/dof/mod.rs index 4cff455fe7fde..35499c2a3575b 100644 --- a/crates/bevy_post_process/src/dof/mod.rs +++ b/crates/bevy_post_process/src/dof/mod.rs @@ -21,12 +21,11 @@ use bevy_derive::{Deref, DerefMut}; use bevy_ecs::{ component::Component, entity::Entity, - query::{QueryItem, With}, + query::With, reflect::ReflectComponent, resource::Resource, schedule::IntoScheduleConfigs as _, - system::{lifetimeless::Read, Commands, Query, Res, ResMut}, - world::World, + system::{Commands, Query, Res, ResMut}, }; use bevy_image::BevyDefault as _; use bevy_math::ops; @@ -34,9 +33,6 @@ use bevy_reflect::{prelude::ReflectDefault, Reflect}; use bevy_render::{ diagnostic::RecordDiagnostics, extract_component::{ComponentUniforms, DynamicUniformIndex, UniformComponentPlugin}, - render_graph::{ - NodeRunError, RenderGraphContext, RenderGraphExt as _, ViewNode, ViewNodeRunner, - }, render_resource::{ binding_types::{ sampler, texture_2d, texture_depth_2d, texture_depth_2d_multisampled, uniform_buffer, @@ -48,7 +44,7 @@ use bevy_render::{ ShaderType, SpecializedRenderPipeline, SpecializedRenderPipelines, StoreOp, TextureDescriptor, TextureDimension, TextureFormat, TextureSampleType, TextureUsages, }, - renderer::{RenderContext, RenderDevice}, + renderer::{RenderContext, RenderDevice, ViewQuery}, sync_component::SyncComponentPlugin, sync_world::RenderEntity, texture::{CachedTexture, TextureCache}, @@ -64,12 +60,12 @@ use smallvec::SmallVec; use tracing::{info, warn}; use bevy_core_pipeline::{ - core_3d::{ - graph::{Core3d, Node3d}, - DEPTH_TEXTURE_SAMPLING_SUPPORTED, - }, + core_3d::DEPTH_TEXTURE_SAMPLING_SUPPORTED, + schedule::Core3d, + tonemapping::tonemapping, FullscreenShader, }; +use crate::bloom::bloom; /// A plugin that adds support for the depth of field effect to Bevy. #[derive(Default)] @@ -244,17 +240,16 @@ impl Plugin for DepthOfFieldPlugin { Render, prepare_depth_of_field_global_bind_group.in_set(RenderSystems::PrepareBindGroups), ) - .add_render_graph_node::>(Core3d, Node3d::DepthOfField) - .add_render_graph_edges( + // Add depth_of_field to the 3d schedule + .add_systems( Core3d, - (Node3d::Bloom, Node3d::DepthOfField, Node3d::Tonemapping), + depth_of_field + .after(bloom) + .before(tonemapping), ); } } -/// The node in the render graph for depth of field. -#[derive(Default)] -pub struct DepthOfFieldNode; /// The layout for the bind group shared among all invocations of the depth of /// field shader. @@ -325,154 +320,6 @@ pub struct DepthOfFieldPipeline { fragment_shader: Handle, } -impl ViewNode for DepthOfFieldNode { - type ViewQuery = ( - Read, - Read, - Read, - Read, - Read, - Read>, - Option>, - ); - - fn run<'w>( - &self, - _: &mut RenderGraphContext, - render_context: &mut RenderContext<'w>, - ( - view_uniform_offset, - view_target, - view_depth_texture, - view_pipelines, - view_bind_group_layouts, - depth_of_field_uniform_index, - auxiliary_dof_texture, - ): QueryItem<'w, '_, Self::ViewQuery>, - world: &'w World, - ) -> Result<(), NodeRunError> { - let pipeline_cache = world.resource::(); - let view_uniforms = world.resource::(); - let global_bind_group = world.resource::(); - - let diagnostics = render_context.diagnostic_recorder(); - - // We can be in either Gaussian blur or bokeh mode here. Both modes are - // similar, consisting of two passes each. We factor out the information - // specific to each pass into - // [`DepthOfFieldPipelines::pipeline_render_info`]. - for pipeline_render_info in view_pipelines.pipeline_render_info().iter() { - let (Some(render_pipeline), Some(view_uniforms_binding), Some(global_bind_group)) = ( - pipeline_cache.get_render_pipeline(pipeline_render_info.pipeline), - view_uniforms.uniforms.binding(), - &**global_bind_group, - ) else { - return Ok(()); - }; - - // We use most of the postprocess infrastructure here. However, - // because the bokeh pass has an additional render target, we have - // to manage a secondary *auxiliary* texture alongside the textures - // managed by the postprocessing logic. - let postprocess = view_target.post_process_write(); - - let view_bind_group = if pipeline_render_info.is_dual_input { - let (Some(auxiliary_dof_texture), Some(dual_input_bind_group_layout)) = ( - auxiliary_dof_texture, - view_bind_group_layouts.dual_input.as_ref(), - ) else { - once!(warn!( - "Should have created the auxiliary depth of field texture by now" - )); - continue; - }; - render_context.render_device().create_bind_group( - Some(pipeline_render_info.view_bind_group_label), - &pipeline_cache.get_bind_group_layout(dual_input_bind_group_layout), - &BindGroupEntries::sequential(( - view_uniforms_binding, - view_depth_texture.view(), - postprocess.source, - &auxiliary_dof_texture.default_view, - )), - ) - } else { - render_context.render_device().create_bind_group( - Some(pipeline_render_info.view_bind_group_label), - &pipeline_cache.get_bind_group_layout(&view_bind_group_layouts.single_input), - &BindGroupEntries::sequential(( - view_uniforms_binding, - view_depth_texture.view(), - postprocess.source, - )), - ) - }; - - // Push the first input attachment. - let mut color_attachments: SmallVec<[_; 2]> = SmallVec::new(); - color_attachments.push(Some(RenderPassColorAttachment { - view: postprocess.destination, - depth_slice: None, - resolve_target: None, - ops: Operations { - load: LoadOp::Clear(default()), - store: StoreOp::Store, - }, - })); - - // The first pass of the bokeh shader has two color outputs, not - // one. Handle this case by attaching the auxiliary texture, which - // should have been created by now in - // `prepare_auxiliary_depth_of_field_textures``. - if pipeline_render_info.is_dual_output { - let Some(auxiliary_dof_texture) = auxiliary_dof_texture else { - once!(warn!( - "Should have created the auxiliary depth of field texture by now" - )); - continue; - }; - color_attachments.push(Some(RenderPassColorAttachment { - view: &auxiliary_dof_texture.default_view, - depth_slice: None, - resolve_target: None, - ops: Operations { - load: LoadOp::Clear(default()), - store: StoreOp::Store, - }, - })); - } - - let render_pass_descriptor = RenderPassDescriptor { - label: Some(pipeline_render_info.pass_label), - color_attachments: &color_attachments, - ..default() - }; - - let mut render_pass = render_context - .command_encoder() - .begin_render_pass(&render_pass_descriptor); - let pass_span = - diagnostics.pass_span(&mut render_pass, pipeline_render_info.pass_label); - - render_pass.set_pipeline(render_pipeline); - // Set the per-view bind group. - render_pass.set_bind_group(0, &view_bind_group, &[view_uniform_offset.offset]); - // Set the global bind group shared among all invocations of the shader. - render_pass.set_bind_group( - 1, - global_bind_group, - &[depth_of_field_uniform_index.index()], - ); - // Render the full-screen pass. - render_pass.draw(0..3, 0..1); - - pass_span.end(&mut render_pass); - } - - Ok(()) - } -} - impl Default for DepthOfField { fn default() -> Self { let physical_camera_default = PhysicalCameraParameters::default(); @@ -922,3 +769,140 @@ impl DepthOfFieldPipelines { } } } + + +pub(crate) fn depth_of_field( + view: ViewQuery<( + &ViewUniformOffset, + &ViewTarget, + &ViewDepthTexture, + &DepthOfFieldPipelines, + &ViewDepthOfFieldBindGroupLayouts, + &DynamicUniformIndex, + Option<&AuxiliaryDepthOfFieldTexture>, + )>, + pipeline_cache: Res, + view_uniforms: Res, + global_bind_group: Res, + mut ctx: RenderContext, +) { + let ( + view_uniform_offset, + view_target, + view_depth_texture, + view_pipelines, + view_bind_group_layouts, + depth_of_field_uniform_index, + auxiliary_dof_texture, + ) = view.into_inner(); + + let diagnostics = ctx.diagnostic_recorder(); + let diagnostics = diagnostics.as_deref(); + let time_span = diagnostics.time_span(ctx.command_encoder(), "depth_of_field"); + + // We can be in either Gaussian blur or bokeh mode here. Both modes are + // similar, consisting of two passes each. + for pipeline_render_info in view_pipelines.pipeline_render_info().iter() { + let (Some(render_pipeline), Some(view_uniforms_binding), Some(global_bind_group)) = ( + pipeline_cache.get_render_pipeline(pipeline_render_info.pipeline), + view_uniforms.uniforms.binding(), + &**global_bind_group, + ) else { + return; + }; + + // We use most of the postprocess infrastructure here. However, + // because the bokeh pass has an additional render target, we have + // to manage a secondary *auxiliary* texture alongside the textures + // managed by the postprocessing logic. + let postprocess = view_target.post_process_write(); + + let view_bind_group = if pipeline_render_info.is_dual_input { + let (Some(auxiliary_dof_texture), Some(dual_input_bind_group_layout)) = ( + auxiliary_dof_texture, + view_bind_group_layouts.dual_input.as_ref(), + ) else { + once!(warn!( + "Should have created the auxiliary depth of field texture by now" + )); + continue; + }; + ctx.render_device().create_bind_group( + Some(pipeline_render_info.view_bind_group_label), + &pipeline_cache.get_bind_group_layout(dual_input_bind_group_layout), + &BindGroupEntries::sequential(( + view_uniforms_binding, + view_depth_texture.view(), + postprocess.source, + &auxiliary_dof_texture.default_view, + )), + ) + } else { + ctx.render_device().create_bind_group( + Some(pipeline_render_info.view_bind_group_label), + &pipeline_cache.get_bind_group_layout(&view_bind_group_layouts.single_input), + &BindGroupEntries::sequential(( + view_uniforms_binding, + view_depth_texture.view(), + postprocess.source, + )), + ) + }; + + // Push the first input attachment. + let mut color_attachments: SmallVec<[_; 2]> = SmallVec::new(); + color_attachments.push(Some(RenderPassColorAttachment { + view: postprocess.destination, + depth_slice: None, + resolve_target: None, + ops: Operations { + load: LoadOp::Clear(default()), + store: StoreOp::Store, + }, + })); + + // The first pass of the bokeh shader has two color outputs, not + // one. Handle this case by attaching the auxiliary texture, which + // should have been created by now in + // `prepare_auxiliary_depth_of_field_textures``. + if pipeline_render_info.is_dual_output { + let Some(auxiliary_dof_texture) = auxiliary_dof_texture else { + once!(warn!( + "Should have created the auxiliary depth of field texture by now" + )); + continue; + }; + color_attachments.push(Some(RenderPassColorAttachment { + view: &auxiliary_dof_texture.default_view, + depth_slice: None, + resolve_target: None, + ops: Operations { + load: LoadOp::Clear(default()), + store: StoreOp::Store, + }, + })); + } + + let render_pass_descriptor = RenderPassDescriptor { + label: Some(pipeline_render_info.pass_label), + color_attachments: &color_attachments, + ..default() + }; + + let mut render_pass = ctx.command_encoder().begin_render_pass(&render_pass_descriptor); + + render_pass.set_pipeline(render_pipeline); + // Set the per-view bind group. + render_pass.set_bind_group(0, &view_bind_group, &[view_uniform_offset.offset]); + // Set the global bind group shared among all invocations of the shader. + render_pass.set_bind_group( + 1, + global_bind_group, + &[depth_of_field_uniform_index.index()], + ); + // Render the full-screen pass. + render_pass.draw(0..3, 0..1); + } + + time_span.end(ctx.command_encoder()); +} diff --git a/crates/bevy_post_process/src/effect_stack/mod.rs b/crates/bevy_post_process/src/effect_stack/mod.rs index 444377781b83d..b42608965752b 100644 --- a/crates/bevy_post_process/src/effect_stack/mod.rs +++ b/crates/bevy_post_process/src/effect_stack/mod.rs @@ -16,7 +16,6 @@ use bevy_ecs::{ resource::Resource, schedule::IntoScheduleConfigs as _, system::{lifetimeless::Read, Commands, Query, Res, ResMut}, - world::World, }; use bevy_image::{BevyDefault, Image}; use bevy_reflect::{std_traits::ReflectDefault, Reflect}; @@ -24,9 +23,6 @@ use bevy_render::{ diagnostic::RecordDiagnostics, extract_component::{ExtractComponent, ExtractComponentPlugin}, render_asset::RenderAssets, - render_graph::{ - NodeRunError, RenderGraphContext, RenderGraphExt as _, ViewNode, ViewNodeRunner, - }, render_resource::{ binding_types::{sampler, texture_2d, uniform_buffer}, BindGroupEntries, BindGroupLayoutDescriptor, BindGroupLayoutEntries, @@ -36,7 +32,7 @@ use bevy_render::{ SamplerDescriptor, ShaderStages, ShaderType, SpecializedRenderPipeline, SpecializedRenderPipelines, TextureDimension, TextureFormat, TextureSampleType, }, - renderer::{RenderContext, RenderDevice, RenderQueue}, + renderer::{RenderContext, RenderDevice, RenderQueue, ViewQuery}, texture::GpuImage, view::{ExtractedView, ViewTarget}, Render, RenderApp, RenderStartup, RenderSystems, @@ -45,10 +41,11 @@ use bevy_shader::{load_shader_library, Shader}; use bevy_utils::prelude::default; use bevy_core_pipeline::{ - core_2d::graph::{Core2d, Node2d}, - core_3d::graph::{Core3d, Node3d}, + schedule::{Core2d, Core3d}, + tonemapping::tonemapping, FullscreenShader, }; +use crate::bloom::bloom; /// The default chromatic aberration intensity amount, in a fraction of the /// window size. @@ -65,7 +62,7 @@ static DEFAULT_CHROMATIC_ABERRATION_LUT_DATA: [u8; 12] = [255, 0, 0, 255, 0, 255, 0, 255, 0, 0, 255, 255]; #[derive(Resource)] -struct DefaultChromaticAberrationLut(Handle); +pub(crate) struct DefaultChromaticAberrationLut(Handle); /// A plugin that implements a built-in postprocessing stack with some common /// effects. @@ -180,10 +177,6 @@ pub struct PostProcessingUniformBufferOffsets { chromatic_aberration: u32, } -/// The render node that runs the built-in postprocessing stack. -#[derive(Default)] -pub struct PostProcessingNode; - impl Plugin for EffectStackPlugin { fn build(&self, app: &mut App) { load_shader_library!(app, "chromatic_aberration.wgsl"); @@ -223,25 +216,17 @@ impl Plugin for EffectStackPlugin { ) .in_set(RenderSystems::Prepare), ) - .add_render_graph_node::>( - Core3d, - Node3d::PostProcessing, - ) - .add_render_graph_edges( + .add_systems( Core3d, - ( - Node3d::DepthOfField, - Node3d::PostProcessing, - Node3d::Tonemapping, - ), + post_processing + .after(bloom) + .before(tonemapping), ) - .add_render_graph_node::>( - Core2d, - Node2d::PostProcessing, - ) - .add_render_graph_edges( + .add_systems( Core2d, - (Node2d::Bloom, Node2d::PostProcessing, Node2d::Tonemapping), + post_processing + .after(bloom) + .before(tonemapping), ); } } @@ -330,94 +315,87 @@ impl SpecializedRenderPipeline for PostProcessingPipeline { } } -impl ViewNode for PostProcessingNode { - type ViewQuery = ( - Read, - Read, - Read, - Read, +pub(crate) fn post_processing( + view: ViewQuery<( + &ViewTarget, + &PostProcessingPipelineId, + &ChromaticAberration, + &PostProcessingUniformBufferOffsets, + )>, + pipeline_cache: Res, + post_processing_pipeline: Res, + post_processing_uniform_buffers: Res, + gpu_image_assets: Res>, + default_lut: Res, + mut ctx: RenderContext, +) { + let (view_target, pipeline_id, chromatic_aberration, post_processing_uniform_buffer_offsets) = + view.into_inner(); + + // We need a render pipeline to be prepared. + let Some(pipeline) = pipeline_cache.get_render_pipeline(**pipeline_id) else { + return; + }; + + // We need the chromatic aberration LUT to be present. + let Some(chromatic_aberration_lut) = gpu_image_assets.get( + chromatic_aberration + .color_lut + .as_ref() + .unwrap_or(&default_lut.0), + ) else { + return; + }; + + // We need the postprocessing settings to be uploaded to the GPU. + let Some(chromatic_aberration_uniform_buffer_binding) = post_processing_uniform_buffers + .chromatic_aberration + .binding() + else { + return; + }; + + // Use the [`PostProcessWrite`] infrastructure, since this is a full-screen pass. + let post_process = view_target.post_process_write(); + + let pass_descriptor = RenderPassDescriptor { + label: Some("postprocessing"), + color_attachments: &[Some(RenderPassColorAttachment { + view: post_process.destination, + depth_slice: None, + resolve_target: None, + ops: Operations::default(), + })], + depth_stencil_attachment: None, + timestamp_writes: None, + occlusion_query_set: None, + }; + + let bind_group = ctx.render_device().create_bind_group( + Some("postprocessing bind group"), + &pipeline_cache.get_bind_group_layout(&post_processing_pipeline.bind_group_layout), + &BindGroupEntries::sequential(( + post_process.source, + &post_processing_pipeline.source_sampler, + &chromatic_aberration_lut.texture_view, + &post_processing_pipeline.chromatic_aberration_lut_sampler, + chromatic_aberration_uniform_buffer_binding, + )), ); - fn run<'w>( - &self, - _: &mut RenderGraphContext, - render_context: &mut RenderContext<'w>, - (view_target, pipeline_id, chromatic_aberration, post_processing_uniform_buffer_offsets): QueryItem<'w, '_, Self::ViewQuery>, - world: &'w World, - ) -> Result<(), NodeRunError> { - let pipeline_cache = world.resource::(); - let post_processing_pipeline = world.resource::(); - let post_processing_uniform_buffers = world.resource::(); - let gpu_image_assets = world.resource::>(); - let default_lut = world.resource::(); - - // We need a render pipeline to be prepared. - let Some(pipeline) = pipeline_cache.get_render_pipeline(**pipeline_id) else { - return Ok(()); - }; - - // We need the chromatic aberration LUT to be present. - let Some(chromatic_aberration_lut) = gpu_image_assets.get( - chromatic_aberration - .color_lut - .as_ref() - .unwrap_or(&default_lut.0), - ) else { - return Ok(()); - }; - - // We need the postprocessing settings to be uploaded to the GPU. - let Some(chromatic_aberration_uniform_buffer_binding) = post_processing_uniform_buffers - .chromatic_aberration - .binding() - else { - return Ok(()); - }; - - let diagnostics = render_context.diagnostic_recorder(); - - // Use the [`PostProcessWrite`] infrastructure, since this is a - // full-screen pass. - let post_process = view_target.post_process_write(); - - let pass_descriptor = RenderPassDescriptor { - label: Some("postprocessing"), - color_attachments: &[Some(RenderPassColorAttachment { - view: post_process.destination, - depth_slice: None, - resolve_target: None, - ops: Operations::default(), - })], - depth_stencil_attachment: None, - timestamp_writes: None, - occlusion_query_set: None, - }; - - let bind_group = render_context.render_device().create_bind_group( - Some("postprocessing bind group"), - &pipeline_cache.get_bind_group_layout(&post_processing_pipeline.bind_group_layout), - &BindGroupEntries::sequential(( - post_process.source, - &post_processing_pipeline.source_sampler, - &chromatic_aberration_lut.texture_view, - &post_processing_pipeline.chromatic_aberration_lut_sampler, - chromatic_aberration_uniform_buffer_binding, - )), - ); + let diagnostics = ctx.diagnostic_recorder(); + let diagnostics = diagnostics.as_deref(); + let time_span = diagnostics.time_span(ctx.command_encoder(), "postprocessing"); - let mut render_pass = render_context - .command_encoder() - .begin_render_pass(&pass_descriptor); - let pass_span = diagnostics.pass_span(&mut render_pass, "postprocessing"); + { + let mut render_pass = ctx.command_encoder().begin_render_pass(&pass_descriptor); render_pass.set_pipeline(pipeline); render_pass.set_bind_group(0, &bind_group, &[**post_processing_uniform_buffer_offsets]); render_pass.draw(0..3, 0..1); - - pass_span.end(&mut render_pass); - - Ok(()) } + + time_span.end(ctx.command_encoder()); } /// Specializes the built-in postprocessing pipeline for each applicable view. diff --git a/crates/bevy_post_process/src/motion_blur/mod.rs b/crates/bevy_post_process/src/motion_blur/mod.rs index 5d66c9f51b68d..f65e3eb69a6fa 100644 --- a/crates/bevy_post_process/src/motion_blur/mod.rs +++ b/crates/bevy_post_process/src/motion_blur/mod.rs @@ -6,8 +6,8 @@ use bevy_app::{App, Plugin}; use bevy_asset::embedded_asset; use bevy_camera::Camera; use bevy_core_pipeline::{ - core_3d::graph::{Core3d, Node3d}, prepass::{DepthPrepass, MotionVectorPrepass}, + schedule::{Core3d, Core3dSystems}, }; use bevy_ecs::{ component::Component, @@ -18,10 +18,10 @@ use bevy_ecs::{ use bevy_reflect::{std_traits::ReflectDefault, Reflect}; use bevy_render::{ extract_component::{ExtractComponent, ExtractComponentPlugin, UniformComponentPlugin}, - render_graph::{RenderGraphExt, ViewNodeRunner}, render_resource::{ShaderType, SpecializedRenderPipelines}, Render, RenderApp, RenderStartup, RenderSystems, }; +use crate::bloom::bloom; pub mod node; pub mod pipeline; @@ -151,18 +151,11 @@ impl Plugin for MotionBlurPlugin { pipeline::prepare_motion_blur_pipelines.in_set(RenderSystems::Prepare), ); - render_app - .add_render_graph_node::>( - Core3d, - Node3d::MotionBlur, - ) - .add_render_graph_edges( - Core3d, - ( - Node3d::StartMainPassPostProcessing, - Node3d::MotionBlur, - Node3d::Bloom, // we want blurred areas to bloom and tonemap properly. - ), - ); + render_app.add_systems( + Core3d, + node::motion_blur + .after(Core3dSystems::StartMainPassPostProcessing) + .before(bloom), + ); } } diff --git a/crates/bevy_post_process/src/motion_blur/node.rs b/crates/bevy_post_process/src/motion_blur/node.rs index 179cb01e00ff3..790a88d515eec 100644 --- a/crates/bevy_post_process/src/motion_blur/node.rs +++ b/crates/bevy_post_process/src/motion_blur/node.rs @@ -1,14 +1,13 @@ -use bevy_ecs::{query::QueryItem, world::World}; +use bevy_ecs::prelude::*; use bevy_render::{ diagnostic::RecordDiagnostics, extract_component::ComponentUniforms, globals::GlobalsBuffer, - render_graph::{NodeRunError, RenderGraphContext, ViewNode}, render_resource::{ BindGroupEntries, Operations, PipelineCache, RenderPassColorAttachment, RenderPassDescriptor, }, - renderer::RenderContext, + renderer::{RenderContext, ViewQuery}, view::{Msaa, ViewTarget}, }; @@ -19,90 +18,84 @@ use super::{ MotionBlurUniform, }; -#[derive(Default)] -pub struct MotionBlurNode; +pub(crate) fn motion_blur( + view: ViewQuery<( + &ViewTarget, + &MotionBlurPipelineId, + &ViewPrepassTextures, + &MotionBlurUniform, + &Msaa, + )>, + motion_blur_pipeline: Res, + pipeline_cache: Res, + settings_uniforms: Res>, + globals_buffer: Res, + mut ctx: RenderContext, +) { + let (view_target, pipeline_id, prepass_textures, motion_blur_uniform, msaa) = + view.into_inner(); -impl ViewNode for MotionBlurNode { - type ViewQuery = ( - &'static ViewTarget, - &'static MotionBlurPipelineId, - &'static ViewPrepassTextures, - &'static MotionBlurUniform, - &'static Msaa, - ); - fn run( - &self, - _graph: &mut RenderGraphContext, - render_context: &mut RenderContext, - (view_target, pipeline_id, prepass_textures, motion_blur, msaa): QueryItem, - world: &World, - ) -> Result<(), NodeRunError> { - if motion_blur.samples == 0 || motion_blur.shutter_angle <= 0.0 { - return Ok(()); // We can skip running motion blur in these cases. - } - - let motion_blur_pipeline = world.resource::(); - let pipeline_cache = world.resource::(); - let settings_uniforms = world.resource::>(); - let Some(pipeline) = pipeline_cache.get_render_pipeline(pipeline_id.0) else { - return Ok(()); - }; + if motion_blur_uniform.samples == 0 || motion_blur_uniform.shutter_angle <= 0.0 { + return; // We can skip running motion blur in these cases. + } - let Some(settings_binding) = settings_uniforms.uniforms().binding() else { - return Ok(()); - }; - let (Some(prepass_motion_vectors_texture), Some(prepass_depth_texture)) = - (&prepass_textures.motion_vectors, &prepass_textures.depth) - else { - return Ok(()); - }; - let Some(globals_uniforms) = world.resource::().buffer.binding() else { - return Ok(()); - }; + let Some(pipeline) = pipeline_cache.get_render_pipeline(pipeline_id.0) else { + return; + }; - let diagnostics = render_context.diagnostic_recorder(); + let Some(settings_binding) = settings_uniforms.uniforms().binding() else { + return; + }; + let (Some(prepass_motion_vectors_texture), Some(prepass_depth_texture)) = + (&prepass_textures.motion_vectors, &prepass_textures.depth) + else { + return; + }; + let Some(globals_uniforms) = globals_buffer.buffer.binding() else { + return; + }; - let post_process = view_target.post_process_write(); + let post_process = view_target.post_process_write(); - let layout = if msaa.samples() == 1 { - &motion_blur_pipeline.layout - } else { - &motion_blur_pipeline.layout_msaa - }; + let layout = if msaa.samples() == 1 { + &motion_blur_pipeline.layout + } else { + &motion_blur_pipeline.layout_msaa + }; - let bind_group = render_context.render_device().create_bind_group( - Some("motion_blur_bind_group"), - &pipeline_cache.get_bind_group_layout(layout), - &BindGroupEntries::sequential(( - post_process.source, - &prepass_motion_vectors_texture.texture.default_view, - &prepass_depth_texture.texture.default_view, - &motion_blur_pipeline.sampler, - settings_binding.clone(), - globals_uniforms.clone(), - )), - ); + let bind_group = ctx.render_device().create_bind_group( + Some("motion_blur_bind_group"), + &pipeline_cache.get_bind_group_layout(layout), + &BindGroupEntries::sequential(( + post_process.source, + &prepass_motion_vectors_texture.texture.default_view, + &prepass_depth_texture.texture.default_view, + &motion_blur_pipeline.sampler, + settings_binding.clone(), + globals_uniforms.clone(), + )), + ); - let mut render_pass = render_context.begin_tracked_render_pass(RenderPassDescriptor { - label: Some("motion_blur"), - color_attachments: &[Some(RenderPassColorAttachment { - view: post_process.destination, - depth_slice: None, - resolve_target: None, - ops: Operations::default(), - })], - depth_stencil_attachment: None, - timestamp_writes: None, - occlusion_query_set: None, - }); - let pass_span = diagnostics.pass_span(&mut render_pass, "motion_blur"); + let diagnostics = ctx.diagnostic_recorder(); + let diagnostics = diagnostics.as_deref(); - render_pass.set_render_pipeline(pipeline); - render_pass.set_bind_group(0, &bind_group, &[]); - render_pass.draw(0..3, 0..1); + let mut render_pass = ctx.begin_tracked_render_pass(RenderPassDescriptor { + label: Some("motion_blur"), + color_attachments: &[Some(RenderPassColorAttachment { + view: post_process.destination, + depth_slice: None, + resolve_target: None, + ops: Operations::default(), + })], + depth_stencil_attachment: None, + timestamp_writes: None, + occlusion_query_set: None, + }); + let pass_span = diagnostics.pass_span(&mut render_pass, "motion_blur"); - pass_span.end(&mut render_pass); + render_pass.set_render_pipeline(pipeline); + render_pass.set_bind_group(0, &bind_group, &[]); + render_pass.draw(0..3, 0..1); - Ok(()) - } + pass_span.end(&mut render_pass); } diff --git a/crates/bevy_post_process/src/msaa_writeback.rs b/crates/bevy_post_process/src/msaa_writeback.rs index 0a35088f7f0f4..a8526361ee944 100644 --- a/crates/bevy_post_process/src/msaa_writeback.rs +++ b/crates/bevy_post_process/src/msaa_writeback.rs @@ -3,16 +3,14 @@ use bevy_camera::MsaaWriteback; use bevy_color::LinearRgba; use bevy_core_pipeline::{ blit::{BlitPipeline, BlitPipelineKey}, - core_2d::graph::{Core2d, Node2d}, - core_3d::graph::{Core3d, Node3d}, + schedule::{Core2d, Core2dSystems, Core3d, Core3dSystems}, }; -use bevy_ecs::{prelude::*, query::QueryItem}; +use bevy_ecs::prelude::*; use bevy_render::{ camera::ExtractedCamera, diagnostic::RecordDiagnostics, - render_graph::{NodeRunError, RenderGraphContext, RenderGraphExt, ViewNode, ViewNodeRunner}, render_resource::*, - renderer::RenderContext, + renderer::{RenderContext, ViewQuery}, view::{Msaa, ViewTarget}, Render, RenderApp, RenderSystems, }; @@ -31,98 +29,77 @@ impl Plugin for MsaaWritebackPlugin { Render, prepare_msaa_writeback_pipelines.in_set(RenderSystems::Prepare), ); - { - render_app - .add_render_graph_node::>( - Core2d, - Node2d::MsaaWriteback, - ) - .add_render_graph_edge(Core2d, Node2d::MsaaWriteback, Node2d::StartMainPass); - } - { - render_app - .add_render_graph_node::>( - Core3d, - Node3d::MsaaWriteback, - ) - .add_render_graph_edge(Core3d, Node3d::MsaaWriteback, Node3d::StartMainPass); - } + render_app.add_systems( + Core3d, + msaa_writeback.before(Core3dSystems::EndPrepasses), + ); + render_app.add_systems( + Core2d, + msaa_writeback.before(Core2dSystems::StartMainPass), + ); } } -#[derive(Default)] -pub struct MsaaWritebackNode; - -impl ViewNode for MsaaWritebackNode { - type ViewQuery = ( - &'static ViewTarget, - &'static MsaaWritebackBlitPipeline, - &'static Msaa, - ); - - fn run<'w>( - &self, - _graph: &mut RenderGraphContext, - render_context: &mut RenderContext<'w>, - (target, blit_pipeline_id, msaa): QueryItem<'w, '_, Self::ViewQuery>, - world: &'w World, - ) -> Result<(), NodeRunError> { - if *msaa == Msaa::Off { - return Ok(()); - } +pub(crate) fn msaa_writeback( + view: ViewQuery<(&ViewTarget, &MsaaWritebackBlitPipeline, &Msaa)>, + blit_pipeline: Res, + pipeline_cache: Res, + mut ctx: RenderContext, +) { + let (target, blit_pipeline_id, msaa) = view.into_inner(); - let blit_pipeline = world.resource::(); - let pipeline_cache = world.resource::(); - let Some(pipeline) = pipeline_cache.get_render_pipeline(blit_pipeline_id.0) else { - return Ok(()); - }; + if *msaa == Msaa::Off { + return; + } - let diagnostics = render_context.diagnostic_recorder(); - - // The current "main texture" needs to be bound as an input resource, and we need the "other" - // unused target to be the "resolve target" for the MSAA write. Therefore this is the same - // as a post process write! - let post_process = target.post_process_write(); - - let pass_descriptor = RenderPassDescriptor { - label: Some("msaa_writeback"), - // The target's "resolve target" is the "destination" in post_process. - // We will indirectly write the results to the "destination" using - // the MSAA resolve step. - color_attachments: &[Some(RenderPassColorAttachment { - // If MSAA is enabled, then the sampled texture will always exist - view: target.sampled_main_texture_view().unwrap(), - depth_slice: None, - resolve_target: Some(post_process.destination), - ops: Operations { - load: LoadOp::Clear(LinearRgba::BLACK.into()), - store: StoreOp::Store, - }, - })], - depth_stencil_attachment: None, - timestamp_writes: None, - occlusion_query_set: None, - }; + let Some(pipeline) = pipeline_cache.get_render_pipeline(blit_pipeline_id.0) else { + return; + }; + + // The current "main texture" needs to be bound as an input resource, and we need the "other" + // unused target to be the "resolve target" for the MSAA write. Therefore this is the same + // as a post process write! + let post_process = target.post_process_write(); + + let pass_descriptor = RenderPassDescriptor { + label: Some("msaa_writeback"), + // The target's "resolve target" is the "destination" in post_process. + // We will indirectly write the results to the "destination" using + // the MSAA resolve step. + color_attachments: &[Some(RenderPassColorAttachment { + // If MSAA is enabled, then the sampled texture will always exist + view: target.sampled_main_texture_view().unwrap(), + depth_slice: None, + resolve_target: Some(post_process.destination), + ops: Operations { + load: LoadOp::Clear(LinearRgba::BLACK.into()), + store: StoreOp::Store, + }, + })], + depth_stencil_attachment: None, + timestamp_writes: None, + occlusion_query_set: None, + }; + + let bind_group = blit_pipeline.create_bind_group( + ctx.render_device(), + post_process.source, + &pipeline_cache, + ); - let bind_group = blit_pipeline.create_bind_group( - render_context.render_device(), - post_process.source, - pipeline_cache, - ); + let diagnostics = ctx.diagnostic_recorder(); + let diagnostics = diagnostics.as_deref(); + let time_span = diagnostics.time_span(ctx.command_encoder(), "msaa_writeback"); - let mut render_pass = render_context - .command_encoder() - .begin_render_pass(&pass_descriptor); - let pass_span = diagnostics.pass_span(&mut render_pass, "msaa_writeback"); + { + let mut render_pass = ctx.command_encoder().begin_render_pass(&pass_descriptor); render_pass.set_pipeline(pipeline); render_pass.set_bind_group(0, &bind_group, &[]); render_pass.draw(0..3, 0..1); - - pass_span.end(&mut render_pass); - - Ok(()) } + + time_span.end(ctx.command_encoder()); } #[derive(Component)] diff --git a/crates/bevy_render/src/camera.rs b/crates/bevy_render/src/camera.rs index 27dac44807dd7..53daac237bc77 100644 --- a/crates/bevy_render/src/camera.rs +++ b/crates/bevy_render/src/camera.rs @@ -3,7 +3,6 @@ use crate::{ extract_component::{ExtractComponent, ExtractComponentPlugin}, extract_resource::{ExtractResource, ExtractResourcePlugin}, render_asset::RenderAssets, - render_graph::{CameraDriverNode, InternedRenderSubGraph, RenderGraph, RenderSubGraph}, render_resource::TextureView, sync_world::{RenderEntity, SyncToRenderWorld}, texture::{GpuImage, ManualTextureViews}, @@ -35,7 +34,7 @@ use bevy_ecs::{ query::{Has, QueryItem}, reflect::ReflectComponent, resource::Resource, - schedule::IntoScheduleConfigs, + schedule::{InternedScheduleLabel, IntoScheduleConfigs, ScheduleLabel}, system::{Commands, Query, Res, ResMut}, world::DeferredWorld, }; @@ -78,9 +77,6 @@ impl Plugin for CameraPlugin { .init_resource::() .add_systems(ExtractSchedule, extract_cameras) .add_systems(Render, sort_cameras.in_set(RenderSystems::ManageViews)); - let camera_driver_node = CameraDriverNode::new(render_app.world_mut()); - let mut render_graph = render_app.world_mut().resource_mut::(); - render_graph.add_node(crate::graph::CameraDriverLabel, camera_driver_node); } } } @@ -127,23 +123,23 @@ impl ExtractComponent for Camera3d { } } -/// Configures the [`RenderGraph`] name assigned to be run for a given [`Camera`] entity. +/// Configures the render schedule to be run for a given [`Camera`] entity. #[derive(Component, Debug, Deref, DerefMut, Reflect, Clone)] #[reflect(opaque)] #[reflect(Component, Debug, Clone)] -pub struct CameraRenderGraph(InternedRenderSubGraph); +pub struct CameraRenderGraph(pub InternedScheduleLabel); impl CameraRenderGraph { - /// Creates a new [`CameraRenderGraph`] from any string-like type. + /// Creates a new [`CameraRenderGraph`] from a schedule label. #[inline] - pub fn new(name: T) -> Self { - Self(name.intern()) + pub fn new(schedule: T) -> Self { + Self(schedule.intern()) } - /// Sets the graph name. + /// Sets the schedule. #[inline] - pub fn set(&mut self, name: T) { - self.0 = name.intern(); + pub fn set(&mut self, schedule: T) { + self.0 = schedule.intern(); } } @@ -406,7 +402,7 @@ pub struct ExtractedCamera { pub physical_viewport_size: Option, pub physical_target_size: Option, pub viewport: Option, - pub render_graph: InternedRenderSubGraph, + pub schedule: InternedScheduleLabel, pub order: isize, pub output_mode: CameraOutputMode, pub msaa_writeback: MsaaWriteback, @@ -533,7 +529,7 @@ pub fn extract_cameras( viewport: camera.viewport.clone(), physical_viewport_size: Some(viewport_size), physical_target_size: Some(target_size), - render_graph: camera_render_graph.0, + schedule: camera_render_graph.0, order: camera.order, output_mode: camera.output_mode, msaa_writeback: camera.msaa_writeback, diff --git a/crates/bevy_render/src/diagnostic/mod.rs b/crates/bevy_render/src/diagnostic/mod.rs index 885b343e86039..61881bb0dcb04 100644 --- a/crates/bevy_render/src/diagnostic/mod.rs +++ b/crates/bevy_render/src/diagnostic/mod.rs @@ -16,11 +16,10 @@ use bevy_app::{App, Plugin, PreUpdate}; use crate::{renderer::RenderAdapterInfo, RenderApp}; -use self::internal::{ - sync_diagnostics, DiagnosticsRecorder, Pass, RenderDiagnosticsMutex, WriteTimestamp, -}; +use self::internal::{sync_diagnostics, Pass, RenderDiagnosticsMutex, WriteTimestamp}; pub use self::{ erased_render_asset_diagnostic_plugin::ErasedRenderAssetDiagnosticPlugin, + internal::DiagnosticsRecorder, mesh_allocator_diagnostic_plugin::MeshAllocatorDiagnosticPlugin, render_asset_diagnostic_plugin::RenderAssetDiagnosticPlugin, }; @@ -194,3 +193,29 @@ impl RecordDiagnostics for Option> { } } } + +impl<'a, T: RecordDiagnostics> RecordDiagnostics for Option<&'a T> { + fn begin_time_span(&self, encoder: &mut E, name: Cow<'static, str>) { + if let Some(recorder) = self { + recorder.begin_time_span(encoder, name); + } + } + + fn end_time_span(&self, encoder: &mut E) { + if let Some(recorder) = self { + recorder.end_time_span(encoder); + } + } + + fn begin_pass_span(&self, pass: &mut P, name: Cow<'static, str>) { + if let Some(recorder) = self { + recorder.begin_pass_span(pass, name); + } + } + + fn end_pass_span(&self, pass: &mut P) { + if let Some(recorder) = self { + recorder.end_pass_span(pass); + } + } +} diff --git a/crates/bevy_render/src/gpu_readback.rs b/crates/bevy_render/src/gpu_readback.rs index efb79a9f7ea8c..c52a2771bdabf 100644 --- a/crates/bevy_render/src/gpu_readback.rs +++ b/crates/bevy_render/src/gpu_readback.rs @@ -5,7 +5,7 @@ use crate::{ Buffer, BufferUsages, CommandEncoder, Extent3d, TexelCopyBufferLayout, Texture, TextureFormat, }, - renderer::{render_system, RenderDevice}, + renderer::RenderDevice, storage::{GpuShaderStorageBuffer, ShaderStorageBuffer}, sync_world::MainEntity, texture::GpuImage, @@ -61,9 +61,8 @@ impl Plugin for GpuReadbackPlugin { Render, ( prepare_buffers.in_set(RenderSystems::PrepareResources), - map_buffers - .after(render_system) - .in_set(RenderSystems::Render), + // TODO: this should be in the graph somehow + map_buffers.in_set(RenderSystems::Cleanup), ), ); } diff --git a/crates/bevy_render/src/lib.rs b/crates/bevy_render/src/lib.rs index 2325139a0af98..f9db069bf4708 100644 --- a/crates/bevy_render/src/lib.rs +++ b/crates/bevy_render/src/lib.rs @@ -52,7 +52,6 @@ pub mod mesh; #[cfg(not(target_arch = "wasm32"))] pub mod pipelined_rendering; pub mod render_asset; -pub mod render_graph; pub mod render_phase; pub mod render_resource; pub mod renderer; @@ -279,13 +278,6 @@ impl DerefMut for MainWorld { } } -pub mod graph { - use crate::render_graph::RenderLabel; - - #[derive(Debug, Hash, PartialEq, Eq, Clone, RenderLabel)] - pub struct CameraDriverLabel; -} - #[derive(Resource)] struct FutureRenderResources(Arc>>); @@ -483,7 +475,7 @@ unsafe fn initialize_render_app(app: &mut App) { render_app .add_schedule(extract_schedule) .add_schedule(Render::base_schedule()) - .init_resource::() + .init_resource::() .insert_resource(app.world().resource::().clone()) .add_systems(ExtractSchedule, PipelineCache::extract_shaders) .add_systems( @@ -492,9 +484,8 @@ unsafe fn initialize_render_app(app: &mut App) { // This set applies the commands from the extract schedule while the render schedule // is running in parallel with the main app. apply_extract_commands.in_set(RenderSystems::ExtractCommands), - (PipelineCache::process_pipeline_queue_system, render_system) - .chain() - .in_set(RenderSystems::Render), + render_system.in_set(RenderSystems::Render), + PipelineCache::process_pipeline_queue_system.in_set(RenderSystems::Render), despawn_temporary_render_entities.in_set(RenderSystems::PostCleanup), ), ); diff --git a/crates/bevy_render/src/render_graph/app.rs b/crates/bevy_render/src/render_graph/app.rs deleted file mode 100644 index 879f28fe54015..0000000000000 --- a/crates/bevy_render/src/render_graph/app.rs +++ /dev/null @@ -1,174 +0,0 @@ -use bevy_app::{App, SubApp}; -use bevy_ecs::world::{FromWorld, World}; -use tracing::warn; - -use super::{IntoRenderNodeArray, Node, RenderGraph, RenderLabel, RenderSubGraph}; - -/// Adds common [`RenderGraph`] operations to [`SubApp`] (and [`App`]). -pub trait RenderGraphExt { - // Add a sub graph to the [`RenderGraph`] - fn add_render_sub_graph(&mut self, sub_graph: impl RenderSubGraph) -> &mut Self; - /// Add a [`Node`] to the [`RenderGraph`]: - /// * Create the [`Node`] using the [`FromWorld`] implementation - /// * Add it to the graph - fn add_render_graph_node( - &mut self, - sub_graph: impl RenderSubGraph, - node_label: impl RenderLabel, - ) -> &mut Self; - /// Automatically add the required node edges based on the given ordering - fn add_render_graph_edges( - &mut self, - sub_graph: impl RenderSubGraph, - edges: impl IntoRenderNodeArray, - ) -> &mut Self; - - /// Add node edge to the specified graph - fn add_render_graph_edge( - &mut self, - sub_graph: impl RenderSubGraph, - output_node: impl RenderLabel, - input_node: impl RenderLabel, - ) -> &mut Self; -} - -impl RenderGraphExt for World { - fn add_render_graph_node( - &mut self, - sub_graph: impl RenderSubGraph, - node_label: impl RenderLabel, - ) -> &mut Self { - let sub_graph = sub_graph.intern(); - let node = T::from_world(self); - let mut render_graph = self.get_resource_mut::().expect( - "RenderGraph not found. Make sure you are using add_render_graph_node on the RenderApp", - ); - if let Some(graph) = render_graph.get_sub_graph_mut(sub_graph) { - graph.add_node(node_label, node); - } else { - warn!( - "Tried adding a render graph node to {sub_graph:?} but the sub graph doesn't exist" - ); - } - self - } - - #[track_caller] - fn add_render_graph_edges( - &mut self, - sub_graph: impl RenderSubGraph, - edges: impl IntoRenderNodeArray, - ) -> &mut Self { - let sub_graph = sub_graph.intern(); - let mut render_graph = self.get_resource_mut::().expect( - "RenderGraph not found. Make sure you are using add_render_graph_edges on the RenderApp", - ); - if let Some(graph) = render_graph.get_sub_graph_mut(sub_graph) { - graph.add_node_edges(edges); - } else { - warn!( - "Tried adding render graph edges to {sub_graph:?} but the sub graph doesn't exist" - ); - } - self - } - - fn add_render_graph_edge( - &mut self, - sub_graph: impl RenderSubGraph, - output_node: impl RenderLabel, - input_node: impl RenderLabel, - ) -> &mut Self { - let sub_graph = sub_graph.intern(); - let mut render_graph = self.get_resource_mut::().expect( - "RenderGraph not found. Make sure you are using add_render_graph_edge on the RenderApp", - ); - if let Some(graph) = render_graph.get_sub_graph_mut(sub_graph) { - graph.add_node_edge(output_node, input_node); - } else { - warn!( - "Tried adding a render graph edge to {sub_graph:?} but the sub graph doesn't exist" - ); - } - self - } - - fn add_render_sub_graph(&mut self, sub_graph: impl RenderSubGraph) -> &mut Self { - let mut render_graph = self.get_resource_mut::().expect( - "RenderGraph not found. Make sure you are using add_render_sub_graph on the RenderApp", - ); - render_graph.add_sub_graph(sub_graph, RenderGraph::default()); - self - } -} - -impl RenderGraphExt for SubApp { - fn add_render_graph_node( - &mut self, - sub_graph: impl RenderSubGraph, - node_label: impl RenderLabel, - ) -> &mut Self { - World::add_render_graph_node::(self.world_mut(), sub_graph, node_label); - self - } - - fn add_render_graph_edge( - &mut self, - sub_graph: impl RenderSubGraph, - output_node: impl RenderLabel, - input_node: impl RenderLabel, - ) -> &mut Self { - World::add_render_graph_edge(self.world_mut(), sub_graph, output_node, input_node); - self - } - - #[track_caller] - fn add_render_graph_edges( - &mut self, - sub_graph: impl RenderSubGraph, - edges: impl IntoRenderNodeArray, - ) -> &mut Self { - World::add_render_graph_edges(self.world_mut(), sub_graph, edges); - self - } - - fn add_render_sub_graph(&mut self, sub_graph: impl RenderSubGraph) -> &mut Self { - World::add_render_sub_graph(self.world_mut(), sub_graph); - self - } -} - -impl RenderGraphExt for App { - fn add_render_graph_node( - &mut self, - sub_graph: impl RenderSubGraph, - node_label: impl RenderLabel, - ) -> &mut Self { - World::add_render_graph_node::(self.world_mut(), sub_graph, node_label); - self - } - - fn add_render_graph_edge( - &mut self, - sub_graph: impl RenderSubGraph, - output_node: impl RenderLabel, - input_node: impl RenderLabel, - ) -> &mut Self { - World::add_render_graph_edge(self.world_mut(), sub_graph, output_node, input_node); - self - } - - fn add_render_graph_edges( - &mut self, - sub_graph: impl RenderSubGraph, - edges: impl IntoRenderNodeArray, - ) -> &mut Self { - World::add_render_graph_edges(self.world_mut(), sub_graph, edges); - self - } - - fn add_render_sub_graph(&mut self, sub_graph: impl RenderSubGraph) -> &mut Self { - World::add_render_sub_graph(self.world_mut(), sub_graph); - self - } -} diff --git a/crates/bevy_render/src/render_graph/camera_driver_node.rs b/crates/bevy_render/src/render_graph/camera_driver_node.rs deleted file mode 100644 index 4ba4e93d33f0c..0000000000000 --- a/crates/bevy_render/src/render_graph/camera_driver_node.rs +++ /dev/null @@ -1,107 +0,0 @@ -use crate::{ - camera::{ExtractedCamera, SortedCameras}, - render_graph::{Node, NodeRunError, RenderGraphContext}, - renderer::RenderContext, - view::ExtractedWindows, -}; -use bevy_camera::{ClearColor, NormalizedRenderTarget}; -use bevy_ecs::{entity::ContainsEntity, prelude::QueryState, world::World}; -use bevy_platform::collections::HashSet; -use wgpu::{LoadOp, Operations, RenderPassColorAttachment, RenderPassDescriptor, StoreOp}; - -pub struct CameraDriverNode { - cameras: QueryState<&'static ExtractedCamera>, -} - -impl CameraDriverNode { - pub fn new(world: &mut World) -> Self { - Self { - cameras: world.query(), - } - } -} - -impl Node for CameraDriverNode { - fn update(&mut self, world: &mut World) { - self.cameras.update_archetypes(world); - } - fn run( - &self, - graph: &mut RenderGraphContext, - render_context: &mut RenderContext, - world: &World, - ) -> Result<(), NodeRunError> { - let sorted_cameras = world.resource::(); - let windows = world.resource::(); - let mut camera_windows = >::default(); - for sorted_camera in &sorted_cameras.0 { - let Ok(camera) = self.cameras.get_manual(world, sorted_camera.entity) else { - continue; - }; - - let mut run_graph = true; - if let Some(NormalizedRenderTarget::Window(window_ref)) = camera.target { - let window_entity = window_ref.entity(); - if windows - .windows - .get(&window_entity) - .is_some_and(|w| w.physical_width > 0 && w.physical_height > 0) - { - camera_windows.insert(window_entity); - } else { - // The window doesn't exist anymore or zero-sized so we don't need to run the graph - run_graph = false; - } - } - if run_graph { - graph.run_sub_graph( - camera.render_graph, - vec![], - Some(sorted_camera.entity), - Some(format!( - "Camera {} ({})", - sorted_camera.order, sorted_camera.entity - )), - )?; - } - } - - let clear_color_global = world.resource::(); - - // wgpu (and some backends) require doing work for swap chains if you call `get_current_texture()` and `present()` - // This ensures that Bevy doesn't crash, even when there are no cameras (and therefore no work submitted). - for (id, window) in world.resource::().iter() { - if camera_windows.contains(id) && render_context.has_commands() { - continue; - } - - let Some(swap_chain_texture) = &window.swap_chain_texture_view else { - continue; - }; - - #[cfg(feature = "trace")] - let _span = tracing::info_span!("no_camera_clear_pass").entered(); - let pass_descriptor = RenderPassDescriptor { - label: Some("no_camera_clear_pass"), - color_attachments: &[Some(RenderPassColorAttachment { - view: swap_chain_texture, - depth_slice: None, - resolve_target: None, - ops: Operations { - load: LoadOp::Clear(clear_color_global.to_linear().into()), - store: StoreOp::Store, - }, - })], - depth_stencil_attachment: None, - timestamp_writes: None, - occlusion_query_set: None, - }; - - render_context - .command_encoder() - .begin_render_pass(&pass_descriptor); - } - - Ok(()) - } -} diff --git a/crates/bevy_render/src/render_graph/context.rs b/crates/bevy_render/src/render_graph/context.rs deleted file mode 100644 index b1f35edc1d1d8..0000000000000 --- a/crates/bevy_render/src/render_graph/context.rs +++ /dev/null @@ -1,286 +0,0 @@ -use crate::{ - render_graph::{NodeState, RenderGraph, SlotInfos, SlotLabel, SlotType, SlotValue}, - render_resource::{Buffer, Sampler, TextureView}, -}; -use alloc::borrow::Cow; -use bevy_ecs::{entity::Entity, intern::Interned}; -use thiserror::Error; - -use super::{InternedRenderSubGraph, RenderLabel, RenderSubGraph}; - -/// A command that signals the graph runner to run the sub graph corresponding to the `sub_graph` -/// with the specified `inputs` next. -pub struct RunSubGraph { - pub sub_graph: InternedRenderSubGraph, - pub inputs: Vec, - pub view_entity: Option, - pub debug_group: Option, -} - -/// The context with all graph information required to run a [`Node`](super::Node). -/// This context is created for each node by the render graph runner. -/// -/// The slot input can be read from here and the outputs must be written back to the context for -/// passing them onto the next node. -/// -/// Sub graphs can be queued for running by adding a [`RunSubGraph`] command to the context. -/// After the node has finished running the graph runner is responsible for executing the sub graphs. -pub struct RenderGraphContext<'a> { - graph: &'a RenderGraph, - node: &'a NodeState, - inputs: &'a [SlotValue], - outputs: &'a mut [Option], - run_sub_graphs: Vec, - /// The `view_entity` associated with the render graph being executed - /// This is optional because you aren't required to have a `view_entity` for a node. - /// For example, compute shader nodes don't have one. - /// It should always be set when the [`RenderGraph`] is running on a View. - view_entity: Option, -} - -impl<'a> RenderGraphContext<'a> { - /// Creates a new render graph context for the `node`. - pub fn new( - graph: &'a RenderGraph, - node: &'a NodeState, - inputs: &'a [SlotValue], - outputs: &'a mut [Option], - ) -> Self { - Self { - graph, - node, - inputs, - outputs, - run_sub_graphs: Vec::new(), - view_entity: None, - } - } - - /// Returns the input slot values for the node. - #[inline] - pub fn inputs(&self) -> &[SlotValue] { - self.inputs - } - - /// Returns the [`SlotInfos`] of the inputs. - pub fn input_info(&self) -> &SlotInfos { - &self.node.input_slots - } - - /// Returns the [`SlotInfos`] of the outputs. - pub fn output_info(&self) -> &SlotInfos { - &self.node.output_slots - } - - /// Retrieves the input slot value referenced by the `label`. - pub fn get_input(&self, label: impl Into) -> Result<&SlotValue, InputSlotError> { - let label = label.into(); - let index = self - .input_info() - .get_slot_index(label.clone()) - .ok_or(InputSlotError::InvalidSlot(label))?; - Ok(&self.inputs[index]) - } - - // TODO: should this return an Arc or a reference? - /// Retrieves the input slot value referenced by the `label` as a [`TextureView`]. - pub fn get_input_texture( - &self, - label: impl Into, - ) -> Result<&TextureView, InputSlotError> { - let label = label.into(); - match self.get_input(label.clone())? { - SlotValue::TextureView(value) => Ok(value), - value => Err(InputSlotError::MismatchedSlotType { - label, - actual: value.slot_type(), - expected: SlotType::TextureView, - }), - } - } - - /// Retrieves the input slot value referenced by the `label` as a [`Sampler`]. - pub fn get_input_sampler( - &self, - label: impl Into, - ) -> Result<&Sampler, InputSlotError> { - let label = label.into(); - match self.get_input(label.clone())? { - SlotValue::Sampler(value) => Ok(value), - value => Err(InputSlotError::MismatchedSlotType { - label, - actual: value.slot_type(), - expected: SlotType::Sampler, - }), - } - } - - /// Retrieves the input slot value referenced by the `label` as a [`Buffer`]. - pub fn get_input_buffer(&self, label: impl Into) -> Result<&Buffer, InputSlotError> { - let label = label.into(); - match self.get_input(label.clone())? { - SlotValue::Buffer(value) => Ok(value), - value => Err(InputSlotError::MismatchedSlotType { - label, - actual: value.slot_type(), - expected: SlotType::Buffer, - }), - } - } - - /// Retrieves the input slot value referenced by the `label` as an [`Entity`]. - pub fn get_input_entity(&self, label: impl Into) -> Result { - let label = label.into(); - match self.get_input(label.clone())? { - SlotValue::Entity(value) => Ok(*value), - value => Err(InputSlotError::MismatchedSlotType { - label, - actual: value.slot_type(), - expected: SlotType::Entity, - }), - } - } - - /// Sets the output slot value referenced by the `label`. - pub fn set_output( - &mut self, - label: impl Into, - value: impl Into, - ) -> Result<(), OutputSlotError> { - let label = label.into(); - let value = value.into(); - let slot_index = self - .output_info() - .get_slot_index(label.clone()) - .ok_or_else(|| OutputSlotError::InvalidSlot(label.clone()))?; - let slot = self - .output_info() - .get_slot(slot_index) - .expect("slot is valid"); - if value.slot_type() != slot.slot_type { - return Err(OutputSlotError::MismatchedSlotType { - label, - actual: slot.slot_type, - expected: value.slot_type(), - }); - } - self.outputs[slot_index] = Some(value); - Ok(()) - } - - pub fn view_entity(&self) -> Entity { - self.view_entity.unwrap() - } - - pub fn get_view_entity(&self) -> Option { - self.view_entity - } - - pub fn set_view_entity(&mut self, view_entity: Entity) { - self.view_entity = Some(view_entity); - } - - /// Queues up a sub graph for execution after the node has finished running. - pub fn run_sub_graph( - &mut self, - name: impl RenderSubGraph, - inputs: Vec, - view_entity: Option, - debug_group: Option, - ) -> Result<(), RunSubGraphError> { - let name = name.intern(); - let sub_graph = self - .graph - .get_sub_graph(name) - .ok_or(RunSubGraphError::MissingSubGraph(name))?; - if let Some(input_node) = sub_graph.get_input_node() { - for (i, input_slot) in input_node.input_slots.iter().enumerate() { - if let Some(input_value) = inputs.get(i) { - if input_slot.slot_type != input_value.slot_type() { - return Err(RunSubGraphError::MismatchedInputSlotType { - graph_name: name, - slot_index: i, - actual: input_value.slot_type(), - expected: input_slot.slot_type, - label: input_slot.name.clone().into(), - }); - } - } else { - return Err(RunSubGraphError::MissingInput { - slot_index: i, - slot_name: input_slot.name.clone(), - graph_name: name, - }); - } - } - } else if !inputs.is_empty() { - return Err(RunSubGraphError::SubGraphHasNoInputs(name)); - } - - self.run_sub_graphs.push(RunSubGraph { - sub_graph: name, - inputs, - view_entity, - debug_group, - }); - - Ok(()) - } - - /// Returns a human-readable label for this node, for debugging purposes. - pub fn label(&self) -> Interned { - self.node.label - } - - /// Finishes the context for this [`Node`](super::Node) by - /// returning the sub graphs to run next. - pub fn finish(self) -> Vec { - self.run_sub_graphs - } -} - -#[derive(Error, Debug, Eq, PartialEq)] -pub enum RunSubGraphError { - #[error("attempted to run sub-graph `{0:?}`, but it does not exist")] - MissingSubGraph(InternedRenderSubGraph), - #[error("attempted to pass inputs to sub-graph `{0:?}`, which has no input slots")] - SubGraphHasNoInputs(InternedRenderSubGraph), - #[error("sub graph (name: `{graph_name:?}`) could not be run because slot `{slot_name}` at index {slot_index} has no value")] - MissingInput { - slot_index: usize, - slot_name: Cow<'static, str>, - graph_name: InternedRenderSubGraph, - }, - #[error("attempted to use the wrong type for input slot")] - MismatchedInputSlotType { - graph_name: InternedRenderSubGraph, - slot_index: usize, - label: SlotLabel, - expected: SlotType, - actual: SlotType, - }, -} - -#[derive(Error, Debug, Eq, PartialEq)] -pub enum OutputSlotError { - #[error("output slot `{0:?}` does not exist")] - InvalidSlot(SlotLabel), - #[error("attempted to output a value of type `{actual}` to output slot `{label:?}`, which has type `{expected}`")] - MismatchedSlotType { - label: SlotLabel, - expected: SlotType, - actual: SlotType, - }, -} - -#[derive(Error, Debug, Eq, PartialEq)] -pub enum InputSlotError { - #[error("input slot `{0:?}` does not exist")] - InvalidSlot(SlotLabel), - #[error("attempted to retrieve a value of type `{actual}` from input slot `{label:?}`, which has type `{expected}`")] - MismatchedSlotType { - label: SlotLabel, - expected: SlotType, - actual: SlotType, - }, -} diff --git a/crates/bevy_render/src/render_graph/edge.rs b/crates/bevy_render/src/render_graph/edge.rs deleted file mode 100644 index 199b7e8abbd96..0000000000000 --- a/crates/bevy_render/src/render_graph/edge.rs +++ /dev/null @@ -1,57 +0,0 @@ -use super::InternedRenderLabel; - -/// An edge, which connects two [`Nodes`](super::Node) in -/// a [`RenderGraph`](crate::render_graph::RenderGraph). -/// -/// They are used to describe the ordering (which node has to run first) -/// and may be of two kinds: [`NodeEdge`](Self::NodeEdge) and [`SlotEdge`](Self::SlotEdge). -/// -/// Edges are added via the [`RenderGraph::add_node_edge`] and the -/// [`RenderGraph::add_slot_edge`] methods. -/// -/// The former simply states that the `output_node` has to be run before the `input_node`, -/// while the later connects an output slot of the `output_node` -/// with an input slot of the `input_node` to pass additional data along. -/// For more information see [`SlotType`](super::SlotType). -/// -/// [`RenderGraph::add_node_edge`]: crate::render_graph::RenderGraph::add_node_edge -/// [`RenderGraph::add_slot_edge`]: crate::render_graph::RenderGraph::add_slot_edge -#[derive(Clone, Debug, Eq, PartialEq)] -pub enum Edge { - /// An edge describing to ordering of both nodes (`output_node` before `input_node`) - /// and connecting the output slot at the `output_index` of the `output_node` - /// with the slot at the `input_index` of the `input_node`. - SlotEdge { - input_node: InternedRenderLabel, - input_index: usize, - output_node: InternedRenderLabel, - output_index: usize, - }, - /// An edge describing to ordering of both nodes (`output_node` before `input_node`). - NodeEdge { - input_node: InternedRenderLabel, - output_node: InternedRenderLabel, - }, -} - -impl Edge { - /// Returns the id of the `input_node`. - pub fn get_input_node(&self) -> InternedRenderLabel { - match self { - Edge::SlotEdge { input_node, .. } | Edge::NodeEdge { input_node, .. } => *input_node, - } - } - - /// Returns the id of the `output_node`. - pub fn get_output_node(&self) -> InternedRenderLabel { - match self { - Edge::SlotEdge { output_node, .. } | Edge::NodeEdge { output_node, .. } => *output_node, - } - } -} - -#[derive(PartialEq, Eq)] -pub enum EdgeExistence { - Exists, - DoesNotExist, -} diff --git a/crates/bevy_render/src/render_graph/graph.rs b/crates/bevy_render/src/render_graph/graph.rs deleted file mode 100644 index b7f8328610054..0000000000000 --- a/crates/bevy_render/src/render_graph/graph.rs +++ /dev/null @@ -1,918 +0,0 @@ -use crate::{ - render_graph::{ - Edge, Node, NodeRunError, NodeState, RenderGraphContext, RenderGraphError, RenderLabel, - SlotInfo, SlotLabel, - }, - renderer::RenderContext, -}; -use bevy_ecs::{define_label, intern::Interned, prelude::World, resource::Resource}; -use bevy_platform::collections::HashMap; -use core::fmt::Debug; - -use super::{EdgeExistence, InternedRenderLabel, IntoRenderNodeArray}; - -pub use bevy_render_macros::RenderSubGraph; - -define_label!( - #[diagnostic::on_unimplemented( - note = "consider annotating `{Self}` with `#[derive(RenderSubGraph)]`" - )] - /// A strongly-typed class of labels used to identify a [`SubGraph`] in a render graph. - RenderSubGraph, - RENDER_SUB_GRAPH_INTERNER -); - -/// A shorthand for `Interned`. -pub type InternedRenderSubGraph = Interned; - -/// The render graph configures the modular and re-usable render logic. -/// -/// It is a retained and stateless (nodes themselves may have their own internal state) structure, -/// which can not be modified while it is executed by the graph runner. -/// -/// The render graph runner is responsible for executing the entire graph each frame. -/// It will execute each node in the graph in the correct order, based on the edges between the nodes. -/// -/// It consists of three main components: [`Nodes`](Node), [`Edges`](Edge) -/// and [`Slots`](super::SlotType). -/// -/// Nodes are responsible for generating draw calls and operating on input and output slots. -/// Edges specify the order of execution for nodes and connect input and output slots together. -/// Slots describe the render resources created or used by the nodes. -/// -/// Additionally a render graph can contain multiple sub graphs, which are run by the -/// corresponding nodes. Every render graph can have its own optional input node. -/// -/// ## Example -/// Here is a simple render graph example with two nodes connected by a node edge. -/// ```ignore -/// # TODO: Remove when #10645 is fixed -/// # use bevy_app::prelude::*; -/// # use bevy_ecs::prelude::World; -/// # use bevy_render::render_graph::{RenderGraph, RenderLabel, Node, RenderGraphContext, NodeRunError}; -/// # use bevy_render::renderer::RenderContext; -/// # -/// #[derive(RenderLabel)] -/// enum Labels { -/// A, -/// B, -/// } -/// -/// # struct MyNode; -/// # -/// # impl Node for MyNode { -/// # fn run(&self, graph: &mut RenderGraphContext, render_context: &mut RenderContext, world: &World) -> Result<(), NodeRunError> { -/// # unimplemented!() -/// # } -/// # } -/// # -/// let mut graph = RenderGraph::default(); -/// graph.add_node(Labels::A, MyNode); -/// graph.add_node(Labels::B, MyNode); -/// graph.add_node_edge(Labels::B, Labels::A); -/// ``` -#[derive(Resource, Default)] -pub struct RenderGraph { - nodes: HashMap, - sub_graphs: HashMap, -} - -/// The label for the input node of a graph. Used to connect other nodes to it. -#[derive(Debug, Hash, PartialEq, Eq, Clone, RenderLabel)] -pub struct GraphInput; - -impl RenderGraph { - /// Updates all nodes and sub graphs of the render graph. Should be called before executing it. - pub fn update(&mut self, world: &mut World) { - for node in self.nodes.values_mut() { - node.node.update(world); - } - - for sub_graph in self.sub_graphs.values_mut() { - sub_graph.update(world); - } - } - - /// Creates an [`GraphInputNode`] with the specified slots if not already present. - pub fn set_input(&mut self, inputs: Vec) { - assert!( - matches!( - self.get_node_state(GraphInput), - Err(RenderGraphError::InvalidNode(_)) - ), - "Graph already has an input node" - ); - - self.add_node(GraphInput, GraphInputNode { inputs }); - } - - /// Returns the [`NodeState`] of the input node of this graph. - /// - /// # See also - /// - /// - [`input_node`](Self::input_node) for an unchecked version. - #[inline] - pub fn get_input_node(&self) -> Option<&NodeState> { - self.get_node_state(GraphInput).ok() - } - - /// Returns the [`NodeState`] of the input node of this graph. - /// - /// # Panics - /// - /// Panics if there is no input node set. - /// - /// # See also - /// - /// - [`get_input_node`](Self::get_input_node) for a version which returns an [`Option`] instead. - #[inline] - pub fn input_node(&self) -> &NodeState { - self.get_input_node().unwrap() - } - - /// Adds the `node` with the `label` to the graph. - /// If the label is already present replaces it instead. - pub fn add_node(&mut self, label: impl RenderLabel, node: T) - where - T: Node, - { - let label = label.intern(); - let node_state = NodeState::new(label, node); - self.nodes.insert(label, node_state); - } - - /// Add `node_edge`s based on the order of the given `edges` array. - /// - /// Defining an edge that already exists is not considered an error with this api. - /// It simply won't create a new edge. - #[track_caller] - pub fn add_node_edges(&mut self, edges: impl IntoRenderNodeArray) { - for window in edges.into_array().windows(2) { - let [a, b] = window else { - break; - }; - if let Err(err) = self.try_add_node_edge(*a, *b) { - match err { - // Already existing edges are very easy to produce with this api - // and shouldn't cause a panic - RenderGraphError::EdgeAlreadyExists(_) => {} - _ => panic!("{err}"), - } - } - } - } - - /// Removes the `node` with the `label` from the graph. - /// If the label does not exist, nothing happens. - pub fn remove_node(&mut self, label: impl RenderLabel) -> Result<(), RenderGraphError> { - let label = label.intern(); - if let Some(node_state) = self.nodes.remove(&label) { - // Remove all edges from other nodes to this one. Note that as we're removing this - // node, we don't need to remove its input edges - for input_edge in node_state.edges.input_edges() { - match input_edge { - Edge::SlotEdge { output_node, .. } - | Edge::NodeEdge { - input_node: _, - output_node, - } => { - if let Ok(output_node) = self.get_node_state_mut(*output_node) { - output_node.edges.remove_output_edge(input_edge.clone())?; - } - } - } - } - // Remove all edges from this node to other nodes. Note that as we're removing this - // node, we don't need to remove its output edges - for output_edge in node_state.edges.output_edges() { - match output_edge { - Edge::SlotEdge { - output_node: _, - output_index: _, - input_node, - input_index: _, - } - | Edge::NodeEdge { - output_node: _, - input_node, - } => { - if let Ok(input_node) = self.get_node_state_mut(*input_node) { - input_node.edges.remove_input_edge(output_edge.clone())?; - } - } - } - } - } - - Ok(()) - } - - /// Retrieves the [`NodeState`] referenced by the `label`. - pub fn get_node_state(&self, label: impl RenderLabel) -> Result<&NodeState, RenderGraphError> { - let label = label.intern(); - self.nodes - .get(&label) - .ok_or(RenderGraphError::InvalidNode(label)) - } - - /// Retrieves the [`NodeState`] referenced by the `label` mutably. - pub fn get_node_state_mut( - &mut self, - label: impl RenderLabel, - ) -> Result<&mut NodeState, RenderGraphError> { - let label = label.intern(); - self.nodes - .get_mut(&label) - .ok_or(RenderGraphError::InvalidNode(label)) - } - - /// Retrieves the [`Node`] referenced by the `label`. - pub fn get_node(&self, label: impl RenderLabel) -> Result<&T, RenderGraphError> - where - T: Node, - { - self.get_node_state(label).and_then(|n| n.node()) - } - - /// Retrieves the [`Node`] referenced by the `label` mutably. - pub fn get_node_mut(&mut self, label: impl RenderLabel) -> Result<&mut T, RenderGraphError> - where - T: Node, - { - self.get_node_state_mut(label).and_then(|n| n.node_mut()) - } - - /// Adds the [`Edge::SlotEdge`] to the graph. This guarantees that the `output_node` - /// is run before the `input_node` and also connects the `output_slot` to the `input_slot`. - /// - /// Fails if any invalid [`RenderLabel`]s or [`SlotLabel`]s are given. - /// - /// # See also - /// - /// - [`add_slot_edge`](Self::add_slot_edge) for an infallible version. - pub fn try_add_slot_edge( - &mut self, - output_node: impl RenderLabel, - output_slot: impl Into, - input_node: impl RenderLabel, - input_slot: impl Into, - ) -> Result<(), RenderGraphError> { - let output_slot = output_slot.into(); - let input_slot = input_slot.into(); - - let output_node = output_node.intern(); - let input_node = input_node.intern(); - - let output_index = self - .get_node_state(output_node)? - .output_slots - .get_slot_index(output_slot.clone()) - .ok_or(RenderGraphError::InvalidOutputNodeSlot(output_slot))?; - let input_index = self - .get_node_state(input_node)? - .input_slots - .get_slot_index(input_slot.clone()) - .ok_or(RenderGraphError::InvalidInputNodeSlot(input_slot))?; - - let edge = Edge::SlotEdge { - output_node, - output_index, - input_node, - input_index, - }; - - self.validate_edge(&edge, EdgeExistence::DoesNotExist)?; - - { - let output_node = self.get_node_state_mut(output_node)?; - output_node.edges.add_output_edge(edge.clone())?; - } - let input_node = self.get_node_state_mut(input_node)?; - input_node.edges.add_input_edge(edge)?; - - Ok(()) - } - - /// Adds the [`Edge::SlotEdge`] to the graph. This guarantees that the `output_node` - /// is run before the `input_node` and also connects the `output_slot` to the `input_slot`. - /// - /// # Panics - /// - /// Any invalid [`RenderLabel`]s or [`SlotLabel`]s are given. - /// - /// # See also - /// - /// - [`try_add_slot_edge`](Self::try_add_slot_edge) for a fallible version. - pub fn add_slot_edge( - &mut self, - output_node: impl RenderLabel, - output_slot: impl Into, - input_node: impl RenderLabel, - input_slot: impl Into, - ) { - self.try_add_slot_edge(output_node, output_slot, input_node, input_slot) - .unwrap(); - } - - /// Removes the [`Edge::SlotEdge`] from the graph. If any nodes or slots do not exist then - /// nothing happens. - pub fn remove_slot_edge( - &mut self, - output_node: impl RenderLabel, - output_slot: impl Into, - input_node: impl RenderLabel, - input_slot: impl Into, - ) -> Result<(), RenderGraphError> { - let output_slot = output_slot.into(); - let input_slot = input_slot.into(); - - let output_node = output_node.intern(); - let input_node = input_node.intern(); - - let output_index = self - .get_node_state(output_node)? - .output_slots - .get_slot_index(output_slot.clone()) - .ok_or(RenderGraphError::InvalidOutputNodeSlot(output_slot))?; - let input_index = self - .get_node_state(input_node)? - .input_slots - .get_slot_index(input_slot.clone()) - .ok_or(RenderGraphError::InvalidInputNodeSlot(input_slot))?; - - let edge = Edge::SlotEdge { - output_node, - output_index, - input_node, - input_index, - }; - - self.validate_edge(&edge, EdgeExistence::Exists)?; - - { - let output_node = self.get_node_state_mut(output_node)?; - output_node.edges.remove_output_edge(edge.clone())?; - } - let input_node = self.get_node_state_mut(input_node)?; - input_node.edges.remove_input_edge(edge)?; - - Ok(()) - } - - /// Adds the [`Edge::NodeEdge`] to the graph. This guarantees that the `output_node` - /// is run before the `input_node`. - /// - /// Fails if any invalid [`RenderLabel`] is given. - /// - /// # See also - /// - /// - [`add_node_edge`](Self::add_node_edge) for an infallible version. - pub fn try_add_node_edge( - &mut self, - output_node: impl RenderLabel, - input_node: impl RenderLabel, - ) -> Result<(), RenderGraphError> { - let output_node = output_node.intern(); - let input_node = input_node.intern(); - - let edge = Edge::NodeEdge { - output_node, - input_node, - }; - - self.validate_edge(&edge, EdgeExistence::DoesNotExist)?; - - { - let output_node = self.get_node_state_mut(output_node)?; - output_node.edges.add_output_edge(edge.clone())?; - } - let input_node = self.get_node_state_mut(input_node)?; - input_node.edges.add_input_edge(edge)?; - - Ok(()) - } - - /// Adds the [`Edge::NodeEdge`] to the graph. This guarantees that the `output_node` - /// is run before the `input_node`. - /// - /// # Panics - /// - /// Panics if any invalid [`RenderLabel`] is given. - /// - /// # See also - /// - /// - [`try_add_node_edge`](Self::try_add_node_edge) for a fallible version. - pub fn add_node_edge(&mut self, output_node: impl RenderLabel, input_node: impl RenderLabel) { - self.try_add_node_edge(output_node, input_node).unwrap(); - } - - /// Removes the [`Edge::NodeEdge`] from the graph. If either node does not exist then nothing - /// happens. - pub fn remove_node_edge( - &mut self, - output_node: impl RenderLabel, - input_node: impl RenderLabel, - ) -> Result<(), RenderGraphError> { - let output_node = output_node.intern(); - let input_node = input_node.intern(); - - let edge = Edge::NodeEdge { - output_node, - input_node, - }; - - self.validate_edge(&edge, EdgeExistence::Exists)?; - - { - let output_node = self.get_node_state_mut(output_node)?; - output_node.edges.remove_output_edge(edge.clone())?; - } - let input_node = self.get_node_state_mut(input_node)?; - input_node.edges.remove_input_edge(edge)?; - - Ok(()) - } - - /// Verifies that the edge existence is as expected and - /// checks that slot edges are connected correctly. - pub fn validate_edge( - &mut self, - edge: &Edge, - should_exist: EdgeExistence, - ) -> Result<(), RenderGraphError> { - if should_exist == EdgeExistence::Exists && !self.has_edge(edge) { - return Err(RenderGraphError::EdgeDoesNotExist(edge.clone())); - } else if should_exist == EdgeExistence::DoesNotExist && self.has_edge(edge) { - return Err(RenderGraphError::EdgeAlreadyExists(edge.clone())); - } - - match *edge { - Edge::SlotEdge { - output_node, - output_index, - input_node, - input_index, - } => { - let output_node_state = self.get_node_state(output_node)?; - let input_node_state = self.get_node_state(input_node)?; - - let output_slot = output_node_state - .output_slots - .get_slot(output_index) - .ok_or(RenderGraphError::InvalidOutputNodeSlot(SlotLabel::Index( - output_index, - )))?; - let input_slot = input_node_state.input_slots.get_slot(input_index).ok_or( - RenderGraphError::InvalidInputNodeSlot(SlotLabel::Index(input_index)), - )?; - - if let Some(Edge::SlotEdge { - output_node: current_output_node, - .. - }) = input_node_state.edges.input_edges().iter().find(|e| { - if let Edge::SlotEdge { - input_index: current_input_index, - .. - } = e - { - input_index == *current_input_index - } else { - false - } - }) && should_exist == EdgeExistence::DoesNotExist - { - return Err(RenderGraphError::NodeInputSlotAlreadyOccupied { - node: input_node, - input_slot: input_index, - occupied_by_node: *current_output_node, - }); - } - - if output_slot.slot_type != input_slot.slot_type { - return Err(RenderGraphError::MismatchedNodeSlots { - output_node, - output_slot: output_index, - input_node, - input_slot: input_index, - }); - } - } - Edge::NodeEdge { .. } => { /* nothing to validate here */ } - } - - Ok(()) - } - - /// Checks whether the `edge` already exists in the graph. - pub fn has_edge(&self, edge: &Edge) -> bool { - let output_node_state = self.get_node_state(edge.get_output_node()); - let input_node_state = self.get_node_state(edge.get_input_node()); - if let Ok(output_node_state) = output_node_state - && output_node_state.edges.output_edges().contains(edge) - && let Ok(input_node_state) = input_node_state - && input_node_state.edges.input_edges().contains(edge) - { - return true; - } - - false - } - - /// Returns an iterator over the [`NodeStates`](NodeState). - pub fn iter_nodes(&self) -> impl Iterator { - self.nodes.values() - } - - /// Returns an iterator over the [`NodeStates`](NodeState), that allows modifying each value. - pub fn iter_nodes_mut(&mut self) -> impl Iterator { - self.nodes.values_mut() - } - - /// Returns an iterator over the sub graphs. - pub fn iter_sub_graphs(&self) -> impl Iterator { - self.sub_graphs.iter().map(|(name, graph)| (*name, graph)) - } - - /// Returns an iterator over the sub graphs, that allows modifying each value. - pub fn iter_sub_graphs_mut( - &mut self, - ) -> impl Iterator { - self.sub_graphs - .iter_mut() - .map(|(name, graph)| (*name, graph)) - } - - /// Returns an iterator over a tuple of the input edges and the corresponding output nodes - /// for the node referenced by the label. - pub fn iter_node_inputs( - &self, - label: impl RenderLabel, - ) -> Result, RenderGraphError> { - let node = self.get_node_state(label)?; - Ok(node - .edges - .input_edges() - .iter() - .map(|edge| (edge, edge.get_output_node())) - .map(move |(edge, output_node)| (edge, self.get_node_state(output_node).unwrap()))) - } - - /// Returns an iterator over a tuple of the output edges and the corresponding input nodes - /// for the node referenced by the label. - pub fn iter_node_outputs( - &self, - label: impl RenderLabel, - ) -> Result, RenderGraphError> { - let node = self.get_node_state(label)?; - Ok(node - .edges - .output_edges() - .iter() - .map(|edge| (edge, edge.get_input_node())) - .map(move |(edge, input_node)| (edge, self.get_node_state(input_node).unwrap()))) - } - - /// Adds the `sub_graph` with the `label` to the graph. - /// If the label is already present replaces it instead. - pub fn add_sub_graph(&mut self, label: impl RenderSubGraph, sub_graph: RenderGraph) { - self.sub_graphs.insert(label.intern(), sub_graph); - } - - /// Removes the `sub_graph` with the `label` from the graph. - /// If the label does not exist then nothing happens. - pub fn remove_sub_graph(&mut self, label: impl RenderSubGraph) { - self.sub_graphs.remove(&label.intern()); - } - - /// Retrieves the sub graph corresponding to the `label`. - pub fn get_sub_graph(&self, label: impl RenderSubGraph) -> Option<&RenderGraph> { - self.sub_graphs.get(&label.intern()) - } - - /// Retrieves the sub graph corresponding to the `label` mutably. - pub fn get_sub_graph_mut(&mut self, label: impl RenderSubGraph) -> Option<&mut RenderGraph> { - self.sub_graphs.get_mut(&label.intern()) - } - - /// Retrieves the sub graph corresponding to the `label`. - /// - /// # Panics - /// - /// Panics if any invalid subgraph label is given. - /// - /// # See also - /// - /// - [`get_sub_graph`](Self::get_sub_graph) for a fallible version. - pub fn sub_graph(&self, label: impl RenderSubGraph) -> &RenderGraph { - let label = label.intern(); - self.sub_graphs - .get(&label) - .unwrap_or_else(|| panic!("Subgraph {label:?} not found")) - } - - /// Retrieves the sub graph corresponding to the `label` mutably. - /// - /// # Panics - /// - /// Panics if any invalid subgraph label is given. - /// - /// # See also - /// - /// - [`get_sub_graph_mut`](Self::get_sub_graph_mut) for a fallible version. - pub fn sub_graph_mut(&mut self, label: impl RenderSubGraph) -> &mut RenderGraph { - let label = label.intern(); - self.sub_graphs - .get_mut(&label) - .unwrap_or_else(|| panic!("Subgraph {label:?} not found")) - } -} - -impl Debug for RenderGraph { - fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { - for node in self.iter_nodes() { - writeln!(f, "{:?}", node.label)?; - writeln!(f, " in: {:?}", node.input_slots)?; - writeln!(f, " out: {:?}", node.output_slots)?; - } - - Ok(()) - } -} - -/// A [`Node`] which acts as an entry point for a [`RenderGraph`] with custom inputs. -/// It has the same input and output slots and simply copies them over when run. -pub struct GraphInputNode { - inputs: Vec, -} - -impl Node for GraphInputNode { - fn input(&self) -> Vec { - self.inputs.clone() - } - - fn output(&self) -> Vec { - self.inputs.clone() - } - - fn run( - &self, - graph: &mut RenderGraphContext, - _render_context: &mut RenderContext, - _world: &World, - ) -> Result<(), NodeRunError> { - for i in 0..graph.inputs().len() { - let input = graph.inputs()[i].clone(); - graph.set_output(i, input)?; - } - Ok(()) - } -} - -#[cfg(test)] -mod tests { - use crate::{ - render_graph::{ - node::IntoRenderNodeArray, Edge, InternedRenderLabel, Node, NodeRunError, RenderGraph, - RenderGraphContext, RenderGraphError, RenderLabel, SlotInfo, SlotType, - }, - renderer::RenderContext, - }; - use bevy_ecs::world::{FromWorld, World}; - use bevy_platform::collections::HashSet; - - #[derive(Debug, Hash, PartialEq, Eq, Clone, RenderLabel)] - enum TestLabel { - A, - B, - C, - D, - } - - #[derive(Debug)] - struct TestNode { - inputs: Vec, - outputs: Vec, - } - - impl TestNode { - pub fn new(inputs: usize, outputs: usize) -> Self { - TestNode { - inputs: (0..inputs) - .map(|i| SlotInfo::new(format!("in_{i}"), SlotType::TextureView)) - .collect(), - outputs: (0..outputs) - .map(|i| SlotInfo::new(format!("out_{i}"), SlotType::TextureView)) - .collect(), - } - } - } - - impl Node for TestNode { - fn input(&self) -> Vec { - self.inputs.clone() - } - - fn output(&self) -> Vec { - self.outputs.clone() - } - - fn run( - &self, - _: &mut RenderGraphContext, - _: &mut RenderContext, - _: &World, - ) -> Result<(), NodeRunError> { - Ok(()) - } - } - - fn input_nodes(label: impl RenderLabel, graph: &RenderGraph) -> HashSet { - graph - .iter_node_inputs(label) - .unwrap() - .map(|(_edge, node)| node.label) - .collect::>() - } - - fn output_nodes(label: impl RenderLabel, graph: &RenderGraph) -> HashSet { - graph - .iter_node_outputs(label) - .unwrap() - .map(|(_edge, node)| node.label) - .collect::>() - } - - #[test] - fn test_graph_edges() { - let mut graph = RenderGraph::default(); - graph.add_node(TestLabel::A, TestNode::new(0, 1)); - graph.add_node(TestLabel::B, TestNode::new(0, 1)); - graph.add_node(TestLabel::C, TestNode::new(1, 1)); - graph.add_node(TestLabel::D, TestNode::new(1, 0)); - - graph.add_slot_edge(TestLabel::A, "out_0", TestLabel::C, "in_0"); - graph.add_node_edge(TestLabel::B, TestLabel::C); - graph.add_slot_edge(TestLabel::C, 0, TestLabel::D, 0); - - assert!( - input_nodes(TestLabel::A, &graph).is_empty(), - "A has no inputs" - ); - assert_eq!( - output_nodes(TestLabel::A, &graph), - HashSet::from_iter((TestLabel::C,).into_array()), - "A outputs to C" - ); - - assert!( - input_nodes(TestLabel::B, &graph).is_empty(), - "B has no inputs" - ); - assert_eq!( - output_nodes(TestLabel::B, &graph), - HashSet::from_iter((TestLabel::C,).into_array()), - "B outputs to C" - ); - - assert_eq!( - input_nodes(TestLabel::C, &graph), - HashSet::from_iter((TestLabel::A, TestLabel::B).into_array()), - "A and B input to C" - ); - assert_eq!( - output_nodes(TestLabel::C, &graph), - HashSet::from_iter((TestLabel::D,).into_array()), - "C outputs to D" - ); - - assert_eq!( - input_nodes(TestLabel::D, &graph), - HashSet::from_iter((TestLabel::C,).into_array()), - "C inputs to D" - ); - assert!( - output_nodes(TestLabel::D, &graph).is_empty(), - "D has no outputs" - ); - } - - #[test] - fn test_get_node_typed() { - struct MyNode { - value: usize, - } - - impl Node for MyNode { - fn run( - &self, - _: &mut RenderGraphContext, - _: &mut RenderContext, - _: &World, - ) -> Result<(), NodeRunError> { - Ok(()) - } - } - - let mut graph = RenderGraph::default(); - - graph.add_node(TestLabel::A, MyNode { value: 42 }); - - let node: &MyNode = graph.get_node(TestLabel::A).unwrap(); - assert_eq!(node.value, 42, "node value matches"); - - let result: Result<&TestNode, RenderGraphError> = graph.get_node(TestLabel::A); - assert_eq!( - result.unwrap_err(), - RenderGraphError::WrongNodeType, - "expect a wrong node type error" - ); - } - - #[test] - fn test_slot_already_occupied() { - let mut graph = RenderGraph::default(); - - graph.add_node(TestLabel::A, TestNode::new(0, 1)); - graph.add_node(TestLabel::B, TestNode::new(0, 1)); - graph.add_node(TestLabel::C, TestNode::new(1, 1)); - - graph.add_slot_edge(TestLabel::A, 0, TestLabel::C, 0); - assert_eq!( - graph.try_add_slot_edge(TestLabel::B, 0, TestLabel::C, 0), - Err(RenderGraphError::NodeInputSlotAlreadyOccupied { - node: TestLabel::C.intern(), - input_slot: 0, - occupied_by_node: TestLabel::A.intern(), - }), - "Adding to a slot that is already occupied should return an error" - ); - } - - #[test] - fn test_edge_already_exists() { - let mut graph = RenderGraph::default(); - - graph.add_node(TestLabel::A, TestNode::new(0, 1)); - graph.add_node(TestLabel::B, TestNode::new(1, 0)); - - graph.add_slot_edge(TestLabel::A, 0, TestLabel::B, 0); - assert_eq!( - graph.try_add_slot_edge(TestLabel::A, 0, TestLabel::B, 0), - Err(RenderGraphError::EdgeAlreadyExists(Edge::SlotEdge { - output_node: TestLabel::A.intern(), - output_index: 0, - input_node: TestLabel::B.intern(), - input_index: 0, - })), - "Adding to a duplicate edge should return an error" - ); - } - - #[test] - fn test_add_node_edges() { - struct SimpleNode; - impl Node for SimpleNode { - fn run( - &self, - _graph: &mut RenderGraphContext, - _render_context: &mut RenderContext, - _world: &World, - ) -> Result<(), NodeRunError> { - Ok(()) - } - } - impl FromWorld for SimpleNode { - fn from_world(_world: &mut World) -> Self { - Self - } - } - - let mut graph = RenderGraph::default(); - graph.add_node(TestLabel::A, SimpleNode); - graph.add_node(TestLabel::B, SimpleNode); - graph.add_node(TestLabel::C, SimpleNode); - - graph.add_node_edges((TestLabel::A, TestLabel::B, TestLabel::C)); - - assert_eq!( - output_nodes(TestLabel::A, &graph), - HashSet::from_iter((TestLabel::B,).into_array()), - "A -> B" - ); - assert_eq!( - input_nodes(TestLabel::B, &graph), - HashSet::from_iter((TestLabel::A,).into_array()), - "A -> B" - ); - assert_eq!( - output_nodes(TestLabel::B, &graph), - HashSet::from_iter((TestLabel::C,).into_array()), - "B -> C" - ); - assert_eq!( - input_nodes(TestLabel::C, &graph), - HashSet::from_iter((TestLabel::B,).into_array()), - "B -> C" - ); - } -} diff --git a/crates/bevy_render/src/render_graph/mod.rs b/crates/bevy_render/src/render_graph/mod.rs deleted file mode 100644 index 6f98a30f4bab7..0000000000000 --- a/crates/bevy_render/src/render_graph/mod.rs +++ /dev/null @@ -1,56 +0,0 @@ -mod app; -mod camera_driver_node; -mod context; -mod edge; -mod graph; -mod node; -mod node_slot; - -pub use app::*; -pub use camera_driver_node::*; -pub use context::*; -pub use edge::*; -pub use graph::*; -pub use node::*; -pub use node_slot::*; - -use thiserror::Error; - -#[derive(Error, Debug, Eq, PartialEq)] -pub enum RenderGraphError { - #[error("node {0:?} does not exist")] - InvalidNode(InternedRenderLabel), - #[error("output node slot does not exist")] - InvalidOutputNodeSlot(SlotLabel), - #[error("input node slot does not exist")] - InvalidInputNodeSlot(SlotLabel), - #[error("node does not match the given type")] - WrongNodeType, - #[error("attempted to connect output slot {output_slot} from node {output_node:?} to incompatible input slot {input_slot} from node {input_node:?}")] - MismatchedNodeSlots { - output_node: InternedRenderLabel, - output_slot: usize, - input_node: InternedRenderLabel, - input_slot: usize, - }, - #[error("attempted to add an edge that already exists")] - EdgeAlreadyExists(Edge), - #[error("attempted to remove an edge that does not exist")] - EdgeDoesNotExist(Edge), - #[error("node {node:?} has an unconnected input slot {input_slot}")] - UnconnectedNodeInputSlot { - node: InternedRenderLabel, - input_slot: usize, - }, - #[error("node {node:?} has an unconnected output slot {output_slot}")] - UnconnectedNodeOutputSlot { - node: InternedRenderLabel, - output_slot: usize, - }, - #[error("node {node:?} input slot {input_slot} already occupied by {occupied_by_node:?}")] - NodeInputSlotAlreadyOccupied { - node: InternedRenderLabel, - input_slot: usize, - occupied_by_node: InternedRenderLabel, - }, -} diff --git a/crates/bevy_render/src/render_graph/node.rs b/crates/bevy_render/src/render_graph/node.rs deleted file mode 100644 index bfc007b12754a..0000000000000 --- a/crates/bevy_render/src/render_graph/node.rs +++ /dev/null @@ -1,426 +0,0 @@ -use crate::{ - render_graph::{ - Edge, InputSlotError, OutputSlotError, RenderGraphContext, RenderGraphError, - RunSubGraphError, SlotInfo, SlotInfos, - }, - render_phase::DrawError, - renderer::RenderContext, -}; -pub use bevy_ecs::label::DynEq; -use bevy_ecs::{ - define_label, - intern::Interned, - query::{QueryItem, QueryState, ReadOnlyQueryData}, - world::{FromWorld, World}, -}; -use core::fmt::Debug; -use downcast_rs::{impl_downcast, Downcast}; -use thiserror::Error; -use variadics_please::all_tuples_with_size; - -pub use bevy_render_macros::RenderLabel; - -use super::{InternedRenderSubGraph, RenderSubGraph}; - -define_label!( - #[diagnostic::on_unimplemented( - note = "consider annotating `{Self}` with `#[derive(RenderLabel)]`" - )] - /// A strongly-typed class of labels used to identify a [`Node`] in a render graph. - RenderLabel, - RENDER_LABEL_INTERNER -); - -/// A shorthand for `Interned`. -pub type InternedRenderLabel = Interned; - -pub trait IntoRenderNodeArray { - fn into_array(self) -> [InternedRenderLabel; N]; -} - -impl IntoRenderNodeArray for Vec { - fn into_array(self) -> [InternedRenderLabel; N] { - self.try_into().unwrap() - } -} - -macro_rules! impl_render_label_tuples { - ($N: expr, $(#[$meta:meta])* $(($T: ident, $I: ident)),*) => { - $(#[$meta])* - impl<$($T: RenderLabel),*> IntoRenderNodeArray<$N> for ($($T,)*) { - #[inline] - fn into_array(self) -> [InternedRenderLabel; $N] { - let ($($I,)*) = self; - [$($I.intern(), )*] - } - } - } -} - -all_tuples_with_size!( - #[doc(fake_variadic)] - impl_render_label_tuples, - 1, - 32, - T, - l -); - -/// A render node that can be added to a [`RenderGraph`](super::RenderGraph). -/// -/// Nodes are the fundamental part of the graph and used to extend its functionality, by -/// generating draw calls and/or running subgraphs. -/// They are added via the `render_graph::add_node(my_node)` method. -/// -/// To determine their position in the graph and ensure that all required dependencies (inputs) -/// are already executed, [`Edges`](Edge) are used. -/// -/// A node can produce outputs used as dependencies by other nodes. -/// Those inputs and outputs are called slots and are the default way of passing render data -/// inside the graph. For more information see [`SlotType`](super::SlotType). -pub trait Node: Downcast + Send + Sync + 'static { - /// Specifies the required input slots for this node. - /// They will then be available during the run method inside the [`RenderGraphContext`]. - fn input(&self) -> Vec { - Vec::new() - } - - /// Specifies the produced output slots for this node. - /// They can then be passed one inside [`RenderGraphContext`] during the run method. - fn output(&self) -> Vec { - Vec::new() - } - - /// Updates internal node state using the current render [`World`] prior to the run method. - fn update(&mut self, _world: &mut World) {} - - /// Runs the graph node logic, issues draw calls, updates the output slots and - /// optionally queues up subgraphs for execution. The graph data, input and output values are - /// passed via the [`RenderGraphContext`]. - fn run<'w>( - &self, - graph: &mut RenderGraphContext, - render_context: &mut RenderContext<'w>, - world: &'w World, - ) -> Result<(), NodeRunError>; -} - -impl_downcast!(Node); - -#[derive(Error, Debug, Eq, PartialEq)] -pub enum NodeRunError { - #[error("encountered an input slot error")] - InputSlotError(#[from] InputSlotError), - #[error("encountered an output slot error")] - OutputSlotError(#[from] OutputSlotError), - #[error("encountered an error when running a sub-graph")] - RunSubGraphError(#[from] RunSubGraphError), - #[error("encountered an error when executing draw command")] - DrawError(#[from] DrawError), -} - -/// A collection of input and output [`Edges`](Edge) for a [`Node`]. -#[derive(Debug)] -pub struct Edges { - label: InternedRenderLabel, - input_edges: Vec, - output_edges: Vec, -} - -impl Edges { - /// Returns all "input edges" (edges going "in") for this node . - #[inline] - pub fn input_edges(&self) -> &[Edge] { - &self.input_edges - } - - /// Returns all "output edges" (edges going "out") for this node . - #[inline] - pub fn output_edges(&self) -> &[Edge] { - &self.output_edges - } - - /// Returns this node's label. - #[inline] - pub fn label(&self) -> InternedRenderLabel { - self.label - } - - /// Adds an edge to the `input_edges` if it does not already exist. - pub(crate) fn add_input_edge(&mut self, edge: Edge) -> Result<(), RenderGraphError> { - if self.has_input_edge(&edge) { - return Err(RenderGraphError::EdgeAlreadyExists(edge)); - } - self.input_edges.push(edge); - Ok(()) - } - - /// Removes an edge from the `input_edges` if it exists. - pub(crate) fn remove_input_edge(&mut self, edge: Edge) -> Result<(), RenderGraphError> { - if let Some(index) = self.input_edges.iter().position(|e| *e == edge) { - self.input_edges.swap_remove(index); - Ok(()) - } else { - Err(RenderGraphError::EdgeDoesNotExist(edge)) - } - } - - /// Adds an edge to the `output_edges` if it does not already exist. - pub(crate) fn add_output_edge(&mut self, edge: Edge) -> Result<(), RenderGraphError> { - if self.has_output_edge(&edge) { - return Err(RenderGraphError::EdgeAlreadyExists(edge)); - } - self.output_edges.push(edge); - Ok(()) - } - - /// Removes an edge from the `output_edges` if it exists. - pub(crate) fn remove_output_edge(&mut self, edge: Edge) -> Result<(), RenderGraphError> { - if let Some(index) = self.output_edges.iter().position(|e| *e == edge) { - self.output_edges.swap_remove(index); - Ok(()) - } else { - Err(RenderGraphError::EdgeDoesNotExist(edge)) - } - } - - /// Checks whether the input edge already exists. - pub fn has_input_edge(&self, edge: &Edge) -> bool { - self.input_edges.contains(edge) - } - - /// Checks whether the output edge already exists. - pub fn has_output_edge(&self, edge: &Edge) -> bool { - self.output_edges.contains(edge) - } - - /// Searches the `input_edges` for a [`Edge::SlotEdge`], - /// which `input_index` matches the `index`; - pub fn get_input_slot_edge(&self, index: usize) -> Result<&Edge, RenderGraphError> { - self.input_edges - .iter() - .find(|e| { - if let Edge::SlotEdge { input_index, .. } = e { - *input_index == index - } else { - false - } - }) - .ok_or(RenderGraphError::UnconnectedNodeInputSlot { - input_slot: index, - node: self.label, - }) - } - - /// Searches the `output_edges` for a [`Edge::SlotEdge`], - /// which `output_index` matches the `index`; - pub fn get_output_slot_edge(&self, index: usize) -> Result<&Edge, RenderGraphError> { - self.output_edges - .iter() - .find(|e| { - if let Edge::SlotEdge { output_index, .. } = e { - *output_index == index - } else { - false - } - }) - .ok_or(RenderGraphError::UnconnectedNodeOutputSlot { - output_slot: index, - node: self.label, - }) - } -} - -/// The internal representation of a [`Node`], with all data required -/// by the [`RenderGraph`](super::RenderGraph). -/// -/// The `input_slots` and `output_slots` are provided by the `node`. -pub struct NodeState { - pub label: InternedRenderLabel, - /// The name of the type that implements [`Node`]. - pub type_name: &'static str, - pub node: Box, - pub input_slots: SlotInfos, - pub output_slots: SlotInfos, - pub edges: Edges, -} - -impl Debug for NodeState { - fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { - writeln!(f, "{:?} ({})", self.label, self.type_name) - } -} - -impl NodeState { - /// Creates an [`NodeState`] without edges, but the `input_slots` and `output_slots` - /// are provided by the `node`. - pub fn new(label: InternedRenderLabel, node: T) -> Self - where - T: Node, - { - NodeState { - label, - input_slots: node.input().into(), - output_slots: node.output().into(), - node: Box::new(node), - type_name: core::any::type_name::(), - edges: Edges { - label, - input_edges: Vec::new(), - output_edges: Vec::new(), - }, - } - } - - /// Retrieves the [`Node`]. - pub fn node(&self) -> Result<&T, RenderGraphError> - where - T: Node, - { - self.node - .downcast_ref::() - .ok_or(RenderGraphError::WrongNodeType) - } - - /// Retrieves the [`Node`] mutably. - pub fn node_mut(&mut self) -> Result<&mut T, RenderGraphError> - where - T: Node, - { - self.node - .downcast_mut::() - .ok_or(RenderGraphError::WrongNodeType) - } - - /// Validates that each input slot corresponds to an input edge. - pub fn validate_input_slots(&self) -> Result<(), RenderGraphError> { - for i in 0..self.input_slots.len() { - self.edges.get_input_slot_edge(i)?; - } - - Ok(()) - } - - /// Validates that each output slot corresponds to an output edge. - pub fn validate_output_slots(&self) -> Result<(), RenderGraphError> { - for i in 0..self.output_slots.len() { - self.edges.get_output_slot_edge(i)?; - } - - Ok(()) - } -} - -/// A [`Node`] without any inputs, outputs and subgraphs, which does nothing when run. -/// Used (as a label) to bundle multiple dependencies into one inside -/// the [`RenderGraph`](super::RenderGraph). -#[derive(Default)] -pub struct EmptyNode; - -impl Node for EmptyNode { - fn run( - &self, - _graph: &mut RenderGraphContext, - _render_context: &mut RenderContext, - _world: &World, - ) -> Result<(), NodeRunError> { - Ok(()) - } -} - -/// A [`RenderGraph`](super::RenderGraph) [`Node`] that runs the configured subgraph once. -/// This makes it easier to insert sub-graph runs into a graph. -pub struct RunGraphOnViewNode { - sub_graph: InternedRenderSubGraph, -} - -impl RunGraphOnViewNode { - pub fn new(sub_graph: T) -> Self { - Self { - sub_graph: sub_graph.intern(), - } - } -} - -impl Node for RunGraphOnViewNode { - fn run( - &self, - graph: &mut RenderGraphContext, - _render_context: &mut RenderContext, - _world: &World, - ) -> Result<(), NodeRunError> { - graph.run_sub_graph(self.sub_graph, vec![], Some(graph.view_entity()), None)?; - Ok(()) - } -} - -/// This trait should be used instead of the [`Node`] trait when making a render node that runs on a view. -/// -/// It is intended to be used with [`ViewNodeRunner`] -pub trait ViewNode { - /// The query that will be used on the view entity. - /// It is guaranteed to run on the view entity, so there's no need for a filter - type ViewQuery: ReadOnlyQueryData; - - /// Updates internal node state using the current render [`World`] prior to the run method. - fn update(&mut self, _world: &mut World) {} - - /// Runs the graph node logic, issues draw calls, updates the output slots and - /// optionally queues up subgraphs for execution. The graph data, input and output values are - /// passed via the [`RenderGraphContext`]. - fn run<'w>( - &self, - graph: &mut RenderGraphContext, - render_context: &mut RenderContext<'w>, - view_query: QueryItem<'w, '_, Self::ViewQuery>, - world: &'w World, - ) -> Result<(), NodeRunError>; -} - -/// This [`Node`] can be used to run any [`ViewNode`]. -/// It will take care of updating the view query in `update()` and running the query in `run()`. -/// -/// This [`Node`] exists to help reduce boilerplate when making a render node that runs on a view. -pub struct ViewNodeRunner { - view_query: QueryState, - node: N, -} - -impl ViewNodeRunner { - pub fn new(node: N, world: &mut World) -> Self { - Self { - view_query: world.query_filtered(), - node, - } - } -} - -impl FromWorld for ViewNodeRunner { - fn from_world(world: &mut World) -> Self { - Self::new(N::from_world(world), world) - } -} - -impl Node for ViewNodeRunner -where - T: ViewNode + Send + Sync + 'static, -{ - fn update(&mut self, world: &mut World) { - self.view_query.update_archetypes(world); - self.node.update(world); - } - - fn run<'w>( - &self, - graph: &mut RenderGraphContext, - render_context: &mut RenderContext<'w>, - world: &'w World, - ) -> Result<(), NodeRunError> { - let Ok(view) = self.view_query.get_manual(world, graph.view_entity()) else { - return Ok(()); - }; - - ViewNode::run(&self.node, graph, render_context, view, world)?; - Ok(()) - } -} diff --git a/crates/bevy_render/src/render_graph/node_slot.rs b/crates/bevy_render/src/render_graph/node_slot.rs deleted file mode 100644 index eb11b368cd7bb..0000000000000 --- a/crates/bevy_render/src/render_graph/node_slot.rs +++ /dev/null @@ -1,165 +0,0 @@ -use alloc::borrow::Cow; -use bevy_ecs::entity::Entity; -use core::fmt; -use derive_more::derive::From; - -use crate::render_resource::{Buffer, Sampler, TextureView}; - -/// A value passed between render [`Nodes`](super::Node). -/// Corresponds to the [`SlotType`] specified in the [`RenderGraph`](super::RenderGraph). -/// -/// Slots can have four different types of values: -/// [`Buffer`], [`TextureView`], [`Sampler`] and [`Entity`]. -/// -/// These values do not contain the actual render data, but only the ids to retrieve them. -#[derive(Debug, Clone, From)] -pub enum SlotValue { - /// A GPU-accessible [`Buffer`]. - Buffer(Buffer), - /// A [`TextureView`] describes a texture used in a pipeline. - TextureView(TextureView), - /// A texture [`Sampler`] defines how a pipeline will sample from a [`TextureView`]. - Sampler(Sampler), - /// An entity from the ECS. - Entity(Entity), -} - -impl SlotValue { - /// Returns the [`SlotType`] of this value. - pub fn slot_type(&self) -> SlotType { - match self { - SlotValue::Buffer(_) => SlotType::Buffer, - SlotValue::TextureView(_) => SlotType::TextureView, - SlotValue::Sampler(_) => SlotType::Sampler, - SlotValue::Entity(_) => SlotType::Entity, - } - } -} - -/// Describes the render resources created (output) or used (input) by -/// the render [`Nodes`](super::Node). -/// -/// This should not be confused with [`SlotValue`], which actually contains the passed data. -#[derive(Debug, Copy, Clone, Eq, PartialEq)] -pub enum SlotType { - /// A GPU-accessible [`Buffer`]. - Buffer, - /// A [`TextureView`] describes a texture used in a pipeline. - TextureView, - /// A texture [`Sampler`] defines how a pipeline will sample from a [`TextureView`]. - Sampler, - /// An entity from the ECS. - Entity, -} - -impl fmt::Display for SlotType { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - let s = match self { - SlotType::Buffer => "Buffer", - SlotType::TextureView => "TextureView", - SlotType::Sampler => "Sampler", - SlotType::Entity => "Entity", - }; - - f.write_str(s) - } -} - -/// A [`SlotLabel`] is used to reference a slot by either its name or index -/// inside the [`RenderGraph`](super::RenderGraph). -#[derive(Debug, Clone, Eq, PartialEq, From)] -pub enum SlotLabel { - Index(usize), - Name(Cow<'static, str>), -} - -impl From<&SlotLabel> for SlotLabel { - fn from(value: &SlotLabel) -> Self { - value.clone() - } -} - -impl From for SlotLabel { - fn from(value: String) -> Self { - SlotLabel::Name(value.into()) - } -} - -impl From<&'static str> for SlotLabel { - fn from(value: &'static str) -> Self { - SlotLabel::Name(value.into()) - } -} - -/// The internal representation of a slot, which specifies its [`SlotType`] and name. -#[derive(Clone, Debug)] -pub struct SlotInfo { - pub name: Cow<'static, str>, - pub slot_type: SlotType, -} - -impl SlotInfo { - pub fn new(name: impl Into>, slot_type: SlotType) -> Self { - SlotInfo { - name: name.into(), - slot_type, - } - } -} - -/// A collection of input or output [`SlotInfos`](SlotInfo) for -/// a [`NodeState`](super::NodeState). -#[derive(Default, Debug)] -pub struct SlotInfos { - slots: Vec, -} - -impl> From for SlotInfos { - fn from(slots: T) -> Self { - SlotInfos { - slots: slots.into_iter().collect(), - } - } -} - -impl SlotInfos { - /// Returns the count of slots. - #[inline] - pub fn len(&self) -> usize { - self.slots.len() - } - - /// Returns true if there are no slots. - #[inline] - pub fn is_empty(&self) -> bool { - self.slots.is_empty() - } - - /// Retrieves the [`SlotInfo`] for the provided label. - pub fn get_slot(&self, label: impl Into) -> Option<&SlotInfo> { - let label = label.into(); - let index = self.get_slot_index(label)?; - self.slots.get(index) - } - - /// Retrieves the [`SlotInfo`] for the provided label mutably. - pub fn get_slot_mut(&mut self, label: impl Into) -> Option<&mut SlotInfo> { - let label = label.into(); - let index = self.get_slot_index(label)?; - self.slots.get_mut(index) - } - - /// Retrieves the index (inside input or output slots) of the slot for the provided label. - pub fn get_slot_index(&self, label: impl Into) -> Option { - let label = label.into(); - match label { - SlotLabel::Index(index) => Some(index), - SlotLabel::Name(ref name) => self.slots.iter().position(|s| s.name == *name), - } - } - - /// Returns an iterator over the slot infos. - pub fn iter(&self) -> impl Iterator { - self.slots.iter() - } -} diff --git a/crates/bevy_render/src/renderer/graph_runner.rs b/crates/bevy_render/src/renderer/graph_runner.rs deleted file mode 100644 index c54737241cbd1..0000000000000 --- a/crates/bevy_render/src/renderer/graph_runner.rs +++ /dev/null @@ -1,292 +0,0 @@ -use bevy_ecs::{prelude::Entity, world::World}; -use bevy_platform::collections::HashMap; -#[cfg(feature = "trace")] -use tracing::info_span; - -use alloc::{borrow::Cow, collections::VecDeque}; -use smallvec::{smallvec, SmallVec}; -use thiserror::Error; - -use crate::{ - diagnostic::internal::{DiagnosticsRecorder, RenderDiagnosticsMutex}, - render_graph::{ - Edge, InternedRenderLabel, InternedRenderSubGraph, NodeRunError, NodeState, RenderGraph, - RenderGraphContext, SlotLabel, SlotType, SlotValue, - }, - renderer::{RenderContext, RenderDevice}, -}; - -/// The [`RenderGraphRunner`] is responsible for executing a [`RenderGraph`]. -/// -/// It will run all nodes in the graph sequentially in the correct order (defined by the edges). -/// Each [`Node`](crate::render_graph::Node) can run any arbitrary code, but will generally -/// either send directly a [`CommandBuffer`] or a task that will asynchronously generate a [`CommandBuffer`] -/// -/// After running the graph, the [`RenderGraphRunner`] will execute in parallel all the tasks to get -/// an ordered list of [`CommandBuffer`]s to execute. These [`CommandBuffer`] will be submitted to the GPU -/// sequentially in the order that the tasks were submitted. (which is the order of the [`RenderGraph`]) -/// -/// [`CommandBuffer`]: wgpu::CommandBuffer -pub(crate) struct RenderGraphRunner; - -#[derive(Error, Debug)] -pub enum RenderGraphRunnerError { - #[error(transparent)] - NodeRunError(#[from] NodeRunError), - #[error("node output slot not set (index {slot_index}, name {slot_name})")] - EmptyNodeOutputSlot { - type_name: &'static str, - slot_index: usize, - slot_name: Cow<'static, str>, - }, - #[error("graph '{sub_graph:?}' could not be run because slot '{slot_name}' at index {slot_index} has no value")] - MissingInput { - slot_index: usize, - slot_name: Cow<'static, str>, - sub_graph: Option, - }, - #[error("attempted to use the wrong type for input slot")] - MismatchedInputSlotType { - slot_index: usize, - label: SlotLabel, - expected: SlotType, - actual: SlotType, - }, - #[error( - "node (name: '{node_name:?}') has {slot_count} input slots, but was provided {value_count} values" - )] - MismatchedInputCount { - node_name: InternedRenderLabel, - slot_count: usize, - value_count: usize, - }, -} - -impl RenderGraphRunner { - pub fn run( - graph: &RenderGraph, - render_device: RenderDevice, - mut diagnostics_recorder: Option, - queue: &wgpu::Queue, - world: &World, - finalizer: impl FnOnce(&mut wgpu::CommandEncoder), - ) -> Result, RenderGraphRunnerError> { - if let Some(recorder) = &mut diagnostics_recorder { - recorder.begin_frame(); - } - - let mut render_context = RenderContext::new(render_device, diagnostics_recorder); - Self::run_graph(graph, None, &mut render_context, world, &[], None, None)?; - finalizer(render_context.command_encoder()); - - let (render_device, mut diagnostics_recorder) = { - let (commands, render_device, diagnostics_recorder) = render_context.finish(); - - #[cfg(feature = "trace")] - let _span = info_span!("submit_graph_commands").entered(); - queue.submit(commands); - - (render_device, diagnostics_recorder) - }; - - if let Some(recorder) = &mut diagnostics_recorder { - let render_diagnostics_mutex = world.resource::().0.clone(); - recorder.finish_frame(&render_device, move |diagnostics| { - *render_diagnostics_mutex.lock().expect("lock poisoned") = Some(diagnostics); - }); - } - - Ok(diagnostics_recorder) - } - - /// Runs the [`RenderGraph`] and all its sub-graphs sequentially, making sure that all nodes are - /// run in the correct order. (a node only runs when all its dependencies have finished running) - fn run_graph<'w>( - graph: &RenderGraph, - sub_graph: Option, - render_context: &mut RenderContext<'w>, - world: &'w World, - inputs: &[SlotValue], - view_entity: Option, - debug_group: Option, - ) -> Result<(), RenderGraphRunnerError> { - let mut node_outputs: HashMap> = - HashMap::default(); - #[cfg(feature = "trace")] - let span = if let Some(render_label) = &sub_graph { - let name = format!("{render_label:?}"); - if let Some(debug_group) = debug_group.as_ref() { - info_span!("run_graph", name = name, debug_group = debug_group) - } else { - info_span!("run_graph", name = name) - } - } else { - info_span!("run_graph", name = "main_graph") - }; - #[cfg(feature = "trace")] - let _guard = span.enter(); - - if let Some(debug_group) = debug_group.as_ref() { - // wgpu 27 changed the debug_group validation which makes it impossible to have - // a debug_group that spans multiple command encoders. - // - // - // - // For now, we use a debug_marker as a workaround - render_context - .command_encoder() - .insert_debug_marker(&format!("Start {debug_group}")); - } - - // Queue up nodes without inputs, which can be run immediately - let mut node_queue: VecDeque<&NodeState> = graph - .iter_nodes() - .filter(|node| node.input_slots.is_empty()) - .collect(); - - // pass inputs into the graph - if let Some(input_node) = graph.get_input_node() { - let mut input_values: SmallVec<[SlotValue; 4]> = SmallVec::new(); - for (i, input_slot) in input_node.input_slots.iter().enumerate() { - if let Some(input_value) = inputs.get(i) { - if input_slot.slot_type != input_value.slot_type() { - return Err(RenderGraphRunnerError::MismatchedInputSlotType { - slot_index: i, - actual: input_value.slot_type(), - expected: input_slot.slot_type, - label: input_slot.name.clone().into(), - }); - } - input_values.push(input_value.clone()); - } else { - return Err(RenderGraphRunnerError::MissingInput { - slot_index: i, - slot_name: input_slot.name.clone(), - sub_graph, - }); - } - } - - node_outputs.insert(input_node.label, input_values); - - for (_, node_state) in graph - .iter_node_outputs(input_node.label) - .expect("node exists") - { - node_queue.push_front(node_state); - } - } - - 'handle_node: while let Some(node_state) = node_queue.pop_back() { - // skip nodes that are already processed - if node_outputs.contains_key(&node_state.label) { - continue; - } - - let mut slot_indices_and_inputs: SmallVec<[(usize, SlotValue); 4]> = SmallVec::new(); - // check if all dependencies have finished running - for (edge, input_node) in graph - .iter_node_inputs(node_state.label) - .expect("node is in graph") - { - match edge { - Edge::SlotEdge { - output_index, - input_index, - .. - } => { - if let Some(outputs) = node_outputs.get(&input_node.label) { - slot_indices_and_inputs - .push((*input_index, outputs[*output_index].clone())); - } else { - node_queue.push_front(node_state); - continue 'handle_node; - } - } - Edge::NodeEdge { .. } => { - if !node_outputs.contains_key(&input_node.label) { - node_queue.push_front(node_state); - continue 'handle_node; - } - } - } - } - - // construct final sorted input list - slot_indices_and_inputs.sort_by_key(|(index, _)| *index); - let inputs: SmallVec<[SlotValue; 4]> = slot_indices_and_inputs - .into_iter() - .map(|(_, value)| value) - .collect(); - - if inputs.len() != node_state.input_slots.len() { - return Err(RenderGraphRunnerError::MismatchedInputCount { - node_name: node_state.label, - slot_count: node_state.input_slots.len(), - value_count: inputs.len(), - }); - } - - let mut outputs: SmallVec<[Option; 4]> = - smallvec![None; node_state.output_slots.len()]; - { - let mut context = RenderGraphContext::new(graph, node_state, &inputs, &mut outputs); - if let Some(view_entity) = view_entity { - context.set_view_entity(view_entity); - } - - { - #[cfg(feature = "trace")] - let _span = info_span!("node", name = node_state.type_name).entered(); - - node_state.node.run(&mut context, render_context, world)?; - } - - for run_sub_graph in context.finish() { - let sub_graph = graph - .get_sub_graph(run_sub_graph.sub_graph) - .expect("sub graph exists because it was validated when queued."); - Self::run_graph( - sub_graph, - Some(run_sub_graph.sub_graph), - render_context, - world, - &run_sub_graph.inputs, - run_sub_graph.view_entity, - run_sub_graph.debug_group, - )?; - } - } - - let mut values: SmallVec<[SlotValue; 4]> = SmallVec::new(); - for (i, output) in outputs.into_iter().enumerate() { - if let Some(value) = output { - values.push(value); - } else { - let empty_slot = node_state.output_slots.get_slot(i).unwrap(); - return Err(RenderGraphRunnerError::EmptyNodeOutputSlot { - type_name: node_state.type_name, - slot_index: i, - slot_name: empty_slot.name.clone(), - }); - } - } - node_outputs.insert(node_state.label, values); - - for (_, node_state) in graph - .iter_node_outputs(node_state.label) - .expect("node exists") - { - node_queue.push_front(node_state); - } - } - - if let Some(debug_group) = debug_group { - render_context - .command_encoder() - .insert_debug_marker(&format!("End {debug_group}")); - } - - Ok(()) - } -} diff --git a/crates/bevy_render/src/renderer/mod.rs b/crates/bevy_render/src/renderer/mod.rs index f4cd4ad07be5d..73712828d5df7 100644 --- a/crates/bevy_render/src/renderer/mod.rs +++ b/crates/bevy_render/src/renderer/mod.rs @@ -1,18 +1,18 @@ -mod graph_runner; #[cfg(feature = "raw_vulkan_init")] pub mod raw_vulkan_init; +mod render_context; mod render_device; mod wgpu_wrapper; -pub use graph_runner::*; +pub use render_context::{ + CurrentView, CurrentViewEntity, FlushCommands, PendingCommandBuffers, RenderContext, + RenderContextState, ViewQuery, +}; pub use render_device::*; pub use wgpu_wrapper::WgpuWrapper; use crate::{ - diagnostic::{internal::DiagnosticsRecorder, RecordDiagnostics}, - render_graph::RenderGraph, - render_phase::TrackedRenderPass, - render_resource::RenderPassDescriptor, + diagnostic::RecordDiagnostics, settings::{RenderResources, WgpuSettings, WgpuSettingsPriority}, view::{ExtractedWindows, ViewTarget}, }; @@ -26,57 +26,41 @@ use bevy_time::TimeSender; use bevy_window::RawHandleWrapperHolder; use tracing::{debug, error, info, info_span, warn}; use wgpu::{ - Adapter, AdapterInfo, Backends, CommandBuffer, CommandEncoder, DeviceType, Instance, Queue, + Adapter, AdapterInfo, Backends, DeviceType, Instance, Queue, RequestAdapterOptions, Trace, }; +use bevy_ecs::schedule::ScheduleLabel; + +#[derive(ScheduleLabel, Debug, Clone, PartialEq, Eq, Hash, Default)] +pub struct RenderGraph; + +impl RenderGraph { + pub fn base_schedule() -> Schedule { + let schedule = Schedule::new(Self); + schedule + } +} -/// Updates the [`RenderGraph`] with all of its nodes and then runs it to render the entire frame. pub fn render_system( world: &mut World, state: &mut SystemState>, ) { - world.resource_scope(|world, mut graph: Mut| { - graph.update(world); - }); + #[cfg(feature = "trace")] + let _span = info_span!("main_render_schedule").entered(); - let diagnostics_recorder = world.remove_resource::(); - - let graph = world.resource::(); - let render_device = world.resource::(); - let render_queue = world.resource::(); - - let res = RenderGraphRunner::run( - graph, - render_device.clone(), // TODO: is this clone really necessary? - diagnostics_recorder, - &render_queue.0, - world, - |encoder| { - crate::view::screenshot::submit_screenshot_commands(world, encoder); - crate::gpu_readback::submit_readback_commands(world, encoder); - }, - ); + world.run_schedule(RenderGraph); - match res { - Ok(Some(diagnostics_recorder)) => { - world.insert_resource(diagnostics_recorder); - } - Ok(None) => {} - Err(e) => { - error!("Error running render graph:"); - { - let mut src: &dyn core::error::Error = &e; - loop { - error!("> {}", src); - match src.source() { - Some(s) => src = s, - None => break, - } - } - } + { + let render_device = world.resource::(); + let render_queue = world.resource::(); - panic!("Error running render graph: {e}"); - } + let mut encoder = + render_device.create_command_encoder(&wgpu::CommandEncoderDescriptor::default()); + + crate::view::screenshot::submit_screenshot_commands(world, &mut encoder); + crate::gpu_readback::submit_readback_commands(world, &mut encoder); + + render_queue.submit([encoder.finish()]); } { @@ -488,199 +472,4 @@ pub async fn initialize_renderer( #[cfg(feature = "raw_vulkan_init")] additional_vulkan_features, ) -} - -/// The context with all information required to interact with the GPU. -/// -/// The [`RenderDevice`] is used to create render resources and the -/// [`CommandEncoder`] is used to record a series of GPU operations. -pub struct RenderContext<'w> { - render_device: RenderDevice, - command_encoder: Option, - command_buffer_queue: Vec>, - diagnostics_recorder: Option>, -} - -impl<'w> RenderContext<'w> { - /// Creates a new [`RenderContext`] from a [`RenderDevice`]. - pub fn new( - render_device: RenderDevice, - diagnostics_recorder: Option, - ) -> Self { - Self { - render_device, - command_encoder: None, - command_buffer_queue: Vec::new(), - diagnostics_recorder: diagnostics_recorder.map(Arc::new), - } - } - - /// Gets the underlying [`RenderDevice`]. - pub fn render_device(&self) -> &RenderDevice { - &self.render_device - } - - /// Gets the diagnostics recorder, used to track elapsed time and pipeline statistics - /// of various render and compute passes. - pub fn diagnostic_recorder(&self) -> impl RecordDiagnostics + use<> { - self.diagnostics_recorder.clone() - } - - /// Gets the current [`CommandEncoder`]. - pub fn command_encoder(&mut self) -> &mut CommandEncoder { - self.command_encoder.get_or_insert_with(|| { - self.render_device - .create_command_encoder(&wgpu::CommandEncoderDescriptor::default()) - }) - } - - pub(crate) fn has_commands(&mut self) -> bool { - self.command_encoder.is_some() || !self.command_buffer_queue.is_empty() - } - - /// Creates a new [`TrackedRenderPass`] for the context, - /// configured using the provided `descriptor`. - pub fn begin_tracked_render_pass<'a>( - &'a mut self, - descriptor: RenderPassDescriptor<'_>, - ) -> TrackedRenderPass<'a> { - // Cannot use command_encoder() as we need to split the borrow on self - let command_encoder = self.command_encoder.get_or_insert_with(|| { - self.render_device - .create_command_encoder(&wgpu::CommandEncoderDescriptor::default()) - }); - - let render_pass = command_encoder.begin_render_pass(&descriptor); - TrackedRenderPass::new(&self.render_device, render_pass) - } - - /// Append a [`CommandBuffer`] to the command buffer queue. - /// - /// If present, this will flush the currently unflushed [`CommandEncoder`] - /// into a [`CommandBuffer`] into the queue before appending the provided - /// buffer. - pub fn add_command_buffer(&mut self, command_buffer: CommandBuffer) { - self.flush_encoder(); - - self.command_buffer_queue - .push(QueuedCommandBuffer::Ready(command_buffer)); - } - - /// Append a function that will generate a [`CommandBuffer`] to the - /// command buffer queue, to be ran later. - /// - /// If present, this will flush the currently unflushed [`CommandEncoder`] - /// into a [`CommandBuffer`] into the queue before appending the provided - /// buffer. - pub fn add_command_buffer_generation_task( - &mut self, - #[cfg(not(all(target_arch = "wasm32", target_feature = "atomics")))] - task: impl FnOnce(RenderDevice) -> CommandBuffer + 'w + Send, - #[cfg(all(target_arch = "wasm32", target_feature = "atomics"))] - task: impl FnOnce(RenderDevice) -> CommandBuffer + 'w, - ) { - self.flush_encoder(); - - self.command_buffer_queue - .push(QueuedCommandBuffer::Task(Box::new(task))); - } - - /// Finalizes and returns the queue of [`CommandBuffer`]s. - /// - /// This function will wait until all command buffer generation tasks are complete - /// by running them in parallel (where supported). - /// - /// The [`CommandBuffer`]s will be returned in the order that they were added. - pub fn finish( - mut self, - ) -> ( - Vec, - RenderDevice, - Option, - ) { - self.flush_encoder(); - - let mut command_buffers = Vec::with_capacity(self.command_buffer_queue.len()); - - #[cfg(feature = "trace")] - let _command_buffer_generation_tasks_span = - info_span!("command_buffer_generation_tasks").entered(); - - #[cfg(not(all(target_arch = "wasm32", target_feature = "atomics")))] - { - let mut task_based_command_buffers = - bevy_tasks::ComputeTaskPool::get().scope(|task_pool| { - for (i, queued_command_buffer) in - self.command_buffer_queue.into_iter().enumerate() - { - match queued_command_buffer { - QueuedCommandBuffer::Ready(command_buffer) => { - command_buffers.push((i, command_buffer)); - } - QueuedCommandBuffer::Task(command_buffer_generation_task) => { - let render_device = self.render_device.clone(); - task_pool.spawn(async move { - (i, command_buffer_generation_task(render_device)) - }); - } - } - } - }); - command_buffers.append(&mut task_based_command_buffers); - } - - #[cfg(all(target_arch = "wasm32", target_feature = "atomics"))] - for (i, queued_command_buffer) in self.command_buffer_queue.into_iter().enumerate() { - match queued_command_buffer { - QueuedCommandBuffer::Ready(command_buffer) => { - command_buffers.push((i, command_buffer)); - } - QueuedCommandBuffer::Task(command_buffer_generation_task) => { - let render_device = self.render_device.clone(); - command_buffers.push((i, command_buffer_generation_task(render_device))); - } - } - } - - #[cfg(feature = "trace")] - drop(_command_buffer_generation_tasks_span); - - command_buffers.sort_unstable_by_key(|(i, _)| *i); - - let mut command_buffers = command_buffers - .into_iter() - .map(|(_, cb)| cb) - .collect::>(); - - let mut diagnostics_recorder = self.diagnostics_recorder.take().map(|v| { - Arc::try_unwrap(v) - .ok() - .expect("diagnostic recorder shouldn't be held longer than necessary") - }); - - if let Some(recorder) = &mut diagnostics_recorder { - let mut command_encoder = self - .render_device - .create_command_encoder(&wgpu::CommandEncoderDescriptor::default()); - recorder.resolve(&mut command_encoder); - command_buffers.push(command_encoder.finish()); - } - - (command_buffers, self.render_device, diagnostics_recorder) - } - - fn flush_encoder(&mut self) { - if let Some(encoder) = self.command_encoder.take() { - self.command_buffer_queue - .push(QueuedCommandBuffer::Ready(encoder.finish())); - } - } -} - -enum QueuedCommandBuffer<'w> { - Ready(CommandBuffer), - #[cfg(not(all(target_arch = "wasm32", target_feature = "atomics")))] - Task(Box CommandBuffer + 'w + Send>), - #[cfg(all(target_arch = "wasm32", target_feature = "atomics"))] - Task(Box CommandBuffer + 'w>), -} +} \ No newline at end of file diff --git a/crates/bevy_render/src/renderer/render_context.rs b/crates/bevy_render/src/renderer/render_context.rs new file mode 100644 index 0000000000000..c6d9c019c12d4 --- /dev/null +++ b/crates/bevy_render/src/renderer/render_context.rs @@ -0,0 +1,315 @@ +//! System parameter-based render context for the schedule-driven render graph replacement. +//! +//! This module provides resources and system parameters for schedule-based rendering, +//! replacing the node-based render graph approach. + +use crate::diagnostic::internal::DiagnosticsRecorder; +use crate::render_phase::TrackedRenderPass; +use crate::render_resource::{CommandEncoder, RenderPassDescriptor}; +use crate::renderer::RenderDevice; +use bevy_ecs::change_detection::Tick; +use bevy_ecs::component::ComponentId; +use bevy_ecs::prelude::*; +use bevy_ecs::query::{FilteredAccessSet, QueryData, QueryFilter, QueryState}; +use bevy_ecs::system::{Deferred, SystemBuffer, SystemMeta, SystemParam, SystemParamValidationError}; +use bevy_ecs::world::unsafe_world_cell::UnsafeWorldCell; +use bevy_ecs::world::DeferredWorld; +use core::marker::PhantomData; +use wgpu::CommandBuffer; + +#[derive(Resource, Default)] +pub struct PendingCommandBuffers { + buffers: Vec, +} + +impl PendingCommandBuffers { + pub fn push(&mut self, buffers: impl IntoIterator) { + self.buffers.extend(buffers); + } + + pub fn take(&mut self) -> Vec { + core::mem::take(&mut self.buffers) + } + + pub fn is_empty(&self) -> bool { + self.buffers.is_empty() + } + + pub fn len(&self) -> usize { + self.buffers.len() + } +} + +#[derive(Default)] +pub struct RenderContextState { + command_encoder: Option, + command_buffers: Vec, + render_device: Option, +} + +impl RenderContextState { + fn flush_encoder(&mut self) { + if let Some(encoder) = self.command_encoder.take() { + self.command_buffers.push(encoder.finish()); + } + } + + fn command_encoder(&mut self) -> &mut CommandEncoder { + let render_device = self + .render_device + .as_ref() + .expect("RenderDevice must be set before accessing command_encoder"); + + self.command_encoder.get_or_insert_with(|| { + render_device.create_command_encoder(&wgpu::CommandEncoderDescriptor::default()) + }) + } + + pub fn finish(&mut self) -> Vec { + self.flush_encoder(); + core::mem::take(&mut self.command_buffers) + } +} + +impl SystemBuffer for RenderContextState { + fn apply(&mut self, _system_meta: &SystemMeta, world: &mut World) { + if !self.command_buffers.is_empty() || self.command_encoder.is_some() { + let command_buffers = self.finish(); + + if !command_buffers.is_empty() { + let mut pending = world.resource_mut::(); + pending.push(command_buffers); + } + } + + self.render_device = None; + } + + fn queue(&mut self, _system_meta: &SystemMeta, _world: DeferredWorld) { + } +} + +#[derive(SystemParam)] +pub struct RenderContext<'w, 's> { + state: Deferred<'s, RenderContextState>, + render_device: Res<'w, RenderDevice>, + diagnostics_recorder: Option>, +} + +impl<'w, 's> RenderContext<'w, 's> { + fn ensure_device(&mut self) { + if self.state.render_device.is_none() { + self.state.render_device = Some(self.render_device.clone()); + } + } + + pub fn render_device(&self) -> &RenderDevice { + &self.render_device + } + + pub fn diagnostic_recorder(&self) -> Option> { + self.diagnostics_recorder.as_ref().map(Res::clone) + } + + pub fn command_encoder(&mut self) -> &mut CommandEncoder { + self.ensure_device(); + self.state.command_encoder() + } + + pub fn begin_tracked_render_pass<'a>( + &'a mut self, + descriptor: RenderPassDescriptor<'_>, + ) -> TrackedRenderPass<'a> { + self.ensure_device(); + + let command_encoder = self.state.command_encoder.get_or_insert_with(|| { + self.render_device + .create_command_encoder(&wgpu::CommandEncoderDescriptor::default()) + }); + + let render_pass = command_encoder.begin_render_pass(&descriptor); + TrackedRenderPass::new(&self.render_device, render_pass) + } + + pub fn add_command_buffer(&mut self, command_buffer: CommandBuffer) { + self.state.flush_encoder(); + self.state.command_buffers.push(command_buffer); + } +} + +#[derive(SystemParam)] +pub struct FlushCommands<'w> { + pending: ResMut<'w, PendingCommandBuffers>, + queue: Res<'w, super::RenderQueue>, +} + +impl<'w> FlushCommands<'w> { + pub fn flush(&mut self) { + let buffers = self.pending.take(); + if !buffers.is_empty() { + self.queue.submit(buffers); + } + } +} + +#[derive(Resource, Debug, Clone, Copy, PartialEq, Eq)] +pub struct CurrentViewEntity(pub Entity); + +impl CurrentViewEntity { + pub fn new(entity: Entity) -> Self { + Self(entity) + } + + #[inline] + pub fn entity(&self) -> Entity { + self.0 + } +} + +#[derive(SystemParam)] +pub struct CurrentView<'w> { + entity: Res<'w, CurrentViewEntity>, +} + +impl<'w> CurrentView<'w> { + #[inline] + pub fn entity(&self) -> Entity { + self.entity.0 + } +} + +impl<'w> core::ops::Deref for CurrentView<'w> { + type Target = Entity; + + #[inline] + fn deref(&self) -> &Self::Target { + &self.entity.0 + } +} + +pub struct ViewQuery<'w, 's, D: QueryData, F: QueryFilter = ()> { + entity: Entity, + item: D::Item<'w, 's>, + _filter: PhantomData, +} + +impl<'w, 's, D: QueryData, F: QueryFilter> ViewQuery<'w, 's, D, F> { + #[inline] + pub fn entity(&self) -> Entity { + self.entity + } + + #[inline] + pub fn into_inner(self) -> D::Item<'w, 's> { + self.item + } +} + +pub struct ViewQueryState { + resource_id: ComponentId, + query_state: QueryState, +} + +// SAFETY: ViewQuery accesses the CurrentViewEntity resource (read) and query components. +// Access is properly registered in init_access. +unsafe impl<'a, D: QueryData + 'static, F: QueryFilter + 'static> SystemParam + for ViewQuery<'a, '_, D, F> +{ + type State = ViewQueryState; + type Item<'w, 's> = ViewQuery<'w, 's, D, F>; + + fn init_state(world: &mut World) -> Self::State { + ViewQueryState { + resource_id: world + .components_registrator() + .register_resource::(), + query_state: QueryState::new(world), + } + } + + fn init_access( + state: &Self::State, + system_meta: &mut SystemMeta, + component_access_set: &mut FilteredAccessSet, + world: &mut World, + ) { + component_access_set.add_unfiltered_resource_read(state.resource_id); + + as SystemParam>::init_access( + &state.query_state, + system_meta, + component_access_set, + world, + ); + } + + #[inline] + unsafe fn validate_param( + state: &mut Self::State, + _system_meta: &SystemMeta, + world: UnsafeWorldCell, + ) -> Result<(), SystemParamValidationError> { + // Check if CurrentViewEntity resource exists and get the entity + // SAFETY: We have registered resource read access in init_access + let current_view = unsafe { world.get_resource::() }; + + let Some(current_view) = current_view else { + return Err(SystemParamValidationError::skipped::( + "CurrentViewEntity resource not present", + )); + }; + + let entity = current_view.entity(); + + // Check if the current view entity matches the query + // SAFETY: Query state access is properly registered in init_access. + // The caller ensures the world matches the one used in init_state. + let result = unsafe { state.query_state.get_unchecked(world, entity) }; + + if result.is_err() { + return Err(SystemParamValidationError::skipped::( + "Current view entity does not match query", + )); + } + + Ok(()) + } + + #[inline] + unsafe fn get_param<'w, 's>( + state: &'s mut Self::State, + _system_meta: &SystemMeta, + world: UnsafeWorldCell<'w>, + _change_tick: Tick, + ) -> Self::Item<'w, 's> { + // SAFETY: We have registered resource read access and validate_param succeeded + let current_view = unsafe { + world + .get_resource::() + .expect("CurrentViewEntity must exist") + }; + + let entity = current_view.entity(); + + // SAFETY: Query state access is properly registered in init_access. + // validate_param verified the entity matches. + let item = unsafe { + state + .query_state + .get_unchecked(world, entity) + .expect("view entity must match query") + }; + + ViewQuery { + entity, + item, + _filter: PhantomData, + } + } +} + +// SAFETY: ViewQuery with ReadOnlyQueryData only reads from the world. +unsafe impl<'w, 's, D: bevy_ecs::query::ReadOnlyQueryData + 'static, F: QueryFilter + 'static> + bevy_ecs::system::ReadOnlySystemParam for ViewQuery<'w, 's, D, F> +{ +} diff --git a/crates/bevy_solari/src/pathtracer/mod.rs b/crates/bevy_solari/src/pathtracer/mod.rs index 3f20119761290..126237b926fbf 100644 --- a/crates/bevy_solari/src/pathtracer/mod.rs +++ b/crates/bevy_solari/src/pathtracer/mod.rs @@ -5,17 +5,15 @@ mod prepare; use crate::SolariPlugins; use bevy_app::{App, Plugin}; use bevy_asset::embedded_asset; -use bevy_core_pipeline::core_3d::graph::{Core3d, Node3d}; +use bevy_core_pipeline::schedule::{Core3d, Core3dSystems}; use bevy_ecs::{component::Component, reflect::ReflectComponent, schedule::IntoScheduleConfigs}; use bevy_reflect::{std_traits::ReflectDefault, Reflect}; use bevy_render::{ - render_graph::{RenderGraphExt, ViewNodeRunner}, - renderer::RenderDevice, - view::Hdr, - ExtractSchedule, Render, RenderApp, RenderSystems, + renderer::RenderDevice, view::Hdr, ExtractSchedule, Render, RenderApp, RenderStartup, + RenderSystems, }; use extract::extract_pathtracer; -use node::PathtracerNode; +use node::{init_pathtracer_pipelines, pathtracer}; use prepare::prepare_pathtracer_accumulation_texture; use tracing::warn; @@ -44,16 +42,16 @@ impl Plugin for PathtracingPlugin { } render_app + .add_systems(RenderStartup, init_pathtracer_pipelines) .add_systems(ExtractSchedule, extract_pathtracer) .add_systems( Render, prepare_pathtracer_accumulation_texture.in_set(RenderSystems::PrepareResources), ) - .add_render_graph_node::>( + .add_systems( Core3d, - node::graph::PathtracerNode, - ) - .add_render_graph_edges(Core3d, (Node3d::EndMainPass, node::graph::PathtracerNode)); + pathtracer.after(Core3dSystems::EndMainPass), + ); } } diff --git a/crates/bevy_solari/src/pathtracer/node.rs b/crates/bevy_solari/src/pathtracer/node.rs index 1c5ec8e72092b..75164dc570403 100644 --- a/crates/bevy_solari/src/pathtracer/node.rs +++ b/crates/bevy_solari/src/pathtracer/node.rs @@ -1,131 +1,121 @@ use super::{prepare::PathtracerAccumulationTexture, Pathtracer}; use crate::scene::RaytracingSceneBindings; -use bevy_asset::load_embedded_asset; -use bevy_ecs::{ - query::QueryItem, - world::{FromWorld, World}, -}; +use bevy_asset::{load_embedded_asset, AssetServer}; +use bevy_ecs::{prelude::*, resource::Resource, system::Commands}; use bevy_render::{ camera::ExtractedCamera, - render_graph::{NodeRunError, RenderGraphContext, ViewNode}, render_resource::{ binding_types::{texture_storage_2d, uniform_buffer}, BindGroupEntries, BindGroupLayoutDescriptor, BindGroupLayoutEntries, CachedComputePipelineId, ComputePassDescriptor, ComputePipelineDescriptor, ImageSubresourceRange, PipelineCache, ShaderStages, StorageTextureAccess, TextureFormat, }, - renderer::RenderContext, + renderer::{RenderContext, RenderDevice, ViewQuery}, view::{ViewTarget, ViewUniform, ViewUniformOffset, ViewUniforms}, }; use bevy_utils::default; -pub mod graph { - use bevy_render::render_graph::RenderLabel; - - #[derive(Debug, Hash, PartialEq, Eq, Clone, RenderLabel)] - pub struct PathtracerNode; -} - -pub struct PathtracerNode { +/// Resource holding the pathtracer pipeline configuration. +#[derive(Resource)] +pub struct PathtracerPipelines { bind_group_layout: BindGroupLayoutDescriptor, pipeline: CachedComputePipelineId, } -impl ViewNode for PathtracerNode { - type ViewQuery = ( - &'static Pathtracer, - &'static PathtracerAccumulationTexture, - &'static ExtractedCamera, - &'static ViewTarget, - &'static ViewUniformOffset, +/// Initializes the pathtracer pipelines at render startup. +pub fn init_pathtracer_pipelines( + mut commands: Commands, + pipeline_cache: Res, + scene_bindings: Res, + asset_server: Res, +) { + let bind_group_layout = BindGroupLayoutDescriptor::new( + "pathtracer_bind_group_layout", + &BindGroupLayoutEntries::sequential( + ShaderStages::COMPUTE, + ( + texture_storage_2d(TextureFormat::Rgba32Float, StorageTextureAccess::ReadWrite), + texture_storage_2d( + ViewTarget::TEXTURE_FORMAT_HDR, + StorageTextureAccess::WriteOnly, + ), + uniform_buffer::(true), + ), + ), ); - fn run( - &self, - _graph: &mut RenderGraphContext, - render_context: &mut RenderContext, - (pathtracer, accumulation_texture, camera, view_target, view_uniform_offset): QueryItem< - Self::ViewQuery, - >, - world: &World, - ) -> Result<(), NodeRunError> { - let pipeline_cache = world.resource::(); - let scene_bindings = world.resource::(); - let view_uniforms = world.resource::(); - let (Some(pipeline), Some(scene_bindings), Some(viewport), Some(view_uniforms)) = ( - pipeline_cache.get_compute_pipeline(self.pipeline), - &scene_bindings.bind_group, - camera.physical_viewport_size, - view_uniforms.uniforms.binding(), - ) else { - return Ok(()); - }; + let pipeline = pipeline_cache.queue_compute_pipeline(ComputePipelineDescriptor { + label: Some("pathtracer_pipeline".into()), + layout: vec![ + scene_bindings.bind_group_layout.clone(), + bind_group_layout.clone(), + ], + shader: load_embedded_asset!(asset_server.as_ref(), "pathtracer.wgsl"), + ..default() + }); - let bind_group = render_context.render_device().create_bind_group( - "pathtracer_bind_group", - &pipeline_cache.get_bind_group_layout(&self.bind_group_layout), - &BindGroupEntries::sequential(( - &accumulation_texture.0.default_view, - view_target.get_unsampled_color_attachment().view, - view_uniforms, - )), - ); + commands.insert_resource(PathtracerPipelines { + bind_group_layout, + pipeline, + }); +} - let command_encoder = render_context.command_encoder(); +pub fn pathtracer( + view: ViewQuery<( + &Pathtracer, + &PathtracerAccumulationTexture, + &ExtractedCamera, + &ViewTarget, + &ViewUniformOffset, + )>, + pathtracer_pipelines: Option>, + pipeline_cache: Res, + scene_bindings: Res, + view_uniforms: Res, + render_device: Res, + mut ctx: RenderContext, +) { + let (pathtracer_settings, accumulation_texture, camera, view_target, view_uniform_offset) = + view.into_inner(); - if pathtracer.reset { - command_encoder.clear_texture( - &accumulation_texture.0.texture, - &ImageSubresourceRange::default(), - ); - } + let Some(pathtracer_pipelines) = pathtracer_pipelines else { + return; + }; - let mut pass = command_encoder.begin_compute_pass(&ComputePassDescriptor { - label: Some("pathtracer"), - timestamp_writes: None, - }); - pass.set_pipeline(pipeline); - pass.set_bind_group(0, scene_bindings, &[]); - pass.set_bind_group(1, &bind_group, &[view_uniform_offset.offset]); - pass.dispatch_workgroups(viewport.x.div_ceil(8), viewport.y.div_ceil(8), 1); + let (Some(pipeline), Some(scene_bind_group), Some(viewport), Some(view_uniforms_binding)) = ( + pipeline_cache.get_compute_pipeline(pathtracer_pipelines.pipeline), + &scene_bindings.bind_group, + camera.physical_viewport_size, + view_uniforms.uniforms.binding(), + ) else { + return; + }; - Ok(()) - } -} + let bind_group = render_device.create_bind_group( + "pathtracer_bind_group", + &pipeline_cache.get_bind_group_layout(&pathtracer_pipelines.bind_group_layout), + &BindGroupEntries::sequential(( + &accumulation_texture.0.default_view, + view_target.get_unsampled_color_attachment().view, + view_uniforms_binding, + )), + ); -impl FromWorld for PathtracerNode { - fn from_world(world: &mut World) -> Self { - let pipeline_cache = world.resource::(); - let scene_bindings = world.resource::(); + let command_encoder = ctx.command_encoder(); - let bind_group_layout = BindGroupLayoutDescriptor::new( - "pathtracer_bind_group_layout", - &BindGroupLayoutEntries::sequential( - ShaderStages::COMPUTE, - ( - texture_storage_2d(TextureFormat::Rgba32Float, StorageTextureAccess::ReadWrite), - texture_storage_2d( - ViewTarget::TEXTURE_FORMAT_HDR, - StorageTextureAccess::WriteOnly, - ), - uniform_buffer::(true), - ), - ), + if pathtracer_settings.reset { + command_encoder.clear_texture( + &accumulation_texture.0.texture, + &ImageSubresourceRange::default(), ); - - let pipeline = pipeline_cache.queue_compute_pipeline(ComputePipelineDescriptor { - label: Some("pathtracer_pipeline".into()), - layout: vec![ - scene_bindings.bind_group_layout.clone(), - bind_group_layout.clone(), - ], - shader: load_embedded_asset!(world, "pathtracer.wgsl"), - ..default() - }); - - Self { - bind_group_layout, - pipeline, - } } + + let mut pass = command_encoder.begin_compute_pass(&ComputePassDescriptor { + label: Some("pathtracer"), + timestamp_writes: None, + }); + pass.set_pipeline(pipeline); + pass.set_bind_group(0, scene_bind_group, &[]); + pass.set_bind_group(1, &bind_group, &[view_uniform_offset.offset]); + pass.dispatch_workgroups(viewport.x.div_ceil(8), viewport.y.div_ceil(8), 1); } diff --git a/crates/bevy_solari/src/realtime/mod.rs b/crates/bevy_solari/src/realtime/mod.rs index 0ab1e13fe6029..780f518a4758d 100644 --- a/crates/bevy_solari/src/realtime/mod.rs +++ b/crates/bevy_solari/src/realtime/mod.rs @@ -6,24 +6,22 @@ use crate::SolariPlugins; use bevy_app::{App, Plugin}; use bevy_asset::embedded_asset; use bevy_core_pipeline::{ - core_3d::graph::{Core3d, Node3d}, prepass::{ DeferredPrepass, DeferredPrepassDoubleBuffer, DepthPrepass, DepthPrepassDoubleBuffer, MotionVectorPrepass, }, + schedule::{Core3d, Core3dSystems}, }; use bevy_ecs::{component::Component, reflect::ReflectComponent, schedule::IntoScheduleConfigs}; use bevy_pbr::DefaultOpaqueRendererMethod; use bevy_reflect::{std_traits::ReflectDefault, Reflect}; use bevy_render::{ - render_graph::{RenderGraphExt, ViewNodeRunner}, - renderer::RenderDevice, - view::Hdr, - ExtractSchedule, Render, RenderApp, RenderSystems, + renderer::RenderDevice, view::Hdr, ExtractSchedule, Render, RenderApp, RenderStartup, + RenderSystems, }; use bevy_shader::load_shader_library; use extract::extract_solari_lighting; -use node::SolariLightingNode; +use node::{init_solari_lighting_pipelines, solari_lighting}; use prepare::prepare_solari_lighting_resources; use tracing::warn; @@ -64,22 +62,17 @@ impl Plugin for SolariLightingPlugin { } render_app + .add_systems(RenderStartup, init_solari_lighting_pipelines) .add_systems(ExtractSchedule, extract_solari_lighting) .add_systems( Render, prepare_solari_lighting_resources.in_set(RenderSystems::PrepareResources), ) - .add_render_graph_node::>( - Core3d, - node::graph::SolariLightingNode, - ) - .add_render_graph_edges( + .add_systems( Core3d, - ( - Node3d::EndPrepasses, - node::graph::SolariLightingNode, - Node3d::EndMainPass, - ), + solari_lighting + .after(Core3dSystems::EndPrepasses) + .before(Core3dSystems::EndMainPass), ); } } diff --git a/crates/bevy_solari/src/realtime/node.rs b/crates/bevy_solari/src/realtime/node.rs index 9e280cd0e1416..c62ec19c3b66c 100644 --- a/crates/bevy_solari/src/realtime/node.rs +++ b/crates/bevy_solari/src/realtime/node.rs @@ -5,18 +5,13 @@ use super::{ use crate::scene::RaytracingSceneBindings; #[cfg(all(feature = "dlss", not(feature = "force_disable_dlss")))] use bevy_anti_alias::dlss::ViewDlssRayReconstructionTextures; -use bevy_asset::{load_embedded_asset, Handle}; +use bevy_asset::{load_embedded_asset, AssetServer, Handle}; use bevy_core_pipeline::prepass::{ PreviousViewData, PreviousViewUniformOffset, PreviousViewUniforms, ViewPrepassTextures, }; use bevy_diagnostic::FrameCount; -use bevy_ecs::{ - query::QueryItem, - world::{FromWorld, World}, -}; +use bevy_ecs::{prelude::*, resource::Resource, system::Commands}; use bevy_render::{ - diagnostic::RecordDiagnostics, - render_graph::{NodeRunError, RenderGraphContext, ViewNode}, render_resource::{ binding_types::{ storage_buffer_sized, texture_2d, texture_depth_2d, texture_storage_2d, uniform_buffer, @@ -26,20 +21,15 @@ use bevy_render::{ PipelineCache, PushConstantRange, RenderPassDescriptor, ShaderStages, StorageTextureAccess, TextureFormat, TextureSampleType, }, - renderer::RenderContext, + renderer::{RenderContext, RenderDevice, ViewQuery}, view::{ViewTarget, ViewUniform, ViewUniformOffset, ViewUniforms}, }; use bevy_shader::{Shader, ShaderDefVal}; use bevy_utils::default; -pub mod graph { - use bevy_render::render_graph::RenderLabel; - - #[derive(Debug, Hash, PartialEq, Eq, Clone, RenderLabel)] - pub struct SolariLightingNode; -} - -pub struct SolariLightingNode { +/// Resource holding the Solari lighting pipeline configuration. +#[derive(Resource)] +pub struct SolariLightingPipelines { bind_group_layout: BindGroupLayoutDescriptor, bind_group_layout_world_cache_active_cells_dispatch: BindGroupLayoutDescriptor, #[cfg(all(feature = "dlss", not(feature = "force_disable_dlss")))] @@ -60,483 +50,688 @@ pub struct SolariLightingNode { resolve_dlss_rr_textures_pipeline: CachedComputePipelineId, } -impl ViewNode for SolariLightingNode { - #[cfg(any(not(feature = "dlss"), feature = "force_disable_dlss"))] - type ViewQuery = ( - &'static SolariLighting, - &'static SolariLightingResources, - &'static ViewTarget, - &'static ViewPrepassTextures, - &'static ViewUniformOffset, - &'static PreviousViewUniformOffset, +/// Initializes the Solari lighting pipelines at render startup. +pub fn init_solari_lighting_pipelines( + mut commands: Commands, + pipeline_cache: Res, + scene_bindings: Res, + asset_server: Res, +) { + let bind_group_layout = BindGroupLayoutDescriptor::new( + "solari_lighting_bind_group_layout", + &BindGroupLayoutEntries::sequential( + ShaderStages::COMPUTE, + ( + texture_storage_2d( + ViewTarget::TEXTURE_FORMAT_HDR, + StorageTextureAccess::ReadWrite, + ), + storage_buffer_sized(false, None), + storage_buffer_sized(false, None), + texture_storage_2d(TextureFormat::Rgba32Uint, StorageTextureAccess::ReadWrite), + texture_storage_2d(TextureFormat::Rgba32Uint, StorageTextureAccess::ReadWrite), + storage_buffer_sized(false, None), + storage_buffer_sized(false, None), + texture_2d(TextureSampleType::Uint), + texture_depth_2d(), + texture_2d(TextureSampleType::Float { filterable: true }), + texture_2d(TextureSampleType::Uint), + texture_depth_2d(), + uniform_buffer::(true), + uniform_buffer::(true), + storage_buffer_sized(false, None), + storage_buffer_sized(false, None), + storage_buffer_sized(false, None), + storage_buffer_sized(false, None), + storage_buffer_sized(false, None), + storage_buffer_sized(false, None), + storage_buffer_sized(false, None), + storage_buffer_sized(false, None), + storage_buffer_sized(false, None), + storage_buffer_sized(false, None), + ), + ), + ); + + let bind_group_layout_world_cache_active_cells_dispatch = BindGroupLayoutDescriptor::new( + "solari_lighting_bind_group_layout_world_cache_active_cells_dispatch", + &BindGroupLayoutEntries::single( + ShaderStages::COMPUTE, + storage_buffer_sized(false, None), + ), ); + #[cfg(all(feature = "dlss", not(feature = "force_disable_dlss")))] - type ViewQuery = ( - &'static SolariLighting, - &'static SolariLightingResources, - &'static ViewTarget, - &'static ViewPrepassTextures, - &'static ViewUniformOffset, - &'static PreviousViewUniformOffset, - Option<&'static ViewDlssRayReconstructionTextures>, + let bind_group_layout_resolve_dlss_rr_textures = BindGroupLayoutDescriptor::new( + "solari_lighting_bind_group_layout_resolve_dlss_rr_textures", + &BindGroupLayoutEntries::sequential( + ShaderStages::COMPUTE, + ( + texture_storage_2d(TextureFormat::Rgba8Unorm, StorageTextureAccess::WriteOnly), + texture_storage_2d(TextureFormat::Rgba8Unorm, StorageTextureAccess::WriteOnly), + texture_storage_2d(TextureFormat::Rgba16Float, StorageTextureAccess::WriteOnly), + texture_storage_2d(TextureFormat::Rg16Float, StorageTextureAccess::WriteOnly), + ), + ), ); - fn run( - &self, - _graph: &mut RenderGraphContext, - render_context: &mut RenderContext, - #[cfg(any(not(feature = "dlss"), feature = "force_disable_dlss"))] ( - solari_lighting, - solari_lighting_resources, - view_target, - view_prepass_textures, - view_uniform_offset, - previous_view_uniform_offset, - ): QueryItem, - #[cfg(all(feature = "dlss", not(feature = "force_disable_dlss")))] ( - solari_lighting, - solari_lighting_resources, - view_target, - view_prepass_textures, - view_uniform_offset, - previous_view_uniform_offset, - view_dlss_rr_textures, - ): QueryItem, - world: &World, - ) -> Result<(), NodeRunError> { - let pipeline_cache = world.resource::(); - let scene_bindings = world.resource::(); - let view_uniforms = world.resource::(); - let previous_view_uniforms = world.resource::(); - let frame_count = world.resource::(); - let ( - Some(decay_world_cache_pipeline), - Some(compact_world_cache_single_block_pipeline), - Some(compact_world_cache_blocks_pipeline), - Some(compact_world_cache_write_active_cells_pipeline), - Some(sample_for_world_cache_pipeline), - Some(blend_new_world_cache_samples_pipeline), - Some(presample_light_tiles_pipeline), - Some(di_initial_and_temporal_pipeline), - Some(di_spatial_and_shade_pipeline), - Some(gi_initial_and_temporal_pipeline), - Some(gi_spatial_and_shade_pipeline), - Some(specular_gi_pipeline), - Some(scene_bindings), - Some(gbuffer), - Some(depth_buffer), - Some(motion_vectors), - Some(previous_gbuffer), - Some(previous_depth_buffer), - Some(view_uniforms), - Some(previous_view_uniforms), - ) = ( - pipeline_cache.get_compute_pipeline(self.decay_world_cache_pipeline), - pipeline_cache.get_compute_pipeline(self.compact_world_cache_single_block_pipeline), - pipeline_cache.get_compute_pipeline(self.compact_world_cache_blocks_pipeline), - pipeline_cache - .get_compute_pipeline(self.compact_world_cache_write_active_cells_pipeline), - pipeline_cache.get_compute_pipeline(self.sample_for_world_cache_pipeline), - pipeline_cache.get_compute_pipeline(self.blend_new_world_cache_samples_pipeline), - pipeline_cache.get_compute_pipeline(self.presample_light_tiles_pipeline), - pipeline_cache.get_compute_pipeline(self.di_initial_and_temporal_pipeline), - pipeline_cache.get_compute_pipeline(self.di_spatial_and_shade_pipeline), - pipeline_cache.get_compute_pipeline(self.gi_initial_and_temporal_pipeline), - pipeline_cache.get_compute_pipeline(self.gi_spatial_and_shade_pipeline), - pipeline_cache.get_compute_pipeline(self.specular_gi_pipeline), - &scene_bindings.bind_group, - view_prepass_textures.deferred_view(), - view_prepass_textures.depth_view(), - view_prepass_textures.motion_vectors_view(), - view_prepass_textures.previous_deferred_view(), - view_prepass_textures.previous_depth_view(), - view_uniforms.uniforms.binding(), - previous_view_uniforms.uniforms.binding(), - ) - else { - return Ok(()); - }; + let create_pipeline = |label: &'static str, + entry_point: &'static str, + shader: Handle, + extra_bind_group_layout: Option<&BindGroupLayoutDescriptor>, + extra_shader_defs: Vec| { + let mut layout = vec![ + scene_bindings.bind_group_layout.clone(), + bind_group_layout.clone(), + ]; + if let Some(extra_bind_group_layout) = extra_bind_group_layout { + layout.push(extra_bind_group_layout.clone()); + } + + let mut shader_defs = vec![ShaderDefVal::UInt( + "WORLD_CACHE_SIZE".into(), + WORLD_CACHE_SIZE as u32, + )]; + shader_defs.extend_from_slice(&extra_shader_defs); + + pipeline_cache.queue_compute_pipeline(ComputePipelineDescriptor { + label: Some(label.into()), + layout, + push_constant_ranges: vec![PushConstantRange { + stages: ShaderStages::COMPUTE, + range: 0..8, + }], + shader, + shader_defs, + entry_point: Some(entry_point.into()), + ..default() + }) + }; + + commands.insert_resource(SolariLightingPipelines { + bind_group_layout: bind_group_layout.clone(), + bind_group_layout_world_cache_active_cells_dispatch: + bind_group_layout_world_cache_active_cells_dispatch.clone(), #[cfg(all(feature = "dlss", not(feature = "force_disable_dlss")))] - let Some(resolve_dlss_rr_textures_pipeline) = - pipeline_cache.get_compute_pipeline(self.resolve_dlss_rr_textures_pipeline) - else { - return Ok(()); - }; - - let view_target = view_target.get_unsampled_color_attachment(); - - let s = solari_lighting_resources; - let bind_group = render_context.render_device().create_bind_group( - "solari_lighting_bind_group", - &pipeline_cache.get_bind_group_layout(&self.bind_group_layout), - &BindGroupEntries::sequential(( - view_target.view, - s.light_tile_samples.as_entire_binding(), - s.light_tile_resolved_samples.as_entire_binding(), - &s.di_reservoirs_a.1, - &s.di_reservoirs_b.1, - s.gi_reservoirs_a.as_entire_binding(), - s.gi_reservoirs_b.as_entire_binding(), - gbuffer, - depth_buffer, - motion_vectors, - previous_gbuffer, - previous_depth_buffer, - view_uniforms, - previous_view_uniforms, - s.world_cache_checksums.as_entire_binding(), - s.world_cache_life.as_entire_binding(), - s.world_cache_radiance.as_entire_binding(), - s.world_cache_geometry_data.as_entire_binding(), - s.world_cache_luminance_deltas.as_entire_binding(), - s.world_cache_active_cells_new_radiance.as_entire_binding(), - s.world_cache_a.as_entire_binding(), - s.world_cache_b.as_entire_binding(), - s.world_cache_active_cell_indices.as_entire_binding(), - s.world_cache_active_cells_count.as_entire_binding(), - )), - ); - let bind_group_world_cache_active_cells_dispatch = - render_context.render_device().create_bind_group( - "solari_lighting_bind_group_world_cache_active_cells_dispatch", - &pipeline_cache.get_bind_group_layout( - &self.bind_group_layout_world_cache_active_cells_dispatch, - ), - &BindGroupEntries::single(s.world_cache_active_cells_dispatch.as_entire_binding()), - ); + bind_group_layout_resolve_dlss_rr_textures: bind_group_layout_resolve_dlss_rr_textures + .clone(), + decay_world_cache_pipeline: create_pipeline( + "solari_lighting_decay_world_cache_pipeline", + "decay_world_cache", + load_embedded_asset!(asset_server.as_ref(), "world_cache_compact.wgsl"), + Some(&bind_group_layout_world_cache_active_cells_dispatch), + vec!["WORLD_CACHE_NON_ATOMIC_LIFE_BUFFER".into()], + ), + compact_world_cache_single_block_pipeline: create_pipeline( + "solari_lighting_compact_world_cache_single_block_pipeline", + "compact_world_cache_single_block", + load_embedded_asset!(asset_server.as_ref(), "world_cache_compact.wgsl"), + Some(&bind_group_layout_world_cache_active_cells_dispatch), + vec!["WORLD_CACHE_NON_ATOMIC_LIFE_BUFFER".into()], + ), + compact_world_cache_blocks_pipeline: create_pipeline( + "solari_lighting_compact_world_cache_blocks_pipeline", + "compact_world_cache_blocks", + load_embedded_asset!(asset_server.as_ref(), "world_cache_compact.wgsl"), + Some(&bind_group_layout_world_cache_active_cells_dispatch), + vec![], + ), + compact_world_cache_write_active_cells_pipeline: create_pipeline( + "solari_lighting_compact_world_cache_write_active_cells_pipeline", + "compact_world_cache_write_active_cells", + load_embedded_asset!(asset_server.as_ref(), "world_cache_compact.wgsl"), + Some(&bind_group_layout_world_cache_active_cells_dispatch), + vec!["WORLD_CACHE_NON_ATOMIC_LIFE_BUFFER".into()], + ), + sample_for_world_cache_pipeline: create_pipeline( + "solari_lighting_sample_for_world_cache_pipeline", + "sample_radiance", + load_embedded_asset!(asset_server.as_ref(), "world_cache_update.wgsl"), + None, + vec!["WORLD_CACHE_QUERY_ATOMIC_MAX_LIFETIME".into()], + ), + blend_new_world_cache_samples_pipeline: create_pipeline( + "solari_lighting_blend_new_world_cache_samples_pipeline", + "blend_new_samples", + load_embedded_asset!(asset_server.as_ref(), "world_cache_update.wgsl"), + None, + vec![], + ), + presample_light_tiles_pipeline: create_pipeline( + "solari_lighting_presample_light_tiles_pipeline", + "presample_light_tiles", + load_embedded_asset!(asset_server.as_ref(), "presample_light_tiles.wgsl"), + None, + vec![], + ), + di_initial_and_temporal_pipeline: create_pipeline( + "solari_lighting_di_initial_and_temporal_pipeline", + "initial_and_temporal", + load_embedded_asset!(asset_server.as_ref(), "restir_di.wgsl"), + None, + vec![], + ), + di_spatial_and_shade_pipeline: create_pipeline( + "solari_lighting_di_spatial_and_shade_pipeline", + "spatial_and_shade", + load_embedded_asset!(asset_server.as_ref(), "restir_di.wgsl"), + None, + vec![], + ), + gi_initial_and_temporal_pipeline: create_pipeline( + "solari_lighting_gi_initial_and_temporal_pipeline", + "initial_and_temporal", + load_embedded_asset!(asset_server.as_ref(), "restir_gi.wgsl"), + None, + vec![], + ), + gi_spatial_and_shade_pipeline: create_pipeline( + "solari_lighting_gi_spatial_and_shade_pipeline", + "spatial_and_shade", + load_embedded_asset!(asset_server.as_ref(), "restir_gi.wgsl"), + None, + vec![], + ), + specular_gi_pipeline: create_pipeline( + "solari_lighting_specular_gi_pipeline", + "specular_gi", + load_embedded_asset!(asset_server.as_ref(), "specular_gi.wgsl"), + None, + vec![], + ), #[cfg(all(feature = "dlss", not(feature = "force_disable_dlss")))] - let bind_group_resolve_dlss_rr_textures = view_dlss_rr_textures.map(|d| { - render_context.render_device().create_bind_group( - "solari_lighting_bind_group_resolve_dlss_rr_textures", - &pipeline_cache - .get_bind_group_layout(&self.bind_group_layout_resolve_dlss_rr_textures), - &BindGroupEntries::sequential(( - &d.diffuse_albedo.default_view, - &d.specular_albedo.default_view, - &d.normal_roughness.default_view, - &d.specular_motion_vectors.default_view, - )), - ) - }); + resolve_dlss_rr_textures_pipeline: create_pipeline( + "solari_lighting_resolve_dlss_rr_textures_pipeline", + "resolve_dlss_rr_textures", + load_embedded_asset!(asset_server.as_ref(), "resolve_dlss_rr_textures.wgsl"), + Some(&bind_group_layout_resolve_dlss_rr_textures), + vec![], + ), + }); +} - // Choice of number here is arbitrary - let frame_index = frame_count.0.wrapping_mul(5782582); - - let diagnostics = render_context.diagnostic_recorder(); - let command_encoder = render_context.command_encoder(); - - // Clear the view target if we're the first node to write to it - if matches!(view_target.ops.load, LoadOp::Clear(_)) { - command_encoder.begin_render_pass(&RenderPassDescriptor { - label: Some("solari_lighting_clear"), - color_attachments: &[Some(view_target)], - depth_stencil_attachment: None, - timestamp_writes: None, - occlusion_query_set: None, - }); - } +#[cfg(any(not(feature = "dlss"), feature = "force_disable_dlss"))] +pub fn solari_lighting( + view: ViewQuery<( + &SolariLighting, + &SolariLightingResources, + &ViewTarget, + &ViewPrepassTextures, + &ViewUniformOffset, + &PreviousViewUniformOffset, + )>, + solari_pipelines: Option>, + pipeline_cache: Res, + scene_bindings: Res, + view_uniforms: Res, + previous_view_uniforms: Res, + frame_count: Res, + render_device: Res, + mut ctx: RenderContext, +) { + let ( + solari_lighting, + solari_lighting_resources, + view_target, + view_prepass_textures, + view_uniform_offset, + previous_view_uniform_offset, + ) = view.into_inner(); + + let Some(pipelines) = solari_pipelines else { + return; + }; + + let ( + Some(decay_world_cache_pipeline), + Some(compact_world_cache_single_block_pipeline), + Some(compact_world_cache_blocks_pipeline), + Some(compact_world_cache_write_active_cells_pipeline), + Some(sample_for_world_cache_pipeline), + Some(blend_new_world_cache_samples_pipeline), + Some(presample_light_tiles_pipeline), + Some(di_initial_and_temporal_pipeline), + Some(di_spatial_and_shade_pipeline), + Some(gi_initial_and_temporal_pipeline), + Some(gi_spatial_and_shade_pipeline), + Some(specular_gi_pipeline), + Some(scene_bind_group), + Some(gbuffer), + Some(depth_buffer), + Some(motion_vectors), + Some(previous_gbuffer), + Some(previous_depth_buffer), + Some(view_uniforms_binding), + Some(previous_view_uniforms_binding), + ) = ( + pipeline_cache.get_compute_pipeline(pipelines.decay_world_cache_pipeline), + pipeline_cache.get_compute_pipeline(pipelines.compact_world_cache_single_block_pipeline), + pipeline_cache.get_compute_pipeline(pipelines.compact_world_cache_blocks_pipeline), + pipeline_cache.get_compute_pipeline(pipelines.compact_world_cache_write_active_cells_pipeline), + pipeline_cache.get_compute_pipeline(pipelines.sample_for_world_cache_pipeline), + pipeline_cache.get_compute_pipeline(pipelines.blend_new_world_cache_samples_pipeline), + pipeline_cache.get_compute_pipeline(pipelines.presample_light_tiles_pipeline), + pipeline_cache.get_compute_pipeline(pipelines.di_initial_and_temporal_pipeline), + pipeline_cache.get_compute_pipeline(pipelines.di_spatial_and_shade_pipeline), + pipeline_cache.get_compute_pipeline(pipelines.gi_initial_and_temporal_pipeline), + pipeline_cache.get_compute_pipeline(pipelines.gi_spatial_and_shade_pipeline), + pipeline_cache.get_compute_pipeline(pipelines.specular_gi_pipeline), + &scene_bindings.bind_group, + view_prepass_textures.deferred_view(), + view_prepass_textures.depth_view(), + view_prepass_textures.motion_vectors_view(), + view_prepass_textures.previous_deferred_view(), + view_prepass_textures.previous_depth_view(), + view_uniforms.uniforms.binding(), + previous_view_uniforms.uniforms.binding(), + ) + else { + return; + }; + + let view_target_attachment = view_target.get_unsampled_color_attachment(); + + let s = solari_lighting_resources; + let bind_group = render_device.create_bind_group( + "solari_lighting_bind_group", + &pipeline_cache.get_bind_group_layout(&pipelines.bind_group_layout), + &BindGroupEntries::sequential(( + view_target_attachment.view, + s.light_tile_samples.as_entire_binding(), + s.light_tile_resolved_samples.as_entire_binding(), + &s.di_reservoirs_a.1, + &s.di_reservoirs_b.1, + s.gi_reservoirs_a.as_entire_binding(), + s.gi_reservoirs_b.as_entire_binding(), + gbuffer, + depth_buffer, + motion_vectors, + previous_gbuffer, + previous_depth_buffer, + view_uniforms_binding, + previous_view_uniforms_binding, + s.world_cache_checksums.as_entire_binding(), + s.world_cache_life.as_entire_binding(), + s.world_cache_radiance.as_entire_binding(), + s.world_cache_geometry_data.as_entire_binding(), + s.world_cache_luminance_deltas.as_entire_binding(), + s.world_cache_active_cells_new_radiance.as_entire_binding(), + s.world_cache_a.as_entire_binding(), + s.world_cache_b.as_entire_binding(), + s.world_cache_active_cell_indices.as_entire_binding(), + s.world_cache_active_cells_count.as_entire_binding(), + )), + ); + let bind_group_world_cache_active_cells_dispatch = render_device.create_bind_group( + "solari_lighting_bind_group_world_cache_active_cells_dispatch", + &pipeline_cache.get_bind_group_layout( + &pipelines.bind_group_layout_world_cache_active_cells_dispatch, + ), + &BindGroupEntries::single(s.world_cache_active_cells_dispatch.as_entire_binding()), + ); + + // Choice of number here is arbitrary + let frame_index = frame_count.0.wrapping_mul(5782582); - let mut pass = command_encoder.begin_compute_pass(&ComputePassDescriptor { - label: Some("solari_lighting"), + let command_encoder = ctx.command_encoder(); + + // Clear the view target if we're the first node to write to it + if matches!(view_target_attachment.ops.load, LoadOp::Clear(_)) { + command_encoder.begin_render_pass(&RenderPassDescriptor { + label: Some("solari_lighting_clear"), + color_attachments: &[Some(view_target_attachment)], + depth_stencil_attachment: None, timestamp_writes: None, + occlusion_query_set: None, }); - let pass_span = diagnostics.pass_span(&mut pass, "solari_lighting"); + } - let dx = solari_lighting_resources.view_size.x.div_ceil(8); - let dy = solari_lighting_resources.view_size.y.div_ceil(8); + let mut pass = command_encoder.begin_compute_pass(&ComputePassDescriptor { + label: Some("solari_lighting"), + timestamp_writes: None, + }); + + let dx = solari_lighting_resources.view_size.x.div_ceil(8); + let dy = solari_lighting_resources.view_size.y.div_ceil(8); + + pass.set_bind_group(0, scene_bind_group, &[]); + pass.set_bind_group( + 1, + &bind_group, + &[ + view_uniform_offset.offset, + previous_view_uniform_offset.offset, + ], + ); - pass.set_bind_group(0, scene_bindings, &[]); - pass.set_bind_group( - 1, - &bind_group, - &[ - view_uniform_offset.offset, - previous_view_uniform_offset.offset, - ], - ); + pass.set_pipeline(presample_light_tiles_pipeline); + pass.set_push_constants( + 0, + bytemuck::cast_slice(&[frame_index, solari_lighting.reset as u32]), + ); + pass.dispatch_workgroups(LIGHT_TILE_BLOCKS as u32, 1, 1); - #[cfg(all(feature = "dlss", not(feature = "force_disable_dlss")))] - if let Some(bind_group_resolve_dlss_rr_textures) = bind_group_resolve_dlss_rr_textures { - pass.set_bind_group(2, &bind_group_resolve_dlss_rr_textures, &[]); - pass.set_pipeline(resolve_dlss_rr_textures_pipeline); - pass.dispatch_workgroups(dx, dy, 1); - } + pass.set_bind_group(2, &bind_group_world_cache_active_cells_dispatch, &[]); - pass.set_pipeline(presample_light_tiles_pipeline); - pass.set_push_constants( - 0, - bytemuck::cast_slice(&[frame_index, solari_lighting.reset as u32]), - ); - pass.dispatch_workgroups(LIGHT_TILE_BLOCKS as u32, 1, 1); - - pass.set_bind_group(2, &bind_group_world_cache_active_cells_dispatch, &[]); - - pass.set_pipeline(decay_world_cache_pipeline); - pass.dispatch_workgroups((WORLD_CACHE_SIZE / 1024) as u32, 1, 1); - - pass.set_pipeline(compact_world_cache_single_block_pipeline); - pass.dispatch_workgroups((WORLD_CACHE_SIZE / 1024) as u32, 1, 1); - - pass.set_pipeline(compact_world_cache_blocks_pipeline); - pass.dispatch_workgroups(1, 1, 1); - - pass.set_pipeline(compact_world_cache_write_active_cells_pipeline); - pass.dispatch_workgroups((WORLD_CACHE_SIZE / 1024) as u32, 1, 1); - - pass.set_bind_group(2, None, &[]); - - pass.set_pipeline(sample_for_world_cache_pipeline); - pass.set_push_constants( - 0, - bytemuck::cast_slice(&[frame_index, solari_lighting.reset as u32]), - ); - pass.dispatch_workgroups_indirect( - &solari_lighting_resources.world_cache_active_cells_dispatch, - 0, - ); - - pass.set_pipeline(blend_new_world_cache_samples_pipeline); - pass.dispatch_workgroups_indirect( - &solari_lighting_resources.world_cache_active_cells_dispatch, - 0, - ); - - pass.set_pipeline(di_initial_and_temporal_pipeline); - pass.set_push_constants( - 0, - bytemuck::cast_slice(&[frame_index, solari_lighting.reset as u32]), - ); - pass.dispatch_workgroups(dx, dy, 1); + pass.set_pipeline(decay_world_cache_pipeline); + pass.dispatch_workgroups((WORLD_CACHE_SIZE / 1024) as u32, 1, 1); - pass.set_pipeline(di_spatial_and_shade_pipeline); - pass.set_push_constants( - 0, - bytemuck::cast_slice(&[frame_index, solari_lighting.reset as u32]), - ); - pass.dispatch_workgroups(dx, dy, 1); + pass.set_pipeline(compact_world_cache_single_block_pipeline); + pass.dispatch_workgroups((WORLD_CACHE_SIZE / 1024) as u32, 1, 1); - pass.set_pipeline(gi_initial_and_temporal_pipeline); - pass.set_push_constants( - 0, - bytemuck::cast_slice(&[frame_index, solari_lighting.reset as u32]), - ); - pass.dispatch_workgroups(dx, dy, 1); + pass.set_pipeline(compact_world_cache_blocks_pipeline); + pass.dispatch_workgroups(1, 1, 1); - pass.set_pipeline(gi_spatial_and_shade_pipeline); - pass.set_push_constants( - 0, - bytemuck::cast_slice(&[frame_index, solari_lighting.reset as u32]), - ); - pass.dispatch_workgroups(dx, dy, 1); + pass.set_pipeline(compact_world_cache_write_active_cells_pipeline); + pass.dispatch_workgroups((WORLD_CACHE_SIZE / 1024) as u32, 1, 1); - pass.set_pipeline(specular_gi_pipeline); - pass.set_push_constants( - 0, - bytemuck::cast_slice(&[frame_index, solari_lighting.reset as u32]), - ); - pass.dispatch_workgroups(dx, dy, 1); + pass.set_bind_group(2, None, &[]); + + pass.set_pipeline(sample_for_world_cache_pipeline); + pass.set_push_constants( + 0, + bytemuck::cast_slice(&[frame_index, solari_lighting.reset as u32]), + ); + pass.dispatch_workgroups_indirect( + &solari_lighting_resources.world_cache_active_cells_dispatch, + 0, + ); - pass_span.end(&mut pass); + pass.set_pipeline(blend_new_world_cache_samples_pipeline); + pass.dispatch_workgroups_indirect( + &solari_lighting_resources.world_cache_active_cells_dispatch, + 0, + ); - Ok(()) - } + pass.set_pipeline(di_initial_and_temporal_pipeline); + pass.set_push_constants( + 0, + bytemuck::cast_slice(&[frame_index, solari_lighting.reset as u32]), + ); + pass.dispatch_workgroups(dx, dy, 1); + + pass.set_pipeline(di_spatial_and_shade_pipeline); + pass.set_push_constants( + 0, + bytemuck::cast_slice(&[frame_index, solari_lighting.reset as u32]), + ); + pass.dispatch_workgroups(dx, dy, 1); + + pass.set_pipeline(gi_initial_and_temporal_pipeline); + pass.set_push_constants( + 0, + bytemuck::cast_slice(&[frame_index, solari_lighting.reset as u32]), + ); + pass.dispatch_workgroups(dx, dy, 1); + + pass.set_pipeline(gi_spatial_and_shade_pipeline); + pass.set_push_constants( + 0, + bytemuck::cast_slice(&[frame_index, solari_lighting.reset as u32]), + ); + pass.dispatch_workgroups(dx, dy, 1); + + pass.set_pipeline(specular_gi_pipeline); + pass.set_push_constants( + 0, + bytemuck::cast_slice(&[frame_index, solari_lighting.reset as u32]), + ); + pass.dispatch_workgroups(dx, dy, 1); } -impl FromWorld for SolariLightingNode { - fn from_world(world: &mut World) -> Self { - let pipeline_cache = world.resource::(); - let scene_bindings = world.resource::(); - - let bind_group_layout = BindGroupLayoutDescriptor::new( - "solari_lighting_bind_group_layout", - &BindGroupLayoutEntries::sequential( - ShaderStages::COMPUTE, - ( - texture_storage_2d( - ViewTarget::TEXTURE_FORMAT_HDR, - StorageTextureAccess::ReadWrite, - ), - storage_buffer_sized(false, None), - storage_buffer_sized(false, None), - texture_storage_2d(TextureFormat::Rgba32Uint, StorageTextureAccess::ReadWrite), - texture_storage_2d(TextureFormat::Rgba32Uint, StorageTextureAccess::ReadWrite), - storage_buffer_sized(false, None), - storage_buffer_sized(false, None), - texture_2d(TextureSampleType::Uint), - texture_depth_2d(), - texture_2d(TextureSampleType::Float { filterable: true }), - texture_2d(TextureSampleType::Uint), - texture_depth_2d(), - uniform_buffer::(true), - uniform_buffer::(true), - storage_buffer_sized(false, None), - storage_buffer_sized(false, None), - storage_buffer_sized(false, None), - storage_buffer_sized(false, None), - storage_buffer_sized(false, None), - storage_buffer_sized(false, None), - storage_buffer_sized(false, None), - storage_buffer_sized(false, None), - storage_buffer_sized(false, None), - storage_buffer_sized(false, None), - ), - ), - ); +#[cfg(all(feature = "dlss", not(feature = "force_disable_dlss")))] +pub fn solari_lighting( + view: ViewQuery<( + &SolariLighting, + &SolariLightingResources, + &ViewTarget, + &ViewPrepassTextures, + &ViewUniformOffset, + &PreviousViewUniformOffset, + Option<&ViewDlssRayReconstructionTextures>, + )>, + solari_pipelines: Option>, + pipeline_cache: Res, + scene_bindings: Res, + view_uniforms: Res, + previous_view_uniforms: Res, + frame_count: Res, + render_device: Res, + mut ctx: RenderContext, +) { + let ( + solari_lighting, + solari_lighting_resources, + view_target, + view_prepass_textures, + view_uniform_offset, + previous_view_uniform_offset, + view_dlss_rr_textures, + ) = view.into_inner(); + + let Some(pipelines) = solari_pipelines else { + return; + }; + + let ( + Some(decay_world_cache_pipeline), + Some(compact_world_cache_single_block_pipeline), + Some(compact_world_cache_blocks_pipeline), + Some(compact_world_cache_write_active_cells_pipeline), + Some(sample_for_world_cache_pipeline), + Some(blend_new_world_cache_samples_pipeline), + Some(presample_light_tiles_pipeline), + Some(di_initial_and_temporal_pipeline), + Some(di_spatial_and_shade_pipeline), + Some(gi_initial_and_temporal_pipeline), + Some(gi_spatial_and_shade_pipeline), + Some(specular_gi_pipeline), + Some(scene_bind_group), + Some(gbuffer), + Some(depth_buffer), + Some(motion_vectors), + Some(previous_gbuffer), + Some(previous_depth_buffer), + Some(view_uniforms_binding), + Some(previous_view_uniforms_binding), + ) = ( + pipeline_cache.get_compute_pipeline(pipelines.decay_world_cache_pipeline), + pipeline_cache.get_compute_pipeline(pipelines.compact_world_cache_single_block_pipeline), + pipeline_cache.get_compute_pipeline(pipelines.compact_world_cache_blocks_pipeline), + pipeline_cache.get_compute_pipeline(pipelines.compact_world_cache_write_active_cells_pipeline), + pipeline_cache.get_compute_pipeline(pipelines.sample_for_world_cache_pipeline), + pipeline_cache.get_compute_pipeline(pipelines.blend_new_world_cache_samples_pipeline), + pipeline_cache.get_compute_pipeline(pipelines.presample_light_tiles_pipeline), + pipeline_cache.get_compute_pipeline(pipelines.di_initial_and_temporal_pipeline), + pipeline_cache.get_compute_pipeline(pipelines.di_spatial_and_shade_pipeline), + pipeline_cache.get_compute_pipeline(pipelines.gi_initial_and_temporal_pipeline), + pipeline_cache.get_compute_pipeline(pipelines.gi_spatial_and_shade_pipeline), + pipeline_cache.get_compute_pipeline(pipelines.specular_gi_pipeline), + &scene_bindings.bind_group, + view_prepass_textures.deferred_view(), + view_prepass_textures.depth_view(), + view_prepass_textures.motion_vectors_view(), + view_prepass_textures.previous_deferred_view(), + view_prepass_textures.previous_depth_view(), + view_uniforms.uniforms.binding(), + previous_view_uniforms.uniforms.binding(), + ) + else { + return; + }; + + let Some(resolve_dlss_rr_textures_pipeline) = + pipeline_cache.get_compute_pipeline(pipelines.resolve_dlss_rr_textures_pipeline) + else { + return; + }; + + let view_target_attachment = view_target.get_unsampled_color_attachment(); + + let s = solari_lighting_resources; + let bind_group = render_device.create_bind_group( + "solari_lighting_bind_group", + &pipeline_cache.get_bind_group_layout(&pipelines.bind_group_layout), + &BindGroupEntries::sequential(( + view_target_attachment.view, + s.light_tile_samples.as_entire_binding(), + s.light_tile_resolved_samples.as_entire_binding(), + &s.di_reservoirs_a.1, + &s.di_reservoirs_b.1, + s.gi_reservoirs_a.as_entire_binding(), + s.gi_reservoirs_b.as_entire_binding(), + gbuffer, + depth_buffer, + motion_vectors, + previous_gbuffer, + previous_depth_buffer, + view_uniforms_binding, + previous_view_uniforms_binding, + s.world_cache_checksums.as_entire_binding(), + s.world_cache_life.as_entire_binding(), + s.world_cache_radiance.as_entire_binding(), + s.world_cache_geometry_data.as_entire_binding(), + s.world_cache_luminance_deltas.as_entire_binding(), + s.world_cache_active_cells_new_radiance.as_entire_binding(), + s.world_cache_a.as_entire_binding(), + s.world_cache_b.as_entire_binding(), + s.world_cache_active_cell_indices.as_entire_binding(), + s.world_cache_active_cells_count.as_entire_binding(), + )), + ); + let bind_group_world_cache_active_cells_dispatch = render_device.create_bind_group( + "solari_lighting_bind_group_world_cache_active_cells_dispatch", + &pipeline_cache.get_bind_group_layout( + &pipelines.bind_group_layout_world_cache_active_cells_dispatch, + ), + &BindGroupEntries::single(s.world_cache_active_cells_dispatch.as_entire_binding()), + ); - let bind_group_layout_world_cache_active_cells_dispatch = BindGroupLayoutDescriptor::new( - "solari_lighting_bind_group_layout_world_cache_active_cells_dispatch", - &BindGroupLayoutEntries::single( - ShaderStages::COMPUTE, - storage_buffer_sized(false, None), - ), - ); + let bind_group_resolve_dlss_rr_textures = view_dlss_rr_textures.map(|d| { + render_device.create_bind_group( + "solari_lighting_bind_group_resolve_dlss_rr_textures", + &pipeline_cache + .get_bind_group_layout(&pipelines.bind_group_layout_resolve_dlss_rr_textures), + &BindGroupEntries::sequential(( + &d.diffuse_albedo.default_view, + &d.specular_albedo.default_view, + &d.normal_roughness.default_view, + &d.specular_motion_vectors.default_view, + )), + ) + }); - #[cfg(all(feature = "dlss", not(feature = "force_disable_dlss")))] - let bind_group_layout_resolve_dlss_rr_textures = BindGroupLayoutDescriptor::new( - "solari_lighting_bind_group_layout_resolve_dlss_rr_textures", - &BindGroupLayoutEntries::sequential( - ShaderStages::COMPUTE, - ( - texture_storage_2d(TextureFormat::Rgba8Unorm, StorageTextureAccess::WriteOnly), - texture_storage_2d(TextureFormat::Rgba8Unorm, StorageTextureAccess::WriteOnly), - texture_storage_2d(TextureFormat::Rgba16Float, StorageTextureAccess::WriteOnly), - texture_storage_2d(TextureFormat::Rg16Float, StorageTextureAccess::WriteOnly), - ), - ), - ); - - let create_pipeline = |label: &'static str, - entry_point: &'static str, - shader: Handle, - extra_bind_group_layout: Option<&BindGroupLayoutDescriptor>, - extra_shader_defs: Vec| { - let mut layout = vec![ - scene_bindings.bind_group_layout.clone(), - bind_group_layout.clone(), - ]; - if let Some(extra_bind_group_layout) = extra_bind_group_layout { - layout.push(extra_bind_group_layout.clone()); - } - - let mut shader_defs = vec![ShaderDefVal::UInt( - "WORLD_CACHE_SIZE".into(), - WORLD_CACHE_SIZE as u32, - )]; - shader_defs.extend_from_slice(&extra_shader_defs); - - pipeline_cache.queue_compute_pipeline(ComputePipelineDescriptor { - label: Some(label.into()), - layout, - push_constant_ranges: vec![PushConstantRange { - stages: ShaderStages::COMPUTE, - range: 0..8, - }], - shader, - shader_defs, - entry_point: Some(entry_point.into()), - ..default() - }) - }; - - Self { - bind_group_layout: bind_group_layout.clone(), - bind_group_layout_world_cache_active_cells_dispatch: - bind_group_layout_world_cache_active_cells_dispatch.clone(), - #[cfg(all(feature = "dlss", not(feature = "force_disable_dlss")))] - bind_group_layout_resolve_dlss_rr_textures: bind_group_layout_resolve_dlss_rr_textures - .clone(), - decay_world_cache_pipeline: create_pipeline( - "solari_lighting_decay_world_cache_pipeline", - "decay_world_cache", - load_embedded_asset!(world, "world_cache_compact.wgsl"), - Some(&bind_group_layout_world_cache_active_cells_dispatch), - vec!["WORLD_CACHE_NON_ATOMIC_LIFE_BUFFER".into()], - ), - compact_world_cache_single_block_pipeline: create_pipeline( - "solari_lighting_compact_world_cache_single_block_pipeline", - "compact_world_cache_single_block", - load_embedded_asset!(world, "world_cache_compact.wgsl"), - Some(&bind_group_layout_world_cache_active_cells_dispatch), - vec!["WORLD_CACHE_NON_ATOMIC_LIFE_BUFFER".into()], - ), - compact_world_cache_blocks_pipeline: create_pipeline( - "solari_lighting_compact_world_cache_blocks_pipeline", - "compact_world_cache_blocks", - load_embedded_asset!(world, "world_cache_compact.wgsl"), - Some(&bind_group_layout_world_cache_active_cells_dispatch), - vec![], - ), - compact_world_cache_write_active_cells_pipeline: create_pipeline( - "solari_lighting_compact_world_cache_write_active_cells_pipeline", - "compact_world_cache_write_active_cells", - load_embedded_asset!(world, "world_cache_compact.wgsl"), - Some(&bind_group_layout_world_cache_active_cells_dispatch), - vec!["WORLD_CACHE_NON_ATOMIC_LIFE_BUFFER".into()], - ), - sample_for_world_cache_pipeline: create_pipeline( - "solari_lighting_sample_for_world_cache_pipeline", - "sample_radiance", - load_embedded_asset!(world, "world_cache_update.wgsl"), - None, - vec!["WORLD_CACHE_QUERY_ATOMIC_MAX_LIFETIME".into()], - ), - blend_new_world_cache_samples_pipeline: create_pipeline( - "solari_lighting_blend_new_world_cache_samples_pipeline", - "blend_new_samples", - load_embedded_asset!(world, "world_cache_update.wgsl"), - None, - vec![], - ), - presample_light_tiles_pipeline: create_pipeline( - "solari_lighting_presample_light_tiles_pipeline", - "presample_light_tiles", - load_embedded_asset!(world, "presample_light_tiles.wgsl"), - None, - vec![], - ), - di_initial_and_temporal_pipeline: create_pipeline( - "solari_lighting_di_initial_and_temporal_pipeline", - "initial_and_temporal", - load_embedded_asset!(world, "restir_di.wgsl"), - None, - vec![], - ), - di_spatial_and_shade_pipeline: create_pipeline( - "solari_lighting_di_spatial_and_shade_pipeline", - "spatial_and_shade", - load_embedded_asset!(world, "restir_di.wgsl"), - None, - vec![], - ), - gi_initial_and_temporal_pipeline: create_pipeline( - "solari_lighting_gi_initial_and_temporal_pipeline", - "initial_and_temporal", - load_embedded_asset!(world, "restir_gi.wgsl"), - None, - vec![], - ), - gi_spatial_and_shade_pipeline: create_pipeline( - "solari_lighting_gi_spatial_and_shade_pipeline", - "spatial_and_shade", - load_embedded_asset!(world, "restir_gi.wgsl"), - None, - vec![], - ), - specular_gi_pipeline: create_pipeline( - "solari_lighting_specular_gi_pipeline", - "specular_gi", - load_embedded_asset!(world, "specular_gi.wgsl"), - None, - vec![], - ), - #[cfg(all(feature = "dlss", not(feature = "force_disable_dlss")))] - resolve_dlss_rr_textures_pipeline: create_pipeline( - "solari_lighting_resolve_dlss_rr_textures_pipeline", - "resolve_dlss_rr_textures", - load_embedded_asset!(world, "resolve_dlss_rr_textures.wgsl"), - Some(&bind_group_layout_resolve_dlss_rr_textures), - vec![], - ), - } + // Choice of number here is arbitrary + let frame_index = frame_count.0.wrapping_mul(5782582); + + let command_encoder = ctx.command_encoder(); + + // Clear the view target if we're the first node to write to it + if matches!(view_target_attachment.ops.load, LoadOp::Clear(_)) { + command_encoder.begin_render_pass(&RenderPassDescriptor { + label: Some("solari_lighting_clear"), + color_attachments: &[Some(view_target_attachment)], + depth_stencil_attachment: None, + timestamp_writes: None, + occlusion_query_set: None, + }); + } + + let mut pass = command_encoder.begin_compute_pass(&ComputePassDescriptor { + label: Some("solari_lighting"), + timestamp_writes: None, + }); + + let dx = solari_lighting_resources.view_size.x.div_ceil(8); + let dy = solari_lighting_resources.view_size.y.div_ceil(8); + + pass.set_bind_group(0, scene_bind_group, &[]); + pass.set_bind_group( + 1, + &bind_group, + &[ + view_uniform_offset.offset, + previous_view_uniform_offset.offset, + ], + ); + + if let Some(bind_group_resolve_dlss_rr_textures) = bind_group_resolve_dlss_rr_textures { + pass.set_bind_group(2, &bind_group_resolve_dlss_rr_textures, &[]); + pass.set_pipeline(resolve_dlss_rr_textures_pipeline); + pass.dispatch_workgroups(dx, dy, 1); } + + pass.set_pipeline(presample_light_tiles_pipeline); + pass.set_push_constants( + 0, + bytemuck::cast_slice(&[frame_index, solari_lighting.reset as u32]), + ); + pass.dispatch_workgroups(LIGHT_TILE_BLOCKS as u32, 1, 1); + + pass.set_bind_group(2, &bind_group_world_cache_active_cells_dispatch, &[]); + + pass.set_pipeline(decay_world_cache_pipeline); + pass.dispatch_workgroups((WORLD_CACHE_SIZE / 1024) as u32, 1, 1); + + pass.set_pipeline(compact_world_cache_single_block_pipeline); + pass.dispatch_workgroups((WORLD_CACHE_SIZE / 1024) as u32, 1, 1); + + pass.set_pipeline(compact_world_cache_blocks_pipeline); + pass.dispatch_workgroups(1, 1, 1); + + pass.set_pipeline(compact_world_cache_write_active_cells_pipeline); + pass.dispatch_workgroups((WORLD_CACHE_SIZE / 1024) as u32, 1, 1); + + pass.set_bind_group(2, None, &[]); + + pass.set_pipeline(sample_for_world_cache_pipeline); + pass.set_push_constants( + 0, + bytemuck::cast_slice(&[frame_index, solari_lighting.reset as u32]), + ); + pass.dispatch_workgroups_indirect( + &solari_lighting_resources.world_cache_active_cells_dispatch, + 0, + ); + + pass.set_pipeline(blend_new_world_cache_samples_pipeline); + pass.dispatch_workgroups_indirect( + &solari_lighting_resources.world_cache_active_cells_dispatch, + 0, + ); + + pass.set_pipeline(di_initial_and_temporal_pipeline); + pass.set_push_constants( + 0, + bytemuck::cast_slice(&[frame_index, solari_lighting.reset as u32]), + ); + pass.dispatch_workgroups(dx, dy, 1); + + pass.set_pipeline(di_spatial_and_shade_pipeline); + pass.set_push_constants( + 0, + bytemuck::cast_slice(&[frame_index, solari_lighting.reset as u32]), + ); + pass.dispatch_workgroups(dx, dy, 1); + + pass.set_pipeline(gi_initial_and_temporal_pipeline); + pass.set_push_constants( + 0, + bytemuck::cast_slice(&[frame_index, solari_lighting.reset as u32]), + ); + pass.dispatch_workgroups(dx, dy, 1); + + pass.set_pipeline(gi_spatial_and_shade_pipeline); + pass.set_push_constants( + 0, + bytemuck::cast_slice(&[frame_index, solari_lighting.reset as u32]), + ); + pass.dispatch_workgroups(dx, dy, 1); + + pass.set_pipeline(specular_gi_pipeline); + pass.set_push_constants( + 0, + bytemuck::cast_slice(&[frame_index, solari_lighting.reset as u32]), + ); + pass.dispatch_workgroups(dx, dy, 1); } diff --git a/crates/bevy_sprite_render/src/mesh2d/wireframe2d.rs b/crates/bevy_sprite_render/src/mesh2d/wireframe2d.rs index 5f9a0a0e6e54d..9b1d05055d5ca 100644 --- a/crates/bevy_sprite_render/src/mesh2d/wireframe2d.rs +++ b/crates/bevy_sprite_render/src/mesh2d/wireframe2d.rs @@ -9,12 +9,11 @@ use bevy_asset::{ }; use bevy_camera::{visibility::ViewVisibility, Camera, Camera2d}; use bevy_color::{Color, ColorToComponents}; -use bevy_core_pipeline::core_2d::graph::{Core2d, Node2d}; +use bevy_core_pipeline::schedule::{Core2d, Core2dSystems}; use bevy_derive::{Deref, DerefMut}; use bevy_ecs::{ change_detection::Tick, prelude::*, - query::QueryItem, system::{lifetimeless::SRes, SystemChangeTick, SystemParamItem}, }; use bevy_mesh::{Mesh2d, MeshVertexBufferLayoutRef}; @@ -26,7 +25,6 @@ use bevy_reflect::{std_traits::ReflectDefault, Reflect}; use bevy_render::{ batching::gpu_preprocessing::GpuPreprocessingMode, camera::ExtractedCamera, - diagnostic::RecordDiagnostics, extract_resource::ExtractResource, mesh::{ allocator::{MeshAllocator, SlabId}, @@ -36,7 +34,6 @@ use bevy_render::{ render_asset::{ prepare_assets, PrepareAssetError, RenderAsset, RenderAssetPlugin, RenderAssets, }, - render_graph::{NodeRunError, RenderGraphContext, RenderGraphExt, ViewNode, ViewNodeRunner}, render_phase::{ AddRenderCommand, BinnedPhaseItem, BinnedRenderPhasePlugin, BinnedRenderPhaseType, CachedRenderPipelinePhaseItem, DrawFunctionId, DrawFunctions, InputUniformIndex, PhaseItem, @@ -44,7 +41,7 @@ use bevy_render::{ SetItemPipeline, TrackedRenderPass, ViewBinnedRenderPhases, }, render_resource::*, - renderer::RenderContext, + renderer::{RenderContext, ViewQuery}, sync_world::{MainEntity, MainEntityHashMap}, view::{ ExtractedView, RenderVisibleEntities, RetainedViewEntity, ViewDepthTexture, ViewTarget, @@ -118,14 +115,11 @@ impl Plugin for Wireframe2dPlugin { .add_render_command::() .init_resource::() .init_resource::>() - .add_render_graph_node::>(Core2d, Node2d::Wireframe) - .add_render_graph_edges( + .add_systems( Core2d, - ( - Node2d::EndMainPass, - Node2d::Wireframe, - Node2d::PostProcessing, - ), + wireframe_2d + .after(Core2dSystems::EndMainPass) + .before(Core2dSystems::StartMainPassPostProcessing), ) .add_systems( RenderStartup, @@ -355,56 +349,43 @@ impl SpecializedMeshPipeline for Wireframe2dPipeline { } } -#[derive(Default)] -struct Wireframe2dNode; -impl ViewNode for Wireframe2dNode { - type ViewQuery = ( - &'static ExtractedCamera, - &'static ExtractedView, - &'static ViewTarget, - &'static ViewDepthTexture, - ); - - fn run<'w>( - &self, - graph: &mut RenderGraphContext, - render_context: &mut RenderContext<'w>, - (camera, view, target, depth): QueryItem<'w, '_, Self::ViewQuery>, - world: &'w World, - ) -> Result<(), NodeRunError> { - let Some(wireframe_phase) = - world.get_resource::>() - else { - return Ok(()); - }; - - let Some(wireframe_phase) = wireframe_phase.get(&view.retained_view_entity) else { - return Ok(()); - }; +pub(crate) fn wireframe_2d( + world: &World, + view: ViewQuery<( + &ExtractedCamera, + &ExtractedView, + &ViewTarget, + &ViewDepthTexture, + )>, + wireframe_phases: Res>, + mut ctx: RenderContext, +) { + let view_entity = view.entity(); - let diagnostics = render_context.diagnostic_recorder(); + let (camera, extracted_view, target, depth) = view.into_inner(); - let mut render_pass = render_context.begin_tracked_render_pass(RenderPassDescriptor { - label: Some("wireframe_2d"), - color_attachments: &[Some(target.get_color_attachment())], - depth_stencil_attachment: Some(depth.get_attachment(StoreOp::Store)), - timestamp_writes: None, - occlusion_query_set: None, - }); - let pass_span = diagnostics.pass_span(&mut render_pass, "wireframe_2d"); + let Some(wireframe_phase) = wireframe_phases.get(&extracted_view.retained_view_entity) else { + return; + }; - if let Some(viewport) = camera.viewport.as_ref() { - render_pass.set_camera_viewport(viewport); - } + if wireframe_phase.is_empty() { + return; + } - if let Err(err) = wireframe_phase.render(&mut render_pass, world, graph.view_entity()) { - error!("Error encountered while rendering the stencil phase {err:?}"); - return Err(NodeRunError::DrawError(err)); - } + let mut render_pass = ctx.begin_tracked_render_pass(RenderPassDescriptor { + label: Some("wireframe_2d"), + color_attachments: &[Some(target.get_color_attachment())], + depth_stencil_attachment: Some(depth.get_attachment(StoreOp::Store)), + timestamp_writes: None, + occlusion_query_set: None, + }); - pass_span.end(&mut render_pass); + if let Some(viewport) = camera.viewport.as_ref() { + render_pass.set_camera_viewport(viewport); + } - Ok(()) + if let Err(err) = wireframe_phase.render(&mut render_pass, world, view_entity) { + error!("Error encountered while rendering the wireframe phase {err:?}"); } } diff --git a/crates/bevy_ui_render/src/lib.rs b/crates/bevy_ui_render/src/lib.rs index fb8b5c9b51af5..108ed7e5a9d62 100644 --- a/crates/bevy_ui_render/src/lib.rs +++ b/crates/bevy_ui_render/src/lib.rs @@ -34,21 +34,21 @@ use bevy_ui::{ use bevy_app::prelude::*; use bevy_asset::{AssetEvent, AssetId, Assets}; use bevy_color::{Alpha, ColorToComponents, LinearRgba}; -use bevy_core_pipeline::core_2d::graph::{Core2d, Node2d}; -use bevy_core_pipeline::core_3d::graph::{Core3d, Node3d}; +use bevy_core_pipeline::schedule::{Core2d, Core2dSystems, Core3d, Core3dSystems}; +use bevy_core_pipeline::upscaling::upscaling; use bevy_ecs::prelude::*; +use bevy_ecs::schedule::IntoScheduleConfigs; use bevy_ecs::system::SystemParam; use bevy_image::{prelude::*, TRANSPARENT_IMAGE_HANDLE}; use bevy_math::{Affine2, FloatOrd, Mat4, Rect, UVec4, Vec2}; use bevy_render::{ render_asset::RenderAssets, - render_graph::{Node as RenderGraphNode, NodeRunError, RenderGraph, RenderGraphContext}, render_phase::{ sort_phase_system, AddRenderCommand, DrawFunctions, PhaseItem, PhaseItemExtraIndex, ViewSortedRenderPhases, }, render_resource::*, - renderer::{RenderContext, RenderDevice, RenderQueue}, + renderer::{RenderDevice, RenderQueue}, sync_world::{MainEntity, RenderEntity, TemporaryRenderEntity}, texture::GpuImage, view::{ExtractedView, Hdr, RetainedViewEntity, ViewUniforms}, @@ -71,24 +71,11 @@ use box_shadow::BoxShadowPlugin; use bytemuck::{Pod, Zeroable}; use core::ops::Range; -use graph::{NodeUi, SubGraphUi}; pub use pipeline::*; pub use render_pass::*; pub use ui_material_pipeline::*; use ui_texture_slice_pipeline::UiTextureSlicerPlugin; -pub mod graph { - use bevy_render::render_graph::{RenderLabel, RenderSubGraph}; - - #[derive(Debug, Hash, PartialEq, Eq, Clone, RenderSubGraph)] - pub struct SubGraphUi; - - #[derive(Debug, Hash, PartialEq, Eq, Clone, RenderLabel)] - pub enum NodeUi { - UiPass, - } -} - pub mod prelude { #[cfg(feature = "bevy_ui_debug")] pub use crate::debug_overlay::UiDebugOptions; @@ -259,31 +246,20 @@ impl Plugin for UiRenderPlugin { sort_phase_system::.in_set(RenderSystems::PhaseSort), prepare_uinodes.in_set(RenderSystems::PrepareBindGroups), ), + ) + .add_systems( + Core2d, + ui_pass + .after(Core2dSystems::EndMainPassPostProcessing) + .before(upscaling), + ) + .add_systems( + Core3d, + ui_pass + .after(Core3dSystems::EndMainPassPostProcessing) + .before(upscaling), ); - // Render graph - render_app - .world_mut() - .resource_scope(|world, mut graph: Mut| { - if let Some(graph_2d) = graph.get_sub_graph_mut(Core2d) { - let ui_graph_2d = new_ui_graph(world); - graph_2d.add_sub_graph(SubGraphUi, ui_graph_2d); - graph_2d.add_node(NodeUi::UiPass, RunUiSubgraphOnUiViewNode); - graph_2d.add_node_edge(Node2d::EndMainPass, NodeUi::UiPass); - graph_2d.add_node_edge(Node2d::EndMainPassPostProcessing, NodeUi::UiPass); - graph_2d.add_node_edge(NodeUi::UiPass, Node2d::Upscaling); - } - - if let Some(graph_3d) = graph.get_sub_graph_mut(Core3d) { - let ui_graph_3d = new_ui_graph(world); - graph_3d.add_sub_graph(SubGraphUi, ui_graph_3d); - graph_3d.add_node(NodeUi::UiPass, RunUiSubgraphOnUiViewNode); - graph_3d.add_node_edge(Node3d::EndMainPass, NodeUi::UiPass); - graph_3d.add_node_edge(Node3d::EndMainPassPostProcessing, NodeUi::UiPass); - graph_3d.add_node_edge(NodeUi::UiPass, Node3d::Upscaling); - } - }); - app.add_plugins(UiTextureSlicerPlugin); app.add_plugins(ColorSpacePlugin); app.add_plugins(GradientPlugin); @@ -291,13 +267,6 @@ impl Plugin for UiRenderPlugin { } } -fn new_ui_graph(world: &mut World) -> RenderGraph { - let ui_pass_node = UiPassNode::new(world); - let mut ui_graph = RenderGraph::default(); - ui_graph.add_node(NodeUi::UiPass, ui_pass_node); - ui_graph -} - #[derive(SystemParam)] pub struct UiCameraMap<'w, 's> { mapping: Query<'w, 's, RenderEntity>, @@ -406,31 +375,6 @@ impl ExtractedUiNodes { } } -/// A [`RenderGraphNode`] that executes the UI rendering subgraph on the UI -/// view. -struct RunUiSubgraphOnUiViewNode; - -impl RenderGraphNode for RunUiSubgraphOnUiViewNode { - fn run<'w>( - &self, - graph: &mut RenderGraphContext, - _: &mut RenderContext<'w>, - world: &'w World, - ) -> Result<(), NodeRunError> { - // Fetch the UI view. - let Some(mut render_views) = world.try_query::<&UiCameraView>() else { - return Ok(()); - }; - let Ok(ui_camera_view) = render_views.get(world, graph.view_entity()) else { - return Ok(()); - }; - - // Run the subgraph on the UI view. - graph.run_sub_graph(SubGraphUi, vec![], Some(ui_camera_view.0), None)?; - Ok(()) - } -} - pub fn extract_uinode_background_colors( mut commands: Commands, mut extracted_uinodes: ResMut, diff --git a/crates/bevy_ui_render/src/render_pass.rs b/crates/bevy_ui_render/src/render_pass.rs index 642f69d9350e8..3b5a11dc92061 100644 --- a/crates/bevy_ui_render/src/render_pass.rs +++ b/crates/bevy_ui_render/src/render_pass.rs @@ -11,106 +11,63 @@ use bevy_math::FloatOrd; use bevy_render::{ camera::ExtractedCamera, diagnostic::RecordDiagnostics, - render_graph::*, render_phase::*, render_resource::{CachedRenderPipelineId, RenderPassDescriptor}, - renderer::*, + renderer::{RenderContext, ViewQuery}, sync_world::MainEntity, view::*, }; use tracing::error; -pub struct UiPassNode { - ui_view_query: QueryState<(&'static ExtractedView, &'static UiViewTarget)>, - ui_view_target_query: QueryState<(&'static ViewTarget, &'static ExtractedCamera)>, - ui_camera_view_query: QueryState<&'static UiCameraView>, -} +pub fn ui_pass( + world: &World, + view: ViewQuery<&UiCameraView>, + ui_view_query: Query<(&ExtractedView, &UiViewTarget)>, + ui_view_target_query: Query<(&ViewTarget, &ExtractedCamera)>, + transparent_render_phases: Res>, + mut ctx: RenderContext, +) { + let ui_camera_view = view.into_inner(); + let ui_view_entity = ui_camera_view.0; -impl UiPassNode { - pub fn new(world: &mut World) -> Self { - Self { - ui_view_query: world.query_filtered(), - ui_view_target_query: world.query(), - ui_camera_view_query: world.query(), - } - } -} + let Ok((extracted_view, ui_view_target)) = ui_view_query.get(ui_view_entity) else { + return; + }; -impl Node for UiPassNode { - fn update(&mut self, world: &mut World) { - self.ui_view_query.update_archetypes(world); - self.ui_view_target_query.update_archetypes(world); - self.ui_camera_view_query.update_archetypes(world); - } + let Ok((target, camera)) = ui_view_target_query.get(ui_view_target.0) else { + return; + }; - fn run( - &self, - graph: &mut RenderGraphContext, - render_context: &mut RenderContext, - world: &World, - ) -> Result<(), NodeRunError> { - // Extract the UI view. - let input_view_entity = graph.view_entity(); - - let Some(transparent_render_phases) = - world.get_resource::>() - else { - return Ok(()); - }; - - // Query the UI view components. - let Ok((view, ui_view_target)) = self.ui_view_query.get_manual(world, input_view_entity) - else { - return Ok(()); - }; + let Some(transparent_phase) = transparent_render_phases.get(&extracted_view.retained_view_entity) + else { + return; + }; - let Ok((target, camera)) = self - .ui_view_target_query - .get_manual(world, ui_view_target.0) - else { - return Ok(()); - }; + if transparent_phase.items.is_empty() { + return; + } - let Some(transparent_phase) = transparent_render_phases.get(&view.retained_view_entity) - else { - return Ok(()); - }; + let diagnostics = ctx.diagnostic_recorder(); + let diagnostics = diagnostics.as_deref(); - if transparent_phase.items.is_empty() { - return Ok(()); - } + let mut render_pass = ctx.begin_tracked_render_pass(RenderPassDescriptor { + label: Some("ui"), + color_attachments: &[Some(target.get_unsampled_color_attachment())], + depth_stencil_attachment: None, + timestamp_writes: None, + occlusion_query_set: None, + }); + let pass_span = diagnostics.pass_span(&mut render_pass, "ui"); - let diagnostics = render_context.diagnostic_recorder(); + if let Some(viewport) = camera.viewport.as_ref() { + render_pass.set_camera_viewport(viewport); + } - // use the UI view entity if it is defined - let view_entity = if let Ok(ui_camera_view) = self - .ui_camera_view_query - .get_manual(world, input_view_entity) - { - ui_camera_view.0 - } else { - input_view_entity - }; - let mut render_pass = render_context.begin_tracked_render_pass(RenderPassDescriptor { - label: Some("ui"), - color_attachments: &[Some(target.get_unsampled_color_attachment())], - depth_stencil_attachment: None, - timestamp_writes: None, - occlusion_query_set: None, - }); - let pass_span = diagnostics.pass_span(&mut render_pass, "ui"); - - if let Some(viewport) = camera.viewport.as_ref() { - render_pass.set_camera_viewport(viewport); - } - if let Err(err) = transparent_phase.render(&mut render_pass, world, view_entity) { - error!("Error encountered while rendering the ui phase {err:?}"); - } - - pass_span.end(&mut render_pass); - - Ok(()) + if let Err(err) = transparent_phase.render(&mut render_pass, world, ui_view_entity) { + error!("Error encountered while rendering the ui phase {err:?}"); } + + pass_span.end(&mut render_pass); } pub struct TransparentUi { diff --git a/examples/shader_advanced/custom_post_processing.rs b/examples/shader_advanced/custom_post_processing.rs index f92b0a347c72a..1372d21f261de 100644 --- a/examples/shader_advanced/custom_post_processing.rs +++ b/examples/shader_advanced/custom_post_processing.rs @@ -1,31 +1,24 @@ -//! This example shows how to create a custom render pass that runs after the main pass +//! This example shows how to create a custom post-processing effect that runs after the main pass //! and reads the texture generated by the main pass. //! //! The example shader is a very simple implementation of chromatic aberration. -//! To adapt this example for 2D, replace all instances of 3D structures (such as `Core3D`, etc.) with their corresponding 2D counterparts. +//! To adapt this example for 2D, replace all instances of 3D structures (such as `Core3d`, etc.) with their corresponding 2D counterparts. //! //! This is a fairly low level example and assumes some familiarity with rendering concepts and wgpu. use bevy::{ - core_pipeline::{ - core_3d::graph::{Core3d, Node3d}, - FullscreenShader, - }, - ecs::query::QueryItem, + core_pipeline::{schedule::Core3d, Core3dSystems, FullscreenShader}, prelude::*, render::{ extract_component::{ ComponentUniforms, DynamicUniformIndex, ExtractComponent, ExtractComponentPlugin, UniformComponentPlugin, }, - render_graph::{ - NodeRunError, RenderGraphContext, RenderGraphExt, RenderLabel, ViewNode, ViewNodeRunner, - }, render_resource::{ binding_types::{sampler, texture_2d, uniform_buffer}, *, }, - renderer::{RenderContext, RenderDevice}, + renderer::{RenderContext, RenderDevice, ViewQuery}, view::ViewTarget, RenderApp, RenderStartup, }, @@ -52,7 +45,7 @@ impl Plugin for PostProcessPlugin { // be extracted to the render world every frame. // This makes it possible to control the effect from the main world. // This plugin will take care of extracting it automatically. - // It's important to derive [`ExtractComponent`] on [`PostProcessingSettings`] + // It's important to derive [`ExtractComponent`] on [`PostProcessSettings`] // for this plugin to work correctly. ExtractComponentPlugin::::default(), // The settings will also be the data used in the shader. @@ -66,157 +59,107 @@ impl Plugin for PostProcessPlugin { return; }; - // RenderStartup runs once on startup after all plugins are built - // It is useful to initialize data that will only live in the RenderApp render_app.add_systems(RenderStartup, init_post_process_pipeline); - - render_app - // Bevy's renderer uses a render graph which is a collection of nodes in a directed acyclic graph. - // It currently runs on each view/camera and executes each node in the specified order. - // It will make sure that any node that needs a dependency from another node - // only runs when that dependency is done. - // - // Each node can execute arbitrary work, but it generally runs at least one render pass. - // A node only has access to the render world, so if you need data from the main world - // you need to extract it manually or with the plugin like above. - // Add a [`Node`] to the [`RenderGraph`] - // The Node needs to impl FromWorld - // - // The [`ViewNodeRunner`] is a special [`Node`] that will automatically run the node for each view - // matching the [`ViewQuery`] - .add_render_graph_node::>( - // Specify the label of the graph, in this case we want the graph for 3d - Core3d, - // It also needs the label of the node - PostProcessLabel, - ) - .add_render_graph_edges( - Core3d, - // Specify the node ordering. - // This will automatically create all required node edges to enforce the given ordering. - ( - Node3d::Tonemapping, - PostProcessLabel, - Node3d::EndMainPassPostProcessing, - ), - ); + render_app.add_systems( + Core3d, + post_process_system + .after(Core3dSystems::PostProcessing) + .before(Core3dSystems::EndMainPassPostProcessing), + ); } } -#[derive(Debug, Hash, PartialEq, Eq, Clone, RenderLabel)] -struct PostProcessLabel; - -// The post process node used for the render graph #[derive(Default)] -struct PostProcessNode; - -// The ViewNode trait is required by the ViewNodeRunner -impl ViewNode for PostProcessNode { - // The node needs a query to gather data from the ECS in order to do its rendering, - // but it's not a normal system so we need to define it manually. - // - // This query will only run on the view entity - type ViewQuery = ( - &'static ViewTarget, - // This makes sure the node only runs on cameras with the PostProcessSettings component - &'static PostProcessSettings, - // As there could be multiple post processing components sent to the GPU (one per camera), - // we need to get the index of the one that is associated with the current view. - &'static DynamicUniformIndex, - ); +struct PostProcessBindGroupCache { + cached: Option<(TextureViewId, BindGroup)>, +} - // Runs the node logic - // This is where you encode draw commands. - // - // This will run on every view on which the graph is running. - // If you don't want your effect to run on every camera, - // you'll need to make sure you have a marker component as part of [`ViewQuery`] - // to identify which camera(s) should run the effect. - fn run( - &self, - _graph: &mut RenderGraphContext, - render_context: &mut RenderContext, - (view_target, _post_process_settings, settings_index): QueryItem, - world: &World, - ) -> Result<(), NodeRunError> { - // Get the pipeline resource that contains the global data we need - // to create the render pipeline - let post_process_pipeline = world.resource::(); +fn post_process_system( + view: ViewQuery<( + &ViewTarget, + &PostProcessSettings, + &DynamicUniformIndex, + )>, + post_process_pipeline: Option>, + pipeline_cache: Res, + settings_uniforms: Res>, + mut cache: Local, + mut ctx: RenderContext, +) { + let Some(post_process_pipeline) = post_process_pipeline else { + return; + }; - // The pipeline cache is a cache of all previously created pipelines. - // It is required to avoid creating a new pipeline each frame, - // which is expensive due to shader compilation. - let pipeline_cache = world.resource::(); + let (view_target, _post_process_settings, settings_index) = view.into_inner(); - // Get the pipeline from the cache - let Some(pipeline) = pipeline_cache.get_render_pipeline(post_process_pipeline.pipeline_id) - else { - return Ok(()); - }; + let Some(pipeline) = pipeline_cache.get_render_pipeline(post_process_pipeline.pipeline_id) + else { + return; + }; - // Get the settings uniform binding - let settings_uniforms = world.resource::>(); - let Some(settings_binding) = settings_uniforms.uniforms().binding() else { - return Ok(()); - }; + let Some(settings_binding) = settings_uniforms.uniforms().binding() else { + return; + }; - // This will start a new "post process write", obtaining two texture - // views from the view target - a `source` and a `destination`. - // `source` is the "current" main texture and you _must_ write into - // `destination` because calling `post_process_write()` on the - // [`ViewTarget`] will internally flip the [`ViewTarget`]'s main - // texture to the `destination` texture. Failing to do so will cause - // the current main texture information to be lost. - let post_process = view_target.post_process_write(); + // This will start a new "post process write", obtaining two texture + // views from the view target - a `source` and a `destination`. + // `source` is the "current" main texture and you _must_ write into + // `destination` because calling `post_process_write()` on the + // [`ViewTarget`] will internally flip the [`ViewTarget`]'s main + // texture to the `destination` texture. Failing to do so will cause + // the current main texture information to be lost. + let post_process = view_target.post_process_write(); - // The bind_group gets created each frame. - // - // Normally, you would create a bind_group in the Queue set, - // but this doesn't work with the post_process_write(). - // The reason it doesn't work is because each post_process_write will alternate the source/destination. - // The only way to have the correct source/destination for the bind_group - // is to make sure you get it during the node execution. - let bind_group = render_context.render_device().create_bind_group( - "post_process_bind_group", - &pipeline_cache.get_bind_group_layout(&post_process_pipeline.layout), - // It's important for this to match the BindGroupLayout defined in the PostProcessPipeline - &BindGroupEntries::sequential(( - // Make sure to use the source view - post_process.source, - // Use the sampler created for the pipeline - &post_process_pipeline.sampler, - // Set the settings binding - settings_binding.clone(), - )), - ); + let bind_group = match &mut cache.cached { + Some((texture_id, bind_group)) if post_process.source.id() == *texture_id => bind_group, + cached => { + // The bind_group gets created each frame. + // + // Normally, you would create a bind_group in the Queue set, + // but this doesn't work with the post_process_write(). + // The reason it doesn't work is because each post_process_write will alternate the source/destination. + // The only way to have the correct source/destination for the bind_group + // is to make sure you get it during the node execution. + let bind_group = ctx.render_device().create_bind_group( + "post_process_bind_group", + &pipeline_cache.get_bind_group_layout(&post_process_pipeline.layout), + // It's important for this to match the BindGroupLayout defined in the PostProcessPipeline + &BindGroupEntries::sequential(( + // Make sure to use the source view + post_process.source, + // Use the sampler created for the pipeline + &post_process_pipeline.sampler, + // Set the settings binding + settings_binding.clone(), + )), + ); - // Begin the render pass - let mut render_pass = render_context.begin_tracked_render_pass(RenderPassDescriptor { - label: Some("post_process_pass"), - color_attachments: &[Some(RenderPassColorAttachment { - // We need to specify the post process destination view here - // to make sure we write to the appropriate texture. - view: post_process.destination, - depth_slice: None, - resolve_target: None, - ops: Operations::default(), - })], - depth_stencil_attachment: None, - timestamp_writes: None, - occlusion_query_set: None, - }); + let (_, bind_group) = cached.insert((post_process.source.id(), bind_group)); + bind_group + } + }; - // This is mostly just wgpu boilerplate for drawing a fullscreen triangle, - // using the pipeline/bind_group created above - render_pass.set_render_pipeline(pipeline); - // By passing in the index of the post process settings on this view, we ensure - // that in the event that multiple settings were sent to the GPU (as would be the - // case with multiple cameras), we use the correct one. - render_pass.set_bind_group(0, &bind_group, &[settings_index.index()]); - render_pass.draw(0..3, 0..1); + let mut render_pass = ctx.command_encoder().begin_render_pass(&RenderPassDescriptor { + label: Some("post_process_pass"), + color_attachments: &[Some(RenderPassColorAttachment { + // We need to specify the post process destination view here + // to make sure we write to the appropriate texture. + view: post_process.destination, + depth_slice: None, + resolve_target: None, + ops: Operations::default(), + })], + depth_stencil_attachment: None, + timestamp_writes: None, + occlusion_query_set: None, + }); - Ok(()) - } + render_pass.set_pipeline(pipeline); + // By passing in the index of the post process settings on this view, we ensure + // that in the event that multiple settings were sent to the GPU (as would be the + // case with multiple cameras), we use the correct one. + render_pass.set_bind_group(0, bind_group, &[settings_index.index()]); + render_pass.draw(0..3, 0..1); } // This contains global data used by the render pipeline. This will be created once on startup. diff --git a/examples/shader_advanced/custom_render_phase.rs b/examples/shader_advanced/custom_render_phase.rs index 8fd1cdc837539..ae001ae7c9bc3 100644 --- a/examples/shader_advanced/custom_render_phase.rs +++ b/examples/shader_advanced/custom_render_phase.rs @@ -16,11 +16,8 @@ use bevy::camera::Viewport; use bevy::pbr::SetMeshViewEmptyBindGroup; use bevy::{ camera::MainPassResolutionOverride, - core_pipeline::core_3d::graph::{Core3d, Node3d}, - ecs::{ - query::QueryItem, - system::{lifetimeless::SRes, SystemParamItem}, - }, + core_pipeline::{schedule::Core3d, Core3dSystems}, + ecs::system::{lifetimeless::SRes, SystemParamItem}, math::FloatOrd, mesh::MeshVertexBufferLayoutRef, pbr::{ @@ -41,9 +38,6 @@ use bevy::{ extract_component::{ExtractComponent, ExtractComponentPlugin}, mesh::{allocator::MeshAllocator, RenderMesh}, render_asset::RenderAssets, - render_graph::{ - NodeRunError, RenderGraphContext, RenderGraphExt, RenderLabel, ViewNode, ViewNodeRunner, - }, render_phase::{ sort_phase_system, AddRenderCommand, CachedRenderPipelinePhaseItem, DrawFunctionId, DrawFunctions, PhaseItem, PhaseItemExtraIndex, SetItemPipeline, SortedPhaseItem, @@ -55,7 +49,7 @@ use bevy::{ SpecializedMeshPipeline, SpecializedMeshPipelineError, SpecializedMeshPipelines, TextureFormat, VertexState, }, - renderer::RenderContext, + renderer::{RenderContext, ViewQuery}, sync_world::MainEntity, view::{ExtractedView, RenderVisibleEntities, RetainedViewEntity, ViewTarget}, Extract, Render, RenderApp, RenderDebugFlags, RenderStartup, RenderSystems, @@ -140,12 +134,13 @@ impl Plugin for MeshStencilPhasePlugin { batch_and_prepare_sorted_render_phase:: .in_set(RenderSystems::PrepareResources), ), + ) + .add_systems( + Core3d, + custom_draw_system + .after(Core3dSystems::StartMainPass) + .before(Core3dSystems::EndMainPass), ); - - render_app - .add_render_graph_node::>(Core3d, CustomDrawPassLabel) - // Tell the node to run after the main pass - .add_render_graph_edges(Core3d, (Node3d::MainOpaquePass, CustomDrawPassLabel)); } } @@ -567,65 +562,43 @@ fn queue_custom_meshes( } } -// Render label used to order our render graph node that will render our phase -#[derive(RenderLabel, Debug, Clone, Hash, PartialEq, Eq)] -struct CustomDrawPassLabel; - -#[derive(Default)] -struct CustomDrawNode; -impl ViewNode for CustomDrawNode { - type ViewQuery = ( - &'static ExtractedCamera, - &'static ExtractedView, - &'static ViewTarget, - Option<&'static MainPassResolutionOverride>, - ); - - fn run<'w>( - &self, - graph: &mut RenderGraphContext, - render_context: &mut RenderContext<'w>, - (camera, view, target, resolution_override): QueryItem<'w, '_, Self::ViewQuery>, - world: &'w World, - ) -> Result<(), NodeRunError> { - // First, we need to get our phases resource - let Some(stencil_phases) = world.get_resource::>() else { - return Ok(()); - }; - - // Get the view entity from the graph - let view_entity = graph.view_entity(); - - // Get the phase for the current view running our node - let Some(stencil_phase) = stencil_phases.get(&view.retained_view_entity) else { - return Ok(()); - }; - - // Render pass setup - let mut render_pass = render_context.begin_tracked_render_pass(RenderPassDescriptor { - label: Some("stencil pass"), - // For the purpose of the example, we will write directly to the view target. A real - // stencil pass would write to a custom texture and that texture would be used in later - // passes to render custom effects using it. - color_attachments: &[Some(target.get_color_attachment())], - // We don't bind any depth buffer for this pass - depth_stencil_attachment: None, - timestamp_writes: None, - occlusion_query_set: None, - }); - - if let Some(viewport) = - Viewport::from_viewport_and_override(camera.viewport.as_ref(), resolution_override) - { - render_pass.set_camera_viewport(&viewport); - } +fn custom_draw_system( + world: &World, + view: ViewQuery<( + &ExtractedCamera, + &ExtractedView, + &ViewTarget, + Option<&MainPassResolutionOverride>, + )>, + stencil_phases: Res>, + mut ctx: RenderContext, +) { + let view_entity = view.entity(); + let (camera, extracted_view, target, resolution_override) = view.into_inner(); + + let Some(stencil_phase) = stencil_phases.get(&extracted_view.retained_view_entity) else { + return; + }; + + let mut render_pass = ctx.begin_tracked_render_pass(RenderPassDescriptor { + label: Some("stencil pass"), + // For the purpose of the example, we will write directly to the view target. A real + // stencil pass would write to a custom texture and that texture would be used in later + // passes to render custom effects using it. + color_attachments: &[Some(target.get_color_attachment())], + // We don't bind any depth buffer for this pass + depth_stencil_attachment: None, + timestamp_writes: None, + occlusion_query_set: None, + }); - // Render the phase - // This will execute each draw functions of each phase items queued in this phase - if let Err(err) = stencil_phase.render(&mut render_pass, world, view_entity) { - error!("Error encountered while rendering the stencil phase {err:?}"); - } + if let Some(viewport) = + Viewport::from_viewport_and_override(camera.viewport.as_ref(), resolution_override) + { + render_pass.set_camera_viewport(&viewport); + } - Ok(()) + if let Err(err) = stencil_phase.render(&mut render_pass, world, view_entity) { + error!("Error encountered while rendering the stencil phase {err:?}"); } } diff --git a/examples/shader_advanced/fullscreen_material.rs b/examples/shader_advanced/fullscreen_material.rs index 4cd5ed6da50e7..c9d95b209ca49 100644 --- a/examples/shader_advanced/fullscreen_material.rs +++ b/examples/shader_advanced/fullscreen_material.rs @@ -1,18 +1,9 @@ -//! Demonstrates how to write a custom fullscreen shader -//! -//! This is currently limited to 3d only but work is in progress to make it work in 2d +//! Demonstrates how to write a custom fullscreen shader. use bevy::{ - core_pipeline::{ - core_3d::graph::Node3d, - fullscreen_material::{FullscreenMaterial, FullscreenMaterialPlugin}, - }, + core_pipeline::fullscreen_material::{FullscreenMaterial, FullscreenMaterialPlugin}, prelude::*, - render::{ - extract_component::ExtractComponent, - render_graph::{InternedRenderLabel, RenderLabel}, - render_resource::ShaderType, - }, + render::{extract_component::ExtractComponent, render_resource::ShaderType}, shader::ShaderRef, }; @@ -31,59 +22,34 @@ fn setup( mut meshes: ResMut>, mut materials: ResMut>, ) { - // camera commands.spawn(( Camera3d::default(), Transform::from_translation(Vec3::new(0.0, 0.0, 5.0)).looking_at(Vec3::default(), Vec3::Y), FullscreenEffect { intensity: 0.005 }, )); - // cube commands.spawn(( Mesh3d(meshes.add(Cuboid::default())), MeshMaterial3d(materials.add(Color::srgb(0.8, 0.7, 0.6))), Transform::default(), )); - // light commands.spawn(DirectionalLight { illuminance: 1_000., ..default() }); } -// This is the struct that will be sent to your shader -// -// Currently, this doesn't support AsBindGroup so you can only use it to send a struct to your -// shader. We are working on adding AsBindGroup support in the future so you can bind anything you -// need. #[derive(Component, ExtractComponent, Clone, Copy, ShaderType, Default)] struct FullscreenEffect { - // For this example, this is used as the intensity of the effect, but you can pass in any valid - // ShaderType - // - // In the future, you will be able to use a full bind group intensity: f32, } impl FullscreenMaterial for FullscreenEffect { - // The shader that will be used fn fragment_shader() -> ShaderRef { "shaders/fullscreen_effect.wgsl".into() } - // This let's you specify a list of edges used to order when your effect pass will run - // - // This example is a post processing effect so it will run after tonemapping but before the end - // post processing pass. - // - // In 2d you would need to use [`Node2d`] instead of [`Node3d`] - fn node_edges() -> Vec { - vec![ - Node3d::Tonemapping.intern(), - // The label is automatically generated from the name of the struct - Self::node_label().intern(), - Node3d::EndMainPassPostProcessing.intern(), - ] - } + // Uses default run_after (Core3dSystems::PostProcessing) and + // run_before (Core3dSystems::EndMainPassPostProcessing) } diff --git a/examples/shader_advanced/render_depth_to_texture.rs b/examples/shader_advanced/render_depth_to_texture.rs index 6cb1d82aa4d8f..a9b66cb51b0f2 100644 --- a/examples/shader_advanced/render_depth_to_texture.rs +++ b/examples/shader_advanced/render_depth_to_texture.rs @@ -6,7 +6,7 @@ //! //! To create a depth-only camera, we create a [`Camera3d`] and set its //! [`RenderTarget`] to [`RenderTarget::None`] to disable creation of a color -//! buffer. Then we add a new node to the render graph that copies the +//! buffer. Then we add a system to the Core3d schedule that copies the //! [`bevy::render::view::ViewDepthTexture`] that Bevy creates for that camera //! to a texture. This texture can then be attached to a material and sampled in //! the shader. @@ -21,11 +21,7 @@ use bevy::{ asset::RenderAssetUsages, camera::RenderTarget, color::palettes::css::LIME, - core_pipeline::{ - core_3d::graph::{Core3d, Node3d}, - prepass::DepthPrepass, - }, - ecs::{query::QueryItem, system::lifetimeless::Read}, + core_pipeline::{prepass::DepthPrepass, schedule::Core3d, Core3dSystems}, image::{ImageCompareFunction, ImageSampler, ImageSamplerDescriptor}, math::ops::{acos, atan2, sin_cos}, prelude::*, @@ -33,15 +29,11 @@ use bevy::{ camera::ExtractedCamera, extract_resource::{ExtractResource, ExtractResourcePlugin}, render_asset::RenderAssets, - render_graph::{ - NodeRunError, RenderGraphContext, RenderGraphExt as _, RenderLabel, ViewNode, - ViewNodeRunner, - }, render_resource::{ - AsBindGroup, CommandEncoderDescriptor, Extent3d, Origin3d, TexelCopyTextureInfo, - TextureAspect, TextureDimension, TextureFormat, + AsBindGroup, Extent3d, Origin3d, TexelCopyTextureInfo, TextureAspect, TextureDimension, + TextureFormat, }, - renderer::RenderContext, + renderer::{RenderContext, ViewQuery}, texture::GpuImage, view::ViewDepthTexture, RenderApp, @@ -64,16 +56,6 @@ struct ShowDepthTextureMaterial { depth_texture: Option>, } -/// A label for the render node that copies the depth buffer from that of the -/// camera to the [`DemoDepthTexture`]. -#[derive(Clone, PartialEq, Eq, Hash, Debug, RenderLabel)] -struct CopyDepthTexturePass; - -/// The render node that copies the depth buffer from that of the camera to the -/// [`DemoDepthTexture`]. -#[derive(Default)] -struct CopyDepthTextureNode; - /// Holds a copy of the depth buffer that the depth-only camera produces. /// /// We need to make a copy for two reasons: @@ -128,29 +110,68 @@ fn main() { .add_systems(Update, draw_camera_gizmo) .add_systems(Update, move_camera); - // Add the `CopyDepthTextureNode` to the render app. let render_app = app .get_sub_app_mut(RenderApp) .expect("Render app should be present"); - render_app.add_render_graph_node::>( - Core3d, - CopyDepthTexturePass, - ); - // We have the texture copy operation run in between the prepasses and - // the opaque pass. Since the depth rendering is part of the prepass, this - // is a reasonable time to perform the operation. - render_app.add_render_graph_edges( + + render_app.add_systems( Core3d, - ( - Node3d::EndPrepasses, - CopyDepthTexturePass, - Node3d::MainOpaquePass, - ), + copy_depth_texture_system + .after(Core3dSystems::EndPrepasses) + .before(Core3dSystems::StartMainPass), ); app.run(); } +fn copy_depth_texture_system( + view: ViewQuery<(&ExtractedCamera, &ViewDepthTexture)>, + demo_depth_texture: Option>, + image_assets: Res>, + mut ctx: RenderContext, +) { + let Some(demo_depth_texture) = demo_depth_texture else { + return; + }; + + let (camera, depth_texture) = view.into_inner(); + + // Make sure we only run on the depth-only camera. + // We could make a marker component for that camera and extract it to + // the render world, but using `order` as a tag to tell the main camera + // and the depth-only camera apart works in a pinch. + if camera.order >= 0 { + return; + } + + let Some(demo_depth_image) = image_assets.get(demo_depth_texture.0.id()) else { + return; + }; + + let command_encoder = ctx.command_encoder(); + command_encoder.push_debug_group("copy depth to demo texture"); + command_encoder.copy_texture_to_texture( + TexelCopyTextureInfo { + texture: &depth_texture.texture, + mip_level: 0, + origin: Origin3d::default(), + aspect: TextureAspect::DepthOnly, + }, + TexelCopyTextureInfo { + texture: &demo_depth_image.texture, + mip_level: 0, + origin: Origin3d::default(), + aspect: TextureAspect::DepthOnly, + }, + Extent3d { + width: DEPTH_TEXTURE_SIZE, + height: DEPTH_TEXTURE_SIZE, + depth_or_array_layers: 1, + }, + ); + command_encoder.pop_debug_group(); +} + /// Creates the scene. fn setup( mut commands: Commands, @@ -282,69 +303,6 @@ impl Material for ShowDepthTextureMaterial { } } -impl ViewNode for CopyDepthTextureNode { - type ViewQuery = (Read, Read); - - fn run<'w>( - &self, - _: &mut RenderGraphContext, - render_context: &mut RenderContext<'w>, - (camera, depth_texture): QueryItem<'w, '_, Self::ViewQuery>, - world: &'w World, - ) -> Result<(), NodeRunError> { - // Make sure we only run on the depth-only camera. - // We could make a marker component for that camera and extract it to - // the render world, but using `order` as a tag to tell the main camera - // and the depth-only camera apart works in a pinch. - if camera.order >= 0 { - return Ok(()); - } - - // Grab the texture we're going to copy to. - let demo_depth_texture = world.resource::(); - let image_assets = world.resource::>(); - let Some(demo_depth_image) = image_assets.get(demo_depth_texture.0.id()) else { - return Ok(()); - }; - - // Perform the copy. - render_context.add_command_buffer_generation_task(move |render_device| { - let mut command_encoder = - render_device.create_command_encoder(&CommandEncoderDescriptor { - label: Some("copy depth to demo texture command encoder"), - }); - command_encoder.push_debug_group("copy depth to demo texture"); - - // Copy from the view's depth texture to the destination depth - // texture. - command_encoder.copy_texture_to_texture( - TexelCopyTextureInfo { - texture: &depth_texture.texture, - mip_level: 0, - origin: Origin3d::default(), - aspect: TextureAspect::DepthOnly, - }, - TexelCopyTextureInfo { - texture: &demo_depth_image.texture, - mip_level: 0, - origin: Origin3d::default(), - aspect: TextureAspect::DepthOnly, - }, - Extent3d { - width: DEPTH_TEXTURE_SIZE, - height: DEPTH_TEXTURE_SIZE, - depth_or_array_layers: 1, - }, - ); - - command_encoder.pop_debug_group(); - command_encoder.finish() - }); - - Ok(()) - } -} - impl FromWorld for DemoDepthTexture { fn from_world(world: &mut World) -> Self { let mut images = world.resource_mut::>(); @@ -379,7 +337,7 @@ impl ExtractResource for DemoDepthTexture { fn extract_resource(source: &Self::Source) -> Self { // Share the `DemoDepthTexture` resource over to the render world so - // that our `CopyDepthTextureNode` can access it. + // that our system can access it. (*source).clone() } } From 2e0c5a61dcc75c7d78d881c3090e2448f3fc3ff2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?charlotte=20=F0=9F=8C=B8?= Date: Mon, 15 Dec 2025 15:15:04 -0800 Subject: [PATCH 02/55] Fmt. --- .../src/contrast_adaptive_sharpening/mod.rs | 2 +- crates/bevy_anti_alias/src/smaa/mod.rs | 10 +- .../src/core_2d/main_opaque_pass_2d_node.rs | 1 - .../core_3d/main_transmissive_pass_3d_node.rs | 3 +- crates/bevy_core_pipeline/src/core_3d/mod.rs | 91 ++++++++------- .../bevy_core_pipeline/src/deferred/node.rs | 11 +- .../src/fullscreen_material.rs | 11 +- crates/bevy_core_pipeline/src/lib.rs | 4 +- crates/bevy_core_pipeline/src/schedule.rs | 12 +- .../src/tonemapping/node.rs | 5 +- .../bevy_core_pipeline/src/upscaling/node.rs | 5 +- crates/bevy_pbr/src/deferred/mod.rs | 8 +- crates/bevy_pbr/src/light_probe/generate.rs | 3 +- .../src/meshlet/material_shade_nodes.rs | 5 +- crates/bevy_pbr/src/meshlet/mod.rs | 2 +- .../meshlet/visibility_buffer_raster_node.rs | 106 ++++++++++-------- crates/bevy_pbr/src/render/gpu_preprocess.rs | 10 +- crates/bevy_pbr/src/render/light.rs | 8 +- crates/bevy_pbr/src/ssao/mod.rs | 9 +- crates/bevy_pbr/src/ssr/mod.rs | 1 - crates/bevy_pbr/src/volumetric_fog/mod.rs | 3 +- crates/bevy_pbr/src/volumetric_fog/render.rs | 1 - crates/bevy_post_process/src/bloom/mod.rs | 18 ++- crates/bevy_post_process/src/dof/mod.rs | 19 +--- .../bevy_post_process/src/effect_stack/mod.rs | 16 +-- .../bevy_post_process/src/motion_blur/mod.rs | 2 +- .../bevy_post_process/src/motion_blur/node.rs | 3 +- .../bevy_post_process/src/msaa_writeback.rs | 17 +-- crates/bevy_render/src/diagnostic/mod.rs | 3 +- crates/bevy_render/src/renderer/mod.rs | 8 +- .../src/renderer/render_context.rs | 7 +- crates/bevy_solari/src/pathtracer/mod.rs | 5 +- crates/bevy_solari/src/realtime/node.rs | 21 ++-- crates/bevy_ui_render/src/render_pass.rs | 3 +- .../shader_advanced/custom_post_processing.rs | 30 ++--- 35 files changed, 222 insertions(+), 241 deletions(-) diff --git a/crates/bevy_anti_alias/src/contrast_adaptive_sharpening/mod.rs b/crates/bevy_anti_alias/src/contrast_adaptive_sharpening/mod.rs index f3519fba12752..da6f74006433b 100644 --- a/crates/bevy_anti_alias/src/contrast_adaptive_sharpening/mod.rs +++ b/crates/bevy_anti_alias/src/contrast_adaptive_sharpening/mod.rs @@ -1,3 +1,4 @@ +use crate::fxaa::fxaa; use bevy_app::prelude::*; use bevy_asset::{embedded_asset, load_embedded_asset, AssetServer, Handle}; use bevy_camera::Camera; @@ -5,7 +6,6 @@ use bevy_core_pipeline::{ schedule::{Core2d, Core2dSystems, Core3d, Core3dSystems}, FullscreenShader, }; -use crate::fxaa::fxaa; use bevy_ecs::{prelude::*, query::QueryItem}; use bevy_image::BevyDefault as _; use bevy_reflect::{std_traits::ReflectDefault, Reflect}; diff --git a/crates/bevy_anti_alias/src/smaa/mod.rs b/crates/bevy_anti_alias/src/smaa/mod.rs index 3b0275a378e69..15da16ff79198 100644 --- a/crates/bevy_anti_alias/src/smaa/mod.rs +++ b/crates/bevy_anti_alias/src/smaa/mod.rs @@ -189,7 +189,6 @@ pub struct ViewSmaaPipelines { neighborhood_blending_pipeline_id: CachedRenderPipelineId, } - /// Values supplied to the GPU for SMAA. /// /// Currently, this just contains the render target metrics and values derived @@ -815,8 +814,13 @@ pub(crate) fn smaa( pipeline_cache: Res, mut ctx: RenderContext, ) { - let (view_target, view_pipelines, view_smaa_uniform_offset, smaa_textures, view_smaa_bind_groups) = - view.into_inner(); + let ( + view_target, + view_pipelines, + view_smaa_uniform_offset, + smaa_textures, + view_smaa_bind_groups, + ) = view.into_inner(); let ( Some(edge_detection_pipeline), diff --git a/crates/bevy_core_pipeline/src/core_2d/main_opaque_pass_2d_node.rs b/crates/bevy_core_pipeline/src/core_2d/main_opaque_pass_2d_node.rs index 369345ae9134b..e50a2e6732f64 100644 --- a/crates/bevy_core_pipeline/src/core_2d/main_opaque_pass_2d_node.rs +++ b/crates/bevy_core_pipeline/src/core_2d/main_opaque_pass_2d_node.rs @@ -80,4 +80,3 @@ pub fn main_opaque_pass_2d( pass_span.end(&mut render_pass); } - diff --git a/crates/bevy_core_pipeline/src/core_3d/main_transmissive_pass_3d_node.rs b/crates/bevy_core_pipeline/src/core_3d/main_transmissive_pass_3d_node.rs index 9346a4f83b2ec..b15ad83c0e024 100644 --- a/crates/bevy_core_pipeline/src/core_3d/main_transmissive_pass_3d_node.rs +++ b/crates/bevy_core_pipeline/src/core_3d/main_transmissive_pass_3d_node.rs @@ -105,8 +105,7 @@ pub fn main_transmissive_pass_3d( physical_target_size.to_extents(), ); - let mut render_pass = - ctx.begin_tracked_render_pass(render_pass_descriptor.clone()); + let mut render_pass = ctx.begin_tracked_render_pass(render_pass_descriptor.clone()); let pass_span = diagnostics.pass_span(&mut render_pass, "main_transmissive_pass_3d"); diff --git a/crates/bevy_core_pipeline/src/core_3d/mod.rs b/crates/bevy_core_pipeline/src/core_3d/mod.rs index 77da3e4636068..fd3ef776ccb27 100644 --- a/crates/bevy_core_pipeline/src/core_3d/mod.rs +++ b/crates/bevy_core_pipeline/src/core_3d/mod.rs @@ -71,20 +71,27 @@ use bevy_render::{ use nonmax::NonMaxU32; use tracing::warn; -use crate::{deferred::{ - AlphaMask3dDeferred, Opaque3dDeferred, DEFERRED_LIGHTING_PASS_ID_FORMAT, - DEFERRED_PREPASS_FORMAT, -}, prepass::{ - AlphaMask3dPrepass, DeferredPrepass, DeferredPrepassDoubleBuffer, DepthPrepass, - DepthPrepassDoubleBuffer, MotionVectorPrepass, NormalPrepass, Opaque3dPrepass, - OpaqueNoLightmap3dBatchSetKey, OpaqueNoLightmap3dBinKey, ViewPrepassTextures, - MOTION_VECTOR_PREPASS_FORMAT, NORMAL_PREPASS_FORMAT, -}, schedule::Core3d, skybox::SkyboxPlugin, tonemapping::{DebandDither, Tonemapping}, Core3dSystems}; use crate::deferred::copy_lighting_id::copy_deferred_lighting_id; use crate::deferred::node::{early_deferred_prepass, late_deferred_prepass}; use crate::prepass::node::{early_prepass, late_prepass}; use crate::tonemapping::tonemapping; use crate::upscaling::upscaling; +use crate::{ + deferred::{ + AlphaMask3dDeferred, Opaque3dDeferred, DEFERRED_LIGHTING_PASS_ID_FORMAT, + DEFERRED_PREPASS_FORMAT, + }, + prepass::{ + AlphaMask3dPrepass, DeferredPrepass, DeferredPrepassDoubleBuffer, DepthPrepass, + DepthPrepassDoubleBuffer, MotionVectorPrepass, NormalPrepass, Opaque3dPrepass, + OpaqueNoLightmap3dBatchSetKey, OpaqueNoLightmap3dBinKey, ViewPrepassTextures, + MOTION_VECTOR_PREPASS_FORMAT, NORMAL_PREPASS_FORMAT, + }, + schedule::Core3d, + skybox::SkyboxPlugin, + tonemapping::{DebandDither, Tonemapping}, + Core3dSystems, +}; pub struct Core3dPlugin; @@ -135,39 +142,39 @@ impl Plugin for Core3dPlugin { ) .add_schedule(Core3d::base_schedule()) .add_systems( - Core3d, - ( - // Prepasses - early_prepass.before(Core3dSystems::EndPrepasses), - early_deferred_prepass - .after(early_prepass) - .before(Core3dSystems::EndPrepasses), - late_prepass - .after(early_deferred_prepass) - .before(Core3dSystems::EndPrepasses), - late_deferred_prepass - .after(late_prepass) - .before(Core3dSystems::EndPrepasses), - copy_deferred_lighting_id - .after(late_deferred_prepass) - .before(Core3dSystems::EndPrepasses), - // Main passes - main_opaque_pass_3d - .after(Core3dSystems::StartMainPass) - .before(Core3dSystems::EndMainPass), - main_transmissive_pass_3d - .after(main_opaque_pass_3d) - .before(Core3dSystems::EndMainPass), - main_transparent_pass_3d - .after(main_transmissive_pass_3d) - .before(Core3dSystems::EndMainPass), - // Post-processing - tonemapping - .after(Core3dSystems::StartMainPassPostProcessing) - .before(Core3dSystems::PostProcessing), - upscaling.after(Core3dSystems::EndMainPassPostProcessing), - ), - ); + Core3d, + ( + // Prepasses + early_prepass.before(Core3dSystems::EndPrepasses), + early_deferred_prepass + .after(early_prepass) + .before(Core3dSystems::EndPrepasses), + late_prepass + .after(early_deferred_prepass) + .before(Core3dSystems::EndPrepasses), + late_deferred_prepass + .after(late_prepass) + .before(Core3dSystems::EndPrepasses), + copy_deferred_lighting_id + .after(late_deferred_prepass) + .before(Core3dSystems::EndPrepasses), + // Main passes + main_opaque_pass_3d + .after(Core3dSystems::StartMainPass) + .before(Core3dSystems::EndMainPass), + main_transmissive_pass_3d + .after(main_opaque_pass_3d) + .before(Core3dSystems::EndMainPass), + main_transparent_pass_3d + .after(main_transmissive_pass_3d) + .before(Core3dSystems::EndMainPass), + // Post-processing + tonemapping + .after(Core3dSystems::StartMainPassPostProcessing) + .before(Core3dSystems::PostProcessing), + upscaling.after(Core3dSystems::EndMainPassPostProcessing), + ), + ); } } diff --git a/crates/bevy_core_pipeline/src/deferred/node.rs b/crates/bevy_core_pipeline/src/deferred/node.rs index 4a41ed34cfe43..fdf172d86244e 100644 --- a/crates/bevy_core_pipeline/src/deferred/node.rs +++ b/crates/bevy_core_pipeline/src/deferred/node.rs @@ -38,8 +38,15 @@ pub(crate) fn early_deferred_prepass( mut ctx: RenderContext, ) { let view_entity = view.entity(); - let (camera, extracted_view, view_depth_texture, view_prepass_textures, resolution_override, _, _) = - view.into_inner(); + let ( + camera, + extracted_view, + view_depth_texture, + view_prepass_textures, + resolution_override, + _, + _, + ) = view.into_inner(); run_deferred_prepass_system( world, diff --git a/crates/bevy_core_pipeline/src/fullscreen_material.rs b/crates/bevy_core_pipeline/src/fullscreen_material.rs index 2845df722fc3a..e1f43bfeb298c 100644 --- a/crates/bevy_core_pipeline/src/fullscreen_material.rs +++ b/crates/bevy_core_pipeline/src/fullscreen_material.rs @@ -67,7 +67,16 @@ impl Plugin for FullscreenMaterialPlugin { /// A trait to define a material that will render to the entire screen using a fullscreen triangle. pub trait FullscreenMaterial: - Component + ExtractComponent + Clone + Copy + ShaderType + WriteInto + Default + Send + Sync + 'static + Component + + ExtractComponent + + Clone + + Copy + + ShaderType + + WriteInto + + Default + + Send + + Sync + + 'static { /// The shader that will run on the entire screen using a fullscreen triangle. fn fragment_shader() -> ShaderRef; diff --git a/crates/bevy_core_pipeline/src/lib.rs b/crates/bevy_core_pipeline/src/lib.rs index 2c2d40d3e9cc8..ce4d836b2e862 100644 --- a/crates/bevy_core_pipeline/src/lib.rs +++ b/crates/bevy_core_pipeline/src/lib.rs @@ -25,6 +25,7 @@ pub use skybox::Skybox; mod fullscreen_vertex_shader; mod skybox; +use crate::schedule::camera_driver; use crate::{ blit::BlitPlugin, core_2d::Core2dPlugin, core_3d::Core3dPlugin, deferred::copy_lighting_id::CopyDeferredLightingIdPlugin, @@ -33,10 +34,9 @@ use crate::{ }; use bevy_app::{App, Plugin}; use bevy_asset::embedded_asset; -use bevy_render::RenderApp; use bevy_render::renderer::RenderGraph; +use bevy_render::RenderApp; use oit::OrderIndependentTransparencyPlugin; -use crate::schedule::camera_driver; #[derive(Default)] pub struct CorePipelinePlugin; diff --git a/crates/bevy_core_pipeline/src/schedule.rs b/crates/bevy_core_pipeline/src/schedule.rs index 098e2920667d9..bdc0ab55dcdc1 100644 --- a/crates/bevy_core_pipeline/src/schedule.rs +++ b/crates/bevy_core_pipeline/src/schedule.rs @@ -1,21 +1,18 @@ +use bevy_camera::{ClearColor, NormalizedRenderTarget}; use bevy_ecs::{ prelude::*, schedule::{IntoScheduleConfigs, Schedule, ScheduleLabel, SystemSet}, }; +use bevy_platform::collections::HashSet; use bevy_render::{ camera::{ExtractedCamera, SortedCameras}, render_resource::{ CommandEncoderDescriptor, LoadOp, Operations, RenderPassColorAttachment, RenderPassDescriptor, StoreOp, }, - renderer::{ - CurrentViewEntity, PendingCommandBuffers, RenderDevice, - RenderQueue, - }, + renderer::{CurrentViewEntity, PendingCommandBuffers, RenderDevice, RenderQueue}, view::ExtractedWindows, }; -use bevy_camera::{ClearColor, NormalizedRenderTarget}; -use bevy_platform::collections::HashSet; /// Schedule label for the Core 3D rendering pipeline. #[derive(ScheduleLabel, Debug, Clone, PartialEq, Eq, Hash, Default)] @@ -183,8 +180,7 @@ fn handle_uncovered_swap_chains(world: &mut World, camera_windows: &HashSet(); let render_queue = world.resource::(); - let mut encoder = - render_device.create_command_encoder(&CommandEncoderDescriptor::default()); + let mut encoder = render_device.create_command_encoder(&CommandEncoderDescriptor::default()); for (swap_chain_texture, clear_color) in &windows_to_clear { #[cfg(feature = "trace")] diff --git a/crates/bevy_core_pipeline/src/tonemapping/node.rs b/crates/bevy_core_pipeline/src/tonemapping/node.rs index 0f9a980826db7..3de01e4b988d4 100644 --- a/crates/bevy_core_pipeline/src/tonemapping/node.rs +++ b/crates/bevy_core_pipeline/src/tonemapping/node.rs @@ -122,9 +122,7 @@ pub fn tonemapping( let time_span = diagnostics.time_span(ctx.command_encoder(), "tonemapping"); { - let mut render_pass = ctx - .command_encoder() - .begin_render_pass(&pass_descriptor); + let mut render_pass = ctx.command_encoder().begin_render_pass(&pass_descriptor); render_pass.set_pipeline(pipeline); render_pass.set_bind_group(0, bind_group, &[view_uniform_offset.offset]); @@ -133,4 +131,3 @@ pub fn tonemapping( time_span.end(ctx.command_encoder()); } - diff --git a/crates/bevy_core_pipeline/src/upscaling/node.rs b/crates/bevy_core_pipeline/src/upscaling/node.rs index ea2337badf53b..536f58be4937b 100644 --- a/crates/bevy_core_pipeline/src/upscaling/node.rs +++ b/crates/bevy_core_pipeline/src/upscaling/node.rs @@ -66,7 +66,9 @@ pub fn upscaling( let pass_descriptor = RenderPassDescriptor { label: Some("upscaling"), - color_attachments: &[Some(target.out_texture_color_attachment(converted_clear_color))], + color_attachments: &[Some( + target.out_texture_color_attachment(converted_clear_color), + )], depth_stencil_attachment: None, timestamp_writes: None, occlusion_query_set: None, @@ -94,4 +96,3 @@ pub fn upscaling( time_span.end(ctx.command_encoder()); } - diff --git a/crates/bevy_pbr/src/deferred/mod.rs b/crates/bevy_pbr/src/deferred/mod.rs index df9ccb5a54e2c..d443c5d0fd633 100644 --- a/crates/bevy_pbr/src/deferred/mod.rs +++ b/crates/bevy_pbr/src/deferred/mod.rs @@ -1,13 +1,13 @@ +use crate::{ + DistanceFog, ExtractedAtmosphere, MeshPipelineKey, ViewFogUniformOffset, + ViewLightsUniformOffset, +}; use crate::{ MeshPipeline, MeshViewBindGroup, RenderViewLightProbes, ScreenSpaceAmbientOcclusion, ScreenSpaceReflectionsUniform, ViewEnvironmentMapUniformOffset, ViewLightProbesUniformOffset, ViewScreenSpaceReflectionsUniformOffset, TONEMAPPING_LUT_SAMPLER_BINDING_INDEX, TONEMAPPING_LUT_TEXTURE_BINDING_INDEX, }; -use crate::{ - DistanceFog, ExtractedAtmosphere, MeshPipelineKey, ViewFogUniformOffset, - ViewLightsUniformOffset, -}; use bevy_app::prelude::*; use bevy_asset::{embedded_asset, load_embedded_asset, AssetServer, Handle}; use bevy_core_pipeline::{ diff --git a/crates/bevy_pbr/src/light_probe/generate.rs b/crates/bevy_pbr/src/light_probe/generate.rs index e0b41901624e7..3184531edee29 100644 --- a/crates/bevy_pbr/src/light_probe/generate.rs +++ b/crates/bevy_pbr/src/light_probe/generate.rs @@ -1015,8 +1015,7 @@ pub fn filtering_system( timestamp_writes: None, }); - let irr_span = - diagnostics.pass_span(&mut compute_pass, "lightprobe_irradiance_map"); + let irr_span = diagnostics.pass_span(&mut compute_pass, "lightprobe_irradiance_map"); compute_pass.set_pipeline(irradiance_pipeline); compute_pass.set_bind_group(0, &bind_groups.irradiance, &[]); diff --git a/crates/bevy_pbr/src/meshlet/material_shade_nodes.rs b/crates/bevy_pbr/src/meshlet/material_shade_nodes.rs index 34a63ea8c70a9..9f2f7f9350b9f 100644 --- a/crates/bevy_pbr/src/meshlet/material_shade_nodes.rs +++ b/crates/bevy_pbr/src/meshlet/material_shade_nodes.rs @@ -15,10 +15,7 @@ use bevy_camera::Viewport; use bevy_core_pipeline::prepass::{ MotionVectorPrepass, PreviousViewUniformOffset, ViewPrepassTextures, }; -use bevy_ecs::{ - prelude::*, - query::Has, -}; +use bevy_ecs::{prelude::*, query::Has}; use bevy_render::{ camera::ExtractedCamera, render_resource::{ diff --git a/crates/bevy_pbr/src/meshlet/mod.rs b/crates/bevy_pbr/src/meshlet/mod.rs index abcfb04b0b67c..f975254d91bdd 100644 --- a/crates/bevy_pbr/src/meshlet/mod.rs +++ b/crates/bevy_pbr/src/meshlet/mod.rs @@ -43,8 +43,8 @@ use self::{ }, visibility_buffer_raster_node::meshlet_visibility_buffer_raster, }; -use crate::{meshlet::meshlet_mesh_manager::init_meshlet_mesh_manager, PreviousGlobalTransform}; use crate::render::early_shadow_pass; +use crate::{meshlet::meshlet_mesh_manager::init_meshlet_mesh_manager, PreviousGlobalTransform}; use bevy_app::{App, Plugin}; use bevy_asset::{embedded_asset, AssetApp, AssetId, Handle}; use bevy_camera::visibility::{self, Visibility, VisibilityClass}; diff --git a/crates/bevy_pbr/src/meshlet/visibility_buffer_raster_node.rs b/crates/bevy_pbr/src/meshlet/visibility_buffer_raster_node.rs index 2701579829f53..1517db2ddc090 100644 --- a/crates/bevy_pbr/src/meshlet/visibility_buffer_raster_node.rs +++ b/crates/bevy_pbr/src/meshlet/visibility_buffer_raster_node.rs @@ -93,8 +93,7 @@ pub fn meshlet_visibility_buffer_raster( meshlet_view_resources.view_size, ); - ctx.command_encoder() - .push_debug_group("meshlet_first_pass"); + ctx.command_encoder().push_debug_group("meshlet_first_pass"); first_cull( &mut ctx, meshlet_view_bind_groups, @@ -122,14 +121,16 @@ pub fn meshlet_visibility_buffer_raster( ); ctx.command_encoder().pop_debug_group(); - meshlet_view_resources.depth_pyramid.downsample_depth_with_ctx( - "downsample_depth", - &mut ctx, - meshlet_view_resources.view_size, - &meshlet_view_bind_groups.downsample_depth, - downsample_depth_first_pipeline, - downsample_depth_second_pipeline, - ); + meshlet_view_resources + .depth_pyramid + .downsample_depth_with_ctx( + "downsample_depth", + &mut ctx, + meshlet_view_resources.view_size, + &meshlet_view_bind_groups.downsample_depth, + downsample_depth_first_pipeline, + downsample_depth_second_pipeline, + ); ctx.command_encoder() .push_debug_group("meshlet_second_pass"); @@ -174,14 +175,16 @@ pub fn meshlet_visibility_buffer_raster( resolve_material_depth_pipeline, camera, ); - meshlet_view_resources.depth_pyramid.downsample_depth_with_ctx( - "downsample_depth", - &mut ctx, - meshlet_view_resources.view_size, - &meshlet_view_bind_groups.downsample_depth, - downsample_depth_first_pipeline, - downsample_depth_second_pipeline, - ); + meshlet_view_resources + .depth_pyramid + .downsample_depth_with_ctx( + "downsample_depth", + &mut ctx, + meshlet_view_resources.view_size, + &meshlet_view_bind_groups.downsample_depth, + downsample_depth_first_pipeline, + downsample_depth_second_pipeline, + ); ctx.command_encoder().pop_debug_group(); for light_entity in &lights.lights { @@ -216,8 +219,7 @@ pub fn meshlet_visibility_buffer_raster( meshlet_view_resources.view_size, ); - ctx.command_encoder() - .push_debug_group("meshlet_first_pass"); + ctx.command_encoder().push_debug_group("meshlet_first_pass"); first_cull( &mut ctx, meshlet_view_bind_groups, @@ -245,14 +247,16 @@ pub fn meshlet_visibility_buffer_raster( ); ctx.command_encoder().pop_debug_group(); - meshlet_view_resources.depth_pyramid.downsample_depth_with_ctx( - "downsample_depth", - &mut ctx, - meshlet_view_resources.view_size, - &meshlet_view_bind_groups.downsample_depth, - downsample_depth_first_shadow_view_pipeline, - downsample_depth_second_shadow_view_pipeline, - ); + meshlet_view_resources + .depth_pyramid + .downsample_depth_with_ctx( + "downsample_depth", + &mut ctx, + meshlet_view_resources.view_size, + &meshlet_view_bind_groups.downsample_depth, + downsample_depth_first_shadow_view_pipeline, + downsample_depth_second_shadow_view_pipeline, + ); ctx.command_encoder() .push_debug_group("meshlet_second_pass"); @@ -290,14 +294,16 @@ pub fn meshlet_visibility_buffer_raster( resolve_depth_shadow_view_pipeline, camera, ); - meshlet_view_resources.depth_pyramid.downsample_depth_with_ctx( - "downsample_depth", - &mut ctx, - meshlet_view_resources.view_size, - &meshlet_view_bind_groups.downsample_depth, - downsample_depth_first_shadow_view_pipeline, - downsample_depth_second_shadow_view_pipeline, - ); + meshlet_view_resources + .depth_pyramid + .downsample_depth_with_ctx( + "downsample_depth", + &mut ctx, + meshlet_view_resources.view_size, + &meshlet_view_bind_groups.downsample_depth, + downsample_depth_first_shadow_view_pipeline, + downsample_depth_second_shadow_view_pipeline, + ); ctx.command_encoder().pop_debug_group(); } } @@ -530,14 +536,16 @@ fn raster_pass( camera: Option<&ExtractedCamera>, raster_cluster_rightmost_slot: u32, ) { - let mut software_pass = ctx.command_encoder().begin_compute_pass(&ComputePassDescriptor { - label: Some(if first_pass { - "raster_software_first" - } else { - "raster_software_second" - }), - timestamp_writes: None, - }); + let mut software_pass = ctx + .command_encoder() + .begin_compute_pass(&ComputePassDescriptor { + label: Some(if first_pass { + "raster_software_first" + } else { + "raster_software_second" + }), + timestamp_writes: None, + }); software_pass.set_pipeline(visibility_buffer_software_raster_pipeline); software_pass.set_bind_group( 0, @@ -583,10 +591,12 @@ fn raster_pass( hardware_pass.draw_indirect(visibility_buffer_hardware_raster_indirect_args, 0); drop(hardware_pass); - let mut fill_counts_pass = ctx.command_encoder().begin_compute_pass(&ComputePassDescriptor { - label: Some("fill_counts"), - timestamp_writes: None, - }); + let mut fill_counts_pass = ctx + .command_encoder() + .begin_compute_pass(&ComputePassDescriptor { + label: Some("fill_counts"), + timestamp_writes: None, + }); fill_counts_pass.set_pipeline(fill_counts_pipeline); fill_counts_pass.set_bind_group(0, &meshlet_view_bind_groups.fill_counts, &[]); fill_counts_pass.dispatch_workgroups(1, 1, 1); diff --git a/crates/bevy_pbr/src/render/gpu_preprocess.rs b/crates/bevy_pbr/src/render/gpu_preprocess.rs index 8bcf0d5fce5f3..2cf762865ec81 100644 --- a/crates/bevy_pbr/src/render/gpu_preprocess.rs +++ b/crates/bevy_pbr/src/render/gpu_preprocess.rs @@ -77,7 +77,6 @@ pub struct GpuMeshPreprocessPlugin { pub use_gpu_instance_buffer_builder: bool, } - /// The compute shader pipelines for the GPU mesh preprocessing and indirect /// parameter building passes. #[derive(Resource)] @@ -496,8 +495,7 @@ pub fn early_gpu_preprocess( continue; }; - let Some(preprocess_pipeline) = - pipeline_cache.get_compute_pipeline(preprocess_pipeline_id) + let Some(preprocess_pipeline) = pipeline_cache.get_compute_pipeline(preprocess_pipeline_id) else { // This will happen while the pipeline is being compiled and is fine. continue; @@ -618,11 +616,7 @@ pub fn early_gpu_preprocess( pub fn late_gpu_preprocess( current_view: ViewQuery< - ( - &ExtractedView, - &PreprocessBindGroups, - &ViewUniformOffset, - ), + (&ExtractedView, &PreprocessBindGroups, &ViewUniformOffset), ( Without, Without, diff --git a/crates/bevy_pbr/src/render/light.rs b/crates/bevy_pbr/src/render/light.rs index 45355c5bc3c78..66226042fbd14 100644 --- a/crates/bevy_pbr/src/render/light.rs +++ b/crates/bevy_pbr/src/render/light.rs @@ -41,6 +41,10 @@ use bevy_render::{ mesh::allocator::MeshAllocator, view::{NoIndirectDrawing, RetainedViewEntity}, }; +use bevy_render::{ + mesh::allocator::SlabId, + sync_world::{MainEntity, RenderEntity}, +}; use bevy_render::{ mesh::RenderMesh, render_asset::RenderAssets, @@ -51,10 +55,6 @@ use bevy_render::{ view::ExtractedView, Extract, }; -use bevy_render::{ - mesh::allocator::SlabId, - sync_world::{MainEntity, RenderEntity}, -}; use bevy_transform::{components::GlobalTransform, prelude::Transform}; use bevy_utils::default; use core::{hash::Hash, ops::Range}; diff --git a/crates/bevy_pbr/src/ssao/mod.rs b/crates/bevy_pbr/src/ssao/mod.rs index d23793d58371c..15bdc05d6091b 100644 --- a/crates/bevy_pbr/src/ssao/mod.rs +++ b/crates/bevy_pbr/src/ssao/mod.rs @@ -233,11 +233,10 @@ fn ssao( } { - let mut spatial_denoise_pass = - command_encoder.begin_compute_pass(&ComputePassDescriptor { - label: Some("ssao_spatial_denoise"), - timestamp_writes: None, - }); + let mut spatial_denoise_pass = command_encoder.begin_compute_pass(&ComputePassDescriptor { + label: Some("ssao_spatial_denoise"), + timestamp_writes: None, + }); spatial_denoise_pass.set_pipeline(spatial_denoise_pipeline); spatial_denoise_pass.set_bind_group(0, &bind_groups.spatial_denoise_bind_group, &[]); spatial_denoise_pass.set_bind_group( diff --git a/crates/bevy_pbr/src/ssr/mod.rs b/crates/bevy_pbr/src/ssr/mod.rs index dc32b03ee3838..d28aa6753c308 100644 --- a/crates/bevy_pbr/src/ssr/mod.rs +++ b/crates/bevy_pbr/src/ssr/mod.rs @@ -205,7 +205,6 @@ impl Plugin for ScreenSpaceReflectionsPlugin { } } - impl Default for ScreenSpaceReflections { // Reasonable default values. // diff --git a/crates/bevy_pbr/src/volumetric_fog/mod.rs b/crates/bevy_pbr/src/volumetric_fog/mod.rs index 4146a2f1c7008..816a52fa219d2 100644 --- a/crates/bevy_pbr/src/volumetric_fog/mod.rs +++ b/crates/bevy_pbr/src/volumetric_fog/mod.rs @@ -43,8 +43,7 @@ use bevy_math::{ }; use bevy_mesh::{Mesh, Meshable}; use bevy_render::{ - render_resource::SpecializedRenderPipelines, - sync_component::SyncComponentPlugin, + render_resource::SpecializedRenderPipelines, sync_component::SyncComponentPlugin, ExtractSchedule, Render, RenderApp, RenderStartup, RenderSystems, }; use render::{volumetric_fog, VolumetricFogPipeline, VolumetricFogUniformBuffer}; diff --git a/crates/bevy_pbr/src/volumetric_fog/render.rs b/crates/bevy_pbr/src/volumetric_fog/render.rs index e4bba273d2c0f..de6c36da5ae9e 100644 --- a/crates/bevy_pbr/src/volumetric_fog/render.rs +++ b/crates/bevy_pbr/src/volumetric_fog/render.rs @@ -120,7 +120,6 @@ pub struct ViewVolumetricFogPipelines { pub textured: CachedRenderPipelineId, } - /// Identifies a single specialization of the volumetric fog shader. #[derive(PartialEq, Eq, Hash, Clone)] pub struct VolumetricFogPipelineKey { diff --git a/crates/bevy_post_process/src/bloom/mod.rs b/crates/bevy_post_process/src/bloom/mod.rs index 3bc7becfb58a2..907ba67a31543 100644 --- a/crates/bevy_post_process/src/bloom/mod.rs +++ b/crates/bevy_post_process/src/bloom/mod.rs @@ -246,14 +246,13 @@ pub(crate) fn bloom( // Final upsample pass { - let mut upsampling_final_pass = - command_encoder.begin_render_pass(&RenderPassDescriptor { - label: Some("bloom_upsampling_final_pass"), - color_attachments: &[Some(view_texture_unsampled)], - depth_stencil_attachment: None, - timestamp_writes: None, - occlusion_query_set: None, - }); + let mut upsampling_final_pass = command_encoder.begin_render_pass(&RenderPassDescriptor { + label: Some("bloom_upsampling_final_pass"), + color_attachments: &[Some(view_texture_unsampled)], + depth_stencil_attachment: None, + timestamp_writes: None, + occlusion_query_set: None, + }); upsampling_final_pass.set_pipeline(upsampling_final_pipeline); upsampling_final_pass.set_bind_group( 0, @@ -270,8 +269,7 @@ pub(crate) fn bloom( viewport.depth.end, ); } - let blend = - compute_blend_factor(bloom_settings, 0.0, (bloom_texture.mip_count - 1) as f32); + let blend = compute_blend_factor(bloom_settings, 0.0, (bloom_texture.mip_count - 1) as f32); upsampling_final_pass.set_blend_constant(LinearRgba::gray(blend).into()); upsampling_final_pass.draw(0..3, 0..1); } diff --git a/crates/bevy_post_process/src/dof/mod.rs b/crates/bevy_post_process/src/dof/mod.rs index 35499c2a3575b..1456fb2ed01e6 100644 --- a/crates/bevy_post_process/src/dof/mod.rs +++ b/crates/bevy_post_process/src/dof/mod.rs @@ -59,13 +59,11 @@ use bevy_utils::{default, once}; use smallvec::SmallVec; use tracing::{info, warn}; +use crate::bloom::bloom; use bevy_core_pipeline::{ - core_3d::DEPTH_TEXTURE_SAMPLING_SUPPORTED, - schedule::Core3d, - tonemapping::tonemapping, + core_3d::DEPTH_TEXTURE_SAMPLING_SUPPORTED, schedule::Core3d, tonemapping::tonemapping, FullscreenShader, }; -use crate::bloom::bloom; /// A plugin that adds support for the depth of field effect to Bevy. #[derive(Default)] @@ -241,16 +239,10 @@ impl Plugin for DepthOfFieldPlugin { prepare_depth_of_field_global_bind_group.in_set(RenderSystems::PrepareBindGroups), ) // Add depth_of_field to the 3d schedule - .add_systems( - Core3d, - depth_of_field - .after(bloom) - .before(tonemapping), - ); + .add_systems(Core3d, depth_of_field.after(bloom).before(tonemapping)); } } - /// The layout for the bind group shared among all invocations of the depth of /// field shader. #[derive(Resource, Clone)] @@ -770,7 +762,6 @@ impl DepthOfFieldPipelines { } } - pub(crate) fn depth_of_field( view: ViewQuery<( &ViewUniformOffset, @@ -889,7 +880,9 @@ pub(crate) fn depth_of_field( ..default() }; - let mut render_pass = ctx.command_encoder().begin_render_pass(&render_pass_descriptor); + let mut render_pass = ctx + .command_encoder() + .begin_render_pass(&render_pass_descriptor); render_pass.set_pipeline(render_pipeline); // Set the per-view bind group. diff --git a/crates/bevy_post_process/src/effect_stack/mod.rs b/crates/bevy_post_process/src/effect_stack/mod.rs index b42608965752b..626a754ee2ee0 100644 --- a/crates/bevy_post_process/src/effect_stack/mod.rs +++ b/crates/bevy_post_process/src/effect_stack/mod.rs @@ -40,12 +40,12 @@ use bevy_render::{ use bevy_shader::{load_shader_library, Shader}; use bevy_utils::prelude::default; +use crate::bloom::bloom; use bevy_core_pipeline::{ schedule::{Core2d, Core3d}, tonemapping::tonemapping, FullscreenShader, }; -use crate::bloom::bloom; /// The default chromatic aberration intensity amount, in a fraction of the /// window size. @@ -216,18 +216,8 @@ impl Plugin for EffectStackPlugin { ) .in_set(RenderSystems::Prepare), ) - .add_systems( - Core3d, - post_processing - .after(bloom) - .before(tonemapping), - ) - .add_systems( - Core2d, - post_processing - .after(bloom) - .before(tonemapping), - ); + .add_systems(Core3d, post_processing.after(bloom).before(tonemapping)) + .add_systems(Core2d, post_processing.after(bloom).before(tonemapping)); } } diff --git a/crates/bevy_post_process/src/motion_blur/mod.rs b/crates/bevy_post_process/src/motion_blur/mod.rs index f65e3eb69a6fa..0262cc22c6fe5 100644 --- a/crates/bevy_post_process/src/motion_blur/mod.rs +++ b/crates/bevy_post_process/src/motion_blur/mod.rs @@ -2,6 +2,7 @@ //! //! Add the [`MotionBlur`] component to a camera to enable motion blur. +use crate::bloom::bloom; use bevy_app::{App, Plugin}; use bevy_asset::embedded_asset; use bevy_camera::Camera; @@ -21,7 +22,6 @@ use bevy_render::{ render_resource::{ShaderType, SpecializedRenderPipelines}, Render, RenderApp, RenderStartup, RenderSystems, }; -use crate::bloom::bloom; pub mod node; pub mod pipeline; diff --git a/crates/bevy_post_process/src/motion_blur/node.rs b/crates/bevy_post_process/src/motion_blur/node.rs index 790a88d515eec..c87a9d64adcb8 100644 --- a/crates/bevy_post_process/src/motion_blur/node.rs +++ b/crates/bevy_post_process/src/motion_blur/node.rs @@ -32,8 +32,7 @@ pub(crate) fn motion_blur( globals_buffer: Res, mut ctx: RenderContext, ) { - let (view_target, pipeline_id, prepass_textures, motion_blur_uniform, msaa) = - view.into_inner(); + let (view_target, pipeline_id, prepass_textures, motion_blur_uniform, msaa) = view.into_inner(); if motion_blur_uniform.samples == 0 || motion_blur_uniform.shutter_angle <= 0.0 { return; // We can skip running motion blur in these cases. diff --git a/crates/bevy_post_process/src/msaa_writeback.rs b/crates/bevy_post_process/src/msaa_writeback.rs index a8526361ee944..a53f11a9c14eb 100644 --- a/crates/bevy_post_process/src/msaa_writeback.rs +++ b/crates/bevy_post_process/src/msaa_writeback.rs @@ -29,14 +29,8 @@ impl Plugin for MsaaWritebackPlugin { Render, prepare_msaa_writeback_pipelines.in_set(RenderSystems::Prepare), ); - render_app.add_systems( - Core3d, - msaa_writeback.before(Core3dSystems::EndPrepasses), - ); - render_app.add_systems( - Core2d, - msaa_writeback.before(Core2dSystems::StartMainPass), - ); + render_app.add_systems(Core3d, msaa_writeback.before(Core3dSystems::EndPrepasses)); + render_app.add_systems(Core2d, msaa_writeback.before(Core2dSystems::StartMainPass)); } } @@ -81,11 +75,8 @@ pub(crate) fn msaa_writeback( occlusion_query_set: None, }; - let bind_group = blit_pipeline.create_bind_group( - ctx.render_device(), - post_process.source, - &pipeline_cache, - ); + let bind_group = + blit_pipeline.create_bind_group(ctx.render_device(), post_process.source, &pipeline_cache); let diagnostics = ctx.diagnostic_recorder(); let diagnostics = diagnostics.as_deref(); diff --git a/crates/bevy_render/src/diagnostic/mod.rs b/crates/bevy_render/src/diagnostic/mod.rs index 61881bb0dcb04..5bd0e300e9c47 100644 --- a/crates/bevy_render/src/diagnostic/mod.rs +++ b/crates/bevy_render/src/diagnostic/mod.rs @@ -19,8 +19,7 @@ use crate::{renderer::RenderAdapterInfo, RenderApp}; use self::internal::{sync_diagnostics, Pass, RenderDiagnosticsMutex, WriteTimestamp}; pub use self::{ erased_render_asset_diagnostic_plugin::ErasedRenderAssetDiagnosticPlugin, - internal::DiagnosticsRecorder, - mesh_allocator_diagnostic_plugin::MeshAllocatorDiagnosticPlugin, + internal::DiagnosticsRecorder, mesh_allocator_diagnostic_plugin::MeshAllocatorDiagnosticPlugin, render_asset_diagnostic_plugin::RenderAssetDiagnosticPlugin, }; diff --git a/crates/bevy_render/src/renderer/mod.rs b/crates/bevy_render/src/renderer/mod.rs index 73712828d5df7..6a12ce65f3489 100644 --- a/crates/bevy_render/src/renderer/mod.rs +++ b/crates/bevy_render/src/renderer/mod.rs @@ -12,13 +12,13 @@ pub use render_device::*; pub use wgpu_wrapper::WgpuWrapper; use crate::{ - diagnostic::RecordDiagnostics, settings::{RenderResources, WgpuSettings, WgpuSettingsPriority}, view::{ExtractedWindows, ViewTarget}, }; use alloc::sync::Arc; use bevy_camera::NormalizedRenderTarget; use bevy_derive::{Deref, DerefMut}; +use bevy_ecs::schedule::ScheduleLabel; use bevy_ecs::{prelude::*, system::SystemState}; use bevy_platform::time::Instant; use bevy_render::camera::ExtractedCamera; @@ -26,10 +26,8 @@ use bevy_time::TimeSender; use bevy_window::RawHandleWrapperHolder; use tracing::{debug, error, info, info_span, warn}; use wgpu::{ - Adapter, AdapterInfo, Backends, DeviceType, Instance, Queue, - RequestAdapterOptions, Trace, + Adapter, AdapterInfo, Backends, DeviceType, Instance, Queue, RequestAdapterOptions, Trace, }; -use bevy_ecs::schedule::ScheduleLabel; #[derive(ScheduleLabel, Debug, Clone, PartialEq, Eq, Hash, Default)] pub struct RenderGraph; @@ -472,4 +470,4 @@ pub async fn initialize_renderer( #[cfg(feature = "raw_vulkan_init")] additional_vulkan_features, ) -} \ No newline at end of file +} diff --git a/crates/bevy_render/src/renderer/render_context.rs b/crates/bevy_render/src/renderer/render_context.rs index c6d9c019c12d4..73ff1c1d69e6f 100644 --- a/crates/bevy_render/src/renderer/render_context.rs +++ b/crates/bevy_render/src/renderer/render_context.rs @@ -11,7 +11,9 @@ use bevy_ecs::change_detection::Tick; use bevy_ecs::component::ComponentId; use bevy_ecs::prelude::*; use bevy_ecs::query::{FilteredAccessSet, QueryData, QueryFilter, QueryState}; -use bevy_ecs::system::{Deferred, SystemBuffer, SystemMeta, SystemParam, SystemParamValidationError}; +use bevy_ecs::system::{ + Deferred, SystemBuffer, SystemMeta, SystemParam, SystemParamValidationError, +}; use bevy_ecs::world::unsafe_world_cell::UnsafeWorldCell; use bevy_ecs::world::DeferredWorld; use core::marker::PhantomData; @@ -85,8 +87,7 @@ impl SystemBuffer for RenderContextState { self.render_device = None; } - fn queue(&mut self, _system_meta: &SystemMeta, _world: DeferredWorld) { - } + fn queue(&mut self, _system_meta: &SystemMeta, _world: DeferredWorld) {} } #[derive(SystemParam)] diff --git a/crates/bevy_solari/src/pathtracer/mod.rs b/crates/bevy_solari/src/pathtracer/mod.rs index 126237b926fbf..74cdca9eb1956 100644 --- a/crates/bevy_solari/src/pathtracer/mod.rs +++ b/crates/bevy_solari/src/pathtracer/mod.rs @@ -48,10 +48,7 @@ impl Plugin for PathtracingPlugin { Render, prepare_pathtracer_accumulation_texture.in_set(RenderSystems::PrepareResources), ) - .add_systems( - Core3d, - pathtracer.after(Core3dSystems::EndMainPass), - ); + .add_systems(Core3d, pathtracer.after(Core3dSystems::EndMainPass)); } } diff --git a/crates/bevy_solari/src/realtime/node.rs b/crates/bevy_solari/src/realtime/node.rs index c62ec19c3b66c..15838cce2771c 100644 --- a/crates/bevy_solari/src/realtime/node.rs +++ b/crates/bevy_solari/src/realtime/node.rs @@ -95,10 +95,7 @@ pub fn init_solari_lighting_pipelines( let bind_group_layout_world_cache_active_cells_dispatch = BindGroupLayoutDescriptor::new( "solari_lighting_bind_group_layout_world_cache_active_cells_dispatch", - &BindGroupLayoutEntries::single( - ShaderStages::COMPUTE, - storage_buffer_sized(false, None), - ), + &BindGroupLayoutEntries::single(ShaderStages::COMPUTE, storage_buffer_sized(false, None)), ); #[cfg(all(feature = "dlss", not(feature = "force_disable_dlss")))] @@ -307,7 +304,8 @@ pub fn solari_lighting( pipeline_cache.get_compute_pipeline(pipelines.decay_world_cache_pipeline), pipeline_cache.get_compute_pipeline(pipelines.compact_world_cache_single_block_pipeline), pipeline_cache.get_compute_pipeline(pipelines.compact_world_cache_blocks_pipeline), - pipeline_cache.get_compute_pipeline(pipelines.compact_world_cache_write_active_cells_pipeline), + pipeline_cache + .get_compute_pipeline(pipelines.compact_world_cache_write_active_cells_pipeline), pipeline_cache.get_compute_pipeline(pipelines.sample_for_world_cache_pipeline), pipeline_cache.get_compute_pipeline(pipelines.blend_new_world_cache_samples_pipeline), pipeline_cache.get_compute_pipeline(pipelines.presample_light_tiles_pipeline), @@ -364,9 +362,8 @@ pub fn solari_lighting( ); let bind_group_world_cache_active_cells_dispatch = render_device.create_bind_group( "solari_lighting_bind_group_world_cache_active_cells_dispatch", - &pipeline_cache.get_bind_group_layout( - &pipelines.bind_group_layout_world_cache_active_cells_dispatch, - ), + &pipeline_cache + .get_bind_group_layout(&pipelines.bind_group_layout_world_cache_active_cells_dispatch), &BindGroupEntries::single(s.world_cache_active_cells_dispatch.as_entire_binding()), ); @@ -538,7 +535,8 @@ pub fn solari_lighting( pipeline_cache.get_compute_pipeline(pipelines.decay_world_cache_pipeline), pipeline_cache.get_compute_pipeline(pipelines.compact_world_cache_single_block_pipeline), pipeline_cache.get_compute_pipeline(pipelines.compact_world_cache_blocks_pipeline), - pipeline_cache.get_compute_pipeline(pipelines.compact_world_cache_write_active_cells_pipeline), + pipeline_cache + .get_compute_pipeline(pipelines.compact_world_cache_write_active_cells_pipeline), pipeline_cache.get_compute_pipeline(pipelines.sample_for_world_cache_pipeline), pipeline_cache.get_compute_pipeline(pipelines.blend_new_world_cache_samples_pipeline), pipeline_cache.get_compute_pipeline(pipelines.presample_light_tiles_pipeline), @@ -601,9 +599,8 @@ pub fn solari_lighting( ); let bind_group_world_cache_active_cells_dispatch = render_device.create_bind_group( "solari_lighting_bind_group_world_cache_active_cells_dispatch", - &pipeline_cache.get_bind_group_layout( - &pipelines.bind_group_layout_world_cache_active_cells_dispatch, - ), + &pipeline_cache + .get_bind_group_layout(&pipelines.bind_group_layout_world_cache_active_cells_dispatch), &BindGroupEntries::single(s.world_cache_active_cells_dispatch.as_entire_binding()), ); diff --git a/crates/bevy_ui_render/src/render_pass.rs b/crates/bevy_ui_render/src/render_pass.rs index 3b5a11dc92061..fffe2c896bb88 100644 --- a/crates/bevy_ui_render/src/render_pass.rs +++ b/crates/bevy_ui_render/src/render_pass.rs @@ -38,7 +38,8 @@ pub fn ui_pass( return; }; - let Some(transparent_phase) = transparent_render_phases.get(&extracted_view.retained_view_entity) + let Some(transparent_phase) = + transparent_render_phases.get(&extracted_view.retained_view_entity) else { return; }; diff --git a/examples/shader_advanced/custom_post_processing.rs b/examples/shader_advanced/custom_post_processing.rs index 1372d21f261de..bed7d32409b53 100644 --- a/examples/shader_advanced/custom_post_processing.rs +++ b/examples/shader_advanced/custom_post_processing.rs @@ -139,20 +139,22 @@ fn post_process_system( } }; - let mut render_pass = ctx.command_encoder().begin_render_pass(&RenderPassDescriptor { - label: Some("post_process_pass"), - color_attachments: &[Some(RenderPassColorAttachment { - // We need to specify the post process destination view here - // to make sure we write to the appropriate texture. - view: post_process.destination, - depth_slice: None, - resolve_target: None, - ops: Operations::default(), - })], - depth_stencil_attachment: None, - timestamp_writes: None, - occlusion_query_set: None, - }); + let mut render_pass = ctx + .command_encoder() + .begin_render_pass(&RenderPassDescriptor { + label: Some("post_process_pass"), + color_attachments: &[Some(RenderPassColorAttachment { + // We need to specify the post process destination view here + // to make sure we write to the appropriate texture. + view: post_process.destination, + depth_slice: None, + resolve_target: None, + ops: Operations::default(), + })], + depth_stencil_attachment: None, + timestamp_writes: None, + occlusion_query_set: None, + }); render_pass.set_pipeline(pipeline); // By passing in the index of the post process settings on this view, we ensure From 8ea6a02c46487278a199d1611fe235802e5aa508 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?charlotte=20=F0=9F=8C=B8?= Date: Mon, 15 Dec 2025 16:30:23 -0800 Subject: [PATCH 03/55] Ci. --- .../src/core_3d/main_opaque_pass_3d_node.rs | 17 ++++---- .../bevy_core_pipeline/src/deferred/node.rs | 5 ++- crates/bevy_core_pipeline/src/prepass/node.rs | 40 ++++++++++--------- .../src/tonemapping/node.rs | 4 +- crates/bevy_render/src/renderer/mod.rs | 5 +-- 5 files changed, 36 insertions(+), 35 deletions(-) diff --git a/crates/bevy_core_pipeline/src/core_3d/main_opaque_pass_3d_node.rs b/crates/bevy_core_pipeline/src/core_3d/main_opaque_pass_3d_node.rs index fd367ff6d4ae1..b2d394b3de940 100644 --- a/crates/bevy_core_pipeline/src/core_3d/main_opaque_pass_3d_node.rs +++ b/crates/bevy_core_pipeline/src/core_3d/main_opaque_pass_3d_node.rs @@ -97,16 +97,15 @@ pub fn main_opaque_pass_3d( if let (Some(skybox_pipeline), Some(SkyboxBindGroup(skybox_bind_group))) = (skybox_pipeline, skybox_bind_group) + && let Some(pipeline) = pipeline_cache.get_render_pipeline(skybox_pipeline.0) { - if let Some(pipeline) = pipeline_cache.get_render_pipeline(skybox_pipeline.0) { - render_pass.set_render_pipeline(pipeline); - render_pass.set_bind_group( - 0, - &skybox_bind_group.0, - &[view_uniform_offset.offset, skybox_bind_group.1], - ); - render_pass.draw(0..3, 0..1); - } + render_pass.set_render_pipeline(pipeline); + render_pass.set_bind_group( + 0, + &skybox_bind_group.0, + &[view_uniform_offset.offset, skybox_bind_group.1], + ); + render_pass.draw(0..3, 0..1); } pass_span.end(&mut render_pass); diff --git a/crates/bevy_core_pipeline/src/deferred/node.rs b/crates/bevy_core_pipeline/src/deferred/node.rs index fdf172d86244e..7494b1a56cc75 100644 --- a/crates/bevy_core_pipeline/src/deferred/node.rs +++ b/crates/bevy_core_pipeline/src/deferred/node.rs @@ -102,7 +102,10 @@ pub(crate) fn late_deferred_prepass( ); } -#[allow(clippy::too_many_arguments)] +#[expect( + clippy::too_many_arguments, + reason = "render system with many view components" +)] fn run_deferred_prepass_system( world: &World, view_entity: Entity, diff --git a/crates/bevy_core_pipeline/src/prepass/node.rs b/crates/bevy_core_pipeline/src/prepass/node.rs index 5be8090d79784..10cbae74e99e1 100644 --- a/crates/bevy_core_pipeline/src/prepass/node.rs +++ b/crates/bevy_core_pipeline/src/prepass/node.rs @@ -134,7 +134,10 @@ pub fn late_prepass( } /// Shared implementation for prepass systems. -#[allow(clippy::too_many_arguments)] +#[expect( + clippy::too_many_arguments, + reason = "render system with many view components" +)] fn run_prepass_system( world: &World, view_entity: Entity, @@ -235,28 +238,27 @@ fn run_prepass_system( skybox_prepass_pipeline, skybox_prepass_bind_group, view_prev_uniform_offset, - ) { - if let Some(pipeline) = pipeline_cache.get_render_pipeline(skybox_prepass_pipeline.0) { - render_pass.set_render_pipeline(pipeline); - render_pass.set_bind_group( - 0, - &skybox_prepass_bind_group.0, - &[view_uniform_offset.offset, view_prev_uniform_offset.offset], - ); - render_pass.draw(0..3, 0..1); - } + ) && let Some(pipeline) = pipeline_cache.get_render_pipeline(skybox_prepass_pipeline.0) + { + render_pass.set_render_pipeline(pipeline); + render_pass.set_bind_group( + 0, + &skybox_prepass_bind_group.0, + &[view_uniform_offset.offset, view_prev_uniform_offset.offset], + ); + render_pass.draw(0..3, 0..1); } pass_span.end(&mut render_pass); drop(render_pass); - if deferred_prepass.is_none() { - if let Some(prepass_depth_texture) = &view_prepass_textures.depth { - ctx.command_encoder().copy_texture_to_texture( - view_depth_texture.texture.as_image_copy(), - prepass_depth_texture.texture.texture.as_image_copy(), - view_prepass_textures.size, - ); - } + if deferred_prepass.is_none() + && let Some(prepass_depth_texture) = &view_prepass_textures.depth + { + ctx.command_encoder().copy_texture_to_texture( + view_depth_texture.texture.as_image_copy(), + prepass_depth_texture.texture.texture.as_image_copy(), + view_prepass_textures.size, + ); } } diff --git a/crates/bevy_core_pipeline/src/tonemapping/node.rs b/crates/bevy_core_pipeline/src/tonemapping/node.rs index 3de01e4b988d4..5deb8db3e9c95 100644 --- a/crates/bevy_core_pipeline/src/tonemapping/node.rs +++ b/crates/bevy_core_pipeline/src/tonemapping/node.rs @@ -59,9 +59,7 @@ pub fn tonemapping( let source = post_process.source; let destination = post_process.destination; - let tonemapping_changed = cache - .last_tonemapping - .map_or(true, |last| *tonemapping != last); + let tonemapping_changed = cache.last_tonemapping != Some(*tonemapping); if tonemapping_changed { cache.last_tonemapping = Some(*tonemapping); } diff --git a/crates/bevy_render/src/renderer/mod.rs b/crates/bevy_render/src/renderer/mod.rs index 6a12ce65f3489..88ff86a259fde 100644 --- a/crates/bevy_render/src/renderer/mod.rs +++ b/crates/bevy_render/src/renderer/mod.rs @@ -24,7 +24,7 @@ use bevy_platform::time::Instant; use bevy_render::camera::ExtractedCamera; use bevy_time::TimeSender; use bevy_window::RawHandleWrapperHolder; -use tracing::{debug, error, info, info_span, warn}; +use tracing::{debug, info, info_span, warn}; use wgpu::{ Adapter, AdapterInfo, Backends, DeviceType, Instance, Queue, RequestAdapterOptions, Trace, }; @@ -34,8 +34,7 @@ pub struct RenderGraph; impl RenderGraph { pub fn base_schedule() -> Schedule { - let schedule = Schedule::new(Self); - schedule + Schedule::new(Self) } } From e5ca7e22dbfe3a32e992a83418a3d4fc01ef051e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?charlotte=20=F0=9F=8C=B8?= Date: Mon, 15 Dec 2025 17:58:47 -0800 Subject: [PATCH 04/55] Insert apply deferred automatically. --- crates/bevy_core_pipeline/src/schedule.rs | 3 --- 1 file changed, 3 deletions(-) diff --git a/crates/bevy_core_pipeline/src/schedule.rs b/crates/bevy_core_pipeline/src/schedule.rs index bdc0ab55dcdc1..3ee1a9beea3d5 100644 --- a/crates/bevy_core_pipeline/src/schedule.rs +++ b/crates/bevy_core_pipeline/src/schedule.rs @@ -36,8 +36,6 @@ impl Core3d { let mut schedule = Schedule::new(Self); schedule.set_build_settings(ScheduleBuildSettings { - // TODO: yes/no? - auto_insert_apply_deferred: false, ..Default::default() }); @@ -77,7 +75,6 @@ impl Core2d { let mut schedule = Schedule::new(Self); schedule.set_build_settings(ScheduleBuildSettings { - auto_insert_apply_deferred: false, ..Default::default() }); From 63388ed66d698e0e9d5d856d273d7cf337d4c6b1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?charlotte=20=F0=9F=8C=B8?= Date: Mon, 15 Dec 2025 19:03:20 -0800 Subject: [PATCH 05/55] Try pushing command encoders. --- crates/bevy_pbr/src/lib.rs | 4 ++- .../src/renderer/render_context.rs | 33 ++++++++++++------- 2 files changed, 24 insertions(+), 13 deletions(-) diff --git a/crates/bevy_pbr/src/lib.rs b/crates/bevy_pbr/src/lib.rs index e7fc420ca1366..36a988657f899 100644 --- a/crates/bevy_pbr/src/lib.rs +++ b/crates/bevy_pbr/src/lib.rs @@ -312,7 +312,9 @@ impl Plugin for PbrPlugin { render_app.add_systems( Core3d, ( - early_shadow_pass.before(late_shadow_pass), + early_shadow_pass + .after(Core3dSystems::EndPrepasses) + .before(late_shadow_pass), late_shadow_pass.before(Core3dSystems::StartMainPass), ), ); diff --git a/crates/bevy_render/src/renderer/render_context.rs b/crates/bevy_render/src/renderer/render_context.rs index 73ff1c1d69e6f..595a055f690e2 100644 --- a/crates/bevy_render/src/renderer/render_context.rs +++ b/crates/bevy_render/src/renderer/render_context.rs @@ -1,8 +1,3 @@ -//! System parameter-based render context for the schedule-driven render graph replacement. -//! -//! This module provides resources and system parameters for schedule-based rendering, -//! replacing the node-based render graph approach. - use crate::diagnostic::internal::DiagnosticsRecorder; use crate::render_phase::TrackedRenderPass; use crate::render_resource::{CommandEncoder, RenderPassDescriptor}; @@ -22,6 +17,7 @@ use wgpu::CommandBuffer; #[derive(Resource, Default)] pub struct PendingCommandBuffers { buffers: Vec, + encoders: Vec, } impl PendingCommandBuffers { @@ -29,16 +25,23 @@ impl PendingCommandBuffers { self.buffers.extend(buffers); } + pub fn push_encoder(&mut self, encoder: CommandEncoder) { + self.encoders.push(encoder); + } + pub fn take(&mut self) -> Vec { + for encoder in self.encoders.drain(..) { + self.buffers.push(encoder.finish()); + } core::mem::take(&mut self.buffers) } pub fn is_empty(&self) -> bool { - self.buffers.is_empty() + self.buffers.is_empty() && self.encoders.is_empty() } pub fn len(&self) -> usize { - self.buffers.len() + self.buffers.len() + self.encoders.len() } } @@ -75,12 +78,18 @@ impl RenderContextState { impl SystemBuffer for RenderContextState { fn apply(&mut self, _system_meta: &SystemMeta, world: &mut World) { - if !self.command_buffers.is_empty() || self.command_encoder.is_some() { - let command_buffers = self.finish(); + let has_buffers = !self.command_buffers.is_empty(); + let has_encoder = self.command_encoder.is_some(); + + if has_buffers || has_encoder { + let mut pending = world.resource_mut::(); + + if has_buffers { + pending.push(core::mem::take(&mut self.command_buffers)); + } - if !command_buffers.is_empty() { - let mut pending = world.resource_mut::(); - pending.push(command_buffers); + if let Some(encoder) = self.command_encoder.take() { + pending.push_encoder(encoder); } } From c6a670660f9fdf93a417f70192c990ff24c55a15 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?charlotte=20=F0=9F=8C=B8?= Date: Mon, 15 Dec 2025 22:47:05 -0800 Subject: [PATCH 06/55] Tracing. --- crates/bevy_core_pipeline/src/schedule.rs | 4 ++++ .../bevy_render/src/renderer/render_context.rs | 18 +++++++++++++++--- 2 files changed, 19 insertions(+), 3 deletions(-) diff --git a/crates/bevy_core_pipeline/src/schedule.rs b/crates/bevy_core_pipeline/src/schedule.rs index 3ee1a9beea3d5..a535dc2a7eff9 100644 --- a/crates/bevy_core_pipeline/src/schedule.rs +++ b/crates/bevy_core_pipeline/src/schedule.rs @@ -13,6 +13,7 @@ use bevy_render::{ renderer::{CurrentViewEntity, PendingCommandBuffers, RenderDevice, RenderQueue}, view::ExtractedWindows, }; +use tracing::info_span; /// Schedule label for the Core 3D rendering pipeline. #[derive(ScheduleLabel, Debug, Clone, PartialEq, Eq, Hash, Default)] @@ -36,6 +37,7 @@ impl Core3d { let mut schedule = Schedule::new(Self); schedule.set_build_settings(ScheduleBuildSettings { + auto_insert_apply_deferred: false, ..Default::default() }); @@ -145,9 +147,11 @@ pub fn camera_driver(world: &mut World) { fn submit_pending_command_buffers(world: &mut World) { let mut pending = world.resource_mut::(); + let buffer_count = pending.len(); let buffers = pending.take(); if !buffers.is_empty() { + let _span = info_span!("queue_submit", count = buffer_count).entered(); let queue = world.resource::(); queue.submit(buffers); } diff --git a/crates/bevy_render/src/renderer/render_context.rs b/crates/bevy_render/src/renderer/render_context.rs index 595a055f690e2..e2be88cafb0e3 100644 --- a/crates/bevy_render/src/renderer/render_context.rs +++ b/crates/bevy_render/src/renderer/render_context.rs @@ -11,7 +11,9 @@ use bevy_ecs::system::{ }; use bevy_ecs::world::unsafe_world_cell::UnsafeWorldCell; use bevy_ecs::world::DeferredWorld; +use bevy_tasks::ComputeTaskPool; use core::marker::PhantomData; +use tracing::info_span; use wgpu::CommandBuffer; #[derive(Resource, Default)] @@ -30,8 +32,16 @@ impl PendingCommandBuffers { } pub fn take(&mut self) -> Vec { - for encoder in self.encoders.drain(..) { - self.buffers.push(encoder.finish()); + if !self.encoders.is_empty() { + let _span = info_span!("finish_encoders", count = self.encoders.len()).entered(); + let encoders = core::mem::take(&mut self.encoders); + let task_pool = ComputeTaskPool::get(); + let finished: Vec = task_pool.scope(|scope| { + for encoder in encoders { + scope.spawn(async move { encoder.finish() }); + } + }); + self.buffers.extend(finished); } core::mem::take(&mut self.buffers) } @@ -77,7 +87,9 @@ impl RenderContextState { } impl SystemBuffer for RenderContextState { - fn apply(&mut self, _system_meta: &SystemMeta, world: &mut World) { + fn apply(&mut self, system_meta: &SystemMeta, world: &mut World) { + let _span = info_span!("RenderContextState::apply", system = %system_meta.name()).entered(); + let has_buffers = !self.command_buffers.is_empty(); let has_encoder = self.command_encoder.is_some(); From 57d84be7ec34f823259f0da5b25f018508d978b6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?charlotte=20=F0=9F=8C=B8?= Date: Mon, 15 Dec 2025 23:29:15 -0800 Subject: [PATCH 07/55] Don't parallelize. --- crates/bevy_render/src/renderer/render_context.rs | 13 ++----------- 1 file changed, 2 insertions(+), 11 deletions(-) diff --git a/crates/bevy_render/src/renderer/render_context.rs b/crates/bevy_render/src/renderer/render_context.rs index e2be88cafb0e3..7c72ff6885702 100644 --- a/crates/bevy_render/src/renderer/render_context.rs +++ b/crates/bevy_render/src/renderer/render_context.rs @@ -11,7 +11,6 @@ use bevy_ecs::system::{ }; use bevy_ecs::world::unsafe_world_cell::UnsafeWorldCell; use bevy_ecs::world::DeferredWorld; -use bevy_tasks::ComputeTaskPool; use core::marker::PhantomData; use tracing::info_span; use wgpu::CommandBuffer; @@ -32,16 +31,8 @@ impl PendingCommandBuffers { } pub fn take(&mut self) -> Vec { - if !self.encoders.is_empty() { - let _span = info_span!("finish_encoders", count = self.encoders.len()).entered(); - let encoders = core::mem::take(&mut self.encoders); - let task_pool = ComputeTaskPool::get(); - let finished: Vec = task_pool.scope(|scope| { - for encoder in encoders { - scope.spawn(async move { encoder.finish() }); - } - }); - self.buffers.extend(finished); + for encoder in self.encoders.drain(..) { + self.buffers.push(encoder.finish()); } core::mem::take(&mut self.buffers) } From f5c54e0330d774639f9b8a67ba20a0a30305cc63 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?charlotte=20=F0=9F=8C=B8?= Date: Tue, 16 Dec 2025 01:38:05 -0800 Subject: [PATCH 08/55] Fix shadow ordering. --- crates/bevy_pbr/src/lib.rs | 6 ++++- crates/bevy_pbr/src/render/gpu_preprocess.rs | 27 +++++++++++++++++--- 2 files changed, 29 insertions(+), 4 deletions(-) diff --git a/crates/bevy_pbr/src/lib.rs b/crates/bevy_pbr/src/lib.rs index 36a988657f899..3a83a41c19495 100644 --- a/crates/bevy_pbr/src/lib.rs +++ b/crates/bevy_pbr/src/lib.rs @@ -313,9 +313,13 @@ impl Plugin for PbrPlugin { Core3d, ( early_shadow_pass + .after(early_prepass_build_indirect_parameters) .after(Core3dSystems::EndPrepasses) .before(late_shadow_pass), - late_shadow_pass.before(Core3dSystems::StartMainPass), + late_shadow_pass + .after(late_prepass_build_indirect_parameters) + .before(main_build_indirect_parameters) + .before(Core3dSystems::StartMainPass), ), ); } diff --git a/crates/bevy_pbr/src/render/gpu_preprocess.rs b/crates/bevy_pbr/src/render/gpu_preprocess.rs index 2cf762865ec81..d5ab9a013c073 100644 --- a/crates/bevy_pbr/src/render/gpu_preprocess.rs +++ b/crates/bevy_pbr/src/render/gpu_preprocess.rs @@ -742,9 +742,10 @@ pub fn late_gpu_preprocess( } pub fn early_prepass_build_indirect_parameters( - _view: ViewQuery< + views: Query< (), ( + With, Without, Without, Or<(With, With)>, @@ -756,6 +757,10 @@ pub fn early_prepass_build_indirect_parameters( indirect_parameters_buffers: Option>, mut ctx: RenderContext, ) { + if views.iter().next().is_none() { + return; + } + run_build_indirect_parameters( &mut ctx, build_indirect_params_bind_groups.as_deref(), @@ -767,9 +772,10 @@ pub fn early_prepass_build_indirect_parameters( } pub fn late_prepass_build_indirect_parameters( - _view: ViewQuery< + views: Query< (), ( + With, Without, Without, Or<(With, With)>, @@ -782,6 +788,10 @@ pub fn late_prepass_build_indirect_parameters( indirect_parameters_buffers: Option>, mut ctx: RenderContext, ) { + if views.iter().next().is_none() { + return; + } + run_build_indirect_parameters( &mut ctx, build_indirect_params_bind_groups.as_deref(), @@ -793,13 +803,24 @@ pub fn late_prepass_build_indirect_parameters( } pub fn main_build_indirect_parameters( - _view: ViewQuery<(), (Without, Without)>, + views: Query< + (), + ( + With, + Without, + Without, + ), + >, preprocess_pipelines: Res, build_indirect_params_bind_groups: Option>, pipeline_cache: Res, indirect_parameters_buffers: Option>, mut ctx: RenderContext, ) { + if views.iter().next().is_none() { + return; + } + run_build_indirect_parameters( &mut ctx, build_indirect_params_bind_groups.as_deref(), From 8c01b325a444be9cfaea46413b2577810624b4ad Mon Sep 17 00:00:00 2001 From: atlas dostal Date: Tue, 16 Dec 2025 10:38:20 -0500 Subject: [PATCH 09/55] ci --- crates/bevy_core_pipeline/src/schedule.rs | 6 +++++- .../src/meshlet/material_pipeline_prepare.rs | 12 ++++++------ crates/bevy_post_process/src/bloom/mod.rs | 2 -- 3 files changed, 11 insertions(+), 9 deletions(-) diff --git a/crates/bevy_core_pipeline/src/schedule.rs b/crates/bevy_core_pipeline/src/schedule.rs index a535dc2a7eff9..a85e3b3032a39 100644 --- a/crates/bevy_core_pipeline/src/schedule.rs +++ b/crates/bevy_core_pipeline/src/schedule.rs @@ -103,7 +103,11 @@ pub fn camera_driver(world: &mut World) { let mut camera_windows = HashSet::default(); - for (camera_entity, order) in sorted_cameras { + for camera in sorted_cameras { + #[cfg(feature = "trace")] + let (camera_entity, order) = camera; + #[cfg(not(feature = "trace"))] + let (camera_entity, _) = camera; let Some(camera) = world.get::(camera_entity) else { continue; }; diff --git a/crates/bevy_pbr/src/meshlet/material_pipeline_prepare.rs b/crates/bevy_pbr/src/meshlet/material_pipeline_prepare.rs index e00c41900146d..c01fc3347f2ec 100644 --- a/crates/bevy_pbr/src/meshlet/material_pipeline_prepare.rs +++ b/crates/bevy_pbr/src/meshlet/material_pipeline_prepare.rs @@ -18,11 +18,11 @@ use bevy_render::{camera::TemporalJitter, render_resource::*, view::ExtractedVie use bevy_utils::default; use core::any::{Any, TypeId}; -/// A list of `(Material ID, Pipeline, BindGroup)` for a view for use in [`MeshletMainOpaquePass3dNode`](`super::MeshletMainOpaquePass3dNode`). +/// A list of `(Material ID, Pipeline, BindGroup)` for a view for use in [`meshlet_main_opaque_pass`](`super::meshlet_main_opaque_pass`). #[derive(Component, Deref, DerefMut, Default)] pub struct MeshletViewMaterialsMainOpaquePass(pub Vec<(u32, CachedRenderPipelineId, BindGroup)>); -/// Prepare [`Material`] pipelines for [`MeshletMesh`](`super::MeshletMesh`) entities for use in [`MeshletMainOpaquePass3dNode`](`super::MeshletMainOpaquePass3dNode`), +/// Prepare [`Material`] pipelines for [`MeshletMesh`](`super::MeshletMesh`) entities for use in [`meshlet_main_opaque_pass`](`super::meshlet_main_opaque_pass`), /// and register the material with [`InstanceManager`]. pub fn prepare_material_meshlet_meshes_main_opaque_pass( resource_manager: ResMut, @@ -245,18 +245,18 @@ pub fn prepare_material_meshlet_meshes_main_opaque_pass( } } -/// A list of `(Material ID, Pipeline, BindGroup)` for a view for use in [`MeshletPrepassNode`](`super::MeshletPrepassNode`). +/// A list of `(Material ID, Pipeline, BindGroup)` for a view for use in [`meshlet_prepass`](`super::meshlet_prepass`). #[derive(Component, Deref, DerefMut, Default)] pub struct MeshletViewMaterialsPrepass(pub Vec<(u32, CachedRenderPipelineId, BindGroup)>); -/// A list of `(Material ID, Pipeline, BindGroup)` for a view for use in [`MeshletDeferredGBufferPrepassNode`](`super::MeshletDeferredGBufferPrepassNode`). +/// A list of `(Material ID, Pipeline, BindGroup)` for a view for use in [`meshlet_deferred_gbuffer_prepass`](`super::meshlet_deferred_gbuffer_prepass`). #[derive(Component, Deref, DerefMut, Default)] pub struct MeshletViewMaterialsDeferredGBufferPrepass( pub Vec<(u32, CachedRenderPipelineId, BindGroup)>, ); -/// Prepare [`Material`] pipelines for [`MeshletMesh`](`super::MeshletMesh`) entities for use in [`MeshletPrepassNode`](`super::MeshletPrepassNode`), -/// and [`MeshletDeferredGBufferPrepassNode`](`super::MeshletDeferredGBufferPrepassNode`) and register the material with [`InstanceManager`]. +/// Prepare [`Material`] pipelines for [`MeshletMesh`](`super::MeshletMesh`) entities for use in [`meshlet_prepass`](`super::meshlet_prepass`), +/// and [`meshlet_deferred_gbuffer_prepass`](`super::meshlet_deferred_gbuffer_prepass`) and register the material with [`InstanceManager`]. pub fn prepare_material_meshlet_meshes_prepass( resource_manager: ResMut, mut instance_manager: ResMut, diff --git a/crates/bevy_post_process/src/bloom/mod.rs b/crates/bevy_post_process/src/bloom/mod.rs index 907ba67a31543..23e7befc7764b 100644 --- a/crates/bevy_post_process/src/bloom/mod.rs +++ b/crates/bevy_post_process/src/bloom/mod.rs @@ -34,8 +34,6 @@ use downsampling_pipeline::{ prepare_downsampling_pipeline, BloomDownsamplingPipeline, BloomDownsamplingPipelineIds, BloomUniforms, }; -#[cfg(feature = "trace")] -use tracing::info_span; use upsampling_pipeline::{ prepare_upsampling_pipeline, BloomUpsamplingPipeline, UpsamplingPipelineIds, }; From ff8f43fec929bced12c4d1fa9b49f0c54bf9a1d3 Mon Sep 17 00:00:00 2001 From: atlas dostal Date: Tue, 16 Dec 2025 11:20:19 -0500 Subject: [PATCH 10/55] examples --- examples/3d/occlusion_culling.rs | 164 +++++++----------- examples/app/headless_renderer.rs | 113 +++++------- .../shader/compute_shader_game_of_life.rs | 150 +++++++--------- examples/shader/gpu_readback.rs | 68 +++----- 4 files changed, 196 insertions(+), 299 deletions(-) diff --git a/examples/3d/occlusion_culling.rs b/examples/3d/occlusion_culling.rs index d61356b9bca8c..650dd6335de37 100644 --- a/examples/3d/occlusion_culling.rs +++ b/examples/3d/occlusion_culling.rs @@ -9,19 +9,12 @@ use std::{ any::TypeId, f32::consts::PI, fmt::Write as _, - result::Result, sync::{Arc, Mutex}, }; use bevy::{ color::palettes::css::{SILVER, WHITE}, - core_pipeline::{ - core_3d::{ - graph::{Core3d, Node3d}, - Opaque3d, - }, - prepass::DepthPrepass, - }, + core_pipeline::{core_3d::Opaque3d, prepass::DepthPrepass, Core3dSystems}, pbr::PbrPlugin, prelude::*, render::{ @@ -29,13 +22,13 @@ use bevy::{ GpuPreprocessingSupport, IndirectParametersBuffers, IndirectParametersIndexed, }, experimental::occlusion_culling::OcclusionCulling, - render_graph::{self, NodeRunError, RenderGraphContext, RenderGraphExt, RenderLabel}, render_resource::{Buffer, BufferDescriptor, BufferUsages, MapMode}, renderer::{RenderContext, RenderDevice}, settings::WgpuFeatures, Render, RenderApp, RenderDebugFlags, RenderPlugin, RenderStartup, RenderSystems, }, }; +use bevy_render::renderer::RenderGraph; use bytemuck::Pod; /// The radius of the spinning sphere of cubes. @@ -66,16 +59,6 @@ struct LargeCube; /// GPU back to the CPU. struct ReadbackIndirectParametersPlugin; -/// The node that we insert into the render graph in order to read the number of -/// culled meshes from the GPU back to the CPU. -#[derive(Default)] -struct ReadbackIndirectParametersNode; - -/// The [`RenderLabel`] that we use to identify the -/// [`ReadbackIndirectParametersNode`]. -#[derive(Clone, PartialEq, Eq, Hash, Debug, RenderLabel)] -struct ReadbackIndirectParameters; - /// The intermediate staging buffers that we use to read back the indirect /// parameters from the GPU to the CPU. /// @@ -239,26 +222,20 @@ impl Plugin for ReadbackIndirectParametersPlugin { create_indirect_parameters_staging_buffers .in_set(RenderSystems::PrepareResourcesFlush), ) - // Add the node that allows us to read the indirect parameters back - // from the GPU to the CPU, which allows us to determine how many - // meshes were culled. - .add_render_graph_node::( - Core3d, - ReadbackIndirectParameters, - ) - // We read back the indirect parameters any time after - // `EndMainPass`. Readback doesn't particularly need to execute - // before `EndMainPassPostProcessing`, but we specify that anyway - // because we want to make the indirect parameters run before - // *something* in the graph, and `EndMainPassPostProcessing` is a - // good a node as any other. - .add_render_graph_edges( - Core3d, - ( - Node3d::EndMainPass, - ReadbackIndirectParameters, - Node3d::EndMainPassPostProcessing, - ), + .add_systems( + RenderGraph, + // Add the node that allows us to read the indirect parameters back + // from the GPU to the CPU, which allows us to determine how many + // meshes were culled. + readback_indirect_parameters_node + // We read back the indirect parameters any time after + // `EndMainPass`. Readback doesn't particularly need to execute + // before `EndMainPassPostProcessing`, but we specify that anyway + // because we want to make the indirect parameters run before + // *something* in the graph, and `EndMainPassPostProcessing` is a + // good a node as any other. + .after(Core3dSystems::EndMainPass) + .before(Core3dSystems::EndMainPassPostProcessing), ); } } @@ -412,70 +389,55 @@ fn spawn_help_text(commands: &mut Commands) { )); } -impl render_graph::Node for ReadbackIndirectParametersNode { - fn run<'w>( - &self, - _: &mut RenderGraphContext, - render_context: &mut RenderContext<'w>, - world: &'w World, - ) -> Result<(), NodeRunError> { - // Extract the buffers that hold the GPU indirect draw parameters from - // the world resources. We're going to read those buffers to determine - // how many meshes were actually drawn. - let (Some(indirect_parameters_buffers), Some(indirect_parameters_mapping_buffers)) = ( - world.get_resource::(), - world.get_resource::(), - ) else { - return Ok(()); - }; - - // Get the indirect parameters buffers corresponding to the opaque 3D - // phase, since all our meshes are in that phase. - let Some(phase_indirect_parameters_buffers) = - indirect_parameters_buffers.get(&TypeId::of::()) - else { - return Ok(()); - }; - - // Grab both the buffers we're copying from and the staging buffers - // we're copying to. Remember that we can't map the indirect parameters - // buffers directly, so we have to copy their contents to a staging - // buffer. - let ( - Some(indexed_data_buffer), - Some(indexed_batch_sets_buffer), - Some(indirect_parameters_staging_data_buffer), - Some(indirect_parameters_staging_batch_sets_buffer), - ) = ( - phase_indirect_parameters_buffers.indexed.data_buffer(), - phase_indirect_parameters_buffers - .indexed - .batch_sets_buffer(), - indirect_parameters_mapping_buffers.data.as_ref(), - indirect_parameters_mapping_buffers.batch_sets.as_ref(), - ) - else { - return Ok(()); - }; +fn readback_indirect_parameters_node( + mut render_context: RenderContext, + indirect_parameters_buffers: Res, + indirect_parameters_mapping_buffers: Res, +) { + // Get the indirect parameters buffers corresponding to the opaque 3D + // phase, since all our meshes are in that phase. + let Some(phase_indirect_parameters_buffers) = + indirect_parameters_buffers.get(&TypeId::of::()) + else { + return; + }; - // Copy from the indirect parameters buffers to the staging buffers. - render_context.command_encoder().copy_buffer_to_buffer( - indexed_data_buffer, - 0, - indirect_parameters_staging_data_buffer, - 0, - indexed_data_buffer.size(), - ); - render_context.command_encoder().copy_buffer_to_buffer( - indexed_batch_sets_buffer, - 0, - indirect_parameters_staging_batch_sets_buffer, - 0, - indexed_batch_sets_buffer.size(), - ); + // Grab both the buffers we're copying from and the staging buffers + // we're copying to. Remember that we can't map the indirect parameters + // buffers directly, so we have to copy their contents to a staging + // buffer. + let ( + Some(indexed_data_buffer), + Some(indexed_batch_sets_buffer), + Some(indirect_parameters_staging_data_buffer), + Some(indirect_parameters_staging_batch_sets_buffer), + ) = ( + phase_indirect_parameters_buffers.indexed.data_buffer(), + phase_indirect_parameters_buffers + .indexed + .batch_sets_buffer(), + indirect_parameters_mapping_buffers.data.as_ref(), + indirect_parameters_mapping_buffers.batch_sets.as_ref(), + ) + else { + return; + }; - Ok(()) - } + // Copy from the indirect parameters buffers to the staging buffers. + render_context.command_encoder().copy_buffer_to_buffer( + indexed_data_buffer, + 0, + indirect_parameters_staging_data_buffer, + 0, + indexed_data_buffer.size(), + ); + render_context.command_encoder().copy_buffer_to_buffer( + indexed_batch_sets_buffer, + 0, + indirect_parameters_staging_batch_sets_buffer, + 0, + indexed_batch_sets_buffer.size(), + ); } /// Creates the staging buffers that we use to read back the indirect parameters diff --git a/examples/app/headless_renderer.rs b/examples/app/headless_renderer.rs index 2ab79e7660a98..78fe27f161462 100644 --- a/examples/app/headless_renderer.rs +++ b/examples/app/headless_renderer.rs @@ -20,7 +20,6 @@ use bevy::{ prelude::*, render::{ render_asset::RenderAssets, - render_graph::{self, NodeRunError, RenderGraph, RenderGraphContext, RenderLabel}, render_resource::{ Buffer, BufferDescriptor, BufferUsages, CommandEncoderDescriptor, Extent3d, MapMode, PollType, TexelCopyBufferInfo, TexelCopyBufferLayout, TextureFormat, TextureUsages, @@ -31,6 +30,7 @@ use bevy::{ window::ExitCondition, winit::WinitPlugin, }; +use bevy_render::renderer::RenderGraph; use crossbeam_channel::{Receiver, Sender}; use std::{ ops::{Deref, DerefMut}, @@ -210,10 +210,6 @@ impl Plugin for ImageCopyPlugin { .insert_resource(MainWorldReceiver(r)) .sub_app_mut(RenderApp); - let mut graph = render_app.world_mut().resource_mut::(); - graph.add_node(ImageCopy, ImageCopyDriver); - graph.add_node_edge(bevy::render::graph::CameraDriverLabel, ImageCopy); - render_app .insert_resource(RenderWorldSender(s)) // Make ImageCopiers accessible in RenderWorld system and plugin @@ -223,7 +219,8 @@ impl Plugin for ImageCopyPlugin { .add_systems( Render, receive_image_from_buffer.after(RenderSystems::Render), - ); + ) + .add_systems(RenderGraph, image_copy_driver); } } @@ -322,71 +319,53 @@ fn image_copy_extract(mut commands: Commands, image_copiers: Extract Result<(), NodeRunError> { - let image_copiers = world.get_resource::().unwrap(); - let gpu_images = world - .get_resource::>() - .unwrap(); - - for image_copier in image_copiers.iter() { - if !image_copier.enabled() { - continue; - } +fn image_copy_driver( + render_context: RenderContext, + image_copiers: Res, + render_queue: Res, + gpu_images: Res>, +) { + for image_copier in image_copiers.iter() { + if !image_copier.enabled() { + continue; + } - let src_image = gpu_images.get(&image_copier.src_image).unwrap(); - - let mut encoder = render_context - .render_device() - .create_command_encoder(&CommandEncoderDescriptor::default()); - - let block_dimensions = src_image.texture_format.block_dimensions(); - let block_size = src_image.texture_format.block_copy_size(None).unwrap(); - - // Calculating correct size of image row because - // copy_texture_to_buffer can copy image only by rows aligned wgpu::COPY_BYTES_PER_ROW_ALIGNMENT - // That's why image in buffer can be little bit wider - // This should be taken into account at copy from buffer stage - let padded_bytes_per_row = RenderDevice::align_copy_bytes_per_row( - (src_image.size.width as usize / block_dimensions.0 as usize) * block_size as usize, - ); - - encoder.copy_texture_to_buffer( - src_image.texture.as_image_copy(), - TexelCopyBufferInfo { - buffer: &image_copier.buffer, - layout: TexelCopyBufferLayout { - offset: 0, - bytes_per_row: Some( - std::num::NonZero::::new(padded_bytes_per_row as u32) - .unwrap() - .into(), - ), - rows_per_image: None, - }, + let src_image = gpu_images.get(&image_copier.src_image).unwrap(); + + let mut encoder = render_context + .render_device() + .create_command_encoder(&CommandEncoderDescriptor::default()); + + let block_dimensions = src_image.texture_format.block_dimensions(); + let block_size = src_image.texture_format.block_copy_size(None).unwrap(); + + // Calculating correct size of image row because + // copy_texture_to_buffer can copy image only by rows aligned wgpu::COPY_BYTES_PER_ROW_ALIGNMENT + // That's why image in buffer can be little bit wider + // This should be taken into account at copy from buffer stage + let padded_bytes_per_row = RenderDevice::align_copy_bytes_per_row( + (src_image.size.width as usize / block_dimensions.0 as usize) * block_size as usize, + ); + + encoder.copy_texture_to_buffer( + src_image.texture.as_image_copy(), + TexelCopyBufferInfo { + buffer: &image_copier.buffer, + layout: TexelCopyBufferLayout { + offset: 0, + bytes_per_row: Some( + std::num::NonZero::::new(padded_bytes_per_row as u32) + .unwrap() + .into(), + ), + rows_per_image: None, }, - src_image.size, - ); - - let render_queue = world.get_resource::().unwrap(); - render_queue.submit(std::iter::once(encoder.finish())); - } + }, + src_image.size, + ); - Ok(()) + render_queue.submit(std::iter::once(encoder.finish())); } } diff --git a/examples/shader/compute_shader_game_of_life.rs b/examples/shader/compute_shader_game_of_life.rs index e9c3af5481eab..4259f1fb508eb 100644 --- a/examples/shader/compute_shader_game_of_life.rs +++ b/examples/shader/compute_shader_game_of_life.rs @@ -9,7 +9,6 @@ use bevy::{ render::{ extract_resource::{ExtractResource, ExtractResourcePlugin}, render_asset::RenderAssets, - render_graph::{self, RenderGraph, RenderLabel}, render_resource::{ binding_types::{texture_storage_2d, uniform_buffer}, *, @@ -20,6 +19,7 @@ use bevy::{ }, shader::PipelineCacheError, }; +use bevy_render::renderer::RenderGraph; use std::borrow::Cow; /// This example uses a shader source file from the assets subdirectory @@ -90,9 +90,6 @@ fn switch_textures(images: Res, mut sprite: Single<&mut Sprite struct GameOfLifeComputePlugin; -#[derive(Debug, Hash, PartialEq, Eq, Clone, RenderLabel)] -struct GameOfLifeLabel; - impl Plugin for GameOfLifeComputePlugin { fn build(&self, app: &mut App) { // Extract the game of life image resource from the main world into the render world @@ -103,15 +100,14 @@ impl Plugin for GameOfLifeComputePlugin { )); let render_app = app.sub_app_mut(RenderApp); render_app + .init_resource::() .add_systems(RenderStartup, init_game_of_life_pipeline) .add_systems( Render, prepare_bind_group.in_set(RenderSystems::PrepareBindGroups), - ); - - let mut render_graph = render_app.world_mut().resource_mut::(); - render_graph.add_node(GameOfLifeLabel, GameOfLifeNode::default()); - render_graph.add_node_edge(GameOfLifeLabel, bevy::render::graph::CameraDriverLabel); + ) + .add_systems(Render, update.in_set(RenderSystems::Prepare)) + .add_systems(RenderGraph, game_of_life); } } @@ -212,96 +208,80 @@ fn init_game_of_life_pipeline( }); } +#[derive(Resource, Default)] enum GameOfLifeState { + #[default] Loading, Init, Update(usize), } -struct GameOfLifeNode { - state: GameOfLifeState, -} - -impl Default for GameOfLifeNode { - fn default() -> Self { - Self { - state: GameOfLifeState::Loading, - } - } -} - -impl render_graph::Node for GameOfLifeNode { - fn update(&mut self, world: &mut World) { - let pipeline = world.resource::(); - let pipeline_cache = world.resource::(); - - // if the corresponding pipeline has loaded, transition to the next stage - match self.state { - GameOfLifeState::Loading => { - match pipeline_cache.get_compute_pipeline_state(pipeline.init_pipeline) { - CachedPipelineState::Ok(_) => { - self.state = GameOfLifeState::Init; - } - // If the shader hasn't loaded yet, just wait. - CachedPipelineState::Err(PipelineCacheError::ShaderNotLoaded(_)) => {} - CachedPipelineState::Err(err) => { - panic!("Initializing assets/{SHADER_ASSET_PATH}:\n{err}") - } - _ => {} +fn update( + pipeline: Res, + pipeline_cache: Res, + mut state: ResMut, +) { + // if the corresponding pipeline has loaded, transition to the next stage + match *state { + GameOfLifeState::Loading => { + match pipeline_cache.get_compute_pipeline_state(pipeline.init_pipeline) { + CachedPipelineState::Ok(_) => { + *state = GameOfLifeState::Init; } - } - GameOfLifeState::Init => { - if let CachedPipelineState::Ok(_) = - pipeline_cache.get_compute_pipeline_state(pipeline.update_pipeline) - { - self.state = GameOfLifeState::Update(1); + // If the shader hasn't loaded yet, just wait. + CachedPipelineState::Err(PipelineCacheError::ShaderNotLoaded(_)) => {} + CachedPipelineState::Err(err) => { + panic!("Initializing assets/{SHADER_ASSET_PATH}:\n{err}") } + _ => {} } - GameOfLifeState::Update(0) => { - self.state = GameOfLifeState::Update(1); - } - GameOfLifeState::Update(1) => { - self.state = GameOfLifeState::Update(0); + } + GameOfLifeState::Init => { + if let CachedPipelineState::Ok(_) = + pipeline_cache.get_compute_pipeline_state(pipeline.update_pipeline) + { + *state = GameOfLifeState::Update(1); } - GameOfLifeState::Update(_) => unreachable!(), } + GameOfLifeState::Update(0) => { + *state = GameOfLifeState::Update(1); + } + GameOfLifeState::Update(1) => { + *state = GameOfLifeState::Update(0); + } + GameOfLifeState::Update(_) => unreachable!(), } +} - fn run( - &self, - _graph: &mut render_graph::RenderGraphContext, - render_context: &mut RenderContext, - world: &World, - ) -> Result<(), render_graph::NodeRunError> { - let bind_groups = &world.resource::().0; - let pipeline_cache = world.resource::(); - let pipeline = world.resource::(); - - let mut pass = render_context - .command_encoder() - .begin_compute_pass(&ComputePassDescriptor::default()); +fn game_of_life( + mut render_context: RenderContext, + bind_groups: Res, + pipeline_cache: Res, + pipeline: Res, + state: Res, +) { + let mut pass = render_context + .command_encoder() + .begin_compute_pass(&ComputePassDescriptor::default()); - // select the pipeline based on the current state - match self.state { - GameOfLifeState::Loading => {} - GameOfLifeState::Init => { - let init_pipeline = pipeline_cache - .get_compute_pipeline(pipeline.init_pipeline) - .unwrap(); - pass.set_bind_group(0, &bind_groups[0], &[]); - pass.set_pipeline(init_pipeline); - pass.dispatch_workgroups(SIZE.x / WORKGROUP_SIZE, SIZE.y / WORKGROUP_SIZE, 1); - } - GameOfLifeState::Update(index) => { - let update_pipeline = pipeline_cache - .get_compute_pipeline(pipeline.update_pipeline) - .unwrap(); - pass.set_bind_group(0, &bind_groups[index], &[]); - pass.set_pipeline(update_pipeline); - pass.dispatch_workgroups(SIZE.x / WORKGROUP_SIZE, SIZE.y / WORKGROUP_SIZE, 1); - } + // select the pipeline based on the current state + match *state { + GameOfLifeState::Loading => {} + GameOfLifeState::Init => { + let init_pipeline = pipeline_cache + .get_compute_pipeline(pipeline.init_pipeline) + .unwrap(); + pass.set_bind_group(0, &bind_groups.0[0], &[]); + pass.set_pipeline(init_pipeline); + pass.dispatch_workgroups(SIZE.x / WORKGROUP_SIZE, SIZE.y / WORKGROUP_SIZE, 1); + } + GameOfLifeState::Update(index) => { + let update_pipeline = pipeline_cache + .get_compute_pipeline(pipeline.update_pipeline) + .unwrap(); + pass.set_bind_group(0, &bind_groups.0[index], &[]); + pass.set_pipeline(update_pipeline); + pass.dispatch_workgroups(SIZE.x / WORKGROUP_SIZE, SIZE.y / WORKGROUP_SIZE, 1); } - - Ok(()) } } diff --git a/examples/shader/gpu_readback.rs b/examples/shader/gpu_readback.rs index 78b5393658304..641720c12d5a9 100644 --- a/examples/shader/gpu_readback.rs +++ b/examples/shader/gpu_readback.rs @@ -8,7 +8,6 @@ use bevy::{ extract_resource::{ExtractResource, ExtractResourcePlugin}, gpu_readback::{Readback, ReadbackComplete}, render_asset::RenderAssets, - render_graph::{self, RenderGraph, RenderLabel}, render_resource::{ binding_types::{storage_buffer, texture_storage_2d}, *, @@ -19,6 +18,7 @@ use bevy::{ Render, RenderApp, RenderStartup, RenderSystems, }, }; +use bevy_render::renderer::RenderGraph; /// This example uses a shader source file from the assets subdirectory const SHADER_ASSET_PATH: &str = "shaders/gpu_readback.wgsl"; @@ -47,17 +47,15 @@ impl Plugin for GpuReadbackPlugin { return; }; render_app - .add_systems( - RenderStartup, - (init_compute_pipeline, add_compute_render_graph_node), - ) + .add_systems(RenderStartup, init_compute_pipeline) .add_systems( Render, prepare_bind_group .in_set(RenderSystems::PrepareBindGroups) // We don't need to recreate the bind group every frame .run_if(not(resource_exists::)), - ); + ) + .add_systems(RenderGraph, compute); } } @@ -139,13 +137,6 @@ fn setup( commands.insert_resource(ReadbackImage(image)); } -fn add_compute_render_graph_node(mut render_graph: ResMut) { - // Add the compute node as a top-level node to the render graph. This means it will only execute - // once per frame. Normally, adding a node would use the `RenderGraphApp::add_render_graph_node` - // method, but it does not allow adding as a top-level node. - render_graph.add_node(ComputeNodeLabel, ComputeNode::default()); -} - #[derive(Resource)] struct GpuBufferBindGroup(BindGroup); @@ -203,38 +194,23 @@ fn init_compute_pipeline( commands.insert_resource(ComputePipeline { layout, pipeline }); } -/// Label to identify the node in the render graph -#[derive(Debug, Hash, PartialEq, Eq, Clone, RenderLabel)] -struct ComputeNodeLabel; - -/// The node that will execute the compute shader -#[derive(Default)] -struct ComputeNode {} - -impl render_graph::Node for ComputeNode { - fn run( - &self, - _graph: &mut render_graph::RenderGraphContext, - render_context: &mut RenderContext, - world: &World, - ) -> Result<(), render_graph::NodeRunError> { - let pipeline_cache = world.resource::(); - let pipeline = world.resource::(); - let bind_group = world.resource::(); - - if let Some(init_pipeline) = pipeline_cache.get_compute_pipeline(pipeline.pipeline) { - let mut pass = - render_context - .command_encoder() - .begin_compute_pass(&ComputePassDescriptor { - label: Some("GPU readback compute pass"), - ..default() - }); - - pass.set_bind_group(0, &bind_group.0, &[]); - pass.set_pipeline(init_pipeline); - pass.dispatch_workgroups(BUFFER_LEN as u32, 1, 1); - } - Ok(()) +fn compute( + mut render_context: RenderContext, + pipeline_cache: Res, + pipeline: Res, + bind_group: Res, +) { + if let Some(init_pipeline) = pipeline_cache.get_compute_pipeline(pipeline.pipeline) { + let mut pass = + render_context + .command_encoder() + .begin_compute_pass(&ComputePassDescriptor { + label: Some("GPU readback compute pass"), + ..default() + }); + + pass.set_bind_group(0, &bind_group.0, &[]); + pass.set_pipeline(init_pipeline); + pass.dispatch_workgroups(BUFFER_LEN as u32, 1, 1); } } From 6342c3105d2145002645d28efc085c0d6afde7cf Mon Sep 17 00:00:00 2001 From: atlas dostal Date: Tue, 16 Dec 2025 11:23:03 -0500 Subject: [PATCH 11/55] example imports --- examples/3d/occlusion_culling.rs | 3 +-- examples/app/headless_renderer.rs | 3 +-- examples/shader/compute_shader_game_of_life.rs | 3 +-- examples/shader/gpu_readback.rs | 3 +-- 4 files changed, 4 insertions(+), 8 deletions(-) diff --git a/examples/3d/occlusion_culling.rs b/examples/3d/occlusion_culling.rs index 650dd6335de37..83ad23c77b4f7 100644 --- a/examples/3d/occlusion_culling.rs +++ b/examples/3d/occlusion_culling.rs @@ -23,12 +23,11 @@ use bevy::{ }, experimental::occlusion_culling::OcclusionCulling, render_resource::{Buffer, BufferDescriptor, BufferUsages, MapMode}, - renderer::{RenderContext, RenderDevice}, + renderer::{RenderContext, RenderDevice, RenderGraph}, settings::WgpuFeatures, Render, RenderApp, RenderDebugFlags, RenderPlugin, RenderStartup, RenderSystems, }, }; -use bevy_render::renderer::RenderGraph; use bytemuck::Pod; /// The radius of the spinning sphere of cubes. diff --git a/examples/app/headless_renderer.rs b/examples/app/headless_renderer.rs index 78fe27f161462..58aafc185ed53 100644 --- a/examples/app/headless_renderer.rs +++ b/examples/app/headless_renderer.rs @@ -24,13 +24,12 @@ use bevy::{ Buffer, BufferDescriptor, BufferUsages, CommandEncoderDescriptor, Extent3d, MapMode, PollType, TexelCopyBufferInfo, TexelCopyBufferLayout, TextureFormat, TextureUsages, }, - renderer::{RenderContext, RenderDevice, RenderQueue}, + renderer::{RenderContext, RenderDevice, RenderGraph, RenderQueue}, Extract, Render, RenderApp, RenderSystems, }, window::ExitCondition, winit::WinitPlugin, }; -use bevy_render::renderer::RenderGraph; use crossbeam_channel::{Receiver, Sender}; use std::{ ops::{Deref, DerefMut}, diff --git a/examples/shader/compute_shader_game_of_life.rs b/examples/shader/compute_shader_game_of_life.rs index 4259f1fb508eb..a0dadc129ef84 100644 --- a/examples/shader/compute_shader_game_of_life.rs +++ b/examples/shader/compute_shader_game_of_life.rs @@ -13,13 +13,12 @@ use bevy::{ binding_types::{texture_storage_2d, uniform_buffer}, *, }, - renderer::{RenderContext, RenderDevice, RenderQueue}, + renderer::{RenderContext, RenderDevice, RenderGraph, RenderQueue}, texture::GpuImage, Render, RenderApp, RenderStartup, RenderSystems, }, shader::PipelineCacheError, }; -use bevy_render::renderer::RenderGraph; use std::borrow::Cow; /// This example uses a shader source file from the assets subdirectory diff --git a/examples/shader/gpu_readback.rs b/examples/shader/gpu_readback.rs index 641720c12d5a9..4c96834d15254 100644 --- a/examples/shader/gpu_readback.rs +++ b/examples/shader/gpu_readback.rs @@ -12,13 +12,12 @@ use bevy::{ binding_types::{storage_buffer, texture_storage_2d}, *, }, - renderer::{RenderContext, RenderDevice}, + renderer::{RenderContext, RenderDevice, RenderGraph}, storage::{GpuShaderStorageBuffer, ShaderStorageBuffer}, texture::GpuImage, Render, RenderApp, RenderStartup, RenderSystems, }, }; -use bevy_render::renderer::RenderGraph; /// This example uses a shader source file from the assets subdirectory const SHADER_ASSET_PATH: &str = "shaders/gpu_readback.wgsl"; From b83663bfcb2ddfb86d4a581e24c8dd40152bba5f Mon Sep 17 00:00:00 2001 From: atlas dostal Date: Tue, 16 Dec 2025 11:52:29 -0500 Subject: [PATCH 12/55] fix --- crates/bevy_pbr/src/lib.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/crates/bevy_pbr/src/lib.rs b/crates/bevy_pbr/src/lib.rs index 3a83a41c19495..f466b13916bb9 100644 --- a/crates/bevy_pbr/src/lib.rs +++ b/crates/bevy_pbr/src/lib.rs @@ -314,7 +314,6 @@ impl Plugin for PbrPlugin { ( early_shadow_pass .after(early_prepass_build_indirect_parameters) - .after(Core3dSystems::EndPrepasses) .before(late_shadow_pass), late_shadow_pass .after(late_prepass_build_indirect_parameters) From 790a86d5b6984bcbca708b3b8d0b00d9148f4757 Mon Sep 17 00:00:00 2001 From: atlas dostal Date: Tue, 16 Dec 2025 12:02:21 -0500 Subject: [PATCH 13/55] fix guides --- release-content/migration-guides/entities_apis.md | 2 +- release-content/release-notes/camera_controllers.md | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/release-content/migration-guides/entities_apis.md b/release-content/migration-guides/entities_apis.md index 40d67d342f00a..ebc475c69c465 100644 --- a/release-content/migration-guides/entities_apis.md +++ b/release-content/migration-guides/entities_apis.md @@ -11,7 +11,7 @@ For a full explanation of the new entity paradigm, errors and terms, see the new If you want more background for the justification of these changes or more information about where these new terms come from, see pr #19451. This opens up a lot of room for performance improvement but also caused a lot of breaking changes: -### `Entities` rework +## `Entities` rework A lot has changed here. First, `alloc`, `free`, `reserve`, `reserve_entity`, `reserve_entities`, `flush`, `flush_as_invalid`, `EntityDoesNotExistError`, `total_count`, `used_count`, and `total_prospective_count` have all been removed 😱. diff --git a/release-content/release-notes/camera_controllers.md b/release-content/release-notes/camera_controllers.md index 599ac905bf9dc..87d544b39c965 100644 --- a/release-content/release-notes/camera_controllers.md +++ b/release-content/release-notes/camera_controllers.md @@ -20,7 +20,7 @@ To that end, we've created `bevy_camera_controller`: giving us a place to store, that we need for easy development, and yes, an eventual Editor. We're kicking it off with a couple of camera controllers, detailed below. -### `FreeCamera` +## `FreeCamera` The first camera controller that we've introduced is a "free camera", designed for quickly moving around a scene, completely ignoring both physics and geometry. @@ -34,7 +34,7 @@ To configure the settings (speed, behavior, keybindings) or enable / disable the We've done our best to select good defaults, but the details of your scene (especially the scale!) will make a big difference to what feels right. -### `PanCamera` +## `PanCamera` The `PanCamera` controller is a simple and effective tool designed for 2D games or any project where you need to pan the camera and zoom in/out with ease. It allows you to move the camera using the WASD keys and zoom @@ -47,7 +47,7 @@ To configure the camera's zoom levels, speed, or keybindings, simply modify the settings should work well for most use cases, but you can adjust them based on your specific needs, especially for large-scale or high-resolution 2D scenes. -### Using `bevy_camera_controller` in your own projects +## Using `bevy_camera_controller` in your own projects The provided camera controllers are designed to be functional, pleasant debug and dev tools: add the correct plugin and camera component and you're good to go! From 1958963730c6790f4240b2e15e032e540046a6d6 Mon Sep 17 00:00:00 2001 From: atlas dostal Date: Tue, 16 Dec 2025 12:50:36 -0500 Subject: [PATCH 14/55] tweaks --- crates/bevy_core_pipeline/src/schedule.rs | 1 + crates/bevy_pbr/src/deferred/mod.rs | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/crates/bevy_core_pipeline/src/schedule.rs b/crates/bevy_core_pipeline/src/schedule.rs index a85e3b3032a39..166f87ec5bf32 100644 --- a/crates/bevy_core_pipeline/src/schedule.rs +++ b/crates/bevy_core_pipeline/src/schedule.rs @@ -77,6 +77,7 @@ impl Core2d { let mut schedule = Schedule::new(Self); schedule.set_build_settings(ScheduleBuildSettings { + auto_insert_apply_deferred: false, ..Default::default() }); diff --git a/crates/bevy_pbr/src/deferred/mod.rs b/crates/bevy_pbr/src/deferred/mod.rs index d443c5d0fd633..be3676b326b0c 100644 --- a/crates/bevy_pbr/src/deferred/mod.rs +++ b/crates/bevy_pbr/src/deferred/mod.rs @@ -547,7 +547,7 @@ pub fn prepare_deferred_lighting_pipelines( } } -/// Component to skip running the deferred lighting pass in [`DeferredOpaquePass3dPbrLightingNode`] for a specific view. +/// Component to skip running the deferred lighting pass in [`deferred_lighting`] for a specific view. /// /// This works like [`crate::PbrPlugin::add_default_deferred_lighting_plugin`], but is per-view instead of global. /// From 6651227eddda041c24ce1d73d6fabe076c9019bf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?charlotte=20=F0=9F=8C=B8?= Date: Mon, 12 Jan 2026 22:28:04 -0800 Subject: [PATCH 15/55] Re-move for diff. --- .../src/contrast_adaptive_sharpening/node.rs | 2 +- .../core_3d/main_transmissive_pass_3d_node.rs | 54 ++--- .../src/deferred/copy_lighting_id.rs | 94 ++++---- .../src/mip_generation/experimental/depth.rs | 91 ++++---- .../src/mip_generation/mod.rs | 201 ++++++++---------- crates/bevy_pbr/src/render/gpu_preprocess.rs | 3 +- .../bevy_post_process/src/effect_stack/mod.rs | 4 +- crates/bevy_render/src/diagnostic/mod.rs | 18 ++ crates/bevy_solari/src/realtime/node.rs | 13 +- 9 files changed, 248 insertions(+), 232 deletions(-) diff --git a/crates/bevy_anti_alias/src/contrast_adaptive_sharpening/node.rs b/crates/bevy_anti_alias/src/contrast_adaptive_sharpening/node.rs index 2d71bfbed191c..461ecb0457ea2 100644 --- a/crates/bevy_anti_alias/src/contrast_adaptive_sharpening/node.rs +++ b/crates/bevy_anti_alias/src/contrast_adaptive_sharpening/node.rs @@ -52,7 +52,7 @@ pub(crate) fn cas( cached => { let bind_group = ctx.render_device().create_bind_group( "cas_bind_group", - &pipeline_cache.get_bind_group_layout(&sharpening_pipeline.texture_bind_group), + &pipeline_cache.get_bind_group_layout(&sharpening_pipeline.layout), &BindGroupEntries::sequential(( view_target.source, &sharpening_pipeline.sampler, diff --git a/crates/bevy_core_pipeline/src/core_3d/main_transmissive_pass_3d_node.rs b/crates/bevy_core_pipeline/src/core_3d/main_transmissive_pass_3d_node.rs index b15ad83c0e024..adc24e47e46f8 100644 --- a/crates/bevy_core_pipeline/src/core_3d/main_transmissive_pass_3d_node.rs +++ b/crates/bevy_core_pipeline/src/core_3d/main_transmissive_pass_3d_node.rs @@ -16,33 +16,6 @@ use tracing::error; #[cfg(feature = "trace")] use tracing::info_span; -/// Splits a [`Range`] into at most `max_num_splits` sub-ranges without overlaps -/// -/// Properly takes into account remainders of inexact divisions (by adding extra -/// elements to the initial sub-ranges as needed) -fn split_range(range: Range, max_num_splits: usize) -> impl Iterator> { - let len = range.end - range.start; - assert!(len > 0, "to be split, a range must not be empty"); - assert!(max_num_splits > 0, "max_num_splits must be at least 1"); - let num_splits = max_num_splits.min(len); - let step = len / num_splits; - let mut rem = len % num_splits; - let mut start = range.start; - - (0..num_splits).map(move |_| { - let extra = if rem > 0 { - rem -= 1; - 1 - } else { - 0 - }; - let end = (start + step + extra).min(range.end); - let result = start..end; - start = end; - result - }) -} - pub fn main_transmissive_pass_3d( world: &World, view: ViewQuery<( @@ -139,3 +112,30 @@ pub fn main_transmissive_pass_3d( } } } + +/// Splits a [`Range`] into at most `max_num_splits` sub-ranges without overlaps +/// +/// Properly takes into account remainders of inexact divisions (by adding extra +/// elements to the initial sub-ranges as needed) +fn split_range(range: Range, max_num_splits: usize) -> impl Iterator> { + let len = range.end - range.start; + assert!(len > 0, "to be split, a range must not be empty"); + assert!(max_num_splits > 0, "max_num_splits must be at least 1"); + let num_splits = max_num_splits.min(len); + let step = len / num_splits; + let mut rem = len % num_splits; + let mut start = range.start; + + (0..num_splits).map(move |_| { + let extra = if rem > 0 { + rem -= 1; + 1 + } else { + 0 + }; + let end = (start + step + extra).min(range.end); + let result = start..end; + start = end; + result + }) +} diff --git a/crates/bevy_core_pipeline/src/deferred/copy_lighting_id.rs b/crates/bevy_core_pipeline/src/deferred/copy_lighting_id.rs index c57bcf2d55e04..41391df9ee2f9 100644 --- a/crates/bevy_core_pipeline/src/deferred/copy_lighting_id.rs +++ b/crates/bevy_core_pipeline/src/deferred/copy_lighting_id.rs @@ -36,6 +36,53 @@ impl Plugin for CopyDeferredLightingIdPlugin { } } +pub(crate) fn copy_deferred_lighting_id( + view: ViewQuery<( + &ViewTarget, + &ViewPrepassTextures, + &DeferredLightingIdDepthTexture, + )>, + copy_pipeline: Res, + pipeline_cache: Res, + mut ctx: RenderContext, +) { + let (_view_target, view_prepass_textures, deferred_lighting_id_depth_texture) = + view.into_inner(); + + let Some(pipeline) = pipeline_cache.get_render_pipeline(copy_pipeline.pipeline_id) else { + return; + }; + let Some(deferred_lighting_pass_id_texture) = &view_prepass_textures.deferred_lighting_pass_id + else { + return; + }; + + let bind_group = ctx.render_device().create_bind_group( + "copy_deferred_lighting_id_bind_group", + &pipeline_cache.get_bind_group_layout(©_pipeline.layout), + &BindGroupEntries::single(&deferred_lighting_pass_id_texture.texture.default_view), + ); + + let mut render_pass = ctx.begin_tracked_render_pass(RenderPassDescriptor { + label: Some("copy_deferred_lighting_id"), + color_attachments: &[], + depth_stencil_attachment: Some(RenderPassDepthStencilAttachment { + view: &deferred_lighting_id_depth_texture.texture.default_view, + depth_ops: Some(Operations { + load: LoadOp::Clear(0.0), + store: StoreOp::Store, + }), + stencil_ops: None, + }), + timestamp_writes: None, + occlusion_query_set: None, + }); + + render_pass.set_render_pipeline(pipeline); + render_pass.set_bind_group(0, &bind_group, &[]); + render_pass.draw(0..3, 0..1); +} + #[derive(Resource)] pub(crate) struct CopyDeferredLightingIdPipeline { layout: BindGroupLayoutDescriptor, @@ -113,50 +160,3 @@ fn prepare_deferred_lighting_id_textures( } } } - -pub(crate) fn copy_deferred_lighting_id( - view: ViewQuery<( - &ViewTarget, - &ViewPrepassTextures, - &DeferredLightingIdDepthTexture, - )>, - copy_pipeline: Res, - pipeline_cache: Res, - mut ctx: RenderContext, -) { - let (_view_target, view_prepass_textures, deferred_lighting_id_depth_texture) = - view.into_inner(); - - let Some(pipeline) = pipeline_cache.get_render_pipeline(copy_pipeline.pipeline_id) else { - return; - }; - let Some(deferred_lighting_pass_id_texture) = &view_prepass_textures.deferred_lighting_pass_id - else { - return; - }; - - let bind_group = ctx.render_device().create_bind_group( - "copy_deferred_lighting_id_bind_group", - &pipeline_cache.get_bind_group_layout(©_pipeline.layout), - &BindGroupEntries::single(&deferred_lighting_pass_id_texture.texture.default_view), - ); - - let mut render_pass = ctx.begin_tracked_render_pass(RenderPassDescriptor { - label: Some("copy_deferred_lighting_id"), - color_attachments: &[], - depth_stencil_attachment: Some(RenderPassDepthStencilAttachment { - view: &deferred_lighting_id_depth_texture.texture.default_view, - depth_ops: Some(Operations { - load: LoadOp::Clear(0.0), - store: StoreOp::Store, - }), - stencil_ops: None, - }), - timestamp_writes: None, - occlusion_query_set: None, - }); - - render_pass.set_render_pipeline(pipeline); - render_pass.set_bind_group(0, &bind_group, &[]); - render_pass.draw(0..3, 0..1); -} diff --git a/crates/bevy_core_pipeline/src/mip_generation/experimental/depth.rs b/crates/bevy_core_pipeline/src/mip_generation/experimental/depth.rs index 98005acc46f6f..767959d4ad08e 100644 --- a/crates/bevy_core_pipeline/src/mip_generation/experimental/depth.rs +++ b/crates/bevy_core_pipeline/src/mip_generation/experimental/depth.rs @@ -67,14 +67,52 @@ pub fn early_downsample_depth( pipeline_cache: Res, mut ctx: RenderContext, ) { - run_downsample_depth_system( + let Some(downsample_depth_pipelines) = downsample_depth_pipelines.as_deref() else { + return; + }; + + let ( + view_depth_pyramid, + view_downsample_depth_bind_group, + view_depth_texture, + maybe_view_light_entities, + ) = view.into_inner(); + + // Downsample depth for the main Z-buffer. + downsample_depth( "early_downsample_depth", - view.into_inner(), - &shadow_view_query, - downsample_depth_pipelines.as_deref(), - &pipeline_cache, &mut ctx, + downsample_depth_pipelines, + &pipeline_cache, + view_depth_pyramid, + view_downsample_depth_bind_group, + uvec2( + view_depth_texture.texture.width(), + view_depth_texture.texture.height(), + ), + view_depth_texture.texture.sample_count(), ); + + // Downsample depth for shadow maps that have occlusion culling enabled. + if let Some(view_light_entities) = maybe_view_light_entities { + for &view_light_entity in &view_light_entities.0 { + let Ok((view_depth_pyramid, view_downsample_depth_bind_group, occlusion_culling)) = + shadow_view_query.get(view_light_entity) + else { + continue; + }; + downsample_depth( + "early_downsample_depth", + &mut ctx, + downsample_depth_pipelines, + &pipeline_cache, + view_depth_pyramid, + view_downsample_depth_bind_group, + UVec2::splat(occlusion_culling.depth_texture_size), + 1, + ); + } + } } pub fn late_downsample_depth( @@ -93,34 +131,7 @@ pub fn late_downsample_depth( pipeline_cache: Res, mut ctx: RenderContext, ) { - run_downsample_depth_system( - "late_downsample_depth", - view.into_inner(), - &shadow_view_query, - downsample_depth_pipelines.as_deref(), - &pipeline_cache, - &mut ctx, - ); -} - -fn run_downsample_depth_system( - label: &'static str, - main_view_data: ( - &ViewDepthPyramid, - &ViewDownsampleDepthBindGroup, - &ViewDepthTexture, - Option<&OcclusionCullingSubviewEntities>, - ), - shadow_view_query: &Query<( - &ViewDepthPyramid, - &ViewDownsampleDepthBindGroup, - &OcclusionCullingSubview, - )>, - downsample_depth_pipelines: Option<&DownsampleDepthPipelines>, - pipeline_cache: &PipelineCache, - ctx: &mut RenderContext, -) { - let Some(downsample_depth_pipelines) = downsample_depth_pipelines else { + let Some(downsample_depth_pipelines) = downsample_depth_pipelines.as_deref() else { return; }; @@ -129,14 +140,14 @@ fn run_downsample_depth_system( view_downsample_depth_bind_group, view_depth_texture, maybe_view_light_entities, - ) = main_view_data; + ) = view.into_inner(); // Downsample depth for the main Z-buffer. downsample_depth( - label, - ctx, + "late_downsample_depth", + &mut ctx, downsample_depth_pipelines, - pipeline_cache, + &pipeline_cache, view_depth_pyramid, view_downsample_depth_bind_group, uvec2( @@ -155,10 +166,10 @@ fn run_downsample_depth_system( continue; }; downsample_depth( - label, - ctx, + "late_downsample_depth", + &mut ctx, downsample_depth_pipelines, - pipeline_cache, + &pipeline_cache, view_depth_pyramid, view_downsample_depth_bind_group, UVec2::splat(occlusion_culling.depth_texture_size), diff --git a/crates/bevy_core_pipeline/src/mip_generation/mod.rs b/crates/bevy_core_pipeline/src/mip_generation/mod.rs index 5b3b1c020c252..293605ba34662 100644 --- a/crates/bevy_core_pipeline/src/mip_generation/mod.rs +++ b/crates/bevy_core_pipeline/src/mip_generation/mod.rs @@ -46,7 +46,6 @@ use bevy_render::{ RenderStartup, }; use bevy_render::{ - render_graph::{Node, NodeRunError, RenderGraphContext}, render_resource::{ binding_types::{sampler, texture_2d, texture_storage_2d}, BindGroup, BindGroupEntries, BindGroupLayoutEntries, CachedComputePipelineId, @@ -371,122 +370,108 @@ impl FromWorld for MipGenerationResources { } } -/// A [`Node`] for use in the render graph that generates mipmaps for a single -/// [`MipGenerationPhaseId`]. +/// Generates mipmaps for all images in a [`MipGenerationPhaseId`]. /// -/// In order to execute a job in [`MipGenerationJobs`], a [`MipGenerationNode`] -/// for the phase that the job belongs to must be added to the -/// [`bevy_render::render_graph::RenderGraph`]. The phased nature of mipmap -/// generation allows precise control over the time when mipmaps are generated -/// for each image. Your application should use -/// [`bevy_render::render_graph::RenderGraph::add_node_edge`] to order each -/// [`MipGenerationNode`] relative to other systems so that the mipmaps will be -/// generated after any passes that *write* to the images in question but before -/// any shaders that *read* from those images execute. +/// This function should be called from within a render system to generate +/// mipmaps for all images that have been enqueued for the specified phase. +/// The phased nature of mipmap generation allows precise control over the time +/// when mipmaps are generated for each image. Your system should be ordered +/// so that the mipmaps will be generated after any passes that *write* to the +/// images in question but before any shaders that *read* from those images +/// execute. /// /// See `dynamic_mip_generation` for an example of use. -#[derive(Deref, DerefMut)] -pub struct MipGenerationNode(pub MipGenerationPhaseId); - -impl Node for MipGenerationNode { - fn run<'w>( - &self, - _: &mut RenderGraphContext, - render_context: &mut RenderContext<'w>, - world: &'w World, - ) -> Result<(), NodeRunError> { - let mip_generation_jobs = world.resource::(); - let Some(mip_generation_phase) = mip_generation_jobs.get(&self.0) else { - return Ok(()); - }; - if mip_generation_phase.is_empty() { - // Quickly bail out if there's nothing to do. - return Ok(()); - } - - let pipeline_cache = world.resource::(); - let mip_generation_bind_groups = world.resource::(); - let gpu_images = world.resource::>(); +pub fn generate_mips_for_phase( + phase_id: MipGenerationPhaseId, + mip_generation_jobs: &MipGenerationJobs, + pipeline_cache: &PipelineCache, + mip_generation_bind_groups: &MipGenerationPipelines, + gpu_images: &RenderAssets, + ctx: &mut RenderContext, +) { + let Some(mip_generation_phase) = mip_generation_jobs.get(&phase_id) else { + return; + }; + if mip_generation_phase.is_empty() { + // Quickly bail out if there's nothing to do. + return; + } - let diagnostics = render_context.diagnostic_recorder(); + let diagnostics = ctx.diagnostic_recorder(); + let diagnostics = diagnostics.as_deref(); - for mip_generation_job in mip_generation_phase.iter() { - let Some(gpu_image) = gpu_images.get(*mip_generation_job) else { - continue; - }; - let Some(mip_generation_job_bind_groups) = mip_generation_bind_groups - .bind_groups - .get(mip_generation_job) - else { - continue; - }; - let Some(mip_generation_pipelines) = mip_generation_bind_groups - .pipelines - .get(&gpu_image.texture_format) - else { - continue; - }; + for mip_generation_job in mip_generation_phase.iter() { + let Some(gpu_image) = gpu_images.get(*mip_generation_job) else { + continue; + }; + let Some(mip_generation_job_bind_groups) = mip_generation_bind_groups + .bind_groups + .get(mip_generation_job) + else { + continue; + }; + let Some(mip_generation_pipelines) = mip_generation_bind_groups + .pipelines + .get(&gpu_image.texture_format) + else { + continue; + }; - // Fetch the mip generation pipelines. - let (Some(mip_generation_pipeline_pass_1), Some(mip_generation_pipeline_pass_2)) = ( - pipeline_cache - .get_compute_pipeline(mip_generation_pipelines.downsampling_pipeline_pass_1), - pipeline_cache - .get_compute_pipeline(mip_generation_pipelines.downsampling_pipeline_pass_2), - ) else { - continue; - }; + // Fetch the mip generation pipelines. + let (Some(mip_generation_pipeline_pass_1), Some(mip_generation_pipeline_pass_2)) = ( + pipeline_cache + .get_compute_pipeline(mip_generation_pipelines.downsampling_pipeline_pass_1), + pipeline_cache + .get_compute_pipeline(mip_generation_pipelines.downsampling_pipeline_pass_2), + ) else { + continue; + }; - // Perform the first downsampling pass. - { - let mut compute_pass_1 = - render_context - .command_encoder() - .begin_compute_pass(&ComputePassDescriptor { - label: Some("mip generation pass 1"), - timestamp_writes: None, - }); - let pass_span = diagnostics.pass_span(&mut compute_pass_1, "mip generation pass 1"); - compute_pass_1.set_pipeline(mip_generation_pipeline_pass_1); - compute_pass_1.set_bind_group( - 0, - &mip_generation_job_bind_groups.downsampling_bind_group_pass_1, - &[], - ); - compute_pass_1.dispatch_workgroups( - gpu_image.size.width.div_ceil(64), - gpu_image.size.height.div_ceil(64), - 1, - ); - pass_span.end(&mut compute_pass_1); - } - - // Perform the second downsampling pass. - { - let mut compute_pass_2 = - render_context - .command_encoder() - .begin_compute_pass(&ComputePassDescriptor { - label: Some("mip generation pass 2"), - timestamp_writes: None, - }); - let pass_span = diagnostics.pass_span(&mut compute_pass_2, "mip generation pass 2"); - compute_pass_2.set_pipeline(mip_generation_pipeline_pass_2); - compute_pass_2.set_bind_group( - 0, - &mip_generation_job_bind_groups.downsampling_bind_group_pass_2, - &[], - ); - compute_pass_2.dispatch_workgroups( - gpu_image.size.width.div_ceil(256), - gpu_image.size.height.div_ceil(256), - 1, - ); - pass_span.end(&mut compute_pass_2); - } + // Perform the first downsampling pass. + { + let mut compute_pass_1 = + ctx.command_encoder() + .begin_compute_pass(&ComputePassDescriptor { + label: Some("mip generation pass 1"), + timestamp_writes: None, + }); + let pass_span = diagnostics.pass_span(&mut compute_pass_1, "mip generation pass 1"); + compute_pass_1.set_pipeline(mip_generation_pipeline_pass_1); + compute_pass_1.set_bind_group( + 0, + &mip_generation_job_bind_groups.downsampling_bind_group_pass_1, + &[], + ); + compute_pass_1.dispatch_workgroups( + gpu_image.size.width.div_ceil(64), + gpu_image.size.height.div_ceil(64), + 1, + ); + pass_span.end(&mut compute_pass_1); } - Ok(()) + // Perform the second downsampling pass. + { + let mut compute_pass_2 = + ctx.command_encoder() + .begin_compute_pass(&ComputePassDescriptor { + label: Some("mip generation pass 2"), + timestamp_writes: None, + }); + let pass_span = diagnostics.pass_span(&mut compute_pass_2, "mip generation pass 2"); + compute_pass_2.set_pipeline(mip_generation_pipeline_pass_2); + compute_pass_2.set_bind_group( + 0, + &mip_generation_job_bind_groups.downsampling_bind_group_pass_2, + &[], + ); + compute_pass_2.dispatch_workgroups( + gpu_image.size.width.div_ceil(256), + gpu_image.size.height.div_ceil(256), + 1, + ); + pass_span.end(&mut compute_pass_2); + } } } diff --git a/crates/bevy_pbr/src/render/gpu_preprocess.rs b/crates/bevy_pbr/src/render/gpu_preprocess.rs index 9c94d65e6a23e..1324585690f01 100644 --- a/crates/bevy_pbr/src/render/gpu_preprocess.rs +++ b/crates/bevy_pbr/src/render/gpu_preprocess.rs @@ -31,6 +31,7 @@ use bevy_ecs::{ }; use bevy_log::warn_once; use bevy_render::{ + diagnostic::RecordDiagnostics as _, batching::gpu_preprocessing::{ BatchedInstanceBuffers, GpuOcclusionCullingWorkItemBuffers, GpuPreprocessingMode, GpuPreprocessingSupport, IndirectBatchSet, IndirectParametersBuffers, @@ -57,7 +58,7 @@ use bevy_shader::Shader; use bevy_utils::{default, TypeIdMap}; use bitflags::bitflags; use smallvec::{smallvec, SmallVec}; -use tracing::{warn, warn_once}; +use tracing::warn; use crate::{MeshCullingData, MeshCullingDataBuffer, MeshInputUniform, MeshUniform}; diff --git a/crates/bevy_post_process/src/effect_stack/mod.rs b/crates/bevy_post_process/src/effect_stack/mod.rs index 626a754ee2ee0..7f999ae185093 100644 --- a/crates/bevy_post_process/src/effect_stack/mod.rs +++ b/crates/bevy_post_process/src/effect_stack/mod.rs @@ -40,7 +40,7 @@ use bevy_render::{ use bevy_shader::{load_shader_library, Shader}; use bevy_utils::prelude::default; -use crate::bloom::bloom; +use crate::{bloom::bloom, dof::depth_of_field}; use bevy_core_pipeline::{ schedule::{Core2d, Core3d}, tonemapping::tonemapping, @@ -216,7 +216,7 @@ impl Plugin for EffectStackPlugin { ) .in_set(RenderSystems::Prepare), ) - .add_systems(Core3d, post_processing.after(bloom).before(tonemapping)) + .add_systems(Core3d, post_processing.after(depth_of_field).before(tonemapping)) .add_systems(Core2d, post_processing.after(bloom).before(tonemapping)); } } diff --git a/crates/bevy_render/src/diagnostic/mod.rs b/crates/bevy_render/src/diagnostic/mod.rs index c37cf7f21bddb..61d6d165755b6 100644 --- a/crates/bevy_render/src/diagnostic/mod.rs +++ b/crates/bevy_render/src/diagnostic/mod.rs @@ -230,6 +230,24 @@ impl RecordDiagnostics for Option> { } impl<'a, T: RecordDiagnostics> RecordDiagnostics for Option<&'a T> { + fn record_f32(&self, command_encoder: &mut CommandEncoder, buffer: &BufferSlice, name: N) + where + N: Into>, + { + if let Some(recorder) = self { + recorder.record_f32(command_encoder, buffer, name); + } + } + + fn record_u32(&self, command_encoder: &mut CommandEncoder, buffer: &BufferSlice, name: N) + where + N: Into>, + { + if let Some(recorder) = self { + recorder.record_u32(command_encoder, buffer, name); + } + } + fn begin_time_span(&self, encoder: &mut E, name: Cow<'static, str>) { if let Some(recorder) = self { recorder.begin_time_span(encoder, name); diff --git a/crates/bevy_solari/src/realtime/node.rs b/crates/bevy_solari/src/realtime/node.rs index 1c1e9774f71b6..bce4a53319a76 100644 --- a/crates/bevy_solari/src/realtime/node.rs +++ b/crates/bevy_solari/src/realtime/node.rs @@ -12,6 +12,7 @@ use bevy_core_pipeline::prepass::{ use bevy_diagnostic::FrameCount; use bevy_ecs::{prelude::*, resource::Resource, system::Commands}; use bevy_render::{ + diagnostic::RecordDiagnostics as _, render_resource::{ binding_types::{ storage_buffer_sized, texture_2d, texture_depth_2d, texture_storage_2d, uniform_buffer, @@ -370,6 +371,9 @@ pub fn solari_lighting( // Choice of number here is arbitrary let frame_index = frame_count.0.wrapping_mul(5782582); + let diagnostics = ctx.diagnostic_recorder(); + let diagnostics = diagnostics.as_deref(); + let command_encoder = ctx.command_encoder(); // Clear the view target if we're the first node to write to it @@ -383,9 +387,6 @@ pub fn solari_lighting( }); } - let diagnostics = ctx.diagnostic_recorder(); - let diagnostics = diagnostics.as_deref(); - let mut pass = command_encoder.begin_compute_pass(&ComputePassDescriptor { label: Some("solari_lighting"), timestamp_writes: None, @@ -640,6 +641,9 @@ pub fn solari_lighting( // Choice of number here is arbitrary let frame_index = frame_count.0.wrapping_mul(5782582); + let diagnostics = ctx.diagnostic_recorder(); + let diagnostics = diagnostics.as_deref(); + let command_encoder = ctx.command_encoder(); // Clear the view target if we're the first node to write to it @@ -653,9 +657,6 @@ pub fn solari_lighting( }); } - let diagnostics = ctx.diagnostic_recorder(); - let diagnostics = diagnostics.as_deref(); - let mut pass = command_encoder.begin_compute_pass(&ComputePassDescriptor { label: Some("solari_lighting"), timestamp_writes: None, From ea05761c20a3f86b0345465211040153a7f2a6bd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?charlotte=20=F0=9F=8C=B8?= Date: Mon, 12 Jan 2026 22:32:21 -0800 Subject: [PATCH 16/55] Ci. --- .../src/mip_generation/mod.rs | 12 ++-- crates/bevy_pbr/src/render/gpu_preprocess.rs | 2 +- .../bevy_post_process/src/effect_stack/mod.rs | 5 +- crates/bevy_solari/src/realtime/node.rs | 2 +- examples/2d/dynamic_mip_generation.rs | 59 +++++++++++-------- 5 files changed, 45 insertions(+), 35 deletions(-) diff --git a/crates/bevy_core_pipeline/src/mip_generation/mod.rs b/crates/bevy_core_pipeline/src/mip_generation/mod.rs index 293605ba34662..8c67453f7676e 100644 --- a/crates/bevy_core_pipeline/src/mip_generation/mod.rs +++ b/crates/bevy_core_pipeline/src/mip_generation/mod.rs @@ -179,12 +179,12 @@ pub struct MipGenerationPhase(pub Vec>); /// scene. In this case, the mipmaps must be generated after the first camera /// renders to the image rendered to but before the second camera's rendering /// samples the image. To express these kinds of dependencies, you group images -/// into *phases* and schedule [`MipGenerationNode`]s in the render graph +/// into *phases* and schedule systems that call [`generate_mips_for_phase`] /// targeting each phase at the appropriate time. /// /// Each phase has an ID, which is an arbitrary 32-bit integer. You may specify -/// any value you wish as a phase ID, so long as the [`MipGenerationNode`] that -/// generates mipmaps for the images in that phase uses the same ID. +/// any value you wish as a phase ID, so long as the system that calls +/// [`generate_mips_for_phase`] uses the same ID. #[derive(Clone, Copy, PartialEq, Eq, Hash)] pub struct MipGenerationPhaseId(pub u32); @@ -194,18 +194,18 @@ pub struct MipGenerationPhaseId(pub u32); /// The `prepare_mip_generator_pipelines` system populates this resource lazily /// as new textures are scheduled. #[derive(Resource, Default)] -struct MipGenerationPipelines { +pub struct MipGenerationPipelines { /// The pipeline for each texture format. /// /// Note that pipelines can be shared among all images that use a single /// texture format. - pipelines: HashMap, + pub(crate) pipelines: HashMap, /// The bind group for each image. /// /// These are cached from frame to frame if the same image needs mips /// generated for it on immediately-consecutive frames. - bind_groups: HashMap, MipGenerationJobBindGroups>, + pub(crate) bind_groups: HashMap, MipGenerationJobBindGroups>, } /// The compute pipelines and bind group layouts for the single-pass diff --git a/crates/bevy_pbr/src/render/gpu_preprocess.rs b/crates/bevy_pbr/src/render/gpu_preprocess.rs index 1324585690f01..45df5491e893e 100644 --- a/crates/bevy_pbr/src/render/gpu_preprocess.rs +++ b/crates/bevy_pbr/src/render/gpu_preprocess.rs @@ -31,7 +31,6 @@ use bevy_ecs::{ }; use bevy_log::warn_once; use bevy_render::{ - diagnostic::RecordDiagnostics as _, batching::gpu_preprocessing::{ BatchedInstanceBuffers, GpuOcclusionCullingWorkItemBuffers, GpuPreprocessingMode, GpuPreprocessingSupport, IndirectBatchSet, IndirectParametersBuffers, @@ -40,6 +39,7 @@ use bevy_render::{ PreprocessWorkItemBuffers, UntypedPhaseBatchedInstanceBuffers, UntypedPhaseIndirectParametersBuffers, }, + diagnostic::RecordDiagnostics as _, experimental::occlusion_culling::OcclusionCulling, render_resource::{ binding_types::{storage_buffer, storage_buffer_read_only, texture_2d, uniform_buffer}, diff --git a/crates/bevy_post_process/src/effect_stack/mod.rs b/crates/bevy_post_process/src/effect_stack/mod.rs index 7f999ae185093..c376efaec0516 100644 --- a/crates/bevy_post_process/src/effect_stack/mod.rs +++ b/crates/bevy_post_process/src/effect_stack/mod.rs @@ -216,7 +216,10 @@ impl Plugin for EffectStackPlugin { ) .in_set(RenderSystems::Prepare), ) - .add_systems(Core3d, post_processing.after(depth_of_field).before(tonemapping)) + .add_systems( + Core3d, + post_processing.after(depth_of_field).before(tonemapping), + ) .add_systems(Core2d, post_processing.after(bloom).before(tonemapping)); } } diff --git a/crates/bevy_solari/src/realtime/node.rs b/crates/bevy_solari/src/realtime/node.rs index bce4a53319a76..3c64676ce85ac 100644 --- a/crates/bevy_solari/src/realtime/node.rs +++ b/crates/bevy_solari/src/realtime/node.rs @@ -89,7 +89,7 @@ pub fn init_solari_lighting_pipelines( storage_buffer_sized(false, None), storage_buffer_sized(false, None), storage_buffer_sized(false, None), - storage_buffer_sized(false, None), // world_cache_active_cells_count - added from main + storage_buffer_sized(false, None), // world_cache_active_cells_count - added from main ), ), ); diff --git a/examples/2d/dynamic_mip_generation.rs b/examples/2d/dynamic_mip_generation.rs index d5e11bcdced74..849287bfc3a19 100644 --- a/examples/2d/dynamic_mip_generation.rs +++ b/examples/2d/dynamic_mip_generation.rs @@ -14,13 +14,19 @@ use std::array; use bevy::{ asset::RenderAssetUsages, - core_pipeline::mip_generation::{MipGenerationJobs, MipGenerationNode, MipGenerationPhaseId}, + core_pipeline::{ + mip_generation::{ + generate_mips_for_phase, MipGenerationJobs, MipGenerationPhaseId, MipGenerationPipelines, + }, + schedule::Core2d, + }, prelude::*, reflect::TypePath, render::{ - graph::CameraDriverLabel, - render_graph::{RenderGraph, RenderLabel}, - render_resource::{AsBindGroup, Extent3d, TextureDimension, TextureFormat, TextureUsages}, + render_asset::RenderAssets, + render_resource::{AsBindGroup, Extent3d, PipelineCache, TextureDimension, TextureFormat, TextureUsages}, + renderer::RenderContext, + texture::GpuImage, Extract, RenderApp, }, shader::ShaderRef, @@ -181,12 +187,7 @@ struct MipmapSizeIterator { size: Option, } -/// A [`RenderLabel`] for the mipmap generation render node. -/// -/// This is needed in order to order the mipmap generation node relative to the -/// node that renders the image for which mipmaps have been generated. -#[derive(Clone, Copy, Hash, PartialEq, Eq, Debug, RenderLabel)] -struct MipGenerationLabel; +const MIP_GENERATION_PHASE_ID: MipGenerationPhaseId = MipGenerationPhaseId(0); /// A marker component for every mesh that displays the image. /// @@ -243,21 +244,7 @@ fn main() { let render_app = app.get_sub_app_mut(RenderApp).expect("Need a render app"); - // Add a `MipGenerationNode` corresponding to our phase to the render graph. - let mut render_graph = render_app.world_mut().resource_mut::(); - render_graph.add_node( - MipGenerationLabel, - MipGenerationNode(MipGenerationPhaseId(0)), - ); - - // Add an edge so that our mip generation node will run prior to rendering - // any cameras. - // If your mip generation node needs to run before some cameras and after - // others, you can use more complex constraints. Or, for more exotic - // scenarios, you can also create a custom render node that wraps a - // `MipGenerationNode` and examines properties of the camera to invoke the - // node at the appropriate time. - render_graph.add_node_edge(MipGenerationLabel, CameraDriverLabel); + render_app.add_systems(Core2d, generate_mips_for_example); // Add the system that adds the image into the `MipGenerationJobs` list. // Note that this must run as part of the extract schedule, because it needs @@ -267,6 +254,26 @@ fn main() { app.run(); } +fn generate_mips_for_example( + mip_generation_jobs: Res, + pipeline_cache: Res, + mip_generation_pipelines: Option>, + gpu_images: Res>, + mut ctx: RenderContext, +) { + let Some(mip_generation_pipelines) = mip_generation_pipelines else { + return; + }; + generate_mips_for_phase( + MIP_GENERATION_PHASE_ID, + &mip_generation_jobs, + &pipeline_cache, + &mip_generation_pipelines, + &gpu_images, + &mut ctx, + ); +} + /// Global assets used for this example. #[derive(Resource)] struct AppAssets { @@ -450,7 +457,7 @@ fn extract_mipmap_source_image( mut mip_generation_jobs: ResMut, ) { if app_status.enable_mip_generation == EnableMipGeneration::On { - mip_generation_jobs.add(MipGenerationPhaseId(0), mipmap_source_image.id()); + mip_generation_jobs.add(MIP_GENERATION_PHASE_ID, mipmap_source_image.id()); } } From ca6343db0fe6d75c3760690011225bf0807f5474 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?charlotte=20=F0=9F=8C=B8?= Date: Wed, 14 Jan 2026 13:33:31 -0800 Subject: [PATCH 17/55] Fix occlusion culling. --- crates/bevy_core_pipeline/src/deferred/node.rs | 2 +- crates/bevy_core_pipeline/src/mip_generation/mod.rs | 4 ++-- crates/bevy_pbr/src/lib.rs | 2 ++ crates/bevy_pbr/src/render/gpu_preprocess.rs | 8 ++------ examples/3d/occlusion_culling.rs | 6 +++--- 5 files changed, 10 insertions(+), 12 deletions(-) diff --git a/crates/bevy_core_pipeline/src/deferred/node.rs b/crates/bevy_core_pipeline/src/deferred/node.rs index 7494b1a56cc75..6c6fa502cc994 100644 --- a/crates/bevy_core_pipeline/src/deferred/node.rs +++ b/crates/bevy_core_pipeline/src/deferred/node.rs @@ -64,7 +64,7 @@ pub(crate) fn early_deferred_prepass( ); } -pub(crate) fn late_deferred_prepass( +pub fn late_deferred_prepass( world: &World, view: ViewQuery, opaque_deferred_phases: Res>, diff --git a/crates/bevy_core_pipeline/src/mip_generation/mod.rs b/crates/bevy_core_pipeline/src/mip_generation/mod.rs index 8c67453f7676e..825e04e533cc4 100644 --- a/crates/bevy_core_pipeline/src/mip_generation/mod.rs +++ b/crates/bevy_core_pipeline/src/mip_generation/mod.rs @@ -199,13 +199,13 @@ pub struct MipGenerationPipelines { /// /// Note that pipelines can be shared among all images that use a single /// texture format. - pub(crate) pipelines: HashMap, + pipelines: HashMap, /// The bind group for each image. /// /// These are cached from frame to frame if the same image needs mips /// generated for it on immediately-consecutive frames. - pub(crate) bind_groups: HashMap, MipGenerationJobBindGroups>, + bind_groups: HashMap, MipGenerationJobBindGroups>, } /// The compute pipelines and bind group layouts for the single-pass diff --git a/crates/bevy_pbr/src/lib.rs b/crates/bevy_pbr/src/lib.rs index f466b13916bb9..2142a74809065 100644 --- a/crates/bevy_pbr/src/lib.rs +++ b/crates/bevy_pbr/src/lib.rs @@ -91,6 +91,7 @@ pub mod prelude { use crate::deferred::DeferredPbrLightingPlugin; use bevy_app::prelude::*; use bevy_asset::{AssetApp, AssetPath, Assets, Handle, RenderAssetUsages}; +use bevy_core_pipeline::mip_generation::experimental::depth::early_downsample_depth; use bevy_core_pipeline::schedule::{Core3d, Core3dSystems}; use bevy_ecs::prelude::*; #[cfg(feature = "bluenoise_texture")] @@ -314,6 +315,7 @@ impl Plugin for PbrPlugin { ( early_shadow_pass .after(early_prepass_build_indirect_parameters) + .before(early_downsample_depth) .before(late_shadow_pass), late_shadow_pass .after(late_prepass_build_indirect_parameters) diff --git a/crates/bevy_pbr/src/render/gpu_preprocess.rs b/crates/bevy_pbr/src/render/gpu_preprocess.rs index 45df5491e893e..a479d5b161d4c 100644 --- a/crates/bevy_pbr/src/render/gpu_preprocess.rs +++ b/crates/bevy_pbr/src/render/gpu_preprocess.rs @@ -11,6 +11,7 @@ use core::num::{NonZero, NonZeroU64}; use bevy_app::{App, Plugin}; use bevy_asset::{embedded_asset, load_embedded_asset, Handle}; use bevy_core_pipeline::{ + deferred::node::late_deferred_prepass, mip_generation::experimental::depth::{early_downsample_depth, ViewDepthPyramid}, prepass::{ node::{early_prepass, late_prepass}, @@ -363,27 +364,22 @@ impl Plugin for GpuMeshPreprocessPlugin { .add_systems( Core3d, ( - // Clear indirect parameters metadata first, before early prepass clear_indirect_parameters_metadata.before(early_prepass), - // Early GPU preprocess runs before early prepasses early_gpu_preprocess .after(clear_indirect_parameters_metadata) .before(early_prepass), - // Early prepass build indirect parameters early_prepass_build_indirect_parameters .after(early_gpu_preprocess) .before(early_prepass), - // Late GPU preprocess runs after early prepasses, before late prepasses late_gpu_preprocess .after(early_downsample_depth) .before(late_prepass), - // Late prepass build indirect parameters late_prepass_build_indirect_parameters .after(late_gpu_preprocess) .before(late_prepass), - // Main build indirect parameters runs before main pass main_build_indirect_parameters .after(late_prepass_build_indirect_parameters) + .after(late_deferred_prepass) .before(Core3dSystems::StartMainPass), ), ); diff --git a/examples/3d/occlusion_culling.rs b/examples/3d/occlusion_culling.rs index 83ad23c77b4f7..f48601154fa4f 100644 --- a/examples/3d/occlusion_culling.rs +++ b/examples/3d/occlusion_culling.rs @@ -14,7 +14,7 @@ use std::{ use bevy::{ color::palettes::css::{SILVER, WHITE}, - core_pipeline::{core_3d::Opaque3d, prepass::DepthPrepass, Core3dSystems}, + core_pipeline::{core_3d::Opaque3d, prepass::DepthPrepass, Core3d, Core3dSystems}, pbr::PbrPlugin, prelude::*, render::{ @@ -23,7 +23,7 @@ use bevy::{ }, experimental::occlusion_culling::OcclusionCulling, render_resource::{Buffer, BufferDescriptor, BufferUsages, MapMode}, - renderer::{RenderContext, RenderDevice, RenderGraph}, + renderer::{RenderContext, RenderDevice}, settings::WgpuFeatures, Render, RenderApp, RenderDebugFlags, RenderPlugin, RenderStartup, RenderSystems, }, @@ -222,7 +222,7 @@ impl Plugin for ReadbackIndirectParametersPlugin { .in_set(RenderSystems::PrepareResourcesFlush), ) .add_systems( - RenderGraph, + Core3d, // Add the node that allows us to read the indirect parameters back // from the GPU to the CPU, which allows us to determine how many // meshes were culled. From f7151773744bbf9058c1a8dbba550ee42ef8d543 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?charlotte=20=F0=9F=8C=B8?= Date: Wed, 14 Jan 2026 14:03:43 -0800 Subject: [PATCH 18/55] Ci. --- crates/bevy_core_pipeline/src/core_3d/mod.rs | 3 --- crates/bevy_post_process/src/auto_exposure/mod.rs | 1 - crates/bevy_post_process/src/bloom/mod.rs | 2 -- crates/bevy_post_process/src/dof/mod.rs | 1 - crates/bevy_render/src/renderer/render_context.rs | 2 -- examples/2d/dynamic_mip_generation.rs | 7 +++++-- 6 files changed, 5 insertions(+), 11 deletions(-) diff --git a/crates/bevy_core_pipeline/src/core_3d/mod.rs b/crates/bevy_core_pipeline/src/core_3d/mod.rs index fd3ef776ccb27..1aa88ab663df1 100644 --- a/crates/bevy_core_pipeline/src/core_3d/mod.rs +++ b/crates/bevy_core_pipeline/src/core_3d/mod.rs @@ -144,7 +144,6 @@ impl Plugin for Core3dPlugin { .add_systems( Core3d, ( - // Prepasses early_prepass.before(Core3dSystems::EndPrepasses), early_deferred_prepass .after(early_prepass) @@ -158,7 +157,6 @@ impl Plugin for Core3dPlugin { copy_deferred_lighting_id .after(late_deferred_prepass) .before(Core3dSystems::EndPrepasses), - // Main passes main_opaque_pass_3d .after(Core3dSystems::StartMainPass) .before(Core3dSystems::EndMainPass), @@ -168,7 +166,6 @@ impl Plugin for Core3dPlugin { main_transparent_pass_3d .after(main_transmissive_pass_3d) .before(Core3dSystems::EndMainPass), - // Post-processing tonemapping .after(Core3dSystems::StartMainPassPostProcessing) .before(Core3dSystems::PostProcessing), diff --git a/crates/bevy_post_process/src/auto_exposure/mod.rs b/crates/bevy_post_process/src/auto_exposure/mod.rs index 4b1b64c3eb7b1..9197e1a4fe132 100644 --- a/crates/bevy_post_process/src/auto_exposure/mod.rs +++ b/crates/bevy_post_process/src/auto_exposure/mod.rs @@ -73,7 +73,6 @@ impl Plugin for AutoExposurePlugin { queue_view_auto_exposure_pipelines.in_set(RenderSystems::Queue), ), ) - // Add auto_exposure to the 3d schedule .add_systems( Core3d, node::auto_exposure diff --git a/crates/bevy_post_process/src/bloom/mod.rs b/crates/bevy_post_process/src/bloom/mod.rs index 23e7befc7764b..f4e5fc58b6dcf 100644 --- a/crates/bevy_post_process/src/bloom/mod.rs +++ b/crates/bevy_post_process/src/bloom/mod.rs @@ -74,14 +74,12 @@ impl Plugin for BloomPlugin { prepare_bloom_bind_groups.in_set(RenderSystems::PrepareBindGroups), ), ) - // Add bloom to the 3d schedule .add_systems( Core3d, bloom .after(Core3dSystems::StartMainPassPostProcessing) .before(tonemapping), ) - // Add bloom to the 2d schedule .add_systems( Core2d, bloom diff --git a/crates/bevy_post_process/src/dof/mod.rs b/crates/bevy_post_process/src/dof/mod.rs index 1456fb2ed01e6..64fff4c809b8e 100644 --- a/crates/bevy_post_process/src/dof/mod.rs +++ b/crates/bevy_post_process/src/dof/mod.rs @@ -238,7 +238,6 @@ impl Plugin for DepthOfFieldPlugin { Render, prepare_depth_of_field_global_bind_group.in_set(RenderSystems::PrepareBindGroups), ) - // Add depth_of_field to the 3d schedule .add_systems(Core3d, depth_of_field.after(bloom).before(tonemapping)); } } diff --git a/crates/bevy_render/src/renderer/render_context.rs b/crates/bevy_render/src/renderer/render_context.rs index 7c72ff6885702..af6f8415bde67 100644 --- a/crates/bevy_render/src/renderer/render_context.rs +++ b/crates/bevy_render/src/renderer/render_context.rs @@ -262,7 +262,6 @@ unsafe impl<'a, D: QueryData + 'static, F: QueryFilter + 'static> SystemParam _system_meta: &SystemMeta, world: UnsafeWorldCell, ) -> Result<(), SystemParamValidationError> { - // Check if CurrentViewEntity resource exists and get the entity // SAFETY: We have registered resource read access in init_access let current_view = unsafe { world.get_resource::() }; @@ -274,7 +273,6 @@ unsafe impl<'a, D: QueryData + 'static, F: QueryFilter + 'static> SystemParam let entity = current_view.entity(); - // Check if the current view entity matches the query // SAFETY: Query state access is properly registered in init_access. // The caller ensures the world matches the one used in init_state. let result = unsafe { state.query_state.get_unchecked(world, entity) }; diff --git a/examples/2d/dynamic_mip_generation.rs b/examples/2d/dynamic_mip_generation.rs index 849287bfc3a19..e2e34990f6db1 100644 --- a/examples/2d/dynamic_mip_generation.rs +++ b/examples/2d/dynamic_mip_generation.rs @@ -16,7 +16,8 @@ use bevy::{ asset::RenderAssetUsages, core_pipeline::{ mip_generation::{ - generate_mips_for_phase, MipGenerationJobs, MipGenerationPhaseId, MipGenerationPipelines, + generate_mips_for_phase, MipGenerationJobs, MipGenerationPhaseId, + MipGenerationPipelines, }, schedule::Core2d, }, @@ -24,7 +25,9 @@ use bevy::{ reflect::TypePath, render::{ render_asset::RenderAssets, - render_resource::{AsBindGroup, Extent3d, PipelineCache, TextureDimension, TextureFormat, TextureUsages}, + render_resource::{ + AsBindGroup, Extent3d, PipelineCache, TextureDimension, TextureFormat, TextureUsages, + }, renderer::RenderContext, texture::GpuImage, Extract, RenderApp, From 8d5dc16f5468a4a9f802137bc8b1cd40cb11edb8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?charlotte=20=F0=9F=8C=B8?= Date: Wed, 14 Jan 2026 19:48:08 -0800 Subject: [PATCH 19/55] Ci. --- crates/bevy_core_pipeline/src/mip_generation/mod.rs | 4 +--- crates/bevy_pbr/src/deferred/mod.rs | 7 ++++--- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/crates/bevy_core_pipeline/src/mip_generation/mod.rs b/crates/bevy_core_pipeline/src/mip_generation/mod.rs index 2976f037737ca..4f3ba9f6446ae 100644 --- a/crates/bevy_core_pipeline/src/mip_generation/mod.rs +++ b/crates/bevy_core_pipeline/src/mip_generation/mod.rs @@ -50,9 +50,7 @@ use bevy_render::{ texture::GpuImage, RenderStartup, }; -use bevy_render::{ - Render, RenderApp, RenderSystems, -}; +use bevy_render::{Render, RenderApp, RenderSystems}; use bevy_shader::{Shader, ShaderDefVal}; use bevy_utils::default; diff --git a/crates/bevy_pbr/src/deferred/mod.rs b/crates/bevy_pbr/src/deferred/mod.rs index 15e596334c4b8..8e2ad6f1a7404 100644 --- a/crates/bevy_pbr/src/deferred/mod.rs +++ b/crates/bevy_pbr/src/deferred/mod.rs @@ -4,9 +4,10 @@ use crate::{ }; use crate::{ MeshPipeline, MeshViewBindGroup, RenderViewLightProbes, ScreenSpaceAmbientOcclusion, - ScreenSpaceReflectionsUniform, ViewContactShadowsUniformOffset, ViewEnvironmentMapUniformOffset, - ViewLightProbesUniformOffset, ViewScreenSpaceReflectionsUniformOffset, - TONEMAPPING_LUT_SAMPLER_BINDING_INDEX, TONEMAPPING_LUT_TEXTURE_BINDING_INDEX, + ScreenSpaceReflectionsUniform, ViewContactShadowsUniformOffset, + ViewEnvironmentMapUniformOffset, ViewLightProbesUniformOffset, + ViewScreenSpaceReflectionsUniformOffset, TONEMAPPING_LUT_SAMPLER_BINDING_INDEX, + TONEMAPPING_LUT_TEXTURE_BINDING_INDEX, }; use bevy_app::prelude::*; use bevy_asset::{embedded_asset, load_embedded_asset, AssetServer, Handle}; From cd7baed75167e014134bfc91b5d9e273953fc119 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?charlotte=20=F0=9F=8C=B8?= Date: Wed, 14 Jan 2026 19:51:34 -0800 Subject: [PATCH 20/55] Oops. --- crates/bevy_pbr/src/render/light.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/crates/bevy_pbr/src/render/light.rs b/crates/bevy_pbr/src/render/light.rs index 8fe6a650367d5..ea68eb4bc4bf9 100644 --- a/crates/bevy_pbr/src/render/light.rs +++ b/crates/bevy_pbr/src/render/light.rs @@ -18,6 +18,7 @@ use bevy_ecs::system::SystemChangeTick; use bevy_ecs::{ entity::{EntityHashMap, EntityHashSet}, prelude::*, + system::{SystemParam, SystemState}, }; use bevy_light::cascade::Cascade; use bevy_light::cluster::assign::{calculate_cluster_factors, ClusterableObjectType}; From 6766122d648a90707dc1dbdcb208526a34de9158 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?charlotte=20=F0=9F=8C=B8?= Date: Wed, 14 Jan 2026 20:47:17 -0800 Subject: [PATCH 21/55] Fix game of life. --- examples/shader/compute_shader_game_of_life.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/shader/compute_shader_game_of_life.rs b/examples/shader/compute_shader_game_of_life.rs index a0dadc129ef84..6e1bdc86033e2 100644 --- a/examples/shader/compute_shader_game_of_life.rs +++ b/examples/shader/compute_shader_game_of_life.rs @@ -17,7 +17,7 @@ use bevy::{ texture::GpuImage, Render, RenderApp, RenderStartup, RenderSystems, }, - shader::PipelineCacheError, + shader::ShaderCacheError, }; use std::borrow::Cow; @@ -228,7 +228,7 @@ fn update( *state = GameOfLifeState::Init; } // If the shader hasn't loaded yet, just wait. - CachedPipelineState::Err(PipelineCacheError::ShaderNotLoaded(_)) => {} + CachedPipelineState::Err(ShaderCacheError::ShaderNotLoaded(_)) => {} CachedPipelineState::Err(err) => { panic!("Initializing assets/{SHADER_ASSET_PATH}:\n{err}") } From 9c80e00231727308b0326c7417e420239dee9ada Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?charlotte=20=F0=9F=8C=B8?= Date: Wed, 14 Jan 2026 20:57:54 -0800 Subject: [PATCH 22/55] Wasm atomics. --- .../src/renderer/render_context.rs | 37 ++++++++++++++----- 1 file changed, 28 insertions(+), 9 deletions(-) diff --git a/crates/bevy_render/src/renderer/render_context.rs b/crates/bevy_render/src/renderer/render_context.rs index af6f8415bde67..3ce0dc667897b 100644 --- a/crates/bevy_render/src/renderer/render_context.rs +++ b/crates/bevy_render/src/renderer/render_context.rs @@ -1,3 +1,4 @@ +use super::WgpuWrapper; use crate::diagnostic::internal::DiagnosticsRecorder; use crate::render_phase::TrackedRenderPass; use crate::render_resource::{CommandEncoder, RenderPassDescriptor}; @@ -15,34 +16,52 @@ use core::marker::PhantomData; use tracing::info_span; use wgpu::CommandBuffer; -#[derive(Resource, Default)] -pub struct PendingCommandBuffers { +struct PendingCommandBuffersInner { buffers: Vec, encoders: Vec, } +impl Default for PendingCommandBuffersInner { + fn default() -> Self { + Self { + buffers: Vec::new(), + encoders: Vec::new(), + } + } +} + +#[derive(Resource)] +pub struct PendingCommandBuffers(WgpuWrapper); + +impl Default for PendingCommandBuffers { + fn default() -> Self { + Self(WgpuWrapper::new(PendingCommandBuffersInner::default())) + } +} + impl PendingCommandBuffers { pub fn push(&mut self, buffers: impl IntoIterator) { - self.buffers.extend(buffers); + self.0.buffers.extend(buffers); } pub fn push_encoder(&mut self, encoder: CommandEncoder) { - self.encoders.push(encoder); + self.0.encoders.push(encoder); } pub fn take(&mut self) -> Vec { - for encoder in self.encoders.drain(..) { - self.buffers.push(encoder.finish()); + let encoders: Vec<_> = self.0.encoders.drain(..).collect(); + for encoder in encoders { + self.0.buffers.push(encoder.finish()); } - core::mem::take(&mut self.buffers) + core::mem::take(&mut self.0.buffers) } pub fn is_empty(&self) -> bool { - self.buffers.is_empty() && self.encoders.is_empty() + self.0.buffers.is_empty() && self.0.encoders.is_empty() } pub fn len(&self) -> usize { - self.buffers.len() + self.encoders.len() + self.0.buffers.len() + self.0.encoders.len() } } From eaa90bbbcffb44b4ad844e772cbe5017c92e15f2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?charlotte=20=F0=9F=8C=B8?= Date: Wed, 14 Jan 2026 20:59:26 -0800 Subject: [PATCH 23/55] Docs. --- crates/bevy_core_pipeline/src/mip_generation/mod.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/crates/bevy_core_pipeline/src/mip_generation/mod.rs b/crates/bevy_core_pipeline/src/mip_generation/mod.rs index 4f3ba9f6446ae..2de6cfc6ae104 100644 --- a/crates/bevy_core_pipeline/src/mip_generation/mod.rs +++ b/crates/bevy_core_pipeline/src/mip_generation/mod.rs @@ -131,7 +131,7 @@ static TEXTURE_FORMATS: [(TextureFormat, &str); 40] = [ /// /// You can add images to this list via the [`MipGenerationJobs::add`] method, /// in the render world. Note that this, by itself, isn't enough to generate -/// the mipmaps; you must also add a [`MipGenerationNode`] to the render graph. +/// the mipmaps; you must also add a [`mip_generation`] system to the render schedule. /// /// This resource exists only in the render world, not the main world. /// Therefore, you typically want to place images in this resource in a system @@ -146,9 +146,9 @@ impl MipGenerationJobs { /// Schedules the generation of mipmaps for an image. /// /// Mipmaps will be generated during the execution of the - /// [`MipGenerationNode`] corresponding to the [`MipGenerationPhaseId`]. - /// Note that, by default, Bevy doesn't automatically add any such node to - /// the render graph; it's up to you to manually add that node. + /// [`mip_generation`] system corresponding to the [`MipGenerationPhaseId`]. + /// Note that, by default, Bevy doesn't automatically add any such system to + /// the render schedule; it's up to you to manually add that system. pub fn add(&mut self, phase: MipGenerationPhaseId, image: impl Into>) { self.entry(phase).or_default().push(image.into()); } From b1da2ceb799fa5e478d333d46887d3cc7dd7f6dc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?charlotte=20=F0=9F=8C=B8?= Date: Wed, 14 Jan 2026 21:04:51 -0800 Subject: [PATCH 24/55] Ci. --- crates/bevy_render/src/renderer/render_context.rs | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/crates/bevy_render/src/renderer/render_context.rs b/crates/bevy_render/src/renderer/render_context.rs index 3ce0dc667897b..d1e6414f08e32 100644 --- a/crates/bevy_render/src/renderer/render_context.rs +++ b/crates/bevy_render/src/renderer/render_context.rs @@ -16,20 +16,12 @@ use core::marker::PhantomData; use tracing::info_span; use wgpu::CommandBuffer; +#[derive(Default)] struct PendingCommandBuffersInner { buffers: Vec, encoders: Vec, } -impl Default for PendingCommandBuffersInner { - fn default() -> Self { - Self { - buffers: Vec::new(), - encoders: Vec::new(), - } - } -} - #[derive(Resource)] pub struct PendingCommandBuffers(WgpuWrapper); From 23803dd6bd68b0e1fc12f38f007e8ebfd37a08ee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?charlotte=20=F0=9F=8C=B8?= Date: Wed, 14 Jan 2026 21:19:18 -0800 Subject: [PATCH 25/55] Docs. --- crates/bevy_core_pipeline/src/mip_generation/mod.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/bevy_core_pipeline/src/mip_generation/mod.rs b/crates/bevy_core_pipeline/src/mip_generation/mod.rs index 2de6cfc6ae104..766ae812b402e 100644 --- a/crates/bevy_core_pipeline/src/mip_generation/mod.rs +++ b/crates/bevy_core_pipeline/src/mip_generation/mod.rs @@ -131,7 +131,7 @@ static TEXTURE_FORMATS: [(TextureFormat, &str); 40] = [ /// /// You can add images to this list via the [`MipGenerationJobs::add`] method, /// in the render world. Note that this, by itself, isn't enough to generate -/// the mipmaps; you must also add a [`mip_generation`] system to the render schedule. +/// the mipmaps; you must also add a [`generate_mips_for_phase`] system to the render schedule. /// /// This resource exists only in the render world, not the main world. /// Therefore, you typically want to place images in this resource in a system @@ -146,7 +146,7 @@ impl MipGenerationJobs { /// Schedules the generation of mipmaps for an image. /// /// Mipmaps will be generated during the execution of the - /// [`mip_generation`] system corresponding to the [`MipGenerationPhaseId`]. + /// [`generate_mips_for_phase`] system corresponding to the [`MipGenerationPhaseId`]. /// Note that, by default, Bevy doesn't automatically add any such system to /// the render schedule; it's up to you to manually add that system. pub fn add(&mut self, phase: MipGenerationPhaseId, image: impl Into>) { From e99c18a3264bd67b99237b79bed4edba728e14d5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?charlotte=20=F0=9F=8C=B8?= Date: Thu, 15 Jan 2026 11:51:48 -0800 Subject: [PATCH 26/55] More wasm atomics. --- .../src/renderer/render_context.rs | 39 ++++++++++++------- 1 file changed, 24 insertions(+), 15 deletions(-) diff --git a/crates/bevy_render/src/renderer/render_context.rs b/crates/bevy_render/src/renderer/render_context.rs index d1e6414f08e32..918a2178f7c69 100644 --- a/crates/bevy_render/src/renderer/render_context.rs +++ b/crates/bevy_render/src/renderer/render_context.rs @@ -58,33 +58,42 @@ impl PendingCommandBuffers { } #[derive(Default)] -pub struct RenderContextState { +struct RenderContextStateInner { command_encoder: Option, command_buffers: Vec, render_device: Option, } +pub struct RenderContextState(WgpuWrapper); + +impl Default for RenderContextState { + fn default() -> Self { + Self(WgpuWrapper::new(RenderContextStateInner::default())) + } +} + impl RenderContextState { fn flush_encoder(&mut self) { - if let Some(encoder) = self.command_encoder.take() { - self.command_buffers.push(encoder.finish()); + if let Some(encoder) = self.0.command_encoder.take() { + self.0.command_buffers.push(encoder.finish()); } } fn command_encoder(&mut self) -> &mut CommandEncoder { let render_device = self + .0 .render_device - .as_ref() + .clone() .expect("RenderDevice must be set before accessing command_encoder"); - self.command_encoder.get_or_insert_with(|| { + self.0.command_encoder.get_or_insert_with(|| { render_device.create_command_encoder(&wgpu::CommandEncoderDescriptor::default()) }) } pub fn finish(&mut self) -> Vec { self.flush_encoder(); - core::mem::take(&mut self.command_buffers) + core::mem::take(&mut self.0.command_buffers) } } @@ -92,22 +101,22 @@ impl SystemBuffer for RenderContextState { fn apply(&mut self, system_meta: &SystemMeta, world: &mut World) { let _span = info_span!("RenderContextState::apply", system = %system_meta.name()).entered(); - let has_buffers = !self.command_buffers.is_empty(); - let has_encoder = self.command_encoder.is_some(); + let has_buffers = !self.0.command_buffers.is_empty(); + let has_encoder = self.0.command_encoder.is_some(); if has_buffers || has_encoder { let mut pending = world.resource_mut::(); if has_buffers { - pending.push(core::mem::take(&mut self.command_buffers)); + pending.push(core::mem::take(&mut self.0.command_buffers)); } - if let Some(encoder) = self.command_encoder.take() { + if let Some(encoder) = self.0.command_encoder.take() { pending.push_encoder(encoder); } } - self.render_device = None; + self.0.render_device = None; } fn queue(&mut self, _system_meta: &SystemMeta, _world: DeferredWorld) {} @@ -122,8 +131,8 @@ pub struct RenderContext<'w, 's> { impl<'w, 's> RenderContext<'w, 's> { fn ensure_device(&mut self) { - if self.state.render_device.is_none() { - self.state.render_device = Some(self.render_device.clone()); + if self.state.0.render_device.is_none() { + self.state.0.render_device = Some(self.render_device.clone()); } } @@ -146,7 +155,7 @@ impl<'w, 's> RenderContext<'w, 's> { ) -> TrackedRenderPass<'a> { self.ensure_device(); - let command_encoder = self.state.command_encoder.get_or_insert_with(|| { + let command_encoder = self.state.0.command_encoder.get_or_insert_with(|| { self.render_device .create_command_encoder(&wgpu::CommandEncoderDescriptor::default()) }); @@ -157,7 +166,7 @@ impl<'w, 's> RenderContext<'w, 's> { pub fn add_command_buffer(&mut self, command_buffer: CommandBuffer) { self.state.flush_encoder(); - self.state.command_buffers.push(command_buffer); + self.state.0.command_buffers.push(command_buffer); } } From 55a9596b7f969a1d8b327e1705944470bc868148 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?charlotte=20=F0=9F=8C=B8?= Date: Thu, 15 Jan 2026 11:54:39 -0800 Subject: [PATCH 27/55] Fix custom_render_phase. --- examples/shader_advanced/custom_render_phase.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/shader_advanced/custom_render_phase.rs b/examples/shader_advanced/custom_render_phase.rs index 1ff81053366ee..c6eb5462085a9 100644 --- a/examples/shader_advanced/custom_render_phase.rs +++ b/examples/shader_advanced/custom_render_phase.rs @@ -16,7 +16,7 @@ use bevy::camera::Viewport; use bevy::pbr::SetMeshViewEmptyBindGroup; use bevy::{ camera::MainPassResolutionOverride, - core_pipeline::{schedule::Core3d, Core3dSystems}, + core_pipeline::{core_3d::main_opaque_pass_3d, schedule::Core3d, Core3dSystems}, ecs::system::{lifetimeless::SRes, SystemParamItem}, math::FloatOrd, mesh::MeshVertexBufferLayoutRef, @@ -138,7 +138,7 @@ impl Plugin for MeshStencilPhasePlugin { .add_systems( Core3d, custom_draw_system - .after(Core3dSystems::StartMainPass) + .after(main_opaque_pass_3d) .before(Core3dSystems::EndMainPass), ); } From e1ecbf7f0b59ce76da5b2ebf66a4ac64505d8907 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?charlotte=20=F0=9F=8C=B8?= Date: Thu, 15 Jan 2026 19:53:47 -0800 Subject: [PATCH 28/55] More constraints --- crates/bevy_anti_alias/Cargo.toml | 1 + crates/bevy_anti_alias/src/dlss/mod.rs | 6 ++++-- crates/bevy_anti_alias/src/taa/mod.rs | 4 +++- crates/bevy_post_process/src/bloom/mod.rs | 6 +++--- crates/bevy_post_process/src/motion_blur/node.rs | 2 +- 5 files changed, 12 insertions(+), 7 deletions(-) diff --git a/crates/bevy_anti_alias/Cargo.toml b/crates/bevy_anti_alias/Cargo.toml index ff86d20468817..05bd90f1ffaee 100644 --- a/crates/bevy_anti_alias/Cargo.toml +++ b/crates/bevy_anti_alias/Cargo.toml @@ -31,6 +31,7 @@ bevy_shader = { path = "../bevy_shader", version = "0.19.0-dev" } bevy_ecs = { path = "../bevy_ecs", version = "0.19.0-dev" } bevy_core_pipeline = { path = "../bevy_core_pipeline", version = "0.19.0-dev" } bevy_diagnostic = { path = "../bevy_diagnostic", version = "0.19.0-dev" } +bevy_post_process = { path = "../bevy_post_process", version = "0.19.0-dev" } # other tracing = { version = "0.1", default-features = false, features = ["std"] } diff --git a/crates/bevy_anti_alias/src/dlss/mod.rs b/crates/bevy_anti_alias/src/dlss/mod.rs index c993734b6a675..3abb232f81012 100644 --- a/crates/bevy_anti_alias/src/dlss/mod.rs +++ b/crates/bevy_anti_alias/src/dlss/mod.rs @@ -27,6 +27,7 @@ use bevy_core_pipeline::{ }; use bevy_ecs::prelude::*; use bevy_math::{UVec2, Vec2}; +use bevy_post_process::{bloom::bloom, motion_blur::node::motion_blur}; use bevy_reflect::{reflect_remote, Reflect}; use bevy_render::{ camera::{MipBias, TemporalJitter}, @@ -192,11 +193,12 @@ impl Plugin for DlssPlugin { Core3d, ( node::dlss_super_resolution + .after(motion_blur) .after(Core3dSystems::StartMainPassPostProcessing) - .before(Core3dSystems::PostProcessing), + .before(bloom), node::dlss_ray_reconstruction .after(node::dlss_super_resolution) - .before(Core3dSystems::PostProcessing), + .before(bloom), ), ); } diff --git a/crates/bevy_anti_alias/src/taa/mod.rs b/crates/bevy_anti_alias/src/taa/mod.rs index 995f98db9dea8..0538ab55a49ff 100644 --- a/crates/bevy_anti_alias/src/taa/mod.rs +++ b/crates/bevy_anti_alias/src/taa/mod.rs @@ -17,6 +17,7 @@ use bevy_ecs::{ }; use bevy_image::{BevyDefault as _, ToExtents}; use bevy_math::vec2; +use bevy_post_process::{bloom::bloom, motion_blur::node::motion_blur}; use bevy_reflect::{std_traits::ReflectDefault, Reflect}; use bevy_render::{ camera::{ExtractedCamera, MipBias, TemporalJitter}, @@ -70,8 +71,9 @@ impl Plugin for TemporalAntiAliasPlugin { render_app.add_systems( Core3d, temporal_anti_alias + .after(motion_blur) .after(Core3dSystems::StartMainPassPostProcessing) - .before(Core3dSystems::PostProcessing), + .before(bloom), ); } } diff --git a/crates/bevy_post_process/src/bloom/mod.rs b/crates/bevy_post_process/src/bloom/mod.rs index f4e5fc58b6dcf..aa77ef2b0c40f 100644 --- a/crates/bevy_post_process/src/bloom/mod.rs +++ b/crates/bevy_post_process/src/bloom/mod.rs @@ -89,7 +89,7 @@ impl Plugin for BloomPlugin { } } -pub(crate) fn bloom( +pub fn bloom( view: ViewQuery<( &ExtractedCamera, &ViewTarget, @@ -275,7 +275,7 @@ pub(crate) fn bloom( } #[derive(Component)] -pub(crate) struct BloomTexture { +pub struct BloomTexture { // First mip is half the screen resolution, successive mips are half the previous #[cfg(any( not(feature = "webgl"), @@ -377,7 +377,7 @@ fn prepare_bloom_textures( } #[derive(Component)] -pub(crate) struct BloomBindGroups { +pub struct BloomBindGroups { cache_key: (TextureId, BufferId), downsampling_bind_groups: Box<[BindGroup]>, upsampling_bind_groups: Box<[BindGroup]>, diff --git a/crates/bevy_post_process/src/motion_blur/node.rs b/crates/bevy_post_process/src/motion_blur/node.rs index c87a9d64adcb8..53fb396279359 100644 --- a/crates/bevy_post_process/src/motion_blur/node.rs +++ b/crates/bevy_post_process/src/motion_blur/node.rs @@ -18,7 +18,7 @@ use super::{ MotionBlurUniform, }; -pub(crate) fn motion_blur( +pub fn motion_blur( view: ViewQuery<( &ViewTarget, &MotionBlurPipelineId, From 3cc28a4eab2b0db81eba9c07b9b84442197a0bb8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?charlotte=20=F0=9F=8C=B8?= Date: Thu, 15 Jan 2026 20:07:59 -0800 Subject: [PATCH 29/55] Respond to feedback. --- crates/bevy_pbr/src/meshlet/mod.rs | 3 ++- crates/bevy_post_process/src/msaa_writeback.rs | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/crates/bevy_pbr/src/meshlet/mod.rs b/crates/bevy_pbr/src/meshlet/mod.rs index f975254d91bdd..dbcfcfd261540 100644 --- a/crates/bevy_pbr/src/meshlet/mod.rs +++ b/crates/bevy_pbr/src/meshlet/mod.rs @@ -49,6 +49,7 @@ use bevy_app::{App, Plugin}; use bevy_asset::{embedded_asset, AssetApp, AssetId, Handle}; use bevy_camera::visibility::{self, Visibility, VisibilityClass}; use bevy_core_pipeline::{ + core_3d::main_opaque_pass_3d, prepass::{DeferredPrepass, MotionVectorPrepass, NormalPrepass}, schedule::{Core3d, Core3dSystems}, }; @@ -205,7 +206,7 @@ impl Plugin for MeshletPlugin { .before(Core3dSystems::EndPrepasses), meshlet_main_opaque_pass .after(Core3dSystems::StartMainPass) - .before(Core3dSystems::EndMainPass), + .before(main_opaque_pass_3d), ), ); } diff --git a/crates/bevy_post_process/src/msaa_writeback.rs b/crates/bevy_post_process/src/msaa_writeback.rs index a53f11a9c14eb..8d7f14e3797d8 100644 --- a/crates/bevy_post_process/src/msaa_writeback.rs +++ b/crates/bevy_post_process/src/msaa_writeback.rs @@ -29,7 +29,7 @@ impl Plugin for MsaaWritebackPlugin { Render, prepare_msaa_writeback_pipelines.in_set(RenderSystems::Prepare), ); - render_app.add_systems(Core3d, msaa_writeback.before(Core3dSystems::EndPrepasses)); + render_app.add_systems(Core3d, msaa_writeback.before(Core3dSystems::StartMainPass)); render_app.add_systems(Core2d, msaa_writeback.before(Core2dSystems::StartMainPass)); } } From ac21b06a50ed5257730e2fa0e69b0594cd1af762 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?charlotte=20=F0=9F=8C=B8?= Date: Thu, 15 Jan 2026 20:27:59 -0800 Subject: [PATCH 30/55] Respond to feedback. --- crates/bevy_pbr/src/lib.rs | 6 +- crates/bevy_pbr/src/meshlet/mod.rs | 6 +- crates/bevy_pbr/src/render/gpu_preprocess.rs | 59 +++++++------------- crates/bevy_pbr/src/render/light.rs | 49 ++-------------- crates/bevy_pbr/src/ssr/mod.rs | 3 +- 5 files changed, 32 insertions(+), 91 deletions(-) diff --git a/crates/bevy_pbr/src/lib.rs b/crates/bevy_pbr/src/lib.rs index 659c3c9c2463c..678ee1f70759e 100644 --- a/crates/bevy_pbr/src/lib.rs +++ b/crates/bevy_pbr/src/lib.rs @@ -320,11 +320,11 @@ impl Plugin for PbrPlugin { render_app.add_systems( Core3d, ( - early_shadow_pass + shadow_pass:: .after(early_prepass_build_indirect_parameters) .before(early_downsample_depth) - .before(late_shadow_pass), - late_shadow_pass + .before(shadow_pass::), + shadow_pass:: .after(late_prepass_build_indirect_parameters) .before(main_build_indirect_parameters) .before(Core3dSystems::StartMainPass), diff --git a/crates/bevy_pbr/src/meshlet/mod.rs b/crates/bevy_pbr/src/meshlet/mod.rs index dbcfcfd261540..d099afa5dcd50 100644 --- a/crates/bevy_pbr/src/meshlet/mod.rs +++ b/crates/bevy_pbr/src/meshlet/mod.rs @@ -43,7 +43,7 @@ use self::{ }, visibility_buffer_raster_node::meshlet_visibility_buffer_raster, }; -use crate::render::early_shadow_pass; +use crate::render::{shadow_pass, EARLY_SHADOW_PASS}; use crate::{meshlet::meshlet_mesh_manager::init_meshlet_mesh_manager, PreviousGlobalTransform}; use bevy_app::{App, Plugin}; use bevy_asset::{embedded_asset, AssetApp, AssetId, Handle}; @@ -197,9 +197,9 @@ impl Plugin for MeshletPlugin { .add_systems( Core3d, ( - meshlet_visibility_buffer_raster.before(early_shadow_pass), + meshlet_visibility_buffer_raster.before(shadow_pass::), meshlet_prepass - .after(early_shadow_pass) + .after(shadow_pass::) .before(Core3dSystems::EndPrepasses), meshlet_deferred_gbuffer_prepass .after(meshlet_prepass) diff --git a/crates/bevy_pbr/src/render/gpu_preprocess.rs b/crates/bevy_pbr/src/render/gpu_preprocess.rs index a479d5b161d4c..5d4fadf9583b8 100644 --- a/crates/bevy_pbr/src/render/gpu_preprocess.rs +++ b/crates/bevy_pbr/src/render/gpu_preprocess.rs @@ -26,7 +26,7 @@ use bevy_ecs::{ prelude::resource_exists, query::{Has, Or, With, Without}, resource::Resource, - schedule::IntoScheduleConfigs as _, + schedule::{common_conditions::any_match_filter, IntoScheduleConfigs as _}, system::{Commands, Query, Res, ResMut}, world::{FromWorld, World}, }; @@ -369,15 +369,33 @@ impl Plugin for GpuMeshPreprocessPlugin { .after(clear_indirect_parameters_metadata) .before(early_prepass), early_prepass_build_indirect_parameters + .run_if(any_match_filter::<( + With, + Without, + Without, + Or<(With, With)>, + )>) .after(early_gpu_preprocess) .before(early_prepass), late_gpu_preprocess .after(early_downsample_depth) .before(late_prepass), late_prepass_build_indirect_parameters + .run_if(any_match_filter::<( + With, + Without, + Without, + Or<(With, With)>, + With, + )>) .after(late_gpu_preprocess) .before(late_prepass), main_build_indirect_parameters + .run_if(any_match_filter::<( + With, + Without, + Without, + )>) .after(late_prepass_build_indirect_parameters) .after(late_deferred_prepass) .before(Core3dSystems::StartMainPass), @@ -754,25 +772,12 @@ pub fn late_gpu_preprocess( } pub fn early_prepass_build_indirect_parameters( - views: Query< - (), - ( - With, - Without, - Without, - Or<(With, With)>, - ), - >, preprocess_pipelines: Res, build_indirect_params_bind_groups: Option>, pipeline_cache: Res, indirect_parameters_buffers: Option>, mut ctx: RenderContext, ) { - if views.iter().next().is_none() { - return; - } - run_build_indirect_parameters( &mut ctx, build_indirect_params_bind_groups.as_deref(), @@ -784,26 +789,12 @@ pub fn early_prepass_build_indirect_parameters( } pub fn late_prepass_build_indirect_parameters( - views: Query< - (), - ( - With, - Without, - Without, - Or<(With, With)>, - With, - ), - >, preprocess_pipelines: Res, build_indirect_params_bind_groups: Option>, pipeline_cache: Res, indirect_parameters_buffers: Option>, mut ctx: RenderContext, ) { - if views.iter().next().is_none() { - return; - } - run_build_indirect_parameters( &mut ctx, build_indirect_params_bind_groups.as_deref(), @@ -815,24 +806,12 @@ pub fn late_prepass_build_indirect_parameters( } pub fn main_build_indirect_parameters( - views: Query< - (), - ( - With, - Without, - Without, - ), - >, preprocess_pipelines: Res, build_indirect_params_bind_groups: Option>, pipeline_cache: Res, indirect_parameters_buffers: Option>, mut ctx: RenderContext, ) { - if views.iter().next().is_none() { - return; - } - run_build_indirect_parameters( &mut ctx, build_indirect_params_bind_groups.as_deref(), diff --git a/crates/bevy_pbr/src/render/light.rs b/crates/bevy_pbr/src/render/light.rs index ea68eb4bc4bf9..668007506661f 100644 --- a/crates/bevy_pbr/src/render/light.rs +++ b/crates/bevy_pbr/src/render/light.rs @@ -2263,57 +2263,18 @@ impl CachedRenderPipelinePhaseItem for Shadow { } } -/// This renders meshes that were "visible" (so to speak) from a light last frame. -/// If occlusion culling for a light is disabled, then this simply renders -/// all meshes in range of the light. -pub fn early_shadow_pass( - world: &World, - view: ViewQuery<&ViewLightEntities>, - view_light_query: Query<(&ShadowView, &ExtractedView, Has)>, - shadow_render_phases: Res>, - mut ctx: RenderContext, -) { - run_shadow_pass( - world, - view.into_inner(), - &view_light_query, - &shadow_render_phases, - &mut ctx, - false, - ); -} +pub const EARLY_SHADOW_PASS: bool = false; +pub const LATE_SHADOW_PASS: bool = true; -/// This renders meshes that became newly "visible" (so to speak) from a light this frame. -/// If occlusion culling for a light is disabled, then this does nothing. -pub fn late_shadow_pass( +pub fn shadow_pass( world: &World, view: ViewQuery<&ViewLightEntities>, view_light_query: Query<(&ShadowView, &ExtractedView, Has)>, shadow_render_phases: Res>, mut ctx: RenderContext, ) { - run_shadow_pass( - world, - view.into_inner(), - &view_light_query, - &shadow_render_phases, - &mut ctx, - true, - ); -} + let view_lights = view.into_inner(); -/// Shared implementation for early and late shadow passes. -/// -/// `is_late` is true if this is the late shadow pass or false if this is -/// the early shadow pass. -fn run_shadow_pass( - world: &World, - view_lights: &ViewLightEntities, - view_light_query: &Query<(&ShadowView, &ExtractedView, Has)>, - shadow_render_phases: &ViewBinnedRenderPhases, - ctx: &mut RenderContext, - is_late: bool, -) { for view_light_entity in view_lights.lights.iter().copied() { let Ok((view_light, extracted_light_view, occlusion_culling)) = view_light_query.get(view_light_entity) @@ -2321,7 +2282,7 @@ fn run_shadow_pass( continue; }; - if is_late && !occlusion_culling { + if IS_LATE && !occlusion_culling { continue; } diff --git a/crates/bevy_pbr/src/ssr/mod.rs b/crates/bevy_pbr/src/ssr/mod.rs index 26e183e39543c..cc1a660617cc8 100644 --- a/crates/bevy_pbr/src/ssr/mod.rs +++ b/crates/bevy_pbr/src/ssr/mod.rs @@ -5,7 +5,7 @@ use bevy_asset::{load_embedded_asset, AssetServer, Handle}; use bevy_core_pipeline::{ core_3d::{main_opaque_pass_3d, DEPTH_TEXTURE_SAMPLING_SUPPORTED}, prepass::{DeferredPrepass, DepthPrepass, MotionVectorPrepass, NormalPrepass}, - schedule::Core3d, + schedule::{Core3d, Core3dSystems}, FullscreenShader, }; use bevy_derive::{Deref, DerefMut}; @@ -201,6 +201,7 @@ impl Plugin for ScreenSpaceReflectionsPlugin { Core3d, screen_space_reflections .after(deferred_lighting) + .after(Core3dSystems::StartMainPass) .before(main_opaque_pass_3d), ); } From b07aed1479e2bbe996e40013c6d8b0b9da5e4464 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?charlotte=20=F0=9F=8C=B8?= Date: Thu, 15 Jan 2026 20:51:09 -0800 Subject: [PATCH 31/55] Fix compute mesh example. --- examples/shader_advanced/compute_mesh.rs | 161 ++++++++++------------- 1 file changed, 68 insertions(+), 93 deletions(-) diff --git a/examples/shader_advanced/compute_mesh.rs b/examples/shader_advanced/compute_mesh.rs index 9cddd76d23a8f..a157849be88f6 100644 --- a/examples/shader_advanced/compute_mesh.rs +++ b/examples/shader_advanced/compute_mesh.rs @@ -20,12 +20,11 @@ use bevy::{ render::{ extract_component::{ExtractComponent, ExtractComponentPlugin}, mesh::allocator::MeshAllocator, - render_graph::{self, RenderGraph, RenderLabel}, render_resource::{ binding_types::{storage_buffer, uniform_buffer}, *, }, - renderer::{RenderContext, RenderQueue}, + renderer::{RenderContext, RenderGraph, RenderQueue}, Render, RenderApp, RenderStartup, }, }; @@ -55,11 +54,9 @@ impl Plugin for ComputeShaderMeshGeneratorPlugin { render_app .init_resource::() - .add_systems( - RenderStartup, - (init_compute_pipeline, add_compute_render_graph_node), - ) - .add_systems(Render, prepare_chunks); + .add_systems(RenderStartup, init_compute_pipeline) + .add_systems(Render, prepare_chunks) + .add_systems(RenderGraph, compute_mesh); } fn finish(&self, app: &mut App) { let Some(render_app) = app.get_sub_app_mut(RenderApp) else { @@ -161,12 +158,6 @@ fn setup( )); } -fn add_compute_render_graph_node(mut render_graph: ResMut) { - render_graph.add_node(ComputeNodeLabel, ComputeNode::default()); - // add_node_edge guarantees that ComputeNodeLabel will run before CameraDriverLabel - render_graph.add_node_edge(ComputeNodeLabel, bevy::render::graph::CameraDriverLabel); -} - /// This is called `ChunksToProcess` because this example originated /// from a use case of generating chunks of landscape or voxels /// It only exists in the render world. @@ -248,14 +239,6 @@ fn init_compute_pipeline( commands.insert_resource(ComputePipeline { layout, pipeline }); } -/// Label to identify the node in the render graph -#[derive(Debug, Hash, PartialEq, Eq, Clone, RenderLabel)] -struct ComputeNodeLabel; - -/// The node that will execute the compute shader -#[derive(Default)] -struct ComputeNode {} - // A uniform that holds the vertex and index offsets // for the vertex/index mesh_allocator buffer slabs #[derive(ShaderType)] @@ -266,79 +249,71 @@ struct DataRanges { index_end: u32, } -impl render_graph::Node for ComputeNode { - fn run( - &self, - _graph: &mut render_graph::RenderGraphContext, - render_context: &mut RenderContext, - world: &World, - ) -> Result<(), render_graph::NodeRunError> { - let chunks = world.resource::(); - let mesh_allocator = world.resource::(); - - for mesh_id in &chunks.0 { - info!(?mesh_id, "processing mesh"); - let pipeline_cache = world.resource::(); - let pipeline = world.resource::(); - - if let Some(init_pipeline) = pipeline_cache.get_compute_pipeline(pipeline.pipeline) { - // the mesh_allocator holds slabs of meshes, so the buffers we get here - // can contain more data than just the mesh we're asking for. - // That's why there is a range field. - // You should *not* touch data in these buffers that is outside of the range. - let vertex_buffer_slice = mesh_allocator.mesh_vertex_slice(mesh_id).unwrap(); - let index_buffer_slice = mesh_allocator.mesh_index_slice(mesh_id).unwrap(); - - let first = DataRanges { - // there are 8 vertex data values (pos, normal, uv) per vertex - // and the vertex_buffer_slice.range.start is in "vertex elements" - // which includes all of that data, so each index is worth 8 indices - // to our shader code. - vertex_start: vertex_buffer_slice.range.start * 8, - vertex_end: vertex_buffer_slice.range.end * 8, - // but each vertex index is a single value, so the index of the - // vertex indices is exactly what the value is - index_start: index_buffer_slice.range.start, - index_end: index_buffer_slice.range.end, - }; - - let mut uniforms = UniformBuffer::from(first); - uniforms.write_buffer( - render_context.render_device(), - world.resource::(), - ); - - // pass in the full mesh_allocator slabs as well as the first index - // offsets for the vertex and index buffers - let bind_group = render_context.render_device().create_bind_group( - None, - &pipeline_cache.get_bind_group_layout(&pipeline.layout), - &BindGroupEntries::sequential(( - &uniforms, - vertex_buffer_slice.buffer.as_entire_buffer_binding(), - index_buffer_slice.buffer.as_entire_buffer_binding(), - )), - ); - - let mut pass = - render_context - .command_encoder() - .begin_compute_pass(&ComputePassDescriptor { - label: Some("Mesh generation compute pass"), - ..default() - }); - pass.push_debug_group("compute_mesh"); - - pass.set_bind_group(0, &bind_group, &[]); - pass.set_pipeline(init_pipeline); - // we only dispatch 1,1,1 workgroup here, but a real compute shader - // would take advantage of more and larger size workgroups - pass.dispatch_workgroups(1, 1, 1); +fn compute_mesh( + mut render_context: RenderContext, + chunks: Res, + mesh_allocator: Res, + pipeline_cache: Res, + pipeline: Res, + render_queue: Res, +) { + let Some(init_pipeline) = pipeline_cache.get_compute_pipeline(pipeline.pipeline) else { + return; + }; - pass.pop_debug_group(); - } - } + for mesh_id in &chunks.0 { + info!(?mesh_id, "processing mesh"); + + // the mesh_allocator holds slabs of meshes, so the buffers we get here + // can contain more data than just the mesh we're asking for. + // That's why there is a range field. + // You should *not* touch data in these buffers that is outside of the range. + let vertex_buffer_slice = mesh_allocator.mesh_vertex_slice(mesh_id).unwrap(); + let index_buffer_slice = mesh_allocator.mesh_index_slice(mesh_id).unwrap(); + + let first = DataRanges { + // there are 8 vertex data values (pos, normal, uv) per vertex + // and the vertex_buffer_slice.range.start is in "vertex elements" + // which includes all of that data, so each index is worth 8 indices + // to our shader code. + vertex_start: vertex_buffer_slice.range.start * 8, + vertex_end: vertex_buffer_slice.range.end * 8, + // but each vertex index is a single value, so the index of the + // vertex indices is exactly what the value is + index_start: index_buffer_slice.range.start, + index_end: index_buffer_slice.range.end, + }; - Ok(()) + let mut uniforms = UniformBuffer::from(first); + uniforms.write_buffer(render_context.render_device(), &render_queue); + + // pass in the full mesh_allocator slabs as well as the first index + // offsets for the vertex and index buffers + let bind_group = render_context.render_device().create_bind_group( + None, + &pipeline_cache.get_bind_group_layout(&pipeline.layout), + &BindGroupEntries::sequential(( + &uniforms, + vertex_buffer_slice.buffer.as_entire_buffer_binding(), + index_buffer_slice.buffer.as_entire_buffer_binding(), + )), + ); + + let mut pass = + render_context + .command_encoder() + .begin_compute_pass(&ComputePassDescriptor { + label: Some("Mesh generation compute pass"), + ..default() + }); + pass.push_debug_group("compute_mesh"); + + pass.set_bind_group(0, &bind_group, &[]); + pass.set_pipeline(init_pipeline); + // we only dispatch 1,1,1 workgroup here, but a real compute shader + // would take advantage of more and larger size workgroups + pass.dispatch_workgroups(1, 1, 1); + + pass.pop_debug_group(); } } From 415f63619758171b5d5076a6e17610fde234c99e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?charlotte=20=F0=9F=8C=B8?= Date: Fri, 16 Jan 2026 12:18:09 -0800 Subject: [PATCH 32/55] Restore missing solari code. --- crates/bevy_solari/src/realtime/node.rs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/crates/bevy_solari/src/realtime/node.rs b/crates/bevy_solari/src/realtime/node.rs index 6e3f69d89ff02..fb74a03752789 100644 --- a/crates/bevy_solari/src/realtime/node.rs +++ b/crates/bevy_solari/src/realtime/node.rs @@ -248,6 +248,14 @@ pub fn init_solari_lighting_pipelines( vec![], ), #[cfg(all(feature = "dlss", not(feature = "force_disable_dlss")))] + specular_gi_with_psr_pipeline: create_pipeline( + "solari_lighting_specular_gi_with_psr_pipeline", + "specular_gi", + load_embedded_asset!(asset_server.as_ref(), "specular_gi.wgsl"), + Some(&bind_group_layout_resolve_dlss_rr_textures), + vec!["DLSS_RR_GUIDE_BUFFERS".into()], + ), + #[cfg(all(feature = "dlss", not(feature = "force_disable_dlss")))] resolve_dlss_rr_textures_pipeline: create_pipeline( "solari_lighting_resolve_dlss_rr_textures_pipeline", "resolve_dlss_rr_textures", From f786942ab5dd3daecdfdf9d09aca818da76152ff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?charlotte=20=F0=9F=8C=B8?= Date: Mon, 19 Jan 2026 13:56:32 -0800 Subject: [PATCH 33/55] Solari fixes. --- crates/bevy_solari/src/realtime/node.rs | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/crates/bevy_solari/src/realtime/node.rs b/crates/bevy_solari/src/realtime/node.rs index fb74a03752789..2506a3923f348 100644 --- a/crates/bevy_solari/src/realtime/node.rs +++ b/crates/bevy_solari/src/realtime/node.rs @@ -573,7 +573,8 @@ pub fn solari_lighting( Some(di_spatial_and_shade_pipeline), Some(gi_initial_and_temporal_pipeline), Some(gi_spatial_and_shade_pipeline), - Some(specular_gi_pipeline), + Some(specular_gi_pipeline_regular), + Some(specular_gi_pipeline_with_psr), Some(scene_bind_group), Some(gbuffer), Some(depth_buffer), @@ -597,6 +598,7 @@ pub fn solari_lighting( pipeline_cache.get_compute_pipeline(pipelines.gi_initial_and_temporal_pipeline), pipeline_cache.get_compute_pipeline(pipelines.gi_spatial_and_shade_pipeline), pipeline_cache.get_compute_pipeline(pipelines.specular_gi_pipeline), + pipeline_cache.get_compute_pipeline(pipelines.specular_gi_with_psr_pipeline), &scene_bindings.bind_group, view_prepass_textures.deferred_view(), view_prepass_textures.depth_view(), @@ -610,6 +612,12 @@ pub fn solari_lighting( return; }; + let specular_gi_pipeline = if view_dlss_rr_textures.is_some() { + specular_gi_pipeline_with_psr + } else { + specular_gi_pipeline_regular + }; + let Some(resolve_dlss_rr_textures_pipeline) = pipeline_cache.get_compute_pipeline(pipelines.resolve_dlss_rr_textures_pipeline) else { From 72fb6940dda917fb2e604ff4f74b9fe915436faf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?charlotte=20=F0=9F=8C=B8?= Date: Mon, 19 Jan 2026 13:59:31 -0800 Subject: [PATCH 34/55] Use chaining --- crates/bevy_anti_alias/src/dlss/mod.rs | 14 +++------ crates/bevy_core_pipeline/src/core_3d/mod.rs | 33 +++++++++----------- crates/bevy_pbr/src/atmosphere/mod.rs | 6 ++-- crates/bevy_pbr/src/render/gpu_preprocess.rs | 29 +++++++++-------- 4 files changed, 35 insertions(+), 47 deletions(-) diff --git a/crates/bevy_anti_alias/src/dlss/mod.rs b/crates/bevy_anti_alias/src/dlss/mod.rs index 3abb232f81012..d942440e70053 100644 --- a/crates/bevy_anti_alias/src/dlss/mod.rs +++ b/crates/bevy_anti_alias/src/dlss/mod.rs @@ -191,15 +191,11 @@ impl Plugin for DlssPlugin { app.sub_app_mut(RenderApp).add_systems( Core3d, - ( - node::dlss_super_resolution - .after(motion_blur) - .after(Core3dSystems::StartMainPassPostProcessing) - .before(bloom), - node::dlss_ray_reconstruction - .after(node::dlss_super_resolution) - .before(bloom), - ), + (node::dlss_super_resolution, node::dlss_ray_reconstruction) + .chain() + .after(motion_blur) + .after(Core3dSystems::StartMainPassPostProcessing) + .before(bloom), ); } } diff --git a/crates/bevy_core_pipeline/src/core_3d/mod.rs b/crates/bevy_core_pipeline/src/core_3d/mod.rs index 1aa88ab663df1..e8f4db3ba5d14 100644 --- a/crates/bevy_core_pipeline/src/core_3d/mod.rs +++ b/crates/bevy_core_pipeline/src/core_3d/mod.rs @@ -144,28 +144,23 @@ impl Plugin for Core3dPlugin { .add_systems( Core3d, ( - early_prepass.before(Core3dSystems::EndPrepasses), - early_deferred_prepass - .after(early_prepass) - .before(Core3dSystems::EndPrepasses), - late_prepass - .after(early_deferred_prepass) - .before(Core3dSystems::EndPrepasses), - late_deferred_prepass - .after(late_prepass) - .before(Core3dSystems::EndPrepasses), - copy_deferred_lighting_id - .after(late_deferred_prepass) + ( + early_prepass, + early_deferred_prepass, + late_prepass, + late_deferred_prepass, + copy_deferred_lighting_id, + ) + .chain() .before(Core3dSystems::EndPrepasses), - main_opaque_pass_3d + ( + main_opaque_pass_3d, + main_transmissive_pass_3d, + main_transparent_pass_3d, + ) + .chain() .after(Core3dSystems::StartMainPass) .before(Core3dSystems::EndMainPass), - main_transmissive_pass_3d - .after(main_opaque_pass_3d) - .before(Core3dSystems::EndMainPass), - main_transparent_pass_3d - .after(main_transmissive_pass_3d) - .before(Core3dSystems::EndMainPass), tonemapping .after(Core3dSystems::StartMainPassPostProcessing) .before(Core3dSystems::PostProcessing), diff --git a/crates/bevy_pbr/src/atmosphere/mod.rs b/crates/bevy_pbr/src/atmosphere/mod.rs index 23f4cf04759ac..fcc9a225acf95 100644 --- a/crates/bevy_pbr/src/atmosphere/mod.rs +++ b/crates/bevy_pbr/src/atmosphere/mod.rs @@ -177,15 +177,13 @@ impl Plugin for AtmospherePlugin { .add_systems( Core3d, ( - atmosphere_luts + (atmosphere_luts, atmosphere_environment) + .chain() .after(Core3dSystems::EndPrepasses) .before(Core3dSystems::StartMainPass), render_sky .after(main_opaque_pass_3d) .before(main_transparent_pass_3d), - atmosphere_environment - .after(atmosphere_luts) - .before(Core3dSystems::StartMainPass), ), ); } diff --git a/crates/bevy_pbr/src/render/gpu_preprocess.rs b/crates/bevy_pbr/src/render/gpu_preprocess.rs index 5d4fadf9583b8..059e953912b20 100644 --- a/crates/bevy_pbr/src/render/gpu_preprocess.rs +++ b/crates/bevy_pbr/src/render/gpu_preprocess.rs @@ -364,31 +364,30 @@ impl Plugin for GpuMeshPreprocessPlugin { .add_systems( Core3d, ( - clear_indirect_parameters_metadata.before(early_prepass), - early_gpu_preprocess - .after(clear_indirect_parameters_metadata) - .before(early_prepass), - early_prepass_build_indirect_parameters - .run_if(any_match_filter::<( + ( + clear_indirect_parameters_metadata, + early_gpu_preprocess, + early_prepass_build_indirect_parameters.run_if(any_match_filter::<( With, Without, Without, Or<(With, With)>, - )>) - .after(early_gpu_preprocess) + )>), + ) + .chain() .before(early_prepass), - late_gpu_preprocess - .after(early_downsample_depth) - .before(late_prepass), - late_prepass_build_indirect_parameters - .run_if(any_match_filter::<( + ( + late_gpu_preprocess, + late_prepass_build_indirect_parameters.run_if(any_match_filter::<( With, Without, Without, Or<(With, With)>, With, - )>) - .after(late_gpu_preprocess) + )>), + ) + .chain() + .after(early_downsample_depth) .before(late_prepass), main_build_indirect_parameters .run_if(any_match_filter::<( From 7c8c843828d49b612658070f05b25252a8e270d2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?charlotte=20=F0=9F=8C=B8?= Date: Mon, 19 Jan 2026 14:00:12 -0800 Subject: [PATCH 35/55] Restore comments. --- .../src/mip_generation/experimental/depth.rs | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/crates/bevy_core_pipeline/src/mip_generation/experimental/depth.rs b/crates/bevy_core_pipeline/src/mip_generation/experimental/depth.rs index 5f9e507bab465..ed20c075f8da2 100644 --- a/crates/bevy_core_pipeline/src/mip_generation/experimental/depth.rs +++ b/crates/bevy_core_pipeline/src/mip_generation/experimental/depth.rs @@ -49,6 +49,18 @@ use tracing::debug; /// support. pub const DEPTH_PYRAMID_MIP_COUNT: usize = 12; +/// Produces a hierarchical Z-buffer (depth pyramid) for occlusion culling. +/// +/// This runs the single-pass downsampling (SPD) shader with the *min* filter in +/// order to generate a series of mipmaps for the Z buffer. The resulting +/// hierarchical Z-buffer can be used for occlusion culling. +/// +/// The *early* downsample depth pass is the first hierarchical Z-buffer stage, +/// which runs after the early prepass and before the late prepass. It prepares +/// the Z-buffer for the bounding box tests that the late mesh preprocessing +/// stage will perform. +/// +/// This system won't do anything if occlusion culling isn't on. pub fn early_downsample_depth( view: ViewQuery<( &ViewDepthPyramid, @@ -113,6 +125,17 @@ pub fn early_downsample_depth( } } +/// Produces a hierarchical Z-buffer (depth pyramid) for occlusion culling. +/// +/// This runs the single-pass downsampling (SPD) shader with the *min* filter in +/// order to generate a series of mipmaps for the Z buffer. The resulting +/// hierarchical Z-buffer can be used for occlusion culling. +/// +/// The *late* downsample depth pass runs at the end of the main phase. It +/// prepares the Z-buffer for the occlusion culling that the early mesh +/// preprocessing phase of the *next* frame will perform. +/// +/// This system won't do anything if occlusion culling isn't on. pub fn late_downsample_depth( view: ViewQuery<( &ViewDepthPyramid, From 8e4b968f8e4ab53ee3e75d41d8ba7667a9449143 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?charlotte=20=F0=9F=8C=B8?= Date: Mon, 19 Jan 2026 14:02:45 -0800 Subject: [PATCH 36/55] Fix render system ordering. --- crates/bevy_render/src/lib.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/crates/bevy_render/src/lib.rs b/crates/bevy_render/src/lib.rs index 51b300e734dba..0128964e304fe 100644 --- a/crates/bevy_render/src/lib.rs +++ b/crates/bevy_render/src/lib.rs @@ -483,8 +483,9 @@ unsafe fn initialize_render_app(app: &mut App) { // This set applies the commands from the extract schedule while the render schedule // is running in parallel with the main app. apply_extract_commands.in_set(RenderSystems::ExtractCommands), - render_system.in_set(RenderSystems::Render), - PipelineCache::process_pipeline_queue_system.in_set(RenderSystems::Render), + (PipelineCache::process_pipeline_queue_system, render_system) + .chain() + .in_set(RenderSystems::Render), despawn_temporary_render_entities.in_set(RenderSystems::PostCleanup), ), ); From c41b2e1afe03e6943c461b706c578f9ac08a916b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?charlotte=20=F0=9F=8C=B8?= Date: Mon, 19 Jan 2026 14:22:34 -0800 Subject: [PATCH 37/55] Add AntiAliasing system set. --- crates/bevy_anti_alias/src/lib.rs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/crates/bevy_anti_alias/src/lib.rs b/crates/bevy_anti_alias/src/lib.rs index 4e83f86e9a301..6a5b2a2066fa1 100644 --- a/crates/bevy_anti_alias/src/lib.rs +++ b/crates/bevy_anti_alias/src/lib.rs @@ -6,6 +6,7 @@ )] use bevy_app::Plugin; +use bevy_ecs::schedule::SystemSet; use contrast_adaptive_sharpening::CasPlugin; use fxaa::FxaaPlugin; use smaa::SmaaPlugin; @@ -18,6 +19,10 @@ pub mod fxaa; pub mod smaa; pub mod taa; +/// System set for ordering render graph systems relative to any anti-aliasing implementation. +#[derive(Debug, Hash, PartialEq, Eq, Clone, SystemSet)] +pub struct AntiAliasing; + /// Adds fxaa, smaa, taa, contrast aware sharpening, and optional dlss support. #[derive(Default)] pub struct AntiAliasPlugin; From a012cad9429ca7eec7366959ca46332f54c4a703 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?charlotte=20=F0=9F=8C=B8?= Date: Mon, 19 Jan 2026 15:03:01 -0800 Subject: [PATCH 38/55] Add AntiAliasing system set. --- .../src/contrast_adaptive_sharpening/mod.rs | 8 +++++--- crates/bevy_anti_alias/src/dlss/mod.rs | 4 +++- crates/bevy_anti_alias/src/fxaa/mod.rs | 7 +++++-- crates/bevy_anti_alias/src/smaa/mod.rs | 7 +++++-- crates/bevy_anti_alias/src/taa/mod.rs | 4 +++- 5 files changed, 21 insertions(+), 9 deletions(-) diff --git a/crates/bevy_anti_alias/src/contrast_adaptive_sharpening/mod.rs b/crates/bevy_anti_alias/src/contrast_adaptive_sharpening/mod.rs index c19678356639f..8866839d2c874 100644 --- a/crates/bevy_anti_alias/src/contrast_adaptive_sharpening/mod.rs +++ b/crates/bevy_anti_alias/src/contrast_adaptive_sharpening/mod.rs @@ -1,4 +1,4 @@ -use crate::fxaa::fxaa; +use crate::{fxaa::fxaa, AntiAliasing}; use bevy_app::prelude::*; use bevy_asset::{embedded_asset, load_embedded_asset, AssetServer}; use bevy_camera::Camera; @@ -116,12 +116,14 @@ impl Plugin for CasPlugin { .add_systems( Core3d, cas.after(fxaa) - .before(Core3dSystems::EndMainPassPostProcessing), + .before(Core3dSystems::EndMainPassPostProcessing) + .in_set(AntiAliasing), ) .add_systems( Core2d, cas.after(fxaa) - .before(Core2dSystems::EndMainPassPostProcessing), + .before(Core2dSystems::EndMainPassPostProcessing) + .in_set(AntiAliasing), ); } } diff --git a/crates/bevy_anti_alias/src/dlss/mod.rs b/crates/bevy_anti_alias/src/dlss/mod.rs index d942440e70053..c1661b561f3a5 100644 --- a/crates/bevy_anti_alias/src/dlss/mod.rs +++ b/crates/bevy_anti_alias/src/dlss/mod.rs @@ -20,6 +20,7 @@ mod prepare; pub use dlss_wgpu::DlssPerfQualityMode; +use crate::AntiAliasing; use bevy_app::{App, Plugin}; use bevy_core_pipeline::{ prepass::{DepthPrepass, MotionVectorPrepass}, @@ -195,7 +196,8 @@ impl Plugin for DlssPlugin { .chain() .after(motion_blur) .after(Core3dSystems::StartMainPassPostProcessing) - .before(bloom), + .before(bloom) + .in_set(AntiAliasing), ); } } diff --git a/crates/bevy_anti_alias/src/fxaa/mod.rs b/crates/bevy_anti_alias/src/fxaa/mod.rs index 1a9ef28e199e1..2717e5bd4c717 100644 --- a/crates/bevy_anti_alias/src/fxaa/mod.rs +++ b/crates/bevy_anti_alias/src/fxaa/mod.rs @@ -1,3 +1,4 @@ +use crate::AntiAliasing; use bevy_app::prelude::*; use bevy_asset::{embedded_asset, load_embedded_asset, AssetServer, Handle}; use bevy_camera::Camera; @@ -102,12 +103,14 @@ impl Plugin for FxaaPlugin { .add_systems( Core3d, fxaa.after(tonemapping) - .before(Core3dSystems::EndMainPassPostProcessing), + .before(Core3dSystems::EndMainPassPostProcessing) + .in_set(AntiAliasing), ) .add_systems( Core2d, fxaa.after(tonemapping) - .before(Core2dSystems::EndMainPassPostProcessing), + .before(Core2dSystems::EndMainPassPostProcessing) + .in_set(AntiAliasing), ); } } diff --git a/crates/bevy_anti_alias/src/smaa/mod.rs b/crates/bevy_anti_alias/src/smaa/mod.rs index 15da16ff79198..f40f287f1d3d8 100644 --- a/crates/bevy_anti_alias/src/smaa/mod.rs +++ b/crates/bevy_anti_alias/src/smaa/mod.rs @@ -29,6 +29,7 @@ //! * Compatibility with SSAA and MSAA. //! //! [SMAA]: https://www.iryoku.com/smaa/ +use crate::AntiAliasing; use bevy_app::{App, Plugin}; use bevy_asset::{embedded_asset, load_embedded_asset, AssetServer, Handle}; #[cfg(not(feature = "smaa_luts"))] @@ -346,12 +347,14 @@ impl Plugin for SmaaPlugin { .add_systems( Core3d, smaa.after(tonemapping) - .before(Core3dSystems::EndMainPassPostProcessing), + .before(Core3dSystems::EndMainPassPostProcessing) + .in_set(AntiAliasing), ) .add_systems( Core2d, smaa.after(tonemapping) - .before(Core2dSystems::EndMainPassPostProcessing), + .before(Core2dSystems::EndMainPassPostProcessing) + .in_set(AntiAliasing), ); } } diff --git a/crates/bevy_anti_alias/src/taa/mod.rs b/crates/bevy_anti_alias/src/taa/mod.rs index 0538ab55a49ff..a9a4296422605 100644 --- a/crates/bevy_anti_alias/src/taa/mod.rs +++ b/crates/bevy_anti_alias/src/taa/mod.rs @@ -1,3 +1,4 @@ +use crate::AntiAliasing; use bevy_app::{App, Plugin}; use bevy_asset::{embedded_asset, load_embedded_asset, AssetServer}; use bevy_camera::{Camera, Camera3d}; @@ -73,7 +74,8 @@ impl Plugin for TemporalAntiAliasPlugin { temporal_anti_alias .after(motion_blur) .after(Core3dSystems::StartMainPassPostProcessing) - .before(bloom), + .before(bloom) + .in_set(AntiAliasing), ); } } From 32a6f256a9b6b8591c6ad771eb43bc9ead2434db Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?charlotte=20=F0=9F=8C=B8?= Date: Mon, 19 Jan 2026 19:18:30 -0800 Subject: [PATCH 39/55] Use single system sets rather than begin/end system sets. --- .../src/contrast_adaptive_sharpening/mod.rs | 4 +- crates/bevy_anti_alias/src/dlss/mod.rs | 2 +- crates/bevy_anti_alias/src/fxaa/mod.rs | 4 +- crates/bevy_anti_alias/src/smaa/mod.rs | 4 +- crates/bevy_anti_alias/src/taa/mod.rs | 2 +- crates/bevy_core_pipeline/src/core_2d/mod.rs | 15 +++---- crates/bevy_core_pipeline/src/core_3d/mod.rs | 11 ++---- .../src/fullscreen_material.rs | 33 ++++++++++------ .../src/mip_generation/mod.rs | 4 +- crates/bevy_core_pipeline/src/oit/mod.rs | 2 +- crates/bevy_core_pipeline/src/schedule.rs | 39 ++++--------------- crates/bevy_dev_tools/src/render_debug.rs | 2 +- crates/bevy_pbr/src/atmosphere/mod.rs | 4 +- crates/bevy_pbr/src/deferred/mod.rs | 4 +- crates/bevy_pbr/src/lib.rs | 2 +- crates/bevy_pbr/src/meshlet/mod.rs | 8 ++-- crates/bevy_pbr/src/render/gpu_preprocess.rs | 2 +- crates/bevy_pbr/src/ssao/mod.rs | 4 +- crates/bevy_pbr/src/ssr/mod.rs | 4 +- crates/bevy_pbr/src/volumetric_fog/mod.rs | 4 +- crates/bevy_pbr/src/wireframe.rs | 4 +- .../src/auto_exposure/mod.rs | 4 +- crates/bevy_post_process/src/bloom/mod.rs | 8 +--- .../bevy_post_process/src/motion_blur/mod.rs | 4 +- .../bevy_post_process/src/msaa_writeback.rs | 4 +- crates/bevy_solari/src/pathtracer/mod.rs | 2 +- crates/bevy_solari/src/realtime/mod.rs | 4 +- .../src/mesh2d/wireframe2d.rs | 4 +- crates/bevy_ui_render/src/lib.rs | 8 +--- examples/3d/occlusion_culling.rs | 11 ++---- .../shader_advanced/custom_post_processing.rs | 4 +- .../shader_advanced/custom_render_phase.rs | 2 +- .../shader_advanced/fullscreen_material.rs | 3 +- .../render_depth_to_texture.rs | 4 +- 34 files changed, 90 insertions(+), 130 deletions(-) diff --git a/crates/bevy_anti_alias/src/contrast_adaptive_sharpening/mod.rs b/crates/bevy_anti_alias/src/contrast_adaptive_sharpening/mod.rs index 8866839d2c874..88b6c2d951543 100644 --- a/crates/bevy_anti_alias/src/contrast_adaptive_sharpening/mod.rs +++ b/crates/bevy_anti_alias/src/contrast_adaptive_sharpening/mod.rs @@ -116,13 +116,13 @@ impl Plugin for CasPlugin { .add_systems( Core3d, cas.after(fxaa) - .before(Core3dSystems::EndMainPassPostProcessing) + .in_set(Core3dSystems::PostProcess) .in_set(AntiAliasing), ) .add_systems( Core2d, cas.after(fxaa) - .before(Core2dSystems::EndMainPassPostProcessing) + .in_set(Core2dSystems::PostProcess) .in_set(AntiAliasing), ); } diff --git a/crates/bevy_anti_alias/src/dlss/mod.rs b/crates/bevy_anti_alias/src/dlss/mod.rs index c1661b561f3a5..0deed479c828c 100644 --- a/crates/bevy_anti_alias/src/dlss/mod.rs +++ b/crates/bevy_anti_alias/src/dlss/mod.rs @@ -195,8 +195,8 @@ impl Plugin for DlssPlugin { (node::dlss_super_resolution, node::dlss_ray_reconstruction) .chain() .after(motion_blur) - .after(Core3dSystems::StartMainPassPostProcessing) .before(bloom) + .in_set(Core3dSystems::PostProcess) .in_set(AntiAliasing), ); } diff --git a/crates/bevy_anti_alias/src/fxaa/mod.rs b/crates/bevy_anti_alias/src/fxaa/mod.rs index 2717e5bd4c717..0c9c7a7dd5ddb 100644 --- a/crates/bevy_anti_alias/src/fxaa/mod.rs +++ b/crates/bevy_anti_alias/src/fxaa/mod.rs @@ -103,13 +103,13 @@ impl Plugin for FxaaPlugin { .add_systems( Core3d, fxaa.after(tonemapping) - .before(Core3dSystems::EndMainPassPostProcessing) + .in_set(Core3dSystems::PostProcess) .in_set(AntiAliasing), ) .add_systems( Core2d, fxaa.after(tonemapping) - .before(Core2dSystems::EndMainPassPostProcessing) + .in_set(Core2dSystems::PostProcess) .in_set(AntiAliasing), ); } diff --git a/crates/bevy_anti_alias/src/smaa/mod.rs b/crates/bevy_anti_alias/src/smaa/mod.rs index f40f287f1d3d8..d3166a24a264e 100644 --- a/crates/bevy_anti_alias/src/smaa/mod.rs +++ b/crates/bevy_anti_alias/src/smaa/mod.rs @@ -347,13 +347,13 @@ impl Plugin for SmaaPlugin { .add_systems( Core3d, smaa.after(tonemapping) - .before(Core3dSystems::EndMainPassPostProcessing) + .in_set(Core3dSystems::PostProcess) .in_set(AntiAliasing), ) .add_systems( Core2d, smaa.after(tonemapping) - .before(Core2dSystems::EndMainPassPostProcessing) + .in_set(Core2dSystems::PostProcess) .in_set(AntiAliasing), ); } diff --git a/crates/bevy_anti_alias/src/taa/mod.rs b/crates/bevy_anti_alias/src/taa/mod.rs index a9a4296422605..07fbebf247cb8 100644 --- a/crates/bevy_anti_alias/src/taa/mod.rs +++ b/crates/bevy_anti_alias/src/taa/mod.rs @@ -73,8 +73,8 @@ impl Plugin for TemporalAntiAliasPlugin { Core3d, temporal_anti_alias .after(motion_blur) - .after(Core3dSystems::StartMainPassPostProcessing) .before(bloom) + .in_set(Core3dSystems::PostProcess) .in_set(AntiAliasing), ); } diff --git a/crates/bevy_core_pipeline/src/core_2d/mod.rs b/crates/bevy_core_pipeline/src/core_2d/mod.rs index f1eb40546690d..456924697f7b2 100644 --- a/crates/bevy_core_pipeline/src/core_2d/mod.rs +++ b/crates/bevy_core_pipeline/src/core_2d/mod.rs @@ -77,16 +77,11 @@ impl Plugin for Core2dPlugin { .add_systems( Core2d, ( - main_opaque_pass_2d - .after(Core2dSystems::StartMainPass) - .before(Core2dSystems::EndMainPass), - main_transparent_pass_2d - .after(main_opaque_pass_2d) - .before(Core2dSystems::EndMainPass), - tonemapping - .after(Core2dSystems::StartMainPassPostProcessing) - .before(Core2dSystems::PostProcessing), - upscaling.after(Core2dSystems::EndMainPassPostProcessing), + (main_opaque_pass_2d, main_transparent_pass_2d) + .chain() + .in_set(Core2dSystems::MainPass), + tonemapping.in_set(Core2dSystems::PostProcess), + upscaling.after(Core2dSystems::PostProcess), ), ); } diff --git a/crates/bevy_core_pipeline/src/core_3d/mod.rs b/crates/bevy_core_pipeline/src/core_3d/mod.rs index e8f4db3ba5d14..a17fe138e9255 100644 --- a/crates/bevy_core_pipeline/src/core_3d/mod.rs +++ b/crates/bevy_core_pipeline/src/core_3d/mod.rs @@ -152,19 +152,16 @@ impl Plugin for Core3dPlugin { copy_deferred_lighting_id, ) .chain() - .before(Core3dSystems::EndPrepasses), + .in_set(Core3dSystems::Prepass), ( main_opaque_pass_3d, main_transmissive_pass_3d, main_transparent_pass_3d, ) .chain() - .after(Core3dSystems::StartMainPass) - .before(Core3dSystems::EndMainPass), - tonemapping - .after(Core3dSystems::StartMainPassPostProcessing) - .before(Core3dSystems::PostProcessing), - upscaling.after(Core3dSystems::EndMainPassPostProcessing), + .in_set(Core3dSystems::MainPass), + tonemapping.in_set(Core3dSystems::PostProcess), + upscaling.after(Core3dSystems::PostProcess), ), ); } diff --git a/crates/bevy_core_pipeline/src/fullscreen_material.rs b/crates/bevy_core_pipeline/src/fullscreen_material.rs index e1f43bfeb298c..198c78b7b2e25 100644 --- a/crates/bevy_core_pipeline/src/fullscreen_material.rs +++ b/crates/bevy_core_pipeline/src/fullscreen_material.rs @@ -56,12 +56,14 @@ impl Plugin for FullscreenMaterialPlugin { render_app.add_systems(RenderStartup, init_pipeline::); - render_app.add_systems( - T::schedule(), - fullscreen_material_system:: - .after(T::run_after()) - .before(T::run_before()), - ); + let mut system = fullscreen_material_system::.in_set(T::run_in()); + if let Some(run_after) = T::run_after() { + system = system.after(run_after); + } + if let Some(run_before) = T::run_before() { + system = system.before(run_before); + } + render_app.add_systems(T::schedule(), system); } } @@ -88,18 +90,25 @@ pub trait FullscreenMaterial: Core3d } + /// The system set this effect belongs to. + /// + /// Defaults to [`Core3dSystems::PostProcess`]. + fn run_in() -> impl SystemSet { + Core3dSystems::PostProcess + } + /// The system set this effect runs after. /// - /// Defaults to [`Core3dSystems::PostProcessing`]. - fn run_after() -> impl SystemSet { - Core3dSystems::PostProcessing + /// Defaults to `None`. + fn run_after() -> Option { + None } /// The system set this effect runs before. /// - /// Defaults to [`Core3dSystems::EndMainPassPostProcessing`]. - fn run_before() -> impl SystemSet { - Core3dSystems::EndMainPassPostProcessing + /// Defaults to `None`. + fn run_before() -> Option { + None } } diff --git a/crates/bevy_core_pipeline/src/mip_generation/mod.rs b/crates/bevy_core_pipeline/src/mip_generation/mod.rs index 766ae812b402e..022d4cca701fb 100644 --- a/crates/bevy_core_pipeline/src/mip_generation/mod.rs +++ b/crates/bevy_core_pipeline/src/mip_generation/mod.rs @@ -298,9 +298,7 @@ impl Plugin for MipGenerationPlugin { early_downsample_depth .after(early_deferred_prepass) .before(late_prepass), - late_downsample_depth - .after(Core3dSystems::StartMainPassPostProcessing) - .before(Core3dSystems::EndMainPassPostProcessing), + late_downsample_depth.in_set(Core3dSystems::PostProcess), ), ) .add_systems( diff --git a/crates/bevy_core_pipeline/src/oit/mod.rs b/crates/bevy_core_pipeline/src/oit/mod.rs index 62d958ee46a2d..a8f5c1e15031e 100644 --- a/crates/bevy_core_pipeline/src/oit/mod.rs +++ b/crates/bevy_core_pipeline/src/oit/mod.rs @@ -120,7 +120,7 @@ impl Plugin for OrderIndependentTransparencyPlugin { Core3d, oit_resolve .after(main_transparent_pass_3d) - .before(Core3dSystems::EndMainPass), + .in_set(Core3dSystems::MainPass), ); } } diff --git a/crates/bevy_core_pipeline/src/schedule.rs b/crates/bevy_core_pipeline/src/schedule.rs index 166f87ec5bf32..8363bb9ac6cb0 100644 --- a/crates/bevy_core_pipeline/src/schedule.rs +++ b/crates/bevy_core_pipeline/src/schedule.rs @@ -21,12 +21,9 @@ pub struct Core3d; #[derive(SystemSet, Debug, Clone, PartialEq, Eq, Hash)] pub enum Core3dSystems { - EndPrepasses, - StartMainPass, - EndMainPass, - StartMainPassPostProcessing, - PostProcessing, - EndMainPassPostProcessing, + Prepass, + MainPass, + PostProcess, } impl Core3d { @@ -41,17 +38,7 @@ impl Core3d { ..Default::default() }); - schedule.configure_sets( - ( - EndPrepasses, - StartMainPass, - EndMainPass, - StartMainPassPostProcessing, - PostProcessing, - EndMainPassPostProcessing, - ) - .chain(), - ); + schedule.configure_sets((Prepass, MainPass, PostProcess).chain()); schedule } @@ -62,11 +49,8 @@ pub struct Core2d; #[derive(SystemSet, Debug, Clone, PartialEq, Eq, Hash)] pub enum Core2dSystems { - StartMainPass, - EndMainPass, - StartMainPassPostProcessing, - PostProcessing, - EndMainPassPostProcessing, + MainPass, + PostProcess, } impl Core2d { @@ -81,16 +65,7 @@ impl Core2d { ..Default::default() }); - schedule.configure_sets( - ( - StartMainPass, - EndMainPass, - StartMainPassPostProcessing, - PostProcessing, - EndMainPassPostProcessing, - ) - .chain(), - ); + schedule.configure_sets((MainPass, PostProcess).chain()); schedule } diff --git a/crates/bevy_dev_tools/src/render_debug.rs b/crates/bevy_dev_tools/src/render_debug.rs index 11da763a7faa3..bba0c9ba607b5 100644 --- a/crates/bevy_dev_tools/src/render_debug.rs +++ b/crates/bevy_dev_tools/src/render_debug.rs @@ -83,7 +83,7 @@ impl Plugin for RenderDebugOverlayPlugin { ) .add_systems( Core3d, - render_debug_overlay.in_set(Core3dSystems::PostProcessing), + render_debug_overlay.in_set(Core3dSystems::PostProcess), ); } } diff --git a/crates/bevy_pbr/src/atmosphere/mod.rs b/crates/bevy_pbr/src/atmosphere/mod.rs index fcc9a225acf95..cb46388a1a10f 100644 --- a/crates/bevy_pbr/src/atmosphere/mod.rs +++ b/crates/bevy_pbr/src/atmosphere/mod.rs @@ -179,8 +179,8 @@ impl Plugin for AtmospherePlugin { ( (atmosphere_luts, atmosphere_environment) .chain() - .after(Core3dSystems::EndPrepasses) - .before(Core3dSystems::StartMainPass), + .after(Core3dSystems::Prepass) + .before(Core3dSystems::MainPass), render_sky .after(main_opaque_pass_3d) .before(main_transparent_pass_3d), diff --git a/crates/bevy_pbr/src/deferred/mod.rs b/crates/bevy_pbr/src/deferred/mod.rs index 8e2ad6f1a7404..9f03793ea906c 100644 --- a/crates/bevy_pbr/src/deferred/mod.rs +++ b/crates/bevy_pbr/src/deferred/mod.rs @@ -117,8 +117,8 @@ impl Plugin for DeferredPbrLightingPlugin { .add_systems( Core3d, deferred_lighting - .after(Core3dSystems::StartMainPass) - .before(main_opaque_pass_3d), + .before(main_opaque_pass_3d) + .in_set(Core3dSystems::MainPass), ); } } diff --git a/crates/bevy_pbr/src/lib.rs b/crates/bevy_pbr/src/lib.rs index 678ee1f70759e..479f88fff61fa 100644 --- a/crates/bevy_pbr/src/lib.rs +++ b/crates/bevy_pbr/src/lib.rs @@ -327,7 +327,7 @@ impl Plugin for PbrPlugin { shadow_pass:: .after(late_prepass_build_indirect_parameters) .before(main_build_indirect_parameters) - .before(Core3dSystems::StartMainPass), + .before(Core3dSystems::MainPass), ), ); } diff --git a/crates/bevy_pbr/src/meshlet/mod.rs b/crates/bevy_pbr/src/meshlet/mod.rs index d099afa5dcd50..e5ffcc4a8eedc 100644 --- a/crates/bevy_pbr/src/meshlet/mod.rs +++ b/crates/bevy_pbr/src/meshlet/mod.rs @@ -200,13 +200,13 @@ impl Plugin for MeshletPlugin { meshlet_visibility_buffer_raster.before(shadow_pass::), meshlet_prepass .after(shadow_pass::) - .before(Core3dSystems::EndPrepasses), + .in_set(Core3dSystems::Prepass), meshlet_deferred_gbuffer_prepass .after(meshlet_prepass) - .before(Core3dSystems::EndPrepasses), + .in_set(Core3dSystems::Prepass), meshlet_main_opaque_pass - .after(Core3dSystems::StartMainPass) - .before(main_opaque_pass_3d), + .before(main_opaque_pass_3d) + .in_set(Core3dSystems::MainPass), ), ); } diff --git a/crates/bevy_pbr/src/render/gpu_preprocess.rs b/crates/bevy_pbr/src/render/gpu_preprocess.rs index 059e953912b20..388c76405b43d 100644 --- a/crates/bevy_pbr/src/render/gpu_preprocess.rs +++ b/crates/bevy_pbr/src/render/gpu_preprocess.rs @@ -397,7 +397,7 @@ impl Plugin for GpuMeshPreprocessPlugin { )>) .after(late_prepass_build_indirect_parameters) .after(late_deferred_prepass) - .before(Core3dSystems::StartMainPass), + .before(Core3dSystems::MainPass), ), ); } diff --git a/crates/bevy_pbr/src/ssao/mod.rs b/crates/bevy_pbr/src/ssao/mod.rs index 15bdc05d6091b..edb800a7260e3 100644 --- a/crates/bevy_pbr/src/ssao/mod.rs +++ b/crates/bevy_pbr/src/ssao/mod.rs @@ -84,8 +84,8 @@ impl Plugin for ScreenSpaceAmbientOcclusionPlugin { render_app.add_systems( Core3d, - ssao.after(Core3dSystems::EndPrepasses) - .before(Core3dSystems::StartMainPass), + ssao.after(Core3dSystems::Prepass) + .before(Core3dSystems::MainPass), ); } } diff --git a/crates/bevy_pbr/src/ssr/mod.rs b/crates/bevy_pbr/src/ssr/mod.rs index 86f72df93eeda..8ab8910dd853a 100644 --- a/crates/bevy_pbr/src/ssr/mod.rs +++ b/crates/bevy_pbr/src/ssr/mod.rs @@ -224,8 +224,8 @@ impl Plugin for ScreenSpaceReflectionsPlugin { Core3d, screen_space_reflections .after(deferred_lighting) - .after(Core3dSystems::StartMainPass) - .before(main_opaque_pass_3d), + .before(main_opaque_pass_3d) + .in_set(Core3dSystems::MainPass), ); } } diff --git a/crates/bevy_pbr/src/volumetric_fog/mod.rs b/crates/bevy_pbr/src/volumetric_fog/mod.rs index 816a52fa219d2..e3b6c57c75824 100644 --- a/crates/bevy_pbr/src/volumetric_fog/mod.rs +++ b/crates/bevy_pbr/src/volumetric_fog/mod.rs @@ -97,8 +97,8 @@ impl Plugin for VolumetricFogPlugin { .add_systems( Core3d, volumetric_fog - .after(Core3dSystems::EndMainPass) - .before(Core3dSystems::StartMainPassPostProcessing), + .after(Core3dSystems::MainPass) + .before(Core3dSystems::PostProcess), ); } } diff --git a/crates/bevy_pbr/src/wireframe.rs b/crates/bevy_pbr/src/wireframe.rs index a184531109fd3..880554677cdb9 100644 --- a/crates/bevy_pbr/src/wireframe.rs +++ b/crates/bevy_pbr/src/wireframe.rs @@ -133,8 +133,8 @@ impl Plugin for WireframePlugin { .add_systems( Core3d, wireframe_3d - .after(Core3dSystems::EndMainPass) - .before(Core3dSystems::StartMainPassPostProcessing), + .after(Core3dSystems::MainPass) + .before(Core3dSystems::PostProcess), ) .add_systems( ExtractSchedule, diff --git a/crates/bevy_post_process/src/auto_exposure/mod.rs b/crates/bevy_post_process/src/auto_exposure/mod.rs index 9197e1a4fe132..5d3ca904ddec8 100644 --- a/crates/bevy_post_process/src/auto_exposure/mod.rs +++ b/crates/bevy_post_process/src/auto_exposure/mod.rs @@ -76,8 +76,8 @@ impl Plugin for AutoExposurePlugin { .add_systems( Core3d, node::auto_exposure - .after(Core3dSystems::StartMainPassPostProcessing) - .before(tonemapping), + .before(tonemapping) + .in_set(Core3dSystems::PostProcess), ); } } diff --git a/crates/bevy_post_process/src/bloom/mod.rs b/crates/bevy_post_process/src/bloom/mod.rs index aa77ef2b0c40f..df1ad75d60dfe 100644 --- a/crates/bevy_post_process/src/bloom/mod.rs +++ b/crates/bevy_post_process/src/bloom/mod.rs @@ -76,15 +76,11 @@ impl Plugin for BloomPlugin { ) .add_systems( Core3d, - bloom - .after(Core3dSystems::StartMainPassPostProcessing) - .before(tonemapping), + bloom.before(tonemapping).in_set(Core3dSystems::PostProcess), ) .add_systems( Core2d, - bloom - .after(Core2dSystems::StartMainPassPostProcessing) - .before(tonemapping), + bloom.before(tonemapping).in_set(Core2dSystems::PostProcess), ); } } diff --git a/crates/bevy_post_process/src/motion_blur/mod.rs b/crates/bevy_post_process/src/motion_blur/mod.rs index 0262cc22c6fe5..a829fb20fc056 100644 --- a/crates/bevy_post_process/src/motion_blur/mod.rs +++ b/crates/bevy_post_process/src/motion_blur/mod.rs @@ -154,8 +154,8 @@ impl Plugin for MotionBlurPlugin { render_app.add_systems( Core3d, node::motion_blur - .after(Core3dSystems::StartMainPassPostProcessing) - .before(bloom), + .before(bloom) + .in_set(Core3dSystems::PostProcess), ); } } diff --git a/crates/bevy_post_process/src/msaa_writeback.rs b/crates/bevy_post_process/src/msaa_writeback.rs index 8d7f14e3797d8..2714da95f66ea 100644 --- a/crates/bevy_post_process/src/msaa_writeback.rs +++ b/crates/bevy_post_process/src/msaa_writeback.rs @@ -29,8 +29,8 @@ impl Plugin for MsaaWritebackPlugin { Render, prepare_msaa_writeback_pipelines.in_set(RenderSystems::Prepare), ); - render_app.add_systems(Core3d, msaa_writeback.before(Core3dSystems::StartMainPass)); - render_app.add_systems(Core2d, msaa_writeback.before(Core2dSystems::StartMainPass)); + render_app.add_systems(Core3d, msaa_writeback.before(Core3dSystems::MainPass)); + render_app.add_systems(Core2d, msaa_writeback.before(Core2dSystems::MainPass)); } } diff --git a/crates/bevy_solari/src/pathtracer/mod.rs b/crates/bevy_solari/src/pathtracer/mod.rs index 74cdca9eb1956..ae456c716f7a6 100644 --- a/crates/bevy_solari/src/pathtracer/mod.rs +++ b/crates/bevy_solari/src/pathtracer/mod.rs @@ -48,7 +48,7 @@ impl Plugin for PathtracingPlugin { Render, prepare_pathtracer_accumulation_texture.in_set(RenderSystems::PrepareResources), ) - .add_systems(Core3d, pathtracer.after(Core3dSystems::EndMainPass)); + .add_systems(Core3d, pathtracer.after(Core3dSystems::MainPass)); } } diff --git a/crates/bevy_solari/src/realtime/mod.rs b/crates/bevy_solari/src/realtime/mod.rs index 534f2663846bf..797c91abdfbaf 100644 --- a/crates/bevy_solari/src/realtime/mod.rs +++ b/crates/bevy_solari/src/realtime/mod.rs @@ -71,8 +71,8 @@ impl Plugin for SolariLightingPlugin { .add_systems( Core3d, solari_lighting - .after(Core3dSystems::EndPrepasses) - .before(Core3dSystems::EndMainPass), + .after(Core3dSystems::Prepass) + .in_set(Core3dSystems::MainPass), ); } } diff --git a/crates/bevy_sprite_render/src/mesh2d/wireframe2d.rs b/crates/bevy_sprite_render/src/mesh2d/wireframe2d.rs index 9b1d05055d5ca..e56a43e37ca73 100644 --- a/crates/bevy_sprite_render/src/mesh2d/wireframe2d.rs +++ b/crates/bevy_sprite_render/src/mesh2d/wireframe2d.rs @@ -118,8 +118,8 @@ impl Plugin for Wireframe2dPlugin { .add_systems( Core2d, wireframe_2d - .after(Core2dSystems::EndMainPass) - .before(Core2dSystems::StartMainPassPostProcessing), + .after(Core2dSystems::MainPass) + .before(Core2dSystems::PostProcess), ) .add_systems( RenderStartup, diff --git a/crates/bevy_ui_render/src/lib.rs b/crates/bevy_ui_render/src/lib.rs index 34866516bf13b..c8b165c0eade0 100644 --- a/crates/bevy_ui_render/src/lib.rs +++ b/crates/bevy_ui_render/src/lib.rs @@ -249,15 +249,11 @@ impl Plugin for UiRenderPlugin { ) .add_systems( Core2d, - ui_pass - .after(Core2dSystems::EndMainPassPostProcessing) - .before(upscaling), + ui_pass.after(Core2dSystems::PostProcess).before(upscaling), ) .add_systems( Core3d, - ui_pass - .after(Core3dSystems::EndMainPassPostProcessing) - .before(upscaling), + ui_pass.after(Core3dSystems::PostProcess).before(upscaling), ); app.add_plugins(UiTextureSlicerPlugin); diff --git a/examples/3d/occlusion_culling.rs b/examples/3d/occlusion_culling.rs index f48601154fa4f..a0d66fddda7e0 100644 --- a/examples/3d/occlusion_culling.rs +++ b/examples/3d/occlusion_culling.rs @@ -228,13 +228,10 @@ impl Plugin for ReadbackIndirectParametersPlugin { // meshes were culled. readback_indirect_parameters_node // We read back the indirect parameters any time after - // `EndMainPass`. Readback doesn't particularly need to execute - // before `EndMainPassPostProcessing`, but we specify that anyway - // because we want to make the indirect parameters run before - // *something* in the graph, and `EndMainPassPostProcessing` is a - // good a node as any other. - .after(Core3dSystems::EndMainPass) - .before(Core3dSystems::EndMainPassPostProcessing), + // `MainPass`. Readback doesn't particularly need to execute + // before PostProcess, but we order it that way anyway. + .after(Core3dSystems::MainPass) + .before(Core3dSystems::PostProcess), ); } } diff --git a/examples/shader_advanced/custom_post_processing.rs b/examples/shader_advanced/custom_post_processing.rs index 8f114125732e2..81b922c308630 100644 --- a/examples/shader_advanced/custom_post_processing.rs +++ b/examples/shader_advanced/custom_post_processing.rs @@ -62,9 +62,7 @@ impl Plugin for PostProcessPlugin { render_app.add_systems(RenderStartup, init_post_process_pipeline); render_app.add_systems( Core3d, - post_process_system - .after(Core3dSystems::PostProcessing) - .before(Core3dSystems::EndMainPassPostProcessing), + post_process_system.in_set(Core3dSystems::PostProcess), ); } } diff --git a/examples/shader_advanced/custom_render_phase.rs b/examples/shader_advanced/custom_render_phase.rs index c6eb5462085a9..e75cb814137e7 100644 --- a/examples/shader_advanced/custom_render_phase.rs +++ b/examples/shader_advanced/custom_render_phase.rs @@ -139,7 +139,7 @@ impl Plugin for MeshStencilPhasePlugin { Core3d, custom_draw_system .after(main_opaque_pass_3d) - .before(Core3dSystems::EndMainPass), + .in_set(Core3dSystems::MainPass), ); } } diff --git a/examples/shader_advanced/fullscreen_material.rs b/examples/shader_advanced/fullscreen_material.rs index c9d95b209ca49..4e8f24c7724df 100644 --- a/examples/shader_advanced/fullscreen_material.rs +++ b/examples/shader_advanced/fullscreen_material.rs @@ -50,6 +50,5 @@ impl FullscreenMaterial for FullscreenEffect { "shaders/fullscreen_effect.wgsl".into() } - // Uses default run_after (Core3dSystems::PostProcessing) and - // run_before (Core3dSystems::EndMainPassPostProcessing) + // Uses default run_in (Core3dSystems::PostProcess) } diff --git a/examples/shader_advanced/render_depth_to_texture.rs b/examples/shader_advanced/render_depth_to_texture.rs index a9b66cb51b0f2..4607c96b40aba 100644 --- a/examples/shader_advanced/render_depth_to_texture.rs +++ b/examples/shader_advanced/render_depth_to_texture.rs @@ -117,8 +117,8 @@ fn main() { render_app.add_systems( Core3d, copy_depth_texture_system - .after(Core3dSystems::EndPrepasses) - .before(Core3dSystems::StartMainPass), + .after(Core3dSystems::Prepass) + .before(Core3dSystems::MainPass), ); app.run(); From bd962c276d3c32f9c23254bf1a04941124ead4c0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?charlotte=20=F0=9F=8C=B8?= Date: Mon, 19 Jan 2026 19:47:17 -0800 Subject: [PATCH 40/55] Try fixing solari again. --- crates/bevy_solari/src/realtime/node.rs | 327 ++++-------------------- 1 file changed, 53 insertions(+), 274 deletions(-) diff --git a/crates/bevy_solari/src/realtime/node.rs b/crates/bevy_solari/src/realtime/node.rs index 2506a3923f348..e65188bd1c410 100644 --- a/crates/bevy_solari/src/realtime/node.rs +++ b/crates/bevy_solari/src/realtime/node.rs @@ -231,7 +231,7 @@ pub fn init_solari_lighting_pipelines( "initial_and_temporal", load_embedded_asset!(asset_server.as_ref(), "restir_gi.wgsl"), None, - vec![], + vec!["WORLD_CACHE_FIRST_BOUNCE_LIGHT_LEAK_PREVENTION".into()], ), gi_spatial_and_shade_pipeline: create_pipeline( "solari_lighting_gi_spatial_and_shade_pipeline", @@ -261,21 +261,34 @@ pub fn init_solari_lighting_pipelines( "resolve_dlss_rr_textures", load_embedded_asset!(asset_server.as_ref(), "resolve_dlss_rr_textures.wgsl"), Some(&bind_group_layout_resolve_dlss_rr_textures), - vec![], + vec!["DLSS_RR_GUIDE_BUFFERS".into()], ), }); } #[cfg(any(not(feature = "dlss"), feature = "force_disable_dlss"))] +type SolariLightingViewQuery = ( + &'static SolariLighting, + &'static SolariLightingResources, + &'static ViewTarget, + &'static ViewPrepassTextures, + &'static ViewUniformOffset, + &'static PreviousViewUniformOffset, +); + +#[cfg(all(feature = "dlss", not(feature = "force_disable_dlss")))] +type SolariLightingViewQuery = ( + &'static SolariLighting, + &'static SolariLightingResources, + &'static ViewTarget, + &'static ViewPrepassTextures, + &'static ViewUniformOffset, + &'static PreviousViewUniformOffset, + Option<&'static ViewDlssRayReconstructionTextures>, +); + pub fn solari_lighting( - view: ViewQuery<( - &SolariLighting, - &SolariLightingResources, - &ViewTarget, - &ViewPrepassTextures, - &ViewUniformOffset, - &PreviousViewUniformOffset, - )>, + view: ViewQuery, solari_pipelines: Option>, pipeline_cache: Res, scene_bindings: Res, @@ -285,6 +298,7 @@ pub fn solari_lighting( render_device: Res, mut ctx: RenderContext, ) { + #[cfg(any(not(feature = "dlss"), feature = "force_disable_dlss"))] let ( solari_lighting, solari_lighting_resources, @@ -294,258 +308,7 @@ pub fn solari_lighting( previous_view_uniform_offset, ) = view.into_inner(); - let Some(pipelines) = solari_pipelines else { - return; - }; - - let ( - Some(decay_world_cache_pipeline), - Some(compact_world_cache_single_block_pipeline), - Some(compact_world_cache_blocks_pipeline), - Some(compact_world_cache_write_active_cells_pipeline), - Some(sample_di_for_world_cache_pipeline), - Some(sample_gi_for_world_cache_pipeline), - Some(blend_new_world_cache_samples_pipeline), - Some(presample_light_tiles_pipeline), - Some(di_initial_and_temporal_pipeline), - Some(di_spatial_and_shade_pipeline), - Some(gi_initial_and_temporal_pipeline), - Some(gi_spatial_and_shade_pipeline), - Some(specular_gi_pipeline), - Some(scene_bind_group), - Some(gbuffer), - Some(depth_buffer), - Some(motion_vectors), - Some(previous_gbuffer), - Some(previous_depth_buffer), - Some(view_uniforms_binding), - Some(previous_view_uniforms_binding), - ) = ( - pipeline_cache.get_compute_pipeline(pipelines.decay_world_cache_pipeline), - pipeline_cache.get_compute_pipeline(pipelines.compact_world_cache_single_block_pipeline), - pipeline_cache.get_compute_pipeline(pipelines.compact_world_cache_blocks_pipeline), - pipeline_cache - .get_compute_pipeline(pipelines.compact_world_cache_write_active_cells_pipeline), - pipeline_cache.get_compute_pipeline(pipelines.sample_di_for_world_cache_pipeline), - pipeline_cache.get_compute_pipeline(pipelines.sample_gi_for_world_cache_pipeline), - pipeline_cache.get_compute_pipeline(pipelines.blend_new_world_cache_samples_pipeline), - pipeline_cache.get_compute_pipeline(pipelines.presample_light_tiles_pipeline), - pipeline_cache.get_compute_pipeline(pipelines.di_initial_and_temporal_pipeline), - pipeline_cache.get_compute_pipeline(pipelines.di_spatial_and_shade_pipeline), - pipeline_cache.get_compute_pipeline(pipelines.gi_initial_and_temporal_pipeline), - pipeline_cache.get_compute_pipeline(pipelines.gi_spatial_and_shade_pipeline), - pipeline_cache.get_compute_pipeline(pipelines.specular_gi_pipeline), - &scene_bindings.bind_group, - view_prepass_textures.deferred_view(), - view_prepass_textures.depth_view(), - view_prepass_textures.motion_vectors_view(), - view_prepass_textures.previous_deferred_view(), - view_prepass_textures.previous_depth_view(), - view_uniforms.uniforms.binding(), - previous_view_uniforms.uniforms.binding(), - ) - else { - return; - }; - - let view_target_attachment = view_target.get_unsampled_color_attachment(); - - let s = solari_lighting_resources; - let bind_group = render_device.create_bind_group( - "solari_lighting_bind_group", - &pipeline_cache.get_bind_group_layout(&pipelines.bind_group_layout), - &BindGroupEntries::sequential(( - view_target_attachment.view, - s.light_tile_samples.as_entire_binding(), - s.light_tile_resolved_samples.as_entire_binding(), - &s.di_reservoirs_a, - &s.di_reservoirs_b, - s.gi_reservoirs_a.as_entire_binding(), - s.gi_reservoirs_b.as_entire_binding(), - gbuffer, - depth_buffer, - motion_vectors, - previous_gbuffer, - previous_depth_buffer, - view_uniforms_binding, - previous_view_uniforms_binding, - s.world_cache_checksums.as_entire_binding(), - s.world_cache_life.as_entire_binding(), - s.world_cache_radiance.as_entire_binding(), - s.world_cache_geometry_data.as_entire_binding(), - s.world_cache_luminance_deltas.as_entire_binding(), - s.world_cache_active_cells_new_radiance.as_entire_binding(), - s.world_cache_a.as_entire_binding(), - s.world_cache_b.as_entire_binding(), - s.world_cache_active_cell_indices.as_entire_binding(), - s.world_cache_active_cells_count.as_entire_binding(), - )), - ); - let bind_group_world_cache_active_cells_dispatch = render_device.create_bind_group( - "solari_lighting_bind_group_world_cache_active_cells_dispatch", - &pipeline_cache - .get_bind_group_layout(&pipelines.bind_group_layout_world_cache_active_cells_dispatch), - &BindGroupEntries::single(s.world_cache_active_cells_dispatch.as_entire_binding()), - ); - - // Choice of number here is arbitrary - let frame_index = frame_count.0.wrapping_mul(5782582); - - let diagnostics = ctx.diagnostic_recorder(); - let diagnostics = diagnostics.as_deref(); - - let command_encoder = ctx.command_encoder(); - - // Clear the view target if we're the first node to write to it - if matches!(view_target_attachment.ops.load, LoadOp::Clear(_)) { - command_encoder.begin_render_pass(&RenderPassDescriptor { - label: Some("solari_lighting_clear"), - color_attachments: &[Some(view_target_attachment)], - depth_stencil_attachment: None, - timestamp_writes: None, - occlusion_query_set: None, - }); - } - - let mut pass = command_encoder.begin_compute_pass(&ComputePassDescriptor { - label: Some("solari_lighting"), - timestamp_writes: None, - }); - - let dx = solari_lighting_resources.view_size.x.div_ceil(8); - let dy = solari_lighting_resources.view_size.y.div_ceil(8); - - pass.set_bind_group(0, scene_bind_group, &[]); - pass.set_bind_group( - 1, - &bind_group, - &[ - view_uniform_offset.offset, - previous_view_uniform_offset.offset, - ], - ); - - let d = diagnostics.time_span(&mut pass, "solari_lighting/presample_light_tiles"); - pass.set_pipeline(presample_light_tiles_pipeline); - pass.set_push_constants( - 0, - bytemuck::cast_slice(&[frame_index, solari_lighting.reset as u32]), - ); - pass.dispatch_workgroups(LIGHT_TILE_BLOCKS as u32, 1, 1); - d.end(&mut pass); - - let d = diagnostics.time_span(&mut pass, "solari_lighting/world_cache"); - - pass.set_bind_group(2, &bind_group_world_cache_active_cells_dispatch, &[]); - - pass.set_pipeline(decay_world_cache_pipeline); - pass.dispatch_workgroups((WORLD_CACHE_SIZE / 1024) as u32, 1, 1); - - pass.set_pipeline(compact_world_cache_single_block_pipeline); - pass.dispatch_workgroups((WORLD_CACHE_SIZE / 1024) as u32, 1, 1); - - pass.set_pipeline(compact_world_cache_blocks_pipeline); - pass.dispatch_workgroups(1, 1, 1); - - pass.set_pipeline(compact_world_cache_write_active_cells_pipeline); - pass.dispatch_workgroups((WORLD_CACHE_SIZE / 1024) as u32, 1, 1); - - pass.set_bind_group(2, None, &[]); - - pass.set_pipeline(sample_di_for_world_cache_pipeline); - pass.set_push_constants( - 0, - bytemuck::cast_slice(&[frame_index, solari_lighting.reset as u32]), - ); - pass.dispatch_workgroups_indirect( - &solari_lighting_resources.world_cache_active_cells_dispatch, - 0, - ); - - pass.set_pipeline(sample_gi_for_world_cache_pipeline); - pass.set_push_constants( - 0, - bytemuck::cast_slice(&[frame_index, solari_lighting.reset as u32]), - ); - pass.dispatch_workgroups_indirect( - &solari_lighting_resources.world_cache_active_cells_dispatch, - 0, - ); - - pass.set_pipeline(blend_new_world_cache_samples_pipeline); - pass.dispatch_workgroups_indirect( - &solari_lighting_resources.world_cache_active_cells_dispatch, - 0, - ); - - d.end(&mut pass); - - let d = diagnostics.time_span(&mut pass, "solari_lighting/direct_lighting"); - - pass.set_pipeline(di_initial_and_temporal_pipeline); - pass.set_push_constants( - 0, - bytemuck::cast_slice(&[frame_index, solari_lighting.reset as u32]), - ); - pass.dispatch_workgroups(dx, dy, 1); - - pass.set_pipeline(di_spatial_and_shade_pipeline); - pass.set_push_constants( - 0, - bytemuck::cast_slice(&[frame_index, solari_lighting.reset as u32]), - ); - pass.dispatch_workgroups(dx, dy, 1); - - d.end(&mut pass); - - let d = diagnostics.time_span(&mut pass, "solari_lighting/diffuse_indirect_lighting"); - - pass.set_pipeline(gi_initial_and_temporal_pipeline); - pass.set_push_constants( - 0, - bytemuck::cast_slice(&[frame_index, solari_lighting.reset as u32]), - ); - pass.dispatch_workgroups(dx, dy, 1); - - pass.set_pipeline(gi_spatial_and_shade_pipeline); - pass.set_push_constants( - 0, - bytemuck::cast_slice(&[frame_index, solari_lighting.reset as u32]), - ); - pass.dispatch_workgroups(dx, dy, 1); - - d.end(&mut pass); - - let d = diagnostics.time_span(&mut pass, "solari_lighting/specular_indirect_lighting"); - pass.set_pipeline(specular_gi_pipeline); - pass.set_push_constants( - 0, - bytemuck::cast_slice(&[frame_index, solari_lighting.reset as u32]), - ); - pass.dispatch_workgroups(dx, dy, 1); - d.end(&mut pass); -} - -#[cfg(all(feature = "dlss", not(feature = "force_disable_dlss")))] -pub fn solari_lighting( - view: ViewQuery<( - &SolariLighting, - &SolariLightingResources, - &ViewTarget, - &ViewPrepassTextures, - &ViewUniformOffset, - &PreviousViewUniformOffset, - Option<&ViewDlssRayReconstructionTextures>, - )>, - solari_pipelines: Option>, - pipeline_cache: Res, - scene_bindings: Res, - view_uniforms: Res, - previous_view_uniforms: Res, - frame_count: Res, - render_device: Res, - mut ctx: RenderContext, -) { + #[cfg(all(feature = "dlss", not(feature = "force_disable_dlss")))] let ( solari_lighting, solari_lighting_resources, @@ -560,6 +323,15 @@ pub fn solari_lighting( return; }; + #[cfg(not(all(feature = "dlss", not(feature = "force_disable_dlss"))))] + let specular_gi_pipeline = pipelines.specular_gi_pipeline; + #[cfg(all(feature = "dlss", not(feature = "force_disable_dlss")))] + let specular_gi_pipeline = if view_dlss_rr_textures.is_some() { + pipelines.specular_gi_with_psr_pipeline + } else { + pipelines.specular_gi_pipeline + }; + let ( Some(decay_world_cache_pipeline), Some(compact_world_cache_single_block_pipeline), @@ -573,8 +345,7 @@ pub fn solari_lighting( Some(di_spatial_and_shade_pipeline), Some(gi_initial_and_temporal_pipeline), Some(gi_spatial_and_shade_pipeline), - Some(specular_gi_pipeline_regular), - Some(specular_gi_pipeline_with_psr), + Some(specular_gi_pipeline), Some(scene_bind_group), Some(gbuffer), Some(depth_buffer), @@ -597,8 +368,7 @@ pub fn solari_lighting( pipeline_cache.get_compute_pipeline(pipelines.di_spatial_and_shade_pipeline), pipeline_cache.get_compute_pipeline(pipelines.gi_initial_and_temporal_pipeline), pipeline_cache.get_compute_pipeline(pipelines.gi_spatial_and_shade_pipeline), - pipeline_cache.get_compute_pipeline(pipelines.specular_gi_pipeline), - pipeline_cache.get_compute_pipeline(pipelines.specular_gi_with_psr_pipeline), + pipeline_cache.get_compute_pipeline(specular_gi_pipeline), &scene_bindings.bind_group, view_prepass_textures.deferred_view(), view_prepass_textures.depth_view(), @@ -612,12 +382,7 @@ pub fn solari_lighting( return; }; - let specular_gi_pipeline = if view_dlss_rr_textures.is_some() { - specular_gi_pipeline_with_psr - } else { - specular_gi_pipeline_regular - }; - + #[cfg(all(feature = "dlss", not(feature = "force_disable_dlss")))] let Some(resolve_dlss_rr_textures_pipeline) = pipeline_cache.get_compute_pipeline(pipelines.resolve_dlss_rr_textures_pipeline) else { @@ -664,6 +429,7 @@ pub fn solari_lighting( &BindGroupEntries::single(s.world_cache_active_cells_dispatch.as_entire_binding()), ); + #[cfg(all(feature = "dlss", not(feature = "force_disable_dlss")))] let bind_group_resolve_dlss_rr_textures = view_dlss_rr_textures.map(|d| { render_device.create_bind_group( "solari_lighting_bind_group_resolve_dlss_rr_textures", @@ -715,8 +481,9 @@ pub fn solari_lighting( ], ); - if let Some(bind_group_resolve_dlss_rr_textures) = bind_group_resolve_dlss_rr_textures { - pass.set_bind_group(2, &bind_group_resolve_dlss_rr_textures, &[]); + #[cfg(all(feature = "dlss", not(feature = "force_disable_dlss")))] + if let Some(bind_group_resolve_dlss_rr_textures) = &bind_group_resolve_dlss_rr_textures { + pass.set_bind_group(2, bind_group_resolve_dlss_rr_textures, &[]); pass.set_pipeline(resolve_dlss_rr_textures_pipeline); pass.dispatch_workgroups(dx, dy, 1); } @@ -813,6 +580,10 @@ pub fn solari_lighting( d.end(&mut pass); let d = diagnostics.time_span(&mut pass, "solari_lighting/specular_indirect_lighting"); + #[cfg(all(feature = "dlss", not(feature = "force_disable_dlss")))] + if let Some(bind_group_resolve_dlss_rr_textures) = &bind_group_resolve_dlss_rr_textures { + pass.set_bind_group(2, bind_group_resolve_dlss_rr_textures, &[]); + } pass.set_pipeline(specular_gi_pipeline); pass.set_push_constants( 0, @@ -820,4 +591,12 @@ pub fn solari_lighting( ); pass.dispatch_workgroups(dx, dy, 1); d.end(&mut pass); + + drop(pass); + + diagnostics.record_u32( + ctx.command_encoder(), + &s.world_cache_active_cells_count.slice(..), + "solari_lighting/world_cache_active_cells_count", + ); } From e29562b02e847422acce5dbffa2c94e74d2e6592 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?charlotte=20=F0=9F=8C=B8?= Date: Mon, 19 Jan 2026 20:04:24 -0800 Subject: [PATCH 41/55] Correctly submit only once. --- crates/bevy_core_pipeline/src/schedule.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/bevy_core_pipeline/src/schedule.rs b/crates/bevy_core_pipeline/src/schedule.rs index 8363bb9ac6cb0..3c9098514b081 100644 --- a/crates/bevy_core_pipeline/src/schedule.rs +++ b/crates/bevy_core_pipeline/src/schedule.rs @@ -117,10 +117,10 @@ pub fn camera_driver(world: &mut World) { .entered(); world.run_schedule(schedule); - submit_pending_command_buffers(world); } } + submit_pending_command_buffers(world); world.remove_resource::(); handle_uncovered_swap_chains(world, &camera_windows); } From 17c70cb3edb7961079ddc7d464f94b2561c8520e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?charlotte=20=F0=9F=8C=B8?= Date: Tue, 20 Jan 2026 17:46:21 -0800 Subject: [PATCH 42/55] Ci. --- crates/bevy_post_process/src/effect_stack/mod.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/crates/bevy_post_process/src/effect_stack/mod.rs b/crates/bevy_post_process/src/effect_stack/mod.rs index f3a5713546f93..3cee8639abc11 100644 --- a/crates/bevy_post_process/src/effect_stack/mod.rs +++ b/crates/bevy_post_process/src/effect_stack/mod.rs @@ -287,8 +287,7 @@ pub(crate) fn post_processing( return; }; - let Some(vignette_uniform_buffer_binding) = - post_processing_uniform_buffers.vignette.binding() + let Some(vignette_uniform_buffer_binding) = post_processing_uniform_buffers.vignette.binding() else { return; }; From 7fbbd532b0d0e6e7d39d8719864ee1c429efa481 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?charlotte=20=F0=9F=8C=B8?= Date: Mon, 26 Jan 2026 13:52:58 -0800 Subject: [PATCH 43/55] Add docs. --- crates/bevy_core_pipeline/src/schedule.rs | 35 +++++++++++++++++++ crates/bevy_render/macros/src/lib.rs | 16 --------- crates/bevy_render/src/renderer/mod.rs | 5 +++ .../src/renderer/render_context.rs | 23 +++++++++++- 4 files changed, 62 insertions(+), 17 deletions(-) diff --git a/crates/bevy_core_pipeline/src/schedule.rs b/crates/bevy_core_pipeline/src/schedule.rs index 3c9098514b081..132041ca43afa 100644 --- a/crates/bevy_core_pipeline/src/schedule.rs +++ b/crates/bevy_core_pipeline/src/schedule.rs @@ -1,3 +1,14 @@ +//! The core rendering pipelines schedules. These schedules define the "default" render graph +//! for 2D and 3D rendering in Bevy. +//! +//! Rendering in Bevy is "camera driven", meaning that for each camera in the world, its +//! associated rendering schedule is executed. This allows different cameras to have different +//! rendering pipelines, for example a 3D camera with post-processing effects and a 2D camera +//! with a simple clear and sprite rendering. +//! +//! The [`camera_driver`] system is responsible for iterating over all cameras in the world +//! and executing their associated schedules. In this way, the schedule for each camera is a +//! sub-schedule or sub-graph of the root render graph schedule. use bevy_camera::{ClearColor, NormalizedRenderTarget}; use bevy_ecs::{ prelude::*, @@ -19,6 +30,14 @@ use tracing::info_span; #[derive(ScheduleLabel, Debug, Clone, PartialEq, Eq, Hash, Default)] pub struct Core3d; +/// System sets for the Core 3D rendering pipeline, defining the main stages of rendering. +/// These stages include and run in the following order: +/// - Prepass: Initial rendering operations, such as depth pre-pass. +/// - MainPass: The primary rendering operations, including drawing opaque and transparent objects. +/// - PostProcess: Final rendering operations, such as post-processing effects. +/// +/// Additional systems can be added to these sets to customize the rendering pipeline, or additional +/// sets can be created relative to these core sets. #[derive(SystemSet, Debug, Clone, PartialEq, Eq, Hash)] pub enum Core3dSystems { Prepass, @@ -44,9 +63,17 @@ impl Core3d { } } +/// Schedule label for the Core 2D rendering pipeline. #[derive(ScheduleLabel, Debug, Clone, PartialEq, Eq, Hash, Default)] pub struct Core2d; +/// System sets for the Core 2D rendering pipeline, defining the main stages of rendering. +/// These stages include and run in the following order: +/// - MainPass: The primary rendering operations, including drawing 2D sprites and meshes. +/// - PostProcess: Final rendering operations, such as post-processing effects. +/// +/// Additional systems can be added to these sets to customize the rendering pipeline, or additional +/// sets can be created relative to these core sets. #[derive(SystemSet, Debug, Clone, PartialEq, Eq, Hash)] pub enum Core2dSystems { MainPass, @@ -71,6 +98,14 @@ impl Core2d { } } +/// The default entry point for camera driven rendering added to the root [`RenderGraph`] +/// schedule. This system iterates over all cameras in the world, executing their associated +/// rendering schedules defined by the [`CameraRenderGraph`] component. +/// +/// After executing all camera schedules, it submits any pending command buffers to the GPU +/// and clears any swap chains that were not covered by a camera. Users can order any additional +/// operations (e.g. one-off compute passes) before or after this system in the root render +/// graph schedule. pub fn camera_driver(world: &mut World) { let sorted_cameras: Vec<_> = { let sorted = world.resource::(); diff --git a/crates/bevy_render/macros/src/lib.rs b/crates/bevy_render/macros/src/lib.rs index 78b255e2a6eee..40c01e10f6ec0 100644 --- a/crates/bevy_render/macros/src/lib.rs +++ b/crates/bevy_render/macros/src/lib.rs @@ -91,22 +91,6 @@ pub fn derive_render_label(input: TokenStream) -> TokenStream { derive_label(input, "RenderLabel", &trait_path) } -/// Derive macro generating an impl of the trait `RenderSubGraph`. -/// -/// This does not work for unions. -#[proc_macro_derive(RenderSubGraph)] -pub fn derive_render_sub_graph(input: TokenStream) -> TokenStream { - let input = parse_macro_input!(input as DeriveInput); - let mut trait_path = bevy_render_path(); - trait_path - .segments - .push(format_ident!("render_graph").into()); - trait_path - .segments - .push(format_ident!("RenderSubGraph").into()); - derive_label(input, "RenderSubGraph", &trait_path) -} - /// Derive macro generating an impl of the trait `Specializer` /// /// This only works for structs whose members all implement `Specializer` diff --git a/crates/bevy_render/src/renderer/mod.rs b/crates/bevy_render/src/renderer/mod.rs index f38e29959024b..b99fe1a079061 100644 --- a/crates/bevy_render/src/renderer/mod.rs +++ b/crates/bevy_render/src/renderer/mod.rs @@ -29,6 +29,8 @@ use wgpu::{ Adapter, AdapterInfo, Backends, DeviceType, Instance, Queue, RequestAdapterOptions, Trace, }; +/// Schedule label for the root render graph schedule. This schedule runs once per frame +/// in the [`render_system`] system and is responsible for driving the entire rendering process. #[derive(ScheduleLabel, Debug, Clone, PartialEq, Eq, Hash, Default)] pub struct RenderGraph; @@ -38,6 +40,9 @@ impl RenderGraph { } } +/// The main render system that drives the rendering process. This system runs the [`RenderGraph`] +/// schedule, runs any finalization commands like screenshot captures and GPU readbacks, and +/// calls present on swap chains that need to be presented. pub fn render_system( world: &mut World, state: &mut SystemState>, diff --git a/crates/bevy_render/src/renderer/render_context.rs b/crates/bevy_render/src/renderer/render_context.rs index 918a2178f7c69..3fe925fc34d0b 100644 --- a/crates/bevy_render/src/renderer/render_context.rs +++ b/crates/bevy_render/src/renderer/render_context.rs @@ -1,7 +1,8 @@ -use super::WgpuWrapper; +use super::{RenderGraph, WgpuWrapper}; use crate::diagnostic::internal::DiagnosticsRecorder; use crate::render_phase::TrackedRenderPass; use crate::render_resource::{CommandEncoder, RenderPassDescriptor}; +use crate::render_system; use crate::renderer::RenderDevice; use bevy_ecs::change_detection::Tick; use bevy_ecs::component::ComponentId; @@ -22,6 +23,7 @@ struct PendingCommandBuffersInner { encoders: Vec, } +/// A resource that holds command buffers and encoders that are pending submission to the render queue. #[derive(Resource)] pub struct PendingCommandBuffers(WgpuWrapper); @@ -64,6 +66,9 @@ struct RenderContextStateInner { render_device: Option, } +/// A resource that holds the current render context state, including command encoder and command buffers. +/// This is used internally by the [`RenderContext`] system parameter. Implements [`SystemBuffer`] to flush +/// command buffers at the end of each render system in topological system order. pub struct RenderContextState(WgpuWrapper); impl Default for RenderContextState { @@ -122,6 +127,9 @@ impl SystemBuffer for RenderContextState { fn queue(&mut self, _system_meta: &SystemMeta, _world: DeferredWorld) {} } +/// A system parameter that provides access to a command encoder and render device for issuing +/// rendering commands inside any system running beneath the root [`RenderGraph`] schedule in the +/// [`render_system`] system. #[derive(SystemParam)] pub struct RenderContext<'w, 's> { state: Deferred<'s, RenderContextState>, @@ -136,19 +144,23 @@ impl<'w, 's> RenderContext<'w, 's> { } } + /// Returns the render device associated with this render context. pub fn render_device(&self) -> &RenderDevice { &self.render_device } + /// Returns the diagnostics recorder, if available. pub fn diagnostic_recorder(&self) -> Option> { self.diagnostics_recorder.as_ref().map(Res::clone) } + /// Returns the current command encoder, creating one if it does not already exist. pub fn command_encoder(&mut self) -> &mut CommandEncoder { self.ensure_device(); self.state.command_encoder() } + /// Begins a tracked render pass with the given descriptor. pub fn begin_tracked_render_pass<'a>( &'a mut self, descriptor: RenderPassDescriptor<'_>, @@ -164,12 +176,16 @@ impl<'w, 's> RenderContext<'w, 's> { TrackedRenderPass::new(&self.render_device, render_pass) } + /// Adds a finished command buffer to be submitted later. pub fn add_command_buffer(&mut self, command_buffer: CommandBuffer) { self.state.flush_encoder(); self.state.0.command_buffers.push(command_buffer); } } +/// A system parameter that can be used to explicitly flush pending command buffers to the render queue. +/// This is typically not necessary, as command buffers are automatically flushed at the end of each +/// render system. However, in some cases it may be useful to flush command buffers earlier. #[derive(SystemParam)] pub struct FlushCommands<'w> { pending: ResMut<'w, PendingCommandBuffers>, @@ -177,6 +193,7 @@ pub struct FlushCommands<'w> { } impl<'w> FlushCommands<'w> { + /// Flushes all pending command buffers to the render queue. pub fn flush(&mut self) { let buffers = self.pending.take(); if !buffers.is_empty() { @@ -185,6 +202,7 @@ impl<'w> FlushCommands<'w> { } } +/// The entity corresponding to the current view being rendered. #[derive(Resource, Debug, Clone, Copy, PartialEq, Eq)] pub struct CurrentViewEntity(pub Entity); @@ -199,6 +217,7 @@ impl CurrentViewEntity { } } +/// A system parameter that provides access to the entity corresponding to the current view being rendered. #[derive(SystemParam)] pub struct CurrentView<'w> { entity: Res<'w, CurrentViewEntity>, @@ -220,6 +239,8 @@ impl<'w> core::ops::Deref for CurrentView<'w> { } } +/// A query that fetches components for the entity corresponding to the current view being rendered, +/// as defined by the [`CurrentViewEntity`] resource, equivalent to `query.get(current_view.entity())`. pub struct ViewQuery<'w, 's, D: QueryData, F: QueryFilter = ()> { entity: Entity, item: D::Item<'w, 's>, From 4506380684edbfd94370842d3cccbcc1f11bcc56 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?charlotte=20=F0=9F=8C=B8?= Date: Mon, 26 Jan 2026 19:13:09 -0800 Subject: [PATCH 44/55] Add missing diagnostic spans. --- crates/bevy_core_pipeline/src/deferred/copy_lighting_id.rs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/crates/bevy_core_pipeline/src/deferred/copy_lighting_id.rs b/crates/bevy_core_pipeline/src/deferred/copy_lighting_id.rs index e7deeebcb0fc9..2edac51022f65 100644 --- a/crates/bevy_core_pipeline/src/deferred/copy_lighting_id.rs +++ b/crates/bevy_core_pipeline/src/deferred/copy_lighting_id.rs @@ -8,6 +8,7 @@ use bevy_ecs::prelude::*; use bevy_image::ToExtents; use bevy_render::{ camera::ExtractedCamera, + diagnostic::RecordDiagnostics, render_resource::{binding_types::texture_2d, *}, renderer::RenderDevice, texture::{CachedTexture, TextureCache}, @@ -63,6 +64,9 @@ pub(crate) fn copy_deferred_lighting_id( &BindGroupEntries::single(&deferred_lighting_pass_id_texture.texture.default_view), ); + let diagnostics = ctx.diagnostic_recorder(); + let diagnostics = diagnostics.as_deref(); + let mut render_pass = ctx.begin_tracked_render_pass(RenderPassDescriptor { label: Some("copy_deferred_lighting_id"), color_attachments: &[], @@ -78,10 +82,13 @@ pub(crate) fn copy_deferred_lighting_id( occlusion_query_set: None, multiview_mask: None, }); + let pass_span = diagnostics.pass_span(&mut render_pass, "copy_deferred_lighting_id"); render_pass.set_render_pipeline(pipeline); render_pass.set_bind_group(0, &bind_group, &[]); render_pass.draw(0..3, 0..1); + + pass_span.end(&mut render_pass); } #[derive(Resource)] From 69190b372b0c022024b6ed3ef8c1a90b689e9eec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?charlotte=20=F0=9F=8C=B8?= Date: Mon, 26 Jan 2026 19:13:36 -0800 Subject: [PATCH 45/55] Fixup FullscreenMaterial. --- crates/bevy_core_pipeline/src/fullscreen_material.rs | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/crates/bevy_core_pipeline/src/fullscreen_material.rs b/crates/bevy_core_pipeline/src/fullscreen_material.rs index 925d078c19ed8..993617d8be584 100644 --- a/crates/bevy_core_pipeline/src/fullscreen_material.rs +++ b/crates/bevy_core_pipeline/src/fullscreen_material.rs @@ -69,16 +69,7 @@ impl Plugin for FullscreenMaterialPlugin { /// A trait to define a material that will render to the entire screen using a fullscreen triangle. pub trait FullscreenMaterial: - Component - + ExtractComponent - + Clone - + Copy - + ShaderType - + WriteInto - + Default - + Send - + Sync - + 'static + Component + ExtractComponent + Clone + Copy + ShaderType + WriteInto + Default { /// The shader that will run on the entire screen using a fullscreen triangle. fn fragment_shader() -> ShaderRef; From e360ac8efb24d02d84b5aba6daf37128da6c0beb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?charlotte=20=F0=9F=8C=B8?= Date: Mon, 26 Jan 2026 19:14:44 -0800 Subject: [PATCH 46/55] Fix debug overlay system ordering. --- crates/bevy_dev_tools/src/render_debug.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/crates/bevy_dev_tools/src/render_debug.rs b/crates/bevy_dev_tools/src/render_debug.rs index a284aed639b1c..dffe82ba977ea 100644 --- a/crates/bevy_dev_tools/src/render_debug.rs +++ b/crates/bevy_dev_tools/src/render_debug.rs @@ -6,6 +6,7 @@ use bevy_core_pipeline::{ mip_generation::experimental::depth::ViewDepthPyramid, oit::OrderIndependentTransparencySettingsOffset, schedule::{Core3d, Core3dSystems}, + tonemapping::tonemapping, FullscreenShader, }; use bevy_ecs::{ @@ -83,7 +84,9 @@ impl Plugin for RenderDebugOverlayPlugin { ) .add_systems( Core3d, - render_debug_overlay.in_set(Core3dSystems::PostProcess), + render_debug_overlay + .after(tonemapping) + .in_set(Core3dSystems::PostProcess), ); } } From 31e3fe772fc0ba7e978e70e377cbeaded9ecf0f8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?charlotte=20=F0=9F=8C=B8?= Date: Mon, 26 Jan 2026 19:18:04 -0800 Subject: [PATCH 47/55] Remove unecessary system param. --- crates/bevy_core_pipeline/src/schedule.rs | 6 +-- crates/bevy_render/src/renderer/mod.rs | 3 +- .../src/renderer/render_context.rs | 52 ++++--------------- 3 files changed, 14 insertions(+), 47 deletions(-) diff --git a/crates/bevy_core_pipeline/src/schedule.rs b/crates/bevy_core_pipeline/src/schedule.rs index 8b4130b90b09c..c70cfcf76ce48 100644 --- a/crates/bevy_core_pipeline/src/schedule.rs +++ b/crates/bevy_core_pipeline/src/schedule.rs @@ -21,7 +21,7 @@ use bevy_render::{ CommandEncoderDescriptor, LoadOp, Operations, RenderPassColorAttachment, RenderPassDescriptor, StoreOp, }, - renderer::{CurrentViewEntity, PendingCommandBuffers, RenderDevice, RenderQueue}, + renderer::{CurrentView, PendingCommandBuffers, RenderDevice, RenderQueue}, view::ExtractedWindows, }; use tracing::info_span; @@ -142,7 +142,7 @@ pub fn camera_driver(world: &mut World) { } if run_schedule { - world.insert_resource(CurrentViewEntity::new(camera_entity)); + world.insert_resource(CurrentView(camera_entity)); #[cfg(feature = "trace")] let _span = tracing::info_span!( @@ -156,7 +156,7 @@ pub fn camera_driver(world: &mut World) { } submit_pending_command_buffers(world); - world.remove_resource::(); + world.remove_resource::(); handle_uncovered_swap_chains(world, &camera_windows); } diff --git a/crates/bevy_render/src/renderer/mod.rs b/crates/bevy_render/src/renderer/mod.rs index 985ef76f0eba9..012abcb8f15fc 100644 --- a/crates/bevy_render/src/renderer/mod.rs +++ b/crates/bevy_render/src/renderer/mod.rs @@ -5,8 +5,7 @@ mod render_device; mod wgpu_wrapper; pub use render_context::{ - CurrentView, CurrentViewEntity, FlushCommands, PendingCommandBuffers, RenderContext, - RenderContextState, ViewQuery, + CurrentView, FlushCommands, PendingCommandBuffers, RenderContext, RenderContextState, ViewQuery, }; pub use render_device::*; pub use wgpu_wrapper::WgpuWrapper; diff --git a/crates/bevy_render/src/renderer/render_context.rs b/crates/bevy_render/src/renderer/render_context.rs index 10677a0550ad8..2db76975a09ba 100644 --- a/crates/bevy_render/src/renderer/render_context.rs +++ b/crates/bevy_render/src/renderer/render_context.rs @@ -1,4 +1,5 @@ use super::WgpuWrapper; +use bevy_derive::{Deref, DerefMut}; use crate::diagnostic::internal::DiagnosticsRecorder; use crate::render_phase::TrackedRenderPass; use crate::render_resource::{CommandEncoder, RenderPassDescriptor}; @@ -202,44 +203,11 @@ impl<'w> FlushCommands<'w> { } /// The entity corresponding to the current view being rendered. -#[derive(Resource, Debug, Clone, Copy, PartialEq, Eq)] -pub struct CurrentViewEntity(pub Entity); - -impl CurrentViewEntity { - pub fn new(entity: Entity) -> Self { - Self(entity) - } - - #[inline] - pub fn entity(&self) -> Entity { - self.0 - } -} - -/// A system parameter that provides access to the entity corresponding to the current view being rendered. -#[derive(SystemParam)] -pub struct CurrentView<'w> { - entity: Res<'w, CurrentViewEntity>, -} - -impl<'w> CurrentView<'w> { - #[inline] - pub fn entity(&self) -> Entity { - self.entity.0 - } -} - -impl<'w> core::ops::Deref for CurrentView<'w> { - type Target = Entity; - - #[inline] - fn deref(&self) -> &Self::Target { - &self.entity.0 - } -} +#[derive(Resource, Debug, Clone, Copy, PartialEq, Eq, Deref, DerefMut)] +pub struct CurrentView(pub Entity); /// A query that fetches components for the entity corresponding to the current view being rendered, -/// as defined by the [`CurrentViewEntity`] resource, equivalent to `query.get(current_view.entity())`. +/// as defined by the [`CurrentView`] resource, equivalent to `query.get(current_view.entity())`. pub struct ViewQuery<'w, 's, D: QueryData, F: QueryFilter = ()> { entity: Entity, item: D::Item<'w, 's>, @@ -263,7 +231,7 @@ pub struct ViewQueryState { query_state: QueryState, } -// SAFETY: ViewQuery accesses the CurrentViewEntity resource (read) and query components. +// SAFETY: ViewQuery accesses the CurrentView resource (read) and query components. // Access is properly registered in init_access. unsafe impl<'a, D: QueryData + 'static, F: QueryFilter + 'static> SystemParam for ViewQuery<'a, '_, D, F> @@ -275,7 +243,7 @@ unsafe impl<'a, D: QueryData + 'static, F: QueryFilter + 'static> SystemParam ViewQueryState { resource_id: world .components_registrator() - .register_resource::(), + .register_resource::(), query_state: QueryState::new(world), } } @@ -303,11 +271,11 @@ unsafe impl<'a, D: QueryData + 'static, F: QueryFilter + 'static> SystemParam world: UnsafeWorldCell, ) -> Result<(), SystemParamValidationError> { // SAFETY: We have registered resource read access in init_access - let current_view = unsafe { world.get_resource::() }; + let current_view = unsafe { world.get_resource::() }; let Some(current_view) = current_view else { return Err(SystemParamValidationError::skipped::( - "CurrentViewEntity resource not present", + "CurrentView resource not present", )); }; @@ -336,8 +304,8 @@ unsafe impl<'a, D: QueryData + 'static, F: QueryFilter + 'static> SystemParam // SAFETY: We have registered resource read access and validate_param succeeded let current_view = unsafe { world - .get_resource::() - .expect("CurrentViewEntity must exist") + .get_resource::() + .expect("CurrentView must exist") }; let entity = current_view.entity(); From 86dc21532480e6181925385e3a64ff80c2f058c3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?charlotte=20=F0=9F=8C=B8?= Date: Mon, 26 Jan 2026 19:19:14 -0800 Subject: [PATCH 48/55] Misc system ordering fixes. --- crates/bevy_solari/src/realtime/mod.rs | 7 +------ examples/shader/compute_shader_game_of_life.rs | 3 ++- examples/shader_advanced/compute_mesh.rs | 3 ++- 3 files changed, 5 insertions(+), 8 deletions(-) diff --git a/crates/bevy_solari/src/realtime/mod.rs b/crates/bevy_solari/src/realtime/mod.rs index 93a46a20bffa3..f148905b1ccc3 100644 --- a/crates/bevy_solari/src/realtime/mod.rs +++ b/crates/bevy_solari/src/realtime/mod.rs @@ -69,12 +69,7 @@ impl Plugin for SolariLightingPlugin { Render, prepare_solari_lighting_resources.in_set(RenderSystems::PrepareResources), ) - .add_systems( - Core3d, - solari_lighting - .after(Core3dSystems::Prepass) - .in_set(Core3dSystems::MainPass), - ); + .add_systems(Core3d, solari_lighting.in_set(Core3dSystems::MainPass)); } } diff --git a/examples/shader/compute_shader_game_of_life.rs b/examples/shader/compute_shader_game_of_life.rs index 6e1bdc86033e2..7054544535bf2 100644 --- a/examples/shader/compute_shader_game_of_life.rs +++ b/examples/shader/compute_shader_game_of_life.rs @@ -5,6 +5,7 @@ use bevy::{ asset::RenderAssetUsages, + core_pipeline::schedule::camera_driver, prelude::*, render::{ extract_resource::{ExtractResource, ExtractResourcePlugin}, @@ -106,7 +107,7 @@ impl Plugin for GameOfLifeComputePlugin { prepare_bind_group.in_set(RenderSystems::PrepareBindGroups), ) .add_systems(Render, update.in_set(RenderSystems::Prepare)) - .add_systems(RenderGraph, game_of_life); + .add_systems(RenderGraph, game_of_life.before(camera_driver)); } } diff --git a/examples/shader_advanced/compute_mesh.rs b/examples/shader_advanced/compute_mesh.rs index a157849be88f6..17e3696b530ec 100644 --- a/examples/shader_advanced/compute_mesh.rs +++ b/examples/shader_advanced/compute_mesh.rs @@ -14,6 +14,7 @@ use std::ops::Not; use bevy::{ asset::RenderAssetUsages, color::palettes::tailwind::{RED_400, SKY_400}, + core_pipeline::schedule::camera_driver, mesh::Indices, platform::collections::HashSet, prelude::*, @@ -56,7 +57,7 @@ impl Plugin for ComputeShaderMeshGeneratorPlugin { .init_resource::() .add_systems(RenderStartup, init_compute_pipeline) .add_systems(Render, prepare_chunks) - .add_systems(RenderGraph, compute_mesh); + .add_systems(RenderGraph, compute_mesh.before(camera_driver)); } fn finish(&self, app: &mut App) { let Some(render_app) = app.get_sub_app_mut(RenderApp) else { From 3f952a073d16aa60d6c5090357cb5b0e4b4bcd75 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?charlotte=20=F0=9F=8C=B8?= Date: Mon, 26 Jan 2026 19:28:27 -0800 Subject: [PATCH 49/55] Ci. --- crates/bevy_core_pipeline/src/lib.rs | 2 +- crates/bevy_pbr/src/render/gpu_preprocess.rs | 6 +++--- crates/bevy_pbr/src/transmission/node.rs | 11 +++++++++-- crates/bevy_post_process/src/effect_stack/mod.rs | 8 ++++---- crates/bevy_render/src/renderer/render_context.rs | 4 ++-- crates/bevy_solari/src/pathtracer/mod.rs | 5 ++--- crates/bevy_solari/src/realtime/mod.rs | 5 ++--- crates/bevy_solari/src/realtime/node.rs | 4 ++-- 8 files changed, 25 insertions(+), 20 deletions(-) diff --git a/crates/bevy_core_pipeline/src/lib.rs b/crates/bevy_core_pipeline/src/lib.rs index 960e7173e5fa8..f4576e76cb9cc 100644 --- a/crates/bevy_core_pipeline/src/lib.rs +++ b/crates/bevy_core_pipeline/src/lib.rs @@ -18,9 +18,9 @@ pub mod schedule; pub mod tonemapping; pub mod upscaling; +pub use bevy_light::Skybox; pub use fullscreen_vertex_shader::FullscreenShader; pub use schedule::{Core2d, Core2dSystems, Core3d, Core3dSystems}; -pub use bevy_light::Skybox; mod fullscreen_vertex_shader; mod skybox; diff --git a/crates/bevy_pbr/src/render/gpu_preprocess.rs b/crates/bevy_pbr/src/render/gpu_preprocess.rs index 6bc8c7433053c..9573861dc8150 100644 --- a/crates/bevy_pbr/src/render/gpu_preprocess.rs +++ b/crates/bevy_pbr/src/render/gpu_preprocess.rs @@ -46,9 +46,9 @@ use bevy_render::{ binding_types::{storage_buffer, storage_buffer_read_only, texture_2d, uniform_buffer}, BindGroup, BindGroupEntries, BindGroupLayoutDescriptor, BindingResource, Buffer, BufferBinding, CachedComputePipelineId, ComputePassDescriptor, ComputePipelineDescriptor, - DynamicBindGroupLayoutEntries, PipelineCache, RawBufferVec, ShaderStages, - ShaderType, SpecializedComputePipeline, SpecializedComputePipelines, - TextureSampleType, UninitBufferVec, + DynamicBindGroupLayoutEntries, PipelineCache, RawBufferVec, ShaderStages, ShaderType, + SpecializedComputePipeline, SpecializedComputePipelines, TextureSampleType, + UninitBufferVec, }, renderer::{RenderContext, RenderDevice, RenderQueue, ViewQuery}, settings::WgpuFeatures, diff --git a/crates/bevy_pbr/src/transmission/node.rs b/crates/bevy_pbr/src/transmission/node.rs index c49838ff049a9..e1095c101f456 100644 --- a/crates/bevy_pbr/src/transmission/node.rs +++ b/crates/bevy_pbr/src/transmission/node.rs @@ -33,8 +33,15 @@ pub fn main_transmissive_pass_3d( ) { let view_entity = view.entity(); - let (camera, extracted_view, transmission_settings, target, transmission, depth, resolution_override) = - view.into_inner(); + let ( + camera, + extracted_view, + transmission_settings, + target, + transmission, + depth, + resolution_override, + ) = view.into_inner(); let Some(transmissive_phase) = transmissive_phases.get(&extracted_view.retained_view_entity) else { diff --git a/crates/bevy_post_process/src/effect_stack/mod.rs b/crates/bevy_post_process/src/effect_stack/mod.rs index d4f88eb2408ee..d84ad799c6fde 100644 --- a/crates/bevy_post_process/src/effect_stack/mod.rs +++ b/crates/bevy_post_process/src/effect_stack/mod.rs @@ -38,10 +38,10 @@ use bevy_render::{ binding_types::{sampler, texture_2d, uniform_buffer}, BindGroupEntries, BindGroupLayoutDescriptor, BindGroupLayoutEntries, CachedRenderPipelineId, ColorTargetState, ColorWrites, DynamicUniformBuffer, Extent3d, - FilterMode, FragmentState, MipmapFilterMode, Operations, PipelineCache, RenderPassColorAttachment, - RenderPassDescriptor, RenderPipelineDescriptor, Sampler, SamplerBindingType, - SamplerDescriptor, ShaderStages, SpecializedRenderPipeline, SpecializedRenderPipelines, - TextureDimension, TextureFormat, TextureSampleType, + FilterMode, FragmentState, MipmapFilterMode, Operations, PipelineCache, + RenderPassColorAttachment, RenderPassDescriptor, RenderPipelineDescriptor, Sampler, + SamplerBindingType, SamplerDescriptor, ShaderStages, SpecializedRenderPipeline, + SpecializedRenderPipelines, TextureDimension, TextureFormat, TextureSampleType, }, renderer::{RenderContext, RenderDevice, RenderQueue, ViewQuery}, texture::GpuImage, diff --git a/crates/bevy_render/src/renderer/render_context.rs b/crates/bevy_render/src/renderer/render_context.rs index 2db76975a09ba..89c4cf401fa88 100644 --- a/crates/bevy_render/src/renderer/render_context.rs +++ b/crates/bevy_render/src/renderer/render_context.rs @@ -1,9 +1,9 @@ use super::WgpuWrapper; -use bevy_derive::{Deref, DerefMut}; use crate::diagnostic::internal::DiagnosticsRecorder; use crate::render_phase::TrackedRenderPass; use crate::render_resource::{CommandEncoder, RenderPassDescriptor}; use crate::renderer::RenderDevice; +use bevy_derive::{Deref, DerefMut}; use bevy_ecs::change_detection::Tick; use bevy_ecs::component::ComponentId; use bevy_ecs::prelude::*; @@ -13,8 +13,8 @@ use bevy_ecs::system::{ }; use bevy_ecs::world::unsafe_world_cell::UnsafeWorldCell; use bevy_ecs::world::DeferredWorld; -use core::marker::PhantomData; use bevy_log::info_span; +use core::marker::PhantomData; use wgpu::CommandBuffer; #[derive(Default)] diff --git a/crates/bevy_solari/src/pathtracer/mod.rs b/crates/bevy_solari/src/pathtracer/mod.rs index 5ae3bd25d9591..93dea34136f33 100644 --- a/crates/bevy_solari/src/pathtracer/mod.rs +++ b/crates/bevy_solari/src/pathtracer/mod.rs @@ -5,13 +5,12 @@ mod prepare; use crate::SolariPlugins; use bevy_app::{App, Plugin}; use bevy_asset::embedded_asset; +use bevy_camera::Hdr; use bevy_core_pipeline::schedule::{Core3d, Core3dSystems}; use bevy_ecs::{component::Component, reflect::ReflectComponent, schedule::IntoScheduleConfigs}; use bevy_reflect::{std_traits::ReflectDefault, Reflect}; -use bevy_camera::Hdr; use bevy_render::{ - renderer::RenderDevice, ExtractSchedule, Render, RenderApp, RenderStartup, - RenderSystems, + renderer::RenderDevice, ExtractSchedule, Render, RenderApp, RenderStartup, RenderSystems, }; use extract::extract_pathtracer; use node::{init_pathtracer_pipelines, pathtracer}; diff --git a/crates/bevy_solari/src/realtime/mod.rs b/crates/bevy_solari/src/realtime/mod.rs index f148905b1ccc3..15a41fcba62f8 100644 --- a/crates/bevy_solari/src/realtime/mod.rs +++ b/crates/bevy_solari/src/realtime/mod.rs @@ -5,6 +5,7 @@ mod prepare; use crate::SolariPlugins; use bevy_app::{App, Plugin}; use bevy_asset::embedded_asset; +use bevy_camera::Hdr; use bevy_core_pipeline::{ prepass::{ DeferredPrepass, DeferredPrepassDoubleBuffer, DepthPrepass, DepthPrepassDoubleBuffer, @@ -15,10 +16,8 @@ use bevy_core_pipeline::{ use bevy_ecs::{component::Component, reflect::ReflectComponent, schedule::IntoScheduleConfigs}; use bevy_pbr::DefaultOpaqueRendererMethod; use bevy_reflect::{std_traits::ReflectDefault, Reflect}; -use bevy_camera::Hdr; use bevy_render::{ - renderer::RenderDevice, ExtractSchedule, Render, RenderApp, RenderStartup, - RenderSystems, + renderer::RenderDevice, ExtractSchedule, Render, RenderApp, RenderStartup, RenderSystems, }; use bevy_shader::load_shader_library; use extract::extract_solari_lighting; diff --git a/crates/bevy_solari/src/realtime/node.rs b/crates/bevy_solari/src/realtime/node.rs index b056852356d85..38daa4a4f6a08 100644 --- a/crates/bevy_solari/src/realtime/node.rs +++ b/crates/bevy_solari/src/realtime/node.rs @@ -19,8 +19,8 @@ use bevy_render::{ }, BindGroupEntries, BindGroupLayoutDescriptor, BindGroupLayoutEntries, CachedComputePipelineId, ComputePassDescriptor, ComputePipelineDescriptor, LoadOp, - PipelineCache, RenderPassDescriptor, ShaderStages, StorageTextureAccess, - TextureFormat, TextureSampleType, + PipelineCache, RenderPassDescriptor, ShaderStages, StorageTextureAccess, TextureFormat, + TextureSampleType, }, renderer::{RenderContext, RenderDevice, ViewQuery}, view::{ViewTarget, ViewUniform, ViewUniformOffset, ViewUniforms}, From 0a40a47a9bad0a8f703cdf5e06d137f183823f2c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?charlotte=20=F0=9F=8C=B8?= Date: Mon, 26 Jan 2026 19:35:01 -0800 Subject: [PATCH 50/55] Fixup meshlets. --- .../src/meshlet/visibility_buffer_raster_node.rs | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/crates/bevy_pbr/src/meshlet/visibility_buffer_raster_node.rs b/crates/bevy_pbr/src/meshlet/visibility_buffer_raster_node.rs index 5b51541fae68d..fbfcc6299eea2 100644 --- a/crates/bevy_pbr/src/meshlet/visibility_buffer_raster_node.rs +++ b/crates/bevy_pbr/src/meshlet/visibility_buffer_raster_node.rs @@ -9,6 +9,7 @@ use bevy_ecs::prelude::*; use bevy_math::UVec2; use bevy_render::{ camera::ExtractedCamera, + diagnostic::RecordDiagnostics, render_resource::*, renderer::{RenderContext, ViewQuery}, view::{ViewDepthTexture, ViewUniformOffset}, @@ -77,8 +78,15 @@ pub fn meshlet_visibility_buffer_raster( return; }; + let diagnostics = ctx.diagnostic_recorder(); + let diagnostics = diagnostics.as_deref(); + ctx.command_encoder() .push_debug_group("meshlet_visibility_buffer_raster"); + let time_span = diagnostics.time_span( + ctx.command_encoder(), + "meshlet_visibility_buffer_raster", + ); ctx.command_encoder().clear_buffer( &resource_manager.visibility_buffer_raster_cluster_prev_counts, @@ -186,6 +194,7 @@ pub fn meshlet_visibility_buffer_raster( downsample_depth_second_pipeline, ); ctx.command_encoder().pop_debug_group(); + time_span.end(ctx.command_encoder()); for light_entity in &lights.lights { let Ok(( @@ -211,6 +220,10 @@ pub fn meshlet_visibility_buffer_raster( "meshlet_visibility_buffer_raster: {}", shadow_view.pass_name )); + let time_span_shadow = diagnostics.time_span( + ctx.command_encoder(), + &format!("meshlet_visibility_buffer_raster: {}", shadow_view.pass_name), + ); clear_visibility_buffer_pass( &mut ctx, @@ -305,6 +318,7 @@ pub fn meshlet_visibility_buffer_raster( downsample_depth_second_shadow_view_pipeline, ); ctx.command_encoder().pop_debug_group(); + time_span_shadow.end(ctx.command_encoder()); } } From 4e63f8a95d542920a014f1a0cd8609e7daaa7063 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?charlotte=20=F0=9F=8C=B8?= Date: Mon, 26 Jan 2026 19:38:45 -0800 Subject: [PATCH 51/55] Dlss. --- crates/bevy_anti_alias/src/dlss/node.rs | 16 +++------------- 1 file changed, 3 insertions(+), 13 deletions(-) diff --git a/crates/bevy_anti_alias/src/dlss/node.rs b/crates/bevy_anti_alias/src/dlss/node.rs index 89adcfcddc645..aa356e37b3a51 100644 --- a/crates/bevy_anti_alias/src/dlss/node.rs +++ b/crates/bevy_anti_alias/src/dlss/node.rs @@ -4,7 +4,7 @@ use super::{ }; use bevy_camera::MainPassResolutionOverride; use bevy_core_pipeline::prepass::ViewPrepassTextures; -use bevy_ecs::system::{Query, Res}; +use bevy_ecs::system::Res; use bevy_render::{ camera::TemporalJitter, diagnostic::RecordDiagnostics, @@ -59,16 +59,11 @@ pub fn dlss_super_resolution( let diagnostics = diagnostics.as_deref(); let time_span = diagnostics.time_span(ctx.command_encoder(), "dlss_super_resolution"); - let command_encoder = ctx.command_encoder(); let mut dlss_context = dlss_context.context.lock().unwrap(); - - command_encoder.push_debug_group("dlss_super_resolution"); - let dlss_command_buffer = dlss_context - .render(render_parameters, command_encoder, &adapter) + .render(render_parameters, ctx.command_encoder(), &adapter) .expect("Failed to render DLSS Super Resolution"); - command_encoder.pop_debug_group(); ctx.add_command_buffer(dlss_command_buffer); time_span.end(ctx.command_encoder()); } @@ -131,16 +126,11 @@ pub fn dlss_ray_reconstruction( let diagnostics = diagnostics.as_deref(); let time_span = diagnostics.time_span(ctx.command_encoder(), "dlss_ray_reconstruction"); - let command_encoder = ctx.command_encoder(); let mut dlss_context = dlss_context.context.lock().unwrap(); - - command_encoder.push_debug_group("dlss_ray_reconstruction"); - let dlss_command_buffer = dlss_context - .render(render_parameters, command_encoder, &adapter) + .render(render_parameters, ctx.command_encoder(), &adapter) .expect("Failed to render DLSS Ray Reconstruction"); - command_encoder.pop_debug_group(); ctx.add_command_buffer(dlss_command_buffer); time_span.end(ctx.command_encoder()); } From c5bbcbd30732b42ca6da6f062b19922f6ff100b2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?charlotte=20=F0=9F=8C=B8?= Date: Mon, 26 Jan 2026 19:39:06 -0800 Subject: [PATCH 52/55] Remove dep on post process. --- crates/bevy_anti_alias/Cargo.toml | 1 - 1 file changed, 1 deletion(-) diff --git a/crates/bevy_anti_alias/Cargo.toml b/crates/bevy_anti_alias/Cargo.toml index 25a3e35b43ac6..86719ce6c69ff 100644 --- a/crates/bevy_anti_alias/Cargo.toml +++ b/crates/bevy_anti_alias/Cargo.toml @@ -31,7 +31,6 @@ bevy_shader = { path = "../bevy_shader", version = "0.19.0-dev" } bevy_ecs = { path = "../bevy_ecs", version = "0.19.0-dev" } bevy_core_pipeline = { path = "../bevy_core_pipeline", version = "0.19.0-dev" } bevy_diagnostic = { path = "../bevy_diagnostic", version = "0.19.0-dev" } -bevy_post_process = { path = "../bevy_post_process", version = "0.19.0-dev" } # other tracing = { version = "0.1", default-features = false, features = ["std"] } From 804b3ed3829ab2bbbcea3b78708936ee263de817 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?charlotte=20=F0=9F=8C=B8?= Date: Mon, 26 Jan 2026 19:50:40 -0800 Subject: [PATCH 53/55] Fixup AntiAliasing system set. --- crates/bevy_anti_alias/src/dlss/mod.rs | 6 +----- crates/bevy_anti_alias/src/lib.rs | 5 +---- crates/bevy_anti_alias/src/taa/mod.rs | 6 +----- crates/bevy_core_pipeline/src/schedule.rs | 4 ++++ .../src/meshlet/visibility_buffer_raster_node.rs | 11 ++++++----- crates/bevy_post_process/src/bloom/mod.rs | 7 +++++-- crates/bevy_post_process/src/motion_blur/mod.rs | 3 ++- 7 files changed, 20 insertions(+), 22 deletions(-) diff --git a/crates/bevy_anti_alias/src/dlss/mod.rs b/crates/bevy_anti_alias/src/dlss/mod.rs index 7dfb5768a7fb3..66a49dcc99367 100644 --- a/crates/bevy_anti_alias/src/dlss/mod.rs +++ b/crates/bevy_anti_alias/src/dlss/mod.rs @@ -20,16 +20,14 @@ mod prepare; pub use dlss_wgpu::DlssPerfQualityMode; -use crate::AntiAliasing; use bevy_app::{App, Plugin}; use bevy_camera::Hdr; use bevy_core_pipeline::{ prepass::{DepthPrepass, MotionVectorPrepass}, - schedule::{Core3d, Core3dSystems}, + schedule::{AntiAliasing, Core3d, Core3dSystems}, }; use bevy_ecs::prelude::*; use bevy_math::{UVec2, Vec2}; -use bevy_post_process::{bloom::bloom, motion_blur::node::motion_blur}; use bevy_reflect::{reflect_remote, Reflect}; use bevy_render::{ camera::{MipBias, TemporalJitter}, @@ -195,8 +193,6 @@ impl Plugin for DlssPlugin { Core3d, (node::dlss_super_resolution, node::dlss_ray_reconstruction) .chain() - .after(motion_blur) - .before(bloom) .in_set(Core3dSystems::PostProcess) .in_set(AntiAliasing), ); diff --git a/crates/bevy_anti_alias/src/lib.rs b/crates/bevy_anti_alias/src/lib.rs index 6a5b2a2066fa1..5c5a8a3e64744 100644 --- a/crates/bevy_anti_alias/src/lib.rs +++ b/crates/bevy_anti_alias/src/lib.rs @@ -6,7 +6,6 @@ )] use bevy_app::Plugin; -use bevy_ecs::schedule::SystemSet; use contrast_adaptive_sharpening::CasPlugin; use fxaa::FxaaPlugin; use smaa::SmaaPlugin; @@ -19,9 +18,7 @@ pub mod fxaa; pub mod smaa; pub mod taa; -/// System set for ordering render graph systems relative to any anti-aliasing implementation. -#[derive(Debug, Hash, PartialEq, Eq, Clone, SystemSet)] -pub struct AntiAliasing; +pub use bevy_core_pipeline::schedule::AntiAliasing; /// Adds fxaa, smaa, taa, contrast aware sharpening, and optional dlss support. #[derive(Default)] diff --git a/crates/bevy_anti_alias/src/taa/mod.rs b/crates/bevy_anti_alias/src/taa/mod.rs index d69eba0155aeb..975d4f5deeab8 100644 --- a/crates/bevy_anti_alias/src/taa/mod.rs +++ b/crates/bevy_anti_alias/src/taa/mod.rs @@ -1,10 +1,9 @@ -use crate::AntiAliasing; use bevy_app::{App, Plugin}; use bevy_asset::{embedded_asset, load_embedded_asset, AssetServer}; use bevy_camera::{Camera, Camera3d}; use bevy_core_pipeline::{ prepass::{DepthPrepass, MotionVectorPrepass, ViewPrepassTextures}, - schedule::{Core3d, Core3dSystems}, + schedule::{AntiAliasing, Core3d, Core3dSystems}, FullscreenShader, }; use bevy_diagnostic::FrameCount; @@ -18,7 +17,6 @@ use bevy_ecs::{ }; use bevy_image::{BevyDefault as _, ToExtents}; use bevy_math::vec2; -use bevy_post_process::{bloom::bloom, motion_blur::node::motion_blur}; use bevy_reflect::{std_traits::ReflectDefault, Reflect}; use bevy_render::{ camera::{ExtractedCamera, MipBias, TemporalJitter}, @@ -72,8 +70,6 @@ impl Plugin for TemporalAntiAliasPlugin { render_app.add_systems( Core3d, temporal_anti_alias - .after(motion_blur) - .before(bloom) .in_set(Core3dSystems::PostProcess) .in_set(AntiAliasing), ); diff --git a/crates/bevy_core_pipeline/src/schedule.rs b/crates/bevy_core_pipeline/src/schedule.rs index c70cfcf76ce48..ffc6e95d91912 100644 --- a/crates/bevy_core_pipeline/src/schedule.rs +++ b/crates/bevy_core_pipeline/src/schedule.rs @@ -80,6 +80,10 @@ pub enum Core2dSystems { PostProcess, } +/// System set for ordering render graph systems relative to any anti-aliasing implementation. +#[derive(Debug, Hash, PartialEq, Eq, Clone, SystemSet)] +pub struct AntiAliasing; + impl Core2d { pub fn base_schedule() -> Schedule { use bevy_ecs::schedule::ScheduleBuildSettings; diff --git a/crates/bevy_pbr/src/meshlet/visibility_buffer_raster_node.rs b/crates/bevy_pbr/src/meshlet/visibility_buffer_raster_node.rs index fbfcc6299eea2..7925a349878a5 100644 --- a/crates/bevy_pbr/src/meshlet/visibility_buffer_raster_node.rs +++ b/crates/bevy_pbr/src/meshlet/visibility_buffer_raster_node.rs @@ -83,10 +83,8 @@ pub fn meshlet_visibility_buffer_raster( ctx.command_encoder() .push_debug_group("meshlet_visibility_buffer_raster"); - let time_span = diagnostics.time_span( - ctx.command_encoder(), - "meshlet_visibility_buffer_raster", - ); + let time_span = + diagnostics.time_span(ctx.command_encoder(), "meshlet_visibility_buffer_raster"); ctx.command_encoder().clear_buffer( &resource_manager.visibility_buffer_raster_cluster_prev_counts, @@ -222,7 +220,10 @@ pub fn meshlet_visibility_buffer_raster( )); let time_span_shadow = diagnostics.time_span( ctx.command_encoder(), - &format!("meshlet_visibility_buffer_raster: {}", shadow_view.pass_name), + &format!( + "meshlet_visibility_buffer_raster: {}", + shadow_view.pass_name + ), ); clear_visibility_buffer_pass( diff --git a/crates/bevy_post_process/src/bloom/mod.rs b/crates/bevy_post_process/src/bloom/mod.rs index 345b6b5d683dc..c0341fc039c57 100644 --- a/crates/bevy_post_process/src/bloom/mod.rs +++ b/crates/bevy_post_process/src/bloom/mod.rs @@ -13,7 +13,7 @@ use bevy_app::{App, Plugin}; use bevy_asset::embedded_asset; use bevy_color::{Gray, LinearRgba}; use bevy_core_pipeline::{ - schedule::{Core2d, Core2dSystems, Core3d, Core3dSystems}, + schedule::{AntiAliasing, Core2d, Core2dSystems, Core3d, Core3dSystems}, tonemapping::tonemapping, }; use bevy_ecs::prelude::*; @@ -76,7 +76,10 @@ impl Plugin for BloomPlugin { ) .add_systems( Core3d, - bloom.before(tonemapping).in_set(Core3dSystems::PostProcess), + bloom + .after(AntiAliasing) + .before(tonemapping) + .in_set(Core3dSystems::PostProcess), ) .add_systems( Core2d, diff --git a/crates/bevy_post_process/src/motion_blur/mod.rs b/crates/bevy_post_process/src/motion_blur/mod.rs index a829fb20fc056..ba636af16b971 100644 --- a/crates/bevy_post_process/src/motion_blur/mod.rs +++ b/crates/bevy_post_process/src/motion_blur/mod.rs @@ -8,7 +8,7 @@ use bevy_asset::embedded_asset; use bevy_camera::Camera; use bevy_core_pipeline::{ prepass::{DepthPrepass, MotionVectorPrepass}, - schedule::{Core3d, Core3dSystems}, + schedule::{AntiAliasing, Core3d, Core3dSystems}, }; use bevy_ecs::{ component::Component, @@ -154,6 +154,7 @@ impl Plugin for MotionBlurPlugin { render_app.add_systems( Core3d, node::motion_blur + .before(AntiAliasing) .before(bloom) .in_set(Core3dSystems::PostProcess), ); From 9d5cd76e375f20fc889ca7b5daa360bd3262e363 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?charlotte=20=F0=9F=8C=B8?= Date: Mon, 26 Jan 2026 20:49:07 -0800 Subject: [PATCH 54/55] Move solari init. --- crates/bevy_solari/src/realtime/node.rs | 418 ++++++++++++------------ 1 file changed, 209 insertions(+), 209 deletions(-) diff --git a/crates/bevy_solari/src/realtime/node.rs b/crates/bevy_solari/src/realtime/node.rs index 38daa4a4f6a08..b076c3f789a53 100644 --- a/crates/bevy_solari/src/realtime/node.rs +++ b/crates/bevy_solari/src/realtime/node.rs @@ -54,215 +54,6 @@ pub struct SolariLightingPipelines { resolve_dlss_rr_textures_pipeline: CachedComputePipelineId, } -/// Initializes the Solari lighting pipelines at render startup. -pub fn init_solari_lighting_pipelines( - mut commands: Commands, - pipeline_cache: Res, - scene_bindings: Res, - asset_server: Res, -) { - let bind_group_layout = BindGroupLayoutDescriptor::new( - "solari_lighting_bind_group_layout", - &BindGroupLayoutEntries::sequential( - ShaderStages::COMPUTE, - ( - texture_storage_2d( - ViewTarget::TEXTURE_FORMAT_HDR, - StorageTextureAccess::ReadWrite, - ), - storage_buffer_sized(false, None), - storage_buffer_sized(false, None), - texture_storage_2d(TextureFormat::Rgba32Uint, StorageTextureAccess::ReadWrite), - texture_storage_2d(TextureFormat::Rgba32Uint, StorageTextureAccess::ReadWrite), - storage_buffer_sized(false, None), - storage_buffer_sized(false, None), - texture_2d(TextureSampleType::Uint), - texture_depth_2d(), - texture_2d(TextureSampleType::Float { filterable: true }), - texture_2d(TextureSampleType::Uint), - texture_depth_2d(), - uniform_buffer::(true), - uniform_buffer::(true), - storage_buffer_sized(false, None), - storage_buffer_sized(false, None), - storage_buffer_sized(false, None), - storage_buffer_sized(false, None), - storage_buffer_sized(false, None), - storage_buffer_sized(false, None), - storage_buffer_sized(false, None), - storage_buffer_sized(false, None), - storage_buffer_sized(false, None), - storage_buffer_sized(false, None), - ), - ), - ); - - let bind_group_layout_world_cache_active_cells_dispatch = BindGroupLayoutDescriptor::new( - "solari_lighting_bind_group_layout_world_cache_active_cells_dispatch", - &BindGroupLayoutEntries::single(ShaderStages::COMPUTE, storage_buffer_sized(false, None)), - ); - - #[cfg(all(feature = "dlss", not(feature = "force_disable_dlss")))] - let bind_group_layout_resolve_dlss_rr_textures = BindGroupLayoutDescriptor::new( - "solari_lighting_bind_group_layout_resolve_dlss_rr_textures", - &BindGroupLayoutEntries::sequential( - ShaderStages::COMPUTE, - ( - texture_storage_2d(TextureFormat::Rgba8Unorm, StorageTextureAccess::WriteOnly), - texture_storage_2d(TextureFormat::Rgba8Unorm, StorageTextureAccess::WriteOnly), - texture_storage_2d(TextureFormat::Rgba16Float, StorageTextureAccess::WriteOnly), - texture_storage_2d(TextureFormat::Rg16Float, StorageTextureAccess::WriteOnly), - ), - ), - ); - - let create_pipeline = |label: &'static str, - entry_point: &'static str, - shader: Handle, - extra_bind_group_layout: Option<&BindGroupLayoutDescriptor>, - extra_shader_defs: Vec| { - let mut layout = vec![ - scene_bindings.bind_group_layout.clone(), - bind_group_layout.clone(), - ]; - if let Some(extra_bind_group_layout) = extra_bind_group_layout { - layout.push(extra_bind_group_layout.clone()); - } - - let mut shader_defs = vec![ShaderDefVal::UInt( - "WORLD_CACHE_SIZE".into(), - WORLD_CACHE_SIZE as u32, - )]; - shader_defs.extend_from_slice(&extra_shader_defs); - - pipeline_cache.queue_compute_pipeline(ComputePipelineDescriptor { - label: Some(label.into()), - layout, - immediate_size: 8, - shader, - shader_defs, - entry_point: Some(entry_point.into()), - ..default() - }) - }; - - commands.insert_resource(SolariLightingPipelines { - bind_group_layout: bind_group_layout.clone(), - bind_group_layout_world_cache_active_cells_dispatch: - bind_group_layout_world_cache_active_cells_dispatch.clone(), - #[cfg(all(feature = "dlss", not(feature = "force_disable_dlss")))] - bind_group_layout_resolve_dlss_rr_textures: bind_group_layout_resolve_dlss_rr_textures - .clone(), - decay_world_cache_pipeline: create_pipeline( - "solari_lighting_decay_world_cache_pipeline", - "decay_world_cache", - load_embedded_asset!(asset_server.as_ref(), "world_cache_compact.wgsl"), - Some(&bind_group_layout_world_cache_active_cells_dispatch), - vec!["WORLD_CACHE_NON_ATOMIC_LIFE_BUFFER".into()], - ), - compact_world_cache_single_block_pipeline: create_pipeline( - "solari_lighting_compact_world_cache_single_block_pipeline", - "compact_world_cache_single_block", - load_embedded_asset!(asset_server.as_ref(), "world_cache_compact.wgsl"), - Some(&bind_group_layout_world_cache_active_cells_dispatch), - vec!["WORLD_CACHE_NON_ATOMIC_LIFE_BUFFER".into()], - ), - compact_world_cache_blocks_pipeline: create_pipeline( - "solari_lighting_compact_world_cache_blocks_pipeline", - "compact_world_cache_blocks", - load_embedded_asset!(asset_server.as_ref(), "world_cache_compact.wgsl"), - Some(&bind_group_layout_world_cache_active_cells_dispatch), - vec![], - ), - compact_world_cache_write_active_cells_pipeline: create_pipeline( - "solari_lighting_compact_world_cache_write_active_cells_pipeline", - "compact_world_cache_write_active_cells", - load_embedded_asset!(asset_server.as_ref(), "world_cache_compact.wgsl"), - Some(&bind_group_layout_world_cache_active_cells_dispatch), - vec!["WORLD_CACHE_NON_ATOMIC_LIFE_BUFFER".into()], - ), - sample_di_for_world_cache_pipeline: create_pipeline( - "solari_lighting_sample_di_for_world_cache_pipeline", - "sample_di", - load_embedded_asset!(asset_server.as_ref(), "world_cache_update.wgsl"), - None, - vec![], - ), - sample_gi_for_world_cache_pipeline: create_pipeline( - "solari_lighting_sample_gi_for_world_cache_pipeline", - "sample_gi", - load_embedded_asset!(asset_server.as_ref(), "world_cache_update.wgsl"), - None, - vec!["WORLD_CACHE_QUERY_ATOMIC_MAX_LIFETIME".into()], - ), - blend_new_world_cache_samples_pipeline: create_pipeline( - "solari_lighting_blend_new_world_cache_samples_pipeline", - "blend_new_samples", - load_embedded_asset!(asset_server.as_ref(), "world_cache_update.wgsl"), - None, - vec![], - ), - presample_light_tiles_pipeline: create_pipeline( - "solari_lighting_presample_light_tiles_pipeline", - "presample_light_tiles", - load_embedded_asset!(asset_server.as_ref(), "presample_light_tiles.wgsl"), - None, - vec![], - ), - di_initial_and_temporal_pipeline: create_pipeline( - "solari_lighting_di_initial_and_temporal_pipeline", - "initial_and_temporal", - load_embedded_asset!(asset_server.as_ref(), "restir_di.wgsl"), - None, - vec![], - ), - di_spatial_and_shade_pipeline: create_pipeline( - "solari_lighting_di_spatial_and_shade_pipeline", - "spatial_and_shade", - load_embedded_asset!(asset_server.as_ref(), "restir_di.wgsl"), - None, - vec![], - ), - gi_initial_and_temporal_pipeline: create_pipeline( - "solari_lighting_gi_initial_and_temporal_pipeline", - "initial_and_temporal", - load_embedded_asset!(asset_server.as_ref(), "restir_gi.wgsl"), - None, - vec!["WORLD_CACHE_FIRST_BOUNCE_LIGHT_LEAK_PREVENTION".into()], - ), - gi_spatial_and_shade_pipeline: create_pipeline( - "solari_lighting_gi_spatial_and_shade_pipeline", - "spatial_and_shade", - load_embedded_asset!(asset_server.as_ref(), "restir_gi.wgsl"), - None, - vec![], - ), - specular_gi_pipeline: create_pipeline( - "solari_lighting_specular_gi_pipeline", - "specular_gi", - load_embedded_asset!(asset_server.as_ref(), "specular_gi.wgsl"), - None, - vec![], - ), - #[cfg(all(feature = "dlss", not(feature = "force_disable_dlss")))] - specular_gi_with_psr_pipeline: create_pipeline( - "solari_lighting_specular_gi_with_psr_pipeline", - "specular_gi", - load_embedded_asset!(asset_server.as_ref(), "specular_gi.wgsl"), - Some(&bind_group_layout_resolve_dlss_rr_textures), - vec!["DLSS_RR_GUIDE_BUFFERS".into()], - ), - #[cfg(all(feature = "dlss", not(feature = "force_disable_dlss")))] - resolve_dlss_rr_textures_pipeline: create_pipeline( - "solari_lighting_resolve_dlss_rr_textures_pipeline", - "resolve_dlss_rr_textures", - load_embedded_asset!(asset_server.as_ref(), "resolve_dlss_rr_textures.wgsl"), - Some(&bind_group_layout_resolve_dlss_rr_textures), - vec!["DLSS_RR_GUIDE_BUFFERS".into()], - ), - }); -} - #[cfg(any(not(feature = "dlss"), feature = "force_disable_dlss"))] type SolariLightingViewQuery = ( &'static SolariLighting, @@ -598,3 +389,212 @@ pub fn solari_lighting( "solari_lighting/world_cache_active_cells_count", ); } + +/// Initializes the Solari lighting pipelines at render startup. +pub fn init_solari_lighting_pipelines( + mut commands: Commands, + pipeline_cache: Res, + scene_bindings: Res, + asset_server: Res, +) { + let bind_group_layout = BindGroupLayoutDescriptor::new( + "solari_lighting_bind_group_layout", + &BindGroupLayoutEntries::sequential( + ShaderStages::COMPUTE, + ( + texture_storage_2d( + ViewTarget::TEXTURE_FORMAT_HDR, + StorageTextureAccess::ReadWrite, + ), + storage_buffer_sized(false, None), + storage_buffer_sized(false, None), + texture_storage_2d(TextureFormat::Rgba32Uint, StorageTextureAccess::ReadWrite), + texture_storage_2d(TextureFormat::Rgba32Uint, StorageTextureAccess::ReadWrite), + storage_buffer_sized(false, None), + storage_buffer_sized(false, None), + texture_2d(TextureSampleType::Uint), + texture_depth_2d(), + texture_2d(TextureSampleType::Float { filterable: true }), + texture_2d(TextureSampleType::Uint), + texture_depth_2d(), + uniform_buffer::(true), + uniform_buffer::(true), + storage_buffer_sized(false, None), + storage_buffer_sized(false, None), + storage_buffer_sized(false, None), + storage_buffer_sized(false, None), + storage_buffer_sized(false, None), + storage_buffer_sized(false, None), + storage_buffer_sized(false, None), + storage_buffer_sized(false, None), + storage_buffer_sized(false, None), + storage_buffer_sized(false, None), + ), + ), + ); + + let bind_group_layout_world_cache_active_cells_dispatch = BindGroupLayoutDescriptor::new( + "solari_lighting_bind_group_layout_world_cache_active_cells_dispatch", + &BindGroupLayoutEntries::single(ShaderStages::COMPUTE, storage_buffer_sized(false, None)), + ); + + #[cfg(all(feature = "dlss", not(feature = "force_disable_dlss")))] + let bind_group_layout_resolve_dlss_rr_textures = BindGroupLayoutDescriptor::new( + "solari_lighting_bind_group_layout_resolve_dlss_rr_textures", + &BindGroupLayoutEntries::sequential( + ShaderStages::COMPUTE, + ( + texture_storage_2d(TextureFormat::Rgba8Unorm, StorageTextureAccess::WriteOnly), + texture_storage_2d(TextureFormat::Rgba8Unorm, StorageTextureAccess::WriteOnly), + texture_storage_2d(TextureFormat::Rgba16Float, StorageTextureAccess::WriteOnly), + texture_storage_2d(TextureFormat::Rg16Float, StorageTextureAccess::WriteOnly), + ), + ), + ); + + let create_pipeline = |label: &'static str, + entry_point: &'static str, + shader: Handle, + extra_bind_group_layout: Option<&BindGroupLayoutDescriptor>, + extra_shader_defs: Vec| { + let mut layout = vec![ + scene_bindings.bind_group_layout.clone(), + bind_group_layout.clone(), + ]; + if let Some(extra_bind_group_layout) = extra_bind_group_layout { + layout.push(extra_bind_group_layout.clone()); + } + + let mut shader_defs = vec![ShaderDefVal::UInt( + "WORLD_CACHE_SIZE".into(), + WORLD_CACHE_SIZE as u32, + )]; + shader_defs.extend_from_slice(&extra_shader_defs); + + pipeline_cache.queue_compute_pipeline(ComputePipelineDescriptor { + label: Some(label.into()), + layout, + immediate_size: 8, + shader, + shader_defs, + entry_point: Some(entry_point.into()), + ..default() + }) + }; + + commands.insert_resource(SolariLightingPipelines { + bind_group_layout: bind_group_layout.clone(), + bind_group_layout_world_cache_active_cells_dispatch: + bind_group_layout_world_cache_active_cells_dispatch.clone(), + #[cfg(all(feature = "dlss", not(feature = "force_disable_dlss")))] + bind_group_layout_resolve_dlss_rr_textures: bind_group_layout_resolve_dlss_rr_textures + .clone(), + decay_world_cache_pipeline: create_pipeline( + "solari_lighting_decay_world_cache_pipeline", + "decay_world_cache", + load_embedded_asset!(asset_server.as_ref(), "world_cache_compact.wgsl"), + Some(&bind_group_layout_world_cache_active_cells_dispatch), + vec!["WORLD_CACHE_NON_ATOMIC_LIFE_BUFFER".into()], + ), + compact_world_cache_single_block_pipeline: create_pipeline( + "solari_lighting_compact_world_cache_single_block_pipeline", + "compact_world_cache_single_block", + load_embedded_asset!(asset_server.as_ref(), "world_cache_compact.wgsl"), + Some(&bind_group_layout_world_cache_active_cells_dispatch), + vec!["WORLD_CACHE_NON_ATOMIC_LIFE_BUFFER".into()], + ), + compact_world_cache_blocks_pipeline: create_pipeline( + "solari_lighting_compact_world_cache_blocks_pipeline", + "compact_world_cache_blocks", + load_embedded_asset!(asset_server.as_ref(), "world_cache_compact.wgsl"), + Some(&bind_group_layout_world_cache_active_cells_dispatch), + vec![], + ), + compact_world_cache_write_active_cells_pipeline: create_pipeline( + "solari_lighting_compact_world_cache_write_active_cells_pipeline", + "compact_world_cache_write_active_cells", + load_embedded_asset!(asset_server.as_ref(), "world_cache_compact.wgsl"), + Some(&bind_group_layout_world_cache_active_cells_dispatch), + vec!["WORLD_CACHE_NON_ATOMIC_LIFE_BUFFER".into()], + ), + sample_di_for_world_cache_pipeline: create_pipeline( + "solari_lighting_sample_di_for_world_cache_pipeline", + "sample_di", + load_embedded_asset!(asset_server.as_ref(), "world_cache_update.wgsl"), + None, + vec![], + ), + sample_gi_for_world_cache_pipeline: create_pipeline( + "solari_lighting_sample_gi_for_world_cache_pipeline", + "sample_gi", + load_embedded_asset!(asset_server.as_ref(), "world_cache_update.wgsl"), + None, + vec!["WORLD_CACHE_QUERY_ATOMIC_MAX_LIFETIME".into()], + ), + blend_new_world_cache_samples_pipeline: create_pipeline( + "solari_lighting_blend_new_world_cache_samples_pipeline", + "blend_new_samples", + load_embedded_asset!(asset_server.as_ref(), "world_cache_update.wgsl"), + None, + vec![], + ), + presample_light_tiles_pipeline: create_pipeline( + "solari_lighting_presample_light_tiles_pipeline", + "presample_light_tiles", + load_embedded_asset!(asset_server.as_ref(), "presample_light_tiles.wgsl"), + None, + vec![], + ), + di_initial_and_temporal_pipeline: create_pipeline( + "solari_lighting_di_initial_and_temporal_pipeline", + "initial_and_temporal", + load_embedded_asset!(asset_server.as_ref(), "restir_di.wgsl"), + None, + vec![], + ), + di_spatial_and_shade_pipeline: create_pipeline( + "solari_lighting_di_spatial_and_shade_pipeline", + "spatial_and_shade", + load_embedded_asset!(asset_server.as_ref(), "restir_di.wgsl"), + None, + vec![], + ), + gi_initial_and_temporal_pipeline: create_pipeline( + "solari_lighting_gi_initial_and_temporal_pipeline", + "initial_and_temporal", + load_embedded_asset!(asset_server.as_ref(), "restir_gi.wgsl"), + None, + vec!["WORLD_CACHE_FIRST_BOUNCE_LIGHT_LEAK_PREVENTION".into()], + ), + gi_spatial_and_shade_pipeline: create_pipeline( + "solari_lighting_gi_spatial_and_shade_pipeline", + "spatial_and_shade", + load_embedded_asset!(asset_server.as_ref(), "restir_gi.wgsl"), + None, + vec![], + ), + specular_gi_pipeline: create_pipeline( + "solari_lighting_specular_gi_pipeline", + "specular_gi", + load_embedded_asset!(asset_server.as_ref(), "specular_gi.wgsl"), + None, + vec![], + ), + #[cfg(all(feature = "dlss", not(feature = "force_disable_dlss")))] + specular_gi_with_psr_pipeline: create_pipeline( + "solari_lighting_specular_gi_with_psr_pipeline", + "specular_gi", + load_embedded_asset!(asset_server.as_ref(), "specular_gi.wgsl"), + Some(&bind_group_layout_resolve_dlss_rr_textures), + vec!["DLSS_RR_GUIDE_BUFFERS".into()], + ), + #[cfg(all(feature = "dlss", not(feature = "force_disable_dlss")))] + resolve_dlss_rr_textures_pipeline: create_pipeline( + "solari_lighting_resolve_dlss_rr_textures_pipeline", + "resolve_dlss_rr_textures", + load_embedded_asset!(asset_server.as_ref(), "resolve_dlss_rr_textures.wgsl"), + Some(&bind_group_layout_resolve_dlss_rr_textures), + vec!["DLSS_RR_GUIDE_BUFFERS".into()], + ), + }); +} From 3c1df3236c42712c2146109a87b66137aa1f7649 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?charlotte=20=F0=9F=8C=B8?= Date: Mon, 26 Jan 2026 21:11:09 -0800 Subject: [PATCH 55/55] Ci. --- crates/bevy_core_pipeline/src/schedule.rs | 10 +++++----- .../src/meshlet/visibility_buffer_raster_node.rs | 9 ++------- 2 files changed, 7 insertions(+), 12 deletions(-) diff --git a/crates/bevy_core_pipeline/src/schedule.rs b/crates/bevy_core_pipeline/src/schedule.rs index ffc6e95d91912..61177e57e631e 100644 --- a/crates/bevy_core_pipeline/src/schedule.rs +++ b/crates/bevy_core_pipeline/src/schedule.rs @@ -32,9 +32,9 @@ pub struct Core3d; /// System sets for the Core 3D rendering pipeline, defining the main stages of rendering. /// These stages include and run in the following order: -/// - Prepass: Initial rendering operations, such as depth pre-pass. -/// - MainPass: The primary rendering operations, including drawing opaque and transparent objects. -/// - PostProcess: Final rendering operations, such as post-processing effects. +/// - `Prepass`: Initial rendering operations, such as depth pre-pass. +/// - `MainPass`: The primary rendering operations, including drawing opaque and transparent objects. +/// - `PostProcess`: Final rendering operations, such as post-processing effects. /// /// Additional systems can be added to these sets to customize the rendering pipeline, or additional /// sets can be created relative to these core sets. @@ -69,8 +69,8 @@ pub struct Core2d; /// System sets for the Core 2D rendering pipeline, defining the main stages of rendering. /// These stages include and run in the following order: -/// - MainPass: The primary rendering operations, including drawing 2D sprites and meshes. -/// - PostProcess: Final rendering operations, such as post-processing effects. +/// - `MainPass`: The primary rendering operations, including drawing 2D sprites and meshes. +/// - `PostProcess`: Final rendering operations, such as post-processing effects. /// /// Additional systems can be added to these sets to customize the rendering pipeline, or additional /// sets can be created relative to these core sets. diff --git a/crates/bevy_pbr/src/meshlet/visibility_buffer_raster_node.rs b/crates/bevy_pbr/src/meshlet/visibility_buffer_raster_node.rs index 7925a349878a5..530854dd5ee8a 100644 --- a/crates/bevy_pbr/src/meshlet/visibility_buffer_raster_node.rs +++ b/crates/bevy_pbr/src/meshlet/visibility_buffer_raster_node.rs @@ -218,13 +218,8 @@ pub fn meshlet_visibility_buffer_raster( "meshlet_visibility_buffer_raster: {}", shadow_view.pass_name )); - let time_span_shadow = diagnostics.time_span( - ctx.command_encoder(), - &format!( - "meshlet_visibility_buffer_raster: {}", - shadow_view.pass_name - ), - ); + let time_span_shadow = + diagnostics.time_span(ctx.command_encoder(), shadow_view.pass_name.clone()); clear_visibility_buffer_pass( &mut ctx,