migrate bloom

This commit is contained in:
Emerson Coskey 2025-07-05 11:07:34 -07:00
parent b01de70bdd
commit b3929ef35b
No known key found for this signature in database
3 changed files with 125 additions and 99 deletions

View File

@ -3,6 +3,7 @@ use crate::FullscreenShader;
use super::{Bloom, BLOOM_TEXTURE_FORMAT}; use super::{Bloom, BLOOM_TEXTURE_FORMAT};
use bevy_asset::{load_embedded_asset, Handle}; use bevy_asset::{load_embedded_asset, Handle};
use bevy_ecs::{ use bevy_ecs::{
error::BevyError,
prelude::{Component, Entity}, prelude::{Component, Entity},
resource::Resource, resource::Resource,
system::{Commands, Query, Res, ResMut}, system::{Commands, Query, Res, ResMut},
@ -29,14 +30,13 @@ pub struct BloomDownsamplingPipeline {
/// Layout with a texture, a sampler, and uniforms /// Layout with a texture, a sampler, and uniforms
pub bind_group_layout: BindGroupLayout, pub bind_group_layout: BindGroupLayout,
pub sampler: Sampler, pub sampler: Sampler,
/// The asset handle for the fullscreen vertex shader. pub specialized_cache: SpecializedCache<RenderPipeline, BloomDownsamplingSpecializer>,
pub fullscreen_shader: FullscreenShader,
/// The fragment shader asset handle.
pub fragment_shader: Handle<Shader>,
} }
#[derive(PartialEq, Eq, Hash, Clone)] pub struct BloomDownsamplingSpecializer;
pub struct BloomDownsamplingPipelineKeys {
#[derive(PartialEq, Eq, Hash, Clone, SpecializerKey)]
pub struct BloomDownsamplingKey {
prefilter: bool, prefilter: bool,
first_downsample: bool, first_downsample: bool,
uniform_scale: bool, uniform_scale: bool,
@ -82,28 +82,57 @@ impl FromWorld for BloomDownsamplingPipeline {
..Default::default() ..Default::default()
}); });
let fullscreen_shader = world.resource::<FullscreenShader>().clone();
let fragment_shader = load_embedded_asset!(world, "bloom.wgsl");
let base_descriptor = RenderPipelineDescriptor {
layout: vec![bind_group_layout.clone()],
vertex: fullscreen_shader.to_vertex_state(),
fragment: Some(FragmentState {
shader: fragment_shader.clone(),
targets: vec![Some(ColorTargetState {
format: BLOOM_TEXTURE_FORMAT,
blend: None,
write_mask: ColorWrites::ALL,
})],
..default()
}),
..default()
};
let specialized_cache =
SpecializedCache::new(BloomDownsamplingSpecializer, None, base_descriptor);
BloomDownsamplingPipeline { BloomDownsamplingPipeline {
bind_group_layout, bind_group_layout,
sampler, sampler,
fullscreen_shader: world.resource::<FullscreenShader>().clone(), specialized_cache,
fragment_shader: load_embedded_asset!(world, "bloom.wgsl"),
} }
} }
} }
impl SpecializedRenderPipeline for BloomDownsamplingPipeline { impl Specializer<RenderPipeline> for BloomDownsamplingSpecializer {
type Key = BloomDownsamplingPipelineKeys; type Key = BloomDownsamplingKey;
fn specialize(&self, key: Self::Key) -> RenderPipelineDescriptor { fn specialize(
let layout = vec![self.bind_group_layout.clone()]; &self,
key: Self::Key,
descriptor: &mut RenderPipelineDescriptor,
) -> Result<Canonical<Self::Key>, BevyError> {
descriptor.label = Some(if key.first_downsample {
"bloom_downsampling_pipeline_first".into()
} else {
"bloom_downsampling_pipeline".into()
});
let entry_point = if key.first_downsample { let fragment = descriptor.fragment.get_or_insert_default();
fragment.entry_point = Some(if key.first_downsample {
"downsample_first".into() "downsample_first".into()
} else { } else {
"downsample".into() "downsample".into()
}; });
let mut shader_defs = vec![]; let shader_defs = &mut fragment.shader_defs;
if key.first_downsample { if key.first_downsample {
shader_defs.push("FIRST_DOWNSAMPLE".into()); shader_defs.push("FIRST_DOWNSAMPLE".into());
@ -117,61 +146,36 @@ impl SpecializedRenderPipeline for BloomDownsamplingPipeline {
shader_defs.push("UNIFORM_SCALE".into()); shader_defs.push("UNIFORM_SCALE".into());
} }
RenderPipelineDescriptor { Ok(key)
label: Some(
if key.first_downsample {
"bloom_downsampling_pipeline_first"
} else {
"bloom_downsampling_pipeline"
}
.into(),
),
layout,
vertex: self.fullscreen_shader.to_vertex_state(),
fragment: Some(FragmentState {
shader: self.fragment_shader.clone(),
shader_defs,
entry_point: Some(entry_point),
targets: vec![Some(ColorTargetState {
format: BLOOM_TEXTURE_FORMAT,
blend: None,
write_mask: ColorWrites::ALL,
})],
}),
..default()
}
} }
} }
pub fn prepare_downsampling_pipeline( pub fn prepare_downsampling_pipeline(
mut commands: Commands, mut commands: Commands,
pipeline_cache: Res<PipelineCache>, pipeline_cache: Res<PipelineCache>,
mut pipelines: ResMut<SpecializedRenderPipelines<BloomDownsamplingPipeline>>, mut pipeline: ResMut<BloomDownsamplingPipeline>,
pipeline: Res<BloomDownsamplingPipeline>,
views: Query<(Entity, &Bloom)>, views: Query<(Entity, &Bloom)>,
) { ) -> Result<(), BevyError> {
for (entity, bloom) in &views { for (entity, bloom) in &views {
let prefilter = bloom.prefilter.threshold > 0.0; let prefilter = bloom.prefilter.threshold > 0.0;
let pipeline_id = pipelines.specialize( let pipeline_id = pipeline.specialized_cache.specialize(
&pipeline_cache, &pipeline_cache,
&pipeline, BloomDownsamplingKey {
BloomDownsamplingPipelineKeys {
prefilter, prefilter,
first_downsample: false, first_downsample: false,
uniform_scale: bloom.scale == Vec2::ONE, uniform_scale: bloom.scale == Vec2::ONE,
}, },
); )?;
let pipeline_first_id = pipelines.specialize( let pipeline_first_id = pipeline.specialized_cache.specialize(
&pipeline_cache, &pipeline_cache,
&pipeline, BloomDownsamplingKey {
BloomDownsamplingPipelineKeys {
prefilter, prefilter,
first_downsample: true, first_downsample: true,
uniform_scale: bloom.scale == Vec2::ONE, uniform_scale: bloom.scale == Vec2::ONE,
}, },
); )?;
commands commands
.entity(entity) .entity(entity)
@ -180,4 +184,5 @@ pub fn prepare_downsampling_pipeline(
main: pipeline_id, main: pipeline_id,
}); });
} }
Ok(())
} }

