diff --git a/crates/bevy_core_pipeline/Cargo.toml b/crates/bevy_core_pipeline/Cargo.toml index 53f8230e4d0f6..180ce89199cd0 100644 --- a/crates/bevy_core_pipeline/Cargo.toml +++ b/crates/bevy_core_pipeline/Cargo.toml @@ -31,6 +31,8 @@ bevy_transform = { path = "../bevy_transform", version = "0.12.0-dev" } bevy_math = { path = "../bevy_math", version = "0.12.0-dev" } bevy_utils = { path = "../bevy_utils", version = "0.12.0-dev" } +arbitrary-int = "1.2.6" +bitbybit = "1.2.2" serde = { version = "1", features = ["derive"] } bitflags = "2.3" radsort = "0.1" diff --git a/crates/bevy_core_pipeline/src/tonemapping/mod.rs b/crates/bevy_core_pipeline/src/tonemapping/mod.rs index bb4dec7f3bc49..7dbebc7d37516 100644 --- a/crates/bevy_core_pipeline/src/tonemapping/mod.rs +++ b/crates/bevy_core_pipeline/src/tonemapping/mod.rs @@ -114,37 +114,36 @@ pub struct TonemappingPipeline { } /// Optionally enables a tonemapping shader that attempts to map linear input stimulus into a perceptually uniform image for a given [`Camera`] entity. -#[derive( - Component, Debug, Hash, Clone, Copy, Reflect, Default, ExtractComponent, PartialEq, Eq, -)] +#[derive(Component, Debug, Hash, Reflect, Default, ExtractComponent, PartialEq, Eq)] #[extract_component_filter(With)] #[reflect(Component)] +#[bitbybit::bitenum(u3, exhaustive: true)] pub enum Tonemapping { /// Bypass tonemapping. - None, + None = 0, /// Suffers from lots hue shifting, brights don't desaturate naturally. /// Bright primaries and secondaries don't desaturate at all. - Reinhard, + Reinhard = 1, /// Suffers from hue shifting. Brights don't desaturate much at all across the spectrum. - ReinhardLuminance, + ReinhardLuminance = 2, /// Same base implementation that Godot 4.0 uses for Tonemap ACES. /// /// Not neutral, has a very specific aesthetic, intentional and dramatic hue shifting. /// Bright greens and reds turn orange. Bright blues turn magenta. /// Significantly increased contrast. Brights desaturate across the spectrum. - AcesFitted, + AcesFitted = 3, /// By Troy Sobotka /// /// Very neutral. Image is somewhat desaturated when compared to other tonemappers. /// Little to no hue shifting. Subtle [Abney shifting](https://en.wikipedia.org/wiki/Abney_effect). /// NOTE: Requires the `tonemapping_luts` cargo feature. - AgX, + AgX = 4, /// By Tomasz Stachowiak /// Has little hue shifting in the darks and mids, but lots in the brights. Brights desaturate across the spectrum. /// Is sort of between Reinhard and ReinhardLuminance. Conceptually similar to reinhard-jodie. /// Designed as a compromise if you want e.g. decent skin tones in low light, but can't afford to re-do your /// VFX to look good without hue shifting. - SomewhatBoringDisplayTransform, + SomewhatBoringDisplayTransform = 5, /// Current Bevy default. /// By Tomasz Stachowiak /// @@ -158,17 +157,42 @@ pub enum Tonemapping { /// To avoid posterization, selective desaturation is employed, with care to avoid the [Abney effect](https://en.wikipedia.org/wiki/Abney_effect). /// NOTE: Requires the `tonemapping_luts` cargo feature. #[default] - TonyMcMapface, + TonyMcMapface = 6, /// Default Filmic Display Transform from blender. /// Somewhat neutral. Suffers from hue shifting. Brights desaturate across the spectrum. /// NOTE: Requires the `tonemapping_luts` cargo feature. - BlenderFilmic, + BlenderFilmic = 7, } impl Tonemapping { pub fn is_enabled(&self) -> bool { *self != Tonemapping::None } + pub const fn define(self) -> &'static str { + use Tonemapping::SomewhatBoringDisplayTransform; + + match self { + Self::None => "TONEMAP_METHOD_NONE", + Self::Reinhard => "TONEMAP_METHOD_REINHARD", + Self::ReinhardLuminance => "TONEMAP_METHOD_REINHARD_LUMINANCE", + Self::AcesFitted => "TONEMAP_METHOD_ACES_FITTED", + Self::AgX => "TONEMAP_METHOD_AGX", + SomewhatBoringDisplayTransform => "TONEMAP_METHOD_SOMEWHAT_BORING_DISPLAY_TRANSFORM", + Self::TonyMcMapface => "TONEMAP_METHOD_TONY_MC_MAPFACE", + Self::BlenderFilmic => "TONEMAP_METHOD_BLENDER_FILMIC", + } + } + + pub fn log_feature_error(self) { + #[cfg(not(feature = "tonemapping_luts"))] + if matches!(self, Self::AgX | Self::TonyMcMapface | Self::BlenderFilmic) { + bevy_log::error!( + "{self:?} tonemapping requires the `tonemapping_luts` feature. Either enable the \ + `tonemapping_luts` feature for bevy in `Cargo.toml` (recommended), or use a \ + different `Tonemapping` method in your `Camera2dBundle`/`Camera3dBundle`." + ); + } + } } #[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)] @@ -185,45 +209,9 @@ impl SpecializedRenderPipeline for TonemappingPipeline { if let DebandDither::Enabled = key.deband_dither { shader_defs.push("DEBAND_DITHER".into()); } + key.tonemapping.log_feature_error(); + shader_defs.push(key.tonemapping.define().into()); - match key.tonemapping { - Tonemapping::None => shader_defs.push("TONEMAP_METHOD_NONE".into()), - Tonemapping::Reinhard => shader_defs.push("TONEMAP_METHOD_REINHARD".into()), - Tonemapping::ReinhardLuminance => { - shader_defs.push("TONEMAP_METHOD_REINHARD_LUMINANCE".into()); - } - Tonemapping::AcesFitted => shader_defs.push("TONEMAP_METHOD_ACES_FITTED".into()), - Tonemapping::AgX => { - #[cfg(not(feature = "tonemapping_luts"))] - bevy_log::error!( - "AgX tonemapping requires the `tonemapping_luts` feature. - Either enable the `tonemapping_luts` feature for bevy in `Cargo.toml` (recommended), - or use a different `Tonemapping` method in your `Camera2dBundle`/`Camera3dBundle`." - ); - shader_defs.push("TONEMAP_METHOD_AGX".into()); - } - Tonemapping::SomewhatBoringDisplayTransform => { - shader_defs.push("TONEMAP_METHOD_SOMEWHAT_BORING_DISPLAY_TRANSFORM".into()); - } - Tonemapping::TonyMcMapface => { - #[cfg(not(feature = "tonemapping_luts"))] - bevy_log::error!( - "TonyMcMapFace tonemapping requires the `tonemapping_luts` feature. - Either enable the `tonemapping_luts` feature for bevy in `Cargo.toml` (recommended), - or use a different `Tonemapping` method in your `Camera2dBundle`/`Camera3dBundle`." - ); - shader_defs.push("TONEMAP_METHOD_TONY_MC_MAPFACE".into()); - } - Tonemapping::BlenderFilmic => { - #[cfg(not(feature = "tonemapping_luts"))] - bevy_log::error!( - "BlenderFilmic tonemapping requires the `tonemapping_luts` feature. - Either enable the `tonemapping_luts` feature for bevy in `Cargo.toml` (recommended), - or use a different `Tonemapping` method in your `Camera2dBundle`/`Camera3dBundle`." - ); - shader_defs.push("TONEMAP_METHOD_BLENDER_FILMIC".into()); - } - } RenderPipelineDescriptor { label: Some("tonemapping pipeline".into()), layout: vec![self.texture_bind_group.clone()], diff --git a/crates/bevy_gizmos/src/pipeline_3d.rs b/crates/bevy_gizmos/src/pipeline_3d.rs index 416d7e9a9992f..184b13d548452 100644 --- a/crates/bevy_gizmos/src/pipeline_3d.rs +++ b/crates/bevy_gizmos/src/pipeline_3d.rs @@ -87,7 +87,7 @@ impl SpecializedRenderPipeline for LineGizmoPipeline { shader_defs.push("PERSPECTIVE".into()); } - let format = if key.mesh_key.contains(MeshPipelineKey::HDR) { + let format = if key.mesh_key.hdr() { ViewTarget::TEXTURE_FORMAT_HDR } else { TextureFormat::bevy_default() @@ -168,8 +168,7 @@ fn queue_line_gizmos_3d( continue; } - let mesh_key = MeshPipelineKey::from_msaa_samples(msaa.samples()) - | MeshPipelineKey::from_hdr(view.hdr); + let mesh_key = MeshPipelineKey::DEFAULT.with_msaa(*msaa).with_hdr(view.hdr); for (entity, handle) in &line_gizmos { let Some(line_gizmo) = line_gizmo_assets.get(handle) else { diff --git a/crates/bevy_pbr/Cargo.toml b/crates/bevy_pbr/Cargo.toml index dc2cdeb1235d2..5f7807ec4954e 100644 --- a/crates/bevy_pbr/Cargo.toml +++ b/crates/bevy_pbr/Cargo.toml @@ -26,6 +26,8 @@ bevy_window = { path = "../bevy_window", version = "0.12.0-dev" } bevy_derive = { path = "../bevy_derive", version = "0.12.0-dev" } # other +arbitrary-int = "1.2.6" +bitbybit = "1.2.2" bitflags = "2.3" fixedbitset = "0.4" # direct dependency required for derive macro diff --git a/crates/bevy_pbr/src/alpha.rs b/crates/bevy_pbr/src/alpha.rs index 2dfad77ac9a60..34fc674c8bea0 100644 --- a/crates/bevy_pbr/src/alpha.rs +++ b/crates/bevy_pbr/src/alpha.rs @@ -1,5 +1,7 @@ use bevy_reflect::std_traits::ReflectDefault; use bevy_reflect::Reflect; +use bevy_render::render_resource::BlendState; +use bitbybit::bitenum; // TODO: add discussion about performance. /// Sets how a material's base color alpha channel is used for transparency. @@ -47,5 +49,59 @@ pub enum AlphaMode { /// Useful for effects like stained glass, window tint film and some colored liquids. Multiply, } +impl AlphaMode { + pub fn may_discard(self) -> bool { + matches!(self, Self::Mask(_)) + } +} + +#[bitenum(u2, exhaustive: true)] +#[derive(PartialEq)] +pub enum BlendMode { + Opaque = 0, + PremultipliedAlpha = 1, + Multiply = 2, + Alpha = 3, +} +impl From for BlendMode { + fn from(value: AlphaMode) -> Self { + match value { + AlphaMode::Premultiplied | AlphaMode::Add => BlendMode::PremultipliedAlpha, + AlphaMode::Blend => BlendMode::Alpha, + AlphaMode::Multiply => BlendMode::Multiply, + _ => BlendMode::Opaque, + } + } +} +impl BlendMode { + pub fn is_opaque(self) -> bool { + matches!(self, Self::Opaque) + } + pub fn state(self) -> Option { + use bevy_render::render_resource::*; + match self { + BlendMode::PremultipliedAlpha => Some(BlendState::PREMULTIPLIED_ALPHA_BLENDING), + BlendMode::Multiply => Some(BlendState { + color: BlendComponent { + src_factor: BlendFactor::Dst, + dst_factor: BlendFactor::OneMinusSrcAlpha, + operation: BlendOperation::Add, + }, + alpha: BlendComponent::OVER, + }), + BlendMode::Alpha => Some(BlendState::ALPHA_BLENDING), + BlendMode::Opaque => None, + } + } + pub const fn defines(self) -> Option<[&'static str; 2]> { + match self { + BlendMode::Alpha | BlendMode::Opaque => None, + BlendMode::PremultipliedAlpha => { + Some(["PREMULTIPLY_ALPHA", "BLEND_PREMULTIPLIED_ALPHA"]) + } + BlendMode::Multiply => Some(["PREMULTIPLY_ALPHA", "BLEND_MULTIPLY"]), + } + } +} impl Eq for AlphaMode {} diff --git a/crates/bevy_pbr/src/deferred/mod.rs b/crates/bevy_pbr/src/deferred/mod.rs index f679f638d42ac..57bb1fe92ec8d 100644 --- a/crates/bevy_pbr/src/deferred/mod.rs +++ b/crates/bevy_pbr/src/deferred/mod.rs @@ -259,66 +259,41 @@ impl SpecializedRenderPipeline for DeferredLightingLayout { #[cfg(all(feature = "webgl", target_arch = "wasm32"))] shader_defs.push("WEBGL2".into()); - if key.contains(MeshPipelineKey::TONEMAP_IN_SHADER) { + if key.tonemap_in_shader() { shader_defs.push("TONEMAP_IN_SHADER".into()); - - let method = key.intersection(MeshPipelineKey::TONEMAP_METHOD_RESERVED_BITS); - - if method == MeshPipelineKey::TONEMAP_METHOD_NONE { - shader_defs.push("TONEMAP_METHOD_NONE".into()); - } else if method == MeshPipelineKey::TONEMAP_METHOD_REINHARD { - shader_defs.push("TONEMAP_METHOD_REINHARD".into()); - } else if method == MeshPipelineKey::TONEMAP_METHOD_REINHARD_LUMINANCE { - shader_defs.push("TONEMAP_METHOD_REINHARD_LUMINANCE".into()); - } else if method == MeshPipelineKey::TONEMAP_METHOD_ACES_FITTED { - shader_defs.push("TONEMAP_METHOD_ACES_FITTED ".into()); - } else if method == MeshPipelineKey::TONEMAP_METHOD_AGX { - shader_defs.push("TONEMAP_METHOD_AGX".into()); - } else if method == MeshPipelineKey::TONEMAP_METHOD_SOMEWHAT_BORING_DISPLAY_TRANSFORM { - shader_defs.push("TONEMAP_METHOD_SOMEWHAT_BORING_DISPLAY_TRANSFORM".into()); - } else if method == MeshPipelineKey::TONEMAP_METHOD_BLENDER_FILMIC { - shader_defs.push("TONEMAP_METHOD_BLENDER_FILMIC".into()); - } else if method == MeshPipelineKey::TONEMAP_METHOD_TONY_MC_MAPFACE { - shader_defs.push("TONEMAP_METHOD_TONY_MC_MAPFACE".into()); - } + shader_defs.push(key.tonemap_method().define().into()); // Debanding is tied to tonemapping in the shader, cannot run without it. - if key.contains(MeshPipelineKey::DEBAND_DITHER) { + if key.deband_dither() { shader_defs.push("DEBAND_DITHER".into()); } } - if key.contains(MeshPipelineKey::SCREEN_SPACE_AMBIENT_OCCLUSION) { + if key.screen_space_ambient_occlusion() { shader_defs.push("SCREEN_SPACE_AMBIENT_OCCLUSION".into()); } - if key.contains(MeshPipelineKey::ENVIRONMENT_MAP) { + if key.environment_map() { shader_defs.push("ENVIRONMENT_MAP".into()); } - if key.contains(MeshPipelineKey::NORMAL_PREPASS) { + if key.normal_prepass() { shader_defs.push("NORMAL_PREPASS".into()); } - if key.contains(MeshPipelineKey::DEPTH_PREPASS) { + if key.depth_prepass() { shader_defs.push("DEPTH_PREPASS".into()); } - if key.contains(MeshPipelineKey::MOTION_VECTOR_PREPASS) { + if key.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 { - shader_defs.push("SHADOW_FILTER_METHOD_HARDWARE_2X2".into()); - } else if shadow_filter_method == MeshPipelineKey::SHADOW_FILTER_METHOD_CASTANO_13 { - shader_defs.push("SHADOW_FILTER_METHOD_CASTANO_13".into()); - } else if shadow_filter_method == MeshPipelineKey::SHADOW_FILTER_METHOD_JIMENEZ_14 { - shader_defs.push("SHADOW_FILTER_METHOD_JIMENEZ_14".into()); + if let Ok(filter_method) = key.shadow_filter_method() { + shader_defs.push(filter_method.define().into()); } #[cfg(all(feature = "webgl", target_arch = "wasm32"))] @@ -341,7 +316,7 @@ impl SpecializedRenderPipeline for DeferredLightingLayout { shader_defs, entry_point: "fragment".into(), targets: vec![Some(ColorTargetState { - format: if key.contains(MeshPipelineKey::HDR) { + format: if key.hdr() { ViewTarget::TEXTURE_FORMAT_HDR } else { TextureFormat::bevy_default() @@ -442,68 +417,28 @@ pub fn prepare_deferred_lighting_pipelines( (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; + let environment_map_loaded = environment_map.is_some_and(|e| e.is_loaded(&images)); + let filtering_method = shadow_filter_method.copied().unwrap_or_default(); + + let mut view_key = MeshPipelineKey::DEFAULT + .with_hdr(view.hdr) + .with_normal_prepass(normal_prepass) + .with_depth_prepass(depth_prepass) + .with_motion_vector_prepass(motion_vector_prepass) + .with_screen_space_ambient_occlusion(ssao.is_some()) + .with_environment_map(environment_map_loaded) + .with_shadow_filter_method(filtering_method) + // Always true, since we're in the deferred lighting pipeline + .with_deferred_prepass(true); if !view.hdr { if let Some(tonemapping) = tonemapping { - view_key |= MeshPipelineKey::TONEMAP_IN_SHADER; - view_key |= match tonemapping { - Tonemapping::None => MeshPipelineKey::TONEMAP_METHOD_NONE, - Tonemapping::Reinhard => MeshPipelineKey::TONEMAP_METHOD_REINHARD, - Tonemapping::ReinhardLuminance => { - MeshPipelineKey::TONEMAP_METHOD_REINHARD_LUMINANCE - } - Tonemapping::AcesFitted => MeshPipelineKey::TONEMAP_METHOD_ACES_FITTED, - Tonemapping::AgX => MeshPipelineKey::TONEMAP_METHOD_AGX, - Tonemapping::SomewhatBoringDisplayTransform => { - MeshPipelineKey::TONEMAP_METHOD_SOMEWHAT_BORING_DISPLAY_TRANSFORM - } - Tonemapping::TonyMcMapface => MeshPipelineKey::TONEMAP_METHOD_TONY_MC_MAPFACE, - Tonemapping::BlenderFilmic => MeshPipelineKey::TONEMAP_METHOD_BLENDER_FILMIC, - }; - } - if let Some(DebandDither::Enabled) = dither { - view_key |= MeshPipelineKey::DEBAND_DITHER; - } - } - - if ssao.is_some() { - view_key |= MeshPipelineKey::SCREEN_SPACE_AMBIENT_OCCLUSION; - } - - let environment_map_loaded = match environment_map { - Some(environment_map) => environment_map.is_loaded(&images), - None => false, - }; - if environment_map_loaded { - view_key |= MeshPipelineKey::ENVIRONMENT_MAP; - } - - match shadow_filter_method.unwrap_or(&ShadowFilteringMethod::default()) { - ShadowFilteringMethod::Hardware2x2 => { - view_key |= MeshPipelineKey::SHADOW_FILTER_METHOD_HARDWARE_2X2; - } - ShadowFilteringMethod::Castano13 => { - view_key |= MeshPipelineKey::SHADOW_FILTER_METHOD_CASTANO_13; - } - ShadowFilteringMethod::Jimenez14 => { - view_key |= MeshPipelineKey::SHADOW_FILTER_METHOD_JIMENEZ_14; + view_key = view_key + .with_tonemap_in_shader(true) + .with_tonemap_method(*tonemapping); } + let deband_dither = dither.is_some_and(|m| matches!(m, DebandDither::Enabled)); + view_key = view_key.with_deband_dither(deband_dither); } let pipeline_id = diff --git a/crates/bevy_pbr/src/light.rs b/crates/bevy_pbr/src/light.rs index 9850d21f8c80e..465f984afdde6 100644 --- a/crates/bevy_pbr/src/light.rs +++ b/crates/bevy_pbr/src/light.rs @@ -609,19 +609,20 @@ pub struct NotShadowReceiver; /// [Percentage Closer Filtering](https://developer.nvidia.com/gpugems/gpugems/part-ii-lighting-and-shadows/chapter-11-shadow-map-antialiasing). /// /// Currently does not affect point lights. -#[derive(Component, ExtractComponent, Reflect, Clone, Copy, PartialEq, Eq, Default)] +#[derive(Component, ExtractComponent, Reflect, PartialEq, Eq, Default)] #[reflect(Component, Default)] +#[bitbybit::bitenum(u2, exhaustive: false)] pub enum ShadowFilteringMethod { /// Hardware 2x2. /// /// Fast but poor quality. - Hardware2x2, + Hardware2x2 = 0, /// Method by Ignacio Castaño for The Witness using 9 samples and smart /// filtering to achieve the same as a regular 5x5 filter kernel. /// /// Good quality, good performance. #[default] - Castano13, + Castano13 = 1, /// Method by Jorge Jimenez for Call of Duty: Advanced Warfare using 8 /// samples in spiral pattern, randomly-rotated by interleaved gradient /// noise with spatial variation. @@ -629,7 +630,16 @@ pub enum ShadowFilteringMethod { /// Good quality when used with /// [`TemporalAntiAliasSettings`](bevy_core_pipeline::experimental::taa::TemporalAntiAliasSettings) /// and good performance. - Jimenez14, + Jimenez14 = 2, +} +impl ShadowFilteringMethod { + pub const fn define(self) -> &'static str { + match self { + Self::Hardware2x2 => "SHADOW_FILTER_METHOD_HARDWARE_2X2", + Self::Castano13 => "SHADOW_FILTER_METHOD_CASTANO_13", + Self::Jimenez14 => "SHADOW_FILTER_METHOD_JIMENEZ_14", + } + } } #[derive(Debug, Hash, PartialEq, Eq, Clone, SystemSet)] diff --git a/crates/bevy_pbr/src/material.rs b/crates/bevy_pbr/src/material.rs index 5a119c42b5d2d..e7623042329e9 100644 --- a/crates/bevy_pbr/src/material.rs +++ b/crates/bevy_pbr/src/material.rs @@ -391,33 +391,6 @@ impl RenderCommand

