Skip to content

Commit 03fd1b4

Browse files
tychedeliaalice-i-cecilemockersf
authored
Move Msaa to component (#14273)
Switches `Msaa` from being a globally configured resource to a per camera view component. Closes #7194 # Objective Allow individual views to describe their own MSAA settings. For example, when rendering to different windows or to different parts of the same view. ## Solution Make `Msaa` a component that is required on all camera bundles. ## Testing Ran a variety of examples to ensure that nothing broke. TODO: - [ ] Make sure android still works per previous comment in `extract_windows`. --- ## Migration Guide `Msaa` is no longer configured as a global resource, and should be specified on each spawned camera if a non-default setting is desired. --------- Co-authored-by: Alice Cecile <[email protected]> Co-authored-by: François Mockers <[email protected]>
1 parent 462da1e commit 03fd1b4

File tree

33 files changed

+223
-219
lines changed

33 files changed

+223
-219
lines changed

crates/bevy_core_pipeline/src/core_2d/camera_2d.rs

+4
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ use crate::core_2d::graph::Core2d;
22
use crate::tonemapping::{DebandDither, Tonemapping};
33
use bevy_ecs::prelude::*;
44
use bevy_reflect::Reflect;
5+
use bevy_render::prelude::Msaa;
56
use bevy_render::{
67
camera::{
78
Camera, CameraMainTextureUsages, CameraProjection, CameraRenderGraph,
@@ -35,6 +36,7 @@ pub struct Camera2dBundle {
3536
pub tonemapping: Tonemapping,
3637
pub deband_dither: DebandDither,
3738
pub main_texture_usages: CameraMainTextureUsages,
39+
pub msaa: Msaa,
3840
}
3941

4042
impl Default for Camera2dBundle {
@@ -58,6 +60,7 @@ impl Default for Camera2dBundle {
5860
tonemapping: Tonemapping::None,
5961
deband_dither: DebandDither::Disabled,
6062
main_texture_usages: Default::default(),
63+
msaa: Default::default(),
6164
}
6265
}
6366
}
@@ -90,6 +93,7 @@ impl Camera2dBundle {
9093
tonemapping: Tonemapping::None,
9194
deband_dither: DebandDither::Disabled,
9295
main_texture_usages: Default::default(),
96+
msaa: Default::default(),
9397
}
9498
}
9599
}

crates/bevy_core_pipeline/src/core_3d/camera_3d.rs

+3
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ use crate::{
44
};
55
use bevy_ecs::prelude::*;
66
use bevy_reflect::{Reflect, ReflectDeserialize, ReflectSerialize};
7+
use bevy_render::view::Msaa;
78
use bevy_render::{
89
camera::{Camera, CameraMainTextureUsages, CameraRenderGraph, Exposure, Projection},
910
extract_component::ExtractComponent,
@@ -152,6 +153,7 @@ pub struct Camera3dBundle {
152153
pub color_grading: ColorGrading,
153154
pub exposure: Exposure,
154155
pub main_texture_usages: CameraMainTextureUsages,
156+
pub msaa: Msaa,
155157
}
156158

157159
// NOTE: ideally Perspective and Orthographic defaults can share the same impl, but sadly it breaks rust's type inference
@@ -171,6 +173,7 @@ impl Default for Camera3dBundle {
171173
exposure: Default::default(),
172174
main_texture_usages: Default::default(),
173175
deband_dither: DebandDither::Enabled,
176+
msaa: Default::default(),
174177
}
175178
}
176179
}

crates/bevy_core_pipeline/src/core_3d/mod.rs

