mirror of
https://github.com/bevyengine/bevy
synced 2024-11-22 20:53:53 +00:00
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:
parent
2023ce63c7
commit
838b318863
36 changed files with 1143 additions and 217 deletions
|
@ -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"
|
||||||
|
|
|
@ -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 },
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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(),
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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);
|
||||||
|
}
|
|
@ -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(),
|
||||||
|
}
|
||||||
|
}
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
11
crates/bevy_core_pipeline/src/tonemapping/blit.wgsl
Normal file
11
crates/bevy_core_pipeline/src/tonemapping/blit.wgsl
Normal 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);
|
||||||
|
}
|
145
crates/bevy_core_pipeline/src/tonemapping/mod.rs
Normal file
145
crates/bevy_core_pipeline/src/tonemapping/mod.rs
Normal 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()
|
||||||
|
}
|
||||||
|
}
|
133
crates/bevy_core_pipeline/src/tonemapping/node.rs
Normal file
133
crates/bevy_core_pipeline/src/tonemapping/node.rs
Normal 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(())
|
||||||
|
}
|
||||||
|
}
|
14
crates/bevy_core_pipeline/src/tonemapping/tonemapping.wgsl
Normal file
14
crates/bevy_core_pipeline/src/tonemapping/tonemapping.wgsl
Normal 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);
|
||||||
|
}
|
|
@ -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);
|
||||||
|
}
|
158
crates/bevy_core_pipeline/src/upscaling/mod.rs
Normal file
158
crates/bevy_core_pipeline/src/upscaling/mod.rs
Normal 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 });
|
||||||
|
}
|
||||||
|
}
|
123
crates/bevy_core_pipeline/src/upscaling/node.rs
Normal file
123
crates/bevy_core_pipeline/src/upscaling/node.rs
Normal 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(())
|
||||||
|
}
|
||||||
|
}
|
13
crates/bevy_core_pipeline/src/upscaling/upscaling.wgsl
Normal file
13
crates/bevy_core_pipeline/src/upscaling/upscaling.wgsl
Normal 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;
|
||||||
|
}
|
|
@ -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;
|
||||||
|
|
|
@ -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 },
|
||||||
|
|
|
@ -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,
|
||||||
})],
|
})],
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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()
|
||||||
|
|
|
@ -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.
|
||||||
|
|
|
@ -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
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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(),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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(
|
||||||
|
|
|
@ -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,
|
||||||
})],
|
})],
|
||||||
|
|
|
@ -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());
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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,
|
||||||
})],
|
})],
|
||||||
|
|
|
@ -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,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
Loading…
Reference in a new issue