diff --git a/crates/bevy_core_pipeline/src/core_3d/mod.rs b/crates/bevy_core_pipeline/src/core_3d/mod.rs index d09f6a5525..fa092b7fd4 100644 --- a/crates/bevy_core_pipeline/src/core_3d/mod.rs +++ b/crates/bevy_core_pipeline/src/core_3d/mod.rs @@ -19,7 +19,8 @@ pub mod graph { EarlyPrepass, EarlyDownsampleDepth, LatePrepass, - DeferredPrepass, + EarlyDeferredPrepass, + LateDeferredPrepass, CopyDeferredLightingId, EndPrepasses, StartMainPass, @@ -112,7 +113,8 @@ use tracing::warn; use crate::{ core_3d::main_transmissive_pass_3d_node::MainTransmissivePass3dNode, deferred::{ - copy_lighting_id::CopyDeferredLightingIdNode, node::DeferredGBufferPrepassNode, + copy_lighting_id::CopyDeferredLightingIdNode, + node::{EarlyDeferredGBufferPrepassNode, LateDeferredGBufferPrepassNode}, AlphaMask3dDeferred, Opaque3dDeferred, DEFERRED_LIGHTING_PASS_ID_FORMAT, DEFERRED_PREPASS_FORMAT, }, @@ -179,9 +181,13 @@ impl Plugin for Core3dPlugin { .add_render_sub_graph(Core3d) .add_render_graph_node::>(Core3d, Node3d::EarlyPrepass) .add_render_graph_node::>(Core3d, Node3d::LatePrepass) - .add_render_graph_node::>( + .add_render_graph_node::>( Core3d, - Node3d::DeferredPrepass, + Node3d::EarlyDeferredPrepass, + ) + .add_render_graph_node::>( + Core3d, + Node3d::LateDeferredPrepass, ) .add_render_graph_node::>( Core3d, @@ -210,8 +216,9 @@ impl Plugin for Core3dPlugin { Core3d, ( Node3d::EarlyPrepass, + Node3d::EarlyDeferredPrepass, Node3d::LatePrepass, - Node3d::DeferredPrepass, + Node3d::LateDeferredPrepass, Node3d::CopyDeferredLightingId, Node3d::EndPrepasses, Node3d::StartMainPass, @@ -943,7 +950,6 @@ fn configure_occlusion_culling_view_targets( With, Without, With, - Without, ), >, ) { diff --git a/crates/bevy_core_pipeline/src/deferred/node.rs b/crates/bevy_core_pipeline/src/deferred/node.rs index 7fe111de9b..ffac1eec6d 100644 --- a/crates/bevy_core_pipeline/src/deferred/node.rs +++ b/crates/bevy_core_pipeline/src/deferred/node.rs @@ -1,7 +1,8 @@ use bevy_ecs::{prelude::*, query::QueryItem}; +use bevy_render::experimental::occlusion_culling::OcclusionCulling; use bevy_render::render_graph::ViewNode; -use bevy_render::view::ExtractedView; +use bevy_render::view::{ExtractedView, NoIndirectDrawing}; use bevy_render::{ camera::ExtractedCamera, render_graph::{NodeRunError, RenderGraphContext}, @@ -18,76 +19,151 @@ use crate::prepass::ViewPrepassTextures; use super::{AlphaMask3dDeferred, Opaque3dDeferred}; -/// Render node used by the prepass. +/// The phase of the deferred prepass that draws meshes that were visible last +/// frame. /// -/// By default, inserted before the main pass in the render graph. +/// 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 DeferredGBufferPrepassNode; +pub struct EarlyDeferredGBufferPrepassNode; -impl ViewNode for DeferredGBufferPrepassNode { +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", + ) + } +} + +/// 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; + +impl ViewNode for LateDeferredGBufferPrepassNode { type ViewQuery = ( &'static ExtractedCamera, &'static ExtractedView, &'static ViewDepthTexture, &'static ViewPrepassTextures, + Has, + Has, ); fn run<'w>( &self, graph: &mut RenderGraphContext, render_context: &mut RenderContext<'w>, - (camera, extracted_view, view_depth_texture, view_prepass_textures): QueryItem< - 'w, - Self::ViewQuery, - >, + view_query: QueryItem<'w, Self::ViewQuery>, world: &'w World, ) -> Result<(), NodeRunError> { - let (Some(opaque_deferred_phases), Some(alpha_mask_deferred_phases)) = ( - world.get_resource::>(), - world.get_resource::>(), - ) else { + let (_, _, _, _, occlusion_culling, no_indirect_drawing) = view_query; + if !occlusion_culling || no_indirect_drawing { 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(()); - }; + run_deferred_prepass( + graph, + render_context, + view_query, + true, + world, + "late deferred prepass", + ) + } +} - let mut color_attachments = vec![]; - color_attachments.push( - view_prepass_textures - .normal - .as_ref() - .map(|normals_texture| normals_texture.get_attachment()), - ); - color_attachments.push( - view_prepass_textures - .motion_vectors - .as_ref() - .map(|motion_vectors_texture| motion_vectors_texture.get_attachment()), - ); +/// 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, _, _): QueryItem< + 'w, + ::ViewQuery, + >, + is_late: bool, + world: &'w World, + label: &'static str, +) -> Result<(), NodeRunError> { + let (Some(opaque_deferred_phases), Some(alpha_mask_deferred_phases)) = ( + world.get_resource::>(), + world.get_resource::>(), + ) else { + return Ok(()); + }; - // If we clear the deferred texture with LoadOp::Clear(Default::default()) we get these errors: - // Chrome: GL_INVALID_OPERATION: No defined conversion between clear value and attachment format. - // Firefox: WebGL warning: clearBufferu?[fi]v: This attachment is of type FLOAT, but this function is of type UINT. - // Appears to be unsupported: https://registry.khronos.org/webgl/specs/latest/2.0/#3.7.9 - // For webgl2 we fallback to manually clearing - #[cfg(all(feature = "webgl", target_arch = "wasm32", not(feature = "webgpu")))] + 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(()); + }; + + let mut color_attachments = vec![]; + color_attachments.push( + view_prepass_textures + .normal + .as_ref() + .map(|normals_texture| normals_texture.get_attachment()), + ); + color_attachments.push( + view_prepass_textures + .motion_vectors + .as_ref() + .map(|motion_vectors_texture| motion_vectors_texture.get_attachment()), + ); + + // If we clear the deferred texture with LoadOp::Clear(Default::default()) we get these errors: + // Chrome: GL_INVALID_OPERATION: No defined conversion between clear value and attachment format. + // Firefox: WebGL warning: clearBufferu?[fi]v: This attachment is of type FLOAT, but this function is of type UINT. + // Appears to be unsupported: https://registry.khronos.org/webgl/specs/latest/2.0/#3.7.9 + // For webgl2 we fallback to manually clearing + #[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( &deferred_texture.texture.texture, &bevy_render::render_resource::ImageSubresourceRange::default(), ); } + } - color_attachments.push( - view_prepass_textures - .deferred - .as_ref() - .map(|deferred_texture| { + color_attachments.push( + view_prepass_textures + .deferred + .as_ref() + .map(|deferred_texture| { + if is_late { + deferred_texture.get_attachment() + } else { #[cfg(all(feature = "webgl", target_arch = "wasm32", not(feature = "webgpu")))] { bevy_render::render_resource::RenderPassColorAttachment { @@ -105,87 +181,82 @@ impl ViewNode for DeferredGBufferPrepassNode { feature = "webgpu" ))] deferred_texture.get_attachment() - }), - ); - - color_attachments.push( - view_prepass_textures - .deferred_lighting_pass_id - .as_ref() - .map(|deferred_lighting_pass_id| deferred_lighting_pass_id.get_attachment()), - ); - - // If all color attachments are none: clear the color attachment list so that no fragment shader is required - if color_attachments.iter().all(Option::is_none) { - color_attachments.clear(); - } - - 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("deferred_prepass"), - color_attachments: &color_attachments, - depth_stencil_attachment, - timestamp_writes: None, - occlusion_query_set: None, - }); - let mut render_pass = TrackedRenderPass::new(&render_device, render_pass); - if let Some(viewport) = camera.viewport.as_ref() { - render_pass.set_camera_viewport(viewport); - } - - // 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:?}"); } - } + }), + ); - // 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:?}" - ); - } - } + color_attachments.push( + view_prepass_textures + .deferred_lighting_pass_id + .as_ref() + .map(|deferred_lighting_pass_id| deferred_lighting_pass_id.get_attachment()), + ); - drop(render_pass); + // If all color attachments are none: clear the color attachment list so that no fragment shader is required + if color_attachments.iter().all(Option::is_none) { + color_attachments.clear(); + } - // 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, - ); - } + let depth_stencil_attachment = Some(view_depth_texture.get_attachment(StoreOp::Store)); - command_encoder.finish() + 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"), }); - Ok(()) - } + // 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); + if let Some(viewport) = camera.viewport.as_ref() { + render_pass.set_camera_viewport(viewport); + } + + // 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:?}"); + } + } + + // 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:?}"); + } + } + + 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, + ); + } + + command_encoder.finish() + }); + + Ok(()) } 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 6f150363fa..0c6a7feddb 100644 --- a/crates/bevy_core_pipeline/src/experimental/mip_generation/mod.rs +++ b/crates/bevy_core_pipeline/src/experimental/mip_generation/mod.rs @@ -46,7 +46,7 @@ use crate::{ graph::{Core3d, Node3d}, prepare_core_3d_depth_textures, }, - prepass::{DeferredPrepass, DepthPrepass}, + prepass::DepthPrepass, }; /// Identifies the `downsample_depth.wgsl` shader. @@ -93,9 +93,10 @@ impl Plugin for MipGenerationPlugin { Core3d, ( Node3d::EarlyPrepass, + Node3d::EarlyDeferredPrepass, Node3d::EarlyDownsampleDepth, Node3d::LatePrepass, - Node3d::DeferredPrepass, + Node3d::LateDeferredPrepass, ), ) .add_render_graph_edges( @@ -651,7 +652,6 @@ fn prepare_view_depth_pyramids( With, Without, With, - Without, ), >, ) { diff --git a/crates/bevy_core_pipeline/src/prepass/node.rs b/crates/bevy_core_pipeline/src/prepass/node.rs index 942983b063..04cc1890b0 100644 --- a/crates/bevy_core_pipeline/src/prepass/node.rs +++ b/crates/bevy_core_pipeline/src/prepass/node.rs @@ -66,6 +66,7 @@ impl ViewNode for LatePrepassNode { Option<&'static PreviousViewUniformOffset>, Has, Has, + Has, ); fn run<'w>( @@ -77,7 +78,7 @@ impl ViewNode for LatePrepassNode { ) -> Result<(), NodeRunError> { // We only need a late prepass if we have occlusion culling and indirect // drawing. - let (_, _, _, _, _, _, _, _, _, occlusion_culling, no_indirect_drawing) = query; + let (_, _, _, _, _, _, _, _, _, occlusion_culling, no_indirect_drawing, _) = query; if !occlusion_culling || no_indirect_drawing { return Ok(()); } @@ -110,10 +111,18 @@ fn run_prepass<'w>( view_prev_uniform_offset, _, _, + has_deferred, ): QueryItem<'w, ::ViewQuery>, world: &'w World, 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(()); + } + let (Some(opaque_prepass_phases), Some(alpha_mask_prepass_phases)) = ( world.get_resource::>(), world.get_resource::>(), diff --git a/crates/bevy_pbr/src/meshlet/mod.rs b/crates/bevy_pbr/src/meshlet/mod.rs index 6e8e9141a1..0333be74fe 100644 --- a/crates/bevy_pbr/src/meshlet/mod.rs +++ b/crates/bevy_pbr/src/meshlet/mod.rs @@ -258,8 +258,6 @@ impl Plugin for MeshletPlugin { NodeMeshlet::Prepass, // NodeMeshlet::DeferredPrepass, - Node3d::DeferredPrepass, - Node3d::CopyDeferredLightingId, Node3d::EndPrepasses, // Node3d::StartMainPass, diff --git a/crates/bevy_pbr/src/render/gpu_preprocess.rs b/crates/bevy_pbr/src/render/gpu_preprocess.rs index 22ebc06ffc..80035a31cc 100644 --- a/crates/bevy_pbr/src/render/gpu_preprocess.rs +++ b/crates/bevy_pbr/src/render/gpu_preprocess.rs @@ -13,10 +13,7 @@ use bevy_asset::{load_internal_asset, weak_handle, Handle}; use bevy_core_pipeline::{ core_3d::graph::{Core3d, Node3d}, experimental::mip_generation::ViewDepthPyramid, - prepass::{ - DeferredPrepass, DepthPrepass, PreviousViewData, PreviousViewUniformOffset, - PreviousViewUniforms, - }, + prepass::{DepthPrepass, PreviousViewData, PreviousViewUniformOffset, PreviousViewUniforms}, }; use bevy_derive::{Deref, DerefMut}; use bevy_ecs::{ @@ -140,7 +137,6 @@ pub struct LateGpuPreprocessNode { Without, With, With, - Without, ), >, } @@ -159,7 +155,6 @@ pub struct EarlyPrepassBuildIndirectParametersNode { Without, Without, With, - Without, ), >, } @@ -180,7 +175,6 @@ pub struct LatePrepassBuildIndirectParametersNode { Without, With, With, - Without, ), >, } @@ -527,21 +521,18 @@ impl Plugin for GpuMeshPreprocessPlugin { NodePbr::EarlyGpuPreprocess, NodePbr::EarlyPrepassBuildIndirectParameters, Node3d::EarlyPrepass, + Node3d::EarlyDeferredPrepass, Node3d::EarlyDownsampleDepth, NodePbr::LateGpuPreprocess, NodePbr::LatePrepassBuildIndirectParameters, Node3d::LatePrepass, + Node3d::LateDeferredPrepass, NodePbr::MainBuildIndirectParameters, // Shadows don't currently support occlusion culling, so we // treat shadows as effectively the main phase for our // purposes. NodePbr::ShadowPass, ), - ) - .add_render_graph_edge( - Core3d, - NodePbr::MainBuildIndirectParameters, - Node3d::DeferredPrepass, ); } } diff --git a/examples/tools/scene_viewer/main.rs b/examples/tools/scene_viewer/main.rs index 6935798169..37ef81dbc6 100644 --- a/examples/tools/scene_viewer/main.rs +++ b/examples/tools/scene_viewer/main.rs @@ -49,6 +49,9 @@ struct Args { /// enable deferred shading #[argh(switch)] deferred: Option, + /// spawn a light even if the scene already has one + #[argh(switch)] + add_light: Option, } fn main() { @@ -204,7 +207,7 @@ fn setup_scene_after_load( } // Spawn a default light if the scene does not have one - if !scene_handle.has_light { + if !scene_handle.has_light || args.add_light == Some(true) { info!("Spawning a directional light"); commands.spawn(( DirectionalLight::default(),