+22-13
Original file line numberDiff line numberDiff line change
@@ -610,16 +610,21 @@ pub fn extract_camera_prepass_phase(
610610
pub fn prepare_core_3d_depth_textures(
611611
mut commands: Commands,
612612
mut texture_cache: ResMut<TextureCache>,
613-
msaa: Res<Msaa>,
614613
render_device: Res<RenderDevice>,
615614
opaque_3d_phases: Res<ViewBinnedRenderPhases<Opaque3d>>,
616615
alpha_mask_3d_phases: Res<ViewBinnedRenderPhases<AlphaMask3d>>,
617616
transmissive_3d_phases: Res<ViewSortedRenderPhases<Transmissive3d>>,
618617
transparent_3d_phases: Res<ViewSortedRenderPhases<Transparent3d>>,
619-
views_3d: Query<(Entity, &ExtractedCamera, Option<&DepthPrepass>, &Camera3d)>,
618+
views_3d: Query<(
619+
Entity,
620+
&ExtractedCamera,
621+
Option<&DepthPrepass>,
622+
&Camera3d,
623+
&Msaa,
624+
)>,
620625
) {
621626
let mut render_target_usage = HashMap::default();
622-
for (view, camera, depth_prepass, camera_3d) in &views_3d {
627+
for (view, camera, depth_prepass, camera_3d, _msaa) in &views_3d {
623628
if !opaque_3d_phases.contains_key(&view)
624629
|| !alpha_mask_3d_phases.contains_key(&view)
625630
|| !transmissive_3d_phases.contains_key(&view)
@@ -641,13 +646,13 @@ pub fn prepare_core_3d_depth_textures(
641646
}
642647

643648
let mut textures = HashMap::default();
644-
for (entity, camera, _, camera_3d) in &views_3d {
649+
for (entity, camera, _, camera_3d, msaa) in &views_3d {
645650
let Some(physical_target_size) = camera.physical_target_size else {
646651
continue;
647652
};
648653

649654
let cached_texture = textures
650-
.entry(camera.target.clone())
655+
.entry((camera.target.clone(), msaa))
651656
.or_insert_with(|| {
652657
// The size of the depth texture
653658
let size = Extent3d {
@@ -779,11 +784,8 @@ pub fn prepare_core_3d_transmission_textures(
779784
}
780785

781786
// Disable MSAA and warn if using deferred rendering
782-
pub fn check_msaa(
783-
mut msaa: ResMut<Msaa>,
784-
deferred_views: Query<Entity, (With<Camera>, With<DeferredPrepass>)>,
785-
) {
786-
if !deferred_views.is_empty() {
787+
pub fn check_msaa(mut deferred_views: Query<&mut Msaa, (With<Camera>, With<DeferredPrepass>)>) {
788+
for mut msaa in deferred_views.iter_mut() {
787789
match *msaa {
788790
Msaa::Off => (),
789791
_ => {
@@ -799,7 +801,6 @@ pub fn check_msaa(
799801
pub fn prepare_prepass_textures(
800802
mut commands: Commands,
801803
mut texture_cache: ResMut<TextureCache>,
802-
msaa: Res<Msaa>,
803804
render_device: Res<RenderDevice>,
804805
opaque_3d_prepass_phases: Res<ViewBinnedRenderPhases<Opaque3dPrepass>>,
805806
alpha_mask_3d_prepass_phases: Res<ViewBinnedRenderPhases<AlphaMask3dPrepass>>,
@@ -808,6 +809,7 @@ pub fn prepare_prepass_textures(
808809
views_3d: Query<(
809810
Entity,
810811
&ExtractedCamera,
812+
&Msaa,
811813
Has<DepthPrepass>,
812814
Has<NormalPrepass>,
813815
Has<MotionVectorPrepass>,
@@ -819,8 +821,15 @@ pub fn prepare_prepass_textures(
819821
let mut deferred_textures = HashMap::default();
820822
let mut deferred_lighting_id_textures = HashMap::default();
821823
let mut motion_vectors_textures = HashMap::default();
822-
for (entity, camera, depth_prepass, normal_prepass, motion_vector_prepass, deferred_prepass) in
823-
&views_3d
824+
for (
825+
entity,
826+
camera,
827+
msaa,
828+
depth_prepass,
829+
normal_prepass,
830+
motion_vector_prepass,
831+
deferred_prepass,
832+
) in &views_3d
824833
{
825834
if !opaque_3d_prepass_phases.contains_key(&entity)
826835
&& !alpha_mask_3d_prepass_phases.contains_key(&entity)

crates/bevy_core_pipeline/src/dof/mod.rs

+4-5
Original file line numberDiff line numberDiff line change
@@ -515,11 +515,10 @@ impl FromWorld for DepthOfFieldGlobalBindGroupLayout {
515515
/// specific to each view.
516516
pub fn prepare_depth_of_field_view_bind_group_layouts(
517517
mut commands: Commands,
518-
view_targets: Query<(Entity, &DepthOfFieldSettings)>,
519-
msaa: Res<Msaa>,
518+
view_targets: Query<(Entity, &DepthOfFieldSettings, &Msaa)>,
520519
render_device: Res<RenderDevice>,
521520
) {
522-
for (view, dof_settings) in view_targets.iter() {
521+
for (view, dof_settings, msaa) in view_targets.iter() {
523522
// Create the bind group layout for the passes that take one input.
524523
let single_input = render_device.create_bind_group_layout(
525524
Some("depth of field bind group layout (single input)"),
@@ -646,16 +645,16 @@ pub fn prepare_depth_of_field_pipelines(
646645
mut commands: Commands,
647646
pipeline_cache: Res<PipelineCache>,
648647
mut pipelines: ResMut<SpecializedRenderPipelines<DepthOfFieldPipeline>>,
649-
msaa: Res<Msaa>,
650648
global_bind_group_layout: Res<DepthOfFieldGlobalBindGroupLayout>,
651649
view_targets: Query<(
652650
Entity,
653651
&ExtractedView,
654652
&DepthOfFieldSettings,
655653
&ViewDepthOfFieldBindGroupLayouts,
654+
&Msaa,
656655
)>,
657656
) {
658-
for (entity, view, dof_settings, view_bind_group_layouts) in view_targets.iter() {
657+
for (entity, view, dof_settings, view_bind_group_layouts, msaa) in view_targets.iter() {
659658
let dof_pipeline = DepthOfFieldPipeline {
660659
view_bind_group_layouts: view_bind_group_layouts.clone(),
661660
global_bind_group_layout: global_bind_group_layout.layout.clone(),

crates/bevy_core_pipeline/src/motion_blur/node.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -27,12 +27,13 @@ impl ViewNode for MotionBlurNode {
2727
&'static MotionBlurPipelineId,
2828
&'static ViewPrepassTextures,
2929
&'static MotionBlur,
30+
&'static Msaa,
3031
);
3132
fn run(
3233
&self,
3334
_graph: &mut RenderGraphContext,
3435
render_context: &mut RenderContext,
35-
(view_target, pipeline_id, prepass_textures, settings): QueryItem<Self::ViewQuery>,
36+
(view_target, pipeline_id, prepass_textures, settings, msaa): QueryItem<Self::ViewQuery>,
3637
world: &World,
3738
) -> Result<(), NodeRunError> {
3839
if settings.samples == 0 || settings.shutter_angle <= 0.0 {
@@ -60,7 +61,6 @@ impl ViewNode for MotionBlurNode {
6061

6162
let post_process = view_target.post_process_write();
6263

63-
let msaa = world.resource::<Msaa>();
6464
let layout = if msaa.samples() == 1 {
6565
&motion_blur_pipeline.layout
6666
} else {

crates/bevy_core_pipeline/src/motion_blur/pipeline.rs

+2-3
Original file line numberDiff line numberDiff line change
@@ -153,10 +153,9 @@ pub(crate) fn prepare_motion_blur_pipelines(
153153
pipeline_cache: Res<PipelineCache>,
154154
mut pipelines: ResMut<SpecializedRenderPipelines<MotionBlurPipeline>>,
155155
pipeline: Res<MotionBlurPipeline>,
156-
msaa: Res<Msaa>,
157-
views: Query<(Entity, &ExtractedView), With<MotionBlur>>,
156+
views: Query<(Entity, &ExtractedView, &Msaa), With<MotionBlur>>,
158157
) {
159-
for (entity, view) in &views {
158+
for (entity, view, msaa) in &views {
160159
let pipeline_id = pipelines.specialize(
161160
&pipeline_cache,
162161
&pipeline,

crates/bevy_core_pipeline/src/msaa_writeback.rs

+66-68
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,11 @@ use crate::{
66
use bevy_app::{App, Plugin};
77
use bevy_color::LinearRgba;
88
use bevy_ecs::prelude::*;
9+
use bevy_ecs::query::QueryItem;
10+
use bevy_render::render_graph::{ViewNode, ViewNodeRunner};
911
use bevy_render::{
1012
camera::ExtractedCamera,
11-
render_graph::{Node, NodeRunError, RenderGraphApp, RenderGraphContext},
13+
render_graph::{NodeRunError, RenderGraphApp, RenderGraphContext},
1214
renderer::RenderContext,
1315
view::{Msaa, ViewTarget},
1416
Render, RenderSet,
@@ -30,90 +32,87 @@ impl Plugin for MsaaWritebackPlugin {
3032
);
3133
{
3234
render_app
33-
.add_render_graph_node::<MsaaWritebackNode>(Core2d, Node2d::MsaaWriteback)
35+
.add_render_graph_node::<ViewNodeRunner<MsaaWritebackNode>>(
36+
Core2d,
37+
Node2d::MsaaWriteback,
38+
)
3439
.add_render_graph_edge(Core2d, Node2d::MsaaWriteback, Node2d::StartMainPass);
3540
}
3641
{
3742
render_app
38-
.add_render_graph_node::<MsaaWritebackNode>(Core3d, Node3d::MsaaWriteback)
43+
.add_render_graph_node::<ViewNodeRunner<MsaaWritebackNode>>(
44+
Core3d,
45+
Node3d::MsaaWriteback,
46+
)
3947
.add_render_graph_edge(Core3d, Node3d::MsaaWriteback, Node3d::StartMainPass);
4048
}
4149
}
4250
}
4351

44-
pub struct MsaaWritebackNode {
45-
cameras: QueryState<(&'static ViewTarget, &'static MsaaWritebackBlitPipeline)>,
46-
}
52+
#[derive(Default)]
53+
pub struct MsaaWritebackNode;
4754

48-
impl FromWorld for MsaaWritebackNode {
49-
fn from_world(world: &mut World) -> Self {
50-
Self {
51-
cameras: world.query(),
52-
}
53-
}
54-
}
55+
impl ViewNode for MsaaWritebackNode {
56+
type ViewQuery = (
57+
&'static ViewTarget,
58+
&'static MsaaWritebackBlitPipeline,
59+
&'static Msaa,
60+
);
5561

56-
impl Node for MsaaWritebackNode {
57-
fn update(&mut self, world: &mut World) {
58-
self.cameras.update_archetypes(world);
59-
}
60-
61-
fn run(
62+
fn run<'w>(
6263
&self,
63-
graph: &mut RenderGraphContext,
64-
render_context: &mut RenderContext,
65-
world: &World,
64+
_graph: &mut RenderGraphContext,
65+
render_context: &mut RenderContext<'w>,
66+
(target, blit_pipeline_id, msaa): QueryItem<'w, Self::ViewQuery>,
67+
world: &'w World,
6668
) -> Result<(), NodeRunError> {
67-
if *world.resource::<Msaa>() == Msaa::Off {
69+
if *msaa == Msaa::Off {
6870
return Ok(());
6971
}
7072

71-
let view_entity = graph.view_entity();
72-
if let Ok((target, blit_pipeline_id)) = self.cameras.get_manual(world, view_entity) {
73-
let blit_pipeline = world.resource::<BlitPipeline>();
74-
let pipeline_cache = world.resource::<PipelineCache>();
75-
let Some(pipeline) = pipeline_cache.get_render_pipeline(blit_pipeline_id.0) else {
76-
return Ok(());
77-
};
78-
79-
// The current "main texture" needs to be bound as an input resource, and we need the "other"
80-
// unused target to be the "resolve target" for the MSAA write. Therefore this is the same
81-
// as a post process write!
82-
let post_process = target.post_process_write();
73+
let blit_pipeline = world.resource::<BlitPipeline>();
74+
let pipeline_cache = world.resource::<PipelineCache>();
75+
let Some(pipeline) = pipeline_cache.get_render_pipeline(blit_pipeline_id.0) else {
76+
return Ok(());
77+
};
8378

84-
let pass_descriptor = RenderPassDescriptor {
85-
label: Some("msaa_writeback"),
86-
// The target's "resolve target" is the "destination" in post_process.
87-
// We will indirectly write the results to the "destination" using
88-
// the MSAA resolve step.
89-
color_attachments: &[Some(RenderPassColorAttachment {
90-
// If MSAA is enabled, then the sampled texture will always exist
91-
view: target.sampled_main_texture_view().unwrap(),
92-
resolve_target: Some(post_process.destination),
93-
ops: Operations {
94-
load: LoadOp::Clear(LinearRgba::BLACK.into()),
95-
store: StoreOp::Store,
96-
},
97-
})],
98-
depth_stencil_attachment: None,
99-
timestamp_writes: None,
100-
occlusion_query_set: None,
101-
};
79+
// The current "main texture" needs to be bound as an input resource, and we need the "other"
80+
// unused target to be the "resolve target" for the MSAA write. Therefore this is the same
81+
// as a post process write!
82+
let post_process = target.post_process_write();
83+
84+
let pass_descriptor = RenderPassDescriptor {
85+
label: Some("msaa_writeback"),
86+
// The target's "resolve target" is the "destination" in post_process.
87+
// We will indirectly write the results to the "destination" using
88+
// the MSAA resolve step.
89+
color_attachments: &[Some(RenderPassColorAttachment {
90+
// If MSAA is enabled, then the sampled texture will always exist
91+
view: target.sampled_main_texture_view().unwrap(),
92+
resolve_target: Some(post_process.destination),
93+
ops: Operations {
94+
load: LoadOp::Clear(LinearRgba::BLACK.into()),
95+
store: StoreOp::Store,
96+
},
97+
})],
98+
depth_stencil_attachment: None,
99+
timestamp_writes: None,
100+
occlusion_query_set: None,
101+
};
102102

103-
let bind_group = render_context.render_device().create_bind_group(
104-
None,
105-
&blit_pipeline.texture_bind_group,
106-
&BindGroupEntries::sequential((post_process.source, &blit_pipeline.sampler)),
107-
);
103+
let bind_group = render_context.render_device().create_bind_group(
104+
None,
105+
&blit_pipeline.texture_bind_group,
106+
&BindGroupEntries::sequential((post_process.source, &blit_pipeline.sampler)),
107+
);
108108

109-
let mut render_pass = render_context
110-
.command_encoder()
111-
.begin_render_pass(&pass_descriptor);
109+
let mut render_pass = render_context
110+
.command_encoder()
111+
.begin_render_pass(&pass_descriptor);
112112

113-
render_pass.set_pipeline(pipeline);
114-
render_pass.set_bind_group(0, &bind_group, &[]);
115-
render_pass.draw(0..3, 0..1);
116-
}
113+
render_pass.set_pipeline(pipeline);
114+
render_pass.set_bind_group(0, &bind_group, &[]);
115+
render_pass.draw(0..3, 0..1);
117116

118117
Ok(())
119118
}
@@ -127,10 +126,9 @@ fn prepare_msaa_writeback_pipelines(
127126
pipeline_cache: Res<PipelineCache>,
128127
mut pipelines: ResMut<SpecializedRenderPipelines<BlitPipeline>>,
129128
blit_pipeline: Res<BlitPipeline>,
130-
view_targets: Query<(Entity, &ViewTarget, &ExtractedCamera)>,
131-
msaa: Res<Msaa>,
129+
view_targets: Query<(Entity, &ViewTarget, &ExtractedCamera, &Msaa)>,
132130
) {
133-
for (entity, view_target, camera) in view_targets.iter() {
131+
for (entity, view_target, camera, msaa) in view_targets.iter() {
134132
// only do writeback if writeback is enabled for the camera and this isn't the first camera in the target,
135133
// as there is nothing to write back for the first camera.
136134
if msaa.samples() > 1 && camera.msaa_writeback && camera.sorted_camera_index_for_target > 0

0 commit comments

Comments
 (0)