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#[derive(Reflect, Component, Clone, ExtractComponent)]
54#[reflect(Component, Default)]
55#[extract_component_filter(With<Camera>)]
56#[doc(alias = "FastApproximateAntiAliasing")]
57pub struct Fxaa {
58 pub enabled: bool,
60
61 pub edge_threshold: Sensitivity,
68
69 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
85pub 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}