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, 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 for NoBackgroundMotionVectors {
60    type Target = Self;
61}
62
63impl ExtractComponent 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.wgsl");
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                    prepare_background_motion_vectors_pipelines.in_set(RenderSystems::Prepare),
129                    prepare_background_motion_vectors_bind_groups
130                        .in_set(RenderSystems::PrepareBindGroups),
131                ),
132            );
133    }
134}
135
136#[derive(Resource)]
137struct BackgroundMotionVectorsPipeline {
138    bind_group_layout: BindGroupLayoutDescriptor,
139    fullscreen_shader: FullscreenShader,
140    fragment_shader: Handle<Shader>,
141}
142
143#[derive(PartialEq, Eq, Hash, Clone, Copy)]
144struct BackgroundMotionVectorsPipelineKey {
145    samples: u32,
146    normal_prepass: bool,
147}
148
149fn init_background_motion_vectors_pipeline(
150    mut commands: Commands,
151    fullscreen_shader: Res<FullscreenShader>,
152    asset_server: Res<AssetServer>,
153) {
154    commands.insert_resource(BackgroundMotionVectorsPipeline {
155        bind_group_layout: BindGroupLayoutDescriptor::new(
156            "background_motion_vectors_bind_group_layout",
157            &BindGroupLayoutEntries::sequential(
158                ShaderStages::FRAGMENT,
159                (
160                    uniform_buffer::<ViewUniform>(true),
161                    uniform_buffer::<PreviousViewData>(true),
162                ),
163            ),
164        ),
165        fullscreen_shader: fullscreen_shader.clone(),
166        fragment_shader: load_embedded_asset!(
167            asset_server.as_ref(),
168            "background_motion_vectors.wgsl"
169        ),
170    });
171}
172
173impl SpecializedRenderPipeline for BackgroundMotionVectorsPipeline {
174    type Key = BackgroundMotionVectorsPipelineKey;
175
176    fn specialize(&self, key: Self::Key) -> RenderPipelineDescriptor {
177        let mut targets = prepass_target_descriptors(key.normal_prepass, true, false);
178        // The shader only outputs to attachment at location 1, set write mask of the other attachments to empty
179        // to avoid WebGPU validation error "Color target has no corresponding fragment stage output but writeMask is not zero".
180        for target in
181            targets
182                .iter_mut()
183                .enumerate()
184                .filter_map(|(i, t)| if i == 1 { None } else { t.as_mut() })
185        {
186            target.write_mask = bevy_render::render_resource::ColorWrites::empty();
187        }
188
189        RenderPipelineDescriptor {
190            label: Some("background_motion_vectors_pipeline".into()),
191            layout: vec![self.bind_group_layout.clone()],
192            vertex: self.fullscreen_shader.to_vertex_state(),
193            depth_stencil: Some(DepthStencilState {
194                format: CORE_3D_DEPTH_FORMAT,
195                depth_write_enabled: Some(false),
196                depth_compare: Some(CompareFunction::GreaterEqual),
197                stencil: default(),
198                bias: default(),
199            }),
200            multisample: MultisampleState {
201                count: key.samples,
202                mask: !0,
203                alpha_to_coverage_enabled: false,
204            },
205            fragment: Some(FragmentState {
206                shader: self.fragment_shader.clone(),
207                targets,
208                ..default()
209            }),
210            ..default()
211        }
212    }
213}
214
215fn prepare_background_motion_vectors_pipelines(
216    mut commands: Commands,
217    pipeline_cache: Res<PipelineCache>,
218    mut pipelines: ResMut<SpecializedRenderPipelines<BackgroundMotionVectorsPipeline>>,
219    pipeline: Res<BackgroundMotionVectorsPipeline>,
220    views: Query<
221        (Entity, Has<NormalPrepass>, &Msaa),
222        (
223            With<MotionVectorPrepass>,
224            Without<NoBackgroundMotionVectors>,
225        ),
226    >,
227) {
228    for (entity, normal_prepass, msaa) in &views {
229        let id = pipelines.specialize(
230            &pipeline_cache,
231            &pipeline,
232            BackgroundMotionVectorsPipelineKey {
233                samples: msaa.samples(),
234                normal_prepass,
235            },
236        );
237        commands
238            .entity(entity)
239            .insert(BackgroundMotionVectorsPipelineId(id));
240    }
241}
242
243fn prepare_background_motion_vectors_bind_groups(
244    mut commands: Commands,
245    pipeline: Res<BackgroundMotionVectorsPipeline>,
246    view_uniforms: Res<ViewUniforms>,
247    prev_view_uniforms: Res<PreviousViewUniforms>,
248    render_device: Res<RenderDevice>,
249    pipeline_cache: Res<PipelineCache>,
250    views: Query<
251        Entity,
252        (
253            With<MotionVectorPrepass>,
254            Without<NoBackgroundMotionVectors>,
255        ),
256    >,
257) {
258    for entity in &views {
259        let (Some(view_binding), Some(prev_view_binding)) = (
260            view_uniforms.uniforms.binding(),
261            prev_view_uniforms.uniforms.binding(),
262        ) else {
263            continue;
264        };
265        let bind_group = render_device.create_bind_group(
266            "background_motion_vectors_bind_group",
267            &pipeline_cache.get_bind_group_layout(&pipeline.bind_group_layout),
268            &BindGroupEntries::sequential((view_binding, prev_view_binding)),
269        );
270        commands
271            .entity(entity)
272            .insert(BackgroundMotionVectorsBindGroup(bind_group));
273    }
274}