@@ -224,9 +224,9 @@ pub fn create(
224224 Camera {
225225 // always load the previous frame (provides sketch like behavior)
226226 clear_color : ClearColorConfig :: None ,
227- // TODO: toggle this conditionally based on whether we need to write back MSAA
228- // when doing manual pixel updates
229- msaa_writeback : MsaaWriteback :: Off ,
227+ // force MSAA writeback every frame so manual writes to `main_texture` (e.g. via
228+ // `graphics_update_region`) survive the next resolve
229+ msaa_writeback : MsaaWriteback :: Always ,
230230 ..default ( )
231231 } ,
232232 target,
@@ -581,6 +581,7 @@ pub fn update_region_write(
581581 ) > ,
582582 graphics_query : Query < & Graphics > ,
583583 graphics_targets : Res < GraphicsTargets > ,
584+ render_device : Res < RenderDevice > ,
584585 render_queue : Res < RenderQueue > ,
585586) -> Result < ( ) > {
586587 let graphics = graphics_query
@@ -599,12 +600,12 @@ pub fn update_region_write(
599600 . get ( & entity)
600601 . ok_or ( ProcessingError :: GraphicsNotFound ) ?;
601602
602- let texture = view_target. main_texture ( ) ;
603+ let main = view_target. main_texture ( ) ;
603604 let bytes_per_row = width * px_size;
604605
605606 render_queue. write_texture (
606607 TexelCopyTextureInfo {
607- texture,
608+ texture : main ,
608609 mip_level : 0 ,
609610 origin : Origin3d { x, y, z : 0 } ,
610611 aspect : Default :: default ( ) ,
@@ -622,6 +623,25 @@ pub fn update_region_write(
622623 } ,
623624 ) ;
624625
626+ // when MSAA is enabled, the main pass renders into a sampled texture and resolves into one of
627+ // the two ping-pong main textures. bevy's main_texture atomic is reset to 0 at the start of
628+ // every frame, so next frame's MSAA writeback sources from whichever side the atomic points
629+ // at after reset, not necessarily the side we just wrote to. copy the current main (including
630+ // our pixel write, since queued writes are applied before this submit) into the other side so
631+ // the writeback propagates the updated content regardless of which direction the ping-pong
632+ // lands in.
633+ //
634+ // TODO: in theory bevy could just re-use the atomic to track which side is the latest main
635+ // texture and avoid this copy, unclear if that would cause other problems, but should be
636+ // considered for upstream if this ever matters for performance
637+ if view_target. sampled_main_texture ( ) . is_some ( ) {
638+ let other = view_target. main_texture_other ( ) ;
639+ let mut encoder =
640+ render_device. create_command_encoder ( & CommandEncoderDescriptor :: default ( ) ) ;
641+ encoder. copy_texture_to_texture ( main. as_image_copy ( ) , other. as_image_copy ( ) , graphics. size ) ;
642+ render_queue. submit ( std:: iter:: once ( encoder. finish ( ) ) ) ;
643+ }
644+
625645 Ok ( ( ) )
626646}
627647
0 commit comments