separate tonemapping and upscaling passes (#3425)

Attempt to make features like bloom https://github.com/bevyengine/bevy/pull/2876 easier to implement.

**This PR:**
- Moves the tonemapping from `pbr.wgsl` into a separate pass
- also add a separate upscaling pass after the tonemapping which writes to the swap chain (enables resolution-independant rendering and post-processing after tonemapping)
- adds a `hdr` bool to the camera which controls whether the pbr and sprite shaders render into a `Rgba16Float` texture

**Open questions:**
- ~should the 2d graph work the same as the 3d one?~ it is the same now
- ~The current solution is a bit inflexible because while you can add a post processing pass that writes to e.g. the `hdr_texture`, you can't write to a separate `user_postprocess_texture` while reading the `hdr_texture` and tell the tone mapping pass to read from the `user_postprocess_texture` instead. If the tonemapping and upscaling render graph nodes were to take in a `TextureView` instead of the view entity this would almost work, but the bind groups for their respective input textures are already created in the `Queue` render stage in the hardcoded order.~ solved by creating bind groups in render node

**New render graph:**

![render_graph](https://user-images.githubusercontent.com/22177966/147767249-57dd4229-cfab-4ec5-9bf3-dc76dccf8e8b.png)
<details>
<summary>Before</summary>

![render_graph_old](https://user-images.githubusercontent.com/22177966/147284579-c895fdbd-4028-41cf-914c-e1ffef60e44e.png)
</details>

Co-authored-by: Carter Anderson <mcanders1@gmail.com>
This commit is contained in:
Jakob Hellermann 2022-10-26 20:13:59 +00:00
parent 2023ce63c7
commit 838b318863
36 changed files with 1143 additions and 217 deletions

View file

@ -19,6 +19,7 @@ webgl = []
[dependencies] [dependencies]
# bevy # bevy
bevy_app = { path = "../bevy_app", version = "0.9.0-dev" } 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_derive = { path = "../bevy_derive", version = "0.9.0-dev" }
bevy_ecs = { path = "../bevy_ecs", 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" } 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" } bevy_utils = { path = "../bevy_utils", version = "0.9.0-dev" }
serde = { version = "1", features = ["derive"] } serde = { version = "1", features = ["derive"] }
bitflags = "1.2"
radsort = "0.1" radsort = "0.1"

View file

@ -1,4 +1,4 @@
use crate::clear_color::ClearColorConfig; use crate::{clear_color::ClearColorConfig, tonemapping::Tonemapping};
use bevy_ecs::{prelude::*, query::QueryItem}; use bevy_ecs::{prelude::*, query::QueryItem};
use bevy_reflect::Reflect; use bevy_reflect::Reflect;
use bevy_render::{ use bevy_render::{
@ -34,6 +34,7 @@ pub struct Camera2dBundle {
pub transform: Transform, pub transform: Transform,
pub global_transform: GlobalTransform, pub global_transform: GlobalTransform,
pub camera_2d: Camera2d, pub camera_2d: Camera2d,
pub tonemapping: Tonemapping,
} }
impl Default for Camera2dBundle { impl Default for Camera2dBundle {
@ -74,6 +75,7 @@ impl Camera2dBundle {
global_transform: Default::default(), global_transform: Default::default(),
camera: Camera::default(), camera: Camera::default(),
camera_2d: Camera2d::default(), camera_2d: Camera2d::default(),
tonemapping: Tonemapping { is_enabled: false },
} }
} }
} }

View file

@ -8,6 +8,8 @@ pub mod graph {
} }
pub mod node { pub mod node {
pub const MAIN_PASS: &str = "main_pass"; 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 bevy_utils::FloatOrd;
use std::ops::Range; use std::ops::Range;
use crate::{tonemapping::TonemappingNode, upscaling::UpscalingNode};
pub struct Core2dPlugin; pub struct Core2dPlugin;
impl Plugin for Core2dPlugin { impl Plugin for Core2dPlugin {
@ -52,10 +56,14 @@ impl Plugin for Core2dPlugin {
); );
let pass_node_2d = MainPass2dNode::new(&mut render_app.world); 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::<RenderGraph>(); let mut graph = render_app.world.resource_mut::<RenderGraph>();
let mut draw_2d_graph = RenderGraph::default(); 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::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( let input_node_id = draw_2d_graph.set_input(vec![SlotInfo::new(
graph::input::VIEW_ENTITY, graph::input::VIEW_ENTITY,
SlotType::Entity, SlotType::Entity,
@ -68,6 +76,28 @@ impl Plugin for Core2dPlugin {
MainPass2dNode::IN_VIEW, MainPass2dNode::IN_VIEW,
) )
.unwrap(); .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); graph.add_sub_graph(graph::NAME, draw_2d_graph);
} }
} }

View file

@ -1,4 +1,4 @@
use crate::clear_color::ClearColorConfig; use crate::{clear_color::ClearColorConfig, tonemapping::Tonemapping};
use bevy_ecs::{prelude::*, query::QueryItem}; use bevy_ecs::{prelude::*, query::QueryItem};
use bevy_reflect::{Reflect, ReflectDeserialize, ReflectSerialize}; use bevy_reflect::{Reflect, ReflectDeserialize, ReflectSerialize};
use bevy_render::{ use bevy_render::{
@ -66,6 +66,7 @@ pub struct Camera3dBundle {
pub transform: Transform, pub transform: Transform,
pub global_transform: GlobalTransform, pub global_transform: GlobalTransform,
pub camera_3d: Camera3d, 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 // 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 { fn default() -> Self {
Self { Self {
camera_render_graph: CameraRenderGraph::new(crate::core_3d::graph::NAME), camera_render_graph: CameraRenderGraph::new(crate::core_3d::graph::NAME),
tonemapping: Tonemapping { is_enabled: true },
camera: Default::default(), camera: Default::default(),
projection: Default::default(), projection: Default::default(),
visible_entities: Default::default(), visible_entities: Default::default(),

View file

@ -8,6 +8,8 @@ pub mod graph {
} }
pub mod node { pub mod node {
pub const MAIN_PASS: &str = "main_pass"; 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 bevy_utils::{FloatOrd, HashMap};
use crate::{tonemapping::TonemappingNode, upscaling::UpscalingNode};
pub struct Core3dPlugin; pub struct Core3dPlugin;
impl Plugin for Core3dPlugin { impl Plugin for Core3dPlugin {
@ -62,10 +66,14 @@ impl Plugin for Core3dPlugin {
.add_system_to_stage(RenderStage::PhaseSort, sort_phase_system::<Transparent3d>); .add_system_to_stage(RenderStage::PhaseSort, sort_phase_system::<Transparent3d>);
let pass_node_3d = MainPass3dNode::new(&mut render_app.world); 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::<RenderGraph>(); let mut graph = render_app.world.resource_mut::<RenderGraph>();
let mut draw_3d_graph = RenderGraph::default(); 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::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( let input_node_id = draw_3d_graph.set_input(vec![SlotInfo::new(
graph::input::VIEW_ENTITY, graph::input::VIEW_ENTITY,
SlotType::Entity, SlotType::Entity,
@ -78,6 +86,28 @@ impl Plugin for Core3dPlugin {
MainPass3dNode::IN_VIEW, MainPass3dNode::IN_VIEW,
) )
.unwrap(); .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); graph.add_sub_graph(graph::NAME, draw_3d_graph);
} }
} }

View file

@ -0,0 +1,16 @@
#define_import_path bevy_core_pipeline::fullscreen_vertex_shader
struct FullscreenVertexOutput {
@builtin(position)
position: vec4<f32>,
@location(0)
uv: vec2<f32>,
};
@vertex
fn fullscreen_vertex_shader(@builtin(vertex_index) vertex_index: u32) -> FullscreenVertexOutput {
let uv = vec2<f32>(f32(vertex_index >> 1u), f32(vertex_index & 1u)) * 2.0;
let clip_position = vec4<f32>(uv * vec2<f32>(2.0, -2.0) + vec2<f32>(-1.0, 1.0), 0.0, 1.0);
return FullscreenVertexOutput(clip_position, uv);
}

View file

@ -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<f32>;
/// [[location(0)]]
/// uv: vec2<f32>;
/// };
/// ```
/// 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(),
}
}

View file

@ -1,6 +1,9 @@
pub mod clear_color; pub mod clear_color;
pub mod core_2d; pub mod core_2d;
pub mod core_3d; pub mod core_3d;
pub mod fullscreen_vertex_shader;
pub mod tonemapping;
pub mod upscaling;
pub mod prelude { pub mod prelude {
#[doc(hidden)] #[doc(hidden)]
@ -15,19 +18,32 @@ use crate::{
clear_color::{ClearColor, ClearColorConfig}, clear_color::{ClearColor, ClearColorConfig},
core_2d::Core2dPlugin, core_2d::Core2dPlugin,
core_3d::Core3dPlugin, core_3d::Core3dPlugin,
fullscreen_vertex_shader::FULLSCREEN_SHADER_HANDLE,
tonemapping::TonemappingPlugin,
upscaling::UpscalingPlugin,
}; };
use bevy_app::{App, Plugin}; 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)] #[derive(Default)]
pub struct CorePipelinePlugin; pub struct CorePipelinePlugin;
impl Plugin for CorePipelinePlugin { impl Plugin for CorePipelinePlugin {
fn build(&self, app: &mut App) { fn build(&self, app: &mut App) {
load_internal_asset!(
app,
FULLSCREEN_SHADER_HANDLE,
"fullscreen_vertex_shader/fullscreen.wgsl",
Shader::from_wgsl
);
app.register_type::<ClearColor>() app.register_type::<ClearColor>()
.register_type::<ClearColorConfig>() .register_type::<ClearColorConfig>()
.init_resource::<ClearColor>() .init_resource::<ClearColor>()
.add_plugin(ExtractResourcePlugin::<ClearColor>::default()) .add_plugin(ExtractResourcePlugin::<ClearColor>::default())
.add_plugin(TonemappingPlugin)
.add_plugin(UpscalingPlugin)
.add_plugin(Core2dPlugin) .add_plugin(Core2dPlugin)
.add_plugin(Core3dPlugin); .add_plugin(Core3dPlugin);
} }

View file

@ -0,0 +1,11 @@
#import bevy_core_pipeline::fullscreen_vertex_shader
@group(0) @binding(0)
var texture: texture_2d<f32>;
@group(0) @binding(1)
var texture_sampler: sampler;
@fragment
fn fs_main(in: FullscreenVertexOutput) -> @location(0) vec4<f32> {
return textureSample(texture, texture_sampler, in.uv);
}

View file

@ -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::<Tonemapping>::default());
if let Ok(render_app) = app.get_sub_app_mut(RenderApp) {
render_app.init_resource::<TonemappingPipeline>();
}
}
}
#[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::<RenderDevice>()
.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::<PipelineCache>();
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<Camera>;
fn extract_component(item: QueryItem<Self::Query>) -> Self {
item.clone()
}
}

View file

@ -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<ExtractedView>>,
cached_texture_bind_group: Mutex<Option<(TextureViewId, BindGroup)>>,
}
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<SlotInfo> {
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::<PipelineCache>();
let tonemapping_pipeline = world.resource::<TonemappingPipeline>();
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(())
}
}

View file

@ -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<f32>;
@group(0) @binding(1)
var hdr_sampler: sampler;
@fragment
fn fs_main(in: FullscreenVertexOutput) -> @location(0) vec4<f32> {
let hdr_color = textureSample(hdr_texture, hdr_sampler, in.uv);
return vec4<f32>(reinhard_luminance(hdr_color.rgb), hdr_color.a);
}

View file

@ -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<f32>) -> vec3<f32> {
return color / (1.0 + color);
}
fn tonemapping_reinhard_extended(color: vec3<f32>, max_white: f32) -> vec3<f32> {
let numerator = color * (1.0 + (color / vec3<f32>(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>) -> f32 {
return dot(v, vec3<f32>(0.2126, 0.7152, 0.0722));
}
fn tonemapping_change_luminance(c_in: vec3<f32>, l_out: f32) -> vec3<f32> {
let l_in = tonemapping_luminance(c_in);
return c_in * (l_out / l_in);
}
fn reinhard_luminance(color: vec3<f32>) -> vec3<f32> {
let l_old = tonemapping_luminance(color);
let l_new = l_old / (1.0 + l_old);
return tonemapping_change_luminance(color, l_new);
}

View file

@ -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::<UpscalingPipeline>()
.init_resource::<SpecializedRenderPipelines<UpscalingPipeline>>()
.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::<RenderDevice>();
let surface_texture_format = render_world.resource::<SurfaceTextureFormat>().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<PipelineCache>,
mut pipelines: ResMut<SpecializedRenderPipelines<UpscalingPipeline>>,
upscaling_pipeline: Res<UpscalingPipeline>,
view_targets: Query<Entity, With<ExtractedView>>,
) {
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 });
}
}

