Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
bevyengine
GitHub Repository: bevyengine/bevy
Path: blob/main/crates/bevy_anti_alias/src/smaa/mod.rs
9419 views
1
//! Subpixel morphological antialiasing (SMAA).
2
//!
3
//! [SMAA] is a 2011 antialiasing technique that takes an aliased image and
4
//! smooths out the *jaggies*, making edges smoother. It's been used in numerous
5
//! games and has become a staple postprocessing technique. Compared to MSAA,
6
//! SMAA has the advantage of compatibility with deferred rendering and
7
//! reduction of GPU memory bandwidth. Compared to FXAA, SMAA has the advantage
8
//! of improved quality, but the disadvantage of reduced performance. Compared
9
//! to TAA, SMAA has the advantage of stability and lack of *ghosting*
10
//! artifacts, but has the disadvantage of not supporting temporal accumulation,
11
//! which have made SMAA less popular when advanced photorealistic rendering
12
//! features are used in recent years.
13
//!
14
//! To use SMAA, add [`Smaa`] to a [`bevy_camera::Camera`]. In a
15
//! pinch, you can simply use the default settings (via the [`Default`] trait)
16
//! for a high-quality, high-performance appearance. When using SMAA, you will
17
//! likely want set [`bevy_render::view::Msaa`] to [`bevy_render::view::Msaa::Off`]
18
//! for every camera using SMAA.
19
//!
20
//! Those who have used SMAA in other engines should be aware that Bevy doesn't
21
//! yet support the following more advanced features of SMAA:
22
//!
23
//! * The temporal variant.
24
//!
25
//! * Depth- and chroma-based edge detection.
26
//!
27
//! * Predicated thresholding.
28
//!
29
//! * Compatibility with SSAA and MSAA.
30
//!
31
//! [SMAA]: https://www.iryoku.com/smaa/
32
use bevy_app::{App, Plugin};
33
use bevy_asset::{embedded_asset, load_embedded_asset, AssetServer, Handle};
34
#[cfg(not(feature = "smaa_luts"))]
35
use bevy_core_pipeline::tonemapping::lut_placeholder;
36
use bevy_core_pipeline::{
37
schedule::{Core2d, Core2dSystems, Core3d, Core3dSystems},
38
tonemapping::tonemapping,
39
};
40
use bevy_derive::{Deref, DerefMut};
41
use bevy_ecs::{
42
component::Component,
43
entity::Entity,
44
query::With,
45
reflect::ReflectComponent,
46
resource::Resource,
47
schedule::IntoScheduleConfigs as _,
48
system::{Commands, Query, Res, ResMut},
49
};
50
use bevy_image::{BevyDefault, Image, ToExtents};
51
use bevy_math::{vec4, Vec4};
52
use bevy_reflect::{std_traits::ReflectDefault, Reflect};
53
use bevy_render::{
54
camera::ExtractedCamera,
55
diagnostic::RecordDiagnostics,
56
extract_component::{ExtractComponent, ExtractComponentPlugin},
57
render_asset::RenderAssets,
58
render_resource::{
59
binding_types::{sampler, texture_2d, uniform_buffer},
60
AddressMode, BindGroup, BindGroupEntries, BindGroupLayoutDescriptor,
61
BindGroupLayoutEntries, CachedRenderPipelineId, ColorTargetState, ColorWrites,
62
CompareFunction, DepthStencilState, DynamicUniformBuffer, FilterMode, FragmentState,
63
LoadOp, Operations, PipelineCache, RenderPassColorAttachment,
64
RenderPassDepthStencilAttachment, RenderPassDescriptor, RenderPipeline,
65
RenderPipelineDescriptor, SamplerBindingType, SamplerDescriptor, ShaderStages, ShaderType,
66
SpecializedRenderPipeline, SpecializedRenderPipelines, StencilFaceState, StencilOperation,
67
StencilState, StoreOp, TextureDescriptor, TextureDimension, TextureFormat,
68
TextureSampleType, TextureUsages, TextureView, VertexState,
69
},
70
renderer::{RenderContext, RenderDevice, RenderQueue, ViewQuery},
71
texture::{CachedTexture, GpuImage, TextureCache},
72
view::{ExtractedView, ViewTarget},
73
Render, RenderApp, RenderStartup, RenderSystems,
74
};
75
use bevy_shader::{Shader, ShaderDefVal};
76
use bevy_utils::prelude::default;
77
78
/// Adds support for subpixel morphological antialiasing, or SMAA.
79
#[derive(Default)]
80
pub struct SmaaPlugin;
81
82
/// A component for enabling Subpixel Morphological Anti-Aliasing (SMAA)
83
/// for a [`bevy_camera::Camera`].
84
#[derive(Clone, Copy, Default, Component, Reflect, ExtractComponent)]
85
#[reflect(Component, Default, Clone)]
86
#[doc(alias = "SubpixelMorphologicalAntiAliasing")]
87
pub struct Smaa {
88
/// A predefined set of SMAA parameters: i.e. a quality level.
89
///
90
/// Generally, you can leave this at its default level.
91
pub preset: SmaaPreset,
92
}
93
94
/// A preset quality level for SMAA.
95
///
96
/// Higher values are slower but result in a higher-quality image.
97
///
98
/// The default value is *high*.
99
#[derive(Clone, Copy, Reflect, Default, PartialEq, Eq, Hash)]
100
#[reflect(Default, Clone, PartialEq, Hash)]
101
pub enum SmaaPreset {
102
/// Four search steps; no diagonal or corner detection.
103
Low,
104
105
/// Eight search steps; no diagonal or corner detection.
106
Medium,
107
108
/// Sixteen search steps, 8 diagonal search steps, and corner detection.
109
///
110
/// This is the default.
111
#[default]
112
High,
113
114
/// Thirty-two search steps, 8 diagonal search steps, and corner detection.
115
Ultra,
116
}
117
118
#[derive(Resource)]
119
struct SmaaLuts {
120
/// The handle of the area LUT, a KTX2 format texture that SMAA uses internally.
121
area_lut: Handle<Image>,
122
/// The handle of the search LUT, a KTX2 format texture that SMAA uses internally.
123
search_lut: Handle<Image>,
124
}
125
126
/// A render world resource that holds all render pipeline data needed for SMAA.
127
///
128
/// There are three separate passes, so we need three separate pipelines.
129
#[derive(Resource)]
130
pub struct SmaaPipelines {
131
/// Pass 1: Edge detection.
132
edge_detection: SmaaEdgeDetectionPipeline,
133
/// Pass 2: Blending weight calculation.
134
blending_weight_calculation: SmaaBlendingWeightCalculationPipeline,
135
/// Pass 3: Neighborhood blending.
136
neighborhood_blending: SmaaNeighborhoodBlendingPipeline,
137
}
138
139
/// The pipeline data for phase 1 of SMAA: edge detection.
140
struct SmaaEdgeDetectionPipeline {
141
/// The bind group layout common to all passes.
142
postprocess_bind_group_layout: BindGroupLayoutDescriptor,
143
/// The bind group layout for data specific to this pass.
144
edge_detection_bind_group_layout: BindGroupLayoutDescriptor,
145
/// The shader asset handle.
146
shader: Handle<Shader>,
147
}
148
149
/// The pipeline data for phase 2 of SMAA: blending weight calculation.
150
struct SmaaBlendingWeightCalculationPipeline {
151
/// The bind group layout common to all passes.
152
postprocess_bind_group_layout: BindGroupLayoutDescriptor,
153
/// The bind group layout for data specific to this pass.
154
blending_weight_calculation_bind_group_layout: BindGroupLayoutDescriptor,
155
/// The shader asset handle.
156
shader: Handle<Shader>,
157
}
158
159
/// The pipeline data for phase 3 of SMAA: neighborhood blending.
160
struct SmaaNeighborhoodBlendingPipeline {
161
/// The bind group layout common to all passes.
162
postprocess_bind_group_layout: BindGroupLayoutDescriptor,
163
/// The bind group layout for data specific to this pass.
164
neighborhood_blending_bind_group_layout: BindGroupLayoutDescriptor,
165
/// The shader asset handle.
166
shader: Handle<Shader>,
167
}
168
169
/// A unique identifier for a set of SMAA pipelines.
170
#[derive(Clone, PartialEq, Eq, Hash)]
171
pub struct SmaaNeighborhoodBlendingPipelineKey {
172
/// The format of the framebuffer.
173
texture_format: TextureFormat,
174
/// The quality preset.
175
preset: SmaaPreset,
176
}
177
178
/// A render world component that holds the pipeline IDs for the SMAA passes.
179
///
180
/// There are three separate SMAA passes, each with a different shader and bind
181
/// group layout, so we need three pipeline IDs.
182
#[derive(Component)]
183
pub struct ViewSmaaPipelines {
184
/// The pipeline ID for edge detection (phase 1).
185
edge_detection_pipeline_id: CachedRenderPipelineId,
186
/// The pipeline ID for blending weight calculation (phase 2).
187
blending_weight_calculation_pipeline_id: CachedRenderPipelineId,
188
/// The pipeline ID for neighborhood blending (phase 3).
189
neighborhood_blending_pipeline_id: CachedRenderPipelineId,
190
}
191
192
/// Values supplied to the GPU for SMAA.
193
///
194
/// Currently, this just contains the render target metrics and values derived
195
/// from them. These could be computed by the shader itself, but the original
196
/// SMAA HLSL code supplied them in a uniform, so we do the same for
197
/// consistency.
198
#[derive(Clone, Copy, ShaderType)]
199
pub struct SmaaInfoUniform {
200
/// Information about the width and height of the framebuffer.
201
///
202
/// * *x*: The reciprocal pixel width of the framebuffer.
203
///
204
/// * *y*: The reciprocal pixel height of the framebuffer.
205
///
206
/// * *z*: The pixel width of the framebuffer.
207
///
208
/// * *w*: The pixel height of the framebuffer.
209
pub rt_metrics: Vec4,
210
}
211
212
/// A render world component that stores the offset of each [`SmaaInfoUniform`]
213
/// within the [`SmaaInfoUniformBuffer`] for each view.
214
#[derive(Clone, Copy, Deref, DerefMut, Component)]
215
pub struct SmaaInfoUniformOffset(pub u32);
216
217
/// The GPU buffer that holds all [`SmaaInfoUniform`]s for all views.
218
///
219
/// This is a resource stored in the render world.
220
#[derive(Resource, Default, Deref, DerefMut)]
221
pub struct SmaaInfoUniformBuffer(pub DynamicUniformBuffer<SmaaInfoUniform>);
222
223
/// A render world component that holds the intermediate textures necessary to
224
/// perform SMAA.
225
///
226
/// This is stored on each view that has enabled SMAA.
227
#[derive(Component)]
228
pub struct SmaaTextures {
229
/// The two-channel texture that stores the output from the first pass (edge
230
/// detection).
231
///
232
/// The second pass (blending weight calculation) reads this texture to do
233
/// its work.
234
pub edge_detection_color_texture: CachedTexture,
235
236
/// The 8-bit stencil texture that records which pixels the first pass
237
/// touched, so that the second pass doesn't have to examine other pixels.
238
///
239
/// Each texel will contain a 0 if the first pass didn't touch the
240
/// corresponding pixel or a 1 if the first pass did touch that pixel.
241
pub edge_detection_stencil_texture: CachedTexture,
242
243
/// A four-channel RGBA texture that stores the output from the second pass
244
/// (blending weight calculation).
245
///
246
/// The final pass (neighborhood blending) reads this texture to do its
247
/// work.
248
pub blend_texture: CachedTexture,
249
}
250
251
/// A render world component that stores the bind groups necessary to perform
252
/// SMAA.
253
///
254
/// This is stored on each view.
255
#[derive(Component)]
256
pub struct SmaaBindGroups {
257
/// The bind group for the first pass (edge detection).
258
pub edge_detection_bind_group: BindGroup,
259
/// The bind group for the second pass (blending weight calculation).
260
pub blending_weight_calculation_bind_group: BindGroup,
261
/// The bind group for the final pass (neighborhood blending).
262
pub neighborhood_blending_bind_group: BindGroup,
263
}
264
265
/// Stores the specialized render pipelines for SMAA.
266
///
267
/// Because SMAA uses three passes, we need three separate render pipeline
268
/// stores.
269
#[derive(Resource, Default)]
270
pub struct SmaaSpecializedRenderPipelines {
271
/// Specialized render pipelines for the first phase (edge detection).
272
edge_detection: SpecializedRenderPipelines<SmaaEdgeDetectionPipeline>,
273
274
/// Specialized render pipelines for the second phase (blending weight
275
/// calculation).
276
blending_weight_calculation: SpecializedRenderPipelines<SmaaBlendingWeightCalculationPipeline>,
277
278
/// Specialized render pipelines for the third phase (neighborhood
279
/// blending).
280
neighborhood_blending: SpecializedRenderPipelines<SmaaNeighborhoodBlendingPipeline>,
281
}
282
283
impl Plugin for SmaaPlugin {
284
fn build(&self, app: &mut App) {
285
// Load the shader.
286
embedded_asset!(app, "smaa.wgsl");
287
288
#[cfg(feature = "smaa_luts")]
289
let smaa_luts = {
290
use bevy_asset::RenderAssetUsages;
291
use bevy_image::ImageLoaderSettings;
292
293
// Load the two lookup textures. These are compressed textures in KTX2 format.
294
embedded_asset!(app, "SMAAAreaLUT.ktx2");
295
embedded_asset!(app, "SMAASearchLUT.ktx2");
296
297
SmaaLuts {
298
area_lut: load_embedded_asset!(
299
app,
300
"SMAAAreaLUT.ktx2",
301
|settings: &mut ImageLoaderSettings| {
302
settings.is_srgb = false;
303
settings.asset_usage = RenderAssetUsages::RENDER_WORLD;
304
}
305
),
306
search_lut: load_embedded_asset!(
307
app,
308
"SMAASearchLUT.ktx2",
309
|settings: &mut ImageLoaderSettings| {
310
settings.is_srgb = false;
311
settings.asset_usage = RenderAssetUsages::RENDER_WORLD;
312
}
313
),
314
}
315
};
316
#[cfg(not(feature = "smaa_luts"))]
317
let smaa_luts = {
318
let mut images = app.world_mut().resource_mut::<bevy_asset::Assets<Image>>();
319
let handle = images.add(lut_placeholder());
320
SmaaLuts {
321
area_lut: handle.clone(),
322
search_lut: handle.clone(),
323
}
324
};
325
326
app.add_plugins(ExtractComponentPlugin::<Smaa>::default());
327
328
let Some(render_app) = app.get_sub_app_mut(RenderApp) else {
329
return;
330
};
331
332
render_app
333
.insert_resource(smaa_luts)
334
.init_resource::<SmaaSpecializedRenderPipelines>()
335
.init_resource::<SmaaInfoUniformBuffer>()
336
.add_systems(RenderStartup, init_smaa_pipelines)
337
.add_systems(
338
Render,
339
(
340
prepare_smaa_pipelines.in_set(RenderSystems::Prepare),
341
prepare_smaa_uniforms.in_set(RenderSystems::PrepareResources),
342
prepare_smaa_textures.in_set(RenderSystems::PrepareResources),
343
prepare_smaa_bind_groups.in_set(RenderSystems::PrepareBindGroups),
344
),
345
)
346
.add_systems(
347
Core3d,
348
smaa.after(tonemapping).in_set(Core3dSystems::PostProcess),
349
)
350
.add_systems(
351
Core2d,
352
smaa.after(tonemapping).in_set(Core2dSystems::PostProcess),
353
);
354
}
355
}
356
357
pub fn init_smaa_pipelines(mut commands: Commands, asset_server: Res<AssetServer>) {
358
// Create the postprocess bind group layout (all passes, bind group 0).
359
let postprocess_bind_group_layout = BindGroupLayoutDescriptor::new(
360
"SMAA postprocess bind group layout",
361
&BindGroupLayoutEntries::sequential(
362
ShaderStages::FRAGMENT,
363
(
364
texture_2d(TextureSampleType::Float { filterable: true }),
365
uniform_buffer::<SmaaInfoUniform>(true).visibility(ShaderStages::VERTEX_FRAGMENT),
366
),
367
),
368
);
369
370
// Create the edge detection bind group layout (pass 1, bind group 1).
371
let edge_detection_bind_group_layout = BindGroupLayoutDescriptor::new(
372
"SMAA edge detection bind group layout",
373
&BindGroupLayoutEntries::sequential(
374
ShaderStages::FRAGMENT,
375
(sampler(SamplerBindingType::Filtering),),
376
),
377
);
378
379
// Create the blending weight calculation bind group layout (pass 2, bind group 1).
380
let blending_weight_calculation_bind_group_layout = BindGroupLayoutDescriptor::new(
381
"SMAA blending weight calculation bind group layout",
382
&BindGroupLayoutEntries::sequential(
383
ShaderStages::FRAGMENT,
384
(
385
texture_2d(TextureSampleType::Float { filterable: true }), // edges texture
386
sampler(SamplerBindingType::Filtering), // edges sampler
387
texture_2d(TextureSampleType::Float { filterable: true }), // search texture
388
texture_2d(TextureSampleType::Float { filterable: true }), // area texture
389
),
390
),
391
);
392
393
// Create the neighborhood blending bind group layout (pass 3, bind group 1).
394
let neighborhood_blending_bind_group_layout = BindGroupLayoutDescriptor::new(
395
"SMAA neighborhood blending bind group layout",
396
&BindGroupLayoutEntries::sequential(
397
ShaderStages::FRAGMENT,
398
(
399
texture_2d(TextureSampleType::Float { filterable: true }),
400
sampler(SamplerBindingType::Filtering),
401
),
402
),
403
);
404
405
let shader = load_embedded_asset!(asset_server.as_ref(), "smaa.wgsl");
406
407
commands.insert_resource(SmaaPipelines {
408
edge_detection: SmaaEdgeDetectionPipeline {
409
postprocess_bind_group_layout: postprocess_bind_group_layout.clone(),
410
edge_detection_bind_group_layout,
411
shader: shader.clone(),
412
},
413
blending_weight_calculation: SmaaBlendingWeightCalculationPipeline {
414
postprocess_bind_group_layout: postprocess_bind_group_layout.clone(),
415
blending_weight_calculation_bind_group_layout,
416
shader: shader.clone(),
417
},
418
neighborhood_blending: SmaaNeighborhoodBlendingPipeline {
419
postprocess_bind_group_layout,
420
neighborhood_blending_bind_group_layout,
421
shader,
422
},
423
});
424
}
425
426
// Phase 1: edge detection.
427
impl SpecializedRenderPipeline for SmaaEdgeDetectionPipeline {
428
type Key = SmaaPreset;
429
430
fn specialize(&self, preset: Self::Key) -> RenderPipelineDescriptor {
431
let shader_defs = vec!["SMAA_EDGE_DETECTION".into(), preset.shader_def()];
432
433
// We mark the pixels that we touched with a 1 so that the blending
434
// weight calculation (phase 2) will only consider those. This reduces
435
// the overhead of phase 2 considerably.
436
let stencil_face_state = StencilFaceState {
437
compare: CompareFunction::Always,
438
fail_op: StencilOperation::Replace,
439
depth_fail_op: StencilOperation::Replace,
440
pass_op: StencilOperation::Replace,
441
};
442
443
RenderPipelineDescriptor {
444
label: Some("SMAA edge detection".into()),
445
layout: vec![
446
self.postprocess_bind_group_layout.clone(),
447
self.edge_detection_bind_group_layout.clone(),
448
],
449
vertex: VertexState {
450
shader: self.shader.clone(),
451
shader_defs: shader_defs.clone(),
452
entry_point: Some("edge_detection_vertex_main".into()),
453
buffers: vec![],
454
},
455
fragment: Some(FragmentState {
456
shader: self.shader.clone(),
457
shader_defs,
458
entry_point: Some("luma_edge_detection_fragment_main".into()),
459
targets: vec![Some(ColorTargetState {
460
format: TextureFormat::Rg8Unorm,
461
blend: None,
462
write_mask: ColorWrites::ALL,
463
})],
464
}),
465
depth_stencil: Some(DepthStencilState {
466
format: TextureFormat::Stencil8,
467
depth_write_enabled: false,
468
depth_compare: CompareFunction::Always,
469
stencil: StencilState {
470
front: stencil_face_state,
471
back: stencil_face_state,
472
read_mask: 1,
473
write_mask: 1,
474
},
475
bias: default(),
476
}),
477
..default()
478
}
479
}
480
}
481
482
// Phase 2: blending weight calculation.
483
impl SpecializedRenderPipeline for SmaaBlendingWeightCalculationPipeline {
484
type Key = SmaaPreset;
485
486
fn specialize(&self, preset: Self::Key) -> RenderPipelineDescriptor {
487
let shader_defs = vec![
488
"SMAA_BLENDING_WEIGHT_CALCULATION".into(),
489
preset.shader_def(),
490
];
491
492
// Only consider the pixels that were touched in phase 1.
493
let stencil_face_state = StencilFaceState {
494
compare: CompareFunction::Equal,
495
fail_op: StencilOperation::Keep,
496
depth_fail_op: StencilOperation::Keep,
497
pass_op: StencilOperation::Keep,
498
};
499
500
RenderPipelineDescriptor {
501
label: Some("SMAA blending weight calculation".into()),
502
layout: vec![
503
self.postprocess_bind_group_layout.clone(),
504
self.blending_weight_calculation_bind_group_layout.clone(),
505
],
506
vertex: VertexState {
507
shader: self.shader.clone(),
508
shader_defs: shader_defs.clone(),
509
entry_point: Some("blending_weight_calculation_vertex_main".into()),
510
buffers: vec![],
511
},
512
fragment: Some(FragmentState {
513
shader: self.shader.clone(),
514
shader_defs,
515
entry_point: Some("blending_weight_calculation_fragment_main".into()),
516
targets: vec![Some(ColorTargetState {
517
format: TextureFormat::Rgba8Unorm,
518
blend: None,
519
write_mask: ColorWrites::ALL,
520
})],
521
}),
522
depth_stencil: Some(DepthStencilState {
523
format: TextureFormat::Stencil8,
524
depth_write_enabled: false,
525
depth_compare: CompareFunction::Always,
526
stencil: StencilState {
527
front: stencil_face_state,
528
back: stencil_face_state,
529
read_mask: 1,
530
write_mask: 1,
531
},
532
bias: default(),
533
}),
534
..default()
535
}
536
}
537
}
538
539
impl SpecializedRenderPipeline for SmaaNeighborhoodBlendingPipeline {
540
type Key = SmaaNeighborhoodBlendingPipelineKey;
541
542
fn specialize(&self, key: Self::Key) -> RenderPipelineDescriptor {
543
let shader_defs = vec!["SMAA_NEIGHBORHOOD_BLENDING".into(), key.preset.shader_def()];
544
545
RenderPipelineDescriptor {
546
label: Some("SMAA neighborhood blending".into()),
547
layout: vec![
548
self.postprocess_bind_group_layout.clone(),
549
self.neighborhood_blending_bind_group_layout.clone(),
550
],
551
vertex: VertexState {
552
shader: self.shader.clone(),
553
shader_defs: shader_defs.clone(),
554
entry_point: Some("neighborhood_blending_vertex_main".into()),
555
buffers: vec![],
556
},
557
fragment: Some(FragmentState {
558
shader: self.shader.clone(),
559
shader_defs,
560
entry_point: Some("neighborhood_blending_fragment_main".into()),
561
targets: vec![Some(ColorTargetState {
562
format: key.texture_format,
563
blend: None,
564
write_mask: ColorWrites::ALL,
565
})],
566
}),
567
..default()
568
}
569
}
570
}
571
572
/// A system, part of the render app, that specializes the three pipelines
573
/// needed for SMAA according to each view's SMAA settings.
574
fn prepare_smaa_pipelines(
575
mut commands: Commands,
576
pipeline_cache: Res<PipelineCache>,
577
mut specialized_render_pipelines: ResMut<SmaaSpecializedRenderPipelines>,
578
smaa_pipelines: Res<SmaaPipelines>,
579
view_targets: Query<(Entity, &ExtractedView, &Smaa)>,
580
) {
581
for (entity, view, smaa) in &view_targets {
582
let edge_detection_pipeline_id = specialized_render_pipelines.edge_detection.specialize(
583
&pipeline_cache,
584
&smaa_pipelines.edge_detection,
585
smaa.preset,
586
);
587
588
let blending_weight_calculation_pipeline_id = specialized_render_pipelines
589
.blending_weight_calculation
590
.specialize(
591
&pipeline_cache,
592
&smaa_pipelines.blending_weight_calculation,
593
smaa.preset,
594
);
595
596
let neighborhood_blending_pipeline_id = specialized_render_pipelines
597
.neighborhood_blending
598
.specialize(
599
&pipeline_cache,
600
&smaa_pipelines.neighborhood_blending,
601
SmaaNeighborhoodBlendingPipelineKey {
602
texture_format: if view.hdr {
603
ViewTarget::TEXTURE_FORMAT_HDR
604
} else {
605
TextureFormat::bevy_default()
606
},
607
preset: smaa.preset,
608
},
609
);
610
611
commands.entity(entity).insert(ViewSmaaPipelines {
612
edge_detection_pipeline_id,
613
blending_weight_calculation_pipeline_id,
614
neighborhood_blending_pipeline_id,
615
});
616
}
617
}
618
619
/// A system, part of the render app, that builds the [`SmaaInfoUniform`] data
620
/// for each view with SMAA enabled and writes the resulting data to GPU memory.
621
fn prepare_smaa_uniforms(
622
mut commands: Commands,
623
render_device: Res<RenderDevice>,
624
render_queue: Res<RenderQueue>,
625
view_targets: Query<(Entity, &ExtractedView), With<Smaa>>,
626
mut smaa_info_buffer: ResMut<SmaaInfoUniformBuffer>,
627
) {
628
smaa_info_buffer.clear();
629
for (entity, view) in &view_targets {
630
let offset = smaa_info_buffer.push(&SmaaInfoUniform {
631
rt_metrics: vec4(
632
1.0 / view.viewport.z as f32,
633
1.0 / view.viewport.w as f32,
634
view.viewport.z as f32,
635
view.viewport.w as f32,
636
),
637
});
638
commands
639
.entity(entity)
640
.insert(SmaaInfoUniformOffset(offset));
641
}
642
643
smaa_info_buffer.write_buffer(&render_device, &render_queue);
644
}
645
646
/// A system, part of the render app, that builds the intermediate textures for
647
/// each view with SMAA enabled.
648
///
649
/// Phase 1 (edge detection) needs a two-channel RG texture and an 8-bit stencil
650
/// texture; phase 2 (blend weight calculation) needs a four-channel RGBA
651
/// texture.
652
fn prepare_smaa_textures(
653
mut commands: Commands,
654
render_device: Res<RenderDevice>,
655
mut texture_cache: ResMut<TextureCache>,
656
view_targets: Query<(Entity, &ExtractedCamera), (With<ExtractedView>, With<Smaa>)>,
657
) {
658
for (entity, camera) in &view_targets {
659
let Some(texture_size) = camera.physical_target_size else {
660
continue;
661
};
662
663
// Create the two-channel RG texture for phase 1 (edge detection).
664
let edge_detection_color_texture = texture_cache.get(
665
&render_device,
666
TextureDescriptor {
667
label: Some("SMAA edge detection color texture"),
668
size: texture_size.to_extents(),
669
mip_level_count: 1,
670
sample_count: 1,
671
dimension: TextureDimension::D2,
672
format: TextureFormat::Rg8Unorm,
673
usage: TextureUsages::TEXTURE_BINDING | TextureUsages::RENDER_ATTACHMENT,
674
view_formats: &[],
675
},
676
);
677
678
// Create the stencil texture for phase 1 (edge detection).
679
let edge_detection_stencil_texture = texture_cache.get(
680
&render_device,
681
TextureDescriptor {
682
label: Some("SMAA edge detection stencil texture"),
683
size: texture_size.to_extents(),
684
mip_level_count: 1,
685
sample_count: 1,
686
dimension: TextureDimension::D2,
687
format: TextureFormat::Stencil8,
688
usage: TextureUsages::RENDER_ATTACHMENT,
689
view_formats: &[],
690
},
691
);
692
693
// Create the four-channel RGBA texture for phase 2 (blending weight
694
// calculation).
695
let blend_texture = texture_cache.get(
696
&render_device,
697
TextureDescriptor {
698
label: Some("SMAA blend texture"),
699
size: texture_size.to_extents(),
700
mip_level_count: 1,
701
sample_count: 1,
702
dimension: TextureDimension::D2,
703
format: TextureFormat::Rgba8Unorm,
704
usage: TextureUsages::TEXTURE_BINDING | TextureUsages::RENDER_ATTACHMENT,
705
view_formats: &[],
706
},
707
);
708
709
commands.entity(entity).insert(SmaaTextures {
710
edge_detection_color_texture,
711
edge_detection_stencil_texture,
712
blend_texture,
713
});
714
}
715
}
716
717
/// A system, part of the render app, that builds the SMAA bind groups for each
718
/// view with SMAA enabled.
719
fn prepare_smaa_bind_groups(
720
mut commands: Commands,
721
render_device: Res<RenderDevice>,
722
smaa_pipelines: Res<SmaaPipelines>,
723
smaa_luts: Res<SmaaLuts>,
724
images: Res<RenderAssets<GpuImage>>,
725
pipeline_cache: Res<PipelineCache>,
726
view_targets: Query<(Entity, &SmaaTextures), (With<ExtractedView>, With<Smaa>)>,
727
) {
728
// Fetch the two lookup textures. These are bundled in this library.
729
let (Some(search_texture), Some(area_texture)) = (
730
images.get(&smaa_luts.search_lut),
731
images.get(&smaa_luts.area_lut),
732
) else {
733
return;
734
};
735
736
for (entity, smaa_textures) in &view_targets {
737
// We use the same sampler settings for all textures, so we can build
738
// only one and reuse it.
739
let sampler = render_device.create_sampler(&SamplerDescriptor {
740
label: Some("SMAA sampler"),
741
address_mode_u: AddressMode::ClampToEdge,
742
address_mode_v: AddressMode::ClampToEdge,
743
address_mode_w: AddressMode::ClampToEdge,
744
mag_filter: FilterMode::Linear,
745
min_filter: FilterMode::Linear,
746
..default()
747
});
748
749
commands.entity(entity).insert(SmaaBindGroups {
750
edge_detection_bind_group: render_device.create_bind_group(
751
Some("SMAA edge detection bind group"),
752
&pipeline_cache.get_bind_group_layout(
753
&smaa_pipelines
754
.edge_detection
755
.edge_detection_bind_group_layout,
756
),
757
&BindGroupEntries::sequential((&sampler,)),
758
),
759
blending_weight_calculation_bind_group: render_device.create_bind_group(
760
Some("SMAA blending weight calculation bind group"),
761
&pipeline_cache.get_bind_group_layout(
762
&smaa_pipelines
763
.blending_weight_calculation
764
.blending_weight_calculation_bind_group_layout,
765
),
766
&BindGroupEntries::sequential((
767
&smaa_textures.edge_detection_color_texture.default_view,
768
&sampler,
769
&search_texture.texture_view,
770
&area_texture.texture_view,
771
)),
772
),
773
neighborhood_blending_bind_group: render_device.create_bind_group(
774
Some("SMAA neighborhood blending bind group"),
775
&pipeline_cache.get_bind_group_layout(
776
&smaa_pipelines
777
.neighborhood_blending
778
.neighborhood_blending_bind_group_layout,
779
),
780
&BindGroupEntries::sequential((
781
&smaa_textures.blend_texture.default_view,
782
&sampler,
783
)),
784
),
785
});
786
}
787
}
788
789
impl SmaaPreset {
790
/// Returns the `#define` in the shader corresponding to this quality
791
/// preset.
792
fn shader_def(&self) -> ShaderDefVal {
793
match *self {
794
SmaaPreset::Low => "SMAA_PRESET_LOW".into(),
795
SmaaPreset::Medium => "SMAA_PRESET_MEDIUM".into(),
796
SmaaPreset::High => "SMAA_PRESET_HIGH".into(),
797
SmaaPreset::Ultra => "SMAA_PRESET_ULTRA".into(),
798
}
799
}
800
}
801
802
pub(crate) fn smaa(
803
view: ViewQuery<(
804
&ViewTarget,
805
&ViewSmaaPipelines,
806
&SmaaInfoUniformOffset,
807
&SmaaTextures,
808
&SmaaBindGroups,
809
)>,
810
smaa_pipelines: Res<SmaaPipelines>,
811
smaa_info_uniform_buffer: Res<SmaaInfoUniformBuffer>,
812
pipeline_cache: Res<PipelineCache>,
813
mut ctx: RenderContext,
814
) {
815
let (
816
view_target,
817
view_pipelines,
818
view_smaa_uniform_offset,
819
smaa_textures,
820
view_smaa_bind_groups,
821
) = view.into_inner();
822
823
let (
824
Some(edge_detection_pipeline),
825
Some(blending_weight_calculation_pipeline),
826
Some(neighborhood_blending_pipeline),
827
) = (
828
pipeline_cache.get_render_pipeline(view_pipelines.edge_detection_pipeline_id),
829
pipeline_cache.get_render_pipeline(view_pipelines.blending_weight_calculation_pipeline_id),
830
pipeline_cache.get_render_pipeline(view_pipelines.neighborhood_blending_pipeline_id),
831
)
832
else {
833
return;
834
};
835
836
let diagnostics = ctx.diagnostic_recorder();
837
let diagnostics = diagnostics.as_deref();
838
let time_span = diagnostics.time_span(ctx.command_encoder(), "smaa");
839
840
ctx.command_encoder().push_debug_group("smaa");
841
842
let postprocess = view_target.post_process_write();
843
let (source, destination) = (postprocess.source, postprocess.destination);
844
845
perform_edge_detection(
846
&mut ctx,
847
&pipeline_cache,
848
&smaa_pipelines,
849
smaa_textures,
850
view_smaa_bind_groups,
851
&smaa_info_uniform_buffer,
852
view_smaa_uniform_offset,
853
edge_detection_pipeline,
854
source,
855
);
856
857
perform_blending_weight_calculation(
858
&mut ctx,
859
&pipeline_cache,
860
&smaa_pipelines,
861
smaa_textures,
862
view_smaa_bind_groups,
863
&smaa_info_uniform_buffer,
864
view_smaa_uniform_offset,
865
blending_weight_calculation_pipeline,
866
source,
867
);
868
869
perform_neighborhood_blending(
870
&mut ctx,
871
&pipeline_cache,
872
&smaa_pipelines,
873
view_smaa_bind_groups,
874
&smaa_info_uniform_buffer,
875
view_smaa_uniform_offset,
876
neighborhood_blending_pipeline,
877
source,
878
destination,
879
);
880
881
ctx.command_encoder().pop_debug_group();
882
time_span.end(ctx.command_encoder());
883
}
884
885
/// Performs edge detection (phase 1).
886
fn perform_edge_detection(
887
ctx: &mut RenderContext,
888
pipeline_cache: &PipelineCache,
889
smaa_pipelines: &SmaaPipelines,
890
smaa_textures: &SmaaTextures,
891
view_smaa_bind_groups: &SmaaBindGroups,
892
smaa_info_uniform_buffer: &SmaaInfoUniformBuffer,
893
view_smaa_uniform_offset: &SmaaInfoUniformOffset,
894
edge_detection_pipeline: &RenderPipeline,
895
source: &TextureView,
896
) {
897
let postprocess_bind_group = ctx.render_device().create_bind_group(
898
None,
899
&pipeline_cache
900
.get_bind_group_layout(&smaa_pipelines.edge_detection.postprocess_bind_group_layout),
901
&BindGroupEntries::sequential((source, &**smaa_info_uniform_buffer)),
902
);
903
904
let pass_descriptor = RenderPassDescriptor {
905
label: Some("SMAA edge detection pass"),
906
color_attachments: &[Some(RenderPassColorAttachment {
907
view: &smaa_textures.edge_detection_color_texture.default_view,
908
depth_slice: None,
909
resolve_target: None,
910
ops: default(),
911
})],
912
depth_stencil_attachment: Some(RenderPassDepthStencilAttachment {
913
view: &smaa_textures.edge_detection_stencil_texture.default_view,
914
depth_ops: None,
915
stencil_ops: Some(Operations {
916
load: LoadOp::Clear(0),
917
store: StoreOp::Store,
918
}),
919
}),
920
timestamp_writes: None,
921
occlusion_query_set: None,
922
multiview_mask: None,
923
};
924
925
let mut render_pass = ctx.command_encoder().begin_render_pass(&pass_descriptor);
926
render_pass.set_pipeline(edge_detection_pipeline);
927
render_pass.set_bind_group(0, &postprocess_bind_group, &[**view_smaa_uniform_offset]);
928
render_pass.set_bind_group(1, &view_smaa_bind_groups.edge_detection_bind_group, &[]);
929
render_pass.set_stencil_reference(1);
930
render_pass.draw(0..3, 0..1);
931
}
932
933
/// Performs blending weight calculation (phase 2).
934
fn perform_blending_weight_calculation(
935
ctx: &mut RenderContext,
936
pipeline_cache: &PipelineCache,
937
smaa_pipelines: &SmaaPipelines,
938
smaa_textures: &SmaaTextures,
939
view_smaa_bind_groups: &SmaaBindGroups,
940
smaa_info_uniform_buffer: &SmaaInfoUniformBuffer,
941
view_smaa_uniform_offset: &SmaaInfoUniformOffset,
942
blending_weight_calculation_pipeline: &RenderPipeline,
943
source: &TextureView,
944
) {
945
let postprocess_bind_group = ctx.render_device().create_bind_group(
946
None,
947
&pipeline_cache.get_bind_group_layout(
948
&smaa_pipelines
949
.blending_weight_calculation
950
.postprocess_bind_group_layout,
951
),
952
&BindGroupEntries::sequential((source, &**smaa_info_uniform_buffer)),
953
);
954
955
let pass_descriptor = RenderPassDescriptor {
956
label: Some("SMAA blending weight calculation pass"),
957
color_attachments: &[Some(RenderPassColorAttachment {
958
view: &smaa_textures.blend_texture.default_view,
959
depth_slice: None,
960
resolve_target: None,
961
ops: default(),
962
})],
963
depth_stencil_attachment: Some(RenderPassDepthStencilAttachment {
964
view: &smaa_textures.edge_detection_stencil_texture.default_view,
965
depth_ops: None,
966
stencil_ops: Some(Operations {
967
load: LoadOp::Load,
968
store: StoreOp::Discard,
969
}),
970
}),
971
timestamp_writes: None,
972
occlusion_query_set: None,
973
multiview_mask: None,
974
};
975
976
let mut render_pass = ctx.command_encoder().begin_render_pass(&pass_descriptor);
977
render_pass.set_pipeline(blending_weight_calculation_pipeline);
978
render_pass.set_bind_group(0, &postprocess_bind_group, &[**view_smaa_uniform_offset]);
979
render_pass.set_bind_group(
980
1,
981
&view_smaa_bind_groups.blending_weight_calculation_bind_group,
982
&[],
983
);
984
render_pass.set_stencil_reference(1);
985
render_pass.draw(0..3, 0..1);
986
}
987
988
/// Performs blending weight calculation (phase 3).
989
fn perform_neighborhood_blending(
990
ctx: &mut RenderContext,
991
pipeline_cache: &PipelineCache,
992
smaa_pipelines: &SmaaPipelines,
993
view_smaa_bind_groups: &SmaaBindGroups,
994
smaa_info_uniform_buffer: &SmaaInfoUniformBuffer,
995
view_smaa_uniform_offset: &SmaaInfoUniformOffset,
996
neighborhood_blending_pipeline: &RenderPipeline,
997
source: &TextureView,
998
destination: &TextureView,
999
) {
1000
let postprocess_bind_group = ctx.render_device().create_bind_group(
1001
None,
1002
&pipeline_cache.get_bind_group_layout(
1003
&smaa_pipelines
1004
.neighborhood_blending
1005
.postprocess_bind_group_layout,
1006
),
1007
&BindGroupEntries::sequential((source, &**smaa_info_uniform_buffer)),
1008
);
1009
1010
let pass_descriptor = RenderPassDescriptor {
1011
label: Some("SMAA neighborhood blending pass"),
1012
color_attachments: &[Some(RenderPassColorAttachment {
1013
view: destination,
1014
depth_slice: None,
1015
resolve_target: None,
1016
ops: default(),
1017
})],
1018
depth_stencil_attachment: None,
1019
timestamp_writes: None,
1020
occlusion_query_set: None,
1021
multiview_mask: None,
1022
};
1023
1024
let mut render_pass = ctx.command_encoder().begin_render_pass(&pass_descriptor);
1025
render_pass.set_pipeline(neighborhood_blending_pipeline);
1026
render_pass.set_bind_group(0, &postprocess_bind_group, &[**view_smaa_uniform_offset]);
1027
render_pass.set_bind_group(
1028
1,
1029
&view_smaa_bind_groups.neighborhood_blending_bind_group,
1030
&[],
1031
);
1032
render_pass.draw(0..3, 0..1);
1033
}
1034
1035