diff --git a/crates/bevy_core_pipeline/Cargo.toml b/crates/bevy_core_pipeline/Cargo.toml index 27825880d3473f..8c59b3321379da 100644 --- a/crates/bevy_core_pipeline/Cargo.toml +++ b/crates/bevy_core_pipeline/Cargo.toml @@ -30,6 +30,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 6aada19c458e5f..234e5dcc40f013 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,31 @@ 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", + } + } } #[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)] @@ -185,22 +198,8 @@ impl SpecializedRenderPipeline for TonemappingPipeline { if let DebandDither::Enabled = key.deband_dither { shader_defs.push("DEBAND_DITHER".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 => shader_defs.push("TONEMAP_METHOD_AGX".into()), - Tonemapping::SomewhatBoringDisplayTransform => { - shader_defs.push("TONEMAP_METHOD_SOMEWHAT_BORING_DISPLAY_TRANSFORM".into()); - } - Tonemapping::TonyMcMapface => shader_defs.push("TONEMAP_METHOD_TONY_MC_MAPFACE".into()), - Tonemapping::BlenderFilmic => { - shader_defs.push("TONEMAP_METHOD_BLENDER_FILMIC".into()); - } - } + shader_defs.push(key.tonemapping.define().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 416d7e9a9992ff..517801bc4a8a43 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,9 @@ 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_samples(*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 dc2cdeb1235d20..5f7807ec4954e3 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 2dfad77ac9a609..bdad7d2b34b8ff 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,52 @@ 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: false)] +#[derive(PartialEq)] +pub enum BlendMode { + PremultipliedAlpha = 1, + Multiply = 2, + Alpha = 3, +} +impl From for Option { + fn from(value: AlphaMode) -> Self { + match value { + AlphaMode::Premultiplied | AlphaMode::Add => Some(BlendMode::PremultipliedAlpha), + AlphaMode::Blend => Some(BlendMode::Alpha), + AlphaMode::Multiply => Some(BlendMode::Multiply), + _ => None, + } + } +} +impl BlendMode { + pub fn state(self) -> BlendState { + use bevy_render::render_resource::*; + match self { + Self::PremultipliedAlpha => BlendState::PREMULTIPLIED_ALPHA_BLENDING, + Self::Multiply => BlendState { + color: BlendComponent { + src_factor: BlendFactor::Dst, + dst_factor: BlendFactor::OneMinusSrcAlpha, + operation: BlendOperation::Add, + }, + alpha: BlendComponent::OVER, + }, + Self::Alpha => BlendState::ALPHA_BLENDING, + } + } + pub const fn defines(self) -> Option<[&'static str; 2]> { + match self { + Self::Alpha => None, + Self::PremultipliedAlpha => Some(["PREMULTIPLY_ALPHA", "BLEND_PREMULTIPLIED_ALPHA"]), + Self::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 f679f638d42ac8..a969b407becb00 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.into()) + // 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 7316604ad3f5c0..c5143582048741 100644 --- a/crates/bevy_pbr/src/light.rs +++ b/crates/bevy_pbr/src/light.rs @@ -614,19 +614,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. @@ -634,7 +635,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 f2967aec0b9e7e..76b8694ecf832d 100644 --- a/crates/bevy_pbr/src/material.rs +++ b/crates/bevy_pbr/src/material.rs @@ -401,33 +401,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>, @@ -483,58 +456,31 @@ 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)); - - 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; - } - } + let filtering_method = shadow_filter_method.copied().unwrap_or_default(); + + let mut view_key = MeshPipelineKey::DEFAULT + .with_msaa_samples(*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.into()) + .with_screen_space_ambient_occlusion(ssao.is_some()); if !view.hdr { if let Some(tonemapping) = tonemapping { - view_key |= MeshPipelineKey::TONEMAP_IN_SHADER; - view_key |= tonemapping_pipeline_key(*tonemapping); - } - if let Some(DebandDither::Enabled) = dither { - view_key |= MeshPipelineKey::DEBAND_DITHER; + 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); } - if ssao.is_some() { - view_key |= MeshPipelineKey::SCREEN_SPACE_AMBIENT_OCCLUSION; - } + let rangefinder = view.rangefinder3d(); for visible_entity in &visible_entities.entities { let Some(material_asset_id) = render_material_instances.get(visible_entity) else { @@ -556,14 +502,15 @@ pub fn queue_material_meshes( OpaqueRendererMethod::Auto => unreachable!(), }; - let mut mesh_key = view_key; - - mesh_key |= MeshPipelineKey::from_primitive_topology(mesh.primitive_topology); + let alpha_mode = material.properties.alpha_mode; + let mut 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()); - if mesh.morph_targets.is_some() { - mesh_key |= MeshPipelineKey::MORPH_TARGETS; + if let Some(blend_mode) = alpha_mode.into() { + mesh_key = mesh_key.with_blend(blend_mode); } - mesh_key |= alpha_mode_pipeline_key(material.properties.alpha_mode); 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 54155ce385a0e8..a5667ebb1ddf8f 100644 --- a/crates/bevy_pbr/src/prepass/mod.rs +++ b/crates/bevy_pbr/src/prepass/mod.rs @@ -51,7 +51,7 @@ use bevy_transform::prelude::GlobalTransform; use bevy_utils::tracing::error; use crate::{ - prepare_materials, setup_morph_and_skinning_defs, AlphaMode, DrawMesh, Material, + prepare_materials, setup_morph_and_skinning_defs, AlphaMode, BlendMode, DrawMesh, Material, MaterialPipeline, MaterialPipelineKey, MeshLayouts, MeshPipeline, MeshPipelineKey, OpaqueRendererMethod, RenderMaterialInstances, RenderMaterials, RenderMeshInstances, SetMaterialBindGroup, SetMeshBindGroup, @@ -368,14 +368,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(); @@ -393,21 +392,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 == Ok(BlendMode::PremultipliedAlpha) { shader_defs.push("BLEND_PREMULTIPLIED_ALPHA".into()); } - if blend_key == MeshPipelineKey::BLEND_ALPHA { + if blend_mode == Ok(BlendMode::Alpha) { shader_defs.push("BLEND_ALPHA".into()); } @@ -416,7 +413,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 @@ -432,14 +429,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) { @@ -448,14 +442,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) { @@ -464,18 +455,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()); } @@ -483,7 +470,7 @@ where &self.mesh_layouts, layout, 4, - &key.mesh_key, + mesh_key, &mut shader_defs, &mut vertex_attributes, ); @@ -493,37 +480,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) { @@ -536,13 +517,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, @@ -563,7 +543,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 { @@ -593,7 +573,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, @@ -799,16 +779,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_samples(*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(); @@ -829,21 +804,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, @@ -851,10 +819,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 258ea59f09065d..806f9ad9b750eb 100644 --- a/crates/bevy_pbr/src/render/light.rs +++ b/crates/bevy_pbr/src/render/light.rs @@ -1613,22 +1613,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 a798cc6ac3b718..3b30e0342bdd76 100644 --- a/crates/bevy_pbr/src/render/mesh.rs +++ b/crates/bevy_pbr/src/render/mesh.rs @@ -1,15 +1,17 @@ use crate::{ - generate_view_layouts, prepare_mesh_view_bind_groups, MaterialBindGroupId, + generate_view_layouts, prepare_mesh_view_bind_groups, BlendMode, MaterialBindGroupId, MeshPipelineViewLayout, MeshPipelineViewLayoutKey, MeshViewBindGroup, NotShadowCaster, - NotShadowReceiver, PreviousGlobalTransform, Shadow, ViewFogUniformOffset, - ViewLightsUniformOffset, CLUSTERED_FORWARD_STORAGE_BUFFER_COUNT, MAX_CASCADES_PER_LIGHT, - MAX_DIRECTIONAL_LIGHTS, + NotShadowReceiver, PreviousGlobalTransform, Shadow, ShadowFilteringMethod, + ViewFogUniformOffset, ViewLightsUniformOffset, CLUSTERED_FORWARD_STORAGE_BUFFER_COUNT, + MAX_CASCADES_PER_LIGHT, MAX_DIRECTIONAL_LIGHTS, }; +use arbitrary_int::u3; use bevy_app::{Plugin, PostUpdate}; 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::{ @@ -30,15 +32,16 @@ use bevy_render::{ render_asset::RenderAssets, render_phase::{PhaseItem, RenderCommand, RenderCommandResult, TrackedRenderPass}, render_resource::*, - renderer::{RenderDevice, RenderQueue}, + renderer::{BevyPrimitiveTopology, RenderDevice, RenderQueue}, texture::{ BevyDefault, DefaultImageSampler, GpuImage, Image, ImageSampler, TextureFormatPixelInfo, }, - 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::bitfield; use std::cell::Cell; use thread_local::ThreadLocal; @@ -479,106 +482,42 @@ 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; - } +#[rustfmt::skip] +#[bitfield(u32)] +#[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: https://www.khronos.org/opengl/wiki/Early_Fragment_Test + #[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: Option, + #[bits(15..=17, rw)] msaa: u3, + #[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, } - 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(); + pub const DEFAULT: Self = Self::new_with_raw_value(0) + .with_msaa_samples(Msaa::Off) + .with_shadow_filter_method(ShadowFilteringMethod::Castano13); - 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(); - - 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 const fn msaa_samples(self) -> u32 { + self.msaa().value() as u32 } - - 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 const fn with_msaa_samples(self, samples: Msaa) -> Self { + let value = samples as u8; + self.with_msaa(u3::new(value)) } } @@ -589,7 +528,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 { @@ -598,8 +537,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() @@ -625,6 +563,12 @@ impl SpecializedMeshPipeline for MeshPipeline { key: Self::Key, layout: &MeshVertexBufferLayout, ) -> Result { + let pipeline_label = |blend_mode| match blend_mode { + None => "opaque_mesh_pipeline", + Some(BlendMode::PremultipliedAlpha) => "premultiplied_alpha_mesh_pipeline", + Some(BlendMode::Multiply) => "multiply_mesh_pipeline", + Some(BlendMode::Alpha) => "alpha_blend_mesh_pipeline", + }; let mut shader_defs = Vec::new(); let mut vertex_attributes = Vec::new(); @@ -673,135 +617,83 @@ 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; + // 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().ok(); + let (label, blend) = (pipeline_label(blend_), blend_.map(BlendMode::state)); + let (depth_write_enabled, is_opaque) = (blend_.is_none(), blend_.is_none()); + if let Some(blend_shader_defs) = blend_.and_then(BlendMode::defines) { + for shader_def in blend_shader_defs { + shader_defs.push(shader_def.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()); } - 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()); } #[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() @@ -850,7 +742,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 { @@ -874,7 +766,7 @@ impl SpecializedMeshPipeline for MeshPipeline { mask: !0, alpha_to_coverage_enabled: false, }, - label: Some(label), + label: Some(label.into()), }) } } @@ -1096,8 +988,10 @@ mod tests { use super::MeshPipelineKey; #[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); + for i in [1_u8, 2, 4, 8, 16, 32, 64, 128] { + let value = arbitrary_int::u3::new(i); + let key = MeshPipelineKey::DEFAULT.with_msaa(value); + assert_eq!(key.msaa().value(), i); } } } diff --git a/crates/bevy_pbr/src/render/mesh_view_bindings.rs b/crates/bevy_pbr/src/render/mesh_view_bindings.rs index ba66561c6d7854..24ec13d7d75553 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 9878284469930d..a89195d95b2c2e 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 73992091ea57ed..1b31275124ba2f 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/examples/shader/shader_instancing.rs b/examples/shader/shader_instancing.rs index fc18bb63c73b7f..941b3f7e7de1d0 100644 --- a/examples/shader/shader_instancing.rs +++ b/examples/shader/shader_instancing.rs @@ -121,10 +121,11 @@ 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_samples(*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 +134,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();