bevy_core_pipeline/fxaa/
mod.rs

1use crate::{
2    core_2d::graph::{Core2d, Node2d},
3    core_3d::graph::{Core3d, Node3d},
4    fullscreen_vertex_shader::fullscreen_shader_vertex_state,
5};
6use bevy_app::prelude::*;
7use bevy_asset::{load_internal_asset, Handle};
8use bevy_ecs::prelude::*;
9use bevy_image::BevyDefault as _;
10use bevy_reflect::{std_traits::ReflectDefault, Reflect};
11use bevy_render::{
12    extract_component::{ExtractComponent, ExtractComponentPlugin},
13    prelude::Camera,
14    render_graph::{RenderGraphApp, ViewNodeRunner},
15    render_resource::{
16        binding_types::{sampler, texture_2d},
17        *,
18    },
19    renderer::RenderDevice,
20    view::{ExtractedView, ViewTarget},
21    Render, RenderApp, RenderSet,
22};
23use bevy_utils::default;
24
25mod node;
26
27pub use node::FxaaNode;
28
29#[derive(Debug, Reflect, Eq, PartialEq, Hash, Clone, Copy)]
30#[reflect(PartialEq, Hash)]
31pub enum Sensitivity {
32    Low,
33    Medium,
34    High,
35    Ultra,
36    Extreme,
37}
38
39impl Sensitivity {
40    pub fn get_str(&self) -> &str {
41        match self {
42            Sensitivity::Low => "LOW",
43            Sensitivity::Medium => "MEDIUM",
44            Sensitivity::High => "HIGH",
45            Sensitivity::Ultra => "ULTRA",
46            Sensitivity::Extreme => "EXTREME",
47        }
48    }
49}
50
51/// A component for enabling Fast Approximate Anti-Aliasing (FXAA)
52/// for a [`bevy_render::camera::Camera`].
53#[derive(Reflect, Component, Clone, ExtractComponent)]
54#[reflect(Component, Default)]
55#[extract_component_filter(With<Camera>)]
56#[doc(alias = "FastApproximateAntiAliasing")]
57pub struct Fxaa {
58    /// Enable render passes for FXAA.
59    pub enabled: bool,
60
61    /// Use lower sensitivity for a sharper, faster, result.
62    /// Use higher sensitivity for a slower, smoother, result.
63    /// [`Ultra`](`Sensitivity::Ultra`) and [`Extreme`](`Sensitivity::Extreme`)
64    /// settings can result in significant smearing and loss of detail.
65    ///
66    /// The minimum amount of local contrast required to apply algorithm.
67    pub edge_threshold: Sensitivity,
68
69    /// Trims the algorithm from processing darks.
70    pub edge_threshold_min: Sensitivity,
71}
72
73impl Default for Fxaa {
74    fn default() -> Self {
75        Fxaa {
76            enabled: true,
77            edge_threshold: Sensitivity::High,
78            edge_threshold_min: Sensitivity::High,
79        }
80    }
81}
82
83const FXAA_SHADER_HANDLE: Handle<Shader> = Handle::weak_from_u128(4182761465141723543);
84
85/// Adds support for Fast Approximate Anti-Aliasing (FXAA)
86pub struct FxaaPlugin;
87impl Plugin for FxaaPlugin {
88    fn build(&self, app: &mut App) {
89        load_internal_asset!(app, FXAA_SHADER_HANDLE, "fxaa.wgsl", Shader::from_wgsl);
90
91        app.register_type::<Fxaa>();
92        app.add_plugins(ExtractComponentPlugin::<Fxaa>::default());
93
94        let Some(render_app) = app.get_sub_app_mut(RenderApp) else {
95            return;
96        };
97        render_app
98            .init_resource::<SpecializedRenderPipelines<FxaaPipeline>>()
99            .add_systems(Render, prepare_fxaa_pipelines.in_set(RenderSet::Prepare))
100            .add_render_graph_node::<ViewNodeRunner<FxaaNode>>(Core3d, Node3d::Fxaa)
101            .add_render_graph_edges(
102                Core3d,
103                (
104                    Node3d::Tonemapping,
105                    Node3d::Fxaa,
106                    Node3d::EndMainPassPostProcessing,
107                ),
108            )
109            .add_render_graph_node::<ViewNodeRunner<FxaaNode>>(Core2d, Node2d::Fxaa)
110            .add_render_graph_edges(
111                Core2d,
112                (
113                    Node2d::Tonemapping,
114                    Node2d::Fxaa,
115                    Node2d::EndMainPassPostProcessing,
116                ),
117            );
118    }
119
120    fn finish(&self, app: &mut App) {
121        let Some(render_app) = app.get_sub_app_mut(RenderApp) else {
122            return;
123        };
124        render_app.init_resource::<FxaaPipeline>();
125    }
126}
127
128#[derive(Resource)]
129pub struct FxaaPipeline {
130    texture_bind_group: BindGroupLayout,
131    sampler: Sampler,
132}
133
134impl FromWorld for FxaaPipeline {
135    fn from_world(render_world: &mut World) -> Self {
136        let render_device = render_world.resource::<RenderDevice>();
137        let texture_bind_group = render_device.create_bind_group_layout(
138            "fxaa_texture_bind_group_layout",
139            &BindGroupLayoutEntries::sequential(
140                ShaderStages::FRAGMENT,
141                (
142                    texture_2d(TextureSampleType::Float { filterable: true }),
143                    sampler(SamplerBindingType::Filtering),
144                ),
145            ),
146        );
147
148        let sampler = render_device.create_sampler(&SamplerDescriptor {
149            mipmap_filter: FilterMode::Linear,
150            mag_filter: FilterMode::Linear,
151            min_filter: FilterMode::Linear,
152            ..default()
153        });
154
155        FxaaPipeline {
156            texture_bind_group,
157            sampler,
158        }
159    }
160}
161
162#[derive(Component)]
163pub struct CameraFxaaPipeline {
164    pub pipeline_id: CachedRenderPipelineId,
165}
166
167#[derive(PartialEq, Eq, Hash, Clone, Copy)]
168pub struct FxaaPipelineKey {
169    edge_threshold: Sensitivity,
170    edge_threshold_min: Sensitivity,
171    texture_format: TextureFormat,
172}
173
174impl SpecializedRenderPipeline for FxaaPipeline {
175    type Key = FxaaPipelineKey;
176
177    fn specialize(&self, key: Self::Key) -> RenderPipelineDescriptor {
178        RenderPipelineDescriptor {
179            label: Some("fxaa".into()),
180            layout: vec![self.texture_bind_group.clone()],
181            vertex: fullscreen_shader_vertex_state(),
182            fragment: Some(FragmentState {
183                shader: FXAA_SHADER_HANDLE,
184                shader_defs: vec![
185                    format!("EDGE_THRESH_{}", key.edge_threshold.get_str()).into(),
186                    format!("EDGE_THRESH_MIN_{}", key.edge_threshold_min.get_str()).into(),
187                ],
188                entry_point: "fragment".into(),
189                targets: vec![Some(ColorTargetState {
190                    format: key.texture_format,
191                    blend: None,
192                    write_mask: ColorWrites::ALL,
193                })],
194            }),
195            primitive: PrimitiveState::default(),
196            depth_stencil: None,
197            multisample: MultisampleState::default(),
198            push_constant_ranges: Vec::new(),
199            zero_initialize_workgroup_memory: false,
200        }
201    }
202}
203
204pub fn prepare_fxaa_pipelines(
205    mut commands: Commands,
206    pipeline_cache: Res<PipelineCache>,
207    mut pipelines: ResMut<SpecializedRenderPipelines<FxaaPipeline>>,
208    fxaa_pipeline: Res<FxaaPipeline>,
209    views: Query<(Entity, &ExtractedView, &Fxaa)>,
210) {
211    for (entity, view, fxaa) in &views {
212        if !fxaa.enabled {
213            continue;
214        }
215        let pipeline_id = pipelines.specialize(
216            &pipeline_cache,
217            &fxaa_pipeline,
218            FxaaPipelineKey {
219                edge_threshold: fxaa.edge_threshold,
220                edge_threshold_min: fxaa.edge_threshold_min,
221                texture_format: if view.hdr {
222                    ViewTarget::TEXTURE_FORMAT_HDR
223                } else {
224                    TextureFormat::bevy_default()
225                },
226            },
227        );
228
229        commands
230            .entity(entity)
231            .insert(CameraFxaaPipeline { pipeline_id });
232    }
233}