View File

@ -57,8 +57,6 @@ impl Plugin for BloomPlugin {
return; return;
}; };
render_app render_app
.init_resource::<SpecializedRenderPipelines<BloomDownsamplingPipeline>>()
.init_resource::<SpecializedRenderPipelines<BloomUpsamplingPipeline>>()
.add_systems( .add_systems(
Render, Render,
( (

View File

@ -5,6 +5,7 @@ use super::{
}; };
use bevy_asset::{load_embedded_asset, Handle}; use bevy_asset::{load_embedded_asset, Handle};
use bevy_ecs::{ use bevy_ecs::{
error::BevyError,
prelude::{Component, Entity}, prelude::{Component, Entity},
resource::Resource, resource::Resource,
system::{Commands, Query, Res, ResMut}, system::{Commands, Query, Res, ResMut},
@ -29,16 +30,7 @@ pub struct UpsamplingPipelineIds {
#[derive(Resource)] #[derive(Resource)]
pub struct BloomUpsamplingPipeline { pub struct BloomUpsamplingPipeline {
pub bind_group_layout: BindGroupLayout, pub bind_group_layout: BindGroupLayout,
/// The asset handle for the fullscreen vertex shader. pub specialized_cache: SpecializedCache<RenderPipeline, BloomUpsamplingSpecializer>,
pub fullscreen_shader: FullscreenShader,
/// The fragment shader asset handle.
pub fragment_shader: Handle<Shader>,
}
#[derive(PartialEq, Eq, Hash, Clone)]
pub struct BloomUpsamplingPipelineKeys {
composite_mode: BloomCompositeMode,
final_pipeline: bool,
} }
impl FromWorld for BloomUpsamplingPipeline { impl FromWorld for BloomUpsamplingPipeline {
@ -60,18 +52,63 @@ impl FromWorld for BloomUpsamplingPipeline {
), ),
); );
let fullscreen_shader = world.resource::<FullscreenShader>().clone();
let fragment_shader = load_embedded_asset!(world, "bloom.wgsl");
let base_descriptor = RenderPipelineDescriptor {
label: Some("bloom_upsampling_pipeline".into()),
layout: vec![bind_group_layout.clone()],
vertex: fullscreen_shader.to_vertex_state(),
fragment: Some(FragmentState {
shader: fragment_shader.clone(),
entry_point: Some("upsample".into()),
targets: vec![Some(ColorTargetState {
format: TextureFormat::Rgba8Unorm, // placeholder
blend: Some(BlendState {
// placeholder
color: BlendComponent {
src_factor: BlendFactor::Zero,
dst_factor: BlendFactor::One,
operation: BlendOperation::Add,
},
alpha: BlendComponent {
src_factor: BlendFactor::Zero,
dst_factor: BlendFactor::One,
operation: BlendOperation::Add,
},
}),
write_mask: ColorWrites::ALL,
})],
..default()
}),
..default()
};
let specialized_cache =
SpecializedCache::new(BloomUpsamplingSpecializer, None, base_descriptor);
BloomUpsamplingPipeline { BloomUpsamplingPipeline {
bind_group_layout, bind_group_layout,
fullscreen_shader: world.resource::<FullscreenShader>().clone(), specialized_cache,
fragment_shader: load_embedded_asset!(world, "bloom.wgsl"),
} }
} }
} }
impl SpecializedRenderPipeline for BloomUpsamplingPipeline { pub struct BloomUpsamplingSpecializer;
type Key = BloomUpsamplingPipelineKeys;
fn specialize(&self, key: Self::Key) -> RenderPipelineDescriptor { #[derive(PartialEq, Eq, Hash, Clone, SpecializerKey)]
pub struct BloomUpsamplingKey {
composite_mode: BloomCompositeMode,
final_pipeline: bool,
}
impl Specializer<RenderPipeline> for BloomUpsamplingSpecializer {
type Key = BloomUpsamplingKey;
fn specialize(
&self,
key: Self::Key,
descriptor: &mut RenderPipelineDescriptor,
) -> Result<Canonical<Self::Key>, BevyError> {
let texture_format = if key.final_pipeline { let texture_format = if key.final_pipeline {
ViewTarget::TEXTURE_FORMAT_HDR ViewTarget::TEXTURE_FORMAT_HDR
} else { } else {
@ -110,28 +147,16 @@ impl SpecializedRenderPipeline for BloomUpsamplingPipeline {
}, },
}; };
RenderPipelineDescriptor { let fragment = descriptor.fragment.get_or_insert_default();
label: Some("bloom_upsampling_pipeline".into()),
layout: vec![self.bind_group_layout.clone()], if let Some(Some(color_target)) = fragment.targets.first_mut()
vertex: self.fullscreen_shader.to_vertex_state(), && let Some(blend_state) = &mut color_target.blend
fragment: Some(FragmentState { {
shader: self.fragment_shader.clone(), blend_state.color = color_blend;
entry_point: Some("upsample".into()), color_target.format = texture_format;
targets: vec![Some(ColorTargetState { Ok(key)
format: texture_format, } else {
blend: Some(BlendState { Err("color target state or blend state missing".into())
color: color_blend,
alpha: BlendComponent {
src_factor: BlendFactor::Zero,
dst_factor: BlendFactor::One,
operation: BlendOperation::Add,
},
}),
write_mask: ColorWrites::ALL,
})],
..default()
}),
..default()
} }
} }
} }
@ -139,32 +164,30 @@ impl SpecializedRenderPipeline for BloomUpsamplingPipeline {
pub fn prepare_upsampling_pipeline( pub fn prepare_upsampling_pipeline(
mut commands: Commands, mut commands: Commands,
pipeline_cache: Res<PipelineCache>, pipeline_cache: Res<PipelineCache>,
mut pipelines: ResMut<SpecializedRenderPipelines<BloomUpsamplingPipeline>>, mut pipeline: ResMut<BloomUpsamplingPipeline>,
pipeline: Res<BloomUpsamplingPipeline>,
views: Query<(Entity, &Bloom)>, views: Query<(Entity, &Bloom)>,
) { ) -> Result<(), BevyError> {
for (entity, bloom) in &views { for (entity, bloom) in &views {
let pipeline_id = pipelines.specialize( let pipeline_id = pipeline.specialized_cache.specialize(
&pipeline_cache, &pipeline_cache,
&pipeline, BloomUpsamplingKey {
BloomUpsamplingPipelineKeys {
composite_mode: bloom.composite_mode, composite_mode: bloom.composite_mode,
final_pipeline: false, final_pipeline: false,
}, },
); )?;
let pipeline_final_id = pipelines.specialize( let pipeline_final_id = pipeline.specialized_cache.specialize(
&pipeline_cache, &pipeline_cache,
&pipeline, BloomUpsamplingKey {
BloomUpsamplingPipelineKeys {
composite_mode: bloom.composite_mode, composite_mode: bloom.composite_mode,
final_pipeline: true, final_pipeline: true,
}, },
); )?;
commands.entity(entity).insert(UpsamplingPipelineIds { commands.entity(entity).insert(UpsamplingPipelineIds {
id_main: pipeline_id, id_main: pipeline_id,
id_final: pipeline_final_id, id_final: pipeline_final_id,
}); });
} }
Ok(())
} }