Skip to content

Commit

Permalink
Use bitbybit to encode the MeshPipelineKey
Browse files Browse the repository at this point in the history
This removes all the maths around bit shifting and masking, it also
makes reading key flags easier, and setting key flags easier.
  • Loading branch information
nicopap committed Oct 24, 2023
1 parent fb55884 commit d40e79c
Show file tree
Hide file tree
Showing 15 changed files with 348 additions and 511 deletions.
2 changes: 2 additions & 0 deletions crates/bevy_core_pipeline/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
53 changes: 26 additions & 27 deletions crates/bevy_core_pipeline/src/tonemapping/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<Camera>)]
#[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.
/// <https://github.com/TheRealMJP/BakingLab/blob/master/BakingLab/ACES.hlsl>
/// 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
/// <https://github.com/sobotka/AgX>
/// 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
/// <https://github.com/h3r2tic/tony-mc-mapface>
Expand All @@ -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)]
Expand All @@ -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()],
Expand Down
7 changes: 4 additions & 3 deletions crates/bevy_gizmos/src/pipeline_3d.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down Expand Up @@ -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 {
Expand Down
2 changes: 2 additions & 0 deletions crates/bevy_pbr/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
49 changes: 49 additions & 0 deletions crates/bevy_pbr/src/alpha.rs
Original file line number Diff line number Diff line change
@@ -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.
Expand Down Expand Up @@ -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<AlphaMode> for Option<BlendMode> {
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 {}
123 changes: 29 additions & 94 deletions crates/bevy_pbr/src/deferred/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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"))]
Expand All @@ -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()
Expand Down Expand Up @@ -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 =
Expand Down
Loading

0 comments on commit d40e79c

Please sign in to comment.