From b3d78304a7e43f8f19fc8da8bbd8d42a8e5377d3 Mon Sep 17 00:00:00 2001 From: Aevyrie Date: Mon, 11 Jul 2022 04:35:07 -0700 Subject: [PATCH 1/7] dithering in fullscreen tonemap --- Cargo.toml | 10 +++ .../src/tonemapping/tonemapping.wgsl | 8 ++- .../src/tonemapping/tonemapping_shared.wgsl | 8 +++ examples/stress_tests/color_banding.rs | 61 +++++++++++++++++++ 4 files changed, 86 insertions(+), 1 deletion(-) create mode 100644 examples/stress_tests/color_banding.rs diff --git a/Cargo.toml b/Cargo.toml index f8f26109910da..caf15eaea0628 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1349,6 +1349,16 @@ description = "Various test cases for hierarchy and transform propagation perfor category = "Stress Tests" wasm = true +[[example]] +name = "color_banding" +path = "examples/stress_tests/color_banding.rs" + +[package.metadata.example.color_banding] +name = "Color Banding" +description = "Test cases for extreme color banding" +category = "Stress Tests" +wasm = false + # Tools [[example]] name = "scene_viewer" diff --git a/crates/bevy_core_pipeline/src/tonemapping/tonemapping.wgsl b/crates/bevy_core_pipeline/src/tonemapping/tonemapping.wgsl index e18ae8a026f40..a920670318480 100644 --- a/crates/bevy_core_pipeline/src/tonemapping/tonemapping.wgsl +++ b/crates/bevy_core_pipeline/src/tonemapping/tonemapping.wgsl @@ -10,5 +10,11 @@ var hdr_sampler: sampler; fn fragment(in: FullscreenVertexOutput) -> @location(0) vec4 { let hdr_color = textureSample(hdr_texture, hdr_sampler, in.uv); - return vec4(reinhard_luminance(hdr_color.rgb), hdr_color.a); + var output_rgb = vec3(reinhard_luminance(hdr_color.rgb)); + output_rgb = pow(output_rgb.rgb, vec3(1.0 / 2.2)); + output_rgb = output_rgb + screen_space_dither(in.position.xy); + // This conversion back to linear space is required because our output texture format is + // SRGB; the GPU will assume our output is linear and will apply an SRGB conversion. + output_rgb = pow(output_rgb.rgb, vec3(2.2)); + return vec4(output_rgb, hdr_color.a); } diff --git a/crates/bevy_core_pipeline/src/tonemapping/tonemapping_shared.wgsl b/crates/bevy_core_pipeline/src/tonemapping/tonemapping_shared.wgsl index d71dd12f08f32..c3105aa51b6df 100644 --- a/crates/bevy_core_pipeline/src/tonemapping/tonemapping_shared.wgsl +++ b/crates/bevy_core_pipeline/src/tonemapping/tonemapping_shared.wgsl @@ -27,3 +27,11 @@ fn reinhard_luminance(color: vec3) -> vec3 { let l_new = l_old / (1.0 + l_old); return tonemapping_change_luminance(color, l_new); } + +// Source: Advanced VR Rendering, GDC 2015, Alex Vlachos, Valve, Slide 49 +// https://media.steampowered.com/apps/valve/2015/Alex_Vlachos_Advanced_VR_Rendering_GDC2015.pdf +fn screen_space_dither(frag_coord: vec2) -> vec3 { + var dither = vec3(dot(vec2(171.0, 231.0), frag_coord)).xxx; + dither = fract(dither.rgb / vec3(103.0, 71.0, 97.0)); + return dither / 255.0; +} \ No newline at end of file diff --git a/examples/stress_tests/color_banding.rs b/examples/stress_tests/color_banding.rs new file mode 100644 index 0000000000000..7a02de63336f5 --- /dev/null +++ b/examples/stress_tests/color_banding.rs @@ -0,0 +1,61 @@ +//! Useful for stress-testing rendering cases that exhibit extreme color banding. + +use bevy::prelude::*; + +fn main() { + App::new() + .add_plugins(DefaultPlugins) + .insert_resource(AmbientLight { + color: Color::BLACK, + brightness: 0.0, + }) + .add_startup_system(setup) + .run(); +} + +/// set up a simple 3D scene +fn setup( + mut commands: Commands, + mut meshes: ResMut>, + mut materials: ResMut>, +) { + let colors = [Color::WHITE, Color::RED, Color::GREEN, Color::BLUE]; + let x = [-5.0, 5.0, -5.0, 5.0]; + let z = [-5.0, -5.0, 5.0, 5.0]; + let mesh = meshes.add(Mesh::from(shape::Plane { size: 10.0 })); + + for i in 0..4 { + // plane + commands.spawn_bundle(PbrBundle { + mesh: mesh.clone(), + material: materials.add(custom_material(colors[i])), + transform: Transform::from_xyz(x[i], 0.0, z[i]), + ..default() + }); + } + // light + commands.spawn_bundle(PointLightBundle { + point_light: PointLight { + intensity: 50.0, + shadows_enabled: false, + ..default() + }, + transform: Transform::from_xyz(0.0, 5.0, 0.0), + ..default() + }); + // camera + commands.spawn_bundle(Camera3dBundle { + transform: Transform::from_xyz(0.0, 6.0, 0.0).looking_at(Vec3::default(), -Vec3::Z), + ..default() + }); +} + +fn custom_material(color: Color) -> StandardMaterial { + StandardMaterial { + base_color: color, + perceptual_roughness: 1.0, + metallic: 0.0, + reflectance: 0.0, + ..default() + } +} From 2e5c7062ad2790afa253cbcbc21e2db0f902b011 Mon Sep 17 00:00:00 2001 From: Aevyrie Roessler Date: Wed, 9 Nov 2022 19:04:16 -0800 Subject: [PATCH 2/7] remove color banding stress test example --- Cargo.toml | 10 ----- examples/stress_tests/color_banding.rs | 61 -------------------------- 2 files changed, 71 deletions(-) delete mode 100644 examples/stress_tests/color_banding.rs diff --git a/Cargo.toml b/Cargo.toml index caf15eaea0628..f8f26109910da 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1349,16 +1349,6 @@ description = "Various test cases for hierarchy and transform propagation perfor category = "Stress Tests" wasm = true -[[example]] -name = "color_banding" -path = "examples/stress_tests/color_banding.rs" - -[package.metadata.example.color_banding] -name = "Color Banding" -description = "Test cases for extreme color banding" -category = "Stress Tests" -wasm = false - # Tools [[example]] name = "scene_viewer" diff --git a/examples/stress_tests/color_banding.rs b/examples/stress_tests/color_banding.rs deleted file mode 100644 index 7a02de63336f5..0000000000000 --- a/examples/stress_tests/color_banding.rs +++ /dev/null @@ -1,61 +0,0 @@ -//! Useful for stress-testing rendering cases that exhibit extreme color banding. - -use bevy::prelude::*; - -fn main() { - App::new() - .add_plugins(DefaultPlugins) - .insert_resource(AmbientLight { - color: Color::BLACK, - brightness: 0.0, - }) - .add_startup_system(setup) - .run(); -} - -/// set up a simple 3D scene -fn setup( - mut commands: Commands, - mut meshes: ResMut>, - mut materials: ResMut>, -) { - let colors = [Color::WHITE, Color::RED, Color::GREEN, Color::BLUE]; - let x = [-5.0, 5.0, -5.0, 5.0]; - let z = [-5.0, -5.0, 5.0, 5.0]; - let mesh = meshes.add(Mesh::from(shape::Plane { size: 10.0 })); - - for i in 0..4 { - // plane - commands.spawn_bundle(PbrBundle { - mesh: mesh.clone(), - material: materials.add(custom_material(colors[i])), - transform: Transform::from_xyz(x[i], 0.0, z[i]), - ..default() - }); - } - // light - commands.spawn_bundle(PointLightBundle { - point_light: PointLight { - intensity: 50.0, - shadows_enabled: false, - ..default() - }, - transform: Transform::from_xyz(0.0, 5.0, 0.0), - ..default() - }); - // camera - commands.spawn_bundle(Camera3dBundle { - transform: Transform::from_xyz(0.0, 6.0, 0.0).looking_at(Vec3::default(), -Vec3::Z), - ..default() - }); -} - -fn custom_material(color: Color) -> StandardMaterial { - StandardMaterial { - base_color: color, - perceptual_roughness: 1.0, - metallic: 0.0, - reflectance: 0.0, - ..default() - } -} From c600c35d3c0b94ce6121120cf2cf728eba8ad0c3 Mon Sep 17 00:00:00 2001 From: Aevyrie Roessler Date: Wed, 9 Nov 2022 22:20:35 -0800 Subject: [PATCH 3/7] get working with and without hdr --- crates/bevy_pbr/src/render/pbr.wgsl | 1 + crates/bevy_pbr/src/render/pbr_functions.wgsl | 4 ++++ 2 files changed, 5 insertions(+) diff --git a/crates/bevy_pbr/src/render/pbr.wgsl b/crates/bevy_pbr/src/render/pbr.wgsl index 716ef89a4350b..4835c1897c426 100644 --- a/crates/bevy_pbr/src/render/pbr.wgsl +++ b/crates/bevy_pbr/src/render/pbr.wgsl @@ -97,6 +97,7 @@ fn fragment(in: FragmentInput) -> @location(0) vec4 { #ifdef TONEMAP_IN_SHADER output_color = tone_mapping(output_color); + output_color = dither(output_color, in.frag_coord.xy); #endif return output_color; } diff --git a/crates/bevy_pbr/src/render/pbr_functions.wgsl b/crates/bevy_pbr/src/render/pbr_functions.wgsl index 58eb7023e7243..44fd2c97c609b 100644 --- a/crates/bevy_pbr/src/render/pbr_functions.wgsl +++ b/crates/bevy_pbr/src/render/pbr_functions.wgsl @@ -260,5 +260,9 @@ fn tone_mapping(in: vec4) -> vec4 { // Not needed with sRGB buffer // output_color.rgb = pow(output_color.rgb, vec3(1.0 / 2.2)); } + +fn dither(color: vec4, pos: vec2) -> vec4 { + return vec4(color.rgb + screen_space_dither(pos.xy), color.a); +} #endif From d8c45e1230abfc5394544ab539416b9acc32ae5c Mon Sep 17 00:00:00 2001 From: Aevyrie Roessler Date: Thu, 10 Nov 2022 00:09:14 -0800 Subject: [PATCH 4/7] unbias dither lightness --- .../bevy_core_pipeline/src/tonemapping/tonemapping_shared.wgsl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/bevy_core_pipeline/src/tonemapping/tonemapping_shared.wgsl b/crates/bevy_core_pipeline/src/tonemapping/tonemapping_shared.wgsl index c3105aa51b6df..deafac0750d84 100644 --- a/crates/bevy_core_pipeline/src/tonemapping/tonemapping_shared.wgsl +++ b/crates/bevy_core_pipeline/src/tonemapping/tonemapping_shared.wgsl @@ -33,5 +33,5 @@ fn reinhard_luminance(color: vec3) -> vec3 { fn screen_space_dither(frag_coord: vec2) -> vec3 { var dither = vec3(dot(vec2(171.0, 231.0), frag_coord)).xxx; dither = fract(dither.rgb / vec3(103.0, 71.0, 97.0)); - return dither / 255.0; + return (dither - 0.5) / 255.0; } \ No newline at end of file From 2ab94a2a1722f1e1eda1031e732d76bdaec46f83 Mon Sep 17 00:00:00 2001 From: Aevyrie Roessler Date: Thu, 10 Nov 2022 12:11:58 -0800 Subject: [PATCH 5/7] Specialize on deband dither key --- .../bevy_core_pipeline/src/core_2d/camera_2d.rs | 2 +- .../bevy_core_pipeline/src/core_3d/camera_3d.rs | 4 +++- .../bevy_core_pipeline/src/tonemapping/mod.rs | 14 ++++++++++++-- .../bevy_core_pipeline/src/tonemapping/node.rs | 2 +- .../src/tonemapping/tonemapping.wgsl | 4 ++++ crates/bevy_pbr/src/material.rs | 11 +++++++++-- crates/bevy_pbr/src/render/mesh.rs | 6 ++++++ crates/bevy_pbr/src/render/pbr.wgsl | 2 ++ crates/bevy_pbr/src/render/pbr_functions.wgsl | 2 ++ crates/bevy_sprite/src/mesh2d/material.rs | 11 +++++++++-- crates/bevy_sprite/src/mesh2d/mesh.rs | 6 ++++++ crates/bevy_sprite/src/render/mod.rs | 17 +++++++++++++++-- 12 files changed, 70 insertions(+), 11 deletions(-) diff --git a/crates/bevy_core_pipeline/src/core_2d/camera_2d.rs b/crates/bevy_core_pipeline/src/core_2d/camera_2d.rs index 711fda3a57a2a..86906240b467c 100644 --- a/crates/bevy_core_pipeline/src/core_2d/camera_2d.rs +++ b/crates/bevy_core_pipeline/src/core_2d/camera_2d.rs @@ -75,7 +75,7 @@ impl Camera2dBundle { global_transform: Default::default(), camera: Camera::default(), camera_2d: Camera2d::default(), - tonemapping: Tonemapping { is_enabled: false }, + tonemapping: Tonemapping::Disabled, } } } diff --git a/crates/bevy_core_pipeline/src/core_3d/camera_3d.rs b/crates/bevy_core_pipeline/src/core_3d/camera_3d.rs index 20a0001457a9f..5278ca5488b70 100644 --- a/crates/bevy_core_pipeline/src/core_3d/camera_3d.rs +++ b/crates/bevy_core_pipeline/src/core_3d/camera_3d.rs @@ -74,7 +74,9 @@ impl Default for Camera3dBundle { fn default() -> Self { Self { camera_render_graph: CameraRenderGraph::new(crate::core_3d::graph::NAME), - tonemapping: Tonemapping { is_enabled: true }, + tonemapping: Tonemapping::Enabled { + is_deband_dither_enabled: true, + }, camera: Default::default(), projection: Default::default(), visible_entities: Default::default(), diff --git a/crates/bevy_core_pipeline/src/tonemapping/mod.rs b/crates/bevy_core_pipeline/src/tonemapping/mod.rs index 3af2ecedccb18..cf587c508740b 100644 --- a/crates/bevy_core_pipeline/src/tonemapping/mod.rs +++ b/crates/bevy_core_pipeline/src/tonemapping/mod.rs @@ -108,8 +108,18 @@ impl FromWorld for TonemappingPipeline { #[derive(Component, Clone, Reflect, Default)] #[reflect(Component)] -pub struct Tonemapping { - pub is_enabled: bool, +pub enum Tonemapping { + #[default] + Disabled, + Enabled { + is_deband_dither_enabled: bool, + }, +} + +impl Tonemapping { + pub fn is_enabled(&self) -> bool { + matches!(self, Tonemapping::Enabled { .. }) + } } impl ExtractComponent for Tonemapping { diff --git a/crates/bevy_core_pipeline/src/tonemapping/node.rs b/crates/bevy_core_pipeline/src/tonemapping/node.rs index 3a41a22025f12..a300303c5c8ae 100644 --- a/crates/bevy_core_pipeline/src/tonemapping/node.rs +++ b/crates/bevy_core_pipeline/src/tonemapping/node.rs @@ -54,7 +54,7 @@ impl Node for TonemappingNode { Err(_) => return Ok(()), }; - let tonemapping_enabled = tonemapping.map_or(false, |t| t.is_enabled); + let tonemapping_enabled = tonemapping.map_or(false, |t| t.is_enabled()); if !tonemapping_enabled || !target.is_hdr() { return Ok(()); } diff --git a/crates/bevy_core_pipeline/src/tonemapping/tonemapping.wgsl b/crates/bevy_core_pipeline/src/tonemapping/tonemapping.wgsl index a920670318480..3415f9da21dce 100644 --- a/crates/bevy_core_pipeline/src/tonemapping/tonemapping.wgsl +++ b/crates/bevy_core_pipeline/src/tonemapping/tonemapping.wgsl @@ -11,10 +11,14 @@ fn fragment(in: FullscreenVertexOutput) -> @location(0) vec4 { let hdr_color = textureSample(hdr_texture, hdr_sampler, in.uv); var output_rgb = vec3(reinhard_luminance(hdr_color.rgb)); + +#ifdef DEBAND_DITHER output_rgb = pow(output_rgb.rgb, vec3(1.0 / 2.2)); output_rgb = output_rgb + screen_space_dither(in.position.xy); // This conversion back to linear space is required because our output texture format is // SRGB; the GPU will assume our output is linear and will apply an SRGB conversion. output_rgb = pow(output_rgb.rgb, vec3(2.2)); +#endif + return vec4(output_rgb, hdr_color.a); } diff --git a/crates/bevy_pbr/src/material.rs b/crates/bevy_pbr/src/material.rs index d294b7eeb386a..c7c5a93a6e9b2 100644 --- a/crates/bevy_pbr/src/material.rs +++ b/crates/bevy_pbr/src/material.rs @@ -363,9 +363,16 @@ pub fn queue_material_meshes( let mut view_key = MeshPipelineKey::from_msaa_samples(msaa.samples) | MeshPipelineKey::from_hdr(view.hdr); - if let Some(tonemapping) = tonemapping { - if tonemapping.is_enabled && !view.hdr { + if let Some(Tonemapping::Enabled { + is_deband_dither_enabled, + }) = tonemapping + { + if !view.hdr { view_key |= MeshPipelineKey::TONEMAP_IN_SHADER; + + if *is_deband_dither_enabled { + view_key |= MeshPipelineKey::DEBAND_DITHER; + } } } let rangefinder = view.rangefinder3d(); diff --git a/crates/bevy_pbr/src/render/mesh.rs b/crates/bevy_pbr/src/render/mesh.rs index b89e521eff824..73e7eea7b0c5f 100644 --- a/crates/bevy_pbr/src/render/mesh.rs +++ b/crates/bevy_pbr/src/render/mesh.rs @@ -518,6 +518,7 @@ bitflags::bitflags! { const TRANSPARENT_MAIN_PASS = (1 << 0); const HDR = (1 << 1); const TONEMAP_IN_SHADER = (1 << 2); + const DEBAND_DITHER = (1 << 3); 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; } @@ -636,6 +637,11 @@ impl SpecializedMeshPipeline for MeshPipeline { if key.contains(MeshPipelineKey::TONEMAP_IN_SHADER) { shader_defs.push("TONEMAP_IN_SHADER".to_string()); + + // Debanding is tied to tonemapping in the shader, cannot run without it. + if key.contains(MeshPipelineKey::DEBAND_DITHER) { + shader_defs.push("DEBAND_DITHER".to_string()); + } } let format = match key.contains(MeshPipelineKey::HDR) { diff --git a/crates/bevy_pbr/src/render/pbr.wgsl b/crates/bevy_pbr/src/render/pbr.wgsl index 4835c1897c426..6f5d94edfbaf3 100644 --- a/crates/bevy_pbr/src/render/pbr.wgsl +++ b/crates/bevy_pbr/src/render/pbr.wgsl @@ -97,6 +97,8 @@ fn fragment(in: FragmentInput) -> @location(0) vec4 { #ifdef TONEMAP_IN_SHADER output_color = tone_mapping(output_color); +#endif +#ifdef DEBAND_DITHER output_color = dither(output_color, in.frag_coord.xy); #endif return output_color; diff --git a/crates/bevy_pbr/src/render/pbr_functions.wgsl b/crates/bevy_pbr/src/render/pbr_functions.wgsl index 44fd2c97c609b..de2e83e4a4681 100644 --- a/crates/bevy_pbr/src/render/pbr_functions.wgsl +++ b/crates/bevy_pbr/src/render/pbr_functions.wgsl @@ -260,7 +260,9 @@ fn tone_mapping(in: vec4) -> vec4 { // Not needed with sRGB buffer // output_color.rgb = pow(output_color.rgb, vec3(1.0 / 2.2)); } +#endif +#ifdef DEBAND_DITHER fn dither(color: vec4, pos: vec2) -> vec4 { return vec4(color.rgb + screen_space_dither(pos.xy), color.a); } diff --git a/crates/bevy_sprite/src/mesh2d/material.rs b/crates/bevy_sprite/src/mesh2d/material.rs index 2e4e03179b216..9b088e9fc906b 100644 --- a/crates/bevy_sprite/src/mesh2d/material.rs +++ b/crates/bevy_sprite/src/mesh2d/material.rs @@ -328,9 +328,16 @@ pub fn queue_material2d_meshes( let mut view_key = Mesh2dPipelineKey::from_msaa_samples(msaa.samples) | Mesh2dPipelineKey::from_hdr(view.hdr); - if let Some(tonemapping) = tonemapping { - if tonemapping.is_enabled && !view.hdr { + if let Some(Tonemapping::Enabled { + is_deband_dither_enabled, + }) = tonemapping + { + if !view.hdr { view_key |= Mesh2dPipelineKey::TONEMAP_IN_SHADER; + + if *is_deband_dither_enabled { + view_key |= Mesh2dPipelineKey::DEBAND_DITHER; + } } } diff --git a/crates/bevy_sprite/src/mesh2d/mesh.rs b/crates/bevy_sprite/src/mesh2d/mesh.rs index 43a932f53d565..5483c2b98d972 100644 --- a/crates/bevy_sprite/src/mesh2d/mesh.rs +++ b/crates/bevy_sprite/src/mesh2d/mesh.rs @@ -288,6 +288,7 @@ bitflags::bitflags! { const NONE = 0; const HDR = (1 << 0); const TONEMAP_IN_SHADER = (1 << 1); + const DEBAND_DITHER = (1 << 2); 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; } @@ -376,6 +377,11 @@ impl SpecializedMeshPipeline for Mesh2dPipeline { if key.contains(Mesh2dPipelineKey::TONEMAP_IN_SHADER) { shader_defs.push("TONEMAP_IN_SHADER".to_string()); + + // Debanding is tied to tonemapping in the shader, cannot run without it. + if key.contains(Mesh2dPipelineKey::DEBAND_DITHER) { + shader_defs.push("DEBAND_DITHER".to_string()); + } } let vertex_buffer_layout = layout.get_layout(&vertex_attributes)?; diff --git a/crates/bevy_sprite/src/render/mod.rs b/crates/bevy_sprite/src/render/mod.rs index 931f99ac6c97b..f9bc957351e05 100644 --- a/crates/bevy_sprite/src/render/mod.rs +++ b/crates/bevy_sprite/src/render/mod.rs @@ -151,6 +151,7 @@ bitflags::bitflags! { const COLORED = (1 << 0); const HDR = (1 << 1); const TONEMAP_IN_SHADER = (1 << 2); + const DEBAND_DITHER = (1 << 3); const MSAA_RESERVED_BITS = Self::MSAA_MASK_BITS << Self::MSAA_SHIFT_BITS; } } @@ -212,6 +213,11 @@ impl SpecializedRenderPipeline for SpritePipeline { if key.contains(SpritePipelineKey::TONEMAP_IN_SHADER) { shader_defs.push("TONEMAP_IN_SHADER".to_string()); + + // Debanding is tied to tonemapping in the shader, cannot run without it. + if key.contains(SpritePipelineKey::DEBAND_DITHER) { + shader_defs.push("DEBAND_DITHER".to_string()); + } } let format = match key.contains(SpritePipelineKey::HDR) { @@ -508,9 +514,16 @@ pub fn queue_sprites( for (mut transparent_phase, visible_entities, view, tonemapping) in &mut views { let mut view_key = SpritePipelineKey::from_hdr(view.hdr) | msaa_key; - if let Some(tonemapping) = tonemapping { - if tonemapping.is_enabled && !view.hdr { + if let Some(Tonemapping::Enabled { + is_deband_dither_enabled, + }) = tonemapping + { + if !view.hdr { view_key |= SpritePipelineKey::TONEMAP_IN_SHADER; + + if *is_deband_dither_enabled { + view_key |= SpritePipelineKey::DEBAND_DITHER; + } } } let pipeline = pipelines.specialize( From d151c394805ad100d9d2aaad84629f9c11c83fd0 Mon Sep 17 00:00:00 2001 From: Carter Anderson Date: Thu, 10 Nov 2022 22:02:20 -0800 Subject: [PATCH 6/7] specialize tonemapping pipeline to enable dithering --- .../src/core_3d/camera_3d.rs | 2 +- .../bevy_core_pipeline/src/tonemapping/mod.rs | 89 +++++++++++++------ .../src/tonemapping/node.rs | 11 +-- .../bevy_core_pipeline/src/upscaling/mod.rs | 12 +-- .../bevy_core_pipeline/src/upscaling/node.rs | 6 +- crates/bevy_pbr/src/material.rs | 7 +- crates/bevy_sprite/src/mesh2d/material.rs | 7 +- crates/bevy_sprite/src/render/mod.rs | 7 +- 8 files changed, 84 insertions(+), 57 deletions(-) diff --git a/crates/bevy_core_pipeline/src/core_3d/camera_3d.rs b/crates/bevy_core_pipeline/src/core_3d/camera_3d.rs index 5278ca5488b70..057f45c53a72c 100644 --- a/crates/bevy_core_pipeline/src/core_3d/camera_3d.rs +++ b/crates/bevy_core_pipeline/src/core_3d/camera_3d.rs @@ -75,7 +75,7 @@ impl Default for Camera3dBundle { Self { camera_render_graph: CameraRenderGraph::new(crate::core_3d::graph::NAME), tonemapping: Tonemapping::Enabled { - is_deband_dither_enabled: true, + deband_dither: true, }, camera: Default::default(), projection: Default::default(), diff --git a/crates/bevy_core_pipeline/src/tonemapping/mod.rs b/crates/bevy_core_pipeline/src/tonemapping/mod.rs index cf587c508740b..3b417e8c82af9 100644 --- a/crates/bevy_core_pipeline/src/tonemapping/mod.rs +++ b/crates/bevy_core_pipeline/src/tonemapping/mod.rs @@ -12,7 +12,7 @@ use bevy_render::camera::Camera; use bevy_render::extract_component::{ExtractComponent, ExtractComponentPlugin}; use bevy_render::renderer::RenderDevice; use bevy_render::view::ViewTarget; -use bevy_render::{render_resource::*, RenderApp}; +use bevy_render::{render_resource::*, RenderApp, RenderStage}; const TONEMAPPING_SHADER_HANDLE: HandleUntyped = HandleUntyped::weak_from_u64(Shader::TYPE_UUID, 17015368199668024512); @@ -42,7 +42,10 @@ impl Plugin for TonemappingPlugin { app.add_plugin(ExtractComponentPlugin::::default()); if let Ok(render_app) = app.get_sub_app_mut(RenderApp) { - render_app.init_resource::(); + render_app + .init_resource::() + .init_resource::>() + .add_system_to_stage(RenderStage::Queue, queue_view_tonemapping_pipelines); } } } @@ -50,7 +53,40 @@ impl Plugin for TonemappingPlugin { #[derive(Resource)] pub struct TonemappingPipeline { texture_bind_group: BindGroupLayout, - tonemapping_pipeline_id: CachedRenderPipelineId, +} + +#[derive(Copy, Clone, PartialEq, Eq, Hash)] +pub struct TonemappingPipelineKey { + deband_dither: bool, +} + +impl SpecializedRenderPipeline for TonemappingPipeline { + type Key = TonemappingPipelineKey; + + fn specialize(&self, key: Self::Key) -> RenderPipelineDescriptor { + let mut shader_defs = Vec::new(); + if key.deband_dither { + shader_defs.push("DEBAND_DITHER".to_string()); + } + RenderPipelineDescriptor { + label: Some("tonemapping pipeline".into()), + layout: Some(vec![self.texture_bind_group.clone()]), + vertex: fullscreen_shader_vertex_state(), + fragment: Some(FragmentState { + shader: TONEMAPPING_SHADER_HANDLE.typed(), + shader_defs, + entry_point: "fragment".into(), + targets: vec![Some(ColorTargetState { + format: ViewTarget::TEXTURE_FORMAT_HDR, + blend: None, + write_mask: ColorWrites::ALL, + })], + }), + primitive: PrimitiveState::default(), + depth_stencil: None, + multisample: MultisampleState::default(), + } + } } impl FromWorld for TonemappingPipeline { @@ -79,29 +115,32 @@ impl FromWorld for TonemappingPipeline { ], }); - let tonemap_descriptor = RenderPipelineDescriptor { - label: Some("tonemapping pipeline".into()), - layout: Some(vec![tonemap_texture_bind_group.clone()]), - vertex: fullscreen_shader_vertex_state(), - fragment: Some(FragmentState { - shader: TONEMAPPING_SHADER_HANDLE.typed(), - shader_defs: vec![], - entry_point: "fragment".into(), - targets: vec![Some(ColorTargetState { - format: ViewTarget::TEXTURE_FORMAT_HDR, - blend: None, - write_mask: ColorWrites::ALL, - })], - }), - primitive: PrimitiveState::default(), - depth_stencil: None, - multisample: MultisampleState::default(), - }; - - let mut cache = render_world.resource_mut::(); TonemappingPipeline { texture_bind_group: tonemap_texture_bind_group, - tonemapping_pipeline_id: cache.queue_render_pipeline(tonemap_descriptor), + } + } +} + +#[derive(Component)] +pub struct ViewTonemappingPipeline(CachedRenderPipelineId); + +pub fn queue_view_tonemapping_pipelines( + mut commands: Commands, + mut pipeline_cache: ResMut, + mut pipelines: ResMut>, + upscaling_pipeline: Res, + view_targets: Query<(Entity, &Tonemapping)>, +) { + for (entity, tonemapping) in view_targets.iter() { + if let Tonemapping::Enabled { deband_dither } = tonemapping { + let key = TonemappingPipelineKey { + deband_dither: *deband_dither, + }; + let pipeline = pipelines.specialize(&mut pipeline_cache, &upscaling_pipeline, key); + + commands + .entity(entity) + .insert(ViewTonemappingPipeline(pipeline)); } } } @@ -112,7 +151,7 @@ pub enum Tonemapping { #[default] Disabled, Enabled { - is_deband_dither_enabled: bool, + deband_dither: bool, }, } diff --git a/crates/bevy_core_pipeline/src/tonemapping/node.rs b/crates/bevy_core_pipeline/src/tonemapping/node.rs index a300303c5c8ae..f9edf882c73fa 100644 --- a/crates/bevy_core_pipeline/src/tonemapping/node.rs +++ b/crates/bevy_core_pipeline/src/tonemapping/node.rs @@ -1,6 +1,6 @@ use std::sync::Mutex; -use crate::tonemapping::{Tonemapping, TonemappingPipeline}; +use crate::tonemapping::{TonemappingPipeline, ViewTonemappingPipeline}; use bevy_ecs::prelude::*; use bevy_ecs::query::QueryState; use bevy_render::{ @@ -15,7 +15,7 @@ use bevy_render::{ }; pub struct TonemappingNode { - query: QueryState<(&'static ViewTarget, Option<&'static Tonemapping>), With>, + query: QueryState<(&'static ViewTarget, &'static ViewTonemappingPipeline), With>, cached_texture_bind_group: Mutex>, } @@ -54,14 +54,11 @@ impl Node for TonemappingNode { Err(_) => return Ok(()), }; - let tonemapping_enabled = tonemapping.map_or(false, |t| t.is_enabled()); - if !tonemapping_enabled || !target.is_hdr() { + if !target.is_hdr() { return Ok(()); } - let pipeline = match pipeline_cache - .get_render_pipeline(tonemapping_pipeline.tonemapping_pipeline_id) - { + let pipeline = match pipeline_cache.get_render_pipeline(tonemapping.0) { Some(pipeline) => pipeline, None => return Ok(()), }; diff --git a/crates/bevy_core_pipeline/src/upscaling/mod.rs b/crates/bevy_core_pipeline/src/upscaling/mod.rs index 565ebad7c15f0..139d060eaf7c7 100644 --- a/crates/bevy_core_pipeline/src/upscaling/mod.rs +++ b/crates/bevy_core_pipeline/src/upscaling/mod.rs @@ -31,7 +31,7 @@ impl Plugin for UpscalingPlugin { render_app .init_resource::() .init_resource::>() - .add_system_to_stage(RenderStage::Queue, queue_upscaling_bind_groups); + .add_system_to_stage(RenderStage::Queue, queue_view_upscaling_pipelines); } } } @@ -110,11 +110,9 @@ impl SpecializedRenderPipeline for UpscalingPipeline { } #[derive(Component)] -pub struct UpscalingTarget { - pub pipeline: CachedRenderPipelineId, -} +pub struct ViewUpscalingPipeline(CachedRenderPipelineId); -fn queue_upscaling_bind_groups( +fn queue_view_upscaling_pipelines( mut commands: Commands, mut pipeline_cache: ResMut, mut pipelines: ResMut>, @@ -128,6 +126,8 @@ fn queue_upscaling_bind_groups( }; let pipeline = pipelines.specialize(&mut pipeline_cache, &upscaling_pipeline, key); - commands.entity(entity).insert(UpscalingTarget { pipeline }); + commands + .entity(entity) + .insert(ViewUpscalingPipeline(pipeline)); } } diff --git a/crates/bevy_core_pipeline/src/upscaling/node.rs b/crates/bevy_core_pipeline/src/upscaling/node.rs index fd785a46477fa..895c3e5e1b0a2 100644 --- a/crates/bevy_core_pipeline/src/upscaling/node.rs +++ b/crates/bevy_core_pipeline/src/upscaling/node.rs @@ -13,10 +13,10 @@ use bevy_render::{ view::{ExtractedView, ViewTarget}, }; -use super::{UpscalingPipeline, UpscalingTarget}; +use super::{UpscalingPipeline, ViewUpscalingPipeline}; pub struct UpscalingNode { - query: QueryState<(&'static ViewTarget, &'static UpscalingTarget), With>, + query: QueryState<(&'static ViewTarget, &'static ViewUpscalingPipeline), With>, cached_texture_bind_group: Mutex>, } @@ -89,7 +89,7 @@ impl Node for UpscalingNode { } }; - let pipeline = match pipeline_cache.get_render_pipeline(upscaling_target.pipeline) { + let pipeline = match pipeline_cache.get_render_pipeline(upscaling_target.0) { Some(pipeline) => pipeline, None => return Ok(()), }; diff --git a/crates/bevy_pbr/src/material.rs b/crates/bevy_pbr/src/material.rs index c7c5a93a6e9b2..2dc0e0f3ed59e 100644 --- a/crates/bevy_pbr/src/material.rs +++ b/crates/bevy_pbr/src/material.rs @@ -363,14 +363,11 @@ pub fn queue_material_meshes( let mut view_key = MeshPipelineKey::from_msaa_samples(msaa.samples) | MeshPipelineKey::from_hdr(view.hdr); - if let Some(Tonemapping::Enabled { - is_deband_dither_enabled, - }) = tonemapping - { + if let Some(Tonemapping::Enabled { deband_dither }) = tonemapping { if !view.hdr { view_key |= MeshPipelineKey::TONEMAP_IN_SHADER; - if *is_deband_dither_enabled { + if *deband_dither { view_key |= MeshPipelineKey::DEBAND_DITHER; } } diff --git a/crates/bevy_sprite/src/mesh2d/material.rs b/crates/bevy_sprite/src/mesh2d/material.rs index 9b088e9fc906b..72dac1b39095e 100644 --- a/crates/bevy_sprite/src/mesh2d/material.rs +++ b/crates/bevy_sprite/src/mesh2d/material.rs @@ -328,14 +328,11 @@ pub fn queue_material2d_meshes( let mut view_key = Mesh2dPipelineKey::from_msaa_samples(msaa.samples) | Mesh2dPipelineKey::from_hdr(view.hdr); - if let Some(Tonemapping::Enabled { - is_deband_dither_enabled, - }) = tonemapping - { + if let Some(Tonemapping::Enabled { deband_dither }) = tonemapping { if !view.hdr { view_key |= Mesh2dPipelineKey::TONEMAP_IN_SHADER; - if *is_deband_dither_enabled { + if *deband_dither { view_key |= Mesh2dPipelineKey::DEBAND_DITHER; } } diff --git a/crates/bevy_sprite/src/render/mod.rs b/crates/bevy_sprite/src/render/mod.rs index f9bc957351e05..8b71558813973 100644 --- a/crates/bevy_sprite/src/render/mod.rs +++ b/crates/bevy_sprite/src/render/mod.rs @@ -514,14 +514,11 @@ pub fn queue_sprites( for (mut transparent_phase, visible_entities, view, tonemapping) in &mut views { let mut view_key = SpritePipelineKey::from_hdr(view.hdr) | msaa_key; - if let Some(Tonemapping::Enabled { - is_deband_dither_enabled, - }) = tonemapping - { + if let Some(Tonemapping::Enabled { deband_dither }) = tonemapping { if !view.hdr { view_key |= SpritePipelineKey::TONEMAP_IN_SHADER; - if *is_deband_dither_enabled { + if *deband_dither { view_key |= SpritePipelineKey::DEBAND_DITHER; } } From a8627ee6d3484637713c46da6f13bf25e1b189cf Mon Sep 17 00:00:00 2001 From: Aevyrie Roessler Date: Fri, 11 Nov 2022 11:36:35 -0800 Subject: [PATCH 7/7] Remove unecessary vec3 cast in tonemapping shader --- crates/bevy_core_pipeline/src/tonemapping/tonemapping.wgsl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/bevy_core_pipeline/src/tonemapping/tonemapping.wgsl b/crates/bevy_core_pipeline/src/tonemapping/tonemapping.wgsl index 3415f9da21dce..a4bc5d4be4364 100644 --- a/crates/bevy_core_pipeline/src/tonemapping/tonemapping.wgsl +++ b/crates/bevy_core_pipeline/src/tonemapping/tonemapping.wgsl @@ -10,7 +10,7 @@ var hdr_sampler: sampler; fn fragment(in: FullscreenVertexOutput) -> @location(0) vec4 { let hdr_color = textureSample(hdr_texture, hdr_sampler, in.uv); - var output_rgb = vec3(reinhard_luminance(hdr_color.rgb)); + var output_rgb = reinhard_luminance(hdr_color.rgb); #ifdef DEBAND_DITHER output_rgb = pow(output_rgb.rgb, vec3(1.0 / 2.2));