From 669df0ed0cf8e7d95fe9889990f60165aab49cd8 Mon Sep 17 00:00:00 2001 From: Marco Buono Date: Mon, 16 Oct 2023 21:16:21 -0300 Subject: [PATCH] `*_PREPASS` Shader Def Cleanup (#10136) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit # Objective - This PR aims to make the various `*_PREPASS` shader defs we have (`NORMAL_PREPASS`, `DEPTH_PREPASS`, `MOTION_VECTORS_PREPASS` AND `DEFERRED_PREPASS`) easier to use and understand: - So that their meaning is now consistent across all contexts; (“prepass X is enabled for the current view”) - So that they're also consistently set across all contexts. - It also aims to enable us to (with a follow up PR) to conditionally gate the `BindGroupEntry` and `BindGroupLayoutEntry` items associated with these prepasses, saving us up to 4 texture slots in WebGL (currently globally limited to 16 per shader, regardless of bind groups) ## Solution - We now consistently set these from `PrepassPipeline`, the `MeshPipeline` and the `DeferredLightingPipeline`, we also set their `MeshPipelineKey`s; - We introduce `PREPASS_PIPELINE`, `MESH_PIPELINE` and `DEFERRED_LIGHTING_PIPELINE` that can be used to detect where the code is running, without overloading the meanings of the prepass shader defs; - We also gate the WGSL functions in `bevy_pbr::prepass_utils` with `#ifdef`s for their respective shader defs, so that shader code can provide a fallback whenever they're not available. - This allows us to conditionally include the bindings for these prepass textures (My next PR, which will hopefully unblock #8015) - @robtfm mentioned [these were being used to prevent accessing the same binding as read/write in the prepass](https://discord.com/channels/691052431525675048/743663924229963868/1163270458393759814), however even after reversing the `#ifndef`s I had no issues running the code, so perhaps the compiler is already smart enough even without tree shaking to know they're not being used, thanks to `#ifdef PREPASS_PIPELINE`? ## Comparison ### Before | Shader Def | `PrepassPipeline` | `MeshPipeline` | `DeferredLightingPipeline` | | ------------------------ | ----------------- | -------------- | -------------------------- | | `NORMAL_PREPASS` | Yes | No | No | | `DEPTH_PREPASS` | Yes | No | No | | `MOTION_VECTORS_PREPASS` | Yes | No | No | | `DEFERRED_PREPASS` | Yes | No | No | | View Key | `PrepassPipeline` | `MeshPipeline` | `DeferredLightingPipeline` | | ------------------------ | ----------------- | -------------- | -------------------------- | | `NORMAL_PREPASS` | Yes | Yes | No | | `DEPTH_PREPASS` | Yes | No | No | | `MOTION_VECTORS_PREPASS` | Yes | No | No | | `DEFERRED_PREPASS` | Yes | Yes\* | No | \* Accidentally was being set twice, once with only `deferred_prepass.is_some()` as a condition, and once with `deferred_p repass.is_some() && !forward` as a condition. ### After | Shader Def | `PrepassPipeline` | `MeshPipeline` | `DeferredLightingPipeline` | | ---------------------------- | ----------------- | --------------- | -------------------------- | | `NORMAL_PREPASS` | Yes | Yes | Yes | | `DEPTH_PREPASS` | Yes | Yes | Yes | | `MOTION_VECTORS_PREPASS` | Yes | Yes | Yes | | `DEFERRED_PREPASS` | Yes | Yes | Unconditionally | | `PREPASS_PIPELINE` | Unconditionally | No | No | | `MESH_PIPELINE` | No | Unconditionally | No | | `DEFERRED_LIGHTING_PIPELINE` | No | No | Unconditionally | | View Key | `PrepassPipeline` | `MeshPipeline` | `DeferredLightingPipeline` | | ------------------------ | ----------------- | -------------- | -------------------------- | | `NORMAL_PREPASS` | Yes | Yes | Yes | | `DEPTH_PREPASS` | Yes | Yes | Yes | | `MOTION_VECTORS_PREPASS` | Yes | Yes | Yes | | `DEFERRED_PREPASS` | Yes | Yes | Unconditionally | --- ## Changelog - Cleaned up WGSL `*_PREPASS` shader defs so they're now consistently used everywhere; - Introduced `PREPASS_PIPELINE`, `MESH_PIPELINE` and `DEFERRED_LIGHTING_PIPELINE` WGSL shader defs for conditionally compiling logic based the current pipeline; - WGSL functions from `bevy_pbr::prepass_utils` are now guarded with `#ifdef` based on the currently enabled prepasses; ## Migration Guide - When using functions from `bevy_pbr::prepass_utils` (`prepass_depth()`, `prepass_normal()`, `prepass_motion_vector()`) in contexts where these prepasses might be disabled, you should now wrap your calls with the appropriate `#ifdef` guards, (`#ifdef DEPTH_PREPASS`, `#ifdef NORMAL_PREPASS`, `#ifdef MOTION_VECTOR_PREPASS`) providing fallback logic where applicable. --------- Co-authored-by: Carter Anderson Co-authored-by: IceSentry --- .../src/deferred/deferred_lighting.wgsl | 2 + crates/bevy_pbr/src/deferred/mod.rs | 52 ++++++++++++++++++- crates/bevy_pbr/src/material.rs | 29 +++++++---- crates/bevy_pbr/src/prepass/mod.rs | 5 ++ .../bevy_pbr/src/prepass/prepass_utils.wgsl | 6 +-- crates/bevy_pbr/src/render/mesh.rs | 19 +++++++ crates/bevy_pbr/src/render/pbr.wgsl | 14 ++--- 7 files changed, 104 insertions(+), 23 deletions(-) diff --git a/crates/bevy_pbr/src/deferred/deferred_lighting.wgsl b/crates/bevy_pbr/src/deferred/deferred_lighting.wgsl index d9c2f3e4e08f8a..acd898a6b32e06 100644 --- a/crates/bevy_pbr/src/deferred/deferred_lighting.wgsl +++ b/crates/bevy_pbr/src/deferred/deferred_lighting.wgsl @@ -50,7 +50,9 @@ fn fragment(in: FullscreenVertexOutput) -> @location(0) vec4 { #ifdef WEBGL2 frag_coord.z = deferred_types::unpack_unorm3x4_plus_unorm_20_(deferred_data.b).w; #else +#ifdef DEPTH_PREPASS frag_coord.z = bevy_pbr::prepass_utils::prepass_depth(in.position, 0u); +#endif #endif var pbr_input = pbr_input_from_deferred_gbuffer(frag_coord, deferred_data); diff --git a/crates/bevy_pbr/src/deferred/mod.rs b/crates/bevy_pbr/src/deferred/mod.rs index 564c973eef460a..568be134be18a8 100644 --- a/crates/bevy_pbr/src/deferred/mod.rs +++ b/crates/bevy_pbr/src/deferred/mod.rs @@ -8,7 +8,7 @@ use bevy_core_pipeline::{ copy_lighting_id::DeferredLightingIdDepthTexture, DEFERRED_LIGHTING_PASS_ID_DEPTH_FORMAT, }, prelude::{Camera3d, ClearColor}, - prepass::DeferredPrepass, + prepass::{DeferredPrepass, DepthPrepass, MotionVectorPrepass, NormalPrepass}, tonemapping::{DebandDither, Tonemapping}, }; use bevy_ecs::{prelude::*, query::QueryItem}; @@ -258,6 +258,9 @@ impl SpecializedRenderPipeline for DeferredLightingLayout { fn specialize(&self, key: Self::Key) -> RenderPipelineDescriptor { let mut shader_defs = Vec::new(); + // Let the shader code know that it's running in a deferred pipeline. + shader_defs.push("DEFERRED_LIGHTING_PIPELINE".into()); + #[cfg(all(feature = "webgl", target_arch = "wasm32"))] shader_defs.push("WEBGL2".into()); @@ -298,6 +301,21 @@ impl SpecializedRenderPipeline for DeferredLightingLayout { shader_defs.push("ENVIRONMENT_MAP".into()); } + if key.contains(MeshPipelineKey::NORMAL_PREPASS) { + shader_defs.push("NORMAL_PREPASS".into()); + } + + if key.contains(MeshPipelineKey::DEPTH_PREPASS) { + shader_defs.push("DEPTH_PREPASS".into()); + } + + if key.contains(MeshPipelineKey::MOTION_VECTOR_PREPASS) { + shader_defs.push("MOTION_VECTOR_PREPASS".into()); + } + + // Always true, since we're in the deferred lighting pipeline + shader_defs.push("DEFERRED_PREPASS".into()); + let shadow_filter_method = key.intersection(MeshPipelineKey::SHADOW_FILTER_METHOD_RESERVED_BITS); if shadow_filter_method == MeshPipelineKey::SHADOW_FILTER_METHOD_HARDWARE_2X2 { @@ -408,14 +426,44 @@ pub fn prepare_deferred_lighting_pipelines( Option<&EnvironmentMapLight>, Option<&ShadowFilteringMethod>, Option<&ScreenSpaceAmbientOcclusionSettings>, + ( + Has, + Has, + Has, + ), ), With, >, images: Res>, ) { - for (entity, view, tonemapping, dither, environment_map, shadow_filter_method, ssao) in &views { + for ( + entity, + view, + tonemapping, + dither, + environment_map, + shadow_filter_method, + ssao, + (normal_prepass, depth_prepass, motion_vector_prepass), + ) in &views + { let mut view_key = MeshPipelineKey::from_hdr(view.hdr); + if normal_prepass { + view_key |= MeshPipelineKey::NORMAL_PREPASS; + } + + if depth_prepass { + view_key |= MeshPipelineKey::DEPTH_PREPASS; + } + + if motion_vector_prepass { + view_key |= MeshPipelineKey::MOTION_VECTOR_PREPASS; + } + + // Always true, since we're in the deferred lighting pipeline + view_key |= MeshPipelineKey::DEFERRED_PREPASS; + if !view.hdr { if let Some(tonemapping) = tonemapping { view_key |= MeshPipelineKey::TONEMAP_IN_SHADER; diff --git a/crates/bevy_pbr/src/material.rs b/crates/bevy_pbr/src/material.rs index 4e1ded19b7750d..490083bae5a0a3 100644 --- a/crates/bevy_pbr/src/material.rs +++ b/crates/bevy_pbr/src/material.rs @@ -8,7 +8,7 @@ use bevy_asset::{Asset, AssetApp, AssetEvent, AssetId, AssetServer, Assets, Hand use bevy_core_pipeline::{ core_3d::{AlphaMask3d, Opaque3d, Transparent3d}, experimental::taa::TemporalAntiAliasSettings, - prepass::{DeferredPrepass, NormalPrepass}, + prepass::{DeferredPrepass, DepthPrepass, MotionVectorPrepass, NormalPrepass}, tonemapping::{DebandDither, Tonemapping}, }; use bevy_derive::{Deref, DerefMut}; @@ -450,8 +450,12 @@ pub fn queue_material_meshes( Option<&EnvironmentMapLight>, Option<&ShadowFilteringMethod>, Option<&ScreenSpaceAmbientOcclusionSettings>, - Option<&NormalPrepass>, - Option<&DeferredPrepass>, + ( + Has, + Has, + Has, + Has, + ), Option<&TemporalAntiAliasSettings>, &mut RenderPhase, &mut RenderPhase, @@ -468,8 +472,7 @@ pub fn queue_material_meshes( environment_map, shadow_filter_method, ssao, - normal_prepass, - deferred_prepass, + (normal_prepass, depth_prepass, motion_vector_prepass, deferred_prepass), taa_settings, mut opaque_phase, mut alpha_mask_phase, @@ -483,11 +486,19 @@ pub fn queue_material_meshes( let mut view_key = MeshPipelineKey::from_msaa_samples(msaa.samples()) | MeshPipelineKey::from_hdr(view.hdr); - if normal_prepass.is_some() { + if normal_prepass { view_key |= MeshPipelineKey::NORMAL_PREPASS; } - if deferred_prepass.is_some() { + if depth_prepass { + view_key |= MeshPipelineKey::DEPTH_PREPASS; + } + + if motion_vector_prepass { + view_key |= MeshPipelineKey::MOTION_VECTOR_PREPASS; + } + + if deferred_prepass { view_key |= MeshPipelineKey::DEFERRED_PREPASS; } @@ -554,10 +565,6 @@ pub fn queue_material_meshes( } mesh_key |= alpha_mode_pipeline_key(material.properties.alpha_mode); - if deferred_prepass.is_some() && !forward { - mesh_key |= MeshPipelineKey::DEFERRED_PREPASS; - } - let pipeline_id = pipelines.specialize( &pipeline_cache, &material_pipeline, diff --git a/crates/bevy_pbr/src/prepass/mod.rs b/crates/bevy_pbr/src/prepass/mod.rs index 53e26506121a0b..6e89575bb6b0a4 100644 --- a/crates/bevy_pbr/src/prepass/mod.rs +++ b/crates/bevy_pbr/src/prepass/mod.rs @@ -378,6 +378,11 @@ where let mut shader_defs = Vec::new(); let mut vertex_attributes = Vec::new(); + // Let the shader code know that it's running in a prepass pipeline. + // (PBR code will use this to detect that it's running in deferred mode, + // since that's the only time it gets called from a prepass pipeline.) + shader_defs.push("PREPASS_PIPELINE".into()); + // NOTE: Eventually, it would be nice to only add this when the shaders are overloaded by the Material. // The main limitation right now is that bind group order is hardcoded in shaders. bind_group_layouts.insert(1, self.material_layout.clone()); diff --git a/crates/bevy_pbr/src/prepass/prepass_utils.wgsl b/crates/bevy_pbr/src/prepass/prepass_utils.wgsl index f36e373684934b..2d8ec615076afc 100644 --- a/crates/bevy_pbr/src/prepass/prepass_utils.wgsl +++ b/crates/bevy_pbr/src/prepass/prepass_utils.wgsl @@ -2,7 +2,7 @@ #import bevy_pbr::mesh_view_bindings as view_bindings -#ifndef DEPTH_PREPASS +#ifdef DEPTH_PREPASS fn prepass_depth(frag_coord: vec4, sample_index: u32) -> f32 { #ifdef MULTISAMPLED let depth_sample = textureLoad(view_bindings::depth_prepass_texture, vec2(frag_coord.xy), i32(sample_index)); @@ -13,7 +13,7 @@ fn prepass_depth(frag_coord: vec4, sample_index: u32) -> f32 { } #endif // DEPTH_PREPASS -#ifndef NORMAL_PREPASS +#ifdef NORMAL_PREPASS fn prepass_normal(frag_coord: vec4, sample_index: u32) -> vec3 { #ifdef MULTISAMPLED let normal_sample = textureLoad(view_bindings::normal_prepass_texture, vec2(frag_coord.xy), i32(sample_index)); @@ -24,7 +24,7 @@ fn prepass_normal(frag_coord: vec4, sample_index: u32) -> vec3 { } #endif // NORMAL_PREPASS -#ifndef MOTION_VECTOR_PREPASS +#ifdef MOTION_VECTOR_PREPASS fn prepass_motion_vector(frag_coord: vec4, sample_index: u32) -> vec2 { #ifdef MULTISAMPLED let motion_vector_sample = textureLoad(view_bindings::motion_vector_prepass_texture, vec2(frag_coord.xy), i32(sample_index)); diff --git a/crates/bevy_pbr/src/render/mesh.rs b/crates/bevy_pbr/src/render/mesh.rs index 1675997707efcb..8d4fbb7b2f5450 100644 --- a/crates/bevy_pbr/src/render/mesh.rs +++ b/crates/bevy_pbr/src/render/mesh.rs @@ -772,6 +772,9 @@ impl SpecializedMeshPipeline for MeshPipeline { let mut shader_defs = Vec::new(); let mut vertex_attributes = Vec::new(); + // Let the shader code know that it's running in a mesh pipeline. + shader_defs.push("MESH_PIPELINE".into()); + shader_defs.push("VERTEX_OUTPUT_INSTANCE_INDEX".into()); if layout.contains(Mesh::ATTRIBUTE_POSITION) { @@ -870,6 +873,22 @@ impl SpecializedMeshPipeline for MeshPipeline { is_opaque = true; } + if key.contains(MeshPipelineKey::NORMAL_PREPASS) { + shader_defs.push("NORMAL_PREPASS".into()); + } + + if key.contains(MeshPipelineKey::DEPTH_PREPASS) { + shader_defs.push("DEPTH_PREPASS".into()); + } + + if key.contains(MeshPipelineKey::MOTION_VECTOR_PREPASS) { + shader_defs.push("MOTION_VECTOR_PREPASS".into()); + } + + if key.contains(MeshPipelineKey::DEFERRED_PREPASS) { + shader_defs.push("DEFERRED_PREPASS".into()); + } + if key.contains(MeshPipelineKey::NORMAL_PREPASS) && key.msaa_samples() == 1 && is_opaque { shader_defs.push("LOAD_PREPASS_NORMALS".into()); } diff --git a/crates/bevy_pbr/src/render/pbr.wgsl b/crates/bevy_pbr/src/render/pbr.wgsl index 77057f27558910..ba7aca60ba0928 100644 --- a/crates/bevy_pbr/src/render/pbr.wgsl +++ b/crates/bevy_pbr/src/render/pbr.wgsl @@ -16,13 +16,13 @@ #import bevy_pbr::gtao_utils gtao_multibounce #endif -#ifdef DEFERRED_PREPASS +#ifdef PREPASS_PIPELINE #import bevy_pbr::pbr_deferred_functions deferred_gbuffer_from_pbr_input #import bevy_pbr::pbr_prepass_functions calculate_motion_vector #import bevy_pbr::prepass_io VertexOutput, FragmentOutput -#else // DEFERRED_PREPASS +#else // PREPASS_PIPELINE #import bevy_pbr::forward_io VertexOutput, FragmentOutput -#endif // DEFERRED_PREPASS +#endif // PREPASS_PIPELINE #ifdef MOTION_VECTOR_PREPASS @group(0) @binding(2) @@ -159,7 +159,7 @@ fn fragment( pbr_input.V = V; } else { // if UNLIT_BIT != 0 -#ifdef DEFERRED_PREPASS +#ifdef PREPASS_PIPELINE // in deferred mode, we need to fill some of the pbr input data even for unlit materials // to pass through the gbuffer to the deferred lighting shader pbr_input = pbr_types::pbr_input_new(); @@ -179,7 +179,7 @@ fn fragment( // generate output // --------------- -#ifdef DEFERRED_PREPASS +#ifdef PREPASS_PIPELINE // write the gbuffer out.deferred = deferred_gbuffer_from_pbr_input(pbr_input); out.deferred_lighting_pass_id = pbr_bindings::material.deferred_lighting_pass_id; @@ -190,7 +190,7 @@ fn fragment( out.motion_vector = calculate_motion_vector(in.world_position, in.previous_world_position); #endif // MOTION_VECTOR_PREPASS -#else // DEFERRED_PREPASS +#else // PREPASS_PIPELINE // in forward mode, we calculate the lit color immediately, and then apply some post-lighting effects here. // in deferred mode the lit color and these effects will be calculated in the deferred lighting shader @@ -222,7 +222,7 @@ fn fragment( // write the final pixel color out.color = output_color; -#endif //DEFERRED_PREPASS +#endif // PREPASS_PIPELINE return out; }