Skip to main content

bevy_core_pipeline/prepass/
background_motion_vectors.rs

1//! Default background motion vector prepass.
2//!
3//! When a camera has [`MotionVectorPrepass`] but no [`NoBackgroundMotionVectors`], this module
4//! writes motion vectors for background pixels (depth == 0 in reversed-Z) based on camera
5//! rotation, so that effects like TAA and motion blur work correctly on the background.
6//!
7//! This is a general solution that works for any background: skyboxes, atmospheric sky,
8//! solid color backgrounds, etc.
9
10use bevy_app::{App, Plugin};
11use bevy_asset::{embedded_asset, load_embedded_asset, AssetServer, Handle};
12use bevy_ecs::{
13    component::Component,
14    entity::Entity,
15    query::{Has, Or, QueryItem, With, Without},
16    reflect::ReflectComponent,
17    resource::Resource,
18    schedule::IntoScheduleConfigs,
19    system::{lifetimeless::Read, Commands, Query, Res, ResMut},
20};
21use bevy_log::warn;
22use bevy_reflect::{std_traits::ReflectDefault, Reflect};
23use bevy_render::{
24    extract_component::{ExtractComponent, ExtractComponentPlugin},
25    render_resource::{
26        binding_types::uniform_buffer, BindGroup, BindGroupEntries, BindGroupLayoutDescriptor,
27        BindGroupLayoutEntries, CachedRenderPipelineId, CompareFunction, DepthStencilState,
28        DownlevelFlags, FragmentState, MultisampleState, PipelineCache, RenderPipelineDescriptor,
29        ShaderStages, SpecializedRenderPipeline, SpecializedRenderPipelines,
30    },
31    renderer::{RenderAdapter, RenderDevice},
32    sync_component::SyncComponent,
33    view::{Msaa, ViewUniform, ViewUniforms},
34    GpuResourceAppExt, Render, RenderApp, RenderStartup, RenderSystems,
35};
36use bevy_shader::Shader;
37use bevy_utils::prelude::default;
38
39use crate::{
40    core_3d::CORE_3D_DEPTH_FORMAT,
41    prepass::{
42        prepass_target_descriptors, MotionVectorPrepass, NormalPrepass, PreviousViewData,
43        PreviousViewUniforms,
44    },
45    FullscreenShader,
46};
47
48/// When added to a camera with [`MotionVectorPrepass`], disables the automatic background motion
49/// vector prepass.
50///
51/// By default, any camera with [`MotionVectorPrepass`] will automatically write camera-rotation
52/// motion vectors for background pixels (those with no geometry, i.e. depth == 0 in reversed-Z).
53/// Add this component to opt out, for example if you are writing custom background motion vectors
54/// for your own effect.
55#[derive(Component, Default, Reflect, Clone)]
56#[reflect(Component, Default, Clone)]
57pub struct NoBackgroundMotionVectors;
58
59impl SyncComponent<RenderApp> for NoBackgroundMotionVectors {
60    type Target = Self;
61}
62
63impl ExtractComponent<RenderApp> for NoBackgroundMotionVectors {
64    type QueryData = Read<NoBackgroundMotionVectors>;
65    type QueryFilter = ();
66    type Out = Self;
67
68    fn extract_component(_item: QueryItem<'_, '_, Self::QueryData>) -> Option<Self::Out> {
69        Some(NoBackgroundMotionVectors)
70    }
71}
72
73/// Stores the background motion vectors pipeline ID on the camera entity. Used by the prepass node.
74#[derive(Component)]
75pub struct BackgroundMotionVectorsPipelineId(pub CachedRenderPipelineId);
76
77/// Stores the background motion vectors bind group on the camera entity. Used by the prepass node.
78#[derive(Component)]
79pub struct BackgroundMotionVectorsBindGroup(pub BindGroup);
80
81/// Plugin that writes camera-rotation motion vectors for background pixels on cameras with
82/// [`MotionVectorPrepass`].
83///
84/// Add [`NoBackgroundMotionVectors`] to a camera to opt out.
85#[derive(Default)]
86pub struct BackgroundMotionVectorsPlugin;
87
88impl BackgroundMotionVectorsPlugin {
89    /// [`DownlevelFlags`] required for this plugin to function.
90    pub fn required_downlevel_flags() -> DownlevelFlags {
91        DownlevelFlags::INDEPENDENT_BLEND
92    }
93}
94
95impl Plugin for BackgroundMotionVectorsPlugin {
96    fn build(&self, app: &mut App) {
97        embedded_asset!(app, "background_motion_vectors.wesl");
98        app.register_type::<NoBackgroundMotionVectors>()
99            .add_plugins(ExtractComponentPlugin::<NoBackgroundMotionVectors>::default());
100
101        let Some(render_app) = app.get_sub_app_mut(RenderApp) else {
102            return;
103        };
104        render_app.init_gpu_resource::<PreviousViewUniforms>();
105    }
106
107    fn finish(&self, app: &mut App) {
108        let Some(render_app) = app.get_sub_app_mut(RenderApp) else {
109            return;
110        };
111
112        let render_adapter = render_app.world().resource::<RenderAdapter>();
113        let downlevel_flags = render_adapter.get_downlevel_capabilities().flags;
114        if !downlevel_flags.contains(BackgroundMotionVectorsPlugin::required_downlevel_flags()) {
115            warn!(
116                "BackgroundMotionVectorsPlugin not loaded. GPU lacks support for required downlevel capability flags: {:?}.",
117                BackgroundMotionVectorsPlugin::required_downlevel_flags().difference(downlevel_flags)
118            );
119            return;
120        }
121
122        render_app
123            .init_gpu_resource::<SpecializedRenderPipelines<BackgroundMotionVectorsPipeline>>()
124            .add_systems(RenderStartup, init_background_motion_vectors_pipeline)
125            .add_systems(
126                Render,
127                (
128                    cleanup_background_motion_vectors.in_set(RenderSystems::Prepare),
129                    prepare_background_motion_vectors_pipelines.in_set(RenderSystems::Prepare),
130                    prepare_background_motion_vectors_bind_groups
131                        .in_set(RenderSystems::PrepareBindGroups),
132                ),
133            );
134    }
135}
136
137#[derive(Resource)]
138struct BackgroundMotionVectorsPipeline {
139    bind_group_layout: BindGroupLayoutDescriptor,
140    fullscreen_shader: FullscreenShader,
141    fragment_shader: Handle<Shader>,
142}
143
144#[derive(PartialEq, Eq, Hash, Clone, Copy)]
145struct BackgroundMotionVectorsPipelineKey {
146    samples: u32,
147    normal_prepass: bool,
148}
149
150fn init_background_motion_vectors_pipeline(
151    mut commands: Commands,
152    fullscreen_shader: Res<FullscreenShader>,
153    asset_server: Res<AssetServer>,
154) {
155    commands.insert_resource(BackgroundMotionVectorsPipeline {
156        bind_group_layout: BindGroupLayoutDescriptor::new(
157            "background_motion_vectors_bind_group_layout",
158            &BindGroupLayoutEntries::sequential(
159                ShaderStages::FRAGMENT,
160                (
161                    uniform_buffer::<ViewUniform>(true),
162                    uniform_buffer::<PreviousViewData>(true),
163                ),
164            ),
165        ),
166        fullscreen_shader: fullscreen_shader.clone(),
167        fragment_shader: load_embedded_asset!(
168            asset_server.as_ref(),
169            "background_motion_vectors.wesl"
170        ),
171    });
172}
173
174impl SpecializedRenderPipeline for BackgroundMotionVectorsPipeline {
175    type Key = BackgroundMotionVectorsPipelineKey;
176
177    fn specialize(&self, key: Self::Key) -> RenderPipelineDescriptor {
178        let mut targets = prepass_target_descriptors(key.normal_prepass, true, false);
179        // The shader only outputs to attachment at location 1, set write mask of the other attachments to empty
180        // to avoid WebGPU validation error "Color target has no corresponding fragment stage output but writeMask is not zero".
181        for target in
182            targets
183                .iter_mut()
184                .enumerate()
185                .filter_map(|(i, t)| if i == 1 { None } else { t.as_mut() })
186        {
187            target.write_mask = bevy_render::render_resource::ColorWrites::empty();
188        }
189
190        RenderPipelineDescriptor {
191            label: Some("background_motion_vectors_pipeline".into()),
192            layout: vec![self.bind_group_layout.clone()],
193            vertex: self.fullscreen_shader.to_vertex_state(),
194            depth_stencil: Some(DepthStencilState {
195                format: CORE_3D_DEPTH_FORMAT,
196                depth_write_enabled: Some(false),
197                depth_compare: Some(CompareFunction::GreaterEqual),
198                stencil: default(),
199                bias: default(),
200            }),
201            multisample: MultisampleState {
202                count: key.samples,
203                mask: !0,
204                alpha_to_coverage_enabled: false,
205            },
206            fragment: Some(FragmentState {
207                shader: self.fragment_shader.clone(),
208                targets,
209                ..default()
210            }),
211            ..default()
212        }
213    }
214}
215
216fn cleanup_background_motion_vectors(
217    mut commands: Commands,
218    views: Query<
219        Entity,
220        (
221            With<BackgroundMotionVectorsPipelineId>,
222            Or<(
223                Without<MotionVectorPrepass>,
224                With<NoBackgroundMotionVectors>,
225            )>,
226        ),
227    >,
228) {
229    for entity in &views {
230        commands.entity(entity).remove::<(
231            BackgroundMotionVectorsPipelineId,
232            BackgroundMotionVectorsBindGroup,
233        )>();
234    }
235}
236
237fn prepare_background_motion_vectors_pipelines(
238    mut commands: Commands,
239    pipeline_cache: Res<PipelineCache>,
240    mut pipelines: ResMut<SpecializedRenderPipelines<BackgroundMotionVectorsPipeline>>,
241    pipeline: Res<BackgroundMotionVectorsPipeline>,
242    views: Query<
243        (Entity, Has<NormalPrepass>, &Msaa),
244        (
245            With<MotionVectorPrepass>,
246            Without<NoBackgroundMotionVectors>,
247        ),
248    >,
249) {
250    for (entity, normal_prepass, msaa) in &views {
251        let id = pipelines.specialize(
252            &pipeline_cache,
253            &pipeline,
254            BackgroundMotionVectorsPipelineKey {
255                samples: msaa.samples(),
256                normal_prepass,
257            },
258        );
259        commands
260            .entity(entity)
261            .insert(BackgroundMotionVectorsPipelineId(id));
262    }
263}
264
265fn prepare_background_motion_vectors_bind_groups(
266    mut commands: Commands,
267    pipeline: Res<BackgroundMotionVectorsPipeline>,
268    view_uniforms: Res<ViewUniforms>,
269    prev_view_uniforms: Res<PreviousViewUniforms>,
270    render_device: Res<RenderDevice>,
271    pipeline_cache: Res<PipelineCache>,
272    views: Query<
273        Entity,
274        (
275            With<MotionVectorPrepass>,
276            Without<NoBackgroundMotionVectors>,
277        ),
278    >,
279) {
280    for entity in &views {
281        let (Some(view_binding), Some(prev_view_binding)) = (
282            view_uniforms.uniforms.binding(),
283            prev_view_uniforms.uniforms.binding(),
284        ) else {
285            continue;
286        };
287        let bind_group = render_device.create_bind_group(
288            "background_motion_vectors_bind_group",
289            &pipeline_cache.get_bind_group_layout(&pipeline.bind_group_layout),
290            &BindGroupEntries::sequential((view_binding, prev_view_binding)),
291        );
292        commands
293            .entity(entity)
294            .insert(BackgroundMotionVectorsBindGroup(bind_group));
295    }
296}