Skip to content

Commit a053145

Browse files
committed
Add anti-alising of jump-flood outline when using MSAA.
1 parent 2aa98ab commit a053145

11 files changed

Lines changed: 378 additions & 49 deletions

src/flood/compose_output.rs

Lines changed: 36 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
use bevy::shader::ShaderDefVal;
12
use bevy::{
23
core_pipeline::FullscreenShader,
34
platform::collections::HashMap,
@@ -12,7 +13,7 @@ use bevy::{
1213
SamplerDescriptor, ShaderType, StoreOp,
1314
},
1415
renderer::{RenderContext, RenderDevice, RenderQueue},
15-
sync_world::{MainEntity, MainEntityHashMap},
16+
sync_world::MainEntity,
1617
texture::CachedTexture,
1718
view::{ExtractedView, ViewDepthTexture, ViewTarget},
1819
},
@@ -23,38 +24,61 @@ use wgpu_types::{
2324
TextureFormat, TextureSampleType,
2425
};
2526

26-
use crate::{pipeline_key::ViewPipelineKey, uniforms::RenderOutlineInstances};
27+
use crate::{
28+
culling::RenderExtractedOutlineEntities, pipeline_key::ViewPipelineKey,
29+
uniforms::RenderOutlineInstances,
30+
};
2731

2832
use super::{DrawMode, OutlineViewUniform, COMPOSE_OUTPUT_SHADER_HANDLE};
2933

3034
#[derive(Clone, ShaderType)]
3135
pub(crate) struct ComposeOutputUniform {
36+
pub flat_depth: f32,
3237
pub volume_offset: f32,
3338
pub volume_colour: Vec4,
3439
}
3540

3641
#[derive(Resource, Default)]
3742
pub(crate) struct ComposeOutputUniforms {
3843
pub buffer: DynamicUniformBuffer<ComposeOutputUniform>,
39-
pub offsets: MainEntityHashMap<u32>,
44+
pub offsets: HashMap<(Entity, MainEntity), u32>,
4045
}
4146

