use crate::{
core_2d::graph::{Core2d, Node2d},
core_3d::graph::{Core3d, Node3d},
fullscreen_vertex_shader::fullscreen_shader_vertex_state,
};
use bevy_app::prelude::*;
use bevy_asset::{load_internal_asset, Handle};
use bevy_ecs::prelude::*;
use bevy_reflect::{std_traits::ReflectDefault, Reflect};
use bevy_render::{
extract_component::{ExtractComponent, ExtractComponentPlugin},
prelude::Camera,
render_graph::RenderGraphApp,
render_graph::ViewNodeRunner,
render_resource::{
binding_types::{sampler, texture_2d},
*,
},
renderer::RenderDevice,
texture::BevyDefault,
view::{ExtractedView, ViewTarget},
Render, RenderApp, RenderSet,
};
use bevy_utils::default;
mod node;
pub use node::FxaaNode;
#[derive(Reflect, Eq, PartialEq, Hash, Clone, Copy)]
#[reflect(PartialEq, Hash)]
pub enum Sensitivity {
Low,
Medium,
High,
Ultra,
Extreme,
}
impl Sensitivity {
pub fn get_str(&self) -> &str {
match self {
Sensitivity::Low => "LOW",
Sensitivity::Medium => "MEDIUM",
Sensitivity::High => "HIGH",
Sensitivity::Ultra => "ULTRA",
Sensitivity::Extreme => "EXTREME",
}
}
}
#[derive(Reflect, Component, Clone, ExtractComponent)]
#[reflect(Component, Default)]
#[extract_component_filter(With<Camera>)]
pub struct Fxaa {
pub enabled: bool,
pub edge_threshold: Sensitivity,
pub edge_threshold_min: Sensitivity,
}
impl Default for Fxaa {
fn default() -> Self {
Fxaa {
enabled: true,
edge_threshold: Sensitivity::High,
edge_threshold_min: Sensitivity::High,
}
}
}
const FXAA_SHADER_HANDLE: Handle<Shader> = Handle::weak_from_u128(4182761465141723543);
pub struct FxaaPlugin;
impl Plugin for FxaaPlugin {
fn build(&self, app: &mut App) {
load_internal_asset!(app, FXAA_SHADER_HANDLE, "fxaa.wgsl", Shader::from_wgsl);
app.register_type::<Fxaa>();
app.add_plugins(ExtractComponentPlugin::<Fxaa>::default());
let Ok(render_app) = app.get_sub_app_mut(RenderApp) else {
return;
};
render_app
.init_resource::<SpecializedRenderPipelines<FxaaPipeline>>()
.add_systems(Render, prepare_fxaa_pipelines.in_set(RenderSet::Prepare))
.add_render_graph_node::<ViewNodeRunner<FxaaNode>>(Core3d, Node3d::Fxaa)
.add_render_graph_edges(
Core3d,
(
Node3d::Tonemapping,
Node3d::Fxaa,
Node3d::EndMainPassPostProcessing,
),
)
.add_render_graph_node::<ViewNodeRunner<FxaaNode>>(Core2d, Node2d::Fxaa)
.add_render_graph_edges(
Core2d,
(
Node2d::Tonemapping,
Node2d::Fxaa,
Node2d::EndMainPassPostProcessing,
),
);
}
fn finish(&self, app: &mut App) {
let Ok(render_app) = app.get_sub_app_mut(RenderApp) else {
return;
};
render_app.init_resource::<FxaaPipeline>();
}
}
#[derive(Resource)]
pub struct FxaaPipeline {
texture_bind_group: BindGroupLayout,
sampler: Sampler,
}
impl FromWorld for FxaaPipeline {
fn from_world(render_world: &mut World) -> Self {
let render_device = render_world.resource::<RenderDevice>();
let texture_bind_group = render_device.create_bind_group_layout(
"fxaa_texture_bind_group_layout",
&BindGroupLayoutEntries::sequential(
ShaderStages::FRAGMENT,
(
texture_2d(TextureSampleType::Float { filterable: true }),
sampler(SamplerBindingType::Filtering),
),
),
);
let sampler = render_device.create_sampler(&SamplerDescriptor {
mipmap_filter: FilterMode::Linear,
mag_filter: FilterMode::Linear,
min_filter: FilterMode::Linear,
..default()
});
FxaaPipeline {
texture_bind_group,
sampler,
}
}
}
#[derive(Component)]
pub struct CameraFxaaPipeline {
pub pipeline_id: CachedRenderPipelineId,
}
#[derive(PartialEq, Eq, Hash, Clone, Copy)]
pub struct FxaaPipelineKey {
edge_threshold: Sensitivity,
edge_threshold_min: Sensitivity,
texture_format: TextureFormat,
}
impl SpecializedRenderPipeline for FxaaPipeline {
type Key = FxaaPipelineKey;
fn specialize(&self, key: Self::Key) -> RenderPipelineDescriptor {
RenderPipelineDescriptor {
label: Some("fxaa".into()),
layout: vec![self.texture_bind_group.clone()],
vertex: fullscreen_shader_vertex_state(),
fragment: Some(FragmentState {
shader: FXAA_SHADER_HANDLE,
shader_defs: vec![
format!("EDGE_THRESH_{}", key.edge_threshold.get_str()).into(),
format!("EDGE_THRESH_MIN_{}", key.edge_threshold_min.get_str()).into(),
],
entry_point: "fragment".into(),
targets: vec![Some(ColorTargetState {
format: key.texture_format,
blend: None,
write_mask: ColorWrites::ALL,
})],
}),
primitive: PrimitiveState::default(),
depth_stencil: None,
multisample: MultisampleState::default(),
push_constant_ranges: Vec::new(),
}
}
}
pub fn prepare_fxaa_pipelines(
mut commands: Commands,
pipeline_cache: Res<PipelineCache>,
mut pipelines: ResMut<SpecializedRenderPipelines<FxaaPipeline>>,
fxaa_pipeline: Res<FxaaPipeline>,
views: Query<(Entity, &ExtractedView, &Fxaa)>,
) {
for (entity, view, fxaa) in &views {
if !fxaa.enabled {
continue;
}
let pipeline_id = pipelines.specialize(
&pipeline_cache,
&fxaa_pipeline,
FxaaPipelineKey {
edge_threshold: fxaa.edge_threshold,
edge_threshold_min: fxaa.edge_threshold_min,
texture_format: if view.hdr {
ViewTarget::TEXTURE_FORMAT_HDR
} else {
TextureFormat::bevy_default()
},
},
);
commands
.entity(entity)
.insert(CameraFxaaPipeline { pipeline_id });
}
}