This commit is contained in:
Emerson Coskey 2025-07-18 10:33:44 +08:00 committed by GitHub
commit 06520b3cd6
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 142 additions and 102 deletions

View File

@ -1,8 +1,11 @@
use core::ops::Deref;
use crate::FullscreenShader;
use super::{Bloom, BLOOM_TEXTURE_FORMAT};
use bevy_asset::{load_embedded_asset, AssetServer, Handle};
use bevy_asset::{load_embedded_asset, AssetServer};
use bevy_ecs::{
error::BevyError,
prelude::{Component, Entity},
resource::Resource,
system::{Commands, Query, Res, ResMut},
@ -28,14 +31,13 @@ pub struct BloomDownsamplingPipeline {
/// Layout with a texture, a sampler, and uniforms
pub bind_group_layout: BindGroupLayout,
pub sampler: Sampler,
/// The asset handle for the fullscreen vertex shader.
pub fullscreen_shader: FullscreenShader,
/// The fragment shader asset handle.
pub fragment_shader: Handle<Shader>,
pub variants: SpecializedCache<RenderPipeline, BloomDownsamplingSpecializer>,
}
#[derive(PartialEq, Eq, Hash, Clone)]
pub struct BloomDownsamplingPipelineKeys {
pub struct BloomDownsamplingSpecializer;
#[derive(PartialEq, Eq, Hash, Clone, SpecializerKey)]
pub struct BloomDownsamplingKey {
prefilter: bool,
first_downsample: bool,
uniform_scale: bool,
@ -83,27 +85,54 @@ pub fn init_bloom_downsampling_pipeline(
..Default::default()
});
let fragment_shader = load_embedded_asset!(asset_server.deref(), "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 variants = SpecializedCache::new(BloomDownsamplingSpecializer, None, base_descriptor);
commands.insert_resource(BloomDownsamplingPipeline {
bind_group_layout,
sampler,
fullscreen_shader: fullscreen_shader.clone(),
fragment_shader: load_embedded_asset!(asset_server.as_ref(), "bloom.wgsl"),
variants,
});
}
impl SpecializedRenderPipeline for BloomDownsamplingPipeline {
type Key = BloomDownsamplingPipelineKeys;
impl Specializer<RenderPipeline> for BloomDownsamplingSpecializer {
type Key = BloomDownsamplingKey;
fn specialize(&self, key: Self::Key) -> RenderPipelineDescriptor {
let layout = vec![self.bind_group_layout.clone()];
fn specialize(
&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_mut()?;
fragment.entry_point = Some(if key.first_downsample {
"downsample_first".into()
} else {
"downsample".into()
};
});
let mut shader_defs = vec![];
let shader_defs = &mut fragment.shader_defs;
if key.first_downsample {
shader_defs.push("FIRST_DOWNSAMPLE".into());
@ -117,61 +146,36 @@ impl SpecializedRenderPipeline for BloomDownsamplingPipeline {
shader_defs.push("UNIFORM_SCALE".into());
}
RenderPipelineDescriptor {
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()
}
Ok(key)
}
}
pub fn prepare_downsampling_pipeline(
mut commands: Commands,
pipeline_cache: Res<PipelineCache>,
mut pipelines: ResMut<SpecializedRenderPipelines<BloomDownsamplingPipeline>>,
pipeline: Res<BloomDownsamplingPipeline>,
mut pipeline: ResMut<BloomDownsamplingPipeline>,
views: Query<(Entity, &Bloom)>,
) {
) -> Result<(), BevyError> {
for (entity, bloom) in &views {
let prefilter = bloom.prefilter.threshold > 0.0;
let pipeline_id = pipelines.specialize(
let pipeline_id = pipeline.variants.specialize(
&pipeline_cache,
&pipeline,
BloomDownsamplingPipelineKeys {
BloomDownsamplingKey {
prefilter,
first_downsample: false,
uniform_scale: bloom.scale == Vec2::ONE,
},
);
)?;
let pipeline_first_id = pipelines.specialize(
let pipeline_first_id = pipeline.variants.specialize(
&pipeline_cache,
&pipeline,
BloomDownsamplingPipelineKeys {
BloomDownsamplingKey {
prefilter,
first_downsample: true,
uniform_scale: bloom.scale == Vec2::ONE,
},
);
)?;
commands
.entity(entity)
@ -180,4 +184,5 @@ pub fn prepare_downsampling_pipeline(
main: pipeline_id,
});
}
Ok(())
}

View File

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

View File

@ -1,10 +1,13 @@
use core::ops::Deref;
use crate::FullscreenShader;
use super::{
downsampling_pipeline::BloomUniforms, Bloom, BloomCompositeMode, BLOOM_TEXTURE_FORMAT,
};
use bevy_asset::{load_embedded_asset, AssetServer, Handle};
use bevy_asset::{load_embedded_asset, AssetServer};
use bevy_ecs::{
error::BevyError,
prelude::{Component, Entity},
resource::Resource,
system::{Commands, Query, Res, ResMut},
@ -28,16 +31,7 @@ pub struct UpsamplingPipelineIds {
#[derive(Resource)]
pub struct BloomUpsamplingPipeline {
pub bind_group_layout: BindGroupLayout,
/// The asset handle for the fullscreen vertex shader.
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,
pub variants: SpecializedCache<RenderPipeline, BloomUpsamplingSpecializer>,
}
pub fn init_bloom_upscaling_pipeline(
@ -61,17 +55,44 @@ pub fn init_bloom_upscaling_pipeline(
),
);
let fragment_shader = load_embedded_asset!(asset_server.deref(), "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()),
..default()
}),
..default()
};
let specialized_cache =
SpecializedCache::new(BloomUpsamplingSpecializer, None, base_descriptor);
commands.insert_resource(BloomUpsamplingPipeline {
bind_group_layout,
fullscreen_shader: fullscreen_shader.clone(),
fragment_shader: load_embedded_asset!(asset_server.as_ref(), "bloom.wgsl"),
variants: specialized_cache,
});
}
impl SpecializedRenderPipeline for BloomUpsamplingPipeline {
type Key = BloomUpsamplingPipelineKeys;
pub struct BloomUpsamplingSpecializer;
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 {
ViewTarget::TEXTURE_FORMAT_HDR
} else {
@ -110,61 +131,52 @@ impl SpecializedRenderPipeline for BloomUpsamplingPipeline {
},
};
RenderPipelineDescriptor {
label: Some("bloom_upsampling_pipeline".into()),
layout: vec![self.bind_group_layout.clone()],
vertex: self.fullscreen_shader.to_vertex_state(),
fragment: Some(FragmentState {
shader: self.fragment_shader.clone(),
entry_point: Some("upsample".into()),
targets: vec![Some(ColorTargetState {
format: texture_format,
blend: Some(BlendState {
color: color_blend,
alpha: BlendComponent {
src_factor: BlendFactor::Zero,
dst_factor: BlendFactor::One,
operation: BlendOperation::Add,
},
}),
write_mask: ColorWrites::ALL,
})],
..default()
let target = ColorTargetState {
format: texture_format,
blend: Some(BlendState {
color: color_blend,
alpha: BlendComponent {
src_factor: BlendFactor::Zero,
dst_factor: BlendFactor::One,
operation: BlendOperation::Add,
},
}),
..default()
}
write_mask: ColorWrites::ALL,
};
descriptor.fragment_mut()?.set_target(0, target);
Ok(key)
}
}
pub fn prepare_upsampling_pipeline(
mut commands: Commands,
pipeline_cache: Res<PipelineCache>,
mut pipelines: ResMut<SpecializedRenderPipelines<BloomUpsamplingPipeline>>,
pipeline: Res<BloomUpsamplingPipeline>,
mut pipeline: ResMut<BloomUpsamplingPipeline>,
views: Query<(Entity, &Bloom)>,
) {
) -> Result<(), BevyError> {
for (entity, bloom) in &views {
let pipeline_id = pipelines.specialize(
let pipeline_id = pipeline.variants.specialize(
&pipeline_cache,
&pipeline,
BloomUpsamplingPipelineKeys {
BloomUpsamplingKey {
composite_mode: bloom.composite_mode,
final_pipeline: false,
},
);
)?;
let pipeline_final_id = pipelines.specialize(
let pipeline_final_id = pipeline.variants.specialize(
&pipeline_cache,
&pipeline,
BloomUpsamplingPipelineKeys {
BloomUpsamplingKey {
composite_mode: bloom.composite_mode,
final_pipeline: true,
},
);
)?;
commands.entity(entity).insert(UpsamplingPipelineIds {
id_main: pipeline_id,
id_final: pipeline_final_id,
});
}
Ok(())
}

View File

@ -7,7 +7,8 @@ use crate::{
use alloc::borrow::Cow;
use bevy_asset::Handle;
use bevy_utils::WgpuWrapper;
use core::ops::Deref;
use core::{iter, ops::Deref};
use thiserror::Error;
use wgpu::{
ColorTargetState, DepthStencilState, MultisampleState, PrimitiveState, PushConstantRange,
};
@ -112,6 +113,16 @@ pub struct RenderPipelineDescriptor {
pub zero_initialize_workgroup_memory: bool,
}
#[derive(Copy, Clone, Debug, Error)]
#[error("RenderPipelineDescriptor has no FragmentState configured")]
pub struct NoFragmentStateError;
impl RenderPipelineDescriptor {
pub fn fragment_mut(&mut self) -> Result<&mut FragmentState, NoFragmentStateError> {
self.fragment.as_mut().ok_or(NoFragmentStateError)
}
}
#[derive(Clone, Debug, Eq, PartialEq, Default)]
pub struct VertexState {
/// The compiled shader module for this stage.
@ -137,6 +148,12 @@ pub struct FragmentState {
pub targets: Vec<Option<ColorTargetState>>,
}
impl FragmentState {
pub fn set_target(&mut self, index: usize, target: ColorTargetState) {
filling_set_at(&mut self.targets, index, None, Some(target));
}
}
/// Describes a compute pipeline.
#[derive(Clone, Debug, PartialEq, Eq, Default)]
pub struct ComputePipelineDescriptor {
@ -153,3 +170,11 @@ pub struct ComputePipelineDescriptor {
/// If this is false, reading from workgroup variables before writing to them will result in garbage values.
pub zero_initialize_workgroup_memory: bool,
}
// utility function to set a value at the specified index, extending with
// a filler value if the index is out of bounds.
fn filling_set_at<T: Clone>(vec: &mut Vec<T>, index: usize, filler: T, value: T) {
let num_to_fill = (index + 1).saturating_sub(vec.len());
vec.extend(iter::repeat_n(filler, num_to_fill));
vec[index] = value;
}