|  | 
|  | 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