Remove WebGL padding from MotionBlur (#18727)

## Objective

The `MotionBlur` component exposes renderer internals. Users shouldn't
have to deal with this.

```rust
MotionBlur {
    shutter_angle: 1.0,
    samples: 2,
    #[cfg(all(feature = "webgl2", target_arch = "wasm32", not(feature = "webgpu")))]
    _webgl2_padding: Default::default(),
},
```

## Solution

The renderer now uses a separate `MotionBlurUniform` struct for its
internals. `MotionBlur` no longer needs padding.

I was a bit unsure about the name `MotionBlurUniform`. Other modules use
a mix of `Uniform` and `Uniforms`.

## Testing

```
cargo run --example motion_blur
```

Tested on Win10/Nvidia across Vulkan, WebGL/Chrome, WebGPU/Chrome.
This commit is contained in:
Greeble 2025-04-06 21:00:59 +01:00 committed by François Mockers
parent f3f4e80d87
commit 8f083307c3
4 changed files with 38 additions and 18 deletions

View File

@ -9,7 +9,10 @@ use crate::{
use bevy_app::{App, Plugin}; use bevy_app::{App, Plugin};
use bevy_asset::{load_internal_asset, weak_handle, Handle}; use bevy_asset::{load_internal_asset, weak_handle, Handle};
use bevy_ecs::{ use bevy_ecs::{
component::Component, query::With, reflect::ReflectComponent, schedule::IntoScheduleConfigs, component::Component,
query::{QueryItem, With},
reflect::ReflectComponent,
schedule::IntoScheduleConfigs,
}; };
use bevy_reflect::{std_traits::ReflectDefault, Reflect}; use bevy_reflect::{std_traits::ReflectDefault, Reflect};
use bevy_render::{ use bevy_render::{
@ -53,9 +56,8 @@ pub mod pipeline;
/// )); /// ));
/// # } /// # }
/// ```` /// ````
#[derive(Reflect, Component, Clone, ExtractComponent, ShaderType)] #[derive(Reflect, Component, Clone)]
#[reflect(Component, Default, Clone)] #[reflect(Component, Default, Clone)]
#[extract_component_filter(With<Camera>)]
#[require(DepthPrepass, MotionVectorPrepass)] #[require(DepthPrepass, MotionVectorPrepass)]
pub struct MotionBlur { pub struct MotionBlur {
/// The strength of motion blur from `0.0` to `1.0`. /// The strength of motion blur from `0.0` to `1.0`.
@ -88,9 +90,6 @@ pub struct MotionBlur {
/// Setting this to `3` will result in `3 * 2 + 1 = 7` samples. Setting this to `0` is /// Setting this to `3` will result in `3 * 2 + 1 = 7` samples. Setting this to `0` is
/// equivalent to disabling motion blur. /// equivalent to disabling motion blur.
pub samples: u32, pub samples: u32,
#[cfg(all(feature = "webgl", target_arch = "wasm32", not(feature = "webgpu")))]
// WebGL2 structs must be 16 byte aligned.
pub _webgl2_padding: bevy_math::Vec2,
} }
impl Default for MotionBlur { impl Default for MotionBlur {
@ -98,12 +97,35 @@ impl Default for MotionBlur {
Self { Self {
shutter_angle: 0.5, shutter_angle: 0.5,
samples: 1, samples: 1,
#[cfg(all(feature = "webgl", target_arch = "wasm32", not(feature = "webgpu")))]
_webgl2_padding: Default::default(),
} }
} }
} }
impl ExtractComponent for MotionBlur {
type QueryData = &'static Self;
type QueryFilter = With<Camera>;
type Out = MotionBlurUniform;
fn extract_component(item: QueryItem<Self::QueryData>) -> Option<Self::Out> {
Some(MotionBlurUniform {
shutter_angle: item.shutter_angle,
samples: item.samples,
#[cfg(all(feature = "webgl", target_arch = "wasm32", not(feature = "webgpu")))]
_webgl2_padding: Default::default(),
})
}
}
#[doc(hidden)]
#[derive(Component, ShaderType, Clone)]
pub struct MotionBlurUniform {
shutter_angle: f32,
samples: u32,
#[cfg(all(feature = "webgl", target_arch = "wasm32", not(feature = "webgpu")))]
// WebGL2 structs must be 16 byte aligned.
_webgl2_padding: bevy_math::Vec2,
}
pub const MOTION_BLUR_SHADER_HANDLE: Handle<Shader> = pub const MOTION_BLUR_SHADER_HANDLE: Handle<Shader> =
weak_handle!("d9ca74af-fa0a-4f11-b0f2-19613b618b93"); weak_handle!("d9ca74af-fa0a-4f11-b0f2-19613b618b93");
@ -119,7 +141,7 @@ impl Plugin for MotionBlurPlugin {
); );
app.add_plugins(( app.add_plugins((
ExtractComponentPlugin::<MotionBlur>::default(), ExtractComponentPlugin::<MotionBlur>::default(),
UniformComponentPlugin::<MotionBlur>::default(), UniformComponentPlugin::<MotionBlurUniform>::default(),
)); ));
let Some(render_app) = app.get_sub_app_mut(RenderApp) else { let Some(render_app) = app.get_sub_app_mut(RenderApp) else {

View File

@ -15,7 +15,7 @@ use crate::prepass::ViewPrepassTextures;
use super::{ use super::{
pipeline::{MotionBlurPipeline, MotionBlurPipelineId}, pipeline::{MotionBlurPipeline, MotionBlurPipelineId},
MotionBlur, MotionBlurUniform,
}; };
#[derive(Default)] #[derive(Default)]
@ -26,7 +26,7 @@ impl ViewNode for MotionBlurNode {
&'static ViewTarget, &'static ViewTarget,
&'static MotionBlurPipelineId, &'static MotionBlurPipelineId,
&'static ViewPrepassTextures, &'static ViewPrepassTextures,
&'static MotionBlur, &'static MotionBlurUniform,
&'static Msaa, &'static Msaa,
); );
fn run( fn run(
@ -42,7 +42,7 @@ impl ViewNode for MotionBlurNode {
let motion_blur_pipeline = world.resource::<MotionBlurPipeline>(); let motion_blur_pipeline = world.resource::<MotionBlurPipeline>();
let pipeline_cache = world.resource::<PipelineCache>(); let pipeline_cache = world.resource::<PipelineCache>();
let settings_uniforms = world.resource::<ComponentUniforms<MotionBlur>>(); let settings_uniforms = world.resource::<ComponentUniforms<MotionBlurUniform>>();
let Some(pipeline) = pipeline_cache.get_render_pipeline(pipeline_id.0) else { let Some(pipeline) = pipeline_cache.get_render_pipeline(pipeline_id.0) else {
return Ok(()); return Ok(());
}; };

View File

@ -26,7 +26,7 @@ use bevy_render::{
use crate::fullscreen_vertex_shader::fullscreen_shader_vertex_state; use crate::fullscreen_vertex_shader::fullscreen_shader_vertex_state;
use super::{MotionBlur, MOTION_BLUR_SHADER_HANDLE}; use super::{MotionBlurUniform, MOTION_BLUR_SHADER_HANDLE};
#[derive(Resource)] #[derive(Resource)]
pub struct MotionBlurPipeline { pub struct MotionBlurPipeline {
@ -49,7 +49,7 @@ impl MotionBlurPipeline {
// Linear Sampler // Linear Sampler
sampler(SamplerBindingType::Filtering), sampler(SamplerBindingType::Filtering),
// Motion blur settings uniform input // Motion blur settings uniform input
uniform_buffer_sized(false, Some(MotionBlur::min_size())), uniform_buffer_sized(false, Some(MotionBlurUniform::min_size())),
// Globals uniform input // Globals uniform input
uniform_buffer_sized(false, Some(GlobalsUniform::min_size())), uniform_buffer_sized(false, Some(GlobalsUniform::min_size())),
), ),
@ -67,7 +67,7 @@ impl MotionBlurPipeline {
// Linear Sampler // Linear Sampler
sampler(SamplerBindingType::Filtering), sampler(SamplerBindingType::Filtering),
// Motion blur settings uniform input // Motion blur settings uniform input
uniform_buffer_sized(false, Some(MotionBlur::min_size())), uniform_buffer_sized(false, Some(MotionBlurUniform::min_size())),
// Globals uniform input // Globals uniform input
uniform_buffer_sized(false, Some(GlobalsUniform::min_size())), uniform_buffer_sized(false, Some(GlobalsUniform::min_size())),
), ),
@ -155,7 +155,7 @@ pub(crate) fn prepare_motion_blur_pipelines(
pipeline_cache: Res<PipelineCache>, pipeline_cache: Res<PipelineCache>,
mut pipelines: ResMut<SpecializedRenderPipelines<MotionBlurPipeline>>, mut pipelines: ResMut<SpecializedRenderPipelines<MotionBlurPipeline>>,
pipeline: Res<MotionBlurPipeline>, pipeline: Res<MotionBlurPipeline>,
views: Query<(Entity, &ExtractedView, &Msaa), With<MotionBlur>>, views: Query<(Entity, &ExtractedView, &Msaa), With<MotionBlurUniform>>,
) { ) {
for (entity, view, msaa) in &views { for (entity, view, msaa) in &views {
let pipeline_id = pipelines.specialize( let pipeline_id = pipelines.specialize(

View File

@ -26,8 +26,6 @@ fn setup_camera(mut commands: Commands) {
MotionBlur { MotionBlur {
shutter_angle: 1.0, shutter_angle: 1.0,
samples: 2, samples: 2,
#[cfg(all(feature = "webgl2", target_arch = "wasm32", not(feature = "webgpu")))]
_webgl2_padding: Default::default(),
}, },
// MSAA and Motion Blur together are not compatible on WebGL // MSAA and Motion Blur together are not compatible on WebGL
#[cfg(all(feature = "webgl2", target_arch = "wasm32", not(feature = "webgpu")))] #[cfg(all(feature = "webgl2", target_arch = "wasm32", not(feature = "webgpu")))]