bevy_post_process/
msaa_writeback.rs1use bevy_app::{App, Plugin};
2use bevy_camera::{ClearColorConfig, MsaaWriteback};
3use bevy_color::LinearRgba;
4use bevy_core_pipeline::{
5 blit::{BlitPipeline, BlitPipelineKey},
6 schedule::{Core2d, Core2dSystems, Core3d, Core3dSystems},
7};
8use bevy_ecs::prelude::*;
9use bevy_render::{
10 camera::ExtractedCamera,
11 diagnostic::RecordDiagnostics,
12 render_resource::*,
13 renderer::{RenderContext, ViewQuery},
14 view::{Msaa, ViewTarget},
15 Render, RenderApp, RenderSystems,
16};
17
18#[derive(Default)]
21pub struct MsaaWritebackPlugin;
22
23impl Plugin for MsaaWritebackPlugin {
24 fn build(&self, app: &mut App) {
25 let Some(render_app) = app.get_sub_app_mut(RenderApp) else {
26 return;
27 };
28 render_app.add_systems(
29 Render,
30 prepare_msaa_writeback_pipelines.in_set(RenderSystems::Prepare),
31 );
32 render_app.add_systems(Core3d, msaa_writeback.before(Core3dSystems::MainPass));
33 render_app.add_systems(Core2d, msaa_writeback.before(Core2dSystems::MainPass));
34 }
35}
36
37pub(crate) fn msaa_writeback(
38 view: ViewQuery<(&ViewTarget, &MsaaWritebackBlitPipeline, &Msaa)>,
39 blit_pipeline: Res<BlitPipeline>,
40 pipeline_cache: Res<PipelineCache>,
41 mut ctx: RenderContext,
42) {
43 let (target, blit_pipeline_id, msaa) = view.into_inner();
44
45 if *msaa == Msaa::Off {
46 return;
47 }
48
49 let Some(pipeline) = pipeline_cache.get_render_pipeline(blit_pipeline_id.0) else {
50 return;
51 };
52
53 let post_process = target.post_process_write();
57
58 let pass_descriptor = RenderPassDescriptor {
59 label: Some("msaa_writeback"),
60 color_attachments: &[Some(RenderPassColorAttachment {
64 view: target.sampled_main_texture_view().unwrap(),
66 depth_slice: None,
67 resolve_target: Some(post_process.destination),
68 ops: Operations {
69 load: LoadOp::Clear(LinearRgba::BLACK.into()),
70 store: StoreOp::Store,
71 },
72 })],
73 depth_stencil_attachment: None,
74 timestamp_writes: None,
75 occlusion_query_set: None,
76 multiview_mask: None,
77 };
78
79 let bind_group =
80 blit_pipeline.create_bind_group(ctx.render_device(), post_process.source, &pipeline_cache);
81
82 let diagnostics = ctx.diagnostic_recorder();
83 let diagnostics = diagnostics.as_deref();
84 let time_span = diagnostics.time_span(ctx.command_encoder(), "msaa_writeback");
85
86 {
87 let mut render_pass = ctx.command_encoder().begin_render_pass(&pass_descriptor);
88
89 render_pass.set_pipeline(pipeline);
90 render_pass.set_bind_group(0, &bind_group, &[]);
91 render_pass.draw(0..3, 0..1);
92 }
93
94 time_span.end(ctx.command_encoder());
95}
96
97#[derive(Component)]
98pub struct MsaaWritebackBlitPipeline(CachedRenderPipelineId);
99
100fn prepare_msaa_writeback_pipelines(
101 mut commands: Commands,
102 pipeline_cache: Res<PipelineCache>,
103 mut pipelines: ResMut<SpecializedRenderPipelines<BlitPipeline>>,
104 blit_pipeline: Res<BlitPipeline>,
105 view_targets: Query<(Entity, &ViewTarget, &ExtractedCamera, &Msaa)>,
106) {
107 for (entity, view_target, camera, msaa) in view_targets.iter() {
108 let should_writeback = match camera.msaa_writeback {
110 MsaaWriteback::Off => false,
111 MsaaWriteback::Auto => {
117 camera.sorted_camera_index_for_target > 0
118 || matches!(camera.clear_color, ClearColorConfig::None)
119 }
120 MsaaWriteback::Always => true,
121 };
122
123 if msaa.samples() > 1 && should_writeback {
124 let key = BlitPipelineKey {
125 target_format: view_target.main_texture_format(),
126 samples: msaa.samples(),
127 blend_state: None,
128 source_space: None,
129 };
130
131 let pipeline = pipelines.specialize(&pipeline_cache, &blit_pipeline, key);
132 commands
133 .entity(entity)
134 .insert(MsaaWritebackBlitPipeline(pipeline));
135 } else {
136 commands
139 .entity(entity)
140 .remove::<MsaaWritebackBlitPipeline>();
141 }
142 }
143}