bevy_post_process/motion_blur/
pipeline.rs1use bevy_asset::{load_embedded_asset, AssetServer, Handle};
2use bevy_core_pipeline::FullscreenShader;
3use bevy_ecs::{
4 component::Component,
5 entity::Entity,
6 query::With,
7 resource::Resource,
8 system::{Commands, Query, Res, ResMut},
9};
10use bevy_render::{
11 globals::GlobalsUniform,
12 render_resource::{
13 binding_types::{sampler, texture_2d, texture_2d_multisampled, uniform_buffer_sized},
14 BindGroupLayoutDescriptor, BindGroupLayoutEntries, CachedRenderPipelineId,
15 ColorTargetState, ColorWrites, FilterMode, FragmentState, PipelineCache,
16 RenderPipelineDescriptor, Sampler, SamplerBindingType, SamplerDescriptor, ShaderStages,
17 ShaderType, SpecializedRenderPipeline, SpecializedRenderPipelines, TextureFormat,
18 TextureSampleType,
19 },
20 renderer::RenderDevice,
21 view::{ExtractedView, Msaa},
22};
23use bevy_shader::{Shader, ShaderDefVal};
24use bevy_utils::default;
25
26use super::MotionBlurUniform;
27
28#[derive(Resource)]
29pub struct MotionBlurPipeline {
30 pub(crate) sampler: Sampler,
31 pub(crate) layout: BindGroupLayoutDescriptor,
32 pub(crate) layout_msaa: BindGroupLayoutDescriptor,
33 pub(crate) fullscreen_shader: FullscreenShader,
34 pub(crate) fragment_shader: Handle<Shader>,
35}
36
37impl MotionBlurPipeline {
38 pub(crate) fn new(
39 render_device: &RenderDevice,
40 fullscreen_shader: FullscreenShader,
41 fragment_shader: Handle<Shader>,
42 ) -> Self {
43 let mb_layout = &BindGroupLayoutEntries::sequential(
44 ShaderStages::FRAGMENT,
45 (
46 texture_2d(TextureSampleType::Float { filterable: true }),
48 texture_2d(TextureSampleType::Float { filterable: true }),
50 texture_2d(TextureSampleType::Float { filterable: false }),
52 sampler(SamplerBindingType::Filtering),
54 uniform_buffer_sized(false, Some(MotionBlurUniform::min_size())),
56 uniform_buffer_sized(false, Some(GlobalsUniform::min_size())),
58 ),
59 );
60
61 let mb_layout_msaa = &BindGroupLayoutEntries::sequential(
62 ShaderStages::FRAGMENT,
63 (
64 texture_2d(TextureSampleType::Float { filterable: true }),
66 texture_2d_multisampled(TextureSampleType::Float { filterable: false }),
68 texture_2d_multisampled(TextureSampleType::Float { filterable: false }),
70 sampler(SamplerBindingType::Filtering),
72 uniform_buffer_sized(false, Some(MotionBlurUniform::min_size())),
74 uniform_buffer_sized(false, Some(GlobalsUniform::min_size())),
76 ),
77 );
78
79 let sampler = render_device.create_sampler(&SamplerDescriptor {
80 mag_filter: FilterMode::Linear,
81 min_filter: FilterMode::Linear,
82 ..Default::default()
83 });
84 let layout = BindGroupLayoutDescriptor::new("motion_blur_layout", mb_layout);
85 let layout_msaa = BindGroupLayoutDescriptor::new("motion_blur_layout_msaa", mb_layout_msaa);
86
87 Self {
88 sampler,
89 layout,
90 layout_msaa,
91 fullscreen_shader,
92 fragment_shader,
93 }
94 }
95}
96
97pub fn init_motion_blur_pipeline(
98 mut commands: Commands,
99 render_device: Res<RenderDevice>,
100 fullscreen_shader: Res<FullscreenShader>,
101 asset_server: Res<AssetServer>,
102) {
103 let fullscreen_shader = fullscreen_shader.clone();
104 let fragment_shader = load_embedded_asset!(asset_server.as_ref(), "motion_blur.wgsl");
105 commands.insert_resource(MotionBlurPipeline::new(
106 &render_device,
107 fullscreen_shader,
108 fragment_shader,
109 ));
110}
111
112#[derive(PartialEq, Eq, Hash, Clone, Copy)]
113pub struct MotionBlurPipelineKey {
114 target_format: TextureFormat,
115 samples: u32,
116}
117
118impl SpecializedRenderPipeline for MotionBlurPipeline {
119 type Key = MotionBlurPipelineKey;
120
121 fn specialize(&self, key: Self::Key) -> RenderPipelineDescriptor {
122 let layout = match key.samples {
123 1 => vec![self.layout.clone()],
124 _ => vec![self.layout_msaa.clone()],
125 };
126
127 let mut shader_defs = vec![];
128
129 if key.samples > 1 {
130 shader_defs.push(ShaderDefVal::from("MULTISAMPLED"));
131 }
132
133 #[cfg(all(feature = "webgl", target_arch = "wasm32", not(feature = "webgpu")))]
134 {
135 shader_defs.push("SIXTEEN_BYTE_ALIGNMENT".into());
136 }
137
138 RenderPipelineDescriptor {
139 label: Some("motion_blur_pipeline".into()),
140 layout,
141 vertex: self.fullscreen_shader.to_vertex_state(),
142 fragment: Some(FragmentState {
143 shader: self.fragment_shader.clone(),
144 shader_defs,
145 targets: vec![Some(ColorTargetState {
146 format: key.target_format,
147 blend: None,
148 write_mask: ColorWrites::ALL,
149 })],
150 ..default()
151 }),
152 ..default()
153 }
154 }
155}
156
157#[derive(Component)]
158pub struct MotionBlurPipelineId(pub CachedRenderPipelineId);
159
160pub(crate) fn prepare_motion_blur_pipelines(
161 mut commands: Commands,
162 pipeline_cache: Res<PipelineCache>,
163 mut pipelines: ResMut<SpecializedRenderPipelines<MotionBlurPipeline>>,
164 pipeline: Res<MotionBlurPipeline>,
165 cameras: Query<(Entity, &ExtractedView, &Msaa), With<MotionBlurUniform>>,
166) {
167 for (entity, view, msaa) in &cameras {
168 let pipeline_id = pipelines.specialize(
169 &pipeline_cache,
170 &pipeline,
171 MotionBlurPipelineKey {
172 target_format: view.target_format,
173 samples: msaa.samples(),
174 },
175 );
176
177 commands
178 .entity(entity)
179 .insert(MotionBlurPipelineId(pipeline_id));
180 }
181}