Path: blob/main/crates/bevy_post_process/src/msaa_writeback.rs
6595 views
use bevy_app::{App, Plugin};1use bevy_color::LinearRgba;2use bevy_core_pipeline::{3blit::{BlitPipeline, BlitPipelineKey},4core_2d::graph::{Core2d, Node2d},5core_3d::graph::{Core3d, Node3d},6};7use bevy_ecs::{prelude::*, query::QueryItem};8use bevy_render::{9camera::ExtractedCamera,10diagnostic::RecordDiagnostics,11render_graph::{NodeRunError, RenderGraphContext, RenderGraphExt, ViewNode, ViewNodeRunner},12render_resource::*,13renderer::RenderContext,14view::{Msaa, ViewTarget},15Render, RenderApp, RenderSystems,16};1718/// This enables "msaa writeback" support for the `core_2d` and `core_3d` pipelines, which can be enabled on cameras19/// using [`bevy_camera::Camera::msaa_writeback`]. See the docs on that field for more information.20#[derive(Default)]21pub struct MsaaWritebackPlugin;2223impl Plugin for MsaaWritebackPlugin {24fn build(&self, app: &mut App) {25let Some(render_app) = app.get_sub_app_mut(RenderApp) else {26return;27};28render_app.add_systems(29Render,30prepare_msaa_writeback_pipelines.in_set(RenderSystems::Prepare),31);32{33render_app34.add_render_graph_node::<ViewNodeRunner<MsaaWritebackNode>>(35Core2d,36Node2d::MsaaWriteback,37)38.add_render_graph_edge(Core2d, Node2d::MsaaWriteback, Node2d::StartMainPass);39}40{41render_app42.add_render_graph_node::<ViewNodeRunner<MsaaWritebackNode>>(43Core3d,44Node3d::MsaaWriteback,45)46.add_render_graph_edge(Core3d, Node3d::MsaaWriteback, Node3d::StartMainPass);47}48}49}5051#[derive(Default)]52pub struct MsaaWritebackNode;5354impl ViewNode for MsaaWritebackNode {55type ViewQuery = (56&'static ViewTarget,57&'static MsaaWritebackBlitPipeline,58&'static Msaa,59);6061fn run<'w>(62&self,63_graph: &mut RenderGraphContext,64render_context: &mut RenderContext<'w>,65(target, blit_pipeline_id, msaa): QueryItem<'w, '_, Self::ViewQuery>,66world: &'w World,67) -> Result<(), NodeRunError> {68if *msaa == Msaa::Off {69return Ok(());70}7172let blit_pipeline = world.resource::<BlitPipeline>();73let pipeline_cache = world.resource::<PipelineCache>();74let Some(pipeline) = pipeline_cache.get_render_pipeline(blit_pipeline_id.0) else {75return Ok(());76};7778let diagnostics = render_context.diagnostic_recorder();7980// The current "main texture" needs to be bound as an input resource, and we need the "other"81// unused target to be the "resolve target" for the MSAA write. Therefore this is the same82// as a post process write!83let post_process = target.post_process_write();8485let pass_descriptor = RenderPassDescriptor {86label: Some("msaa_writeback"),87// The target's "resolve target" is the "destination" in post_process.88// We will indirectly write the results to the "destination" using89// the MSAA resolve step.90color_attachments: &[Some(RenderPassColorAttachment {91// If MSAA is enabled, then the sampled texture will always exist92view: target.sampled_main_texture_view().unwrap(),93depth_slice: None,94resolve_target: Some(post_process.destination),95ops: Operations {96load: LoadOp::Clear(LinearRgba::BLACK.into()),97store: StoreOp::Store,98},99})],100depth_stencil_attachment: None,101timestamp_writes: None,102occlusion_query_set: None,103};104105let bind_group =106blit_pipeline.create_bind_group(render_context.render_device(), post_process.source);107108let mut render_pass = render_context109.command_encoder()110.begin_render_pass(&pass_descriptor);111let pass_span = diagnostics.pass_span(&mut render_pass, "msaa_writeback");112113render_pass.set_pipeline(pipeline);114render_pass.set_bind_group(0, &bind_group, &[]);115render_pass.draw(0..3, 0..1);116117pass_span.end(&mut render_pass);118119Ok(())120}121}122123#[derive(Component)]124pub struct MsaaWritebackBlitPipeline(CachedRenderPipelineId);125126fn prepare_msaa_writeback_pipelines(127mut commands: Commands,128pipeline_cache: Res<PipelineCache>,129mut pipelines: ResMut<SpecializedRenderPipelines<BlitPipeline>>,130blit_pipeline: Res<BlitPipeline>,131view_targets: Query<(Entity, &ViewTarget, &ExtractedCamera, &Msaa)>,132) {133for (entity, view_target, camera, msaa) in view_targets.iter() {134// only do writeback if writeback is enabled for the camera and this isn't the first camera in the target,135// as there is nothing to write back for the first camera.136if msaa.samples() > 1 && camera.msaa_writeback && camera.sorted_camera_index_for_target > 0137{138let key = BlitPipelineKey {139texture_format: view_target.main_texture_format(),140samples: msaa.samples(),141blend_state: None,142};143144let pipeline = pipelines.specialize(&pipeline_cache, &blit_pipeline, key);145commands146.entity(entity)147.insert(MsaaWritebackBlitPipeline(pipeline));148} else {149// This isn't strictly necessary now, but if we move to retained render entity state I don't150// want this to silently break151commands152.entity(entity)153.remove::<MsaaWritebackBlitPipeline>();154}155}156}157158159