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