Skip to content

Commit 852787e

Browse files
cartProfLander
authored andcommitted
Camera Output Modes, MSAA Writeback, and BlitPipeline (bevyengine#7671)
# Objective Alternative to bevyengine#7490. I wrote all of the code in this PR, but I have added @robtfm as co-author on commits that build on ideas from bevyengine#7490. I would not have been able to solve these problems on my own without much more time investment and I'm largely just rephrasing the ideas from that PR. Fixes bevyengine#7435 Fixes bevyengine#7361 Fixes bevyengine#5721 ## Solution This implements the solution I [outlined here](bevyengine#7490 (comment)). * Adds "msaa writeback" as an explicit "msaa camera feature" and default to msaa_writeback: true for each camera. If this is true, a camera has MSAA enabled, and it isn't the first camera for the target, add a writeback before the main pass for that camera. * Adds a CameraOutputMode, which can be used to configure if (and how) the results of a camera's rendering will be written to the final RenderTarget output texture (via the upscaling node). The `blend_state` and `color_attachment_load_op` are now configurable, giving much more control over how a camera will write to the output texture. * Made cameras with the same target share the same main_texture tracker by using `Arc<AtomicUsize>`, which ensures continuity across cameras. This was previously broken / could produce weird results in some cases. `ViewTarget::main_texture()` is now correct in every context. * Added a new generic / specializable BlitPipeline, which the new MsaaWritebackNode uses internally. The UpscalingPipelineNode now uses BlitPipeline instead of its own pipeline. We might ultimately need to fork this back out if we choose to add more configurability to the upscaling, but for now this will save on binary size by not embedding the same shader twice. * Moved the "camera sorting" logic from the camera driver node to its own system. The results are now stored in the `SortedCameras` resource, which can be used anywhere in the renderer. MSAA writeback makes use of this. --- ## Changelog - Added `Camera::msaa_writeback` which can enable and disable msaa writeback. - Added specializable `BlitPipeline` and ported the upscaling node to use this. - Added SortedCameras, exposing information that was previously internal to the camera driver node. - Made cameras with the same target share the same main_texture tracker, which ensures continuity across cameras.
1 parent 54edab7 commit 852787e

File tree

13 files changed

+487
-160
lines changed

13 files changed

+487
-160
lines changed
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,11 @@
11
#import bevy_core_pipeline::fullscreen_vertex_shader
22

33
@group(0) @binding(0)
4-
var hdr_texture: texture_2d<f32>;
4+
var in_texture: texture_2d<f32>;
55
@group(0) @binding(1)
6-
var hdr_sampler: sampler;
6+
var in_sampler: sampler;
77

88
@fragment
99
fn fs_main(in: FullscreenVertexOutput) -> @location(0) vec4<f32> {
10-
let hdr_color = textureSample(hdr_texture, hdr_sampler, in.uv);
11-
12-
return hdr_color;
10+
return textureSample(in_texture, in_sampler, in.uv);
1311
}
Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
use bevy_app::{App, Plugin};
2+
use bevy_asset::{load_internal_asset, HandleUntyped};
3+
use bevy_ecs::prelude::*;
4+
use bevy_reflect::TypeUuid;
5+
use bevy_render::{render_resource::*, renderer::RenderDevice, RenderApp};
6+
7+
use crate::fullscreen_vertex_shader::fullscreen_shader_vertex_state;
8+
9+
pub const BLIT_SHADER_HANDLE: HandleUntyped =
10+
HandleUntyped::weak_from_u64(Shader::TYPE_UUID, 2312396983770133547);
11+
12+
/// Adds support for specialized "blit pipelines", which can be used to write one texture to another.
13+
pub struct BlitPlugin;
14+
15+
impl Plugin for BlitPlugin {
16+
fn build(&self, app: &mut App) {
17+
load_internal_asset!(app, BLIT_SHADER_HANDLE, "blit.wgsl", Shader::from_wgsl);
18+
let Ok(render_app) = app.get_sub_app_mut(RenderApp) else {
19+
return
20+
};
21+
22+
render_app
23+
.init_resource::<BlitPipeline>()
24+
.init_resource::<SpecializedRenderPipelines<BlitPipeline>>();
25+
}
26+
}
27+
28+
#[derive(Resource)]
29+
pub struct BlitPipeline {
30+
pub texture_bind_group: BindGroupLayout,
31+
pub sampler: Sampler,
32+
}
33+
34+
impl FromWorld for BlitPipeline {
35+
fn from_world(render_world: &mut World) -> Self {
36+
let render_device = render_world.resource::<RenderDevice>();
37+
38+
let texture_bind_group =
39+
render_device.create_bind_group_layout(&BindGroupLayoutDescriptor {
40+
label: Some("blit_bind_group_layout"),
41+
entries: &[
42+
BindGroupLayoutEntry {
43+
binding: 0,
44+
visibility: ShaderStages::FRAGMENT,
45+
ty: BindingType::Texture {
46+
sample_type: TextureSampleType::Float { filterable: false },
47+
view_dimension: TextureViewDimension::D2,
48+
multisampled: false,
49+
},
50+
count: None,
51+
},
52+
BindGroupLayoutEntry {
53+
binding: 1,
54+
visibility: ShaderStages::FRAGMENT,
55+
ty: BindingType::Sampler(SamplerBindingType::NonFiltering),
56+
count: None,
57+
},
58+
],
59+
});
60+
61+
let sampler = render_device.create_sampler(&SamplerDescriptor::default());
62+
63+
BlitPipeline {
64+
texture_bind_group,
65+
sampler,
66+
}
67+
}
68+
}
69+
70+
#[derive(PartialEq, Eq, Hash, Clone, Copy)]
71+
pub struct BlitPipelineKey {
72+
pub texture_format: TextureFormat,
73+
pub blend_state: Option<BlendState>,
74+
pub samples: u32,
75+
}
76+
77+
impl SpecializedRenderPipeline for BlitPipeline {
78+
type Key = BlitPipelineKey;
79+
80+
fn specialize(&self, key: Self::Key) -> RenderPipelineDescriptor {
81+
RenderPipelineDescriptor {
82+
label: Some("blit pipeline".into()),
83+
layout: vec![self.texture_bind_group.clone()],
84+
vertex: fullscreen_shader_vertex_state(),
85+
fragment: Some(FragmentState {
86+
shader: BLIT_SHADER_HANDLE.typed(),
87+
shader_defs: vec![],
88+
entry_point: "fs_main".into(),
89+
targets: vec![Some(ColorTargetState {
90+
format: key.texture_format,
91+
blend: key.blend_state,
92+
write_mask: ColorWrites::ALL,
93+
})],
94+
}),
95+
primitive: PrimitiveState::default(),
96+
depth_stencil: None,
97+
multisample: MultisampleState {
98+
count: key.samples,
99+
..Default::default()
100+
},
101+
push_constant_ranges: Vec::new(),
102+
}
103+
}
104+
}

crates/bevy_core_pipeline/src/bloom/mod.rs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -513,7 +513,11 @@ impl FromWorld for BloomPipelines {
513513
dst_factor: BlendFactor::One,
514514
operation: BlendOperation::Add,
515515
},
516-
alpha: BlendComponent::REPLACE,
516+
alpha: BlendComponent {
517+
src_factor: BlendFactor::One,
518+
dst_factor: BlendFactor::One,
519+
operation: BlendOperation::Max,
520+
},
517521
}),
518522
write_mask: ColorWrites::ALL,
519523
})],

