bevy_core_pipeline/contrast_adaptive_sharpening/
mod.rs1use 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, weak_handle, Handle};
8use bevy_ecs::{prelude::*, query::QueryItem};
9use bevy_image::BevyDefault as _;
10use bevy_reflect::{std_traits::ReflectDefault, Reflect};
11use bevy_render::{
12 extract_component::{ExtractComponent, ExtractComponentPlugin, UniformComponentPlugin},
13 prelude::Camera,
14 render_graph::RenderGraphApp,
15 render_resource::{
16 binding_types::{sampler, texture_2d, uniform_buffer},
17 *,
18 },
19 renderer::RenderDevice,
20 view::{ExtractedView, ViewTarget},
21 Render, RenderApp, RenderSet,
22};
23
24mod node;
25
26pub use node::CasNode;
27
28#[derive(Component, Reflect, Clone)]
39#[reflect(Component, Default, Clone)]
40pub struct ContrastAdaptiveSharpening {
41 pub enabled: bool,
43 pub sharpening_strength: f32,
49 pub denoise: bool,
55}
56
57impl Default for ContrastAdaptiveSharpening {
58 fn default() -> Self {
59 ContrastAdaptiveSharpening {
60 enabled: true,
61 sharpening_strength: 0.6,
62 denoise: false,
63 }
64 }
65}
66
67#[derive(Component, Default, Reflect, Clone)]
68#[reflect(Component, Default, Clone)]
69pub struct DenoiseCas(bool);
70
71#[doc(hidden)]
74#[derive(Component, ShaderType, Clone)]
75pub struct CasUniform {
76 sharpness: f32,
77}
78
79impl ExtractComponent for ContrastAdaptiveSharpening {
80 type QueryData = &'static Self;
81 type QueryFilter = With<Camera>;
82 type Out = (DenoiseCas, CasUniform);
83
84 fn extract_component(item: QueryItem<Self::QueryData>) -> Option<Self::Out> {
85 if !item.enabled || item.sharpening_strength == 0.0 {
86 return None;
87 }
88 Some((
89 DenoiseCas(item.denoise),
90 CasUniform {
91 sharpness: item.sharpening_strength.clamp(0.0, 1.0),
93 },
94 ))
95 }
96}
97
98const CONTRAST_ADAPTIVE_SHARPENING_SHADER_HANDLE: Handle<Shader> =
99 weak_handle!("ef83f0a5-51df-4b51-9ab7-b5fd1ae5a397");
100
101pub struct CasPlugin;
103
104impl Plugin for CasPlugin {
105 fn build(&self, app: &mut App) {
106 load_internal_asset!(
107 app,
108 CONTRAST_ADAPTIVE_SHARPENING_SHADER_HANDLE,
109 "robust_contrast_adaptive_sharpening.wgsl",
110 Shader::from_wgsl
111 );
112
113 app.register_type::<ContrastAdaptiveSharpening>();
114 app.add_plugins((
115 ExtractComponentPlugin::<ContrastAdaptiveSharpening>::default(),
116 UniformComponentPlugin::<CasUniform>::default(),
117 ));
118
119 let Some(render_app) = app.get_sub_app_mut(RenderApp) else {
120 return;
121 };
122 render_app
123 .init_resource::<SpecializedRenderPipelines<CasPipeline>>()
124 .add_systems(Render, prepare_cas_pipelines.in_set(RenderSet::Prepare));
125
126 {
127 render_app
128 .add_render_graph_node::<CasNode>(Core3d, Node3d::ContrastAdaptiveSharpening)
129 .add_render_graph_edge(
130 Core3d,
131 Node3d::Tonemapping,
132 Node3d::ContrastAdaptiveSharpening,
133 )
134 .add_render_graph_edges(
135 Core3d,
136 (
137 Node3d::Fxaa,
138 Node3d::ContrastAdaptiveSharpening,
139 Node3d::EndMainPassPostProcessing,
140 ),
141 );
142 }
143 {
144 render_app
145 .add_render_graph_node::<CasNode>(Core2d, Node2d::ContrastAdaptiveSharpening)
146 .add_render_graph_edge(
147 Core2d,
148 Node2d::Tonemapping,
149 Node2d::ContrastAdaptiveSharpening,
150 )
151 .add_render_graph_edges(
152 Core2d,
153 (
154 Node2d::Fxaa,
155 Node2d::ContrastAdaptiveSharpening,
156 Node2d::EndMainPassPostProcessing,
157 ),
158 );
159 }
160 }
161
162 fn finish(&self, app: &mut App) {
163 let Some(render_app) = app.get_sub_app_mut(RenderApp) else {
164 return;
165 };
166 render_app.init_resource::<CasPipeline>();
167 }
168}
169
170#[derive(Resource)]
171pub struct CasPipeline {
172 texture_bind_group: BindGroupLayout,
173 sampler: Sampler,
174}
175
176impl FromWorld for CasPipeline {
177 fn from_world(render_world: &mut World) -> Self {
178 let render_device = render_world.resource::<RenderDevice>();
179 let texture_bind_group = render_device.create_bind_group_layout(
180 "sharpening_texture_bind_group_layout",
181 &BindGroupLayoutEntries::sequential(
182 ShaderStages::FRAGMENT,
183 (
184 texture_2d(TextureSampleType::Float { filterable: true }),
185 sampler(SamplerBindingType::Filtering),
186 uniform_buffer::<CasUniform>(true),
188 ),
189 ),
190 );
191
192 let sampler = render_device.create_sampler(&SamplerDescriptor::default());
193
194 CasPipeline {
195 texture_bind_group,
196 sampler,
197 }
198 }
199}
200
201#[derive(PartialEq, Eq, Hash, Clone, Copy)]
202pub struct CasPipelineKey {
203 texture_format: TextureFormat,
204 denoise: bool,
205}
206
207impl SpecializedRenderPipeline for CasPipeline {
208 type Key = CasPipelineKey;
209
210 fn specialize(&self, key: Self::Key) -> RenderPipelineDescriptor {
211 let mut shader_defs = vec![];
212 if key.denoise {
213 shader_defs.push("RCAS_DENOISE".into());
214 }
215 RenderPipelineDescriptor {
216 label: Some("contrast_adaptive_sharpening".into()),
217 layout: vec![self.texture_bind_group.clone()],
218 vertex: fullscreen_shader_vertex_state(),
219 fragment: Some(FragmentState {
220 shader: CONTRAST_ADAPTIVE_SHARPENING_SHADER_HANDLE,
221 shader_defs,
222 entry_point: "fragment".into(),
223 targets: vec![Some(ColorTargetState {
224 format: key.texture_format,
225 blend: None,
226 write_mask: ColorWrites::ALL,
227 })],
228 }),
229 primitive: PrimitiveState::default(),
230 depth_stencil: None,
231 multisample: MultisampleState::default(),
232 push_constant_ranges: Vec::new(),
233 zero_initialize_workgroup_memory: false,
234 }
235 }
236}
237
238fn prepare_cas_pipelines(
239 mut commands: Commands,
240 pipeline_cache: Res<PipelineCache>,
241 mut pipelines: ResMut<SpecializedRenderPipelines<CasPipeline>>,
242 sharpening_pipeline: Res<CasPipeline>,
243 views: Query<
244 (Entity, &ExtractedView, &DenoiseCas),
245 Or<(Added<CasUniform>, Changed<DenoiseCas>)>,
246 >,
247 mut removals: RemovedComponents<CasUniform>,
248) {
249 for entity in removals.read() {
250 commands.entity(entity).remove::<ViewCasPipeline>();
251 }
252
253 for (entity, view, denoise_cas) in &views {
254 let pipeline_id = pipelines.specialize(
255 &pipeline_cache,
256 &sharpening_pipeline,
257 CasPipelineKey {
258 denoise: denoise_cas.0,
259 texture_format: if view.hdr {
260 ViewTarget::TEXTURE_FORMAT_HDR
261 } else {
262 TextureFormat::bevy_default()
263 },
264 },
265 );
266
267 commands.entity(entity).insert(ViewCasPipeline(pipeline_id));
268 }
269}
270
271#[derive(Component)]
272pub struct ViewCasPipeline(CachedRenderPipelineId);