diff --git a/crates/bevy_core_pipeline/Cargo.toml b/crates/bevy_core_pipeline/Cargo.toml index 42612f77bf0c3..2f984c0404575 100644 --- a/crates/bevy_core_pipeline/Cargo.toml +++ b/crates/bevy_core_pipeline/Cargo.toml @@ -19,6 +19,7 @@ webgl = [] [dependencies] # bevy bevy_app = { path = "../bevy_app", version = "0.9.0-dev" } +bevy_asset = { path = "../bevy_asset", version = "0.9.0-dev" } bevy_derive = { path = "../bevy_derive", version = "0.9.0-dev" } bevy_ecs = { path = "../bevy_ecs", version = "0.9.0-dev" } bevy_reflect = { path = "../bevy_reflect", version = "0.9.0-dev" } @@ -27,4 +28,5 @@ bevy_transform = { path = "../bevy_transform", version = "0.9.0-dev" } bevy_utils = { path = "../bevy_utils", version = "0.9.0-dev" } serde = { version = "1", features = ["derive"] } +bitflags = "1.2" radsort = "0.1" diff --git a/crates/bevy_core_pipeline/src/core_2d/camera_2d.rs b/crates/bevy_core_pipeline/src/core_2d/camera_2d.rs index b94b59a0b6525..ca426788529fe 100644 --- a/crates/bevy_core_pipeline/src/core_2d/camera_2d.rs +++ b/crates/bevy_core_pipeline/src/core_2d/camera_2d.rs @@ -1,4 +1,4 @@ -use crate::clear_color::ClearColorConfig; +use crate::{clear_color::ClearColorConfig, tonemapping::Tonemapping}; use bevy_ecs::{prelude::*, query::QueryItem}; use bevy_reflect::Reflect; use bevy_render::{ @@ -34,6 +34,7 @@ pub struct Camera2dBundle { pub transform: Transform, pub global_transform: GlobalTransform, pub camera_2d: Camera2d, + pub tonemapping: Tonemapping, } impl Default for Camera2dBundle { @@ -74,6 +75,7 @@ impl Camera2dBundle { global_transform: Default::default(), camera: Camera::default(), camera_2d: Camera2d::default(), + tonemapping: Tonemapping { is_enabled: false }, } } } diff --git a/crates/bevy_core_pipeline/src/core_2d/mod.rs b/crates/bevy_core_pipeline/src/core_2d/mod.rs index cc0ddc184746b..b390a42ca1a8c 100644 --- a/crates/bevy_core_pipeline/src/core_2d/mod.rs +++ b/crates/bevy_core_pipeline/src/core_2d/mod.rs @@ -8,6 +8,8 @@ pub mod graph { } pub mod node { pub const MAIN_PASS: &str = "main_pass"; + pub const TONEMAPPING: &str = "tonemapping"; + pub const UPSCALING: &str = "upscaling"; } } @@ -30,6 +32,8 @@ use bevy_render::{ use bevy_utils::FloatOrd; use std::ops::Range; +use crate::{tonemapping::TonemappingNode, upscaling::UpscalingNode}; + pub struct Core2dPlugin; impl Plugin for Core2dPlugin { @@ -52,10 +56,14 @@ impl Plugin for Core2dPlugin { ); let pass_node_2d = MainPass2dNode::new(&mut render_app.world); + let tonemapping = TonemappingNode::new(&mut render_app.world); + let upscaling = 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(graph::node::MAIN_PASS, pass_node_2d); + draw_2d_graph.add_node(graph::node::TONEMAPPING, tonemapping); + draw_2d_graph.add_node(graph::node::UPSCALING, upscaling); let input_node_id = draw_2d_graph.set_input(vec![SlotInfo::new( graph::input::VIEW_ENTITY, SlotType::Entity, @@ -68,6 +76,28 @@ impl Plugin for Core2dPlugin { MainPass2dNode::IN_VIEW, ) .unwrap(); + draw_2d_graph + .add_slot_edge( + input_node_id, + graph::input::VIEW_ENTITY, + graph::node::TONEMAPPING, + TonemappingNode::IN_VIEW, + ) + .unwrap(); + draw_2d_graph + .add_slot_edge( + input_node_id, + graph::input::VIEW_ENTITY, + graph::node::UPSCALING, + UpscalingNode::IN_VIEW, + ) + .unwrap(); + draw_2d_graph + .add_node_edge(graph::node::MAIN_PASS, graph::node::TONEMAPPING) + .unwrap(); + draw_2d_graph + .add_node_edge(graph::node::TONEMAPPING, graph::node::UPSCALING) + .unwrap(); graph.add_sub_graph(graph::NAME, draw_2d_graph); } } diff --git a/crates/bevy_core_pipeline/src/core_3d/camera_3d.rs b/crates/bevy_core_pipeline/src/core_3d/camera_3d.rs index ce91c2d2e04a8..c5309c7c045b3 100644 --- a/crates/bevy_core_pipeline/src/core_3d/camera_3d.rs +++ b/crates/bevy_core_pipeline/src/core_3d/camera_3d.rs @@ -1,4 +1,4 @@ -use crate::clear_color::ClearColorConfig; +use crate::{clear_color::ClearColorConfig, tonemapping::Tonemapping}; use bevy_ecs::{prelude::*, query::QueryItem}; use bevy_reflect::{Reflect, ReflectDeserialize, ReflectSerialize}; use bevy_render::{ @@ -66,6 +66,7 @@ pub struct Camera3dBundle { pub transform: Transform, pub global_transform: GlobalTransform, pub camera_3d: Camera3d, + pub tonemapping: Tonemapping, } // NOTE: ideally Perspective and Orthographic defaults can share the same impl, but sadly it breaks rust's type inference @@ -73,6 +74,7 @@ impl Default for Camera3dBundle { fn default() -> Self { Self { camera_render_graph: CameraRenderGraph::new(crate::core_3d::graph::NAME), + tonemapping: Tonemapping { is_enabled: true }, camera: Default::default(), projection: Default::default(), visible_entities: Default::default(), diff --git a/crates/bevy_core_pipeline/src/core_3d/mod.rs b/crates/bevy_core_pipeline/src/core_3d/mod.rs index 4faa8ea1ca227..84e5f95783983 100644 --- a/crates/bevy_core_pipeline/src/core_3d/mod.rs +++ b/crates/bevy_core_pipeline/src/core_3d/mod.rs @@ -8,6 +8,8 @@ pub mod graph { } pub mod node { pub const MAIN_PASS: &str = "main_pass"; + pub const TONEMAPPING: &str = "tonemapping"; + pub const UPSCALING: &str = "upscaling"; } } @@ -38,6 +40,8 @@ use bevy_render::{ }; use bevy_utils::{FloatOrd, HashMap}; +use crate::{tonemapping::TonemappingNode, upscaling::UpscalingNode}; + pub struct Core3dPlugin; impl Plugin for Core3dPlugin { @@ -62,10 +66,14 @@ impl Plugin for Core3dPlugin { .add_system_to_stage(RenderStage::PhaseSort, sort_phase_system::); let pass_node_3d = MainPass3dNode::new(&mut render_app.world); + let tonemapping = TonemappingNode::new(&mut render_app.world); + let upscaling = UpscalingNode::new(&mut render_app.world); let mut graph = render_app.world.resource_mut::(); let mut draw_3d_graph = RenderGraph::default(); draw_3d_graph.add_node(graph::node::MAIN_PASS, pass_node_3d); + draw_3d_graph.add_node(graph::node::TONEMAPPING, tonemapping); + draw_3d_graph.add_node(graph::node::UPSCALING, upscaling); let input_node_id = draw_3d_graph.set_input(vec![SlotInfo::new( graph::input::VIEW_ENTITY, SlotType::Entity, @@ -78,6 +86,28 @@ impl Plugin for Core3dPlugin { MainPass3dNode::IN_VIEW, ) .unwrap(); + draw_3d_graph + .add_slot_edge( + input_node_id, + graph::input::VIEW_ENTITY, + graph::node::TONEMAPPING, + TonemappingNode::IN_VIEW, + ) + .unwrap(); + draw_3d_graph + .add_slot_edge( + input_node_id, + graph::input::VIEW_ENTITY, + graph::node::UPSCALING, + UpscalingNode::IN_VIEW, + ) + .unwrap(); + draw_3d_graph + .add_node_edge(graph::node::MAIN_PASS, graph::node::TONEMAPPING) + .unwrap(); + draw_3d_graph + .add_node_edge(graph::node::TONEMAPPING, graph::node::UPSCALING) + .unwrap(); graph.add_sub_graph(graph::NAME, draw_3d_graph); } } diff --git a/crates/bevy_core_pipeline/src/fullscreen_vertex_shader/fullscreen.wgsl b/crates/bevy_core_pipeline/src/fullscreen_vertex_shader/fullscreen.wgsl new file mode 100644 index 0000000000000..bc328269ab48c --- /dev/null +++ b/crates/bevy_core_pipeline/src/fullscreen_vertex_shader/fullscreen.wgsl @@ -0,0 +1,16 @@ +#define_import_path bevy_core_pipeline::fullscreen_vertex_shader + +struct FullscreenVertexOutput { + @builtin(position) + position: vec4, + @location(0) + uv: vec2, +}; + +@vertex +fn fullscreen_vertex_shader(@builtin(vertex_index) vertex_index: u32) -> FullscreenVertexOutput { + let uv = vec2(f32(vertex_index >> 1u), f32(vertex_index & 1u)) * 2.0; + let clip_position = vec4(uv * vec2(2.0, -2.0) + vec2(-1.0, 1.0), 0.0, 1.0); + + return FullscreenVertexOutput(clip_position, uv); +} diff --git a/crates/bevy_core_pipeline/src/fullscreen_vertex_shader/mod.rs b/crates/bevy_core_pipeline/src/fullscreen_vertex_shader/mod.rs new file mode 100644 index 0000000000000..7227cfa3bc0cb --- /dev/null +++ b/crates/bevy_core_pipeline/src/fullscreen_vertex_shader/mod.rs @@ -0,0 +1,26 @@ +use bevy_asset::HandleUntyped; +use bevy_reflect::TypeUuid; +use bevy_render::{prelude::Shader, render_resource::VertexState}; + +pub const FULLSCREEN_SHADER_HANDLE: HandleUntyped = + HandleUntyped::weak_from_u64(Shader::TYPE_UUID, 7837534426033940724); + +/// uses the [`FULLSCREEN_SHADER_HANDLE`] to output a +/// ```wgsl +/// struct FullscreenVertexOutput { +/// [[builtin(position)]] +/// position: vec4; +/// [[location(0)]] +/// uv: vec2; +/// }; +/// ``` +/// from the vertex shader. +/// The draw call should render one triangle: `render_pass.draw(0..3, 0..1);` +pub fn fullscreen_shader_vertex_state() -> VertexState { + VertexState { + shader: FULLSCREEN_SHADER_HANDLE.typed(), + shader_defs: Vec::new(), + entry_point: "fullscreen_vertex_shader".into(), + buffers: Vec::new(), + } +} diff --git a/crates/bevy_core_pipeline/src/lib.rs b/crates/bevy_core_pipeline/src/lib.rs index 0cc46286148d8..b46dba079b0cb 100644 --- a/crates/bevy_core_pipeline/src/lib.rs +++ b/crates/bevy_core_pipeline/src/lib.rs @@ -1,6 +1,9 @@ pub mod clear_color; pub mod core_2d; pub mod core_3d; +pub mod fullscreen_vertex_shader; +pub mod tonemapping; +pub mod upscaling; pub mod prelude { #[doc(hidden)] @@ -15,19 +18,32 @@ use crate::{ clear_color::{ClearColor, ClearColorConfig}, core_2d::Core2dPlugin, core_3d::Core3dPlugin, + fullscreen_vertex_shader::FULLSCREEN_SHADER_HANDLE, + tonemapping::TonemappingPlugin, + upscaling::UpscalingPlugin, }; use bevy_app::{App, Plugin}; -use bevy_render::extract_resource::ExtractResourcePlugin; +use bevy_asset::load_internal_asset; +use bevy_render::{extract_resource::ExtractResourcePlugin, prelude::Shader}; #[derive(Default)] pub struct CorePipelinePlugin; impl Plugin for CorePipelinePlugin { fn build(&self, app: &mut App) { + load_internal_asset!( + app, + FULLSCREEN_SHADER_HANDLE, + "fullscreen_vertex_shader/fullscreen.wgsl", + Shader::from_wgsl + ); + app.register_type::() .register_type::() .init_resource::() .add_plugin(ExtractResourcePlugin::::default()) + .add_plugin(TonemappingPlugin) + .add_plugin(UpscalingPlugin) .add_plugin(Core2dPlugin) .add_plugin(Core3dPlugin); } diff --git a/crates/bevy_core_pipeline/src/tonemapping/blit.wgsl b/crates/bevy_core_pipeline/src/tonemapping/blit.wgsl new file mode 100644 index 0000000000000..feb46c5405b35 --- /dev/null +++ b/crates/bevy_core_pipeline/src/tonemapping/blit.wgsl @@ -0,0 +1,11 @@ +#import bevy_core_pipeline::fullscreen_vertex_shader + +@group(0) @binding(0) +var texture: texture_2d; +@group(0) @binding(1) +var texture_sampler: sampler; + +@fragment +fn fs_main(in: FullscreenVertexOutput) -> @location(0) vec4 { + return textureSample(texture, texture_sampler, in.uv); +} 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..cb0f74ba12207 --- /dev/null +++ b/crates/bevy_core_pipeline/src/tonemapping/mod.rs @@ -0,0 +1,145 @@ +mod node; + +use bevy_ecs::query::QueryItem; +use bevy_render::camera::Camera; +use bevy_render::extract_component::{ExtractComponent, ExtractComponentPlugin}; +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::{render_resource::*, RenderApp}; + +use bevy_reflect::TypeUuid; + +use crate::fullscreen_vertex_shader::fullscreen_shader_vertex_state; + +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); + +const BLIT_SHADER_HANDLE: HandleUntyped = + HandleUntyped::weak_from_u64(Shader::TYPE_UUID, 2982361071241723543); + +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 + ); + load_internal_asset!( + app, + TONEMAPPING_SHARED_SHADER_HANDLE, + "tonemapping_shared.wgsl", + Shader::from_wgsl + ); + load_internal_asset!(app, BLIT_SHADER_HANDLE, "blit.wgsl", Shader::from_wgsl); + + app.add_plugin(ExtractComponentPlugin::::default()); + + if let Ok(render_app) = app.get_sub_app_mut(RenderApp) { + render_app.init_resource::(); + } + } +} + +#[derive(Resource)] +pub struct TonemappingPipeline { + hdr_texture_bind_group: BindGroupLayout, + tonemapping_pipeline_id: CachedRenderPipelineId, + blit_pipeline_id: CachedRenderPipelineId, +} + +impl FromWorld for TonemappingPipeline { + fn from_world(render_world: &mut World) -> Self { + let tonemap_texture_bind_group = render_world + .resource::() + .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, + }, + ], + }); + + let tonemap_descriptor = RenderPipelineDescriptor { + label: Some("tonemapping pipeline".into()), + layout: Some(vec![tonemap_texture_bind_group.clone()]), + vertex: fullscreen_shader_vertex_state(), + fragment: Some(FragmentState { + shader: TONEMAPPING_SHADER_HANDLE.typed(), + shader_defs: vec![], + entry_point: "fs_main".into(), + targets: vec![Some(ColorTargetState { + format: TextureFormat::bevy_default(), + blend: None, + write_mask: ColorWrites::ALL, + })], + }), + primitive: PrimitiveState::default(), + depth_stencil: None, + multisample: MultisampleState::default(), + }; + + let blit_descriptor = RenderPipelineDescriptor { + label: Some("blit pipeline".into()), + layout: Some(vec![tonemap_texture_bind_group.clone()]), + vertex: fullscreen_shader_vertex_state(), + fragment: Some(FragmentState { + shader: BLIT_SHADER_HANDLE.typed(), + shader_defs: vec![], + entry_point: "fs_main".into(), + targets: vec![Some(ColorTargetState { + format: TextureFormat::bevy_default(), + blend: None, + write_mask: ColorWrites::ALL, + })], + }), + primitive: PrimitiveState::default(), + depth_stencil: None, + multisample: MultisampleState::default(), + }; + let mut cache = render_world.resource_mut::(); + TonemappingPipeline { + hdr_texture_bind_group: tonemap_texture_bind_group, + tonemapping_pipeline_id: cache.queue_render_pipeline(tonemap_descriptor), + blit_pipeline_id: cache.queue_render_pipeline(blit_descriptor), + } + } +} + +#[derive(Component, Clone)] +pub struct Tonemapping { + pub is_enabled: bool, +} + +impl ExtractComponent for Tonemapping { + type Query = &'static Self; + type Filter = With; + + fn extract_component(item: QueryItem) -> Self { + item.clone() + } +} 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..ddae11c8cbb54 --- /dev/null +++ b/crates/bevy_core_pipeline/src/tonemapping/node.rs @@ -0,0 +1,133 @@ +use std::sync::Mutex; + +use crate::tonemapping::{Tonemapping, TonemappingPipeline}; +use bevy_ecs::prelude::*; +use bevy_ecs::query::QueryState; +use bevy_render::{ + render_graph::{Node, NodeRunError, RenderGraphContext, SlotInfo, SlotType}, + render_resource::{ + BindGroup, BindGroupDescriptor, BindGroupEntry, BindingResource, LoadOp, Operations, + PipelineCache, RenderPassColorAttachment, RenderPassDescriptor, SamplerDescriptor, + TextureViewId, + }, + renderer::RenderContext, + view::{ExtractedView, ViewMainTexture, ViewTarget}, +}; + +pub struct TonemappingNode { + query: QueryState<(&'static ViewTarget, Option<&'static Tonemapping>), With>, + cached_texture_bind_group: Mutex>, +} + +impl TonemappingNode { + pub const IN_VIEW: &'static str = "view"; + + 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)] + } + + 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 pipeline_cache = world.resource::(); + let tonemapping_pipeline = world.resource::(); + + let (target, tonemapping) = match self.query.get_manual(world, view_entity) { + Ok(result) => result, + Err(_) => 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 + return Ok(()); + } + }; + + let tonemapping_enabled = tonemapping.map_or(false, |t| t.is_enabled); + let pipeline_id = if tonemapping_enabled { + tonemapping_pipeline.tonemapping_pipeline_id + } else { + tonemapping_pipeline.blit_pipeline_id + }; + + let pipeline = match pipeline_cache.get_render_pipeline(pipeline_id) { + Some(pipeline) => pipeline, + None => return Ok(()), + }; + + let main_texture = target.main_texture.texture(); + + 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 main_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(main_texture), + }, + BindGroupEntry { + binding: 1, + resource: BindingResource::Sampler(&sampler), + }, + ], + }); + + let (_, bind_group) = cached_bind_group.insert((main_texture.id(), bind_group)); + bind_group + } + }; + + let pass_descriptor = RenderPassDescriptor { + label: Some("tonemapping_pass"), + color_attachments: &[Some(RenderPassColorAttachment { + view: 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, 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..08a07aa56fe02 --- /dev/null +++ b/crates/bevy_core_pipeline/src/tonemapping/tonemapping.wgsl @@ -0,0 +1,14 @@ +#import bevy_core_pipeline::fullscreen_vertex_shader +#import bevy_core_pipeline::tonemapping + +@group(0) @binding(0) +var hdr_texture: texture_2d; +@group(0) @binding(1) +var hdr_sampler: sampler; + +@fragment +fn fs_main(in: FullscreenVertexOutput) -> @location(0) vec4 { + let hdr_color = textureSample(hdr_texture, hdr_sampler, in.uv); + + return vec4(reinhard_luminance(hdr_color.rgb), hdr_color.a); +} 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..d71dd12f08f32 --- /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); +} 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..cf34911565f42 --- /dev/null +++ b/crates/bevy_core_pipeline/src/upscaling/mod.rs @@ -0,0 +1,158 @@ +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, SurfaceTextureFormat}; +use bevy_render::view::ExtractedView; +use bevy_render::{render_resource::*, RenderApp, RenderStage}; + +use bevy_reflect::TypeUuid; + +use crate::fullscreen_vertex_shader::fullscreen_shader_vertex_state; + +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 + ); + + if let Ok(render_app) = app.get_sub_app_mut(RenderApp) { + render_app + .init_resource::() + .init_resource::>() + .add_system_to_stage(RenderStage::Queue, queue_upscaling_bind_groups); + } + } +} + +#[derive(Resource)] +pub struct UpscalingPipeline { + ldr_texture_bind_group: BindGroupLayout, + surface_texture_format: TextureFormat, +} + +impl FromWorld for UpscalingPipeline { + fn from_world(render_world: &mut World) -> Self { + let render_device = render_world.resource::(); + let surface_texture_format = render_world.resource::().0; + + let ldr_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, + surface_texture_format, + } + } +} + +#[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 SpecializedRenderPipeline for UpscalingPipeline { + type Key = UpscalingPipelineKey; + + fn specialize(&self, _: Self::Key) -> RenderPipelineDescriptor { + RenderPipelineDescriptor { + label: Some("upscaling pipeline".into()), + layout: Some(vec![self.ldr_texture_bind_group.clone()]), + vertex: fullscreen_shader_vertex_state(), + fragment: Some(FragmentState { + shader: UPSCALING_SHADER_HANDLE.typed(), + shader_defs: vec![], + entry_point: "fs_main".into(), + targets: vec![Some(ColorTargetState { + format: self.surface_texture_format, + blend: None, + write_mask: ColorWrites::ALL, + })], + }), + primitive: PrimitiveState::default(), + depth_stencil: None, + multisample: MultisampleState::default(), + } + } +} + +#[derive(Component)] +pub struct UpscalingTarget { + pub pipeline: CachedRenderPipelineId, +} + +fn queue_upscaling_bind_groups( + mut commands: Commands, + mut pipeline_cache: ResMut, + mut pipelines: ResMut>, + upscaling_pipeline: Res, + view_targets: Query>, +) { + for entity in view_targets.iter() { + let key = UpscalingPipelineKey::from_upscaling_mode(UpscalingMode::Filtering); + let pipeline = pipelines.specialize(&mut pipeline_cache, &upscaling_pipeline, key); + + 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 new file mode 100644 index 0000000000000..8ab6f327ea129 --- /dev/null +++ b/crates/bevy_core_pipeline/src/upscaling/node.rs @@ -0,0 +1,123 @@ +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::{ + BindGroup, BindGroupDescriptor, BindGroupEntry, BindingResource, LoadOp, Operations, + PipelineCache, RenderPassColorAttachment, RenderPassDescriptor, SamplerDescriptor, + TextureViewId, + }, + renderer::RenderContext, + view::{ExtractedView, ViewTarget}, +}; + +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 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)] + } + + 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 pipeline_cache = world.get_resource::().unwrap(); + let upscaling_pipeline = world.get_resource::().unwrap(); + + let (target, upscaling_target) = match self.query.get_manual(world, view_entity) { + Ok(query) => query, + Err(_) => return Ok(()), + }; + + let upscaled_texture = match &target.main_texture { + bevy_render::view::ViewMainTexture::Hdr { ldr_texture, .. } => ldr_texture, + bevy_render::view::ViewMainTexture::Sdr { texture, .. } => texture, + }; + + 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 upscaled_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(upscaled_texture), + }, + BindGroupEntry { + binding: 1, + resource: BindingResource::Sampler(&sampler), + }, + ], + }); + + let (_, bind_group) = cached_bind_group.insert((upscaled_texture.id(), bind_group)); + bind_group + } + }; + + let pipeline = match pipeline_cache.get_render_pipeline(upscaling_target.pipeline) { + Some(pipeline) => pipeline, + None => return Ok(()), + }; + + let pass_descriptor = RenderPassDescriptor { + label: Some("upscaling_pass"), + color_attachments: &[Some(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, 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..56aae87920865 --- /dev/null +++ b/crates/bevy_core_pipeline/src/upscaling/upscaling.wgsl @@ -0,0 +1,13 @@ +#import bevy_core_pipeline::fullscreen_vertex_shader + +@group(0) @binding(0) +var hdr_texture: texture_2d; +@group(0) @binding(1) +var hdr_sampler: sampler; + +@fragment +fn fs_main(in: FullscreenVertexOutput) -> @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 d744ec77bb041..fecb2e9861ea8 100644 --- a/crates/bevy_pbr/src/material.rs +++ b/crates/bevy_pbr/src/material.rs @@ -4,7 +4,10 @@ use crate::{ }; use bevy_app::{App, Plugin}; use bevy_asset::{AddAsset, AssetEvent, AssetServer, Assets, Handle}; -use bevy_core_pipeline::core_3d::{AlphaMask3d, Opaque3d, Transparent3d}; +use bevy_core_pipeline::{ + core_3d::{AlphaMask3d, Opaque3d, Transparent3d}, + tonemapping::Tonemapping, +}; use bevy_derive::{Deref, DerefMut}; use bevy_ecs::{ entity::Entity, @@ -327,6 +330,7 @@ pub fn queue_material_meshes( mut views: Query<( &ExtractedView, &VisibleEntities, + Option<&Tonemapping>, &mut RenderPhase, &mut RenderPhase, &mut RenderPhase, @@ -334,8 +338,14 @@ pub fn queue_material_meshes( ) where M::Data: PartialEq + Eq + Hash + Clone, { - for (view, visible_entities, mut opaque_phase, mut alpha_mask_phase, mut transparent_phase) in - &mut views + for ( + view, + visible_entities, + tonemapping, + mut opaque_phase, + mut alpha_mask_phase, + mut transparent_phase, + ) in &mut views { let draw_opaque_pbr = opaque_draw_functions .read() @@ -350,8 +360,15 @@ pub fn queue_material_meshes( .get_id::>() .unwrap(); + let mut view_key = + MeshPipelineKey::from_msaa_samples(msaa.samples) | MeshPipelineKey::from_hdr(view.hdr); + + if let Some(tonemapping) = tonemapping { + if tonemapping.is_enabled && !view.hdr { + view_key |= MeshPipelineKey::TONEMAP_IN_SHADER; + } + } let rangefinder = view.rangefinder3d(); - let msaa_key = MeshPipelineKey::from_msaa_samples(msaa.samples); for visible_entity in &visible_entities.entities { if let Ok((material_handle, mesh_handle, mesh_uniform)) = @@ -361,7 +378,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) - | msaa_key; + | view_key; let alpha_mode = material.properties.alpha_mode; if let AlphaMode::Blend = alpha_mode { mesh_key |= MeshPipelineKey::TRANSPARENT_MAIN_PASS; diff --git a/crates/bevy_pbr/src/render/light.rs b/crates/bevy_pbr/src/render/light.rs index 8c40a315615c3..b82f4030e886c 100644 --- a/crates/bevy_pbr/src/render/light.rs +++ b/crates/bevy_pbr/src/render/light.rs @@ -1031,6 +1031,7 @@ pub fn prepare_lights( ), transform: view_translation * *view_rotation, projection: cube_face_projection, + hdr: false, }, RenderPhase::::default(), LightEntity::Point { @@ -1086,6 +1087,7 @@ pub fn prepare_lights( ), transform: spot_view_transform, projection: spot_projection, + hdr: false, }, RenderPhase::::default(), LightEntity::Spot { light_entity }, @@ -1169,6 +1171,7 @@ pub fn prepare_lights( ), transform: GlobalTransform::from(view.inverse()), projection, + 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 698d34b384d6f..0862ff5248e5d 100644 --- a/crates/bevy_pbr/src/render/mesh.rs +++ b/crates/bevy_pbr/src/render/mesh.rs @@ -21,9 +21,11 @@ use bevy_render::{ render_asset::RenderAssets, render_phase::{EntityRenderCommand, RenderCommandResult, TrackedRenderPass}, render_resource::*, - renderer::{RenderDevice, RenderQueue, RenderTextureFormat}, - texture::{DefaultImageSampler, GpuImage, Image, ImageSampler, TextureFormatPixelInfo}, - view::{ComputedVisibility, ViewUniform, ViewUniformOffset, ViewUniforms}, + renderer::{RenderDevice, RenderQueue}, + texture::{ + BevyDefault, DefaultImageSampler, GpuImage, Image, ImageSampler, TextureFormatPixelInfo, + }, + view::{ComputedVisibility, ViewTarget, ViewUniform, ViewUniformOffset, ViewUniforms}, Extract, RenderApp, RenderStage, }; use bevy_transform::components::GlobalTransform; @@ -268,10 +270,8 @@ impl FromWorld for MeshPipeline { Res, Res, Res, - Res, )> = SystemState::new(world); - let (render_device, default_sampler, render_queue, first_available_texture_format) = - system_state.get_mut(world); + let (render_device, default_sampler, render_queue) = system_state.get_mut(world); let clustered_forward_buffer_binding_type = render_device .get_supported_read_only_binding_type(CLUSTERED_FORWARD_STORAGE_BUFFER_COUNT); @@ -438,7 +438,7 @@ impl FromWorld for MeshPipeline { Extent3d::default(), TextureDimension::D2, &[255u8; 4], - first_available_texture_format.0, + TextureFormat::bevy_default(), ); let texture = render_device.create_texture(&image.texture_descriptor); let sampler = match image.sampler_descriptor { @@ -516,6 +516,8 @@ bitflags::bitflags! { pub struct MeshPipelineKey: u32 { const NONE = 0; const TRANSPARENT_MAIN_PASS = (1 << 0); + const HDR = (1 << 1); + const TONEMAP_IN_SHADER = (1 << 2); const MSAA_RESERVED_BITS = Self::MSAA_MASK_BITS << Self::MSAA_SHIFT_BITS; const PRIMITIVE_TOPOLOGY_RESERVED_BITS = Self::PRIMITIVE_TOPOLOGY_MASK_BITS << Self::PRIMITIVE_TOPOLOGY_SHIFT_BITS; } @@ -533,6 +535,14 @@ impl MeshPipelineKey { Self::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 { 1 << ((self.bits >> Self::MSAA_SHIFT_BITS) & Self::MSAA_MASK_BITS) } @@ -624,6 +634,15 @@ impl SpecializedMeshPipeline for MeshPipeline { depth_write_enabled = true; } + if key.contains(MeshPipelineKey::TONEMAP_IN_SHADER) { + shader_defs.push("TONEMAP_IN_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::(), @@ -636,7 +655,7 @@ impl SpecializedMeshPipeline for MeshPipeline { shader_defs, entry_point: "fragment".into(), targets: vec![Some(ColorTargetState { - format: self.dummy_white_gpu_image.texture_format, + 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 cf71d1829a7a6..b11700342f79f 100644 --- a/crates/bevy_pbr/src/render/pbr.wgsl +++ b/crates/bevy_pbr/src/render/pbr.wgsl @@ -87,8 +87,10 @@ fn fragment(in: FragmentInput) -> @location(0) vec4 { in.is_front, ); pbr_input.V = calculate_view(in.world_position, pbr_input.is_orthographic); - - output_color = tone_mapping(pbr(pbr_input)); + output_color = pbr(pbr_input); +#ifdef TONEMAP_IN_SHADER + output_color = tone_mapping(output_color); +#endif } else { output_color = alpha_discard(material, output_color); } diff --git a/crates/bevy_pbr/src/render/pbr_functions.wgsl b/crates/bevy_pbr/src/render/pbr_functions.wgsl index 08c4235ab09d3..4be10410b41d5 100644 --- a/crates/bevy_pbr/src/render/pbr_functions.wgsl +++ b/crates/bevy_pbr/src/render/pbr_functions.wgsl @@ -1,5 +1,10 @@ #define_import_path bevy_pbr::pbr_functions +#ifdef TONEMAP_IN_SHADER +#import bevy_core_pipeline::tonemapping +#endif + + fn alpha_discard(material: StandardMaterial, output_color: vec4) -> vec4{ var color = output_color; if ((material.flags & STANDARD_MATERIAL_FLAGS_ALPHA_MODE_OPAQUE) != 0u) { @@ -245,6 +250,7 @@ fn pbr( return output_color; } +#ifdef TONEMAP_IN_SHADER fn tone_mapping(in: vec4) -> vec4 { // tone_mapping return vec4(reinhard_luminance(in.rgb), in.a); @@ -253,4 +259,5 @@ fn tone_mapping(in: vec4) -> vec4 { // Not needed with sRGB buffer // output_color.rgb = pow(output_color.rgb, vec3(1.0 / 2.2)); } +#endif diff --git a/crates/bevy_pbr/src/render/pbr_lighting.wgsl b/crates/bevy_pbr/src/render/pbr_lighting.wgsl index 063f7e00ac479..97ad2dd687b1c 100644 --- a/crates/bevy_pbr/src/render/pbr_lighting.wgsl +++ b/crates/bevy_pbr/src/render/pbr_lighting.wgsl @@ -149,41 +149,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 point_light( world_position: vec3, light: PointLight, roughness: f32, NdotV: f32, N: vec3, V: vec3, R: vec3, F0: vec3, diffuseColor: vec3 diff --git a/crates/bevy_pbr/src/wireframe.rs b/crates/bevy_pbr/src/wireframe.rs index 0d15b39f58044..47171ba392834 100644 --- a/crates/bevy_pbr/src/wireframe.rs +++ b/crates/bevy_pbr/src/wireframe.rs @@ -124,10 +124,11 @@ fn queue_wireframes( for (view, visible_entities, mut opaque_phase) in &mut views { let rangefinder = view.rangefinder3d(); + let view_key = msaa_key | MeshPipelineKey::from_hdr(view.hdr); 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 + let key = view_key | MeshPipelineKey::from_primitive_topology(mesh.primitive_topology); let pipeline_id = pipelines.specialize( &mut pipeline_cache, diff --git a/crates/bevy_render/src/camera/camera.rs b/crates/bevy_render/src/camera/camera.rs index df2be98df1b08..586ca3a786feb 100644 --- a/crates/bevy_render/src/camera/camera.rs +++ b/crates/bevy_render/src/camera/camera.rs @@ -96,6 +96,12 @@ pub struct Camera { /// The "target" that this camera will render to. #[reflect(ignore)] pub target: RenderTarget, + /// If this is set to `true`, the camera will use an intermediate "high dynamic range" render texture. + /// Warning: we are still working on this feature. If MSAA is enabled, there will be artifacts in + /// some cases. When rendering with WebGL, this will crash if MSAA is enabled. + /// See for details. + // TODO: resolve the issues mentioned in the doc comment above, then remove the warning. + pub hdr: bool, } impl Default for Camera { @@ -106,6 +112,7 @@ impl Default for Camera { viewport: None, computed: Default::default(), target: Default::default(), + hdr: false, } } } @@ -478,6 +485,7 @@ pub fn extract_cameras( ExtractedView { projection: camera.projection_matrix(), transform: *transform, + hdr: camera.hdr, viewport: UVec4::new( viewport_origin.x, viewport_origin.y, diff --git a/crates/bevy_render/src/lib.rs b/crates/bevy_render/src/lib.rs index ef8185825379c..10c1a20dff21b 100644 --- a/crates/bevy_render/src/lib.rs +++ b/crates/bevy_render/src/lib.rs @@ -48,7 +48,7 @@ use crate::{ primitives::{CubemapFrusta, Frustum}, render_graph::RenderGraph, render_resource::{PipelineCache, Shader, ShaderLoader}, - renderer::{render_system, RenderInstance, RenderTextureFormat}, + renderer::{render_system, RenderInstance, SurfaceTextureFormat}, texture::BevyDefault, view::{ViewPlugin, WindowRenderPlugin}, }; @@ -166,7 +166,7 @@ impl Plugin for RenderPlugin { &options, &request_adapter_options, )); - let texture_format = RenderTextureFormat( + let texture_format = SurfaceTextureFormat( available_texture_formats .get(0) .cloned() diff --git a/crates/bevy_render/src/renderer/mod.rs b/crates/bevy_render/src/renderer/mod.rs index 941b04a2666bc..4f51bd3ccbc09 100644 --- a/crates/bevy_render/src/renderer/mod.rs +++ b/crates/bevy_render/src/renderer/mod.rs @@ -102,10 +102,10 @@ pub struct RenderInstance(pub Instance); #[derive(Resource, Clone, Deref, DerefMut)] pub struct RenderAdapterInfo(pub AdapterInfo); -/// The [`TextureFormat`](wgpu::TextureFormat) used for rendering. +/// The [`TextureFormat`](wgpu::TextureFormat) used for rendering to window surfaces. /// Initially it's the first element in `AvailableTextureFormats`, or Bevy default format. #[derive(Resource, Clone, Deref, DerefMut)] -pub struct RenderTextureFormat(pub wgpu::TextureFormat); +pub struct SurfaceTextureFormat(pub wgpu::TextureFormat); /// The available [`TextureFormat`](wgpu::TextureFormat)s on the [`RenderAdapter`]. /// Will be inserted as a `Resource` after the renderer is initialized. diff --git a/crates/bevy_render/src/texture/mod.rs b/crates/bevy_render/src/texture/mod.rs index 332d788b1698d..7600d5e1305b5 100644 --- a/crates/bevy_render/src/texture/mod.rs +++ b/crates/bevy_render/src/texture/mod.rs @@ -111,11 +111,6 @@ pub trait BevyDefault { impl BevyDefault for wgpu::TextureFormat { fn bevy_default() -> Self { - if cfg!(target_os = "android") || cfg!(target_arch = "wasm32") { - // Bgra8UnormSrgb texture missing on some Android devices - wgpu::TextureFormat::Rgba8UnormSrgb - } else { - wgpu::TextureFormat::Bgra8UnormSrgb - } + wgpu::TextureFormat::Rgba8UnormSrgb } } diff --git a/crates/bevy_render/src/view/mod.rs b/crates/bevy_render/src/view/mod.rs index 3df524c58ac16..06ce106fe2927 100644 --- a/crates/bevy_render/src/view/mod.rs +++ b/crates/bevy_render/src/view/mod.rs @@ -1,10 +1,11 @@ pub mod visibility; pub mod window; +use bevy_utils::HashMap; pub use visibility::*; use wgpu::{ Color, Extent3d, Operations, RenderPassColorAttachment, TextureDescriptor, TextureDimension, - TextureUsages, + TextureFormat, TextureUsages, }; pub use window::*; @@ -15,8 +16,8 @@ use crate::{ rangefinder::ViewRangefinder3d, render_asset::RenderAssets, render_resource::{DynamicUniformBuffer, ShaderType, Texture, TextureView}, - renderer::{RenderDevice, RenderQueue, RenderTextureFormat}, - texture::TextureCache, + renderer::{RenderDevice, RenderQueue}, + texture::{BevyDefault, TextureCache}, RenderApp, RenderStage, }; use bevy_app::{App, Plugin}; @@ -24,7 +25,6 @@ use bevy_ecs::prelude::*; use bevy_math::{Mat4, UVec4, Vec3, Vec4}; use bevy_reflect::Reflect; use bevy_transform::components::GlobalTransform; -use bevy_utils::HashMap; pub struct ViewPlugin; @@ -81,6 +81,7 @@ impl Default for Msaa { pub struct ExtractedView { pub projection: Mat4, pub transform: GlobalTransform, + pub hdr: bool, // uvec4(origin.x, origin.y, width, height) pub viewport: UVec4, } @@ -115,21 +116,74 @@ pub struct ViewUniformOffset { pub offset: u32, } +#[derive(Clone)] +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 view: TextureView, - pub sampled_target: Option, + pub main_texture: ViewMainTexture, + pub out_texture: TextureView, } impl ViewTarget { + pub const TEXTURE_FORMAT_HDR: TextureFormat = TextureFormat::Rgba16Float; + pub fn get_color_attachment(&self, ops: Operations) -> RenderPassColorAttachment { + 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(target), + ops, + }, + None => RenderPassColorAttachment { + view: target, + resolve_target: None, + ops, + }, + } + } + + pub fn get_unsampled_color_attachment( + &self, + ops: Operations, + ) -> RenderPassColorAttachment { RenderPassColorAttachment { - view: self.sampled_target.as_ref().unwrap_or(&self.view), - resolve_target: if self.sampled_target.is_some() { - Some(&self.view) - } else { - None + view: match &self.main_texture { + ViewMainTexture::Hdr { hdr_texture, .. } => hdr_texture, + ViewMainTexture::Sdr { texture, .. } => texture, }, + resolve_target: None, ops, } } @@ -182,42 +236,89 @@ fn prepare_view_targets( images: Res>, msaa: Res, render_device: Res, - texture_format: Res, mut texture_cache: ResMut, - cameras: Query<(Entity, &ExtractedCamera)>, + cameras: Query<(Entity, &ExtractedCamera, &ExtractedView)>, ) { - let mut sampled_textures = HashMap::default(); - for (entity, camera) in &cameras { + let mut textures = HashMap::default(); + for (entity, camera, view) in cameras.iter() { if let Some(target_size) = camera.physical_target_size { if let Some(texture_view) = camera.target.get_texture_view(&windows, &images) { - let sampled_target = if msaa.samples > 1 { - let sampled_texture = sampled_textures - .entry(camera.target.clone()) - .or_insert_with(|| { - texture_cache.get( + let size = Extent3d { + width: target_size.x, + height: target_size.y, + depth_or_array_layers: 1, + }; + + let main_texture = textures + .entry((camera.target.clone(), view.hdr)) + .or_insert_with(|| { + let main_texture_format = if view.hdr { + ViewTarget::TEXTURE_FORMAT_HDR + } else { + TextureFormat::bevy_default() + }; + + let main_texture = texture_cache.get( + &render_device, + TextureDescriptor { + label: Some("main_texture"), + size, + mip_level_count: 1, + sample_count: 1, + dimension: TextureDimension::D2, + format: main_texture_format, + usage: TextureUsages::RENDER_ATTACHMENT + | TextureUsages::TEXTURE_BINDING, + }, + ); + + let sampled_main_texture = (msaa.samples > 1).then(|| { + texture_cache + .get( + &render_device, + TextureDescriptor { + label: Some("main_texture_sampled"), + size, + mip_level_count: 1, + sample_count: msaa.samples, + dimension: TextureDimension::D2, + format: main_texture_format, + usage: TextureUsages::RENDER_ATTACHMENT, + }, + ) + .default_view + }); + if view.hdr { + let ldr_texture = texture_cache.get( &render_device, TextureDescriptor { - label: Some("sampled_color_attachment_texture"), - size: Extent3d { - width: target_size.x, - height: target_size.y, - depth_or_array_layers: 1, - }, + label: Some("ldr_texture"), + size, mip_level_count: 1, - sample_count: msaa.samples, + sample_count: 1, dimension: TextureDimension::D2, - format: **texture_format, - usage: TextureUsages::RENDER_ATTACHMENT, + format: TextureFormat::bevy_default(), + usage: TextureUsages::RENDER_ATTACHMENT + | TextureUsages::TEXTURE_BINDING, }, - ) - }); - Some(sampled_texture.default_view.clone()) - } else { - None - }; + ); + + 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, + } + } + }); + commands.entity(entity).insert(ViewTarget { - view: texture_view.clone(), - sampled_target, + main_texture: main_texture.clone(), + out_texture: texture_view.clone(), }); } } diff --git a/crates/bevy_sprite/src/mesh2d/material.rs b/crates/bevy_sprite/src/mesh2d/material.rs index 5dd2bc5b206b3..01bd223bbf3a3 100644 --- a/crates/bevy_sprite/src/mesh2d/material.rs +++ b/crates/bevy_sprite/src/mesh2d/material.rs @@ -1,6 +1,6 @@ use bevy_app::{App, Plugin}; use bevy_asset::{AddAsset, AssetEvent, AssetServer, Assets, Handle}; -use bevy_core_pipeline::core_2d::Transparent2d; +use bevy_core_pipeline::{core_2d::Transparent2d, tonemapping::Tonemapping}; use bevy_derive::{Deref, DerefMut}; use bevy_ecs::{ entity::Entity, @@ -31,7 +31,7 @@ use bevy_render::{ }, renderer::RenderDevice, texture::FallbackImage, - view::{ComputedVisibility, Msaa, Visibility, VisibleEntities}, + view::{ComputedVisibility, ExtractedView, Msaa, Visibility, VisibleEntities}, Extract, RenderApp, RenderStage, }; use bevy_transform::components::{GlobalTransform, Transform}; @@ -306,20 +306,33 @@ pub fn queue_material2d_meshes( render_meshes: Res>, render_materials: Res>, material2d_meshes: Query<(&Handle, &Mesh2dHandle, &Mesh2dUniform)>, - mut views: Query<(&VisibleEntities, &mut RenderPhase)>, + mut views: Query<( + &ExtractedView, + &VisibleEntities, + Option<&Tonemapping>, + &mut RenderPhase, + )>, ) where M::Data: PartialEq + Eq + Hash + Clone, { if material2d_meshes.is_empty() { return; } - for (visible_entities, mut transparent_phase) in &mut views { + + for (view, visible_entities, tonemapping, mut transparent_phase) in &mut views { let draw_transparent_pbr = transparent_draw_functions .read() .get_id::>() .unwrap(); - let msaa_key = Mesh2dPipelineKey::from_msaa_samples(msaa.samples); + let mut view_key = Mesh2dPipelineKey::from_msaa_samples(msaa.samples) + | Mesh2dPipelineKey::from_hdr(view.hdr); + + if let Some(tonemapping) = tonemapping { + if tonemapping.is_enabled && !view.hdr { + view_key |= Mesh2dPipelineKey::TONEMAP_IN_SHADER; + } + } for visible_entity in &visible_entities.entities { if let Ok((material2d_handle, mesh2d_handle, mesh2d_uniform)) = @@ -327,7 +340,7 @@ pub fn queue_material2d_meshes( { if let Some(material2d) = render_materials.get(material2d_handle) { if let Some(mesh) = render_meshes.get(&mesh2d_handle.0) { - let mesh_key = msaa_key + let mesh_key = view_key | Mesh2dPipelineKey::from_primitive_topology(mesh.primitive_topology); let pipeline_id = pipelines.specialize( diff --git a/crates/bevy_sprite/src/mesh2d/mesh.rs b/crates/bevy_sprite/src/mesh2d/mesh.rs index 6aa9ac5c51373..43a932f53d565 100644 --- a/crates/bevy_sprite/src/mesh2d/mesh.rs +++ b/crates/bevy_sprite/src/mesh2d/mesh.rs @@ -13,9 +13,13 @@ use bevy_render::{ render_asset::RenderAssets, render_phase::{EntityRenderCommand, RenderCommandResult, TrackedRenderPass}, render_resource::*, - renderer::{RenderDevice, RenderQueue, RenderTextureFormat}, - texture::{DefaultImageSampler, GpuImage, Image, ImageSampler, TextureFormatPixelInfo}, - view::{ComputedVisibility, ExtractedView, ViewUniform, ViewUniformOffset, ViewUniforms}, + renderer::{RenderDevice, RenderQueue}, + texture::{ + BevyDefault, DefaultImageSampler, GpuImage, Image, ImageSampler, TextureFormatPixelInfo, + }, + view::{ + ComputedVisibility, ExtractedView, ViewTarget, ViewUniform, ViewUniformOffset, ViewUniforms, + }, Extract, RenderApp, RenderStage, }; use bevy_transform::components::GlobalTransform; @@ -157,13 +161,9 @@ pub struct Mesh2dPipeline { impl FromWorld for Mesh2dPipeline { fn from_world(world: &mut World) -> Self { - let mut system_state: SystemState<( - Res, - Res, - Res, - )> = SystemState::new(world); - let (render_device, default_sampler, first_available_texture_format) = - system_state.get_mut(world); + let mut system_state: SystemState<(Res, Res)> = + SystemState::new(world); + let (render_device, default_sampler) = system_state.get_mut(world); let view_layout = render_device.create_bind_group_layout(&BindGroupLayoutDescriptor { entries: &[ // View @@ -210,7 +210,7 @@ impl FromWorld for Mesh2dPipeline { Extent3d::default(), TextureDimension::D2, &[255u8; 4], - first_available_texture_format.0, + TextureFormat::bevy_default(), ); let texture = render_device.create_texture(&image.texture_descriptor); let sampler = match image.sampler_descriptor { @@ -286,6 +286,8 @@ bitflags::bitflags! { // FIXME: make normals optional? pub struct Mesh2dPipelineKey: u32 { const NONE = 0; + const HDR = (1 << 0); + const TONEMAP_IN_SHADER = (1 << 1); const MSAA_RESERVED_BITS = Self::MSAA_MASK_BITS << Self::MSAA_SHIFT_BITS; const PRIMITIVE_TOPOLOGY_RESERVED_BITS = Self::PRIMITIVE_TOPOLOGY_MASK_BITS << Self::PRIMITIVE_TOPOLOGY_SHIFT_BITS; } @@ -303,6 +305,14 @@ impl Mesh2dPipelineKey { Self::from_bits(msaa_bits).unwrap() } + pub fn from_hdr(hdr: bool) -> Self { + if hdr { + Mesh2dPipelineKey::HDR + } else { + Mesh2dPipelineKey::NONE + } + } + pub fn msaa_samples(&self) -> u32 { 1 << ((self.bits >> Self::MSAA_SHIFT_BITS) & Self::MSAA_MASK_BITS) } @@ -364,8 +374,17 @@ impl SpecializedMeshPipeline for Mesh2dPipeline { vertex_attributes.push(Mesh::ATTRIBUTE_COLOR.at_shader_location(4)); } + if key.contains(Mesh2dPipelineKey::TONEMAP_IN_SHADER) { + shader_defs.push("TONEMAP_IN_SHADER".to_string()); + } + let vertex_buffer_layout = layout.get_layout(&vertex_attributes)?; + let format = match key.contains(Mesh2dPipelineKey::HDR) { + true => ViewTarget::TEXTURE_FORMAT_HDR, + false => TextureFormat::bevy_default(), + }; + Ok(RenderPipelineDescriptor { vertex: VertexState { shader: MESH2D_SHADER_HANDLE.typed::(), @@ -378,7 +397,7 @@ impl SpecializedMeshPipeline for Mesh2dPipeline { shader_defs, entry_point: "fragment".into(), targets: vec![Some(ColorTargetState { - format: self.dummy_white_gpu_image.texture_format, + format, blend: Some(BlendState::ALPHA_BLENDING), write_mask: ColorWrites::ALL, })], diff --git a/crates/bevy_sprite/src/render/mod.rs b/crates/bevy_sprite/src/render/mod.rs index 03dab6d70bfd2..25d25cd315eef 100644 --- a/crates/bevy_sprite/src/render/mod.rs +++ b/crates/bevy_sprite/src/render/mod.rs @@ -5,7 +5,7 @@ use crate::{ Sprite, SPRITE_SHADER_HANDLE, }; use bevy_asset::{AssetEvent, Assets, Handle, HandleId}; -use bevy_core_pipeline::core_2d::Transparent2d; +use bevy_core_pipeline::{core_2d::Transparent2d, tonemapping::Tonemapping}; use bevy_ecs::{ prelude::*, system::{lifetimeless::*, SystemParamItem, SystemState}, @@ -20,10 +20,13 @@ use bevy_render::{ RenderPhase, SetItemPipeline, TrackedRenderPass, }, render_resource::*, - renderer::{RenderDevice, RenderQueue, RenderTextureFormat}, - texture::{DefaultImageSampler, GpuImage, Image, ImageSampler, TextureFormatPixelInfo}, + renderer::{RenderDevice, RenderQueue}, + texture::{ + BevyDefault, DefaultImageSampler, GpuImage, Image, ImageSampler, TextureFormatPixelInfo, + }, view::{ - ComputedVisibility, Msaa, ViewUniform, ViewUniformOffset, ViewUniforms, VisibleEntities, + ComputedVisibility, ExtractedView, Msaa, ViewTarget, ViewUniform, ViewUniformOffset, + ViewUniforms, VisibleEntities, }, Extract, }; @@ -46,10 +49,8 @@ impl FromWorld for SpritePipeline { Res, Res, Res, - Res, )> = SystemState::new(world); - let (render_device, default_sampler, render_queue, first_available_texture_format) = - system_state.get_mut(world); + let (render_device, default_sampler, render_queue) = system_state.get_mut(world); let view_layout = render_device.create_bind_group_layout(&BindGroupLayoutDescriptor { entries: &[BindGroupLayoutEntry { @@ -91,7 +92,7 @@ impl FromWorld for SpritePipeline { Extent3d::default(), TextureDimension::D2, &[255u8; 4], - first_available_texture_format.0, + TextureFormat::bevy_default(), ); let texture = render_device.create_texture(&image.texture_descriptor); let sampler = match image.sampler_descriptor { @@ -148,6 +149,8 @@ bitflags::bitflags! { pub struct SpritePipelineKey: u32 { const NONE = 0; const COLORED = (1 << 0); + const HDR = (1 << 1); + const TONEMAP_IN_SHADER = (1 << 2); const MSAA_RESERVED_BITS = Self::MSAA_MASK_BITS << Self::MSAA_SHIFT_BITS; } } @@ -165,6 +168,22 @@ impl SpritePipelineKey { pub fn msaa_samples(&self) -> u32 { 1 << ((self.bits >> Self::MSAA_SHIFT_BITS) & Self::MSAA_MASK_BITS) } + + 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 SpecializedRenderPipeline for SpritePipeline { @@ -191,6 +210,15 @@ impl SpecializedRenderPipeline for SpritePipeline { shader_defs.push("COLORED".to_string()); } + if key.contains(SpritePipelineKey::TONEMAP_IN_SHADER) { + shader_defs.push("TONEMAP_IN_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::(), @@ -203,7 +231,7 @@ impl SpecializedRenderPipeline for SpritePipeline { shader_defs, entry_point: "fragment".into(), targets: vec![Some(ColorTargetState { - format: self.dummy_white_gpu_image.texture_format, + format, blend: Some(BlendState::ALPHA_BLENDING), write_mask: ColorWrites::ALL, })], @@ -418,7 +446,12 @@ pub fn queue_sprites( gpu_images: Res>, msaa: Res, mut extracted_sprites: ResMut, - mut views: Query<(&VisibleEntities, &mut RenderPhase)>, + mut views: Query<( + &mut RenderPhase, + &VisibleEntities, + &ExtractedView, + Option<&Tonemapping>, + )>, events: Res, ) { // If an image has changed, the GpuImage has (probably) changed @@ -431,6 +464,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; @@ -448,18 +483,13 @@ 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 + let extracted_sprites = &mut extracted_sprites.sprites; // Sort sprites by z for correct transparency and then by handle to improve batching // NOTE: This can be done independent of views by reasonably assuming that all 2D views look along the negative-z axis in world space @@ -476,7 +506,24 @@ pub fn queue_sprites( }); let image_bind_groups = &mut *image_bind_groups; - for (visible_entities, mut transparent_phase) in &mut views { + for (mut transparent_phase, visible_entities, view, tonemapping) in &mut views { + let mut view_key = SpritePipelineKey::from_hdr(view.hdr) | msaa_key; + if let Some(tonemapping) = tonemapping { + if tonemapping.is_enabled && !view.hdr { + view_key |= SpritePipelineKey::TONEMAP_IN_SHADER; + } + } + let pipeline = pipelines.specialize( + &mut pipeline_cache, + &sprite_pipeline, + view_key | SpritePipelineKey::from_colored(false), + ); + let colored_pipeline = pipelines.specialize( + &mut pipeline_cache, + &sprite_pipeline, + view_key | SpritePipelineKey::from_colored(true), + ); + view_entities.clear(); view_entities.extend(visible_entities.entities.iter().map(|e| e.id() as usize)); transparent_phase.items.reserve(extracted_sprites.len()); diff --git a/crates/bevy_sprite/src/render/sprite.wgsl b/crates/bevy_sprite/src/render/sprite.wgsl index 441140549a562..d1097e61af6fc 100644 --- a/crates/bevy_sprite/src/render/sprite.wgsl +++ b/crates/bevy_sprite/src/render/sprite.wgsl @@ -1,3 +1,7 @@ +#ifdef TONEMAP_IN_SHADER +#import bevy_core_pipeline::tonemapping +#endif + struct View { view_proj: mat4x4, inverse_view_proj: mat4x4, @@ -48,5 +52,10 @@ fn fragment(in: VertexOutput) -> @location(0) vec4 { #ifdef COLORED color = in.color * color; #endif + +#ifdef TONEMAP_IN_SHADER + color = vec4(reinhard_luminance(color.rgb), color.a); +#endif + return color; } diff --git a/crates/bevy_ui/src/render/mod.rs b/crates/bevy_ui/src/render/mod.rs index 3e836cc303f7c..d2cd2d2ac2a1d 100644 --- a/crates/bevy_ui/src/render/mod.rs +++ b/crates/bevy_ui/src/render/mod.rs @@ -115,6 +115,18 @@ pub fn build_ui_render(app: &mut App) { RunGraphOnViewNode::IN_VIEW, ) .unwrap(); + graph_2d + .add_node_edge( + bevy_core_pipeline::core_2d::graph::node::TONEMAPPING, + draw_ui_graph::node::UI_PASS, + ) + .unwrap(); + graph_2d + .add_node_edge( + draw_ui_graph::node::UI_PASS, + bevy_core_pipeline::core_2d::graph::node::UPSCALING, + ) + .unwrap(); } if let Some(graph_3d) = graph.get_sub_graph_mut(bevy_core_pipeline::core_3d::graph::NAME) { @@ -129,6 +141,18 @@ pub fn build_ui_render(app: &mut App) { draw_ui_graph::node::UI_PASS, ) .unwrap(); + graph_3d + .add_node_edge( + bevy_core_pipeline::core_3d::graph::node::TONEMAPPING, + draw_ui_graph::node::UI_PASS, + ) + .unwrap(); + graph_3d + .add_node_edge( + draw_ui_graph::node::UI_PASS, + bevy_core_pipeline::core_3d::graph::node::UPSCALING, + ) + .unwrap(); graph_3d .add_slot_edge( graph_3d.input_node().unwrap().id, @@ -258,6 +282,7 @@ pub fn extract_default_ui_camera_view( 0.0, UI_CAMERA_FAR + UI_CAMERA_TRANSFORM_OFFSET, ), + hdr: camera.hdr, viewport: UVec4::new( physical_origin.x, physical_origin.y, diff --git a/crates/bevy_ui/src/render/pipeline.rs b/crates/bevy_ui/src/render/pipeline.rs index 5d384518f3d96..e199cef521e0b 100644 --- a/crates/bevy_ui/src/render/pipeline.rs +++ b/crates/bevy_ui/src/render/pipeline.rs @@ -1,29 +1,17 @@ -use bevy_ecs::{prelude::*, system::SystemState}; -use bevy_math::Vec2; +use bevy_ecs::prelude::*; use bevy_render::{ - render_resource::*, - renderer::{RenderDevice, RenderQueue, RenderTextureFormat}, - texture::{DefaultImageSampler, GpuImage, Image, ImageSampler, TextureFormatPixelInfo}, - view::ViewUniform, + render_resource::*, renderer::RenderDevice, texture::BevyDefault, view::ViewUniform, }; #[derive(Resource)] pub struct UiPipeline { pub view_layout: BindGroupLayout, pub image_layout: BindGroupLayout, - pub dummy_white_gpu_image: GpuImage, } impl FromWorld for UiPipeline { fn from_world(world: &mut World) -> Self { - let mut system_state: SystemState<( - Res, - Res, - Res, - Res, - )> = SystemState::new(world); - let (render_device, default_sampler, render_queue, first_available_texture_format) = - system_state.get_mut(world); + let render_device = world.resource::(); let view_layout = render_device.create_bind_group_layout(&BindGroupLayoutDescriptor { entries: &[BindGroupLayoutEntry { @@ -60,57 +48,10 @@ impl FromWorld for UiPipeline { ], label: Some("ui_image_layout"), }); - let dummy_white_gpu_image = { - let image = Image::new_fill( - Extent3d::default(), - TextureDimension::D2, - &[255u8; 4], - first_available_texture_format.0, - ); - let texture = render_device.create_texture(&image.texture_descriptor); - let sampler = match image.sampler_descriptor { - ImageSampler::Default => (**default_sampler).clone(), - ImageSampler::Descriptor(descriptor) => render_device.create_sampler(&descriptor), - }; - - let format_size = image.texture_descriptor.format.pixel_size(); - render_queue.write_texture( - ImageCopyTexture { - texture: &texture, - mip_level: 0, - origin: Origin3d::ZERO, - aspect: TextureAspect::All, - }, - &image.data, - ImageDataLayout { - offset: 0, - bytes_per_row: Some( - std::num::NonZeroU32::new( - image.texture_descriptor.size.width * format_size as u32, - ) - .unwrap(), - ), - rows_per_image: None, - }, - image.texture_descriptor.size, - ); - let texture_view = texture.create_view(&TextureViewDescriptor::default()); - GpuImage { - texture, - texture_view, - texture_format: image.texture_descriptor.format, - sampler, - size: Vec2::new( - image.texture_descriptor.size.width as f32, - image.texture_descriptor.size.height as f32, - ), - } - }; UiPipeline { view_layout, image_layout, - dummy_white_gpu_image, } } } @@ -147,7 +88,7 @@ impl SpecializedRenderPipeline for UiPipeline { shader_defs, entry_point: "fragment".into(), targets: vec![Some(ColorTargetState { - format: self.dummy_white_gpu_image.texture_format, + format: TextureFormat::bevy_default(), 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 66445c92a17c3..16164c61a564a 100644 --- a/crates/bevy_ui/src/render/render_pass.rs +++ b/crates/bevy_ui/src/render/render_pass.rs @@ -7,9 +7,7 @@ use bevy_ecs::{ use bevy_render::{ render_graph::*, render_phase::*, - render_resource::{ - CachedRenderPipelineId, LoadOp, Operations, RenderPassColorAttachment, RenderPassDescriptor, - }, + render_resource::{CachedRenderPipelineId, LoadOp, Operations, RenderPassDescriptor}, renderer::*, view::*, }; @@ -81,14 +79,10 @@ impl Node for UiPassNode { }; let pass_descriptor = RenderPassDescriptor { label: Some("ui_pass"), - color_attachments: &[Some(RenderPassColorAttachment { - view: &target.view, - resolve_target: None, - ops: Operations { - load: LoadOp::Load, - store: true, - }, - })], + color_attachments: &[Some(target.get_unsampled_color_attachment(Operations { + load: LoadOp::Load, + store: true, + }))], depth_stencil_attachment: None, }; diff --git a/examples/2d/mesh2d_manual.rs b/examples/2d/mesh2d_manual.rs index 5eb72f3a19d88..d19a3a320a837 100644 --- a/examples/2d/mesh2d_manual.rs +++ b/examples/2d/mesh2d_manual.rs @@ -20,7 +20,7 @@ use bevy::{ TextureFormat, VertexBufferLayout, VertexFormat, VertexState, VertexStepMode, }, texture::BevyDefault, - view::VisibleEntities, + view::{ExtractedView, ViewTarget, VisibleEntities}, Extract, RenderApp, RenderStage, }, sprite::{ @@ -146,6 +146,11 @@ impl SpecializedRenderPipeline for ColoredMesh2dPipeline { let vertex_layout = VertexBufferLayout::from_vertex_formats(VertexStepMode::Vertex, formats); + let format = match key.contains(Mesh2dPipelineKey::HDR) { + true => ViewTarget::TEXTURE_FORMAT_HDR, + false => TextureFormat::bevy_default(), + }; + RenderPipelineDescriptor { vertex: VertexState { // Use our custom shader @@ -161,7 +166,7 @@ impl SpecializedRenderPipeline for ColoredMesh2dPipeline { shader_defs: Vec::new(), entry_point: "fragment".into(), targets: vec![Some(ColorTargetState { - format: TextureFormat::bevy_default(), + format, blend: Some(BlendState::ALPHA_BLENDING), write_mask: ColorWrites::ALL, })], @@ -311,19 +316,24 @@ pub fn queue_colored_mesh2d( msaa: Res, render_meshes: Res>, colored_mesh2d: Query<(&Mesh2dHandle, &Mesh2dUniform), With>, - mut views: Query<(&VisibleEntities, &mut RenderPhase)>, + mut views: Query<( + &VisibleEntities, + &mut RenderPhase, + &ExtractedView, + )>, ) { if colored_mesh2d.is_empty() { return; } // Iterate each view (a camera is a view) - for (visible_entities, mut transparent_phase) in &mut views { + for (visible_entities, mut transparent_phase, view) in &mut views { let draw_colored_mesh2d = transparent_draw_functions .read() .get_id::() .unwrap(); - let mesh_key = Mesh2dPipelineKey::from_msaa_samples(msaa.samples); + let mesh_key = Mesh2dPipelineKey::from_msaa_samples(msaa.samples) + | Mesh2dPipelineKey::from_hdr(view.hdr); // Queue all entities visible to that view for visible_entity in &visible_entities.entities {