Skip to content

Commit 9f17c2e

Browse files
authored
Make ClearColorConfig::None work correctly (esp in the presence of MSAA) (#23844)
Two separate issues: 1. Because we were re-creating the atomics used for tracking which main texture is "active", post-process writes could be lost across frames because the atomic was being reset. This is a bug even without MSAA. 2. We need to force writeback when the user opts into `Load` semantics on their camera and MSAA is enabled, otherwise we'll throw away the main target texture incorrectly.
1 parent 7c5a1c3 commit 9f17c2e

2 files changed

Lines changed: 85 additions & 56 deletions

File tree

crates/bevy_post_process/src/msaa_writeback.rs

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
use bevy_app::{App, Plugin};
2-
use bevy_camera::MsaaWriteback;
2+
use bevy_camera::{ClearColorConfig, MsaaWriteback};
33
use bevy_color::LinearRgba;
44
use bevy_core_pipeline::{
55
blit::{BlitPipeline, BlitPipelineKey},
@@ -108,7 +108,15 @@ fn prepare_msaa_writeback_pipelines(
108108
// Determine if we should do MSAA writeback based on the camera's setting
109109
let should_writeback = match camera.msaa_writeback {
110110
MsaaWriteback::Off => false,
111-
MsaaWriteback::Auto => camera.sorted_camera_index_for_target > 0,
111+
// writeback is needed when the main pass must load existing content
112+
// from the main texture, either because another camera already
113+
// rendered to this target or because this camera preserves content across
114+
// frames via load op load. otherwise we'd read from an ephemeral sampled
115+
// texture that doesn't have the real content
116+
MsaaWriteback::Auto => {
117+
camera.sorted_camera_index_for_target > 0
118+
|| matches!(camera.clear_color, ClearColorConfig::None)
119+
}
112120
MsaaWriteback::Always => true,
113121
};
114122

crates/bevy_render/src/view/mod.rs

Lines changed: 75 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ use crate::{
2424
},
2525
GpuResourceAppExt, Render, RenderApp, RenderSystems,
2626
};
27-
use alloc::sync::Arc;
27+
use alloc::sync::{Arc, Weak};
2828
use bevy_app::{App, Plugin};
2929
use bevy_color::{LinearRgba, Oklaba};
3030
use bevy_derive::{Deref, DerefMut};
@@ -1127,6 +1127,13 @@ pub fn cleanup_view_targets_for_resize(
11271127
}
11281128
}
11291129

1130+
type MainTextureKey = (
1131+
Option<NormalizedRenderTarget>,
1132+
TextureUsages,
1133+
TextureFormat,
1134+
Msaa,
1135+
);
1136+
11301137
pub fn prepare_view_targets(
11311138
mut commands: Commands,
11321139
clear_color_global: Res<ClearColor>,
@@ -1140,7 +1147,10 @@ pub fn prepare_view_targets(
11401147
&Msaa,
11411148
)>,
11421149
view_target_attachments: Res<ViewTargetAttachments>,
1150+
mut main_texture_atomics: Local<HashMap<MainTextureKey, Weak<AtomicUsize>>>,
11431151
) {
1152+
main_texture_atomics.retain(|_, weak| weak.strong_count() > 0);
1153+
11441154
let mut textures = <HashMap<_, _>>::default();
11451155
for (entity, camera, view, texture_usage, msaa) in cameras.iter() {
11461156
let (Some(target_size), Some(out_attachment)) = (
@@ -1181,63 +1191,74 @@ pub fn prepare_view_targets(
11811191
}
11821192
});
11831193

1184-
let (a, b, sampled, main_texture) = textures
1185-
.entry((
1186-
camera.target.clone(),
1187-
texture_usage.0,
1188-
main_texture_format,
1189-
msaa,
1190-
))
1191-
.or_insert_with(|| {
1192-
let descriptor = TextureDescriptor {
1193-
label: None,
1194-
size: target_size.to_extents(),
1195-
mip_level_count: 1,
1196-
sample_count: 1,
1197-
dimension: TextureDimension::D2,
1198-
format: main_texture_format,
1199-
usage: texture_usage.0,
1200-
view_formats: match main_texture_format {
1201-
TextureFormat::Bgra8Unorm => &[TextureFormat::Bgra8UnormSrgb],
1202-
TextureFormat::Rgba8Unorm => &[TextureFormat::Rgba8UnormSrgb],
1203-
_ => &[],
1204-
},
1205-
};
1206-
let a = texture_cache.get(
1207-
&render_device,
1208-
TextureDescriptor {
1209-
label: Some("main_texture_a"),
1210-
..descriptor
1211-
},
1212-
);
1213-
let b = texture_cache.get(
1194+
let key: MainTextureKey = (
1195+
camera.target.clone(),
1196+
texture_usage.0,
1197+
main_texture_format,
1198+
*msaa,
1199+
);
1200+
let (a, b, sampled, main_texture) = textures.entry(key.clone()).or_insert_with(|| {
1201+
let descriptor = TextureDescriptor {
1202+
label: None,
1203+
size: target_size.to_extents(),
1204+
mip_level_count: 1,
1205+
sample_count: 1,
1206+
dimension: TextureDimension::D2,
1207+
format: main_texture_format,
1208+
usage: texture_usage.0,
1209+
view_formats: match main_texture_format {
1210+
TextureFormat::Bgra8Unorm => &[TextureFormat::Bgra8UnormSrgb],
1211+
TextureFormat::Rgba8Unorm => &[TextureFormat::Rgba8UnormSrgb],
1212+
_ => &[],
1213+
},
1214+
};
1215+
let a = texture_cache.get(
1216+
&render_device,
1217+
TextureDescriptor {
1218+
label: Some("main_texture_a"),
1219+
..descriptor
1220+
},
1221+
);
1222+
let b = texture_cache.get(
1223+
&render_device,
1224+
TextureDescriptor {
1225+
label: Some("main_texture_b"),
1226+
..descriptor
1227+
},
1228+
);
1229+
let sampled = if msaa.samples() > 1 {
1230+
let sampled = texture_cache.get(
12141231
&render_device,
12151232
TextureDescriptor {
1216-
label: Some("main_texture_b"),
1217-
..descriptor
1233+
label: Some("main_texture_sampled"),
1234+
size: target_size.to_extents(),
1235+
mip_level_count: 1,
1236+
sample_count: msaa.samples(),
1237+
dimension: TextureDimension::D2,
1238+
format: main_texture_format,
1239+
usage: TextureUsages::RENDER_ATTACHMENT,
1240+
view_formats: descriptor.view_formats,
12181241
},
12191242
);
1220-
let sampled = if msaa.samples() > 1 {
1221-
let sampled = texture_cache.get(
1222-
&render_device,
1223-
TextureDescriptor {
1224-
label: Some("main_texture_sampled"),
1225-
size: target_size.to_extents(),
1226-
mip_level_count: 1,
1227-
sample_count: msaa.samples(),
1228-
dimension: TextureDimension::D2,
1229-
format: main_texture_format,
1230-
usage: TextureUsages::RENDER_ATTACHMENT,
1231-
view_formats: descriptor.view_formats,
1232-
},
1233-
);
1234-
Some(sampled)
1235-
} else {
1236-
None
1237-
};
1238-
let main_texture = Arc::new(AtomicUsize::new(0));
1239-
(a, b, sampled, main_texture)
1240-
});
1243+
Some(sampled)
1244+
} else {
1245+
None
1246+
};
1247+
// re-use the same atomics frame to frame for views with the same main texture
1248+
// to ensure post process writes persist through msaa writeback
1249+
let main_texture = match main_texture_atomics.entry(key) {
1250+
Entry::Occupied(e) => e
1251+
.get()
1252+
.upgrade()
1253+
.expect("dead weaks were pruned at top of system"),
1254+
Entry::Vacant(e) => {
1255+
let arc = Arc::new(AtomicUsize::new(0));
1256+
e.insert(Arc::downgrade(&arc));
1257+
arc
1258+
}
1259+
};
1260+
(a, b, sampled, main_texture)
1261+
});
12411262

12421263
let main_textures = MainTargetTextures {
12431264
a: ColorAttachment::new(a.clone(), sampled.clone(), None, converted_clear_color),

0 commit comments

Comments
 (0)