From 3a86a0e6ef4d2e270c507e16c977b9a9162a6479 Mon Sep 17 00:00:00 2001 From: Jakob Hellermann Date: Thu, 23 Dec 2021 19:38:33 +0100 Subject: [PATCH 01/26] add separate tonemapping and upscaling pass --- crates/bevy_core_pipeline/Cargo.toml | 2 +- crates/bevy_core_pipeline/src/lib.rs | 87 ++++++++++- .../bevy_core_pipeline/src/tonemapping/mod.rs | 147 ++++++++++++++++++ .../src/tonemapping/node.rs | 80 ++++++++++ .../src/tonemapping/tonemapping.wgsl | 54 +++++++ .../src/tonemapping/tonemapping_shared.wgsl | 29 ++++ .../bevy_core_pipeline/src/upscaling/mod.rs | 142 +++++++++++++++++ .../bevy_core_pipeline/src/upscaling/node.rs | 80 ++++++++++ .../src/upscaling/upscaling.wgsl | 25 +++ crates/bevy_pbr/src/material.rs | 1 + crates/bevy_pbr/src/render/mesh.rs | 4 +- crates/bevy_pbr/src/render/pbr.wgsl | 41 ----- crates/bevy_pbr/src/wireframe.rs | 5 +- crates/bevy_render/src/view/mod.rs | 111 ++++++++++--- crates/bevy_sprite/src/render/mod.rs | 6 +- crates/bevy_ui/src/render/render_pass.rs | 2 +- 16 files changed, 746 insertions(+), 70 deletions(-) create mode 100644 crates/bevy_core_pipeline/src/tonemapping/mod.rs create mode 100644 crates/bevy_core_pipeline/src/tonemapping/node.rs create mode 100644 crates/bevy_core_pipeline/src/tonemapping/tonemapping.wgsl create mode 100644 crates/bevy_core_pipeline/src/tonemapping/tonemapping_shared.wgsl create mode 100644 crates/bevy_core_pipeline/src/upscaling/mod.rs create mode 100644 crates/bevy_core_pipeline/src/upscaling/node.rs create mode 100644 crates/bevy_core_pipeline/src/upscaling/upscaling.wgsl diff --git a/crates/bevy_core_pipeline/Cargo.toml b/crates/bevy_core_pipeline/Cargo.toml index 4b8b862820460..671645e40889c 100644 --- a/crates/bevy_core_pipeline/Cargo.toml +++ b/crates/bevy_core_pipeline/Cargo.toml @@ -23,4 +23,4 @@ bevy_core = { path = "../bevy_core", version = "0.7.0-dev" } bevy_ecs = { path = "../bevy_ecs", version = "0.7.0-dev" } bevy_render = { path = "../bevy_render", version = "0.7.0-dev" } bevy_utils = { path = "../bevy_utils", version = "0.7.0-dev" } - +bevy_reflect = { path = "../bevy_reflect", version = "0.7.0-dev" } \ No newline at end of file diff --git a/crates/bevy_core_pipeline/src/lib.rs b/crates/bevy_core_pipeline/src/lib.rs index bb915965c73a2..cc1423b6f262c 100644 --- a/crates/bevy_core_pipeline/src/lib.rs +++ b/crates/bevy_core_pipeline/src/lib.rs @@ -3,6 +3,8 @@ mod clear_pass_driver; mod main_pass_2d; mod main_pass_3d; mod main_pass_driver; +mod tonemapping; +mod upscaling; pub mod prelude { #[doc(hidden)] @@ -36,6 +38,10 @@ use bevy_render::{ view::{ExtractedView, Msaa, ViewDepthTexture}, RenderApp, RenderStage, RenderWorld, }; +use tonemapping::TonemappingNode; +use tonemapping::TonemappingPlugin; +use upscaling::UpscalingNode; +use upscaling::UpscalingPlugin; /// When used as a resource, sets the color that is used to clear the screen between frames. /// @@ -82,6 +88,8 @@ pub mod draw_2d_graph { } pub mod node { pub const MAIN_PASS: &str = "main_pass"; + pub const TONEMAPPING: &str = "tonemapping"; + pub const UPSCALING: &str = "upscaling"; } } @@ -92,6 +100,8 @@ pub mod draw_3d_graph { } pub mod node { pub const MAIN_PASS: &str = "main_pass"; + pub const TONEMAPPING: &str = "tonemapping"; + pub const UPSCALING: &str = "upscaling"; } } @@ -113,7 +123,9 @@ pub enum CorePipelineRenderSystems { impl Plugin for CorePipelinePlugin { fn build(&self, app: &mut App) { app.init_resource::() - .init_resource::(); + .init_resource::() + .add_plugin(TonemappingPlugin) + .add_plugin(UpscalingPlugin); let render_app = match app.get_sub_app_mut(RenderApp) { Ok(render_app) => render_app, @@ -144,10 +156,17 @@ impl Plugin for CorePipelinePlugin { let clear_pass_node = ClearPassNode::new(&mut render_app.world); let pass_node_2d = MainPass2dNode::new(&mut render_app.world); + let tonemapping_2d = TonemappingNode::new(&mut render_app.world); + let upscaling_2d = UpscalingNode::new(&mut render_app.world); + let pass_node_3d = MainPass3dNode::new(&mut render_app.world); + let tonemapping_3d = TonemappingNode::new(&mut render_app.world); + let upscaling_3d = UpscalingNode::new(&mut render_app.world); let mut graph = render_app.world.resource_mut::(); let mut draw_2d_graph = RenderGraph::default(); + draw_2d_graph.add_node(draw_2d_graph::node::TONEMAPPING, tonemapping_2d); + draw_2d_graph.add_node(draw_2d_graph::node::UPSCALING, upscaling_2d); draw_2d_graph.add_node(draw_2d_graph::node::MAIN_PASS, pass_node_2d); let input_node_id = draw_2d_graph.set_input(vec![SlotInfo::new( draw_2d_graph::input::VIEW_ENTITY, @@ -161,14 +180,49 @@ impl Plugin for CorePipelinePlugin { MainPass2dNode::IN_VIEW, ) .unwrap(); + + draw_2d_graph + .add_node_edge( + draw_2d_graph::node::MAIN_PASS, + draw_2d_graph::node::TONEMAPPING, + ) + .unwrap(); + draw_2d_graph + .add_slot_edge( + input_node_id, + draw_2d_graph::input::VIEW_ENTITY, + draw_2d_graph::node::TONEMAPPING, + TonemappingNode::IN_VIEW, + ) + .unwrap(); + + draw_2d_graph + .add_node_edge( + draw_2d_graph::node::TONEMAPPING, + draw_2d_graph::node::UPSCALING, + ) + .unwrap(); + draw_2d_graph + .add_slot_edge( + input_node_id, + draw_2d_graph::input::VIEW_ENTITY, + draw_2d_graph::node::UPSCALING, + UpscalingNode::IN_VIEW, + ) + .unwrap(); + graph.add_sub_graph(draw_2d_graph::NAME, draw_2d_graph); let mut draw_3d_graph = RenderGraph::default(); draw_3d_graph.add_node(draw_3d_graph::node::MAIN_PASS, pass_node_3d); + draw_3d_graph.add_node(draw_3d_graph::node::TONEMAPPING, tonemapping_3d); + draw_3d_graph.add_node(draw_3d_graph::node::UPSCALING, upscaling_3d); + let input_node_id = draw_3d_graph.set_input(vec![SlotInfo::new( draw_3d_graph::input::VIEW_ENTITY, SlotType::Entity, )]); + draw_3d_graph .add_slot_edge( input_node_id, @@ -177,6 +231,37 @@ impl Plugin for CorePipelinePlugin { MainPass3dNode::IN_VIEW, ) .unwrap(); + + draw_3d_graph + .add_node_edge( + draw_3d_graph::node::MAIN_PASS, + draw_3d_graph::node::TONEMAPPING, + ) + .unwrap(); + draw_3d_graph + .add_slot_edge( + input_node_id, + draw_3d_graph::input::VIEW_ENTITY, + draw_3d_graph::node::TONEMAPPING, + TonemappingNode::IN_VIEW, + ) + .unwrap(); + + draw_3d_graph + .add_node_edge( + draw_3d_graph::node::TONEMAPPING, + draw_3d_graph::node::UPSCALING, + ) + .unwrap(); + draw_3d_graph + .add_slot_edge( + input_node_id, + draw_3d_graph::input::VIEW_ENTITY, + draw_3d_graph::node::UPSCALING, + UpscalingNode::IN_VIEW, + ) + .unwrap(); + graph.add_sub_graph(draw_3d_graph::NAME, draw_3d_graph); let mut clear_graph = RenderGraph::default(); diff --git a/crates/bevy_core_pipeline/src/tonemapping/mod.rs b/crates/bevy_core_pipeline/src/tonemapping/mod.rs new file mode 100644 index 0000000000000..f0ccf4780c928 --- /dev/null +++ b/crates/bevy_core_pipeline/src/tonemapping/mod.rs @@ -0,0 +1,147 @@ +mod node; + +pub use node::TonemappingNode; + +use bevy_app::prelude::*; +use bevy_asset::{load_internal_asset, HandleUntyped}; +use bevy_ecs::prelude::*; +use bevy_render::renderer::RenderDevice; +use bevy_render::texture::BevyDefault; +use bevy_render::view::ViewTarget; +use bevy_render::{render_resource::*, RenderApp, RenderStage}; + +use bevy_reflect::TypeUuid; + +const TONEMAPPING_SHADER_HANDLE: HandleUntyped = + HandleUntyped::weak_from_u64(Shader::TYPE_UUID, 17015368199668024512); + +pub struct TonemappingPlugin; + +impl Plugin for TonemappingPlugin { + fn build(&self, app: &mut App) { + load_internal_asset!( + app, + TONEMAPPING_SHADER_HANDLE, + "tonemapping.wgsl", + Shader::from_wgsl + ); + + let render_app = match app.get_sub_app_mut(RenderApp) { + Ok(render_app) => render_app, + Err(_) => return, + }; + + render_app + .init_resource::() + .init_resource::>() + .add_system_to_stage(RenderStage::Queue, queue_tonemapping_bind_groups); + } +} + +pub struct TonemappingPipeline { + hdr_texture_bind_group: BindGroupLayout, +} + +impl FromWorld for TonemappingPipeline { + fn from_world(render_world: &mut World) -> Self { + let render_device = render_world.get_resource::().unwrap(); + + let hdr_texture_bind_group = + render_device.create_bind_group_layout(&BindGroupLayoutDescriptor { + label: Some("tonemapping_hdr_texture_bind_group_layout"), + entries: &[ + BindGroupLayoutEntry { + binding: 0, + visibility: ShaderStages::FRAGMENT, + ty: BindingType::Texture { + sample_type: TextureSampleType::Float { filterable: false }, + view_dimension: TextureViewDimension::D2, + multisampled: false, + }, + count: None, + }, + BindGroupLayoutEntry { + binding: 1, + visibility: ShaderStages::FRAGMENT, + ty: BindingType::Sampler(SamplerBindingType::NonFiltering), + count: None, + }, + ], + }); + + TonemappingPipeline { + hdr_texture_bind_group, + } + } +} + +impl SpecializedPipeline for TonemappingPipeline { + type Key = (); + + fn specialize(&self, _: Self::Key) -> RenderPipelineDescriptor { + RenderPipelineDescriptor { + label: Some("tonemapping pipeline".into()), + layout: Some(vec![self.hdr_texture_bind_group.clone()]), + vertex: VertexState { + shader: TONEMAPPING_SHADER_HANDLE.typed(), + shader_defs: vec![], + entry_point: "vs_main".into(), + buffers: vec![], + }, + fragment: Some(FragmentState { + shader: TONEMAPPING_SHADER_HANDLE.typed(), + shader_defs: vec![], + entry_point: "fs_main".into(), + targets: vec![ColorTargetState { + format: TextureFormat::bevy_default(), + blend: None, + write_mask: ColorWrites::ALL, + }], + }), + primitive: PrimitiveState::default(), + depth_stencil: None, + multisample: MultisampleState::default(), + } + } +} + +#[derive(Component)] +pub struct TonemappingTarget { + pub hdr_texture_bind_group: BindGroup, + pub pipeline: CachedPipelineId, +} + +fn queue_tonemapping_bind_groups( + mut commands: Commands, + render_device: Res, + mut render_pipeline_cache: ResMut, + mut pipelines: ResMut>, + tonemapping_pipeline: Res, + view_targets: Query<(Entity, &ViewTarget)>, +) { + for (entity, target) in view_targets.iter() { + let pipeline = pipelines.specialize(&mut render_pipeline_cache, &tonemapping_pipeline, ()); + + let sampler = render_device.create_sampler(&SamplerDescriptor::default()); + + let bind_group = render_device.create_bind_group(&BindGroupDescriptor { + label: None, + layout: &tonemapping_pipeline.hdr_texture_bind_group, + entries: &[ + BindGroupEntry { + binding: 0, + resource: BindingResource::TextureView(&target.hdr_texture), + }, + BindGroupEntry { + binding: 1, + resource: BindingResource::Sampler(&sampler), + }, + ], + }); + + commands.entity(entity).insert(TonemappingTarget { + hdr_texture_bind_group: bind_group, + pipeline, + }); + } +} diff --git a/crates/bevy_core_pipeline/src/tonemapping/node.rs b/crates/bevy_core_pipeline/src/tonemapping/node.rs new file mode 100644 index 0000000000000..b84c83eba7acb --- /dev/null +++ b/crates/bevy_core_pipeline/src/tonemapping/node.rs @@ -0,0 +1,80 @@ +use bevy_ecs::prelude::*; +use bevy_ecs::query::QueryState; +use bevy_render::{ + render_graph::{Node, NodeRunError, RenderGraphContext, SlotInfo, SlotType}, + render_resource::{ + LoadOp, Operations, RenderPassColorAttachment, RenderPassDescriptor, RenderPipelineCache, + }, + renderer::RenderContext, + view::{ExtractedView, ViewTarget}, +}; + +use super::TonemappingTarget; + +pub struct TonemappingNode { + query: QueryState<(&'static ViewTarget, &'static TonemappingTarget), With>, +} + +impl TonemappingNode { + pub const IN_VIEW: &'static str = "view"; + + pub fn new(world: &mut World) -> Self { + Self { + query: QueryState::new(world), + } + } +} + +impl Node for TonemappingNode { + fn input(&self) -> Vec { + vec![SlotInfo::new(TonemappingNode::IN_VIEW, SlotType::Entity)] + } + + fn update(&mut self, world: &mut World) { + self.query.update_archetypes(world); + } + + fn run( + &self, + graph: &mut RenderGraphContext, + render_context: &mut RenderContext, + world: &World, + ) -> Result<(), NodeRunError> { + let view_entity = graph.get_input_entity(Self::IN_VIEW)?; + + let render_pipeline_cache = world.get_resource::().unwrap(); + + let (target, tonemapping_target) = match self.query.get_manual(world, view_entity) { + Ok(query) => query, + Err(_) => return Ok(()), + }; + + let pipeline = match render_pipeline_cache.get(tonemapping_target.pipeline) { + Some(pipeline) => pipeline, + None => return Ok(()), + }; + + let pass_descriptor = RenderPassDescriptor { + label: Some("tonemapping_pass"), + color_attachments: &[RenderPassColorAttachment { + view: &target.ldr_texture, + resolve_target: None, + ops: Operations { + load: LoadOp::Clear(Default::default()), // TODO shouldn't need to be cleared + store: true, + }, + }], + depth_stencil_attachment: None, + }; + + let mut render_pass = render_context + .command_encoder + .begin_render_pass(&pass_descriptor); + + render_pass.set_pipeline(pipeline); + render_pass.set_bind_group(0, &tonemapping_target.hdr_texture_bind_group, &[]); + render_pass.draw(0..3, 0..1); + + Ok(()) + } +} diff --git a/crates/bevy_core_pipeline/src/tonemapping/tonemapping.wgsl b/crates/bevy_core_pipeline/src/tonemapping/tonemapping.wgsl new file mode 100644 index 0000000000000..ec18f6d215d6c --- /dev/null +++ b/crates/bevy_core_pipeline/src/tonemapping/tonemapping.wgsl @@ -0,0 +1,54 @@ +struct VertexOutput { + [[builtin(position)]] + position: vec4; + [[location(0)]] + uv: vec2; +}; + +[[stage(vertex)]] +fn vs_main([[builtin(vertex_index)]] in_vertex_index: u32) -> VertexOutput { + let uv = vec2(f32((in_vertex_index << 1u) & 2u), f32(in_vertex_index & 2u)); + let position = vec4(uv * vec2(2.0, -2.0) + vec2(-1.0, 1.0), 0.0, 1.0); + return VertexOutput(position, uv); +} + + +[[group(0), binding(0)]] +var hdr_texture: texture_2d; +[[group(0), binding(1)]] +var hdr_sampler: sampler; + +// from https://64.github.io/tonemapping/ +// reinhard on RGB oversaturates colors +fn reinhard(color: vec3) -> vec3 { + return color / (1.0 + color); +} + +fn reinhard_extended(color: vec3, max_white: f32) -> vec3 { + let numerator = color * (1.0 + (color / vec3(max_white * max_white))); + return numerator / (1.0 + color); +} + +// luminance coefficients from Rec. 709. +// https://en.wikipedia.org/wiki/Rec._709 +fn luminance(v: vec3) -> f32 { + return dot(v, vec3(0.2126, 0.7152, 0.0722)); +} + +fn change_luminance(c_in: vec3, l_out: f32) -> vec3 { + let l_in = luminance(c_in); + return c_in * (l_out / l_in); +} + +fn reinhard_luminance(color: vec3) -> vec3 { + let l_old = luminance(color); + let l_new = l_old / (1.0 + l_old); + return change_luminance(color, l_new); +} + +[[stage(fragment)]] +fn fs_main(in: VertexOutput) -> [[location(0)]] vec4 { + let hdr_color = textureSample(hdr_texture, hdr_sampler, in.uv); + + return vec4(reinhard_luminance(hdr_color.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 new file mode 100644 index 0000000000000..34ea21d8d5511 --- /dev/null +++ b/crates/bevy_core_pipeline/src/tonemapping/tonemapping_shared.wgsl @@ -0,0 +1,29 @@ +#define_import_path bevy_core_pipeline::tonemapping + +// from https://64.github.io/tonemapping/ +// reinhard on RGB oversaturates colors +fn tonemapping_reinhard(color: vec3) -> vec3 { + return color / (1.0 + color); +} + +fn tonemapping_reinhard_extended(color: vec3, max_white: f32) -> vec3 { + let numerator = color * (1.0 + (color / vec3(max_white * max_white))); + return numerator / (1.0 + color); +} + +// luminance coefficients from Rec. 709. +// https://en.wikipedia.org/wiki/Rec._709 +fn tonemapping_luminance(v: vec3) -> f32 { + return dot(v, vec3(0.2126, 0.7152, 0.0722)); +} + +fn tonemapping_change_luminance(c_in: vec3, l_out: f32) -> vec3 { + let l_in = tonemapping_luminance(c_in); + return c_in * (l_out / l_in); +} + +fn reinhard_luminance(color: vec3) -> vec3 { + let l_old = tonemapping_luminance(color); + let l_new = l_old / (1.0 + l_old); + return tonemapping_change_luminance(color, l_new); +} \ No newline at end of file diff --git a/crates/bevy_core_pipeline/src/upscaling/mod.rs b/crates/bevy_core_pipeline/src/upscaling/mod.rs new file mode 100644 index 0000000000000..57c7f6be6905e --- /dev/null +++ b/crates/bevy_core_pipeline/src/upscaling/mod.rs @@ -0,0 +1,142 @@ +mod node; + +pub use node::UpscalingNode; + +use bevy_app::prelude::*; +use bevy_asset::{load_internal_asset, HandleUntyped}; +use bevy_ecs::prelude::*; +use bevy_render::renderer::RenderDevice; +use bevy_render::texture::BevyDefault; +use bevy_render::view::ViewTarget; +use bevy_render::{render_resource::*, RenderApp, RenderStage}; + +use bevy_reflect::TypeUuid; + +const UPSCALING_SHADER_HANDLE: HandleUntyped = + HandleUntyped::weak_from_u64(Shader::TYPE_UUID, 14589267395627146578); + +pub struct UpscalingPlugin; + +impl Plugin for UpscalingPlugin { + fn build(&self, app: &mut App) { + load_internal_asset!( + app, + UPSCALING_SHADER_HANDLE, + "upscaling.wgsl", + Shader::from_wgsl + ); + + app.sub_app_mut(RenderApp) + .init_resource::() + .init_resource::>() + .add_system_to_stage(RenderStage::Queue, queue_upscaling_bind_groups); + } +} + +pub struct UpscalingPipeline { + ldr_texture_bind_group: BindGroupLayout, +} + +impl FromWorld for UpscalingPipeline { + fn from_world(render_world: &mut World) -> Self { + let render_device = render_world.get_resource::().unwrap(); + + let hdr_texture_bind_group = + render_device.create_bind_group_layout(&BindGroupLayoutDescriptor { + label: Some("upscaling_ldr_texture_bind_group_layout"), + entries: &[ + BindGroupLayoutEntry { + binding: 0, + visibility: ShaderStages::FRAGMENT, + ty: BindingType::Texture { + sample_type: TextureSampleType::Float { filterable: false }, + view_dimension: TextureViewDimension::D2, + multisampled: false, + }, + count: None, + }, + BindGroupLayoutEntry { + binding: 1, + visibility: ShaderStages::FRAGMENT, + ty: BindingType::Sampler(SamplerBindingType::NonFiltering), + count: None, + }, + ], + }); + + UpscalingPipeline { + ldr_texture_bind_group: hdr_texture_bind_group, + } + } +} + +impl SpecializedPipeline for UpscalingPipeline { + type Key = (); + + fn specialize(&self, _: Self::Key) -> RenderPipelineDescriptor { + RenderPipelineDescriptor { + label: Some("upscaling pipeline".into()), + layout: Some(vec![self.ldr_texture_bind_group.clone()]), + vertex: VertexState { + shader: UPSCALING_SHADER_HANDLE.typed(), + shader_defs: vec![], + entry_point: "vs_main".into(), + buffers: vec![], + }, + fragment: Some(FragmentState { + shader: UPSCALING_SHADER_HANDLE.typed(), + shader_defs: vec![], + entry_point: "fs_main".into(), + targets: vec![ColorTargetState { + format: TextureFormat::bevy_default(), + blend: None, + write_mask: ColorWrites::ALL, + }], + }), + primitive: PrimitiveState::default(), + depth_stencil: None, + multisample: MultisampleState::default(), + } + } +} + +#[derive(Component)] +pub struct UpscalingTarget { + pub ldr_texture_bind_group: BindGroup, + pub pipeline: CachedPipelineId, +} + +fn queue_upscaling_bind_groups( + mut commands: Commands, + render_device: Res, + mut render_pipeline_cache: ResMut, + mut pipelines: ResMut>, + upscaling_pipeline: Res, + view_targets: Query<(Entity, &ViewTarget)>, +) { + for (entity, target) in view_targets.iter() { + let pipeline = pipelines.specialize(&mut render_pipeline_cache, &upscaling_pipeline, ()); + + let sampler = render_device.create_sampler(&SamplerDescriptor::default()); + + let bind_group = render_device.create_bind_group(&BindGroupDescriptor { + label: None, + layout: &upscaling_pipeline.ldr_texture_bind_group, + entries: &[ + BindGroupEntry { + binding: 0, + resource: BindingResource::TextureView(&target.ldr_texture), + }, + BindGroupEntry { + binding: 1, + resource: BindingResource::Sampler(&sampler), + }, + ], + }); + + commands.entity(entity).insert(UpscalingTarget { + ldr_texture_bind_group: bind_group, + pipeline, + }); + } +} diff --git a/crates/bevy_core_pipeline/src/upscaling/node.rs b/crates/bevy_core_pipeline/src/upscaling/node.rs new file mode 100644 index 0000000000000..e67a788949356 --- /dev/null +++ b/crates/bevy_core_pipeline/src/upscaling/node.rs @@ -0,0 +1,80 @@ +use bevy_ecs::prelude::*; +use bevy_ecs::query::QueryState; +use bevy_render::{ + render_graph::{Node, NodeRunError, RenderGraphContext, SlotInfo, SlotType}, + render_resource::{ + LoadOp, Operations, RenderPassColorAttachment, RenderPassDescriptor, RenderPipelineCache, + }, + renderer::RenderContext, + view::{ExtractedView, ViewTarget}, +}; + +use super::UpscalingTarget; + +pub struct UpscalingNode { + query: QueryState<(&'static ViewTarget, &'static UpscalingTarget), With>, +} + +impl UpscalingNode { + pub const IN_VIEW: &'static str = "view"; + + pub fn new(world: &mut World) -> Self { + Self { + query: QueryState::new(world), + } + } +} + +impl Node for UpscalingNode { + fn input(&self) -> Vec { + vec![SlotInfo::new(UpscalingNode::IN_VIEW, SlotType::Entity)] + } + + fn update(&mut self, world: &mut World) { + self.query.update_archetypes(world); + } + + fn run( + &self, + graph: &mut RenderGraphContext, + render_context: &mut RenderContext, + world: &World, + ) -> Result<(), NodeRunError> { + let view_entity = graph.get_input_entity(Self::IN_VIEW)?; + + let render_pipeline_cache = world.get_resource::().unwrap(); + + let (target, upscaling_target) = match self.query.get_manual(world, view_entity) { + Ok(query) => query, + Err(_) => return Ok(()), + }; + + let pipeline = match render_pipeline_cache.get(upscaling_target.pipeline) { + Some(pipeline) => pipeline, + None => return Ok(()), + }; + + let pass_descriptor = RenderPassDescriptor { + label: Some("upscaling_pass"), + color_attachments: &[RenderPassColorAttachment { + view: &target.out_texture, + resolve_target: None, + ops: Operations { + load: LoadOp::Clear(Default::default()), // TODO dont_care + store: true, + }, + }], + depth_stencil_attachment: None, + }; + + let mut render_pass = render_context + .command_encoder + .begin_render_pass(&pass_descriptor); + + render_pass.set_pipeline(pipeline); + render_pass.set_bind_group(0, &upscaling_target.ldr_texture_bind_group, &[]); + render_pass.draw(0..3, 0..1); + + Ok(()) + } +} diff --git a/crates/bevy_core_pipeline/src/upscaling/upscaling.wgsl b/crates/bevy_core_pipeline/src/upscaling/upscaling.wgsl new file mode 100644 index 0000000000000..e47f0ceb5e73c --- /dev/null +++ b/crates/bevy_core_pipeline/src/upscaling/upscaling.wgsl @@ -0,0 +1,25 @@ +struct VertexOutput { + [[builtin(position)]] + position: vec4; + [[location(0)]] + uv: vec2; +}; + +[[stage(vertex)]] +fn vs_main([[builtin(vertex_index)]] in_vertex_index: u32) -> VertexOutput { + let uv = vec2(f32((in_vertex_index << 1u) & 2u), f32(in_vertex_index & 2u)); + let position = vec4(uv * vec2(2.0, -2.0) + vec2(-1.0, 1.0), 0.0, 1.0); + return VertexOutput(position, uv); +} + +[[group(0), binding(0)]] +var hdr_texture: texture_2d; +[[group(0), binding(1)]] +var hdr_sampler: sampler; + +[[stage(fragment)]] +fn fs_main(in: VertexOutput) -> [[location(0)]] vec4 { + let hdr_color = textureSample(hdr_texture, hdr_sampler, in.uv); + + return hdr_color; +} diff --git a/crates/bevy_pbr/src/material.rs b/crates/bevy_pbr/src/material.rs index 537026524b51d..ff664ba31e68e 100644 --- a/crates/bevy_pbr/src/material.rs +++ b/crates/bevy_pbr/src/material.rs @@ -348,6 +348,7 @@ pub fn queue_material_meshes( if let Some(mesh) = render_meshes.get(mesh_handle) { let mut mesh_key = MeshPipelineKey::from_primitive_topology(mesh.primitive_topology) + | MeshPipelineKey::from_hdr(view.hdr) | msaa_key; let alpha_mode = M::alpha_mode(material); if let AlphaMode::Blend = alpha_mode { diff --git a/crates/bevy_pbr/src/render/mesh.rs b/crates/bevy_pbr/src/render/mesh.rs index 8d00086881215..03e41423c23d3 100644 --- a/crates/bevy_pbr/src/render/mesh.rs +++ b/crates/bevy_pbr/src/render/mesh.rs @@ -18,7 +18,7 @@ use bevy_render::{ render_resource::{std140::AsStd140, *}, renderer::{RenderDevice, RenderQueue}, texture::{BevyDefault, GpuImage, Image, TextureFormatPixelInfo}, - view::{ComputedVisibility, ViewUniform, ViewUniformOffset, ViewUniforms}, + view::{ComputedVisibility, ViewTarget, ViewUniform, ViewUniformOffset, ViewUniforms}, RenderApp, RenderStage, }; use bevy_transform::components::GlobalTransform; @@ -462,7 +462,7 @@ impl SpecializedMeshPipeline for MeshPipeline { shader_defs, entry_point: "fragment".into(), targets: vec![ColorTargetState { - format: TextureFormat::bevy_default(), + format: ViewTarget::TEXTURE_FORMAT_HDR, blend, write_mask: ColorWrites::ALL, }], diff --git a/crates/bevy_pbr/src/render/pbr.wgsl b/crates/bevy_pbr/src/render/pbr.wgsl index 1e1e69f02a235..9fbe5a082e900 100644 --- a/crates/bevy_pbr/src/render/pbr.wgsl +++ b/crates/bevy_pbr/src/render/pbr.wgsl @@ -204,41 +204,6 @@ fn perceptualRoughnessToRoughness(perceptualRoughness: f32) -> f32 { return clampedPerceptualRoughness * clampedPerceptualRoughness; } -// from https://64.github.io/tonemapping/ -// reinhard on RGB oversaturates colors -fn reinhard(color: vec3) -> vec3 { - return color / (1.0 + color); -} - -fn reinhard_extended(color: vec3, max_white: f32) -> vec3 { - let numerator = color * (1.0 + (color / vec3(max_white * max_white))); - return numerator / (1.0 + color); -} - -// luminance coefficients from Rec. 709. -// https://en.wikipedia.org/wiki/Rec._709 -fn luminance(v: vec3) -> f32 { - return dot(v, vec3(0.2126, 0.7152, 0.0722)); -} - -fn change_luminance(c_in: vec3, l_out: f32) -> vec3 { - let l_in = luminance(c_in); - return c_in * (l_out / l_in); -} - -fn reinhard_luminance(color: vec3) -> vec3 { - let l_old = luminance(color); - let l_new = l_old / (1.0 + l_old); - return change_luminance(color, l_new); -} - -fn reinhard_extended_luminance(color: vec3, max_white_l: f32) -> vec3 { - let l_old = luminance(color); - let numerator = l_old * (1.0 + (l_old / (max_white_l * max_white_l))); - let l_new = numerator / (1.0 + l_old); - return change_luminance(color, l_new); -} - fn view_z_to_z_slice(view_z: f32, is_orthographic: bool) -> u32 { if (is_orthographic) { // NOTE: view_z is correct in the orthographic case @@ -645,12 +610,6 @@ fn fragment(in: FragmentInput) -> [[location(0)]] vec4 { output_color.a ); #endif // CLUSTERED_FORWARD_DEBUG_CLUSTER_COHERENCY - - // tone_mapping - output_color = vec4(reinhard_luminance(output_color.rgb), output_color.a); - // Gamma correction. - // Not needed with sRGB buffer - // output_color.rgb = pow(output_color.rgb, vec3(1.0 / 2.2)); } return output_color; diff --git a/crates/bevy_pbr/src/wireframe.rs b/crates/bevy_pbr/src/wireframe.rs index 952ba6bdcdd9b..a63aa7bf126f9 100644 --- a/crates/bevy_pbr/src/wireframe.rs +++ b/crates/bevy_pbr/src/wireframe.rs @@ -130,8 +130,9 @@ fn queue_wireframes( let add_render_phase = |(entity, mesh_handle, mesh_uniform): (Entity, &Handle, &MeshUniform)| { if let Some(mesh) = render_meshes.get(mesh_handle) { - let key = msaa_key - | MeshPipelineKey::from_primitive_topology(mesh.primitive_topology); + let key = MeshPipelineKey::from_primitive_topology(mesh.primitive_topology) + | MeshPipelineKey::from_hdr(view.hdr) + | msaa_key; let pipeline_id = specialized_pipelines.specialize( &mut pipeline_cache, &wireframe_pipeline, diff --git a/crates/bevy_render/src/view/mod.rs b/crates/bevy_render/src/view/mod.rs index ba712a31943cd..fb27b746aa244 100644 --- a/crates/bevy_render/src/view/mod.rs +++ b/crates/bevy_render/src/view/mod.rs @@ -19,7 +19,7 @@ use crate::{ }; use bevy_app::{App, Plugin}; use bevy_ecs::prelude::*; -use bevy_math::{Mat4, Vec3}; +use bevy_math::{Mat4, UVec2, Vec3}; use bevy_transform::components::GlobalTransform; pub struct ViewPlugin; @@ -109,24 +109,92 @@ pub struct ViewUniformOffset { #[derive(Component)] pub struct ViewTarget { - pub view: TextureView, - pub sampled_target: Option, + pub hdr_texture: TextureView, + pub sampled_hdr_texture: Option, + + pub ldr_texture: TextureView, + pub out_texture: TextureView, } impl ViewTarget { + pub const TEXTURE_FORMAT_HDR: TextureFormat = TextureFormat::Rgba16Float; + + pub fn new( + render_device: &RenderDevice, + texture_cache: &mut TextureCache, + msaa: &Msaa, + size: UVec2, + out_texture: TextureView, + ) -> ViewTarget { + let size = Extent3d { + width: size.x, + height: size.y, + depth_or_array_layers: 1, + }; + + let hdr_texture = texture_cache.get( + render_device, + TextureDescriptor { + label: Some("hdr_texture"), + size, + mip_level_count: 1, + sample_count: 1, + dimension: TextureDimension::D2, + format: ViewTarget::TEXTURE_FORMAT_HDR, + usage: TextureUsages::RENDER_ATTACHMENT | TextureUsages::TEXTURE_BINDING, + }, + ); + + let sampled_hdr_texture = (msaa.samples > 1).then(|| { + texture_cache + .get( + render_device, + TextureDescriptor { + label: Some("hdr_texture_sampled"), + size, + mip_level_count: 1, + sample_count: msaa.samples, + dimension: TextureDimension::D2, + format: ViewTarget::TEXTURE_FORMAT_HDR, + usage: TextureUsages::RENDER_ATTACHMENT, + }, + ) + .default_view + }); + + let ldr_texture = texture_cache.get( + render_device, + TextureDescriptor { + label: Some("ldr_texture"), + size, + mip_level_count: 1, + sample_count: 1, + dimension: TextureDimension::D2, + format: TextureFormat::bevy_default(), + usage: TextureUsages::RENDER_ATTACHMENT | TextureUsages::TEXTURE_BINDING, + }, + ); + + ViewTarget { + hdr_texture: hdr_texture.default_view, + sampled_hdr_texture, + ldr_texture: ldr_texture.default_view, + out_texture, + } + } + pub fn get_color_attachment(&self, ops: Operations) -> RenderPassColorAttachment { - RenderPassColorAttachment { - view: if let Some(sampled_target) = &self.sampled_target { - sampled_target - } else { - &self.view + match &self.sampled_hdr_texture { + Some(sampled_target) => RenderPassColorAttachment { + view: sampled_target, + resolve_target: Some(&self.hdr_texture), + ops, }, - resolve_target: if self.sampled_target.is_some() { - Some(&self.view) - } else { - None + None => RenderPassColorAttachment { + view: &self.hdr_texture, + resolve_target: None, + ops, }, - ops, } } } @@ -184,7 +252,15 @@ fn prepare_view_targets( for (entity, camera) in cameras.iter() { if let Some(size) = camera.physical_size { if let Some(texture_view) = camera.target.get_texture_view(&windows, &images) { - let sampled_target = if msaa.samples > 1 { + let view_target = ViewTarget::new( + &*render_device, + &mut *texture_cache, + &*msaa, + size, + texture_view.clone(), + ); + + /*let sampled_target = if msaa.samples > 1 { let sampled_texture = texture_cache.get( &render_device, TextureDescriptor { @@ -204,11 +280,8 @@ fn prepare_view_targets( Some(sampled_texture.default_view.clone()) } else { None - }; - commands.entity(entity).insert(ViewTarget { - view: texture_view.clone(), - sampled_target, - }); + };*/ + commands.entity(entity).insert(view_target); } } } diff --git a/crates/bevy_sprite/src/render/mod.rs b/crates/bevy_sprite/src/render/mod.rs index 7e3ffe4c97038..f1a1e5b539538 100644 --- a/crates/bevy_sprite/src/render/mod.rs +++ b/crates/bevy_sprite/src/render/mod.rs @@ -22,8 +22,8 @@ use bevy_render::{ }, render_resource::{std140::AsStd140, *}, renderer::{RenderDevice, RenderQueue}, - texture::{BevyDefault, Image}, - view::{Msaa, ViewUniform, ViewUniformOffset, ViewUniforms, Visibility}, + texture::Image, + view::{Msaa, ViewTarget, ViewUniform, ViewUniformOffset, ViewUniforms, Visibility}, RenderWorld, }; use bevy_transform::components::GlobalTransform; @@ -145,7 +145,7 @@ impl SpecializedPipeline for SpritePipeline { shader_defs, entry_point: "fragment".into(), targets: vec![ColorTargetState { - format: TextureFormat::bevy_default(), + format: ViewTarget::TEXTURE_FORMAT_HDR, blend: Some(BlendState::ALPHA_BLENDING), write_mask: ColorWrites::ALL, }], diff --git a/crates/bevy_ui/src/render/render_pass.rs b/crates/bevy_ui/src/render/render_pass.rs index 23d1577e6a3bc..d7a4d04e8af1f 100644 --- a/crates/bevy_ui/src/render/render_pass.rs +++ b/crates/bevy_ui/src/render/render_pass.rs @@ -73,7 +73,7 @@ impl Node for UiPassNode { let pass_descriptor = RenderPassDescriptor { label: Some("ui_pass"), color_attachments: &[RenderPassColorAttachment { - view: &target.view, + view: &target.out_texture, resolve_target: None, ops: Operations { load: LoadOp::Load, From 315ef6f0ed41156298edd8692421c75d006674f8 Mon Sep 17 00:00:00 2001 From: Jakob Hellermann Date: Fri, 24 Dec 2021 12:17:16 +0100 Subject: [PATCH 02/26] return rendered-to texture view as output --- crates/bevy_core_pipeline/src/main_pass_2d.rs | 13 +++++++++++++ crates/bevy_core_pipeline/src/main_pass_3d.rs | 12 ++++++++++++ 2 files changed, 25 insertions(+) diff --git a/crates/bevy_core_pipeline/src/main_pass_2d.rs b/crates/bevy_core_pipeline/src/main_pass_2d.rs index 0207863cd98f5..e101706b8bd23 100644 --- a/crates/bevy_core_pipeline/src/main_pass_2d.rs +++ b/crates/bevy_core_pipeline/src/main_pass_2d.rs @@ -15,6 +15,7 @@ pub struct MainPass2dNode { impl MainPass2dNode { pub const IN_VIEW: &'static str = "view"; + pub const OUT_TEXTURE: &'static str = "output"; pub fn new(world: &mut World) -> Self { Self { @@ -28,6 +29,13 @@ impl Node for MainPass2dNode { vec![SlotInfo::new(MainPass2dNode::IN_VIEW, SlotType::Entity)] } + fn output(&self) -> Vec { + vec![SlotInfo::new( + MainPass2dNode::OUT_TEXTURE, + SlotType::TextureView, + )] + } + fn update(&mut self, world: &mut World) { self.query.update_archetypes(world); } @@ -65,6 +73,11 @@ impl Node for MainPass2dNode { let draw_function = draw_functions.get_mut(item.draw_function).unwrap(); draw_function.draw(world, &mut tracked_pass, view_entity, item); } + + graph + .set_output(MainPass2dNode::OUT_TEXTURE, target.hdr_texture.clone()) + .unwrap(); + Ok(()) } } diff --git a/crates/bevy_core_pipeline/src/main_pass_3d.rs b/crates/bevy_core_pipeline/src/main_pass_3d.rs index 242a0596be565..7dfe13fea33cd 100644 --- a/crates/bevy_core_pipeline/src/main_pass_3d.rs +++ b/crates/bevy_core_pipeline/src/main_pass_3d.rs @@ -25,6 +25,7 @@ pub struct MainPass3dNode { impl MainPass3dNode { pub const IN_VIEW: &'static str = "view"; + pub const OUT_TEXTURE: &'static str = "output"; pub fn new(world: &mut World) -> Self { Self { @@ -38,6 +39,13 @@ impl Node for MainPass3dNode { vec![SlotInfo::new(MainPass3dNode::IN_VIEW, SlotType::Entity)] } + fn output(&self) -> Vec { + vec![SlotInfo::new( + MainPass3dNode::OUT_TEXTURE, + SlotType::TextureView, + )] + } + fn update(&mut self, world: &mut World) { self.query.update_archetypes(world); } @@ -166,6 +174,10 @@ impl Node for MainPass3dNode { } } + graph + .set_output(MainPass3dNode::OUT_TEXTURE, target.hdr_texture.clone()) + .unwrap(); + Ok(()) } } From cf014e8e0ada08aebac38fc70d1bc705ab7bfa2d Mon Sep 17 00:00:00 2001 From: Jakob Hellermann Date: Wed, 29 Dec 2021 17:19:51 +0100 Subject: [PATCH 03/26] create bind group in tonemapping/upscaling node to enable customization --- crates/bevy_core_pipeline/src/lib.rs | 32 +++++++++ .../bevy_core_pipeline/src/tonemapping/mod.rs | 32 ++------- .../src/tonemapping/node.rs | 68 +++++++++++++++++-- .../bevy_core_pipeline/src/upscaling/mod.rs | 30 ++------ .../bevy_core_pipeline/src/upscaling/node.rs | 51 ++++++++++++-- 5 files changed, 152 insertions(+), 61 deletions(-) diff --git a/crates/bevy_core_pipeline/src/lib.rs b/crates/bevy_core_pipeline/src/lib.rs index cc1423b6f262c..1abd923c1df01 100644 --- a/crates/bevy_core_pipeline/src/lib.rs +++ b/crates/bevy_core_pipeline/src/lib.rs @@ -195,6 +195,14 @@ impl Plugin for CorePipelinePlugin { TonemappingNode::IN_VIEW, ) .unwrap(); + draw_2d_graph + .add_slot_edge( + draw_2d_graph::node::MAIN_PASS, + MainPass2dNode::OUT_TEXTURE, + draw_2d_graph::node::TONEMAPPING, + TonemappingNode::IN_TEXTURE, + ) + .unwrap(); draw_2d_graph .add_node_edge( @@ -210,6 +218,14 @@ impl Plugin for CorePipelinePlugin { UpscalingNode::IN_VIEW, ) .unwrap(); + draw_2d_graph + .add_slot_edge( + draw_2d_graph::node::TONEMAPPING, + TonemappingNode::OUT_TEXTURE, + draw_2d_graph::node::UPSCALING, + UpscalingNode::IN_TEXTURE, + ) + .unwrap(); graph.add_sub_graph(draw_2d_graph::NAME, draw_2d_graph); @@ -246,6 +262,14 @@ impl Plugin for CorePipelinePlugin { TonemappingNode::IN_VIEW, ) .unwrap(); + draw_3d_graph + .add_slot_edge( + draw_3d_graph::node::MAIN_PASS, + MainPass3dNode::OUT_TEXTURE, + draw_3d_graph::node::TONEMAPPING, + TonemappingNode::IN_TEXTURE, + ) + .unwrap(); draw_3d_graph .add_node_edge( @@ -261,6 +285,14 @@ impl Plugin for CorePipelinePlugin { UpscalingNode::IN_VIEW, ) .unwrap(); + draw_3d_graph + .add_slot_edge( + draw_3d_graph::node::TONEMAPPING, + TonemappingNode::OUT_TEXTURE, + draw_3d_graph::node::UPSCALING, + UpscalingNode::IN_TEXTURE, + ) + .unwrap(); graph.add_sub_graph(draw_3d_graph::NAME, draw_3d_graph); diff --git a/crates/bevy_core_pipeline/src/tonemapping/mod.rs b/crates/bevy_core_pipeline/src/tonemapping/mod.rs index f0ccf4780c928..481ae4268366c 100644 --- a/crates/bevy_core_pipeline/src/tonemapping/mod.rs +++ b/crates/bevy_core_pipeline/src/tonemapping/mod.rs @@ -7,7 +7,7 @@ use bevy_asset::{load_internal_asset, HandleUntyped}; use bevy_ecs::prelude::*; use bevy_render::renderer::RenderDevice; use bevy_render::texture::BevyDefault; -use bevy_render::view::ViewTarget; +use bevy_render::view::ExtractedView; use bevy_render::{render_resource::*, RenderApp, RenderStage}; use bevy_reflect::TypeUuid; @@ -107,41 +107,21 @@ impl SpecializedPipeline for TonemappingPipeline { #[derive(Component)] pub struct TonemappingTarget { - pub hdr_texture_bind_group: BindGroup, pub pipeline: CachedPipelineId, } fn queue_tonemapping_bind_groups( mut commands: Commands, - render_device: Res, mut render_pipeline_cache: ResMut, mut pipelines: ResMut>, tonemapping_pipeline: Res, - view_targets: Query<(Entity, &ViewTarget)>, + views: Query>, ) { - for (entity, target) in view_targets.iter() { + for entity in views.iter() { let pipeline = pipelines.specialize(&mut render_pipeline_cache, &tonemapping_pipeline, ()); - let sampler = render_device.create_sampler(&SamplerDescriptor::default()); - - let bind_group = render_device.create_bind_group(&BindGroupDescriptor { - label: None, - layout: &tonemapping_pipeline.hdr_texture_bind_group, - entries: &[ - BindGroupEntry { - binding: 0, - resource: BindingResource::TextureView(&target.hdr_texture), - }, - BindGroupEntry { - binding: 1, - resource: BindingResource::Sampler(&sampler), - }, - ], - }); - - commands.entity(entity).insert(TonemappingTarget { - hdr_texture_bind_group: bind_group, - pipeline, - }); + commands + .entity(entity) + .insert(TonemappingTarget { pipeline }); } } diff --git a/crates/bevy_core_pipeline/src/tonemapping/node.rs b/crates/bevy_core_pipeline/src/tonemapping/node.rs index b84c83eba7acb..9f21efe6b4bb0 100644 --- a/crates/bevy_core_pipeline/src/tonemapping/node.rs +++ b/crates/bevy_core_pipeline/src/tonemapping/node.rs @@ -1,33 +1,51 @@ +use std::sync::Mutex; + use bevy_ecs::prelude::*; use bevy_ecs::query::QueryState; use bevy_render::{ - render_graph::{Node, NodeRunError, RenderGraphContext, SlotInfo, SlotType}, + render_graph::{Node, NodeRunError, RenderGraphContext, SlotInfo, SlotType, SlotValue}, render_resource::{ - LoadOp, Operations, RenderPassColorAttachment, RenderPassDescriptor, RenderPipelineCache, + BindGroup, BindGroupDescriptor, BindGroupEntry, BindingResource, LoadOp, Operations, + RenderPassColorAttachment, RenderPassDescriptor, RenderPipelineCache, SamplerDescriptor, + TextureViewId, }, renderer::RenderContext, view::{ExtractedView, ViewTarget}, }; -use super::TonemappingTarget; +use super::{TonemappingPipeline, TonemappingTarget}; pub struct TonemappingNode { query: QueryState<(&'static ViewTarget, &'static TonemappingTarget), With>, + cached_texture_bind_group: Mutex>, } impl TonemappingNode { pub const IN_VIEW: &'static str = "view"; + pub const IN_TEXTURE: &'static str = "in_texture"; + pub const OUT_TEXTURE: &'static str = "out_texture"; pub fn new(world: &mut World) -> Self { Self { query: QueryState::new(world), + cached_texture_bind_group: Mutex::new(None), } } } impl Node for TonemappingNode { fn input(&self) -> Vec { - vec![SlotInfo::new(TonemappingNode::IN_VIEW, SlotType::Entity)] + vec![ + SlotInfo::new(TonemappingNode::IN_TEXTURE, SlotType::TextureView), + SlotInfo::new(TonemappingNode::IN_VIEW, SlotType::Entity), + ] + } + + fn output(&self) -> Vec { + vec![SlotInfo::new( + TonemappingNode::OUT_TEXTURE, + SlotType::TextureView, + )] } fn update(&mut self, world: &mut World) { @@ -41,8 +59,41 @@ impl Node for TonemappingNode { world: &World, ) -> Result<(), NodeRunError> { let view_entity = graph.get_input_entity(Self::IN_VIEW)?; + let in_texture = graph.get_input_texture(Self::IN_TEXTURE)?; let render_pipeline_cache = world.get_resource::().unwrap(); + let tonemapping_pipeline = world.get_resource::().unwrap(); + + let mut cached_bind_group = self.cached_texture_bind_group.lock().unwrap(); + let bind_group = match &mut *cached_bind_group { + Some((id, bind_group)) if in_texture.id() == *id => bind_group, + cached_bind_group => { + let sampler = render_context + .render_device + .create_sampler(&SamplerDescriptor::default()); + + let bind_group = + render_context + .render_device + .create_bind_group(&BindGroupDescriptor { + label: None, + layout: &tonemapping_pipeline.hdr_texture_bind_group, + entries: &[ + BindGroupEntry { + binding: 0, + resource: BindingResource::TextureView(in_texture), + }, + BindGroupEntry { + binding: 1, + resource: BindingResource::Sampler(&sampler), + }, + ], + }); + + let (_, bind_group) = cached_bind_group.insert((in_texture.id(), bind_group)); + bind_group + } + }; let (target, tonemapping_target) = match self.query.get_manual(world, view_entity) { Ok(query) => query, @@ -72,9 +123,16 @@ impl Node for TonemappingNode { .begin_render_pass(&pass_descriptor); render_pass.set_pipeline(pipeline); - render_pass.set_bind_group(0, &tonemapping_target.hdr_texture_bind_group, &[]); + render_pass.set_bind_group(0, bind_group, &[]); render_pass.draw(0..3, 0..1); + graph + .set_output( + TonemappingNode::OUT_TEXTURE, + SlotValue::TextureView(target.ldr_texture.clone()), + ) + .unwrap(); + Ok(()) } } diff --git a/crates/bevy_core_pipeline/src/upscaling/mod.rs b/crates/bevy_core_pipeline/src/upscaling/mod.rs index 57c7f6be6905e..157a8d1636cd5 100644 --- a/crates/bevy_core_pipeline/src/upscaling/mod.rs +++ b/crates/bevy_core_pipeline/src/upscaling/mod.rs @@ -7,7 +7,7 @@ use bevy_asset::{load_internal_asset, HandleUntyped}; use bevy_ecs::prelude::*; use bevy_render::renderer::RenderDevice; use bevy_render::texture::BevyDefault; -use bevy_render::view::ViewTarget; +use bevy_render::view::ExtractedView; use bevy_render::{render_resource::*, RenderApp, RenderStage}; use bevy_reflect::TypeUuid; @@ -102,41 +102,19 @@ impl SpecializedPipeline for UpscalingPipeline { #[derive(Component)] pub struct UpscalingTarget { - pub ldr_texture_bind_group: BindGroup, pub pipeline: CachedPipelineId, } fn queue_upscaling_bind_groups( mut commands: Commands, - render_device: Res, mut render_pipeline_cache: ResMut, mut pipelines: ResMut>, upscaling_pipeline: Res, - view_targets: Query<(Entity, &ViewTarget)>, + view_targets: Query>, ) { - for (entity, target) in view_targets.iter() { + for entity in view_targets.iter() { let pipeline = pipelines.specialize(&mut render_pipeline_cache, &upscaling_pipeline, ()); - let sampler = render_device.create_sampler(&SamplerDescriptor::default()); - - let bind_group = render_device.create_bind_group(&BindGroupDescriptor { - label: None, - layout: &upscaling_pipeline.ldr_texture_bind_group, - entries: &[ - BindGroupEntry { - binding: 0, - resource: BindingResource::TextureView(&target.ldr_texture), - }, - BindGroupEntry { - binding: 1, - resource: BindingResource::Sampler(&sampler), - }, - ], - }); - - commands.entity(entity).insert(UpscalingTarget { - ldr_texture_bind_group: bind_group, - pipeline, - }); + commands.entity(entity).insert(UpscalingTarget { pipeline }); } } diff --git a/crates/bevy_core_pipeline/src/upscaling/node.rs b/crates/bevy_core_pipeline/src/upscaling/node.rs index e67a788949356..721151ba59174 100644 --- a/crates/bevy_core_pipeline/src/upscaling/node.rs +++ b/crates/bevy_core_pipeline/src/upscaling/node.rs @@ -1,33 +1,43 @@ +use std::sync::Mutex; + use bevy_ecs::prelude::*; use bevy_ecs::query::QueryState; use bevy_render::{ render_graph::{Node, NodeRunError, RenderGraphContext, SlotInfo, SlotType}, render_resource::{ - LoadOp, Operations, RenderPassColorAttachment, RenderPassDescriptor, RenderPipelineCache, + BindGroup, BindGroupDescriptor, BindGroupEntry, BindingResource, LoadOp, Operations, + RenderPassColorAttachment, RenderPassDescriptor, RenderPipelineCache, SamplerDescriptor, + TextureViewId, }, renderer::RenderContext, view::{ExtractedView, ViewTarget}, }; -use super::UpscalingTarget; +use super::{UpscalingPipeline, UpscalingTarget}; pub struct UpscalingNode { query: QueryState<(&'static ViewTarget, &'static UpscalingTarget), With>, + cached_texture_bind_group: Mutex>, } impl UpscalingNode { pub const IN_VIEW: &'static str = "view"; + pub const IN_TEXTURE: &'static str = "in_texture"; pub fn new(world: &mut World) -> Self { Self { query: QueryState::new(world), + cached_texture_bind_group: Mutex::new(None), } } } impl Node for UpscalingNode { fn input(&self) -> Vec { - vec![SlotInfo::new(UpscalingNode::IN_VIEW, SlotType::Entity)] + vec![ + SlotInfo::new(UpscalingNode::IN_TEXTURE, SlotType::TextureView), + SlotInfo::new(UpscalingNode::IN_VIEW, SlotType::Entity), + ] } fn update(&mut self, world: &mut World) { @@ -41,8 +51,41 @@ impl Node for UpscalingNode { world: &World, ) -> Result<(), NodeRunError> { let view_entity = graph.get_input_entity(Self::IN_VIEW)?; + let in_texture = graph.get_input_texture(Self::IN_TEXTURE)?; let render_pipeline_cache = world.get_resource::().unwrap(); + let upscaling_pipeline = world.get_resource::().unwrap(); + + let mut cached_bind_group = self.cached_texture_bind_group.lock().unwrap(); + let bind_group = match &mut *cached_bind_group { + Some((id, bind_group)) if in_texture.id() == *id => bind_group, + cached_bind_group => { + let sampler = render_context + .render_device + .create_sampler(&SamplerDescriptor::default()); + + let bind_group = + render_context + .render_device + .create_bind_group(&BindGroupDescriptor { + label: None, + layout: &upscaling_pipeline.ldr_texture_bind_group, + entries: &[ + BindGroupEntry { + binding: 0, + resource: BindingResource::TextureView(in_texture), + }, + BindGroupEntry { + binding: 1, + resource: BindingResource::Sampler(&sampler), + }, + ], + }); + + let (_, bind_group) = cached_bind_group.insert((in_texture.id(), bind_group)); + bind_group + } + }; let (target, upscaling_target) = match self.query.get_manual(world, view_entity) { Ok(query) => query, @@ -72,7 +115,7 @@ impl Node for UpscalingNode { .begin_render_pass(&pass_descriptor); render_pass.set_pipeline(pipeline); - render_pass.set_bind_group(0, &upscaling_target.ldr_texture_bind_group, &[]); + render_pass.set_bind_group(0, bind_group, &[]); render_pass.draw(0..3, 0..1); Ok(()) From babcbb2001a3e742ee3a181a60cec93ce374cb33 Mon Sep 17 00:00:00 2001 From: Jakob Hellermann Date: Thu, 30 Dec 2021 16:35:16 +0100 Subject: [PATCH 04/26] make hdr configurable --- crates/bevy_core_pipeline/src/main_pass_2d.rs | 5 +- crates/bevy_core_pipeline/src/main_pass_3d.rs | 5 +- .../bevy_core_pipeline/src/tonemapping/mod.rs | 9 ++ .../src/tonemapping/node.rs | 41 ++++--- .../src/tonemapping/tonemapping.wgsl | 30 +---- crates/bevy_pbr/src/material.rs | 1 + crates/bevy_pbr/src/render/light.rs | 2 + crates/bevy_pbr/src/render/mesh.rs | 20 +++- crates/bevy_pbr/src/render/pbr.wgsl | 22 +++- crates/bevy_render/src/camera/bundle.rs | 3 + crates/bevy_render/src/camera/camera.rs | 2 + crates/bevy_render/src/view/mod.rs | 109 +++++++++++++----- crates/bevy_sprite/src/render/mod.rs | 61 ++++++++-- crates/bevy_sprite/src/render/sprite.wgsl | 9 ++ examples/shader/shader_defs.rs | 5 +- 15 files changed, 233 insertions(+), 91 deletions(-) diff --git a/crates/bevy_core_pipeline/src/main_pass_2d.rs b/crates/bevy_core_pipeline/src/main_pass_2d.rs index e101706b8bd23..f04b8f0120e07 100644 --- a/crates/bevy_core_pipeline/src/main_pass_2d.rs +++ b/crates/bevy_core_pipeline/src/main_pass_2d.rs @@ -75,7 +75,10 @@ impl Node for MainPass2dNode { } graph - .set_output(MainPass2dNode::OUT_TEXTURE, target.hdr_texture.clone()) + .set_output( + MainPass2dNode::OUT_TEXTURE, + target.main_texture.texture().clone(), + ) .unwrap(); Ok(()) diff --git a/crates/bevy_core_pipeline/src/main_pass_3d.rs b/crates/bevy_core_pipeline/src/main_pass_3d.rs index 7dfe13fea33cd..d113a7148e390 100644 --- a/crates/bevy_core_pipeline/src/main_pass_3d.rs +++ b/crates/bevy_core_pipeline/src/main_pass_3d.rs @@ -175,7 +175,10 @@ impl Node for MainPass3dNode { } graph - .set_output(MainPass3dNode::OUT_TEXTURE, target.hdr_texture.clone()) + .set_output( + MainPass3dNode::OUT_TEXTURE, + target.main_texture.texture().clone(), + ) .unwrap(); Ok(()) diff --git a/crates/bevy_core_pipeline/src/tonemapping/mod.rs b/crates/bevy_core_pipeline/src/tonemapping/mod.rs index 481ae4268366c..cc0e7131f22ea 100644 --- a/crates/bevy_core_pipeline/src/tonemapping/mod.rs +++ b/crates/bevy_core_pipeline/src/tonemapping/mod.rs @@ -15,6 +15,9 @@ use bevy_reflect::TypeUuid; const TONEMAPPING_SHADER_HANDLE: HandleUntyped = HandleUntyped::weak_from_u64(Shader::TYPE_UUID, 17015368199668024512); +const TONEMAPPING_SHARED_SHADER_HANDLE: HandleUntyped = + HandleUntyped::weak_from_u64(Shader::TYPE_UUID, 2499430578245347910); + pub struct TonemappingPlugin; impl Plugin for TonemappingPlugin { @@ -25,6 +28,12 @@ impl Plugin for TonemappingPlugin { "tonemapping.wgsl", Shader::from_wgsl ); + load_internal_asset!( + app, + TONEMAPPING_SHARED_SHADER_HANDLE, + "tonemapping_shared.wgsl", + Shader::from_wgsl + ); let render_app = match app.get_sub_app_mut(RenderApp) { Ok(render_app) => render_app, diff --git a/crates/bevy_core_pipeline/src/tonemapping/node.rs b/crates/bevy_core_pipeline/src/tonemapping/node.rs index 9f21efe6b4bb0..d64c26d47ac48 100644 --- a/crates/bevy_core_pipeline/src/tonemapping/node.rs +++ b/crates/bevy_core_pipeline/src/tonemapping/node.rs @@ -10,7 +10,7 @@ use bevy_render::{ TextureViewId, }, renderer::RenderContext, - view::{ExtractedView, ViewTarget}, + view::{ExtractedView, ViewMainTexture, ViewTarget}, }; use super::{TonemappingPipeline, TonemappingTarget}; @@ -64,6 +64,31 @@ impl Node for TonemappingNode { let render_pipeline_cache = world.get_resource::().unwrap(); let tonemapping_pipeline = world.get_resource::().unwrap(); + let (target, tonemapping_target) = match self.query.get_manual(world, view_entity) { + Ok(query) => query, + Err(_) => return Ok(()), + }; + + let pipeline = match render_pipeline_cache.get(tonemapping_target.pipeline) { + Some(pipeline) => pipeline, + None => return Ok(()), + }; + + let ldr_texture = match &target.main_texture { + ViewMainTexture::Hdr { ldr_texture, .. } => ldr_texture, + ViewMainTexture::Sdr { .. } => { + // non-hdr does tone mapping in the main pass node + let in_texture = in_texture.clone(); + graph + .set_output( + TonemappingNode::OUT_TEXTURE, + SlotValue::TextureView(in_texture), + ) + .unwrap(); + return Ok(()); + } + }; + let mut cached_bind_group = self.cached_texture_bind_group.lock().unwrap(); let bind_group = match &mut *cached_bind_group { Some((id, bind_group)) if in_texture.id() == *id => bind_group, @@ -95,20 +120,10 @@ impl Node for TonemappingNode { } }; - let (target, tonemapping_target) = match self.query.get_manual(world, view_entity) { - Ok(query) => query, - Err(_) => return Ok(()), - }; - - let pipeline = match render_pipeline_cache.get(tonemapping_target.pipeline) { - Some(pipeline) => pipeline, - None => return Ok(()), - }; - let pass_descriptor = RenderPassDescriptor { label: Some("tonemapping_pass"), color_attachments: &[RenderPassColorAttachment { - view: &target.ldr_texture, + view: ldr_texture, resolve_target: None, ops: Operations { load: LoadOp::Clear(Default::default()), // TODO shouldn't need to be cleared @@ -129,7 +144,7 @@ impl Node for TonemappingNode { graph .set_output( TonemappingNode::OUT_TEXTURE, - SlotValue::TextureView(target.ldr_texture.clone()), + SlotValue::TextureView(ldr_texture.clone()), ) .unwrap(); diff --git a/crates/bevy_core_pipeline/src/tonemapping/tonemapping.wgsl b/crates/bevy_core_pipeline/src/tonemapping/tonemapping.wgsl index ec18f6d215d6c..7ca60eb4c650c 100644 --- a/crates/bevy_core_pipeline/src/tonemapping/tonemapping.wgsl +++ b/crates/bevy_core_pipeline/src/tonemapping/tonemapping.wgsl @@ -1,3 +1,5 @@ +#import bevy_core_pipeline::tonemapping + struct VertexOutput { [[builtin(position)]] position: vec4; @@ -18,34 +20,6 @@ var hdr_texture: texture_2d; [[group(0), binding(1)]] var hdr_sampler: sampler; -// from https://64.github.io/tonemapping/ -// reinhard on RGB oversaturates colors -fn reinhard(color: vec3) -> vec3 { - return color / (1.0 + color); -} - -fn reinhard_extended(color: vec3, max_white: f32) -> vec3 { - let numerator = color * (1.0 + (color / vec3(max_white * max_white))); - return numerator / (1.0 + color); -} - -// luminance coefficients from Rec. 709. -// https://en.wikipedia.org/wiki/Rec._709 -fn luminance(v: vec3) -> f32 { - return dot(v, vec3(0.2126, 0.7152, 0.0722)); -} - -fn change_luminance(c_in: vec3, l_out: f32) -> vec3 { - let l_in = luminance(c_in); - return c_in * (l_out / l_in); -} - -fn reinhard_luminance(color: vec3) -> vec3 { - let l_old = luminance(color); - let l_new = l_old / (1.0 + l_old); - return change_luminance(color, l_new); -} - [[stage(fragment)]] fn fs_main(in: VertexOutput) -> [[location(0)]] vec4 { let hdr_color = textureSample(hdr_texture, hdr_sampler, in.uv); diff --git a/crates/bevy_pbr/src/material.rs b/crates/bevy_pbr/src/material.rs index ff664ba31e68e..1581a76952a61 100644 --- a/crates/bevy_pbr/src/material.rs +++ b/crates/bevy_pbr/src/material.rs @@ -338,6 +338,7 @@ pub fn queue_material_meshes( let inverse_view_matrix = view.transform.compute_matrix().inverse(); let inverse_view_row_2 = inverse_view_matrix.row(2); + let msaa_key = MeshPipelineKey::from_msaa_samples(msaa.samples); for visible_entity in &visible_entities.entities { diff --git a/crates/bevy_pbr/src/render/light.rs b/crates/bevy_pbr/src/render/light.rs index c0605748262ae..47239b6baf57c 100644 --- a/crates/bevy_pbr/src/render/light.rs +++ b/crates/bevy_pbr/src/render/light.rs @@ -741,6 +741,7 @@ pub fn prepare_lights( projection: cube_face_projection, near: POINT_LIGHT_NEAR_Z, far: light.range, + hdr: false, }, RenderPhase::::default(), LightEntity::Point { @@ -826,6 +827,7 @@ pub fn prepare_lights( projection, near: light.near, far: light.far, + hdr: false, }, RenderPhase::::default(), LightEntity::Directional { light_entity }, diff --git a/crates/bevy_pbr/src/render/mesh.rs b/crates/bevy_pbr/src/render/mesh.rs index 03e41423c23d3..e21efe7849f60 100644 --- a/crates/bevy_pbr/src/render/mesh.rs +++ b/crates/bevy_pbr/src/render/mesh.rs @@ -368,6 +368,7 @@ bitflags::bitflags! { pub struct MeshPipelineKey: u32 { const NONE = 0; const TRANSPARENT_MAIN_PASS = (1 << 0); + const HDR = (1 << 2); const MSAA_RESERVED_BITS = MeshPipelineKey::MSAA_MASK_BITS << MeshPipelineKey::MSAA_SHIFT_BITS; const PRIMITIVE_TOPOLOGY_RESERVED_BITS = MeshPipelineKey::PRIMITIVE_TOPOLOGY_MASK_BITS << MeshPipelineKey::PRIMITIVE_TOPOLOGY_SHIFT_BITS; } @@ -384,6 +385,14 @@ impl MeshPipelineKey { MeshPipelineKey::from_bits(msaa_bits).unwrap() } + pub fn from_hdr(hdr: bool) -> Self { + if hdr { + MeshPipelineKey::HDR + } else { + MeshPipelineKey::NONE + } + } + pub fn msaa_samples(&self) -> u32 { ((self.bits >> Self::MSAA_SHIFT_BITS) & Self::MSAA_MASK_BITS) + 1 } @@ -450,6 +459,15 @@ impl SpecializedMeshPipeline for MeshPipeline { #[cfg(feature = "webgl")] shader_defs.push(String::from("NO_ARRAY_TEXTURES_SUPPORT")); + if !key.contains(MeshPipelineKey::HDR) { + shader_defs.push("TONEMAPPING_IN_PBR_SHADER".to_string()); + } + + let format = match key.contains(MeshPipelineKey::HDR) { + true => ViewTarget::TEXTURE_FORMAT_HDR, + false => TextureFormat::bevy_default(), + }; + Ok(RenderPipelineDescriptor { vertex: VertexState { shader: MESH_SHADER_HANDLE.typed::(), @@ -462,7 +480,7 @@ impl SpecializedMeshPipeline for MeshPipeline { shader_defs, entry_point: "fragment".into(), targets: vec![ColorTargetState { - format: ViewTarget::TEXTURE_FORMAT_HDR, + format, blend, write_mask: ColorWrites::ALL, }], diff --git a/crates/bevy_pbr/src/render/pbr.wgsl b/crates/bevy_pbr/src/render/pbr.wgsl index 9fbe5a082e900..517ef9b3518e7 100644 --- a/crates/bevy_pbr/src/render/pbr.wgsl +++ b/crates/bevy_pbr/src/render/pbr.wgsl @@ -35,6 +35,10 @@ #import bevy_pbr::mesh_view_bind_group #import bevy_pbr::mesh_struct +#ifdef TONEMAPPING_IN_PBR_SHADER +#import bevy_core_pipeline::tonemapping +#endif + [[group(2), binding(0)]] var mesh: Mesh; @@ -156,8 +160,7 @@ fn fresnel(f0: vec3, LoH: f32) -> vec3 { // Cook-Torrance approximation of the microfacet model integration using Fresnel law F to model f_m // f_r(v,l) = { D(h,α) G(v,l,α) F(v,h,f0) } / { 4 (n⋅v) (n⋅l) } -fn specular(f0: vec3, roughness: f32, h: vec3, NoV: f32, NoL: f32, - NoH: f32, LoH: f32, specularIntensity: f32) -> vec3 { +fn specular(f0: vec3, roughness: f32, h: vec3, NoV: f32, NoL: f32, NoH: f32, LoH: f32, specularIntensity: f32) -> vec3 { let D = D_GGX(roughness, NoH, h); let V = V_SmithGGXCorrelated(roughness, NoV, NoL); let F = fresnel(f0, LoH); @@ -254,8 +257,15 @@ fn get_light_id(index: u32) -> u32 { } fn point_light( - world_position: vec3, light: PointLight, roughness: f32, NdotV: f32, N: vec3, V: vec3, - R: vec3, F0: vec3, diffuseColor: vec3 + world_position: vec3, + light: PointLight, + roughness: f32, + NdotV: f32, + N: vec3, + V: vec3, + R: vec3, + F0: vec3, + diffuseColor: vec3, ) -> vec3 { let light_to_frag = light.position_radius.xyz - world_position.xyz; let distance_square = dot(light_to_frag, light_to_frag); @@ -612,5 +622,9 @@ fn fragment(in: FragmentInput) -> [[location(0)]] vec4 { #endif // CLUSTERED_FORWARD_DEBUG_CLUSTER_COHERENCY } + #ifdef TONEMAPPING_IN_PBR_SHADER + output_color = vec4(reinhard_luminance(output_color.rgb), output_color.a); + #endif + return output_color; } diff --git a/crates/bevy_render/src/camera/bundle.rs b/crates/bevy_render/src/camera/bundle.rs index f32aad826c5b6..abdb279a4fc7a 100644 --- a/crates/bevy_render/src/camera/bundle.rs +++ b/crates/bevy_render/src/camera/bundle.rs @@ -55,6 +55,7 @@ impl PerspectiveCameraBundle { camera: Camera { near: perspective_projection.near, far: perspective_projection.far, + hdr: true, ..Default::default() }, perspective_projection, @@ -99,6 +100,7 @@ impl OrthographicCameraBundle { camera: Camera { near: orthographic_projection.near, far: orthographic_projection.far, + hdr: true, ..Default::default() }, orthographic_projection, @@ -151,6 +153,7 @@ impl OrthographicCameraBundle { camera: Camera { near: orthographic_projection.near, far: orthographic_projection.far, + hdr: false, ..Default::default() }, orthographic_projection, diff --git a/crates/bevy_render/src/camera/camera.rs b/crates/bevy_render/src/camera/camera.rs index 6da11ccb66473..98d83569e6552 100644 --- a/crates/bevy_render/src/camera/camera.rs +++ b/crates/bevy_render/src/camera/camera.rs @@ -37,6 +37,7 @@ pub struct Camera { pub depth_calculation: DepthCalculation, pub near: f32, pub far: f32, + pub hdr: bool, } #[derive(Debug, Clone, Reflect, PartialEq, Eq, Hash)] @@ -307,6 +308,7 @@ pub fn extract_cameras( height: size.y.max(1), near: camera.near, far: camera.far, + hdr: camera.hdr, }, visible_entities.clone(), M::default(), diff --git a/crates/bevy_render/src/view/mod.rs b/crates/bevy_render/src/view/mod.rs index fb27b746aa244..9b16dcf3c7d76 100644 --- a/crates/bevy_render/src/view/mod.rs +++ b/crates/bevy_render/src/view/mod.rs @@ -82,6 +82,7 @@ pub struct ExtractedView { pub height: u32, pub near: f32, pub far: f32, + pub hdr: bool, } #[derive(Clone, AsStd140)] @@ -107,12 +108,31 @@ pub struct ViewUniformOffset { pub offset: u32, } +pub enum ViewMainTexture { + Hdr { + hdr_texture: TextureView, + sampled_hdr_texture: Option, + + ldr_texture: TextureView, + }, + Sdr { + texture: TextureView, + sampled_texture: Option, + }, +} + +impl ViewMainTexture { + pub fn texture(&self) -> &TextureView { + match self { + ViewMainTexture::Hdr { hdr_texture, .. } => hdr_texture, + ViewMainTexture::Sdr { texture, .. } => texture, + } + } +} + #[derive(Component)] pub struct ViewTarget { - pub hdr_texture: TextureView, - pub sampled_hdr_texture: Option, - - pub ldr_texture: TextureView, + pub main_texture: ViewMainTexture, pub out_texture: TextureView, } @@ -125,6 +145,7 @@ impl ViewTarget { msaa: &Msaa, size: UVec2, out_texture: TextureView, + hdr: bool, ) -> ViewTarget { let size = Extent3d { width: size.x, @@ -132,66 +153,93 @@ impl ViewTarget { depth_or_array_layers: 1, }; - let hdr_texture = texture_cache.get( + let main_texture_format = match hdr { + true => ViewTarget::TEXTURE_FORMAT_HDR, + false => TextureFormat::bevy_default(), + }; + + let main_texture = texture_cache.get( render_device, TextureDescriptor { - label: Some("hdr_texture"), + label: Some("main_texture"), size, mip_level_count: 1, sample_count: 1, dimension: TextureDimension::D2, - format: ViewTarget::TEXTURE_FORMAT_HDR, + format: main_texture_format, usage: TextureUsages::RENDER_ATTACHMENT | TextureUsages::TEXTURE_BINDING, }, ); - let sampled_hdr_texture = (msaa.samples > 1).then(|| { + let sampled_main_texture = (msaa.samples > 1).then(|| { texture_cache .get( render_device, TextureDescriptor { - label: Some("hdr_texture_sampled"), + label: Some("main_texture_sampled"), size, mip_level_count: 1, sample_count: msaa.samples, dimension: TextureDimension::D2, - format: ViewTarget::TEXTURE_FORMAT_HDR, + format: main_texture_format, usage: TextureUsages::RENDER_ATTACHMENT, }, ) .default_view }); - let ldr_texture = texture_cache.get( - render_device, - TextureDescriptor { - label: Some("ldr_texture"), - size, - mip_level_count: 1, - sample_count: 1, - dimension: TextureDimension::D2, - format: TextureFormat::bevy_default(), - usage: TextureUsages::RENDER_ATTACHMENT | TextureUsages::TEXTURE_BINDING, - }, - ); + let main_texture = if hdr { + let ldr_texture = texture_cache.get( + render_device, + TextureDescriptor { + label: Some("ldr_texture"), + size, + mip_level_count: 1, + sample_count: 1, + dimension: TextureDimension::D2, + format: TextureFormat::bevy_default(), + usage: TextureUsages::RENDER_ATTACHMENT | TextureUsages::TEXTURE_BINDING, + }, + ); + + ViewMainTexture::Hdr { + hdr_texture: main_texture.default_view, + sampled_hdr_texture: sampled_main_texture, + ldr_texture: ldr_texture.default_view, + } + } else { + ViewMainTexture::Sdr { + texture: main_texture.default_view, + sampled_texture: sampled_main_texture, + } + }; ViewTarget { - hdr_texture: hdr_texture.default_view, - sampled_hdr_texture, - ldr_texture: ldr_texture.default_view, + main_texture, out_texture, } } pub fn get_color_attachment(&self, ops: Operations) -> RenderPassColorAttachment { - match &self.sampled_hdr_texture { + let (target, sampled) = match &self.main_texture { + ViewMainTexture::Hdr { + hdr_texture, + sampled_hdr_texture, + .. + } => (hdr_texture, sampled_hdr_texture), + ViewMainTexture::Sdr { + texture, + sampled_texture, + } => (texture, sampled_texture), + }; + match sampled { Some(sampled_target) => RenderPassColorAttachment { view: sampled_target, - resolve_target: Some(&self.hdr_texture), + resolve_target: Some(target), ops, }, None => RenderPassColorAttachment { - view: &self.hdr_texture, + view: target, resolve_target: None, ops, }, @@ -247,9 +295,9 @@ fn prepare_view_targets( msaa: Res, render_device: Res, mut texture_cache: ResMut, - cameras: Query<(Entity, &ExtractedCamera)>, + cameras: Query<(Entity, &ExtractedCamera, &ExtractedView)>, ) { - for (entity, camera) in cameras.iter() { + for (entity, camera, view) in cameras.iter() { if let Some(size) = camera.physical_size { if let Some(texture_view) = camera.target.get_texture_view(&windows, &images) { let view_target = ViewTarget::new( @@ -258,6 +306,7 @@ fn prepare_view_targets( &*msaa, size, texture_view.clone(), + view.hdr, ); /*let sampled_target = if msaa.samples > 1 { diff --git a/crates/bevy_sprite/src/render/mod.rs b/crates/bevy_sprite/src/render/mod.rs index f1a1e5b539538..a6ac5a7bfbf29 100644 --- a/crates/bevy_sprite/src/render/mod.rs +++ b/crates/bevy_sprite/src/render/mod.rs @@ -22,8 +22,11 @@ use bevy_render::{ }, render_resource::{std140::AsStd140, *}, renderer::{RenderDevice, RenderQueue}, + texture::BevyDefault, texture::Image, - view::{Msaa, ViewTarget, ViewUniform, ViewUniformOffset, ViewUniforms, Visibility}, + view::{ + ExtractedView, Msaa, ViewTarget, ViewUniform, ViewUniformOffset, ViewUniforms, Visibility, + }, RenderWorld, }; use bevy_transform::components::GlobalTransform; @@ -91,6 +94,7 @@ bitflags::bitflags! { pub struct SpritePipelineKey: u32 { const NONE = 0; const COLORED = (1 << 0); + const HDR = (1 << 1); const MSAA_RESERVED_BITS = SpritePipelineKey::MSAA_MASK_BITS << SpritePipelineKey::MSAA_SHIFT_BITS; } } @@ -107,6 +111,22 @@ impl SpritePipelineKey { pub fn msaa_samples(&self) -> u32 { ((self.bits >> Self::MSAA_SHIFT_BITS) & Self::MSAA_MASK_BITS) + 1 } + + pub fn from_colored(colored: bool) -> Self { + if colored { + SpritePipelineKey::COLORED + } else { + SpritePipelineKey::NONE + } + } + + pub fn from_hdr(hdr: bool) -> Self { + if hdr { + SpritePipelineKey::HDR + } else { + SpritePipelineKey::NONE + } + } } impl SpecializedPipeline for SpritePipeline { @@ -133,6 +153,15 @@ impl SpecializedPipeline for SpritePipeline { shader_defs.push("COLORED".to_string()); } + if key.contains(SpritePipelineKey::HDR) { + shader_defs.push("TONEMAPPING_IN_SPRITE_SHADER".to_string()); + } + + let format = match key.contains(SpritePipelineKey::HDR) { + true => ViewTarget::TEXTURE_FORMAT_HDR, + false => TextureFormat::bevy_default(), + }; + RenderPipelineDescriptor { vertex: VertexState { shader: SPRITE_SHADER_HANDLE.typed::(), @@ -145,7 +174,7 @@ impl SpecializedPipeline for SpritePipeline { shader_defs, entry_point: "fragment".into(), targets: vec![ColorTargetState { - format: ViewTarget::TEXTURE_FORMAT_HDR, + format, blend: Some(BlendState::ALPHA_BLENDING), write_mask: ColorWrites::ALL, }], @@ -344,7 +373,7 @@ pub fn queue_sprites( gpu_images: Res>, msaa: Res, mut extracted_sprites: ResMut, - mut views: Query<&mut RenderPhase>, + mut views: Query<(&mut RenderPhase, &ExtractedView)>, events: Res, ) { // If an image has changed, the GpuImage has (probably) changed @@ -356,6 +385,8 @@ pub fn queue_sprites( }; } + let msaa_key = SpritePipelineKey::from_msaa_samples(msaa.samples); + if let Some(view_binding) = view_uniforms.uniforms.binding() { let sprite_meta = &mut sprite_meta; @@ -373,20 +404,28 @@ pub fn queue_sprites( })); let draw_sprite_function = draw_functions.read().get_id::().unwrap(); - let key = SpritePipelineKey::from_msaa_samples(msaa.samples); - let pipeline = pipelines.specialize(&mut pipeline_cache, &sprite_pipeline, key); - let colored_pipeline = pipelines.specialize( - &mut pipeline_cache, - &sprite_pipeline, - key | SpritePipelineKey::COLORED, - ); // Vertex buffer indices let mut index = 0; let mut colored_index = 0; // FIXME: VisibleEntities is ignored - for mut transparent_phase in views.iter_mut() { + for (mut transparent_phase, view) in views.iter_mut() { + let pipeline = pipelines.specialize( + &mut pipeline_cache, + &sprite_pipeline, + SpritePipelineKey::from_colored(false) + | SpritePipelineKey::from_hdr(view.hdr) + | msaa_key, + ); + let colored_pipeline = pipelines.specialize( + &mut pipeline_cache, + &sprite_pipeline, + SpritePipelineKey::from_colored(true) + | SpritePipelineKey::from_hdr(view.hdr) + | msaa_key, + ); + let extracted_sprites = &mut extracted_sprites.sprites; let image_bind_groups = &mut *image_bind_groups; diff --git a/crates/bevy_sprite/src/render/sprite.wgsl b/crates/bevy_sprite/src/render/sprite.wgsl index 128536c691787..fc6dfc9dafae0 100644 --- a/crates/bevy_sprite/src/render/sprite.wgsl +++ b/crates/bevy_sprite/src/render/sprite.wgsl @@ -1,3 +1,7 @@ +#ifdef TONEMAPPING_IN_SPRITE_SHADER +#import bevy_core_pipeline::tonemapping +#endif + struct View { view_proj: mat4x4; world_position: vec3; @@ -41,5 +45,10 @@ fn fragment(in: VertexOutput) -> [[location(0)]] vec4 { #ifdef COLORED color = in.color * color; #endif + +#ifdef TONEMAPPING_IN_SPRITE_SHADER + color = vec4(reinhard_luminance(color.rgb), color.a); +#endif + return color; } \ No newline at end of file diff --git a/examples/shader/shader_defs.rs b/examples/shader/shader_defs.rs index 2e15a52c3684a..8865781bdea08 100644 --- a/examples/shader/shader_defs.rs +++ b/examples/shader/shader_defs.rs @@ -153,8 +153,9 @@ fn queue_custom( let view_row_2 = view_matrix.row(2); for (entity, mesh_handle, mesh_uniform, is_red) in material_meshes.iter() { if let Some(mesh) = render_meshes.get(mesh_handle) { - let key = - msaa_key | MeshPipelineKey::from_primitive_topology(mesh.primitive_topology); + let key = msaa_key + | MeshPipelineKey::from_primitive_topology(mesh.primitive_topology) + | MeshPipelineKey::from_hdr(view.hdr); let pipeline = pipelines .specialize( &mut pipeline_cache, From 21ba00d86d5b3fd196782e9fad5eb0db4368062f Mon Sep 17 00:00:00 2001 From: Jakob Hellermann Date: Sun, 20 Mar 2022 13:16:21 +0100 Subject: [PATCH 05/26] add UpscalingPipelineKey --- crates/bevy_core_pipeline/Cargo.toml | 4 +- crates/bevy_core_pipeline/src/lib.rs | 2 +- .../bevy_core_pipeline/src/upscaling/mod.rs | 40 ++++++++++++++++++- 3 files changed, 42 insertions(+), 4 deletions(-) diff --git a/crates/bevy_core_pipeline/Cargo.toml b/crates/bevy_core_pipeline/Cargo.toml index 671645e40889c..db325f7e3a19e 100644 --- a/crates/bevy_core_pipeline/Cargo.toml +++ b/crates/bevy_core_pipeline/Cargo.toml @@ -23,4 +23,6 @@ bevy_core = { path = "../bevy_core", version = "0.7.0-dev" } bevy_ecs = { path = "../bevy_ecs", version = "0.7.0-dev" } bevy_render = { path = "../bevy_render", version = "0.7.0-dev" } bevy_utils = { path = "../bevy_utils", version = "0.7.0-dev" } -bevy_reflect = { path = "../bevy_reflect", version = "0.7.0-dev" } \ No newline at end of file +bevy_reflect = { path = "../bevy_reflect", version = "0.7.0-dev" } + +bitflags = "1.2" \ No newline at end of file diff --git a/crates/bevy_core_pipeline/src/lib.rs b/crates/bevy_core_pipeline/src/lib.rs index 1abd923c1df01..b6efaaa2d7221 100644 --- a/crates/bevy_core_pipeline/src/lib.rs +++ b/crates/bevy_core_pipeline/src/lib.rs @@ -18,6 +18,7 @@ pub use clear_pass_driver::*; pub use main_pass_2d::*; pub use main_pass_3d::*; pub use main_pass_driver::*; +pub use upscaling::*; use std::ops::Range; @@ -41,7 +42,6 @@ use bevy_render::{ use tonemapping::TonemappingNode; use tonemapping::TonemappingPlugin; use upscaling::UpscalingNode; -use upscaling::UpscalingPlugin; /// When used as a resource, sets the color that is used to clear the screen between frames. /// diff --git a/crates/bevy_core_pipeline/src/upscaling/mod.rs b/crates/bevy_core_pipeline/src/upscaling/mod.rs index 157a8d1636cd5..8ca7b085e3ed8 100644 --- a/crates/bevy_core_pipeline/src/upscaling/mod.rs +++ b/crates/bevy_core_pipeline/src/upscaling/mod.rs @@ -70,8 +70,43 @@ impl FromWorld for UpscalingPipeline { } } +#[repr(u8)] +pub enum UpscalingMode { + Filtering = 0, + Nearest = 1, +} + +bitflags::bitflags! { + #[repr(transparent)] + pub struct UpscalingPipelineKey: u32 { + const NONE = 0; + const UPSCALING_MODE_RESERVED_BITS = UpscalingPipelineKey::UPSCALING_MODE_MASK_BITS << UpscalingPipelineKey::UPSCALING_MODE_SHIFT_BITS; + } +} + +impl UpscalingPipelineKey { + const UPSCALING_MODE_MASK_BITS: u32 = 0b1111; // enough for 16 different modes + const UPSCALING_MODE_SHIFT_BITS: u32 = 32 - 4; + + pub fn from_upscaling_mode(upscaling_mode: UpscalingMode) -> Self { + let upscaling_mode_bits = ((upscaling_mode as u32) & Self::UPSCALING_MODE_MASK_BITS) + << Self::UPSCALING_MODE_SHIFT_BITS; + UpscalingPipelineKey::from_bits(upscaling_mode_bits).unwrap() + } + + pub fn upscaling_mode(&self) -> UpscalingMode { + let upscaling_mode_bits = + (self.bits >> Self::UPSCALING_MODE_SHIFT_BITS) & Self::UPSCALING_MODE_MASK_BITS; + match upscaling_mode_bits { + 0 => UpscalingMode::Filtering, + 1 => UpscalingMode::Nearest, + other => panic!("invalid upscaling mode bits in UpscalingPipelineKey: {other}"), + } + } +} + impl SpecializedPipeline for UpscalingPipeline { - type Key = (); + type Key = UpscalingPipelineKey; fn specialize(&self, _: Self::Key) -> RenderPipelineDescriptor { RenderPipelineDescriptor { @@ -113,7 +148,8 @@ fn queue_upscaling_bind_groups( view_targets: Query>, ) { for entity in view_targets.iter() { - let pipeline = pipelines.specialize(&mut render_pipeline_cache, &upscaling_pipeline, ()); + let key = UpscalingPipelineKey::from_upscaling_mode(UpscalingMode::Filtering); + let pipeline = pipelines.specialize(&mut render_pipeline_cache, &upscaling_pipeline, key); commands.entity(entity).insert(UpscalingTarget { pipeline }); } From 08e4ff2fecfac8c0aa896b5016a021f0f61f0794 Mon Sep 17 00:00:00 2001 From: Jakob Hellermann Date: Fri, 24 Dec 2021 12:40:13 +0100 Subject: [PATCH 06/26] add bloom node --- Cargo.toml | 4 + crates/bevy_pbr/src/bloom/bloom.wgsl | 159 ++++ crates/bevy_pbr/src/bloom/mod.rs | 777 ++++++++++++++++++ crates/bevy_pbr/src/lib.rs | 4 + crates/bevy_render/src/render_resource/mod.rs | 8 +- examples/3d/bloom.rs | 66 ++ 6 files changed, 1014 insertions(+), 4 deletions(-) create mode 100644 crates/bevy_pbr/src/bloom/bloom.wgsl create mode 100644 crates/bevy_pbr/src/bloom/mod.rs create mode 100644 examples/3d/bloom.rs diff --git a/Cargo.toml b/Cargo.toml index cc7d80db0629d..69fcf96ae97a9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -179,6 +179,10 @@ path = "examples/3d/3d_scene.rs" name = "lighting" path = "examples/3d/lighting.rs" +[[example]] +name = "bloom" +path = "examples/3d/bloom.rs" + [[example]] name = "load_gltf" path = "examples/3d/load_gltf.rs" diff --git a/crates/bevy_pbr/src/bloom/bloom.wgsl b/crates/bevy_pbr/src/bloom/bloom.wgsl new file mode 100644 index 0000000000000..c2a0071f34d0e --- /dev/null +++ b/crates/bevy_pbr/src/bloom/bloom.wgsl @@ -0,0 +1,159 @@ +struct VertexOutput { + [[builtin(position)]] position: vec4; + [[location(0)]] uv: vec2; +}; + +var vertices: array, 3> = array, 3>( + vec2(-1.0, -1.0), + vec2(3.0, -1.0), + vec2(-1.0, 3.0), +); + +// full screen triangle vertex shader +[[stage(vertex)]] +fn vertex([[builtin(vertex_index)]] idx: u32) -> VertexOutput { + var out: VertexOutput; + + out.position = vec4(vertices[idx], 0.0, 1.0); + out.uv = vertices[idx] * vec2(0.5, -0.5); + out.uv = out.uv + 0.5; + + return out; +} + +struct Uniforms { + threshold: f32; + knee: f32; + scale: f32; +}; + +[[group(0), binding(0)]] +var org: texture_2d; + +[[group(0), binding(1)]] +var org_sampler: sampler; + +[[group(0), binding(2)]] +var uniforms: Uniforms; + +[[group(0), binding(3)]] +var up: texture_2d; + +fn quadratic_threshold(color: vec4, threshold: f32, curve: vec3) -> vec4 { + let br = max(max(color.r, color.g), color.b); + + var rq: f32 = clamp(br - curve.x, 0.0, curve.y); + rq = curve.z * rq * rq; + + return color * max(rq, br - threshold) / max(br, 0.0001); +} + +// samples org around the supplied uv using a filter +// +// o o o +// o o +// o o o +// o o +// o o o +// +// this is used because it has a number of advantages that +// outway the cost of 13 samples that basically boil down +// to it looking better +// +// these advantages are outlined in a youtube video by the Cherno: +// https://www.youtube.com/watch?v=tI70-HIc5ro +fn sample_13_tap(uv: vec2, scale: vec2) -> vec4 { + let a = textureSample(org, org_sampler, uv + vec2(-1.0, -1.0) * scale); + let b = textureSample(org, org_sampler, uv + vec2( 0.0, -1.0) * scale); + let c = textureSample(org, org_sampler, uv + vec2( 1.0, -1.0) * scale); + let d = textureSample(org, org_sampler, uv + vec2(-0.5, -0.5) * scale); + let e = textureSample(org, org_sampler, uv + vec2( 0.5, -0.5) * scale); + let f = textureSample(org, org_sampler, uv + vec2(-1.0, 0.0) * scale); + let g = textureSample(org, org_sampler, uv + vec2( 0.0, 0.0) * scale); + let h = textureSample(org, org_sampler, uv + vec2( 1.0, 0.0) * scale); + let i = textureSample(org, org_sampler, uv + vec2(-0.5, 0.5) * scale); + let j = textureSample(org, org_sampler, uv + vec2( 0.5, 0.5) * scale); + let k = textureSample(org, org_sampler, uv + vec2(-1.0, 1.0) * scale); + let l = textureSample(org, org_sampler, uv + vec2( 0.0, 1.0) * scale); + let m = textureSample(org, org_sampler, uv + vec2( 1.0, 1.0) * scale); + + let div = (1.0 / 4.0) * vec2(0.5, 0.125); + + var o: vec4 = (d + e + i + j) * div.x; + o = o + (a + b + g + f) * div.y; + o = o + (b + c + h + g) * div.y; + o = o + (f + g + l + k) * div.y; + o = o + (g + h + m + l) * div.y; + + return o; +} + +// samples org using a 3x3 tent filter +// +// NOTE: use a 2x2 filter for better perf, but 3x3 looks better +fn sample_3x3_tent(uv: vec2, scale: vec2) -> vec4 { + let d = vec4(1.0, 1.0, -1.0, 0.0); + + var s: vec4 = textureSample(org, org_sampler, uv - d.xy * scale); + s = s + textureSample(org, org_sampler, uv - d.wy * scale) * 2.0; + s = s + textureSample(org, org_sampler, uv - d.zy * scale); + + s = s + textureSample(org, org_sampler, uv + d.zw * scale) * 2.0; + s = s + textureSample(org, org_sampler, uv ) * 4.0; + s = s + textureSample(org, org_sampler, uv + d.xw * scale) * 2.0; + + s = s + textureSample(org, org_sampler, uv + d.zy * scale); + s = s + textureSample(org, org_sampler, uv + d.wy * scale) * 2.0; + s = s + textureSample(org, org_sampler, uv + d.xy * scale); + + return s / 16.0; +} + +[[stage(fragment)]] +fn down_sample_pre_filter(in: VertexOutput) -> [[location(0)]] vec4 { + let texel_size = 1.0 / vec2(textureDimensions(org)); + + let scale = texel_size; + + let curve = vec3( + uniforms.threshold - uniforms.knee, + uniforms.knee * 2.0, + 0.25 / uniforms.knee, + ); + + var o: vec4 = sample_13_tap(in.uv, scale); + + o = quadratic_threshold(o, uniforms.threshold, curve); + o = max(o, vec4(0.00001)); + + return o; +} + +[[stage(fragment)]] +fn down_sample(in: VertexOutput) -> [[location(0)]] vec4 { + let texel_size = 1.0 / vec2(textureDimensions(org)); + + let scale = texel_size; + + return sample_13_tap(in.uv, scale); +} + +[[stage(fragment)]] +fn up_sample(in: VertexOutput) -> [[location(0)]] vec4 { + let texel_size = 1.0 / vec2(textureDimensions(org)); + + let up_sample = sample_3x3_tent(in.uv, texel_size * uniforms.scale); + var color: vec4 = textureSample(up, org_sampler, in.uv); + color = vec4(color.rgb + up_sample.rgb, up_sample.a); + + return color; +} + +[[stage(fragment)]] +fn up_sample_final(in: VertexOutput) -> [[location(0)]] vec4 { + let texel_size = 1.0 / vec2(textureDimensions(org)); + + let up_sample = sample_3x3_tent(in.uv, texel_size * uniforms.scale); + + return up_sample; +} diff --git a/crates/bevy_pbr/src/bloom/mod.rs b/crates/bevy_pbr/src/bloom/mod.rs new file mode 100644 index 0000000000000..38423967fa99b --- /dev/null +++ b/crates/bevy_pbr/src/bloom/mod.rs @@ -0,0 +1,777 @@ +use std::{num::NonZeroU32, sync::Mutex}; + +use bevy_app::Plugin; +use bevy_core_pipeline::MainPass3dNode; +use bevy_ecs::prelude::*; +use bevy_render::{ + render_graph::{Node, NodeRunError, RenderGraph, RenderGraphContext, SlotInfo, SlotType}, + render_resource::std140::{AsStd140, Std140}, + render_resource::*, + renderer::{RenderContext, RenderDevice, RenderQueue}, + view::{ExtractedView, ViewTarget}, + RenderApp, RenderStage, +}; + +pub struct BloomPlugin; + +impl Plugin for BloomPlugin { + fn build(&self, app: &mut bevy_app::App) { + app.init_resource::(); + + let render_app = app.sub_app_mut(RenderApp); + render_app + .init_resource::() + .add_system_to_stage(RenderStage::Extract, extract_bloom_settings); + + let bloom_node = BloomNode::new(&mut render_app.world); + + let mut render_graph = render_app.world.get_resource_mut::().unwrap(); + + let draw_3d_graph = render_graph + .get_sub_graph_mut(bevy_core_pipeline::draw_3d_graph::NAME) + .unwrap(); + draw_3d_graph.add_node(BloomNode::NODE_NAME, bloom_node); + + draw_3d_graph + .add_node_edge( + bevy_core_pipeline::draw_3d_graph::node::MAIN_PASS, + BloomNode::NODE_NAME, + ) + .unwrap(); + draw_3d_graph + .add_node_edge( + BloomNode::NODE_NAME, + bevy_core_pipeline::draw_3d_graph::node::TONEMAPPING, + ) + .unwrap(); + + draw_3d_graph + .add_slot_edge( + draw_3d_graph.input_node().unwrap().id, + bevy_core_pipeline::draw_3d_graph::input::VIEW_ENTITY, + BloomNode::NODE_NAME, + BloomNode::IN_VIEW, + ) + .unwrap(); + + draw_3d_graph + .add_slot_edge( + bevy_core_pipeline::draw_3d_graph::node::MAIN_PASS, + MainPass3dNode::OUT_TEXTURE, + BloomNode::NODE_NAME, + BloomNode::IN_HDR, + ) + .unwrap(); + } +} + +fn extract_bloom_settings(mut commands: Commands, bloom_settings: Res) { + commands.insert_resource(bloom_settings.into_inner().clone()); +} + +/// Resources used by [`BloomNode`]. +pub struct BloomShaders { + pub down_sampling_pipeline: RenderPipeline, + pub down_sampling_pre_filter_pipeline: RenderPipeline, + pub up_sampling_pipeline: RenderPipeline, + pub up_sampling_final_pipeline: RenderPipeline, + pub shader_module: ShaderModule, + pub down_layout: BindGroupLayout, + pub up_layout: BindGroupLayout, + pub sampler: Sampler, +} + +impl FromWorld for BloomShaders { + fn from_world(world: &mut World) -> Self { + let render_device = world.get_resource::().unwrap(); + let shader = ShaderModuleDescriptor { + label: Some("bloom shader"), + source: ShaderSource::Wgsl(include_str!("bloom.wgsl").into()), + }; + let shader_module = render_device.create_shader_module(&shader); + + let down_layout = render_device.create_bind_group_layout(&BindGroupLayoutDescriptor { + label: Some("bloom_down_sampling_layout"), + entries: &[ + BindGroupLayoutEntry { + binding: 0, + ty: BindingType::Texture { + sample_type: TextureSampleType::Float { filterable: true }, + view_dimension: TextureViewDimension::D2, + multisampled: false, + }, + visibility: ShaderStages::VERTEX_FRAGMENT, + count: None, + }, + BindGroupLayoutEntry { + binding: 1, + ty: BindingType::Sampler(SamplerBindingType::Filtering), + visibility: ShaderStages::VERTEX_FRAGMENT, + count: None, + }, + BindGroupLayoutEntry { + binding: 2, + ty: BindingType::Buffer { + ty: BufferBindingType::Uniform, + has_dynamic_offset: false, + min_binding_size: None, + }, + visibility: ShaderStages::VERTEX_FRAGMENT, + count: None, + }, + ], + }); + + let up_layout = render_device.create_bind_group_layout(&BindGroupLayoutDescriptor { + label: Some("bloom_up_sampling_layout"), + entries: &[ + BindGroupLayoutEntry { + binding: 0, + ty: BindingType::Texture { + sample_type: TextureSampleType::Float { filterable: true }, + view_dimension: TextureViewDimension::D2, + multisampled: false, + }, + visibility: ShaderStages::VERTEX_FRAGMENT, + count: None, + }, + BindGroupLayoutEntry { + binding: 1, + ty: BindingType::Sampler(SamplerBindingType::Filtering), + visibility: ShaderStages::VERTEX_FRAGMENT, + count: None, + }, + BindGroupLayoutEntry { + binding: 2, + ty: BindingType::Buffer { + ty: BufferBindingType::Uniform, + has_dynamic_offset: false, + min_binding_size: None, + }, + visibility: ShaderStages::VERTEX_FRAGMENT, + count: None, + }, + BindGroupLayoutEntry { + binding: 3, + ty: BindingType::Texture { + sample_type: TextureSampleType::Float { filterable: true }, + view_dimension: TextureViewDimension::D2, + multisampled: false, + }, + visibility: ShaderStages::VERTEX_FRAGMENT, + count: None, + }, + ], + }); + + let down_pipeline_layout = + render_device.create_pipeline_layout(&PipelineLayoutDescriptor { + label: Some("bloom_down_sampling_layout"), + bind_group_layouts: &[&down_layout], + push_constant_ranges: &[], + }); + + let up_pipeline_layout = render_device.create_pipeline_layout(&PipelineLayoutDescriptor { + label: Some("bloom_up_sampling_layout"), + bind_group_layouts: &[&up_layout], + push_constant_ranges: &[], + }); + + let down_sampling_pre_filter_pipeline = + render_device.create_render_pipeline(&RawRenderPipelineDescriptor { + label: Some("bloom_down_sampling_pre_filter_pipeline"), + layout: Some(&down_pipeline_layout), + vertex: RawVertexState { + module: &shader_module, + entry_point: "vertex", + buffers: &[], + }, + fragment: Some(RawFragmentState { + module: &shader_module, + entry_point: "down_sample_pre_filter", + targets: &[ColorTargetState { + format: ViewTarget::TEXTURE_FORMAT_HDR, + blend: None, + write_mask: ColorWrites::ALL, + }], + }), + primitive: PrimitiveState { + cull_mode: Some(Face::Back), + ..Default::default() + }, + multisample: Default::default(), + depth_stencil: None, + multiview: None, + }); + + let down_sampling_pipeline = + render_device.create_render_pipeline(&RawRenderPipelineDescriptor { + label: Some("bloom_down_sampling_pipeline"), + layout: Some(&down_pipeline_layout), + vertex: RawVertexState { + module: &shader_module, + entry_point: "vertex", + buffers: &[], + }, + fragment: Some(RawFragmentState { + module: &shader_module, + entry_point: "down_sample", + targets: &[ColorTargetState { + format: ViewTarget::TEXTURE_FORMAT_HDR, + blend: None, + write_mask: ColorWrites::ALL, + }], + }), + primitive: PrimitiveState { + cull_mode: Some(Face::Back), + ..Default::default() + }, + multisample: Default::default(), + depth_stencil: None, + multiview: None, + }); + + let up_sampling_pipeline = + render_device.create_render_pipeline(&RawRenderPipelineDescriptor { + label: Some("bloom_up_sampling_pipeline"), + layout: Some(&up_pipeline_layout), + vertex: RawVertexState { + module: &shader_module, + entry_point: "vertex", + buffers: &[], + }, + fragment: Some(RawFragmentState { + module: &shader_module, + entry_point: "up_sample", + targets: &[ColorTargetState { + format: ViewTarget::TEXTURE_FORMAT_HDR, + blend: None, + write_mask: ColorWrites::ALL, + }], + }), + primitive: PrimitiveState { + cull_mode: Some(Face::Back), + ..Default::default() + }, + multisample: Default::default(), + depth_stencil: None, + multiview: None, + }); + + let up_sampling_final_pipeline = + render_device.create_render_pipeline(&RawRenderPipelineDescriptor { + label: Some("bloom_up_sampling_final_pipeline"), + layout: Some(&down_pipeline_layout), + vertex: RawVertexState { + module: &shader_module, + entry_point: "vertex", + buffers: &[], + }, + fragment: Some(RawFragmentState { + module: &shader_module, + entry_point: "up_sample_final", + targets: &[ColorTargetState { + format: ViewTarget::TEXTURE_FORMAT_HDR, + blend: Some(BlendState { + color: BlendComponent { + src_factor: BlendFactor::One, + dst_factor: BlendFactor::One, + operation: BlendOperation::Add, + }, + alpha: BlendComponent::REPLACE, + }), + write_mask: ColorWrites::ALL, + }], + }), + primitive: PrimitiveState { + cull_mode: Some(Face::Back), + ..Default::default() + }, + multisample: Default::default(), + depth_stencil: None, + multiview: None, + }); + + let sampler = render_device.create_sampler(&SamplerDescriptor { + min_filter: FilterMode::Linear, + mag_filter: FilterMode::Linear, + address_mode_u: AddressMode::ClampToEdge, + address_mode_v: AddressMode::ClampToEdge, + ..Default::default() + }); + + BloomShaders { + down_sampling_pre_filter_pipeline, + down_sampling_pipeline, + up_sampling_pipeline, + up_sampling_final_pipeline, + shader_module, + down_layout, + up_layout, + sampler, + } + } +} + +struct MipChain { + mips: u32, + scale: f32, + width: u32, + height: u32, + target_id: TextureViewId, + tex_a: Texture, + tex_b: Texture, + pre_filter_bind_group: BindGroup, + down_sampling_bind_groups: Vec, + up_sampling_bind_groups: Vec, + up_sampling_final_bind_group: BindGroup, +} + +impl MipChain { + fn new( + render_device: &RenderDevice, + bloom_shaders: &BloomShaders, + uniforms_buffer: &Buffer, + hdr_target: &TextureView, + width: u32, + height: u32, + ) -> Self { + let min_element = width.min(height) / 2; + let mut mips = 1; + + while min_element / 2u32.pow(mips) > 4 { + mips += 1; + } + + let size = Extent3d { + width: (width / 2).max(1), + height: (height / 2).max(1), + depth_or_array_layers: 1, + }; + + let tex_a = render_device.create_texture(&TextureDescriptor { + label: Some("bloom_tex_a"), + size, + mip_level_count: mips, + sample_count: 1, + dimension: TextureDimension::D2, + format: ViewTarget::TEXTURE_FORMAT_HDR, + usage: TextureUsages::COPY_DST + | TextureUsages::COPY_SRC + | TextureUsages::RENDER_ATTACHMENT + | TextureUsages::TEXTURE_BINDING, + }); + + let tex_b = render_device.create_texture(&TextureDescriptor { + label: Some("bloom_tex_b"), + size, + mip_level_count: mips, + sample_count: 1, + dimension: TextureDimension::D2, + format: ViewTarget::TEXTURE_FORMAT_HDR, + usage: TextureUsages::COPY_DST + | TextureUsages::COPY_SRC + | TextureUsages::RENDER_ATTACHMENT + | TextureUsages::TEXTURE_BINDING, + }); + + let pre_filter_bind_group = render_device.create_bind_group(&BindGroupDescriptor { + label: Some("bloom_pre_filter_bind_group"), + layout: &bloom_shaders.down_layout, + entries: &[ + BindGroupEntry { + binding: 0, + resource: BindingResource::TextureView(hdr_target), + }, + BindGroupEntry { + binding: 1, + resource: BindingResource::Sampler(&bloom_shaders.sampler), + }, + BindGroupEntry { + binding: 2, + resource: uniforms_buffer.as_entire_binding(), + }, + ], + }); + + let mut down_sampling_bind_groups = Vec::new(); + + for mip in 1..mips { + let view = tex_a.create_view(&TextureViewDescriptor { + label: None, + base_mip_level: mip - 1, + mip_level_count: Some(unsafe { NonZeroU32::new_unchecked(1) }), + ..Default::default() + }); + + let bind_group = render_device.create_bind_group(&BindGroupDescriptor { + label: Some("bloom_down_sampling_bind_group"), + layout: &bloom_shaders.down_layout, + entries: &[ + BindGroupEntry { + binding: 0, + resource: BindingResource::TextureView(&view), + }, + BindGroupEntry { + binding: 1, + resource: BindingResource::Sampler(&bloom_shaders.sampler), + }, + BindGroupEntry { + binding: 2, + resource: uniforms_buffer.as_entire_binding(), + }, + ], + }); + + down_sampling_bind_groups.push(bind_group); + } + + let mut up_sampling_bind_groups = Vec::new(); + + for mip in 1..mips { + let up = tex_a.create_view(&TextureViewDescriptor { + label: None, + base_mip_level: mip - 1, + mip_level_count: Some(unsafe { NonZeroU32::new_unchecked(1) }), + ..Default::default() + }); + + let org_tex = if mip == mips - 1 { &tex_a } else { &tex_b }; + + let org = org_tex.create_view(&TextureViewDescriptor { + label: None, + base_mip_level: mip, + mip_level_count: Some(unsafe { NonZeroU32::new_unchecked(1) }), + ..Default::default() + }); + + let bind_group = render_device.create_bind_group(&BindGroupDescriptor { + label: Some("bloom_up_sampling_bind_group"), + layout: &bloom_shaders.up_layout, + entries: &[ + BindGroupEntry { + binding: 0, + resource: BindingResource::TextureView(&org), + }, + BindGroupEntry { + binding: 1, + resource: BindingResource::Sampler(&bloom_shaders.sampler), + }, + BindGroupEntry { + binding: 2, + resource: uniforms_buffer.as_entire_binding(), + }, + BindGroupEntry { + binding: 3, + resource: BindingResource::TextureView(&up), + }, + ], + }); + + up_sampling_bind_groups.push(bind_group); + } + + let org = tex_b.create_view(&TextureViewDescriptor { + label: None, + base_mip_level: 0, + ..Default::default() + }); + + let up_sampling_final_bind_group = render_device.create_bind_group(&BindGroupDescriptor { + label: Some("bloom_up_sampling_final_bind_group"), + layout: &bloom_shaders.down_layout, + entries: &[ + BindGroupEntry { + binding: 0, + resource: BindingResource::TextureView(&org), + }, + BindGroupEntry { + binding: 1, + resource: BindingResource::Sampler(&bloom_shaders.sampler), + }, + BindGroupEntry { + binding: 2, + resource: uniforms_buffer.as_entire_binding(), + }, + ], + }); + + Self { + mips, + scale: (min_element / 2u32.pow(mips)) as f32 / 8.0, + width, + height, + target_id: hdr_target.id(), + tex_a, + tex_b, + pre_filter_bind_group, + down_sampling_bind_groups, + up_sampling_bind_groups, + up_sampling_final_bind_group, + } + } +} + +#[repr(C)] +#[derive(Copy, Clone, AsStd140, Default, Debug)] +struct Uniforms { + threshold: f32, + knee: f32, + scale: f32, +} + +/// Settings for bloom. +#[derive(Clone, Debug)] +pub struct BloomSettings { + /// Enables bloom. + pub enabled: bool, + /// Threshold for for bloom to apply. + pub threshold: f32, + /// Adjusts the threshold curve. + pub knee: f32, + /// Scale used when up sampling. + pub up_sample_scale: f32, +} + +impl Default for BloomSettings { + #[inline] + fn default() -> Self { + Self { + enabled: true, + threshold: 1.0, + knee: 0.1, + up_sample_scale: 1.0, + } + } +} + +/// Applies bloom effect to the input texture. +/// +/// Use [`BloomSettings`] to configure the effect at runtime. +pub struct BloomNode { + query: QueryState<&'static ExtractedView>, + uniforms_buffer: Option, + mip_chain: Mutex>, +} + +impl BloomNode { + pub fn new(render_world: &mut World) -> Self { + BloomNode { + query: QueryState::new(render_world), + uniforms_buffer: None, + mip_chain: Mutex::new(None), + } + } +} + +impl BloomNode { + pub const NODE_NAME: &'static str = "bloom"; + pub const IN_VIEW: &'static str = "view_entity"; + pub const IN_HDR: &'static str = "hdr_target"; +} + +impl Node for BloomNode { + fn input(&self) -> Vec { + vec![ + SlotInfo::new(Self::IN_VIEW, SlotType::Entity), + SlotInfo::new(Self::IN_HDR, SlotType::TextureView), + ] + } + + fn update(&mut self, world: &mut World) { + self.query.update_archetypes(world); + + if self.uniforms_buffer.is_none() { + let render_device = world.get_resource::().unwrap(); + + let buffer = render_device.create_buffer(&BufferDescriptor { + label: Some("bloom_uniforms_buffer"), + size: Uniforms::std140_size_static() as u64, + mapped_at_creation: false, + usage: BufferUsages::COPY_DST | BufferUsages::UNIFORM, + }); + + self.uniforms_buffer = Some(buffer); + } + } + + fn run( + &self, + graph: &mut RenderGraphContext, + render_context: &mut RenderContext, + world: &World, + ) -> Result<(), NodeRunError> { + let view_entity = graph.get_input_entity(Self::IN_VIEW)?; + + let view = match self.query.get_manual(world, view_entity) { + Ok(view) => view, + Err(_) => return Ok(()), + }; + + let bloom_shaders = world.get_resource::().unwrap(); + + let render_queue = world.get_resource::().unwrap(); + let settings = world.get_resource::().unwrap(); + + if !settings.enabled { + return Ok(()); + } + + let hdr_target = graph.get_input_texture(Self::IN_HDR)?; + + let uniforms_buffer = self.uniforms_buffer.as_ref().unwrap(); + let mut mip_chain = self.mip_chain.lock().unwrap(); + + let mip_chain = if let Some(ref mut mip_chain) = *mip_chain { + // if the window changes but the size of the new window doesn't, using ExtractedWindow.size_changed + // wouldn't trigger a resize, comparing the size ensures that it does + if mip_chain.width != view.width + || mip_chain.height != view.height + || mip_chain.target_id != hdr_target.id() + { + *mip_chain = MipChain::new( + &render_context.render_device, + bloom_shaders, + uniforms_buffer, + hdr_target, + view.width, + view.height, + ); + } + + mip_chain + } else { + *mip_chain = Some(MipChain::new( + &render_context.render_device, + bloom_shaders, + uniforms_buffer, + hdr_target, + view.width, + view.height, + )); + + mip_chain.as_ref().unwrap() + }; + + let uniforms = Uniforms { + threshold: settings.threshold, + knee: settings.knee, + scale: settings.up_sample_scale * mip_chain.scale, + }; + + render_queue.write_buffer(uniforms_buffer, 0, uniforms.as_std140().as_bytes()); + + let view = mip_chain.tex_a.create_view(&TextureViewDescriptor { + mip_level_count: Some(unsafe { NonZeroU32::new_unchecked(1) }), + ..Default::default() + }); + + { + let mut pre_filter_pass = + render_context + .command_encoder + .begin_render_pass(&RenderPassDescriptor { + label: Some("bloom_pre_filter_pass"), + color_attachments: &[RenderPassColorAttachment { + view: &view, + resolve_target: None, + ops: Operations { + load: LoadOp::Clear(RawColor::BLACK), + store: true, + }, + }], + depth_stencil_attachment: None, + }); + + pre_filter_pass.set_pipeline(&bloom_shaders.down_sampling_pre_filter_pipeline); + pre_filter_pass.set_bind_group(0, &mip_chain.pre_filter_bind_group, &[]); + pre_filter_pass.draw(0..3, 0..1); + } + + for mip in 1..mip_chain.mips { + let view = mip_chain.tex_a.create_view(&TextureViewDescriptor { + base_mip_level: mip, + mip_level_count: Some(unsafe { NonZeroU32::new_unchecked(1) }), + ..Default::default() + }); + + let mut down_sampling_pass = + render_context + .command_encoder + .begin_render_pass(&RenderPassDescriptor { + label: Some("bloom_down_sampling_pass"), + color_attachments: &[RenderPassColorAttachment { + view: &view, + resolve_target: None, + ops: Operations { + load: LoadOp::Clear(RawColor::BLACK), + store: true, + }, + }], + depth_stencil_attachment: None, + }); + + down_sampling_pass.set_pipeline(&bloom_shaders.down_sampling_pipeline); + down_sampling_pass.set_bind_group( + 0, + &mip_chain.down_sampling_bind_groups[mip as usize - 1], + &[], + ); + down_sampling_pass.draw(0..3, 0..1); + } + + for mip in (1..mip_chain.mips).rev() { + let view = mip_chain.tex_b.create_view(&TextureViewDescriptor { + base_mip_level: mip - 1, + mip_level_count: Some(unsafe { NonZeroU32::new_unchecked(1) }), + ..Default::default() + }); + + let mut up_sampling_pass = + render_context + .command_encoder + .begin_render_pass(&RenderPassDescriptor { + label: Some("bloom_up_sampling_pass"), + color_attachments: &[RenderPassColorAttachment { + view: &view, + resolve_target: None, + ops: Operations { + load: LoadOp::Clear(RawColor::BLACK), + store: true, + }, + }], + depth_stencil_attachment: None, + }); + + up_sampling_pass.set_pipeline(&bloom_shaders.up_sampling_pipeline); + up_sampling_pass.set_bind_group( + 0, + &mip_chain.up_sampling_bind_groups[mip as usize - 1], + &[], + ); + up_sampling_pass.draw(0..3, 0..1); + } + + let mut up_sampling_final_pass = + render_context + .command_encoder + .begin_render_pass(&RenderPassDescriptor { + label: Some("bloom_up_sampling_final_pass"), + color_attachments: &[RenderPassColorAttachment { + view: hdr_target, + resolve_target: None, + ops: Operations { + load: LoadOp::Load, + store: true, + }, + }], + depth_stencil_attachment: None, + }); + + up_sampling_final_pass.set_pipeline(&bloom_shaders.up_sampling_final_pipeline); + up_sampling_final_pass.set_bind_group(0, &mip_chain.up_sampling_final_bind_group, &[]); + up_sampling_final_pass.draw(0..3, 0..1); + + Ok(()) + } +} diff --git a/crates/bevy_pbr/src/lib.rs b/crates/bevy_pbr/src/lib.rs index 94234fc73388e..2282b349731ae 100644 --- a/crates/bevy_pbr/src/lib.rs +++ b/crates/bevy_pbr/src/lib.rs @@ -1,6 +1,7 @@ pub mod wireframe; mod alpha; +pub mod bloom; mod bundle; mod light; mod material; @@ -8,6 +9,7 @@ mod pbr_material; mod render; pub use alpha::*; +use bloom::BloomPlugin; pub use bundle::*; pub use light::*; pub use material::*; @@ -65,6 +67,8 @@ impl Plugin for PbrPlugin { Shader::from_wgsl ); + app.add_plugin(BloomPlugin); + app.register_type::() .register_type::() .register_type::() diff --git a/crates/bevy_render/src/render_resource/mod.rs b/crates/bevy_render/src/render_resource/mod.rs index 8089199f790da..afd824c8601d9 100644 --- a/crates/bevy_render/src/render_resource/mod.rs +++ b/crates/bevy_render/src/render_resource/mod.rs @@ -25,10 +25,10 @@ pub use wgpu::{ util::BufferInitDescriptor, AdapterInfo as WgpuAdapterInfo, AddressMode, BindGroupDescriptor, BindGroupEntry, BindGroupLayoutDescriptor, BindGroupLayoutEntry, BindingResource, BindingType, BlendComponent, BlendFactor, BlendOperation, BlendState, BufferAddress, BufferBinding, - BufferBindingType, BufferDescriptor, BufferSize, BufferUsages, ColorTargetState, ColorWrites, - CommandEncoder, CommandEncoderDescriptor, CompareFunction, ComputePassDescriptor, - ComputePipelineDescriptor, DepthBiasState, DepthStencilState, Extent3d, Face, - Features as WgpuFeatures, FilterMode, FragmentState as RawFragmentState, FrontFace, + BufferBindingType, BufferDescriptor, BufferSize, BufferUsages, Color as RawColor, + ColorTargetState, ColorWrites, CommandEncoder, CommandEncoderDescriptor, CompareFunction, + ComputePassDescriptor, ComputePipelineDescriptor, DepthBiasState, DepthStencilState, Extent3d, + Face, Features as WgpuFeatures, FilterMode, FragmentState as RawFragmentState, FrontFace, ImageCopyBuffer, ImageCopyBufferBase, ImageCopyTexture, ImageCopyTextureBase, ImageDataLayout, ImageSubresourceRange, IndexFormat, Limits as WgpuLimits, LoadOp, MapMode, MultisampleState, Operations, Origin3d, PipelineLayout, PipelineLayoutDescriptor, PolygonMode, PrimitiveState, diff --git a/examples/3d/bloom.rs b/examples/3d/bloom.rs new file mode 100644 index 0000000000000..8cfd3379060e2 --- /dev/null +++ b/examples/3d/bloom.rs @@ -0,0 +1,66 @@ +use bevy::prelude::*; + +fn main() { + App::new() + .add_plugins(DefaultPlugins) + .insert_resource(BloomSettings { + enabled: true, + threshold: 1.0, + knee: 0.1, + up_sample_scale: 1.0, + }) + .add_startup_system(setup) + .add_system(bounce) + .run(); +} + +#[derive(Component)] +struct Bouncing; + +/// set up a simple 3D scene +fn setup( + mut commands: Commands, + mut meshes: ResMut>, + mut materials: ResMut>, +) { + let mesh = meshes.add( + shape::Icosphere { + radius: 0.5, + subdivisions: 5, + } + .into(), + ); + + let material = materials.add(StandardMaterial { + emissive: Color::rgb_linear(1.0, 0.3, 0.2) * 4.0, + ..Default::default() + }); + + for x in -10..10 { + for z in -10..10 { + commands + .spawn_bundle(PbrBundle { + mesh: mesh.clone(), + material: material.clone(), + transform: Transform::from_xyz(x as f32 * 2.0, 0.0, z as f32 * 2.0), + ..Default::default() + }) + .insert(Bouncing); + } + } + + // camera + commands.spawn_bundle(PerspectiveCameraBundle { + transform: Transform::from_xyz(-2.0, 2.5, 5.0).looking_at(Vec3::ZERO, Vec3::Y), + ..Default::default() + }); +} + +fn bounce(time: Res