1#![expect(deprecated)]
2
3use crate::{
4 core_3d::graph::{Core3d, Node3d},
5 fullscreen_vertex_shader::fullscreen_shader_vertex_state,
6 prelude::Camera3d,
7 prepass::{DepthPrepass, MotionVectorPrepass, ViewPrepassTextures},
8};
9use bevy_app::{App, Plugin};
10use bevy_asset::{load_internal_asset, Handle};
11use bevy_core::FrameCount;
12use bevy_ecs::{
13 prelude::{Bundle, Component, Entity, ReflectComponent},
14 query::{QueryItem, With},
15 schedule::IntoSystemConfigs,
16 system::{Commands, Query, Res, ResMut, Resource},
17 world::{FromWorld, World},
18};
19use bevy_image::BevyDefault as _;
20use bevy_math::vec2;
21use bevy_reflect::{std_traits::ReflectDefault, Reflect};
22use bevy_render::{
23 camera::{ExtractedCamera, MipBias, TemporalJitter},
24 prelude::{Camera, Projection},
25 render_graph::{NodeRunError, RenderGraphApp, RenderGraphContext, ViewNode, ViewNodeRunner},
26 render_resource::{
27 binding_types::{sampler, texture_2d, texture_depth_2d},
28 BindGroupEntries, BindGroupLayout, BindGroupLayoutEntries, CachedRenderPipelineId,
29 ColorTargetState, ColorWrites, Extent3d, FilterMode, FragmentState, MultisampleState,
30 Operations, PipelineCache, PrimitiveState, RenderPassColorAttachment, RenderPassDescriptor,
31 RenderPipelineDescriptor, Sampler, SamplerBindingType, SamplerDescriptor, Shader,
32 ShaderStages, SpecializedRenderPipeline, SpecializedRenderPipelines, TextureDescriptor,
33 TextureDimension, TextureFormat, TextureSampleType, TextureUsages,
34 },
35 renderer::{RenderContext, RenderDevice},
36 sync_component::SyncComponentPlugin,
37 sync_world::RenderEntity,
38 texture::{CachedTexture, TextureCache},
39 view::{ExtractedView, Msaa, ViewTarget},
40 ExtractSchedule, MainWorld, Render, RenderApp, RenderSet,
41};
42use bevy_utils::tracing::warn;
43
44const TAA_SHADER_HANDLE: Handle<Shader> = Handle::weak_from_u128(656865235226276);
45
46pub struct TemporalAntiAliasPlugin;
50
51impl Plugin for TemporalAntiAliasPlugin {
52 fn build(&self, app: &mut App) {
53 load_internal_asset!(app, TAA_SHADER_HANDLE, "taa.wgsl", Shader::from_wgsl);
54
55 app.register_type::<TemporalAntiAliasing>();
56
57 app.add_plugins(SyncComponentPlugin::<TemporalAntiAliasing>::default());
58
59 let Some(render_app) = app.get_sub_app_mut(RenderApp) else {
60 return;
61 };
62 render_app
63 .init_resource::<SpecializedRenderPipelines<TaaPipeline>>()
64 .add_systems(ExtractSchedule, extract_taa_settings)
65 .add_systems(
66 Render,
67 (
68 prepare_taa_jitter_and_mip_bias.in_set(RenderSet::ManageViews),
69 prepare_taa_pipelines.in_set(RenderSet::Prepare),
70 prepare_taa_history_textures.in_set(RenderSet::PrepareResources),
71 ),
72 )
73 .add_render_graph_node::<ViewNodeRunner<TemporalAntiAliasNode>>(Core3d, Node3d::Taa)
74 .add_render_graph_edges(
75 Core3d,
76 (
77 Node3d::EndMainPass,
78 Node3d::MotionBlur, Node3d::Taa,
80 Node3d::Bloom,
81 Node3d::Tonemapping,
82 ),
83 );
84 }
85
86 fn finish(&self, app: &mut App) {
87 let Some(render_app) = app.get_sub_app_mut(RenderApp) else {
88 return;
89 };
90
91 render_app.init_resource::<TaaPipeline>();
92 }
93}
94
95#[derive(Bundle, Default, Clone)]
97#[deprecated(
98 since = "0.15.0",
99 note = "Use the `TemporalAntiAlias` component instead. Inserting it will now also insert the other components required by it automatically."
100)]
101pub struct TemporalAntiAliasBundle {
102 pub settings: TemporalAntiAliasing,
103 pub jitter: TemporalJitter,
104 pub depth_prepass: DepthPrepass,
105 pub motion_vector_prepass: MotionVectorPrepass,
106}
107
108#[derive(Component, Reflect, Clone)]
148#[reflect(Component, Default)]
149#[require(TemporalJitter, DepthPrepass, MotionVectorPrepass)]
150#[doc(alias = "Taa")]
151pub struct TemporalAntiAliasing {
152 pub reset: bool,
160}
161
162#[deprecated(since = "0.15.0", note = "Renamed to `TemporalAntiAliasing`")]
163pub type TemporalAntiAliasSettings = TemporalAntiAliasing;
164
165impl Default for TemporalAntiAliasing {
166 fn default() -> Self {
167 Self { reset: true }
168 }
169}
170
171#[derive(Default)]
173pub struct TemporalAntiAliasNode;
174
175impl ViewNode for TemporalAntiAliasNode {
176 type ViewQuery = (
177 &'static ExtractedCamera,
178 &'static ViewTarget,
179 &'static TemporalAntiAliasHistoryTextures,
180 &'static ViewPrepassTextures,
181 &'static TemporalAntiAliasPipelineId,
182 &'static Msaa,
183 );
184
185 fn run(
186 &self,
187 _graph: &mut RenderGraphContext,
188 render_context: &mut RenderContext,
189 (camera, view_target, taa_history_textures, prepass_textures, taa_pipeline_id, msaa): QueryItem<
190 Self::ViewQuery,
191 >,
192 world: &World,
193 ) -> Result<(), NodeRunError> {
194 if *msaa != Msaa::Off {
195 warn!("Temporal anti-aliasing requires MSAA to be disabled");
196 return Ok(());
197 }
198
199 let (Some(pipelines), Some(pipeline_cache)) = (
200 world.get_resource::<TaaPipeline>(),
201 world.get_resource::<PipelineCache>(),
202 ) else {
203 return Ok(());
204 };
205 let (Some(taa_pipeline), Some(prepass_motion_vectors_texture), Some(prepass_depth_texture)) = (
206 pipeline_cache.get_render_pipeline(taa_pipeline_id.0),
207 &prepass_textures.motion_vectors,
208 &prepass_textures.depth,
209 ) else {
210 return Ok(());
211 };
212 let view_target = view_target.post_process_write();
213
214 let taa_bind_group = render_context.render_device().create_bind_group(
215 "taa_bind_group",
216 &pipelines.taa_bind_group_layout,
217 &BindGroupEntries::sequential((
218 view_target.source,
219 &taa_history_textures.read.default_view,
220 &prepass_motion_vectors_texture.texture.default_view,
221 &prepass_depth_texture.texture.default_view,
222 &pipelines.nearest_sampler,
223 &pipelines.linear_sampler,
224 )),
225 );
226
227 {
228 let mut taa_pass = render_context.begin_tracked_render_pass(RenderPassDescriptor {
229 label: Some("taa_pass"),
230 color_attachments: &[
231 Some(RenderPassColorAttachment {
232 view: view_target.destination,
233 resolve_target: None,
234 ops: Operations::default(),
235 }),
236 Some(RenderPassColorAttachment {
237 view: &taa_history_textures.write.default_view,
238 resolve_target: None,
239 ops: Operations::default(),
240 }),
241 ],
242 depth_stencil_attachment: None,
243 timestamp_writes: None,
244 occlusion_query_set: None,
245 });
246 taa_pass.set_render_pipeline(taa_pipeline);
247 taa_pass.set_bind_group(0, &taa_bind_group, &[]);
248 if let Some(viewport) = camera.viewport.as_ref() {
249 taa_pass.set_camera_viewport(viewport);
250 }
251 taa_pass.draw(0..3, 0..1);
252 }
253
254 Ok(())
255 }
256}
257
258#[derive(Resource)]
259struct TaaPipeline {
260 taa_bind_group_layout: BindGroupLayout,
261 nearest_sampler: Sampler,
262 linear_sampler: Sampler,
263}
264
265impl FromWorld for TaaPipeline {
266 fn from_world(world: &mut World) -> Self {
267 let render_device = world.resource::<RenderDevice>();
268
269 let nearest_sampler = render_device.create_sampler(&SamplerDescriptor {
270 label: Some("taa_nearest_sampler"),
271 mag_filter: FilterMode::Nearest,
272 min_filter: FilterMode::Nearest,
273 ..SamplerDescriptor::default()
274 });
275 let linear_sampler = render_device.create_sampler(&SamplerDescriptor {
276 label: Some("taa_linear_sampler"),
277 mag_filter: FilterMode::Linear,
278 min_filter: FilterMode::Linear,
279 ..SamplerDescriptor::default()
280 });
281
282 let taa_bind_group_layout = render_device.create_bind_group_layout(
283 "taa_bind_group_layout",
284 &BindGroupLayoutEntries::sequential(
285 ShaderStages::FRAGMENT,
286 (
287 texture_2d(TextureSampleType::Float { filterable: true }),
289 texture_2d(TextureSampleType::Float { filterable: true }),
291 texture_2d(TextureSampleType::Float { filterable: true }),
293 texture_depth_2d(),
295 sampler(SamplerBindingType::NonFiltering),
297 sampler(SamplerBindingType::Filtering),
299 ),
300 ),
301 );
302
303 TaaPipeline {
304 taa_bind_group_layout,
305 nearest_sampler,
306 linear_sampler,
307 }
308 }
309}
310
311#[derive(PartialEq, Eq, Hash, Clone)]
312struct TaaPipelineKey {
313 hdr: bool,
314 reset: bool,
315}
316
317impl SpecializedRenderPipeline for TaaPipeline {
318 type Key = TaaPipelineKey;
319
320 fn specialize(&self, key: Self::Key) -> RenderPipelineDescriptor {
321 let mut shader_defs = vec![];
322
323 let format = if key.hdr {
324 shader_defs.push("TONEMAP".into());
325 ViewTarget::TEXTURE_FORMAT_HDR
326 } else {
327 TextureFormat::bevy_default()
328 };
329
330 if key.reset {
331 shader_defs.push("RESET".into());
332 }
333
334 RenderPipelineDescriptor {
335 label: Some("taa_pipeline".into()),
336 layout: vec![self.taa_bind_group_layout.clone()],
337 vertex: fullscreen_shader_vertex_state(),
338 fragment: Some(FragmentState {
339 shader: TAA_SHADER_HANDLE,
340 shader_defs,
341 entry_point: "taa".into(),
342 targets: vec![
343 Some(ColorTargetState {
344 format,
345 blend: None,
346 write_mask: ColorWrites::ALL,
347 }),
348 Some(ColorTargetState {
349 format,
350 blend: None,
351 write_mask: ColorWrites::ALL,
352 }),
353 ],
354 }),
355 primitive: PrimitiveState::default(),
356 depth_stencil: None,
357 multisample: MultisampleState::default(),
358 push_constant_ranges: Vec::new(),
359 zero_initialize_workgroup_memory: false,
360 }
361 }
362}
363
364fn extract_taa_settings(mut commands: Commands, mut main_world: ResMut<MainWorld>) {
365 let mut cameras_3d = main_world.query_filtered::<(
366 RenderEntity,
367 &Camera,
368 &Projection,
369 &mut TemporalAntiAliasing,
370 ), (
371 With<Camera3d>,
372 With<TemporalJitter>,
373 With<DepthPrepass>,
374 With<MotionVectorPrepass>,
375 )>();
376
377 for (entity, camera, camera_projection, mut taa_settings) in
378 cameras_3d.iter_mut(&mut main_world)
379 {
380 let has_perspective_projection = matches!(camera_projection, Projection::Perspective(_));
381 let mut entity_commands = commands
382 .get_entity(entity)
383 .expect("Camera entity wasn't synced.");
384 if camera.is_active && has_perspective_projection {
385 entity_commands.insert(taa_settings.clone());
386 taa_settings.reset = false;
387 } else {
388 entity_commands.remove::<(
390 TemporalAntiAliasing,
391 TemporalAntiAliasHistoryTextures,
393 TemporalAntiAliasPipelineId,
394 )>();
395 }
396 }
397}
398
399fn prepare_taa_jitter_and_mip_bias(
400 frame_count: Res<FrameCount>,
401 mut query: Query<(Entity, &mut TemporalJitter, Option<&MipBias>), With<TemporalAntiAliasing>>,
402 mut commands: Commands,
403) {
404 let halton_sequence = [
406 vec2(0.0, -0.16666666),
407 vec2(-0.25, 0.16666669),
408 vec2(0.25, -0.3888889),
409 vec2(-0.375, -0.055555552),
410 vec2(0.125, 0.2777778),
411 vec2(-0.125, -0.2777778),
412 vec2(0.375, 0.055555582),
413 vec2(-0.4375, 0.3888889),
414 ];
415
416 let offset = halton_sequence[frame_count.0 as usize % halton_sequence.len()];
417
418 for (entity, mut jitter, mip_bias) in &mut query {
419 jitter.offset = offset;
420
421 if mip_bias.is_none() {
422 commands.entity(entity).insert(MipBias(-1.0));
423 }
424 }
425}
426
427#[derive(Component)]
428pub struct TemporalAntiAliasHistoryTextures {
429 write: CachedTexture,
430 read: CachedTexture,
431}
432
433fn prepare_taa_history_textures(
434 mut commands: Commands,
435 mut texture_cache: ResMut<TextureCache>,
436 render_device: Res<RenderDevice>,
437 frame_count: Res<FrameCount>,
438 views: Query<(Entity, &ExtractedCamera, &ExtractedView), With<TemporalAntiAliasing>>,
439) {
440 for (entity, camera, view) in &views {
441 if let Some(physical_target_size) = camera.physical_target_size {
442 let mut texture_descriptor = TextureDescriptor {
443 label: None,
444 size: Extent3d {
445 depth_or_array_layers: 1,
446 width: physical_target_size.x,
447 height: physical_target_size.y,
448 },
449 mip_level_count: 1,
450 sample_count: 1,
451 dimension: TextureDimension::D2,
452 format: if view.hdr {
453 ViewTarget::TEXTURE_FORMAT_HDR
454 } else {
455 TextureFormat::bevy_default()
456 },
457 usage: TextureUsages::TEXTURE_BINDING | TextureUsages::RENDER_ATTACHMENT,
458 view_formats: &[],
459 };
460
461 texture_descriptor.label = Some("taa_history_1_texture");
462 let history_1_texture = texture_cache.get(&render_device, texture_descriptor.clone());
463
464 texture_descriptor.label = Some("taa_history_2_texture");
465 let history_2_texture = texture_cache.get(&render_device, texture_descriptor);
466
467 let textures = if frame_count.0 % 2 == 0 {
468 TemporalAntiAliasHistoryTextures {
469 write: history_1_texture,
470 read: history_2_texture,
471 }
472 } else {
473 TemporalAntiAliasHistoryTextures {
474 write: history_2_texture,
475 read: history_1_texture,
476 }
477 };
478
479 commands.entity(entity).insert(textures);
480 }
481 }
482}
483
484#[derive(Component)]
485pub struct TemporalAntiAliasPipelineId(CachedRenderPipelineId);
486
487fn prepare_taa_pipelines(
488 mut commands: Commands,
489 pipeline_cache: Res<PipelineCache>,
490 mut pipelines: ResMut<SpecializedRenderPipelines<TaaPipeline>>,
491 pipeline: Res<TaaPipeline>,
492 views: Query<(Entity, &ExtractedView, &TemporalAntiAliasing)>,
493) {
494 for (entity, view, taa_settings) in &views {
495 let mut pipeline_key = TaaPipelineKey {
496 hdr: view.hdr,
497 reset: taa_settings.reset,
498 };
499 let pipeline_id = pipelines.specialize(&pipeline_cache, &pipeline, pipeline_key.clone());
500
501 if pipeline_key.reset {
503 pipeline_key.reset = false;
504 pipelines.specialize(&pipeline_cache, &pipeline, pipeline_key);
505 }
506
507 commands
508 .entity(entity)
509 .insert(TemporalAntiAliasPipelineId(pipeline_id));
510 }
511}