bevy_post_process/motion_blur/
node.rs

1use bevy_ecs::{query::QueryItem, world::World};
2use bevy_render::{
3    diagnostic::RecordDiagnostics,
4    extract_component::ComponentUniforms,
5    globals::GlobalsBuffer,
6    render_graph::{NodeRunError, RenderGraphContext, ViewNode},
7    render_resource::{
8        BindGroupEntries, Operations, PipelineCache, RenderPassColorAttachment,
9        RenderPassDescriptor,
10    },
11    renderer::RenderContext,
12    view::{Msaa, ViewTarget},
13};
14
15use bevy_core_pipeline::prepass::ViewPrepassTextures;
16
17use super::{
18    pipeline::{MotionBlurPipeline, MotionBlurPipelineId},
19    MotionBlurUniform,
20};
21
22#[derive(Default)]
23pub struct MotionBlurNode;
24
25impl ViewNode for MotionBlurNode {
26    type ViewQuery = (
27        &'static ViewTarget,
28        &'static MotionBlurPipelineId,
29        &'static ViewPrepassTextures,
30        &'static MotionBlurUniform,
31        &'static Msaa,
32    );
33    fn run(
34        &self,
35        _graph: &mut RenderGraphContext,
36        render_context: &mut RenderContext,
37        (view_target, pipeline_id, prepass_textures, motion_blur, msaa): QueryItem<Self::ViewQuery>,
38        world: &World,
39    ) -> Result<(), NodeRunError> {
40        if motion_blur.samples == 0 || motion_blur.shutter_angle <= 0.0 {
41            return Ok(()); // We can skip running motion blur in these cases.
42        }
43
44        let motion_blur_pipeline = world.resource::<MotionBlurPipeline>();
45        let pipeline_cache = world.resource::<PipelineCache>();
46        let settings_uniforms = world.resource::<ComponentUniforms<MotionBlurUniform>>();
47        let Some(pipeline) = pipeline_cache.get_render_pipeline(pipeline_id.0) else {
48            return Ok(());
49        };
50
51        let Some(settings_binding) = settings_uniforms.uniforms().binding() else {
52            return Ok(());
53        };
54        let (Some(prepass_motion_vectors_texture), Some(prepass_depth_texture)) =
55            (&prepass_textures.motion_vectors, &prepass_textures.depth)
56        else {
57            return Ok(());
58        };
59        let Some(globals_uniforms) = world.resource::<GlobalsBuffer>().buffer.binding() else {
60            return Ok(());
61        };
62
63        let diagnostics = render_context.diagnostic_recorder();
64
65        let post_process = view_target.post_process_write();
66
67        let layout = if msaa.samples() == 1 {
68            &motion_blur_pipeline.layout
69        } else {
70            &motion_blur_pipeline.layout_msaa
71        };
72
73        let bind_group = render_context.render_device().create_bind_group(
74            Some("motion_blur_bind_group"),
75            layout,
76            &BindGroupEntries::sequential((
77                post_process.source,
78                &prepass_motion_vectors_texture.texture.default_view,
79                &prepass_depth_texture.texture.default_view,
80                &motion_blur_pipeline.sampler,
81                settings_binding.clone(),
82                globals_uniforms.clone(),
83            )),
84        );
85
86        let mut render_pass = render_context.begin_tracked_render_pass(RenderPassDescriptor {
87            label: Some("motion_blur"),
88            color_attachments: &[Some(RenderPassColorAttachment {
89                view: post_process.destination,
90                depth_slice: None,
91                resolve_target: None,
92                ops: Operations::default(),
93            })],
94            depth_stencil_attachment: None,
95            timestamp_writes: None,
96            occlusion_query_set: None,
97        });
98        let pass_span = diagnostics.pass_span(&mut render_pass, "motion_blur");
99
100        render_pass.set_render_pipeline(pipeline);
101        render_pass.set_bind_group(0, &bind_group, &[]);
102        render_pass.draw(0..3, 0..1);
103
104        pass_span.end(&mut render_pass);
105
106        Ok(())
107    }
108}