for SetMaterial pub type RenderMaterialInstances = ExtractedInstances>; -const fn alpha_mode_pipeline_key(alpha_mode: AlphaMode) -> MeshPipelineKey { - match alpha_mode { - // Premultiplied and Add share the same pipeline key - // They're made distinct in the PBR shader, via `premultiply_alpha()` - AlphaMode::Premultiplied | AlphaMode::Add => MeshPipelineKey::BLEND_PREMULTIPLIED_ALPHA, - AlphaMode::Blend => MeshPipelineKey::BLEND_ALPHA, - AlphaMode::Multiply => MeshPipelineKey::BLEND_MULTIPLY, - AlphaMode::Mask(_) => MeshPipelineKey::MAY_DISCARD, - _ => MeshPipelineKey::NONE, - } -} - -const fn tonemapping_pipeline_key(tonemapping: Tonemapping) -> MeshPipelineKey { - match tonemapping { - Tonemapping::None => MeshPipelineKey::TONEMAP_METHOD_NONE, - Tonemapping::Reinhard => MeshPipelineKey::TONEMAP_METHOD_REINHARD, - Tonemapping::ReinhardLuminance => MeshPipelineKey::TONEMAP_METHOD_REINHARD_LUMINANCE, - Tonemapping::AcesFitted => MeshPipelineKey::TONEMAP_METHOD_ACES_FITTED, - Tonemapping::AgX => MeshPipelineKey::TONEMAP_METHOD_AGX, - Tonemapping::SomewhatBoringDisplayTransform => { - MeshPipelineKey::TONEMAP_METHOD_SOMEWHAT_BORING_DISPLAY_TRANSFORM - } - Tonemapping::TonyMcMapface => MeshPipelineKey::TONEMAP_METHOD_TONY_MC_MAPFACE, - Tonemapping::BlenderFilmic => MeshPipelineKey::TONEMAP_METHOD_BLENDER_FILMIC, - } -} - #[allow(clippy::too_many_arguments)] pub fn queue_material_meshes( opaque_draw_functions: Res>, @@ -475,65 +448,37 @@ pub fn queue_material_meshes( let draw_alpha_mask_pbr = alpha_mask_draw_functions.read().id::>(); let draw_transparent_pbr = transparent_draw_functions.read().id::>(); - let mut view_key = MeshPipelineKey::from_msaa_samples(msaa.samples()) - | 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; - } - - if deferred_prepass { - view_key |= MeshPipelineKey::DEFERRED_PREPASS; - } - - if taa_settings.is_some() { - view_key |= MeshPipelineKey::TAA; - } let environment_map_loaded = environment_map.is_some_and(|map| map.is_loaded(&images)); + let filtering_method = shadow_filter_method.copied().unwrap_or_default(); - if environment_map_loaded { - view_key |= MeshPipelineKey::ENVIRONMENT_MAP; - } - - if let Some(projection) = projection { - view_key |= match projection { - Projection::Perspective(_) => MeshPipelineKey::VIEW_PROJECTION_PERSPECTIVE, - Projection::Orthographic(_) => MeshPipelineKey::VIEW_PROJECTION_ORTHOGRAPHIC, - }; - } - - match shadow_filter_method.unwrap_or(&ShadowFilteringMethod::default()) { - ShadowFilteringMethod::Hardware2x2 => { - view_key |= MeshPipelineKey::SHADOW_FILTER_METHOD_HARDWARE_2X2; - } - ShadowFilteringMethod::Castano13 => { - view_key |= MeshPipelineKey::SHADOW_FILTER_METHOD_CASTANO_13; - } - ShadowFilteringMethod::Jimenez14 => { - view_key |= MeshPipelineKey::SHADOW_FILTER_METHOD_JIMENEZ_14; - } - } + let view_projection = match projection { + Some(Projection::Perspective(_)) => ViewProjection::Perspective, + Some(Projection::Orthographic(_)) => ViewProjection::Orthographic, + None => ViewProjection::Nonstandard, + }; + let mut view_key = MeshPipelineKey::DEFAULT + .with_msaa(*msaa) + .with_hdr(view.hdr) + .with_normal_prepass(normal_prepass) + .with_depth_prepass(depth_prepass) + .with_motion_vector_prepass(motion_vector_prepass) + .with_deferred_prepass(deferred_prepass) + .with_taa(taa_settings.is_some()) + .with_environment_map(environment_map_loaded) + .with_shadow_filter_method(filtering_method) + .with_screen_space_ambient_occlusion(ssao.is_some()) + .with_view_projection(view_projection); if !view.hdr { if let Some(tonemapping) = tonemapping { - view_key |= MeshPipelineKey::TONEMAP_IN_SHADER; - view_key |= tonemapping_pipeline_key(*tonemapping); + view_key = view_key + .with_tonemap_in_shader(true) + .with_tonemap_method(*tonemapping); } - if let Some(DebandDither::Enabled) = dither { - view_key |= MeshPipelineKey::DEBAND_DITHER; - } - } - if ssao.is_some() { - view_key |= MeshPipelineKey::SCREEN_SPACE_AMBIENT_OCCLUSION; + let deband_dither = dither.is_some_and(|m| matches!(m, DebandDither::Enabled)); + view_key = view_key.with_deband_dither(deband_dither); } + let rangefinder = view.rangefinder3d(); for visible_entity in &visible_entities.entities { let Some(material_asset_id) = render_material_instances.get(visible_entity) else { @@ -555,14 +500,12 @@ pub fn queue_material_meshes( OpaqueRendererMethod::Auto => unreachable!(), }; - let mut mesh_key = view_key; - - mesh_key |= MeshPipelineKey::from_primitive_topology(mesh.primitive_topology); - - if mesh.morph_targets.is_some() { - mesh_key |= MeshPipelineKey::MORPH_TARGETS; - } - mesh_key |= alpha_mode_pipeline_key(material.properties.alpha_mode); + let alpha_mode = material.properties.alpha_mode; + let mesh_key = view_key + .with_primitive_topology(mesh.primitive_topology.into()) + .with_morph_targets(mesh.morph_targets.is_some()) + .with_may_discard(alpha_mode.may_discard()) + .with_blend(alpha_mode.into()); let pipeline_id = pipelines.specialize( &pipeline_cache, diff --git a/crates/bevy_pbr/src/prepass/mod.rs b/crates/bevy_pbr/src/prepass/mod.rs index c519f7c32ab58..1f5530ca4c6d5 100644 --- a/crates/bevy_pbr/src/prepass/mod.rs +++ b/crates/bevy_pbr/src/prepass/mod.rs @@ -342,14 +342,13 @@ where key: Self::Key, layout: &MeshVertexBufferLayout, ) -> Result { - let mut bind_group_layouts = vec![if key - .mesh_key - .contains(MeshPipelineKey::MOTION_VECTOR_PREPASS) - { - self.view_layout_motion_vectors.clone() + let mesh_key = key.mesh_key; + + let mut bind_group_layouts = if mesh_key.motion_vector_prepass() { + vec![self.view_layout_motion_vectors.clone()] } else { - self.view_layout_no_motion_vectors.clone() - }]; + vec![self.view_layout_no_motion_vectors.clone()] + }; let mut shader_defs = Vec::new(); let mut vertex_attributes = Vec::new(); @@ -367,21 +366,19 @@ where shader_defs.push("VERTEX_OUTPUT_INSTANCE_INDEX".into()); - if key.mesh_key.contains(MeshPipelineKey::DEPTH_PREPASS) { + if mesh_key.depth_prepass() { shader_defs.push("DEPTH_PREPASS".into()); } - if key.mesh_key.contains(MeshPipelineKey::MAY_DISCARD) { + if mesh_key.may_discard() { shader_defs.push("MAY_DISCARD".into()); } - let blend_key = key - .mesh_key - .intersection(MeshPipelineKey::BLEND_RESERVED_BITS); - if blend_key == MeshPipelineKey::BLEND_PREMULTIPLIED_ALPHA { + let blend_mode = mesh_key.blend(); + if blend_mode == BlendMode::PremultipliedAlpha { shader_defs.push("BLEND_PREMULTIPLIED_ALPHA".into()); } - if blend_key == MeshPipelineKey::BLEND_ALPHA { + if blend_mode == BlendMode::Alpha { shader_defs.push("BLEND_ALPHA".into()); } @@ -390,7 +387,7 @@ where vertex_attributes.push(Mesh::ATTRIBUTE_POSITION.at_shader_location(0)); } - if key.mesh_key.contains(MeshPipelineKey::DEPTH_CLAMP_ORTHO) { + if mesh_key.depth_clamp_ortho() { shader_defs.push("DEPTH_CLAMP_ORTHO".into()); // PERF: This line forces the "prepass fragment shader" to always run in // common scenarios like "directional light calculation". Doing so resolves @@ -406,14 +403,11 @@ where vertex_attributes.push(Mesh::ATTRIBUTE_UV_0.at_shader_location(1)); } - if key.mesh_key.contains(MeshPipelineKey::NORMAL_PREPASS) { + if mesh_key.normal_prepass() { shader_defs.push("NORMAL_PREPASS".into()); } - if key - .mesh_key - .intersects(MeshPipelineKey::NORMAL_PREPASS | MeshPipelineKey::DEFERRED_PREPASS) - { + if mesh_key.normal_prepass() | mesh_key.deferred_prepass() { vertex_attributes.push(Mesh::ATTRIBUTE_NORMAL.at_shader_location(2)); shader_defs.push("NORMAL_PREPASS_OR_DEFERRED_PREPASS".into()); if layout.contains(Mesh::ATTRIBUTE_TANGENT) { @@ -422,14 +416,11 @@ where } } - if key - .mesh_key - .intersects(MeshPipelineKey::MOTION_VECTOR_PREPASS | MeshPipelineKey::DEFERRED_PREPASS) - { + if mesh_key.motion_vector_prepass() | mesh_key.depth_prepass() { shader_defs.push("MOTION_VECTOR_PREPASS_OR_DEFERRED_PREPASS".into()); } - if key.mesh_key.contains(MeshPipelineKey::DEFERRED_PREPASS) { + if mesh_key.deferred_prepass() { shader_defs.push("DEFERRED_PREPASS".into()); if layout.contains(Mesh::ATTRIBUTE_COLOR) { @@ -438,18 +429,14 @@ where } } - if key - .mesh_key - .contains(MeshPipelineKey::MOTION_VECTOR_PREPASS) - { + if mesh_key.motion_vector_prepass() { shader_defs.push("MOTION_VECTOR_PREPASS".into()); } - if key.mesh_key.intersects( - MeshPipelineKey::NORMAL_PREPASS - | MeshPipelineKey::MOTION_VECTOR_PREPASS - | MeshPipelineKey::DEFERRED_PREPASS, - ) { + if mesh_key.normal_prepass() + | mesh_key.motion_vector_prepass() + | mesh_key.deferred_prepass() + { shader_defs.push("PREPASS_FRAGMENT".into()); } @@ -457,7 +444,7 @@ where &self.mesh_layouts, layout, 4, - &key.mesh_key, + mesh_key, &mut shader_defs, &mut vertex_attributes, ); @@ -467,37 +454,31 @@ where // Setup prepass fragment targets - normals in slot 0 (or None if not needed), motion vectors in slot 1 let mut targets = vec![ - key.mesh_key - .contains(MeshPipelineKey::NORMAL_PREPASS) - .then_some(ColorTargetState { - format: NORMAL_PREPASS_FORMAT, - // BlendState::REPLACE is not needed here, and None will be potentially much faster in some cases. - blend: None, - write_mask: ColorWrites::ALL, - }), - key.mesh_key - .contains(MeshPipelineKey::MOTION_VECTOR_PREPASS) + mesh_key.normal_prepass().then_some(ColorTargetState { + format: NORMAL_PREPASS_FORMAT, + // BlendState::REPLACE is not needed here, and None will be potentially much faster in some cases. + blend: None, + write_mask: ColorWrites::ALL, + }), + mesh_key + .motion_vector_prepass() .then_some(ColorTargetState { format: MOTION_VECTOR_PREPASS_FORMAT, // BlendState::REPLACE is not needed here, and None will be potentially much faster in some cases. blend: None, write_mask: ColorWrites::ALL, }), - key.mesh_key - .contains(MeshPipelineKey::DEFERRED_PREPASS) - .then_some(ColorTargetState { - format: DEFERRED_PREPASS_FORMAT, - // BlendState::REPLACE is not needed here, and None will be potentially much faster in some cases. - blend: None, - write_mask: ColorWrites::ALL, - }), - key.mesh_key - .contains(MeshPipelineKey::DEFERRED_PREPASS) - .then_some(ColorTargetState { - format: DEFERRED_LIGHTING_PASS_ID_FORMAT, - blend: None, - write_mask: ColorWrites::ALL, - }), + mesh_key.deferred_prepass().then_some(ColorTargetState { + format: DEFERRED_PREPASS_FORMAT, + // BlendState::REPLACE is not needed here, and None will be potentially much faster in some cases. + blend: None, + write_mask: ColorWrites::ALL, + }), + mesh_key.deferred_prepass().then_some(ColorTargetState { + format: DEFERRED_LIGHTING_PASS_ID_FORMAT, + blend: None, + write_mask: ColorWrites::ALL, + }), ]; if targets.iter().all(Option::is_none) { @@ -510,13 +491,12 @@ where // is enabled or the material uses alpha cutoff values and doesn't rely on the standard // prepass shader or we are clamping the orthographic depth. let fragment_required = !targets.is_empty() - || key.mesh_key.contains(MeshPipelineKey::DEPTH_CLAMP_ORTHO) - || (key.mesh_key.contains(MeshPipelineKey::MAY_DISCARD) - && self.prepass_material_fragment_shader.is_some()); + || mesh_key.depth_clamp_ortho() + || (mesh_key.may_discard() && self.prepass_material_fragment_shader.is_some()); let fragment = fragment_required.then(|| { // Use the fragment shader from the material - let frag_shader_handle = if key.mesh_key.contains(MeshPipelineKey::DEFERRED_PREPASS) { + let frag_shader_handle = if mesh_key.deferred_prepass() { match self.deferred_material_fragment_shader.clone() { Some(frag_shader_handle) => frag_shader_handle, _ => PREPASS_SHADER_HANDLE, @@ -537,7 +517,7 @@ where }); // Use the vertex shader from the material if present - let vert_shader_handle = if key.mesh_key.contains(MeshPipelineKey::DEFERRED_PREPASS) { + let vert_shader_handle = if mesh_key.deferred_prepass() { if let Some(handle) = &self.deferred_material_vertex_shader { handle.clone() } else { @@ -567,7 +547,7 @@ where fragment, layout: bind_group_layouts, primitive: PrimitiveState { - topology: key.mesh_key.primitive_topology(), + topology: mesh_key.primitive_topology().unwrap_or_default().into(), strip_index_format: None, front_face: FrontFace::Ccw, cull_mode: None, @@ -773,16 +753,11 @@ pub fn queue_prepass_material_meshes( deferred_prepass, ) in &mut views { - let mut view_key = MeshPipelineKey::from_msaa_samples(msaa.samples()); - if depth_prepass.is_some() { - view_key |= MeshPipelineKey::DEPTH_PREPASS; - } - if normal_prepass.is_some() { - view_key |= MeshPipelineKey::NORMAL_PREPASS; - } - if motion_vector_prepass.is_some() { - view_key |= MeshPipelineKey::MOTION_VECTOR_PREPASS; - } + let view_key = MeshPipelineKey::DEFAULT + .with_msaa(*msaa) + .with_depth_prepass(depth_prepass.is_some()) + .with_normal_prepass(normal_prepass.is_some()) + .with_motion_vector_prepass(motion_vector_prepass.is_some()); let mut opaque_phase_deferred = opaque_deferred_phase.as_mut(); let mut alpha_mask_phase_deferred = alpha_mask_deferred_phase.as_mut(); @@ -803,21 +778,14 @@ pub fn queue_prepass_material_meshes( continue; }; - let mut mesh_key = - MeshPipelineKey::from_primitive_topology(mesh.primitive_topology) | view_key; - if mesh.morph_targets.is_some() { - mesh_key |= MeshPipelineKey::MORPH_TARGETS; - } let alpha_mode = material.properties.alpha_mode; match alpha_mode { - AlphaMode::Opaque => {} - AlphaMode::Mask(_) => mesh_key |= MeshPipelineKey::MAY_DISCARD, AlphaMode::Blend | AlphaMode::Premultiplied | AlphaMode::Add | AlphaMode::Multiply => continue, + _ => {} } - let forward = match material.properties.render_method { OpaqueRendererMethod::Forward => true, OpaqueRendererMethod::Deferred => false, @@ -825,10 +793,11 @@ pub fn queue_prepass_material_meshes( }; let deferred = deferred_prepass.is_some() && !forward; - - if deferred { - mesh_key |= MeshPipelineKey::DEFERRED_PREPASS; - } + let mesh_key = view_key + .with_primitive_topology(mesh.primitive_topology.into()) + .with_morph_targets(mesh.morph_targets.is_some()) + .with_may_discard(alpha_mode.may_discard()) + .with_deferred_prepass(deferred); let pipeline_id = pipelines.specialize( &pipeline_cache, diff --git a/crates/bevy_pbr/src/render/light.rs b/crates/bevy_pbr/src/render/light.rs index 62a7367520756..0b746a3dd3444 100644 --- a/crates/bevy_pbr/src/render/light.rs +++ b/crates/bevy_pbr/src/render/light.rs @@ -1605,22 +1605,19 @@ pub fn queue_shadows( continue; }; - let mut mesh_key = - MeshPipelineKey::from_primitive_topology(mesh.primitive_topology) - | MeshPipelineKey::DEPTH_PREPASS; - if mesh.morph_targets.is_some() { - mesh_key |= MeshPipelineKey::MORPH_TARGETS; - } - if is_directional_light { - mesh_key |= MeshPipelineKey::DEPTH_CLAMP_ORTHO; - } - mesh_key |= match material.properties.alpha_mode { + let may_discard = matches!( + material.properties.alpha_mode, AlphaMode::Mask(_) - | AlphaMode::Blend - | AlphaMode::Premultiplied - | AlphaMode::Add => MeshPipelineKey::MAY_DISCARD, - _ => MeshPipelineKey::NONE, - }; + | AlphaMode::Blend + | AlphaMode::Premultiplied + | AlphaMode::Add + ); + let mesh_key = MeshPipelineKey::DEFAULT + .with_primitive_topology(mesh.primitive_topology.into()) + .with_depth_prepass(true) + .with_morph_targets(mesh.morph_targets.is_some()) + .with_depth_clamp_ortho(is_directional_light) + .with_may_discard(may_discard); let pipeline_id = pipelines.specialize( &pipeline_cache, &prepass_pipeline, diff --git a/crates/bevy_pbr/src/render/mesh.rs b/crates/bevy_pbr/src/render/mesh.rs index bc0925a164319..a83796262cab5 100644 --- a/crates/bevy_pbr/src/render/mesh.rs +++ b/crates/bevy_pbr/src/render/mesh.rs @@ -3,6 +3,7 @@ use bevy_asset::{load_internal_asset, AssetId, Handle}; use bevy_core_pipeline::{ core_3d::{AlphaMask3d, Opaque3d, Transparent3d, CORE_3D_DEPTH_FORMAT}, deferred::{AlphaMask3dDeferred, Opaque3dDeferred}, + tonemapping::Tonemapping, }; use bevy_derive::{Deref, DerefMut}; use bevy_ecs::{ @@ -20,13 +21,14 @@ use bevy_render::{ render_asset::RenderAssets, render_phase::{PhaseItem, RenderCommand, RenderCommandResult, TrackedRenderPass}, render_resource::*, - renderer::{RenderDevice, RenderQueue}, + renderer::{BevyPrimitiveTopology, RenderDevice, RenderQueue}, texture::*, - view::{ViewTarget, ViewUniformOffset, ViewVisibility}, + view::{Msaa, ViewTarget, ViewUniformOffset, ViewVisibility}, Extract, ExtractSchedule, Render, RenderApp, RenderSet, }; use bevy_transform::components::GlobalTransform; use bevy_utils::{tracing::error, EntityHashMap, HashMap, Hashed}; +use bitbybit::{bitenum, bitfield}; use std::cell::Cell; use thread_local::ThreadLocal; @@ -468,115 +470,44 @@ impl GetBatchData for MeshPipeline { } } -bitflags::bitflags! { - #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] - #[repr(transparent)] - // NOTE: Apparently quadro drivers support up to 64x MSAA. - /// MSAA uses the highest 3 bits for the MSAA log2(sample count) to support up to 128x MSAA. - pub struct MeshPipelineKey: u32 { - const NONE = 0; - const HDR = (1 << 0); - const TONEMAP_IN_SHADER = (1 << 1); - const DEBAND_DITHER = (1 << 2); - const DEPTH_PREPASS = (1 << 3); - const NORMAL_PREPASS = (1 << 4); - const DEFERRED_PREPASS = (1 << 5); - const MOTION_VECTOR_PREPASS = (1 << 6); - const MAY_DISCARD = (1 << 7); // Guards shader codepaths that may discard, allowing early depth tests in most cases - // See: https://www.khronos.org/opengl/wiki/Early_Fragment_Test - const ENVIRONMENT_MAP = (1 << 8); - const SCREEN_SPACE_AMBIENT_OCCLUSION = (1 << 9); - const DEPTH_CLAMP_ORTHO = (1 << 10); - const TAA = (1 << 11); - const MORPH_TARGETS = (1 << 12); - const BLEND_RESERVED_BITS = Self::BLEND_MASK_BITS << Self::BLEND_SHIFT_BITS; // ← Bitmask reserving bits for the blend state - const BLEND_OPAQUE = (0 << Self::BLEND_SHIFT_BITS); // ← Values are just sequential within the mask, and can range from 0 to 3 - const BLEND_PREMULTIPLIED_ALPHA = (1 << Self::BLEND_SHIFT_BITS); // - const BLEND_MULTIPLY = (2 << Self::BLEND_SHIFT_BITS); // ← We still have room for one more value without adding more bits - const BLEND_ALPHA = (3 << Self::BLEND_SHIFT_BITS); - const MSAA_RESERVED_BITS = Self::MSAA_MASK_BITS << Self::MSAA_SHIFT_BITS; - const PRIMITIVE_TOPOLOGY_RESERVED_BITS = Self::PRIMITIVE_TOPOLOGY_MASK_BITS << Self::PRIMITIVE_TOPOLOGY_SHIFT_BITS; - const TONEMAP_METHOD_RESERVED_BITS = Self::TONEMAP_METHOD_MASK_BITS << Self::TONEMAP_METHOD_SHIFT_BITS; - const TONEMAP_METHOD_NONE = 0 << Self::TONEMAP_METHOD_SHIFT_BITS; - const TONEMAP_METHOD_REINHARD = 1 << Self::TONEMAP_METHOD_SHIFT_BITS; - const TONEMAP_METHOD_REINHARD_LUMINANCE = 2 << Self::TONEMAP_METHOD_SHIFT_BITS; - const TONEMAP_METHOD_ACES_FITTED = 3 << Self::TONEMAP_METHOD_SHIFT_BITS; - const TONEMAP_METHOD_AGX = 4 << Self::TONEMAP_METHOD_SHIFT_BITS; - const TONEMAP_METHOD_SOMEWHAT_BORING_DISPLAY_TRANSFORM = 5 << Self::TONEMAP_METHOD_SHIFT_BITS; - const TONEMAP_METHOD_TONY_MC_MAPFACE = 6 << Self::TONEMAP_METHOD_SHIFT_BITS; - const TONEMAP_METHOD_BLENDER_FILMIC = 7 << Self::TONEMAP_METHOD_SHIFT_BITS; - const SHADOW_FILTER_METHOD_RESERVED_BITS = Self::SHADOW_FILTER_METHOD_MASK_BITS << Self::SHADOW_FILTER_METHOD_SHIFT_BITS; - const SHADOW_FILTER_METHOD_HARDWARE_2X2 = 0 << Self::SHADOW_FILTER_METHOD_SHIFT_BITS; - const SHADOW_FILTER_METHOD_CASTANO_13 = 1 << Self::SHADOW_FILTER_METHOD_SHIFT_BITS; - const SHADOW_FILTER_METHOD_JIMENEZ_14 = 2 << Self::SHADOW_FILTER_METHOD_SHIFT_BITS; - const VIEW_PROJECTION_RESERVED_BITS = Self::VIEW_PROJECTION_MASK_BITS << Self::VIEW_PROJECTION_SHIFT_BITS; - const VIEW_PROJECTION_NONSTANDARD = 0 << Self::VIEW_PROJECTION_SHIFT_BITS; - const VIEW_PROJECTION_PERSPECTIVE = 1 << Self::VIEW_PROJECTION_SHIFT_BITS; - const VIEW_PROJECTION_ORTHOGRAPHIC = 2 << Self::VIEW_PROJECTION_SHIFT_BITS; - const VIEW_PROJECTION_RESERVED = 3 << Self::VIEW_PROJECTION_SHIFT_BITS; - } +#[bitenum(u2, exhaustive: true)] +pub enum ViewProjection { + Nonstandard = 0, + Perspective = 1, + Orthographic = 2, + Reserved = 3, } +#[rustfmt::skip] +#[bitfield(u32, default: 0)] +#[derive(Debug, PartialEq, Eq, Hash)] +pub struct MeshPipelineKey { + #[bit(0, rw)] pub hdr: bool, + #[bit(1, rw)] pub tonemap_in_shader: bool, + #[bit(2, rw)] pub deband_dither: bool, + #[bit(3, rw)] pub depth_prepass: bool, + #[bit(4, rw)] pub normal_prepass: bool, + #[bit(5, rw)] pub deferred_prepass: bool, + #[bit(6, rw)] pub motion_vector_prepass: bool, + /// Guards shader codepaths that may discard, allowing early depth tests in most cases. + /// + /// See: + #[bit(7, rw)] pub may_discard: bool, + #[bit(8, rw)] pub environment_map: bool, + #[bit(9, rw)] pub screen_space_ambient_occlusion: bool, + #[bit(10, rw)] pub depth_clamp_ortho: bool, + #[bit(11, rw)] pub taa: bool, + #[bit(12, rw)] pub morph_targets: bool, + #[bits(13..=14, rw)] pub blend: BlendMode, + #[bits(15..=17, rw)] pub msaa: Msaa, + #[bits(18..=20, rw)] pub primitive_topology: Option, + #[bits(21..=23, rw)] pub tonemap_method: Tonemapping, + #[bits(24..=25, rw)] pub shadow_filter_method: Option, + #[bits(26..=27, rw)] pub view_projection: ViewProjection, +} impl MeshPipelineKey { - const MSAA_MASK_BITS: u32 = 0b111; - const MSAA_SHIFT_BITS: u32 = 32 - Self::MSAA_MASK_BITS.count_ones(); - - const PRIMITIVE_TOPOLOGY_MASK_BITS: u32 = 0b111; - const PRIMITIVE_TOPOLOGY_SHIFT_BITS: u32 = - Self::MSAA_SHIFT_BITS - Self::PRIMITIVE_TOPOLOGY_MASK_BITS.count_ones(); - - const BLEND_MASK_BITS: u32 = 0b11; - const BLEND_SHIFT_BITS: u32 = - Self::PRIMITIVE_TOPOLOGY_SHIFT_BITS - Self::BLEND_MASK_BITS.count_ones(); - - const TONEMAP_METHOD_MASK_BITS: u32 = 0b111; - const TONEMAP_METHOD_SHIFT_BITS: u32 = - Self::BLEND_SHIFT_BITS - Self::TONEMAP_METHOD_MASK_BITS.count_ones(); - - const SHADOW_FILTER_METHOD_MASK_BITS: u32 = 0b11; - const SHADOW_FILTER_METHOD_SHIFT_BITS: u32 = - Self::TONEMAP_METHOD_SHIFT_BITS - Self::SHADOW_FILTER_METHOD_MASK_BITS.count_ones(); - - const VIEW_PROJECTION_MASK_BITS: u32 = 0b11; - const VIEW_PROJECTION_SHIFT_BITS: u32 = - Self::SHADOW_FILTER_METHOD_SHIFT_BITS - Self::VIEW_PROJECTION_MASK_BITS.count_ones(); - - pub fn from_msaa_samples(msaa_samples: u32) -> Self { - let msaa_bits = - (msaa_samples.trailing_zeros() & Self::MSAA_MASK_BITS) << Self::MSAA_SHIFT_BITS; - Self::from_bits_retain(msaa_bits) - } - - pub fn from_hdr(hdr: bool) -> Self { - if hdr { - MeshPipelineKey::HDR - } else { - MeshPipelineKey::NONE - } - } - - pub fn msaa_samples(&self) -> u32 { - 1 << ((self.bits() >> Self::MSAA_SHIFT_BITS) & Self::MSAA_MASK_BITS) - } - - pub fn from_primitive_topology(primitive_topology: PrimitiveTopology) -> Self { - let primitive_topology_bits = ((primitive_topology as u32) - & Self::PRIMITIVE_TOPOLOGY_MASK_BITS) - << Self::PRIMITIVE_TOPOLOGY_SHIFT_BITS; - Self::from_bits_retain(primitive_topology_bits) - } - - pub fn primitive_topology(&self) -> PrimitiveTopology { - let primitive_topology_bits = (self.bits() >> Self::PRIMITIVE_TOPOLOGY_SHIFT_BITS) - & Self::PRIMITIVE_TOPOLOGY_MASK_BITS; - match primitive_topology_bits { - x if x == PrimitiveTopology::PointList as u32 => PrimitiveTopology::PointList, - x if x == PrimitiveTopology::LineList as u32 => PrimitiveTopology::LineList, - x if x == PrimitiveTopology::LineStrip as u32 => PrimitiveTopology::LineStrip, - x if x == PrimitiveTopology::TriangleList as u32 => PrimitiveTopology::TriangleList, - x if x == PrimitiveTopology::TriangleStrip as u32 => PrimitiveTopology::TriangleStrip, - _ => PrimitiveTopology::default(), - } + pub fn msaa_samples(self) -> u32 { + self.msaa().samples() } } @@ -587,7 +518,7 @@ pub fn setup_morph_and_skinning_defs( mesh_layouts: &MeshLayouts, layout: &Hashed, offset: u32, - key: &MeshPipelineKey, + key: MeshPipelineKey, shader_defs: &mut Vec, vertex_attributes: &mut Vec, ) -> BindGroupLayout { @@ -596,8 +527,7 @@ pub fn setup_morph_and_skinning_defs( vertex_attributes.push(Mesh::ATTRIBUTE_JOINT_INDEX.at_shader_location(offset)); vertex_attributes.push(Mesh::ATTRIBUTE_JOINT_WEIGHT.at_shader_location(offset + 1)); }; - let is_morphed = key.intersects(MeshPipelineKey::MORPH_TARGETS); - match (is_skinned(layout), is_morphed) { + match (is_skinned(layout), key.morph_targets()) { (true, false) => { add_skin_data(); mesh_layouts.skinned.clone() @@ -671,144 +601,96 @@ impl SpecializedMeshPipeline for MeshPipeline { &self.mesh_layouts, layout, 6, - &key, + key, &mut shader_defs, &mut vertex_attributes, )); - if key.contains(MeshPipelineKey::SCREEN_SPACE_AMBIENT_OCCLUSION) { + if key.screen_space_ambient_occlusion() { shader_defs.push("SCREEN_SPACE_AMBIENT_OCCLUSION".into()); } let vertex_buffer_layout = layout.get_layout(&vertex_attributes)?; - let (label, blend, depth_write_enabled); - let pass = key.intersection(MeshPipelineKey::BLEND_RESERVED_BITS); - let mut is_opaque = false; - if pass == MeshPipelineKey::BLEND_ALPHA { - label = "alpha_blend_mesh_pipeline".into(); - blend = Some(BlendState::ALPHA_BLENDING); - // For the transparent pass, fragments that are closer will be alpha blended - // but their depth is not written to the depth buffer - depth_write_enabled = false; - } else if pass == MeshPipelineKey::BLEND_PREMULTIPLIED_ALPHA { - label = "premultiplied_alpha_mesh_pipeline".into(); - blend = Some(BlendState::PREMULTIPLIED_ALPHA_BLENDING); - shader_defs.push("PREMULTIPLY_ALPHA".into()); - shader_defs.push("BLEND_PREMULTIPLIED_ALPHA".into()); - // For the transparent pass, fragments that are closer will be alpha blended - // but their depth is not written to the depth buffer - depth_write_enabled = false; - } else if pass == MeshPipelineKey::BLEND_MULTIPLY { - label = "multiply_mesh_pipeline".into(); - blend = Some(BlendState { - color: BlendComponent { - src_factor: BlendFactor::Dst, - dst_factor: BlendFactor::OneMinusSrcAlpha, - operation: BlendOperation::Add, - }, - alpha: BlendComponent::OVER, - }); - shader_defs.push("PREMULTIPLY_ALPHA".into()); - shader_defs.push("BLEND_MULTIPLY".into()); - // For the multiply pass, fragments that are closer will be alpha blended - // but their depth is not written to the depth buffer - depth_write_enabled = false; - } else { - label = "opaque_mesh_pipeline".into(); - // BlendState::REPLACE is not needed here, and None will be potentially much faster in some cases - blend = None; - // For the opaque and alpha mask passes, fragments that are closer will replace - // the current fragment value in the output and the depth is written to the - // depth buffer - depth_write_enabled = true; - is_opaque = true; - } - - if key.contains(MeshPipelineKey::NORMAL_PREPASS) { + let pipeline_label = |blend_mode| match blend_mode { + BlendMode::Opaque => "opaque_mesh_pipeline", + BlendMode::PremultipliedAlpha => "premultiplied_alpha_mesh_pipeline", + BlendMode::Multiply => "multiply_mesh_pipeline", + BlendMode::Alpha => "alpha_blend_mesh_pipeline", + }; + // For the transparent pass, fragments that are closer will be alpha blended + // but their depth is not written to the depth buffer + // For the multiply pass, fragments that are closer will be alpha blended + // but their depth is not written to the depth buffer + // For the opaque and alpha mask passes, fragments that are closer will replace + // the current fragment value in the output and the depth is written to the + // depth buffer + let blend = key.blend(); + let (label, blend_state) = (pipeline_label(blend), blend.state()); + let (depth_write_enabled, is_opaque) = (blend.is_opaque(), blend.is_opaque()); + + if let Some([def1, def2]) = blend.defines() { + shader_defs.push(def1.into()); + shader_defs.push(def2.into()); + } + + if key.normal_prepass() { shader_defs.push("NORMAL_PREPASS".into()); } - if key.contains(MeshPipelineKey::DEPTH_PREPASS) { + if key.depth_prepass() { shader_defs.push("DEPTH_PREPASS".into()); } - if key.contains(MeshPipelineKey::MOTION_VECTOR_PREPASS) { + if key.motion_vector_prepass() { shader_defs.push("MOTION_VECTOR_PREPASS".into()); } - if key.contains(MeshPipelineKey::DEFERRED_PREPASS) { + if key.deferred_prepass() { shader_defs.push("DEFERRED_PREPASS".into()); } - if key.contains(MeshPipelineKey::NORMAL_PREPASS) && key.msaa_samples() == 1 && is_opaque { + if key.normal_prepass() && key.msaa_samples() == 1 && is_opaque { shader_defs.push("LOAD_PREPASS_NORMALS".into()); } - let view_projection = key.intersection(MeshPipelineKey::VIEW_PROJECTION_RESERVED_BITS); - if view_projection == MeshPipelineKey::VIEW_PROJECTION_NONSTANDARD { - shader_defs.push("VIEW_PROJECTION_NONSTANDARD".into()); - } else if view_projection == MeshPipelineKey::VIEW_PROJECTION_PERSPECTIVE { - shader_defs.push("VIEW_PROJECTION_PERSPECTIVE".into()); - } else if view_projection == MeshPipelineKey::VIEW_PROJECTION_ORTHOGRAPHIC { - shader_defs.push("VIEW_PROJECTION_ORTHOGRAPHIC".into()); + match key.view_projection() { + ViewProjection::Nonstandard => shader_defs.push("VIEW_PROJECTION_NONSTANDARD".into()), + ViewProjection::Perspective => shader_defs.push("VIEW_PROJECTION_PERSPECTIVE".into()), + ViewProjection::Orthographic => shader_defs.push("VIEW_PROJECTION_ORTHOGRAPHIC".into()), + ViewProjection::Reserved => {} } #[cfg(all(feature = "webgl", target_arch = "wasm32"))] shader_defs.push("WEBGL2".into()); - if key.contains(MeshPipelineKey::TONEMAP_IN_SHADER) { + if key.tonemap_in_shader() { shader_defs.push("TONEMAP_IN_SHADER".into()); - - let method = key.intersection(MeshPipelineKey::TONEMAP_METHOD_RESERVED_BITS); - - if method == MeshPipelineKey::TONEMAP_METHOD_NONE { - shader_defs.push("TONEMAP_METHOD_NONE".into()); - } else if method == MeshPipelineKey::TONEMAP_METHOD_REINHARD { - shader_defs.push("TONEMAP_METHOD_REINHARD".into()); - } else if method == MeshPipelineKey::TONEMAP_METHOD_REINHARD_LUMINANCE { - shader_defs.push("TONEMAP_METHOD_REINHARD_LUMINANCE".into()); - } else if method == MeshPipelineKey::TONEMAP_METHOD_ACES_FITTED { - shader_defs.push("TONEMAP_METHOD_ACES_FITTED ".into()); - } else if method == MeshPipelineKey::TONEMAP_METHOD_AGX { - shader_defs.push("TONEMAP_METHOD_AGX".into()); - } else if method == MeshPipelineKey::TONEMAP_METHOD_SOMEWHAT_BORING_DISPLAY_TRANSFORM { - shader_defs.push("TONEMAP_METHOD_SOMEWHAT_BORING_DISPLAY_TRANSFORM".into()); - } else if method == MeshPipelineKey::TONEMAP_METHOD_BLENDER_FILMIC { - shader_defs.push("TONEMAP_METHOD_BLENDER_FILMIC".into()); - } else if method == MeshPipelineKey::TONEMAP_METHOD_TONY_MC_MAPFACE { - shader_defs.push("TONEMAP_METHOD_TONY_MC_MAPFACE".into()); - } + shader_defs.push(key.tonemap_method().define().into()); // Debanding is tied to tonemapping in the shader, cannot run without it. - if key.contains(MeshPipelineKey::DEBAND_DITHER) { + if key.deband_dither() { shader_defs.push("DEBAND_DITHER".into()); } } - if key.contains(MeshPipelineKey::MAY_DISCARD) { + if key.may_discard() { shader_defs.push("MAY_DISCARD".into()); } - if key.contains(MeshPipelineKey::ENVIRONMENT_MAP) { + if key.environment_map() { shader_defs.push("ENVIRONMENT_MAP".into()); } - if key.contains(MeshPipelineKey::TAA) { + if key.taa() { shader_defs.push("TAA".into()); } - let shadow_filter_method = - key.intersection(MeshPipelineKey::SHADOW_FILTER_METHOD_RESERVED_BITS); - if shadow_filter_method == MeshPipelineKey::SHADOW_FILTER_METHOD_HARDWARE_2X2 { - shader_defs.push("SHADOW_FILTER_METHOD_HARDWARE_2X2".into()); - } else if shadow_filter_method == MeshPipelineKey::SHADOW_FILTER_METHOD_CASTANO_13 { - shader_defs.push("SHADOW_FILTER_METHOD_CASTANO_13".into()); - } else if shadow_filter_method == MeshPipelineKey::SHADOW_FILTER_METHOD_JIMENEZ_14 { - shader_defs.push("SHADOW_FILTER_METHOD_JIMENEZ_14".into()); + if let Ok(filter_method) = key.shadow_filter_method() { + shader_defs.push(filter_method.define().into()); } - let format = if key.contains(MeshPipelineKey::HDR) { + let format = if key.hdr() { ViewTarget::TEXTURE_FORMAT_HDR } else { TextureFormat::bevy_default() @@ -845,7 +727,7 @@ impl SpecializedMeshPipeline for MeshPipeline { entry_point: "fragment".into(), targets: vec![Some(ColorTargetState { format, - blend, + blend: blend_state, write_mask: ColorWrites::ALL, })], }), @@ -857,7 +739,7 @@ impl SpecializedMeshPipeline for MeshPipeline { unclipped_depth: false, polygon_mode: PolygonMode::Fill, conservative: false, - topology: key.primitive_topology(), + topology: key.primitive_topology().unwrap_or_default().into(), strip_index_format: None, }, depth_stencil: Some(DepthStencilState { @@ -881,7 +763,7 @@ impl SpecializedMeshPipeline for MeshPipeline { mask: !0, alpha_to_coverage_enabled: false, }, - label: Some(label), + label: Some(label.into()), }) } } @@ -1100,11 +982,12 @@ impl RenderCommand

for DrawMesh { #[cfg(test)] mod tests { - use super::MeshPipelineKey; + use super::{MeshPipelineKey, Msaa}; #[test] fn mesh_key_msaa_samples() { for i in [1, 2, 4, 8, 16, 32, 64, 128] { - assert_eq!(MeshPipelineKey::from_msaa_samples(i).msaa_samples(), i); + let key = MeshPipelineKey::DEFAULT.with_msaa(Msaa::from_samples(i).unwrap()); + assert_eq!(key.msaa_samples(), i); } } } diff --git a/crates/bevy_pbr/src/render/mesh_view_bindings.rs b/crates/bevy_pbr/src/render/mesh_view_bindings.rs index ba66561c6d785..24ec13d7d7555 100644 --- a/crates/bevy_pbr/src/render/mesh_view_bindings.rs +++ b/crates/bevy_pbr/src/render/mesh_view_bindings.rs @@ -91,16 +91,16 @@ impl From for MeshPipelineViewLayoutKey { if value.msaa_samples() > 1 { result |= MeshPipelineViewLayoutKey::MULTISAMPLED; } - if value.contains(MeshPipelineKey::DEPTH_PREPASS) { + if value.depth_prepass() { result |= MeshPipelineViewLayoutKey::DEPTH_PREPASS; } - if value.contains(MeshPipelineKey::NORMAL_PREPASS) { + if value.normal_prepass() { result |= MeshPipelineViewLayoutKey::NORMAL_PREPASS; } - if value.contains(MeshPipelineKey::MOTION_VECTOR_PREPASS) { + if value.motion_vector_prepass() { result |= MeshPipelineViewLayoutKey::MOTION_VECTOR_PREPASS; } - if value.contains(MeshPipelineKey::DEFERRED_PREPASS) { + if value.deferred_prepass() { result |= MeshPipelineViewLayoutKey::DEFERRED_PREPASS; } diff --git a/crates/bevy_render/Cargo.toml b/crates/bevy_render/Cargo.toml index 9878284469930..a89195d95b2c2 100644 --- a/crates/bevy_render/Cargo.toml +++ b/crates/bevy_render/Cargo.toml @@ -57,6 +57,8 @@ bevy_tasks = { path = "../bevy_tasks", version = "0.12.0-dev" } image = { version = "0.24", default-features = false } # misc +arbitrary-int = "1.2.6" +bitbybit = "1.2.2" codespan-reporting = "0.11.0" # `fragile-send-sync-non-atomic-wasm` feature means we can't use WASM threads for rendering # It is enabled for now to avoid having to do a significant overhaul of the renderer just for wasm diff --git a/crates/bevy_render/src/renderer/mod.rs b/crates/bevy_render/src/renderer/mod.rs index 73992091ea57e..1b31275124ba2 100644 --- a/crates/bevy_render/src/renderer/mod.rs +++ b/crates/bevy_render/src/renderer/mod.rs @@ -18,9 +18,38 @@ use bevy_time::TimeSender; use bevy_utils::Instant; use std::sync::Arc; use wgpu::{ - Adapter, AdapterInfo, CommandBuffer, CommandEncoder, Instance, Queue, RequestAdapterOptions, + Adapter, AdapterInfo, CommandBuffer, CommandEncoder, Instance, PrimitiveTopology, Queue, + RequestAdapterOptions, }; +#[bitbybit::bitenum(u3, exhaustive: false)] +#[derive(Default)] +pub enum BevyPrimitiveTopology { + PointList = 0, + LineList = 1, + LineStrip = 2, + #[default] + TriangleList = 3, + TriangleStrip = 4, +} +impl From for BevyPrimitiveTopology { + fn from(value: PrimitiveTopology) -> Self { + let value = arbitrary_int::u3::new(value as u8); + Self::new_with_raw_value(value).unwrap_or_default() + } +} +impl From for PrimitiveTopology { + fn from(value: BevyPrimitiveTopology) -> Self { + match value { + BevyPrimitiveTopology::PointList => Self::PointList, + BevyPrimitiveTopology::LineList => Self::LineList, + BevyPrimitiveTopology::LineStrip => Self::LineStrip, + BevyPrimitiveTopology::TriangleList => Self::TriangleList, + BevyPrimitiveTopology::TriangleStrip => Self::TriangleStrip, + } + } +} + /// Updates the [`RenderGraph`] with all of its nodes and then runs it to render the entire frame. pub fn render_system(world: &mut World) { world.resource_scope(|world, mut graph: Mut| { diff --git a/crates/bevy_render/src/view/mod.rs b/crates/bevy_render/src/view/mod.rs index 9759684148324..e69cc1aeb1519 100644 --- a/crates/bevy_render/src/view/mod.rs +++ b/crates/bevy_render/src/view/mod.rs @@ -82,22 +82,41 @@ impl Plugin for ViewPlugin { /// .insert_resource(Msaa::default()) /// .run(); /// ``` -#[derive( - Resource, Default, Clone, Copy, ExtractResource, Reflect, PartialEq, PartialOrd, Debug, -)] +#[bitbybit::bitenum(u3, exhaustive: true)] +#[derive(Resource, Default, ExtractResource, Reflect, PartialEq, PartialOrd, Debug)] #[reflect(Resource)] pub enum Msaa { - Off = 1, - Sample2 = 2, + Off = 0, + Sample2 = 1, #[default] - Sample4 = 4, - Sample8 = 8, + Sample4 = 2, + Sample8 = 3, + Sample16 = 4, + Sample32 = 5, + Sample64 = 6, + Sample128 = 7, } impl Msaa { #[inline] pub fn samples(&self) -> u32 { - *self as u32 + 1 << u32::from(self.raw_value()) + } + pub fn from_samples(samples: u32) -> Option { + if samples == 0 { + return None; + } + match samples.ilog2() { + 0 => Some(Msaa::Off), + 1 => Some(Msaa::Sample2), + 2 => Some(Msaa::Sample4), + 3 => Some(Msaa::Sample8), + 4 => Some(Msaa::Sample16), + 5 => Some(Msaa::Sample32), + 6 => Some(Msaa::Sample64), + 7 => Some(Msaa::Sample128), + _ => None, + } } } diff --git a/examples/shader/shader_instancing.rs b/examples/shader/shader_instancing.rs index fc18bb63c73b7..32d55720a2316 100644 --- a/examples/shader/shader_instancing.rs +++ b/examples/shader/shader_instancing.rs @@ -121,10 +121,9 @@ fn queue_custom( ) { let draw_custom = transparent_3d_draw_functions.read().id::(); - let msaa_key = MeshPipelineKey::from_msaa_samples(msaa.samples()); - for (view, mut transparent_phase) in &mut views { - let view_key = msaa_key | MeshPipelineKey::from_hdr(view.hdr); + let view_key = MeshPipelineKey::DEFAULT.with_msaa(*msaa).with_hdr(view.hdr); + let rangefinder = view.rangefinder3d(); for entity in &material_meshes { let Some(mesh_instance) = render_mesh_instances.get(&entity) else { @@ -133,7 +132,7 @@ fn queue_custom( let Some(mesh) = meshes.get(mesh_instance.mesh_asset_id) else { continue; }; - let key = view_key | MeshPipelineKey::from_primitive_topology(mesh.primitive_topology); + let key = view_key.with_primitive_topology(mesh.primitive_topology.into()); let pipeline = pipelines .specialize(&pipeline_cache, &custom_pipeline, key, &mesh.layout) .unwrap();