View file

@ -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<ExtractedView>>,
cached_texture_bind_group: Mutex<Option<(TextureViewId, BindGroup)>>,
}
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<SlotInfo> {
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::<PipelineCache>().unwrap();
let upscaling_pipeline = world.get_resource::<UpscalingPipeline>().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(())
}
}

View file

@ -0,0 +1,13 @@
#import bevy_core_pipeline::fullscreen_vertex_shader
@group(0) @binding(0)
var hdr_texture: texture_2d<f32>;
@group(0) @binding(1)
var hdr_sampler: sampler;
@fragment
fn fs_main(in: FullscreenVertexOutput) -> @location(0) vec4<f32> {
let hdr_color = textureSample(hdr_texture, hdr_sampler, in.uv);
return hdr_color;
}

View file

@ -4,7 +4,10 @@ use crate::{
}; };
use bevy_app::{App, Plugin}; use bevy_app::{App, Plugin};
use bevy_asset::{AddAsset, AssetEvent, AssetServer, Assets, Handle}; 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_derive::{Deref, DerefMut};
use bevy_ecs::{ use bevy_ecs::{
entity::Entity, entity::Entity,
@ -327,6 +330,7 @@ pub fn queue_material_meshes<M: Material>(
mut views: Query<( mut views: Query<(
&ExtractedView, &ExtractedView,
&VisibleEntities, &VisibleEntities,
Option<&Tonemapping>,
&mut RenderPhase<Opaque3d>, &mut RenderPhase<Opaque3d>,
&mut RenderPhase<AlphaMask3d>, &mut RenderPhase<AlphaMask3d>,
&mut RenderPhase<Transparent3d>, &mut RenderPhase<Transparent3d>,
@ -334,8 +338,14 @@ pub fn queue_material_meshes<M: Material>(
) where ) where
M::Data: PartialEq + Eq + Hash + Clone, M::Data: PartialEq + Eq + Hash + Clone,
{ {
for (view, visible_entities, mut opaque_phase, mut alpha_mask_phase, mut transparent_phase) in for (
&mut views view,
visible_entities,
tonemapping,
mut opaque_phase,
mut alpha_mask_phase,
mut transparent_phase,
) in &mut views
{ {
let draw_opaque_pbr = opaque_draw_functions let draw_opaque_pbr = opaque_draw_functions
.read() .read()
@ -350,8 +360,15 @@ pub fn queue_material_meshes<M: Material>(
.get_id::<DrawMaterial<M>>() .get_id::<DrawMaterial<M>>()
.unwrap(); .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 rangefinder = view.rangefinder3d();
let msaa_key = MeshPipelineKey::from_msaa_samples(msaa.samples);
for visible_entity in &visible_entities.entities { for visible_entity in &visible_entities.entities {
if let Ok((material_handle, mesh_handle, mesh_uniform)) = if let Ok((material_handle, mesh_handle, mesh_uniform)) =
@ -361,7 +378,7 @@ pub fn queue_material_meshes<M: Material>(
if let Some(mesh) = render_meshes.get(mesh_handle) { if let Some(mesh) = render_meshes.get(mesh_handle) {
let mut mesh_key = let mut mesh_key =
MeshPipelineKey::from_primitive_topology(mesh.primitive_topology) MeshPipelineKey::from_primitive_topology(mesh.primitive_topology)
| msaa_key; | view_key;
let alpha_mode = material.properties.alpha_mode; let alpha_mode = material.properties.alpha_mode;
if let AlphaMode::Blend = alpha_mode { if let AlphaMode::Blend = alpha_mode {
mesh_key |= MeshPipelineKey::TRANSPARENT_MAIN_PASS; mesh_key |= MeshPipelineKey::TRANSPARENT_MAIN_PASS;

View file

@ -1031,6 +1031,7 @@ pub fn prepare_lights(
), ),
transform: view_translation * *view_rotation, transform: view_translation * *view_rotation,
projection: cube_face_projection, projection: cube_face_projection,
hdr: false,
}, },
RenderPhase::<Shadow>::default(), RenderPhase::<Shadow>::default(),
LightEntity::Point { LightEntity::Point {
@ -1086,6 +1087,7 @@ pub fn prepare_lights(
), ),
transform: spot_view_transform, transform: spot_view_transform,
projection: spot_projection, projection: spot_projection,
hdr: false,
}, },
RenderPhase::<Shadow>::default(), RenderPhase::<Shadow>::default(),
LightEntity::Spot { light_entity }, LightEntity::Spot { light_entity },
@ -1169,6 +1171,7 @@ pub fn prepare_lights(
), ),
transform: GlobalTransform::from(view.inverse()), transform: GlobalTransform::from(view.inverse()),
projection, projection,
hdr: false,
}, },
RenderPhase::<Shadow>::default(), RenderPhase::<Shadow>::default(),
LightEntity::Directional { light_entity }, LightEntity::Directional { light_entity },

View file

@ -21,9 +21,11 @@ use bevy_render::{
render_asset::RenderAssets, render_asset::RenderAssets,
render_phase::{EntityRenderCommand, RenderCommandResult, TrackedRenderPass}, render_phase::{EntityRenderCommand, RenderCommandResult, TrackedRenderPass},
render_resource::*, render_resource::*,
renderer::{RenderDevice, RenderQueue, RenderTextureFormat}, renderer::{RenderDevice, RenderQueue},
texture::{DefaultImageSampler, GpuImage, Image, ImageSampler, TextureFormatPixelInfo}, texture::{
view::{ComputedVisibility, ViewUniform, ViewUniformOffset, ViewUniforms}, BevyDefault, DefaultImageSampler, GpuImage, Image, ImageSampler, TextureFormatPixelInfo,
},
view::{ComputedVisibility, ViewTarget, ViewUniform, ViewUniformOffset, ViewUniforms},
Extract, RenderApp, RenderStage, Extract, RenderApp, RenderStage,
}; };
use bevy_transform::components::GlobalTransform; use bevy_transform::components::GlobalTransform;
@ -268,10 +270,8 @@ impl FromWorld for MeshPipeline {
Res<RenderDevice>, Res<RenderDevice>,
Res<DefaultImageSampler>, Res<DefaultImageSampler>,
Res<RenderQueue>, Res<RenderQueue>,
Res<RenderTextureFormat>,
)> = SystemState::new(world); )> = SystemState::new(world);
let (render_device, default_sampler, render_queue, first_available_texture_format) = let (render_device, default_sampler, render_queue) = system_state.get_mut(world);
system_state.get_mut(world);
let clustered_forward_buffer_binding_type = render_device let clustered_forward_buffer_binding_type = render_device
.get_supported_read_only_binding_type(CLUSTERED_FORWARD_STORAGE_BUFFER_COUNT); .get_supported_read_only_binding_type(CLUSTERED_FORWARD_STORAGE_BUFFER_COUNT);
@ -438,7 +438,7 @@ impl FromWorld for MeshPipeline {
Extent3d::default(), Extent3d::default(),
TextureDimension::D2, TextureDimension::D2,
&[255u8; 4], &[255u8; 4],
first_available_texture_format.0, TextureFormat::bevy_default(),
); );
let texture = render_device.create_texture(&image.texture_descriptor); let texture = render_device.create_texture(&image.texture_descriptor);
let sampler = match image.sampler_descriptor { let sampler = match image.sampler_descriptor {
@ -516,6 +516,8 @@ bitflags::bitflags! {
pub struct MeshPipelineKey: u32 { pub struct MeshPipelineKey: u32 {
const NONE = 0; const NONE = 0;
const TRANSPARENT_MAIN_PASS = (1 << 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 MSAA_RESERVED_BITS = Self::MSAA_MASK_BITS << Self::MSAA_SHIFT_BITS;
const PRIMITIVE_TOPOLOGY_RESERVED_BITS = Self::PRIMITIVE_TOPOLOGY_MASK_BITS << Self::PRIMITIVE_TOPOLOGY_SHIFT_BITS; const 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() 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 { pub fn msaa_samples(&self) -> u32 {
1 << ((self.bits >> Self::MSAA_SHIFT_BITS) & Self::MSAA_MASK_BITS) 1 << ((self.bits >> Self::MSAA_SHIFT_BITS) & Self::MSAA_MASK_BITS)
} }
@ -624,6 +634,15 @@ impl SpecializedMeshPipeline for MeshPipeline {
depth_write_enabled = true; 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 { Ok(RenderPipelineDescriptor {
vertex: VertexState { vertex: VertexState {
shader: MESH_SHADER_HANDLE.typed::<Shader>(), shader: MESH_SHADER_HANDLE.typed::<Shader>(),
@ -636,7 +655,7 @@ impl SpecializedMeshPipeline for MeshPipeline {
shader_defs, shader_defs,
entry_point: "fragment".into(), entry_point: "fragment".into(),
targets: vec![Some(ColorTargetState { targets: vec![Some(ColorTargetState {
format: self.dummy_white_gpu_image.texture_format, format,
blend, blend,
write_mask: ColorWrites::ALL, write_mask: ColorWrites::ALL,
})], })],

View file

@ -87,8 +87,10 @@ fn fragment(in: FragmentInput) -> @location(0) vec4<f32> {
in.is_front, in.is_front,
); );
pbr_input.V = calculate_view(in.world_position, pbr_input.is_orthographic); pbr_input.V = calculate_view(in.world_position, pbr_input.is_orthographic);
output_color = pbr(pbr_input);
output_color = tone_mapping(pbr(pbr_input)); #ifdef TONEMAP_IN_SHADER
output_color = tone_mapping(output_color);
#endif
} else { } else {
output_color = alpha_discard(material, output_color); output_color = alpha_discard(material, output_color);
} }

View file

@ -1,5 +1,10 @@
#define_import_path bevy_pbr::pbr_functions #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<f32>) -> vec4<f32>{ fn alpha_discard(material: StandardMaterial, output_color: vec4<f32>) -> vec4<f32>{
var color = output_color; var color = output_color;
if ((material.flags & STANDARD_MATERIAL_FLAGS_ALPHA_MODE_OPAQUE) != 0u) { if ((material.flags & STANDARD_MATERIAL_FLAGS_ALPHA_MODE_OPAQUE) != 0u) {
@ -245,6 +250,7 @@ fn pbr(
return output_color; return output_color;
} }
#ifdef TONEMAP_IN_SHADER
fn tone_mapping(in: vec4<f32>) -> vec4<f32> { fn tone_mapping(in: vec4<f32>) -> vec4<f32> {
// tone_mapping // tone_mapping
return vec4<f32>(reinhard_luminance(in.rgb), in.a); return vec4<f32>(reinhard_luminance(in.rgb), in.a);
@ -253,4 +259,5 @@ fn tone_mapping(in: vec4<f32>) -> vec4<f32> {
// Not needed with sRGB buffer // Not needed with sRGB buffer
// output_color.rgb = pow(output_color.rgb, vec3(1.0 / 2.2)); // output_color.rgb = pow(output_color.rgb, vec3(1.0 / 2.2));
} }
#endif

View file

@ -149,41 +149,6 @@ fn perceptualRoughnessToRoughness(perceptualRoughness: f32) -> f32 {
return clampedPerceptualRoughness * clampedPerceptualRoughness; return clampedPerceptualRoughness * clampedPerceptualRoughness;
} }
// from https://64.github.io/tonemapping/
// reinhard on RGB oversaturates colors
fn reinhard(color: vec3<f32>) -> vec3<f32> {
return color / (1.0 + color);
}
fn reinhard_extended(color: vec3<f32>, max_white: f32) -> vec3<f32> {
let numerator = color * (1.0 + (color / vec3<f32>(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>) -> f32 {
return dot(v, vec3<f32>(0.2126, 0.7152, 0.0722));
}
fn change_luminance(c_in: vec3<f32>, l_out: f32) -> vec3<f32> {
let l_in = luminance(c_in);
return c_in * (l_out / l_in);
}
fn reinhard_luminance(color: vec3<f32>) -> vec3<f32> {
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<f32>, max_white_l: f32) -> vec3<f32> {
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( fn point_light(
world_position: vec3<f32>, light: PointLight, roughness: f32, NdotV: f32, N: vec3<f32>, V: vec3<f32>, world_position: vec3<f32>, light: PointLight, roughness: f32, NdotV: f32, N: vec3<f32>, V: vec3<f32>,
R: vec3<f32>, F0: vec3<f32>, diffuseColor: vec3<f32> R: vec3<f32>, F0: vec3<f32>, diffuseColor: vec3<f32>

View file

@ -124,10 +124,11 @@ fn queue_wireframes(
for (view, visible_entities, mut opaque_phase) in &mut views { for (view, visible_entities, mut opaque_phase) in &mut views {
let rangefinder = view.rangefinder3d(); let rangefinder = view.rangefinder3d();
let view_key = msaa_key | MeshPipelineKey::from_hdr(view.hdr);
let add_render_phase = let add_render_phase =
|(entity, mesh_handle, mesh_uniform): (Entity, &Handle<Mesh>, &MeshUniform)| { |(entity, mesh_handle, mesh_uniform): (Entity, &Handle<Mesh>, &MeshUniform)| {
if let Some(mesh) = render_meshes.get(mesh_handle) { if let Some(mesh) = render_meshes.get(mesh_handle) {
let key = msaa_key let key = view_key
| MeshPipelineKey::from_primitive_topology(mesh.primitive_topology); | MeshPipelineKey::from_primitive_topology(mesh.primitive_topology);
let pipeline_id = pipelines.specialize( let pipeline_id = pipelines.specialize(
&mut pipeline_cache, &mut pipeline_cache,

View file

@ -96,6 +96,12 @@ pub struct Camera {
/// The "target" that this camera will render to. /// The "target" that this camera will render to.
#[reflect(ignore)] #[reflect(ignore)]
pub target: RenderTarget, 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 <https://github.com/bevyengine/bevy/pull/3425> for details.
// TODO: resolve the issues mentioned in the doc comment above, then remove the warning.
pub hdr: bool,
} }
impl Default for Camera { impl Default for Camera {
@ -106,6 +112,7 @@ impl Default for Camera {
viewport: None, viewport: None,
computed: Default::default(), computed: Default::default(),
target: Default::default(), target: Default::default(),
hdr: false,
} }
} }
} }
@ -478,6 +485,7 @@ pub fn extract_cameras(
ExtractedView { ExtractedView {
projection: camera.projection_matrix(), projection: camera.projection_matrix(),
transform: *transform, transform: *transform,
hdr: camera.hdr,
viewport: UVec4::new( viewport: UVec4::new(
viewport_origin.x, viewport_origin.x,
viewport_origin.y, viewport_origin.y,

View file

@ -48,7 +48,7 @@ use crate::{
primitives::{CubemapFrusta, Frustum}, primitives::{CubemapFrusta, Frustum},
render_graph::RenderGraph, render_graph::RenderGraph,
render_resource::{PipelineCache, Shader, ShaderLoader}, render_resource::{PipelineCache, Shader, ShaderLoader},
renderer::{render_system, RenderInstance, RenderTextureFormat}, renderer::{render_system, RenderInstance, SurfaceTextureFormat},
texture::BevyDefault, texture::BevyDefault,
view::{ViewPlugin, WindowRenderPlugin}, view::{ViewPlugin, WindowRenderPlugin},
}; };
@ -166,7 +166,7 @@ impl Plugin for RenderPlugin {
&options, &options,
&request_adapter_options, &request_adapter_options,
)); ));
let texture_format = RenderTextureFormat( let texture_format = SurfaceTextureFormat(
available_texture_formats available_texture_formats
.get(0) .get(0)
.cloned() .cloned()

View file

@ -102,10 +102,10 @@ pub struct RenderInstance(pub Instance);
#[derive(Resource, Clone, Deref, DerefMut)] #[derive(Resource, Clone, Deref, DerefMut)]
pub struct RenderAdapterInfo(pub AdapterInfo); 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. /// Initially it's the first element in `AvailableTextureFormats`, or Bevy default format.
#[derive(Resource, Clone, Deref, DerefMut)] #[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`]. /// The available [`TextureFormat`](wgpu::TextureFormat)s on the [`RenderAdapter`].
/// Will be inserted as a `Resource` after the renderer is initialized. /// Will be inserted as a `Resource` after the renderer is initialized.

View file

@ -111,11 +111,6 @@ pub trait BevyDefault {
impl BevyDefault for wgpu::TextureFormat { impl BevyDefault for wgpu::TextureFormat {
fn bevy_default() -> Self { fn bevy_default() -> Self {
if cfg!(target_os = "android") || cfg!(target_arch = "wasm32") { wgpu::TextureFormat::Rgba8UnormSrgb
// Bgra8UnormSrgb texture missing on some Android devices
wgpu::TextureFormat::Rgba8UnormSrgb
} else {
wgpu::TextureFormat::Bgra8UnormSrgb
}
} }
} }

View file

@ -1,10 +1,11 @@
pub mod visibility; pub mod visibility;
pub mod window; pub mod window;
use bevy_utils::HashMap;
pub use visibility::*; pub use visibility::*;
use wgpu::{ use wgpu::{
Color, Extent3d, Operations, RenderPassColorAttachment, TextureDescriptor, TextureDimension, Color, Extent3d, Operations, RenderPassColorAttachment, TextureDescriptor, TextureDimension,
TextureUsages, TextureFormat, TextureUsages,
}; };
pub use window::*; pub use window::*;
@ -15,8 +16,8 @@ use crate::{
rangefinder::ViewRangefinder3d, rangefinder::ViewRangefinder3d,
render_asset::RenderAssets, render_asset::RenderAssets,
render_resource::{DynamicUniformBuffer, ShaderType, Texture, TextureView}, render_resource::{DynamicUniformBuffer, ShaderType, Texture, TextureView},
renderer::{RenderDevice, RenderQueue, RenderTextureFormat}, renderer::{RenderDevice, RenderQueue},
texture::TextureCache, texture::{BevyDefault, TextureCache},
RenderApp, RenderStage, RenderApp, RenderStage,
}; };
use bevy_app::{App, Plugin}; use bevy_app::{App, Plugin};
@ -24,7 +25,6 @@ use bevy_ecs::prelude::*;
use bevy_math::{Mat4, UVec4, Vec3, Vec4}; use bevy_math::{Mat4, UVec4, Vec3, Vec4};
use bevy_reflect::Reflect; use bevy_reflect::Reflect;
use bevy_transform::components::GlobalTransform; use bevy_transform::components::GlobalTransform;
use bevy_utils::HashMap;
pub struct ViewPlugin; pub struct ViewPlugin;
@ -81,6 +81,7 @@ impl Default for Msaa {
pub struct ExtractedView { pub struct ExtractedView {
pub projection: Mat4, pub projection: Mat4,
pub transform: GlobalTransform, pub transform: GlobalTransform,
pub hdr: bool,
// uvec4(origin.x, origin.y, width, height) // uvec4(origin.x, origin.y, width, height)
pub viewport: UVec4, pub viewport: UVec4,
} }
@ -115,21 +116,74 @@ pub struct ViewUniformOffset {
pub offset: u32, pub offset: u32,
} }
#[derive(Clone)]
pub enum ViewMainTexture {
Hdr {
hdr_texture: TextureView,
sampled_hdr_texture: Option<TextureView>,
ldr_texture: TextureView,
},
Sdr {
texture: TextureView,
sampled_texture: Option<TextureView>,
},
}
impl ViewMainTexture {
pub fn texture(&self) -> &TextureView {
match self {
ViewMainTexture::Hdr { hdr_texture, .. } => hdr_texture,
ViewMainTexture::Sdr { texture, .. } => texture,
}
}
}
#[derive(Component)] #[derive(Component)]
pub struct ViewTarget { pub struct ViewTarget {
pub view: TextureView, pub main_texture: ViewMainTexture,
pub sampled_target: Option<TextureView>, pub out_texture: TextureView,
} }
impl ViewTarget { impl ViewTarget {
pub const TEXTURE_FORMAT_HDR: TextureFormat = TextureFormat::Rgba16Float;
pub fn get_color_attachment(&self, ops: Operations<Color>) -> RenderPassColorAttachment { pub fn get_color_attachment(&self, ops: Operations<Color>) -> RenderPassColorAttachment {
RenderPassColorAttachment { let (target, sampled) = match &self.main_texture {
view: self.sampled_target.as_ref().unwrap_or(&self.view), ViewMainTexture::Hdr {
resolve_target: if self.sampled_target.is_some() { hdr_texture,
Some(&self.view) sampled_hdr_texture,
} else { ..
None } => (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<Color>,
) -> RenderPassColorAttachment {
RenderPassColorAttachment {
view: match &self.main_texture {
ViewMainTexture::Hdr { hdr_texture, .. } => hdr_texture,
ViewMainTexture::Sdr { texture, .. } => texture,
},
resolve_target: None,
ops, ops,
} }
} }
@ -182,42 +236,89 @@ fn prepare_view_targets(
images: Res<RenderAssets<Image>>, images: Res<RenderAssets<Image>>,
msaa: Res<Msaa>, msaa: Res<Msaa>,
render_device: Res<RenderDevice>, render_device: Res<RenderDevice>,
texture_format: Res<RenderTextureFormat>,
mut texture_cache: ResMut<TextureCache>, mut texture_cache: ResMut<TextureCache>,
cameras: Query<(Entity, &ExtractedCamera)>, cameras: Query<(Entity, &ExtractedCamera, &ExtractedView)>,
) { ) {
let mut sampled_textures = HashMap::default(); let mut textures = HashMap::default();
for (entity, camera) in &cameras { for (entity, camera, view) in cameras.iter() {
if let Some(target_size) = camera.physical_target_size { if let Some(target_size) = camera.physical_target_size {
if let Some(texture_view) = camera.target.get_texture_view(&windows, &images) { if let Some(texture_view) = camera.target.get_texture_view(&windows, &images) {
let sampled_target = if msaa.samples > 1 { let size = Extent3d {
let sampled_texture = sampled_textures width: target_size.x,
.entry(camera.target.clone()) height: target_size.y,
.or_insert_with(|| { depth_or_array_layers: 1,
texture_cache.get( };
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, &render_device,
TextureDescriptor { TextureDescriptor {
label: Some("sampled_color_attachment_texture"), label: Some("ldr_texture"),
size: Extent3d { size,
width: target_size.x,
height: target_size.y,
depth_or_array_layers: 1,
},
mip_level_count: 1, mip_level_count: 1,
sample_count: msaa.samples, sample_count: 1,
dimension: TextureDimension::D2, dimension: TextureDimension::D2,
format: **texture_format, format: TextureFormat::bevy_default(),
usage: TextureUsages::RENDER_ATTACHMENT, usage: TextureUsages::RENDER_ATTACHMENT
| TextureUsages::TEXTURE_BINDING,
}, },
) );
});
Some(sampled_texture.default_view.clone()) ViewMainTexture::Hdr {
} else { hdr_texture: main_texture.default_view,
None 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 { commands.entity(entity).insert(ViewTarget {
view: texture_view.clone(), main_texture: main_texture.clone(),
sampled_target, out_texture: texture_view.clone(),
}); });
} }
} }

View file

@ -1,6 +1,6 @@
use bevy_app::{App, Plugin}; use bevy_app::{App, Plugin};
use bevy_asset::{AddAsset, AssetEvent, AssetServer, Assets, Handle}; 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_derive::{Deref, DerefMut};
use bevy_ecs::{ use bevy_ecs::{
entity::Entity, entity::Entity,
@ -31,7 +31,7 @@ use bevy_render::{
}, },
renderer::RenderDevice, renderer::RenderDevice,
texture::FallbackImage, texture::FallbackImage,
view::{ComputedVisibility, Msaa, Visibility, VisibleEntities}, view::{ComputedVisibility, ExtractedView, Msaa, Visibility, VisibleEntities},
Extract, RenderApp, RenderStage, Extract, RenderApp, RenderStage,
}; };
use bevy_transform::components::{GlobalTransform, Transform}; use bevy_transform::components::{GlobalTransform, Transform};
@ -306,20 +306,33 @@ pub fn queue_material2d_meshes<M: Material2d>(
render_meshes: Res<RenderAssets<Mesh>>, render_meshes: Res<RenderAssets<Mesh>>,
render_materials: Res<RenderMaterials2d<M>>, render_materials: Res<RenderMaterials2d<M>>,
material2d_meshes: Query<(&Handle<M>, &Mesh2dHandle, &Mesh2dUniform)>, material2d_meshes: Query<(&Handle<M>, &Mesh2dHandle, &Mesh2dUniform)>,
mut views: Query<(&VisibleEntities, &mut RenderPhase<Transparent2d>)>, mut views: Query<(
&ExtractedView,
&VisibleEntities,
Option<&Tonemapping>,
&mut RenderPhase<Transparent2d>,
)>,
) where ) where
M::Data: PartialEq + Eq + Hash + Clone, M::Data: PartialEq + Eq + Hash + Clone,
{ {
if material2d_meshes.is_empty() { if material2d_meshes.is_empty() {
return; 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 let draw_transparent_pbr = transparent_draw_functions
.read() .read()
.get_id::<DrawMaterial2d<M>>() .get_id::<DrawMaterial2d<M>>()
.unwrap(); .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 { for visible_entity in &visible_entities.entities {
if let Ok((material2d_handle, mesh2d_handle, mesh2d_uniform)) = if let Ok((material2d_handle, mesh2d_handle, mesh2d_uniform)) =
@ -327,7 +340,7 @@ pub fn queue_material2d_meshes<M: Material2d>(
{ {
if let Some(material2d) = render_materials.get(material2d_handle) { if let Some(material2d) = render_materials.get(material2d_handle) {
if let Some(mesh) = render_meshes.get(&mesh2d_handle.0) { 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); | Mesh2dPipelineKey::from_primitive_topology(mesh.primitive_topology);
let pipeline_id = pipelines.specialize( let pipeline_id = pipelines.specialize(

View file

@ -13,9 +13,13 @@ use bevy_render::{
render_asset::RenderAssets, render_asset::RenderAssets,
render_phase::{EntityRenderCommand, RenderCommandResult, TrackedRenderPass}, render_phase::{EntityRenderCommand, RenderCommandResult, TrackedRenderPass},
render_resource::*, render_resource::*,
renderer::{RenderDevice, RenderQueue, RenderTextureFormat}, renderer::{RenderDevice, RenderQueue},
texture::{DefaultImageSampler, GpuImage, Image, ImageSampler, TextureFormatPixelInfo}, texture::{
view::{ComputedVisibility, ExtractedView, ViewUniform, ViewUniformOffset, ViewUniforms}, BevyDefault, DefaultImageSampler, GpuImage, Image, ImageSampler, TextureFormatPixelInfo,
},
view::{
ComputedVisibility, ExtractedView, ViewTarget, ViewUniform, ViewUniformOffset, ViewUniforms,
},
Extract, RenderApp, RenderStage, Extract, RenderApp, RenderStage,
}; };
use bevy_transform::components::GlobalTransform; use bevy_transform::components::GlobalTransform;
@ -157,13 +161,9 @@ pub struct Mesh2dPipeline {
impl FromWorld for Mesh2dPipeline { impl FromWorld for Mesh2dPipeline {
fn from_world(world: &mut World) -> Self { fn from_world(world: &mut World) -> Self {
let mut system_state: SystemState<( let mut system_state: SystemState<(Res<RenderDevice>, Res<DefaultImageSampler>)> =
Res<RenderDevice>, SystemState::new(world);
Res<DefaultImageSampler>, let (render_device, default_sampler) = system_state.get_mut(world);
Res<RenderTextureFormat>,
)> = SystemState::new(world);
let (render_device, default_sampler, first_available_texture_format) =
system_state.get_mut(world);
let view_layout = render_device.create_bind_group_layout(&BindGroupLayoutDescriptor { let view_layout = render_device.create_bind_group_layout(&BindGroupLayoutDescriptor {
entries: &[ entries: &[
// View // View
@ -210,7 +210,7 @@ impl FromWorld for Mesh2dPipeline {
Extent3d::default(), Extent3d::default(),
TextureDimension::D2, TextureDimension::D2,
&[255u8; 4], &[255u8; 4],
first_available_texture_format.0, TextureFormat::bevy_default(),
); );
let texture = render_device.create_texture(&image.texture_descriptor); let texture = render_device.create_texture(&image.texture_descriptor);
let sampler = match image.sampler_descriptor { let sampler = match image.sampler_descriptor {
@ -286,6 +286,8 @@ bitflags::bitflags! {
// FIXME: make normals optional? // FIXME: make normals optional?
pub struct Mesh2dPipelineKey: u32 { pub struct Mesh2dPipelineKey: u32 {
const NONE = 0; 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 MSAA_RESERVED_BITS = Self::MSAA_MASK_BITS << Self::MSAA_SHIFT_BITS;
const PRIMITIVE_TOPOLOGY_RESERVED_BITS = Self::PRIMITIVE_TOPOLOGY_MASK_BITS << Self::PRIMITIVE_TOPOLOGY_SHIFT_BITS; const 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() 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 { pub fn msaa_samples(&self) -> u32 {
1 << ((self.bits >> Self::MSAA_SHIFT_BITS) & Self::MSAA_MASK_BITS) 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)); 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 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 { Ok(RenderPipelineDescriptor {
vertex: VertexState { vertex: VertexState {
shader: MESH2D_SHADER_HANDLE.typed::<Shader>(), shader: MESH2D_SHADER_HANDLE.typed::<Shader>(),
@ -378,7 +397,7 @@ impl SpecializedMeshPipeline for Mesh2dPipeline {
shader_defs, shader_defs,
entry_point: "fragment".into(), entry_point: "fragment".into(),
targets: vec![Some(ColorTargetState { targets: vec![Some(ColorTargetState {
format: self.dummy_white_gpu_image.texture_format, format,
blend: Some(BlendState::ALPHA_BLENDING), blend: Some(BlendState::ALPHA_BLENDING),
write_mask: ColorWrites::ALL, write_mask: ColorWrites::ALL,
})], })],

View file

@ -5,7 +5,7 @@ use crate::{
Sprite, SPRITE_SHADER_HANDLE, Sprite, SPRITE_SHADER_HANDLE,
}; };
use bevy_asset::{AssetEvent, Assets, Handle, HandleId}; 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::{ use bevy_ecs::{
prelude::*, prelude::*,
system::{lifetimeless::*, SystemParamItem, SystemState}, system::{lifetimeless::*, SystemParamItem, SystemState},
@ -20,10 +20,13 @@ use bevy_render::{
RenderPhase, SetItemPipeline, TrackedRenderPass, RenderPhase, SetItemPipeline, TrackedRenderPass,
}, },
render_resource::*, render_resource::*,
renderer::{RenderDevice, RenderQueue, RenderTextureFormat}, renderer::{RenderDevice, RenderQueue},
texture::{DefaultImageSampler, GpuImage, Image, ImageSampler, TextureFormatPixelInfo}, texture::{
BevyDefault, DefaultImageSampler, GpuImage, Image, ImageSampler, TextureFormatPixelInfo,
},
view::{ view::{
ComputedVisibility, Msaa, ViewUniform, ViewUniformOffset, ViewUniforms, VisibleEntities, ComputedVisibility, ExtractedView, Msaa, ViewTarget, ViewUniform, ViewUniformOffset,
ViewUniforms, VisibleEntities,
}, },
Extract, Extract,
}; };
@ -46,10 +49,8 @@ impl FromWorld for SpritePipeline {
Res<RenderDevice>, Res<RenderDevice>,
Res<DefaultImageSampler>, Res<DefaultImageSampler>,
Res<RenderQueue>, Res<RenderQueue>,
Res<RenderTextureFormat>,
)> = SystemState::new(world); )> = SystemState::new(world);
let (render_device, default_sampler, render_queue, first_available_texture_format) = let (render_device, default_sampler, render_queue) = system_state.get_mut(world);
system_state.get_mut(world);
let view_layout = render_device.create_bind_group_layout(&BindGroupLayoutDescriptor { let view_layout = render_device.create_bind_group_layout(&BindGroupLayoutDescriptor {
entries: &[BindGroupLayoutEntry { entries: &[BindGroupLayoutEntry {
@ -91,7 +92,7 @@ impl FromWorld for SpritePipeline {
Extent3d::default(), Extent3d::default(),
TextureDimension::D2, TextureDimension::D2,
&[255u8; 4], &[255u8; 4],
first_available_texture_format.0, TextureFormat::bevy_default(),
); );
let texture = render_device.create_texture(&image.texture_descriptor); let texture = render_device.create_texture(&image.texture_descriptor);
let sampler = match image.sampler_descriptor { let sampler = match image.sampler_descriptor {
@ -148,6 +149,8 @@ bitflags::bitflags! {
pub struct SpritePipelineKey: u32 { pub struct SpritePipelineKey: u32 {
const NONE = 0; const NONE = 0;
const COLORED = (1 << 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; const MSAA_RESERVED_BITS = Self::MSAA_MASK_BITS << Self::MSAA_SHIFT_BITS;
} }
} }
@ -165,6 +168,22 @@ impl SpritePipelineKey {
pub fn msaa_samples(&self) -> u32 { pub fn msaa_samples(&self) -> u32 {
1 << ((self.bits >> Self::MSAA_SHIFT_BITS) & Self::MSAA_MASK_BITS) 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 { impl SpecializedRenderPipeline for SpritePipeline {
@ -191,6 +210,15 @@ impl SpecializedRenderPipeline for SpritePipeline {
shader_defs.push("COLORED".to_string()); 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 { RenderPipelineDescriptor {
vertex: VertexState { vertex: VertexState {
shader: SPRITE_SHADER_HANDLE.typed::<Shader>(), shader: SPRITE_SHADER_HANDLE.typed::<Shader>(),
@ -203,7 +231,7 @@ impl SpecializedRenderPipeline for SpritePipeline {
shader_defs, shader_defs,
entry_point: "fragment".into(), entry_point: "fragment".into(),
targets: vec![Some(ColorTargetState { targets: vec![Some(ColorTargetState {
format: self.dummy_white_gpu_image.texture_format, format,
blend: Some(BlendState::ALPHA_BLENDING), blend: Some(BlendState::ALPHA_BLENDING),
write_mask: ColorWrites::ALL, write_mask: ColorWrites::ALL,
})], })],
@ -418,7 +446,12 @@ pub fn queue_sprites(
gpu_images: Res<RenderAssets<Image>>, gpu_images: Res<RenderAssets<Image>>,
msaa: Res<Msaa>, msaa: Res<Msaa>,
mut extracted_sprites: ResMut<ExtractedSprites>, mut extracted_sprites: ResMut<ExtractedSprites>,
mut views: Query<(&VisibleEntities, &mut RenderPhase<Transparent2d>)>, mut views: Query<(
&mut RenderPhase<Transparent2d>,
&VisibleEntities,
&ExtractedView,
Option<&Tonemapping>,
)>,
events: Res<SpriteAssetEvents>, events: Res<SpriteAssetEvents>,
) { ) {
// If an image has changed, the GpuImage has (probably) changed // 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() { if let Some(view_binding) = view_uniforms.uniforms.binding() {
let sprite_meta = &mut sprite_meta; let sprite_meta = &mut sprite_meta;
@ -448,18 +483,13 @@ pub fn queue_sprites(
})); }));
let draw_sprite_function = draw_functions.read().get_id::<DrawSprite>().unwrap(); let draw_sprite_function = draw_functions.read().get_id::<DrawSprite>().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 // Vertex buffer indices
let mut index = 0; let mut index = 0;
let mut colored_index = 0; let mut colored_index = 0;
// FIXME: VisibleEntities is ignored
let extracted_sprites = &mut extracted_sprites.sprites; let extracted_sprites = &mut extracted_sprites.sprites;
// Sort sprites by z for correct transparency and then by handle to improve batching // 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 // 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; 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.clear();
view_entities.extend(visible_entities.entities.iter().map(|e| e.id() as usize)); view_entities.extend(visible_entities.entities.iter().map(|e| e.id() as usize));
transparent_phase.items.reserve(extracted_sprites.len()); transparent_phase.items.reserve(extracted_sprites.len());

View file

@ -1,3 +1,7 @@
#ifdef TONEMAP_IN_SHADER
#import bevy_core_pipeline::tonemapping
#endif
struct View { struct View {
view_proj: mat4x4<f32>, view_proj: mat4x4<f32>,
inverse_view_proj: mat4x4<f32>, inverse_view_proj: mat4x4<f32>,
@ -48,5 +52,10 @@ fn fragment(in: VertexOutput) -> @location(0) vec4<f32> {
#ifdef COLORED #ifdef COLORED
color = in.color * color; color = in.color * color;
#endif #endif
#ifdef TONEMAP_IN_SHADER
color = vec4<f32>(reinhard_luminance(color.rgb), color.a);
#endif
return color; return color;
} }

View file

@ -115,6 +115,18 @@ pub fn build_ui_render(app: &mut App) {
RunGraphOnViewNode::IN_VIEW, RunGraphOnViewNode::IN_VIEW,
) )
.unwrap(); .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) { 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, draw_ui_graph::node::UI_PASS,
) )
.unwrap(); .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 graph_3d
.add_slot_edge( .add_slot_edge(
graph_3d.input_node().unwrap().id, graph_3d.input_node().unwrap().id,
@ -258,6 +282,7 @@ pub fn extract_default_ui_camera_view<T: Component>(
0.0, 0.0,
UI_CAMERA_FAR + UI_CAMERA_TRANSFORM_OFFSET, UI_CAMERA_FAR + UI_CAMERA_TRANSFORM_OFFSET,
), ),
hdr: camera.hdr,
viewport: UVec4::new( viewport: UVec4::new(
physical_origin.x, physical_origin.x,
physical_origin.y, physical_origin.y,

View file

@ -1,29 +1,17 @@
use bevy_ecs::{prelude::*, system::SystemState}; use bevy_ecs::prelude::*;
use bevy_math::Vec2;
use bevy_render::{ use bevy_render::{
render_resource::*, render_resource::*, renderer::RenderDevice, texture::BevyDefault, view::ViewUniform,
renderer::{RenderDevice, RenderQueue, RenderTextureFormat},
texture::{DefaultImageSampler, GpuImage, Image, ImageSampler, TextureFormatPixelInfo},
view::ViewUniform,
}; };
#[derive(Resource)] #[derive(Resource)]
pub struct UiPipeline { pub struct UiPipeline {
pub view_layout: BindGroupLayout, pub view_layout: BindGroupLayout,
pub image_layout: BindGroupLayout, pub image_layout: BindGroupLayout,
pub dummy_white_gpu_image: GpuImage,
} }
impl FromWorld for UiPipeline { impl FromWorld for UiPipeline {
fn from_world(world: &mut World) -> Self { fn from_world(world: &mut World) -> Self {
let mut system_state: SystemState<( let render_device = world.resource::<RenderDevice>();
Res<RenderDevice>,
Res<DefaultImageSampler>,
Res<RenderQueue>,
Res<RenderTextureFormat>,
)> = SystemState::new(world);
let (render_device, default_sampler, render_queue, first_available_texture_format) =
system_state.get_mut(world);
let view_layout = render_device.create_bind_group_layout(&BindGroupLayoutDescriptor { let view_layout = render_device.create_bind_group_layout(&BindGroupLayoutDescriptor {
entries: &[BindGroupLayoutEntry { entries: &[BindGroupLayoutEntry {
@ -60,57 +48,10 @@ impl FromWorld for UiPipeline {
], ],
label: Some("ui_image_layout"), 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 { UiPipeline {
view_layout, view_layout,
image_layout, image_layout,
dummy_white_gpu_image,
} }
} }
} }
@ -147,7 +88,7 @@ impl SpecializedRenderPipeline for UiPipeline {
shader_defs, shader_defs,
entry_point: "fragment".into(), entry_point: "fragment".into(),
targets: vec![Some(ColorTargetState { targets: vec![Some(ColorTargetState {
format: self.dummy_white_gpu_image.texture_format, format: TextureFormat::bevy_default(),
blend: Some(BlendState::ALPHA_BLENDING), blend: Some(BlendState::ALPHA_BLENDING),
write_mask: ColorWrites::ALL, write_mask: ColorWrites::ALL,
})], })],

View file

@ -7,9 +7,7 @@ use bevy_ecs::{
use bevy_render::{ use bevy_render::{
render_graph::*, render_graph::*,
render_phase::*, render_phase::*,
render_resource::{ render_resource::{CachedRenderPipelineId, LoadOp, Operations, RenderPassDescriptor},
CachedRenderPipelineId, LoadOp, Operations, RenderPassColorAttachment, RenderPassDescriptor,
},
renderer::*, renderer::*,
view::*, view::*,
}; };
@ -81,14 +79,10 @@ impl Node for UiPassNode {
}; };
let pass_descriptor = RenderPassDescriptor { let pass_descriptor = RenderPassDescriptor {
label: Some("ui_pass"), label: Some("ui_pass"),
color_attachments: &[Some(RenderPassColorAttachment { color_attachments: &[Some(target.get_unsampled_color_attachment(Operations {
view: &target.view, load: LoadOp::Load,
resolve_target: None, store: true,
ops: Operations { }))],
load: LoadOp::Load,
store: true,
},
})],
depth_stencil_attachment: None, depth_stencil_attachment: None,
}; };

View file

@ -20,7 +20,7 @@ use bevy::{
TextureFormat, VertexBufferLayout, VertexFormat, VertexState, VertexStepMode, TextureFormat, VertexBufferLayout, VertexFormat, VertexState, VertexStepMode,
}, },
texture::BevyDefault, texture::BevyDefault,
view::VisibleEntities, view::{ExtractedView, ViewTarget, VisibleEntities},
Extract, RenderApp, RenderStage, Extract, RenderApp, RenderStage,
}, },
sprite::{ sprite::{
@ -146,6 +146,11 @@ impl SpecializedRenderPipeline for ColoredMesh2dPipeline {
let vertex_layout = let vertex_layout =
VertexBufferLayout::from_vertex_formats(VertexStepMode::Vertex, formats); VertexBufferLayout::from_vertex_formats(VertexStepMode::Vertex, formats);
let format = match key.contains(Mesh2dPipelineKey::HDR) {
true => ViewTarget::TEXTURE_FORMAT_HDR,
false => TextureFormat::bevy_default(),
};
RenderPipelineDescriptor { RenderPipelineDescriptor {
vertex: VertexState { vertex: VertexState {
// Use our custom shader // Use our custom shader
@ -161,7 +166,7 @@ impl SpecializedRenderPipeline for ColoredMesh2dPipeline {
shader_defs: Vec::new(), shader_defs: Vec::new(),
entry_point: "fragment".into(), entry_point: "fragment".into(),
targets: vec![Some(ColorTargetState { targets: vec![Some(ColorTargetState {
format: TextureFormat::bevy_default(), format,
blend: Some(BlendState::ALPHA_BLENDING), blend: Some(BlendState::ALPHA_BLENDING),
write_mask: ColorWrites::ALL, write_mask: ColorWrites::ALL,
})], })],
@ -311,19 +316,24 @@ pub fn queue_colored_mesh2d(
msaa: Res<Msaa>, msaa: Res<Msaa>,
render_meshes: Res<RenderAssets<Mesh>>, render_meshes: Res<RenderAssets<Mesh>>,
colored_mesh2d: Query<(&Mesh2dHandle, &Mesh2dUniform), With<ColoredMesh2d>>, colored_mesh2d: Query<(&Mesh2dHandle, &Mesh2dUniform), With<ColoredMesh2d>>,
mut views: Query<(&VisibleEntities, &mut RenderPhase<Transparent2d>)>, mut views: Query<(
&VisibleEntities,
&mut RenderPhase<Transparent2d>,
&ExtractedView,
)>,
) { ) {
if colored_mesh2d.is_empty() { if colored_mesh2d.is_empty() {
return; return;
} }
// Iterate each view (a camera is a view) // 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 let draw_colored_mesh2d = transparent_draw_functions
.read() .read()
.get_id::<DrawColoredMesh2d>() .get_id::<DrawColoredMesh2d>()
.unwrap(); .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 // Queue all entities visible to that view
for visible_entity in &visible_entities.entities { for visible_entity in &visible_entities.entities {