bevy_post_process/
msaa_writeback.rs

1use bevy_app::{App, Plugin};
2use bevy_color::LinearRgba;
3use bevy_core_pipeline::{
4    blit::{BlitPipeline, BlitPipelineKey},
5    core_2d::graph::{Core2d, Node2d},
6    core_3d::graph::{Core3d, Node3d},
7};
8use bevy_ecs::{prelude::*, query::QueryItem};
9use bevy_render::{
10    camera::ExtractedCamera,
11    diagnostic::RecordDiagnostics,
12    render_graph::{NodeRunError, RenderGraphContext, RenderGraphExt, ViewNode, ViewNodeRunner},
13    render_resource::*,
14    renderer::RenderContext,
15    view::{Msaa, ViewTarget},
16    Render, RenderApp, RenderSystems,
17};
18
19/// This enables "msaa writeback" support for the `core_2d` and `core_3d` pipelines, which can be enabled on cameras
20/// using [`bevy_camera::Camera::msaa_writeback`]. See the docs on that field for more information.
21#[derive(Default)]
22pub struct MsaaWritebackPlugin;
23
24impl Plugin for MsaaWritebackPlugin {
25    fn build(&self, app: &mut App) {
26        let Some(render_app) = app.get_sub_app_mut(RenderApp) else {
27            return;
28        };
29        render_app.add_systems(
30            Render,
31            prepare_msaa_writeback_pipelines.in_set(RenderSystems::Prepare),
32        );
33        {
34            render_app
35                .add_render_graph_node::<ViewNodeRunner<MsaaWritebackNode>>(
36                    Core2d,
37                    Node2d::MsaaWriteback,
38                )
39                .add_render_graph_edge(Core2d, Node2d::MsaaWriteback, Node2d::StartMainPass);
40        }
41        {
42            render_app
43                .add_render_graph_node::<ViewNodeRunner<MsaaWritebackNode>>(
44                    Core3d,
45                    Node3d::MsaaWriteback,
46                )
47                .add_render_graph_edge(Core3d, Node3d::MsaaWriteback, Node3d::StartMainPass);
48        }
49    }
50}
51
52#[derive(Default)]
53pub struct MsaaWritebackNode;
54
55impl ViewNode for MsaaWritebackNode {
56    type ViewQuery = (
57        &'static ViewTarget,
58        &'static MsaaWritebackBlitPipeline,
59        &'static Msaa,
60    );
61
62    fn run<'w>(
63        &self,
64        _graph: &mut RenderGraphContext,
65        render_context: &mut RenderContext<'w>,
66        (target, blit_pipeline_id, msaa): QueryItem<'w, '_, Self::ViewQuery>,
67        world: &'w World,
68    ) -> Result<(), NodeRunError> {
69        if *msaa == Msaa::Off {
70            return Ok(());
71        }
72
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        let diagnostics = render_context.diagnostic_recorder();
80
81        // The current "main texture" needs to be bound as an input resource, and we need the "other"
82        // unused target to be the "resolve target" for the MSAA write. Therefore this is the same
83        // as a post process write!
84        let post_process = target.post_process_write();
85
86        let pass_descriptor = RenderPassDescriptor {
87            label: Some("msaa_writeback"),
88            // The target's "resolve target" is the "destination" in post_process.
89            // We will indirectly write the results to the "destination" using
90            // the MSAA resolve step.
91            color_attachments: &[Some(RenderPassColorAttachment {
92                // If MSAA is enabled, then the sampled texture will always exist
93                view: target.sampled_main_texture_view().unwrap(),
94                depth_slice: None,
95                resolve_target: Some(post_process.destination),
96                ops: Operations {
97                    load: LoadOp::Clear(LinearRgba::BLACK.into()),
98                    store: StoreOp::Store,
99                },
100            })],
101            depth_stencil_attachment: None,
102            timestamp_writes: None,
103            occlusion_query_set: None,
104        };
105
106        let bind_group =
107            blit_pipeline.create_bind_group(render_context.render_device(), post_process.source);
108
109        let mut render_pass = render_context
110            .command_encoder()
111            .begin_render_pass(&pass_descriptor);
112        let pass_span = diagnostics.pass_span(&mut render_pass, "msaa_writeback");
113
114        render_pass.set_pipeline(pipeline);
115        render_pass.set_bind_group(0, &bind_group, &[]);
116        render_pass.draw(0..3, 0..1);
117
118        pass_span.end(&mut render_pass);
119
120        Ok(())
121    }
122}
123
124#[derive(Component)]
125pub struct MsaaWritebackBlitPipeline(CachedRenderPipelineId);
126
127fn prepare_msaa_writeback_pipelines(
128    mut commands: Commands,
129    pipeline_cache: Res<PipelineCache>,
130    mut pipelines: ResMut<SpecializedRenderPipelines<BlitPipeline>>,
131    blit_pipeline: Res<BlitPipeline>,
132    view_targets: Query<(Entity, &ViewTarget, &ExtractedCamera, &Msaa)>,
133) {
134    for (entity, view_target, camera, msaa) in view_targets.iter() {
135        // only do writeback if writeback is enabled for the camera and this isn't the first camera in the target,
136        // as there is nothing to write back for the first camera.
137        if msaa.samples() > 1 && camera.msaa_writeback && camera.sorted_camera_index_for_target > 0
138        {
139            let key = BlitPipelineKey {
140                texture_format: view_target.main_texture_format(),
141                samples: msaa.samples(),
142                blend_state: None,
143            };
144
145            let pipeline = pipelines.specialize(&pipeline_cache, &blit_pipeline, key);
146            commands
147                .entity(entity)
148                .insert(MsaaWritebackBlitPipeline(pipeline));
149        } else {
150            // This isn't strictly necessary now, but if we move to retained render entity state I don't
151            // want this to silently break
152            commands
153                .entity(entity)
154                .remove::<MsaaWritebackBlitPipeline>();
155        }
156    }
157}