4247
pub(crate) fn prepare_compose_output_uniform(
4348
render_outlines: Res<RenderOutlineInstances>,
49+
render_extracted: Res<RenderExtractedOutlineEntities>,
50+
views: Query<(Entity, &ExtractedView, &OutlineViewUniform)>,
4451
mut uniforms: ResMut<ComposeOutputUniforms>,
4552
render_device: Res<RenderDevice>,
4653
render_queue: Res<RenderQueue>,
4754
) {
4855
let uniforms = uniforms.as_mut();
4956
uniforms.buffer.clear();
5057
uniforms.offsets.clear();
51-
for (main_entity, outline) in render_outlines.iter() {
52-
if outline.draw_mode == DrawMode::JumpFlood {
58+
for (view_entity, view_extracted, view) in views.iter() {
59+
let Some(render_view_extracted) = render_extracted
60+
.views
61+
.get(&view_extracted.retained_view_entity)
62+
else {
63+
continue;
64+
};
65+
let model_eye = view_extracted.world_from_view.forward();
66+
for (_, main_entity) in render_view_extracted.visible_entities.entities.iter() {
67+
let Some(outline) = render_outlines.get(main_entity) else {
68+
continue;
69+
};
70+
if outline.draw_mode != DrawMode::JumpFlood {
71+
continue;
72+
}
73+
let world_plane = outline.instance_data.world_plane_origin
74+
+ *model_eye * outline.instance_data.world_plane_offset;
75+
let clip = view.clip_from_world * world_plane.extend(1.0);
5376
let offset = uniforms.buffer.push(&ComposeOutputUniform {
77+
flat_depth: clip.z / clip.w,
5478
volume_offset: outline.instance_data.volume_offset,
5579
volume_colour: outline.instance_data.volume_colour,
5680
});
57-
uniforms.offsets.insert(*main_entity, offset);
81+
uniforms.offsets.insert((view_entity, *main_entity), offset);
5882
}
5983
}
6084
uniforms.buffer.write_buffer(&render_device, &render_queue);
@@ -101,13 +125,17 @@ impl ComposeOutputPipeline {
101125
key: ViewPipelineKey,
102126
) -> CachedRenderPipelineId {
103127
*self.pipeline_cache.entry(key).or_insert_with(|| {
128+
let mut shader_defs = vec![];
129+
if key.msaa().samples() > 1 {
130+
shader_defs.push(ShaderDefVal::from("MSAA"));
131+
}
104132
pipeline_cache.queue_render_pipeline(RenderPipelineDescriptor {
105133
label: Some("outline_flood_compose_output_pipeline".into()),
106134
layout: vec![self.layout.clone()],
107135
vertex: fullscreen_shader.to_vertex_state(),
108136
fragment: Some(FragmentState {
109137
shader: COMPOSE_OUTPUT_SHADER_HANDLE,
110-
shader_defs: vec![],
138+
shader_defs,
111139
entry_point: None,
112140
targets: vec![Some(ColorTargetState {
113141
format: key.target_format(),
@@ -214,7 +242,7 @@ impl<'w> ComposeOutputPass<'w> {
214242
let dynamic_index = *self
215243
.compose_output_uniforms
216244
.offsets
217-
.get(&main_entity)
245+
.get(&(view_entity, main_entity))
218246
.unwrap();
219247

220248
let bind_group = render_context.render_device().create_bind_group(

src/flood/compose_output.wgsl

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
#import bevy_mod_outline::common::OutlineViewUniform
33

44
struct ComposeOutputUniform {
5+
flat_depth: f32,
56
volume_offset: f32,
67
volume_colour: vec4<f32>,
78
}
@@ -19,13 +20,24 @@ struct FragmentOutput {
1920
@fragment
2021
fn fragment(in: FullscreenVertexOutput) -> FragmentOutput {
2122
let tex = textureSample(screen_texture, texture_sampler, in.uv);
23+
let threshold = view.scale_physical_from_logical * instance.volume_offset;
24+
let dist = distance(in.position.xy, tex.xy);
2225
var out: FragmentOutput;
23-
if distance(in.position.xy, tex.xy) <= view.scale_physical_from_logical * instance.volume_offset {
24-
out.colour = instance.volume_colour;
25-
out.frag_depth = tex.z;
26+
#ifdef MSAA
27+
let inner = max(threshold - 1.0, 0.0);
28+
let coverage = 1.0 - smoothstep(inner, threshold, dist);
29+
if coverage <= 0.0 {
30+
discard;
2631
}
27-
else {
32+
out.colour = vec4<f32>(instance.volume_colour.rgb, instance.volume_colour.a * coverage);
33+
out.frag_depth = instance.flat_depth;
34+
#else
35+
if dist <= threshold {
36+
out.colour = instance.volume_colour;
37+
out.frag_depth = instance.flat_depth;
38+
} else {
2839
discard;
2940
}
41+
#endif
3042
return out;
3143
}

src/flood/flood_init.rs

Lines changed: 31 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -158,7 +158,7 @@ impl<'w> FloodInitPass<'w> {
158158
}
159159
}
160160

161-
pub fn execute(
161+
pub fn execute_direct(
162162
&mut self,
163163
render_context: &mut RenderContext<'_, '_>,
164164
range: Range<usize>,
@@ -172,13 +172,42 @@ impl<'w> FloodInitPass<'w> {
172172
load: LoadOp::Clear(wgpu_types::Color {
173173
r: -1.0,
174174
g: -1.0,
175-
b: -1.0,
175+
b: 0.0,
176176
a: 0.0,
177177
}),
178178
store: StoreOp::Store,
179179
},
180180
};
181181

182+
self.run(render_context, range, color_attachment);
183+
}
184+
185+
pub fn execute_coverage(
186+
&mut self,
187+
render_context: &mut RenderContext<'_, '_>,
188+
range: Range<usize>,
189+
coverage_msaa: &CachedTexture,
190+
resolve_target: &CachedTexture,
191+
) {
192+
let color_attachment = RenderPassColorAttachment {
193+
view: &coverage_msaa.default_view,
194+
depth_slice: None,
195+
resolve_target: Some(&resolve_target.default_view),
196+
ops: Operations {
197+
load: LoadOp::Clear(wgpu_types::Color::TRANSPARENT),
198+
store: StoreOp::Store,
199+
},
200+
};
201+
202+
self.run(render_context, range, color_attachment);
203+
}
204+
205+
fn run(
206+
&mut self,
207+
render_context: &mut RenderContext<'_, '_>,
208+
range: Range<usize>,
209+
color_attachment: RenderPassColorAttachment<'_>,
210+
) {
182211
let mut init_pass = render_context.begin_tracked_render_pass(RenderPassDescriptor {
183212
label: Some("outline_flood_init"),
184213
color_attachments: &[Some(color_attachment)],

src/flood/jump_flood.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ pub(crate) fn init_jump_flood_pipeline(
6565
shader_defs: vec![],
6666
entry_point: None,
6767
targets: vec![Some(ColorTargetState {
68-
format: TextureFormat::Rgba16Float,
68+
format: TextureFormat::Rg16Float,
6969
blend: None,
7070
write_mask: ColorWrites::ALL,
7171
})],

src/flood/mod.rs

Lines changed: 53 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ use compose_output::{
2525
use flood_init::{prepare_flood_phases, queue_flood_meshes};
2626
use jump_flood::init_jump_flood_pipeline;
2727
use node::{flood_render_pass, FloodOutline};
28+
use sobel_init::init_sobel_init_pipeline;
2829

2930
use crate::add_dummy_phase_buffer;
3031
use crate::node::outline_render_pass;
@@ -37,17 +38,27 @@ mod compose_output;
3738
mod flood_init;
3839
mod jump_flood;
3940
mod node;
41+
mod sobel_init;
4042

4143
const JUMP_FLOOD_SHADER_HANDLE: Handle<Shader> =
4244
uuid_handle!("66f5981f-0cc2-4e62-8221-cd495062f3ac");
4345
const COMPOSE_OUTPUT_SHADER_HANDLE: Handle<Shader> =
4446
uuid_handle!("3c0c1990-4202-48ef-8aa4-bbbb3a334471");
47+
const SOBEL_INIT_SHADER_HANDLE: Handle<Shader> =
48+
uuid_handle!("e011500d-544c-4a0a-85ee-e7de0b1fda3f");
49+
50+
#[derive(Clone)]
51+
pub(crate) struct FloodCoverageTextures {
52+
pub msaa_tex: CachedTexture,
53+
pub resolved: CachedTexture,
54+
}
4555

4656
#[derive(Clone, Component)]
4757
pub(crate) struct FloodTextures {
4858
pub flip: bool,
4959
pub texture_a: CachedTexture,
5060
pub texture_b: CachedTexture,
61+
pub coverage: Option<FloodCoverageTextures>,
5162
}
5263

5364
impl FloodTextures {
@@ -76,9 +87,9 @@ pub fn prepare_flood_textures(
7687
mut commands: Commands,
7788
mut texture_cache: ResMut<TextureCache>,
7889
render_device: Res<RenderDevice>,
79-
cameras: Query<(Entity, &ExtractedCamera)>,
90+
cameras: Query<(Entity, &ExtractedCamera, &Msaa)>,
8091
) {
81-
for (entity, camera) in cameras.iter() {
92+
for (entity, camera, msaa) in cameras.iter() {
8293
let Some(target_size) = camera.physical_target_size else {
8394
continue;
8495
};
@@ -95,15 +106,42 @@ pub fn prepare_flood_textures(
95106
mip_level_count: 1,
96107
sample_count: 1,
97108
dimension: TextureDimension::D2,
98-
format: TextureFormat::Rgba16Float,
109+
format: TextureFormat::Rg16Float,
99110
usage: TextureUsages::TEXTURE_BINDING | TextureUsages::RENDER_ATTACHMENT,
100111
view_formats: &[],
101112
};
102113

114+
let coverage = if msaa.samples() > 1 {
115+
let coverage_descriptor = TextureDescriptor {
116+
format: TextureFormat::R8Unorm,
117+
..texture_descriptor.clone()
118+
};
119+
let msaa_tex = texture_cache.get(
120+
&render_device,
121+
TextureDescriptor {
122+
label: Some("outline_flood_coverage_msaa"),
123+
sample_count: msaa.samples(),
124+
usage: TextureUsages::RENDER_ATTACHMENT,
125+
..coverage_descriptor.clone()
126+
},
127+
);
128+
let resolved = texture_cache.get(
129+
&render_device,
130+
TextureDescriptor {
131+
label: Some("outline_flood_coverage_resolved"),
132+
..coverage_descriptor
133+
},
134+
);
135+
Some(FloodCoverageTextures { msaa_tex, resolved })
136+
} else {
137+
None
138+
};
139+
103140
commands.entity(entity).insert(FloodTextures {
104141
flip: false,
105142
texture_a: texture_cache.get(&render_device, texture_descriptor.clone()),
106143
texture_b: texture_cache.get(&render_device, texture_descriptor),
144+
coverage,
107145
});
108146
}
109147
}
@@ -131,6 +169,13 @@ impl Plugin for FloodPlugin {
131169
"compose_output.wgsl",
132170
Shader::from_wgsl
133171
);
172+
load_internal_asset!(
173+
app,
174+
SOBEL_INIT_SHADER_HANDLE,
175+
"sobel_init.wgsl",
176+
Shader::from_wgsl
177+
);
178+
134179
app.add_plugins(SortedRenderPhasePlugin::<FloodOutline, OutlinePipeline>::new(
135180
RenderDebugFlags::empty(),
136181
))
@@ -165,7 +210,11 @@ impl Plugin for FloodPlugin {
165210
let render_app = app.sub_app_mut(RenderApp);
166211
render_app.add_systems(
167212
RenderStartup,
168-
(init_jump_flood_pipeline, init_compose_output_pipeline),
213+
(
214+
init_sobel_init_pipeline,
215+
init_jump_flood_pipeline,
216+
init_compose_output_pipeline,
217+
),
169218
);
170219

171220
let gpu_preprocessing_support = render_app.world().resource::<GpuPreprocessingSupport>();

0 commit comments

Comments
 (0)