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