crates/bevy_core_pipeline/src/core_2d/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ pub mod graph {
77
pub const VIEW_ENTITY: &str = "view_entity";
88
}
99
pub mod node {
10+
pub const MSAA_WRITEBACK: &str = "msaa_writeback";
1011
pub const MAIN_PASS: &str = "main_pass";
1112
pub const BLOOM: &str = "bloom";
1213
pub const TONEMAPPING: &str = "tonemapping";

crates/bevy_core_pipeline/src/core_3d/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ pub mod graph {
77
pub const VIEW_ENTITY: &str = "view_entity";
88
}
99
pub mod node {
10+
pub const MSAA_WRITEBACK: &str = "msaa_writeback";
1011
pub const PREPASS: &str = "prepass";
1112
pub const MAIN_PASS: &str = "main_pass";
1213
pub const BLOOM: &str = "bloom";

crates/bevy_core_pipeline/src/lib.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
1+
pub mod blit;
12
pub mod bloom;
23
pub mod clear_color;
34
pub mod core_2d;
45
pub mod core_3d;
56
pub mod fullscreen_vertex_shader;
67
pub mod fxaa;
8+
pub mod msaa_writeback;
79
pub mod prepass;
810
pub mod tonemapping;
911
pub mod upscaling;
@@ -18,12 +20,14 @@ pub mod prelude {
1820
}
1921

2022
use crate::{
23+
blit::BlitPlugin,
2124
bloom::BloomPlugin,
2225
clear_color::{ClearColor, ClearColorConfig},
2326
core_2d::Core2dPlugin,
2427
core_3d::Core3dPlugin,
2528
fullscreen_vertex_shader::FULLSCREEN_SHADER_HANDLE,
2629
fxaa::FxaaPlugin,
30+
msaa_writeback::MsaaWritebackPlugin,
2731
prepass::{DepthPrepass, NormalPrepass},
2832
tonemapping::TonemappingPlugin,
2933
upscaling::UpscalingPlugin,
@@ -52,6 +56,8 @@ impl Plugin for CorePipelinePlugin {
5256
.add_plugin(ExtractResourcePlugin::<ClearColor>::default())
5357
.add_plugin(Core2dPlugin)
5458
.add_plugin(Core3dPlugin)
59+
.add_plugin(BlitPlugin)
60+
.add_plugin(MsaaWritebackPlugin)
5561
.add_plugin(TonemappingPlugin)
5662
.add_plugin(UpscalingPlugin)
5763
.add_plugin(BloomPlugin)
Lines changed: 181 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,181 @@
1+
use crate::blit::{BlitPipeline, BlitPipelineKey};
2+
use bevy_app::{App, Plugin};
3+
use bevy_ecs::prelude::*;
4+
use bevy_render::{
5+
camera::ExtractedCamera,
6+
render_graph::{Node, NodeRunError, RenderGraph, RenderGraphContext, SlotInfo, SlotType},
7+
renderer::RenderContext,
8+
view::{Msaa, ViewTarget},
9+
RenderSet,
10+
};
11+
use bevy_render::{render_resource::*, RenderApp};
12+
13+
/// This enables "msaa writeback" support for the `core_2d` and `core_3d` pipelines, which can be enabled on cameras
14+
/// using [`bevy_render::camera::Camera::msaa_writeback`]. See the docs on that field for more information.
15+
pub struct MsaaWritebackPlugin;
16+
17+
impl Plugin for MsaaWritebackPlugin {
18+
fn build(&self, app: &mut App) {
19+
let Ok(render_app) = app.get_sub_app_mut(RenderApp) else {
20+
return
21+
};
22+
23+
render_app.add_system(queue_msaa_writeback_pipelines.in_set(RenderSet::Queue));
24+
let msaa_writeback_2d = MsaaWritebackNode::new(&mut render_app.world);
25+
let msaa_writeback_3d = MsaaWritebackNode::new(&mut render_app.world);
26+
let mut graph = render_app.world.resource_mut::<RenderGraph>();
27+
if let Some(core_2d) = graph.get_sub_graph_mut(crate::core_2d::graph::NAME) {
28+
let input_node = core_2d.input_node().id;
29+
core_2d.add_node(
30+
crate::core_2d::graph::node::MSAA_WRITEBACK,
31+
msaa_writeback_2d,
32+
);
33+
core_2d.add_node_edge(
34+
crate::core_2d::graph::node::MSAA_WRITEBACK,
35+
crate::core_2d::graph::node::MAIN_PASS,
36+
);
37+
core_2d.add_slot_edge(
38+
input_node,
39+
crate::core_2d::graph::input::VIEW_ENTITY,
40+
crate::core_2d::graph::node::MSAA_WRITEBACK,
41+
MsaaWritebackNode::IN_VIEW,
42+
);
43+
}
44+
45+
if let Some(core_3d) = graph.get_sub_graph_mut(crate::core_3d::graph::NAME) {
46+
let input_node = core_3d.input_node().id;
47+
core_3d.add_node(
48+
crate::core_3d::graph::node::MSAA_WRITEBACK,
49+
msaa_writeback_3d,
50+
);
51+
core_3d.add_node_edge(
52+
crate::core_3d::graph::node::MSAA_WRITEBACK,
53+
crate::core_3d::graph::node::MAIN_PASS,
54+
);
55+
core_3d.add_slot_edge(
56+
input_node,
57+
crate::core_3d::graph::input::VIEW_ENTITY,
58+
crate::core_3d::graph::node::MSAA_WRITEBACK,
59+
MsaaWritebackNode::IN_VIEW,
60+
);
61+
}
62+
}
63+
}
64+
65+
pub struct MsaaWritebackNode {
66+
cameras: QueryState<(&'static ViewTarget, &'static MsaaWritebackBlitPipeline)>,
67+
}
68+
69+
impl MsaaWritebackNode {
70+
pub const IN_VIEW: &'static str = "view";
71+
72+
pub fn new(world: &mut World) -> Self {
73+
Self {
74+
cameras: world.query(),
75+
}
76+
}
77+
}
78+
79+
impl Node for MsaaWritebackNode {
80+
fn input(&self) -> Vec<SlotInfo> {
81+
vec![SlotInfo::new(Self::IN_VIEW, SlotType::Entity)]
82+
}
83+
fn update(&mut self, world: &mut World) {
84+
self.cameras.update_archetypes(world);
85+
}
86+
fn run(
87+
&self,
88+
graph: &mut RenderGraphContext,
89+
render_context: &mut RenderContext,
90+
world: &World,
91+
) -> Result<(), NodeRunError> {
92+
let view_entity = graph.get_input_entity(Self::IN_VIEW)?;
93+
if let Ok((target, blit_pipeline_id)) = self.cameras.get_manual(world, view_entity) {
94+
let blit_pipeline = world.resource::<BlitPipeline>();
95+
let pipeline_cache = world.resource::<PipelineCache>();
96+
let pipeline = pipeline_cache
97+
.get_render_pipeline(blit_pipeline_id.0)
98+
.unwrap();
99+
100+
// The current "main texture" needs to be bound as an input resource, and we need the "other"
101+
// unused target to be the "resolve target" for the MSAA write. Therefore this is the same
102+
// as a post process write!
103+
let post_process = target.post_process_write();
104+
105+
let pass_descriptor = RenderPassDescriptor {
106+
label: Some("msaa_writeback"),
107+
// The target's "resolve target" is the "destination" in post_process
108+
// We will indirectly write the results to the "destination" using
109+
// the MSAA resolve step.
110+
color_attachments: &[Some(target.get_color_attachment(Operations {
111+
load: LoadOp::Clear(Default::default()),
112+
store: true,
113+
}))],
114+
depth_stencil_attachment: None,
115+
};
116+
117+
let bind_group =
118+
render_context
119+
.render_device()
120+
.create_bind_group(&BindGroupDescriptor {
121+
label: None,
122+
layout: &blit_pipeline.texture_bind_group,
123+
entries: &[
124+
BindGroupEntry {
125+
binding: 0,
126+
resource: BindingResource::TextureView(post_process.source),
127+
},
128+
BindGroupEntry {
129+
binding: 1,
130+
resource: BindingResource::Sampler(&blit_pipeline.sampler),
131+
},
132+
],
133+
});
134+
135+
let mut render_pass = render_context
136+
.command_encoder()
137+
.begin_render_pass(&pass_descriptor);
138+
139+
render_pass.set_pipeline(pipeline);
140+
render_pass.set_bind_group(0, &bind_group, &[]);
141+
render_pass.draw(0..3, 0..1);
142+
}
143+
Ok(())
144+
}
145+
}
146+
147+
#[derive(Component)]
148+
pub struct MsaaWritebackBlitPipeline(CachedRenderPipelineId);
149+
150+
fn queue_msaa_writeback_pipelines(
151+
mut commands: Commands,
152+
pipeline_cache: Res<PipelineCache>,
153+
mut pipelines: ResMut<SpecializedRenderPipelines<BlitPipeline>>,
154+
blit_pipeline: Res<BlitPipeline>,
155+
view_targets: Query<(Entity, &ViewTarget, &ExtractedCamera)>,
156+
msaa: Res<Msaa>,
157+
) {
158+
for (entity, view_target, camera) in view_targets.iter() {
159+
// only do writeback if writeback is enabled for the camera and this isn't the first camera in the target,
160+
// as there is nothing to write back for the first camera.
161+
if msaa.samples() > 1 && camera.msaa_writeback && camera.sorted_camera_index_for_target > 0
162+
{
163+
let key = BlitPipelineKey {
164+
texture_format: view_target.main_texture_format(),
165+
samples: msaa.samples(),
166+
blend_state: None,
167+
};
168+
169+
let pipeline = pipelines.specialize(&pipeline_cache, &blit_pipeline, key);
170+
commands
171+
.entity(entity)
172+
.insert(MsaaWritebackBlitPipeline(pipeline));
173+
} else {
174+
// This isn't strictly necessary now, but if we move to retained render entity state I don't
175+
// want this to silently break
176+
commands
177+
.entity(entity)
178+
.remove::<MsaaWritebackBlitPipeline>();
179+
}
180+
}
181+
}

0 commit comments

Comments
 (0)