diff --git a/crates/bevy_core_pipeline/src/bloom/downsampling_pipeline.rs b/crates/bevy_core_pipeline/src/bloom/downsampling_pipeline.rs index aa8d3d37afc3e..fa50a47c9c0ea 100644 --- a/crates/bevy_core_pipeline/src/bloom/downsampling_pipeline.rs +++ b/crates/bevy_core_pipeline/src/bloom/downsampling_pipeline.rs @@ -1,8 +1,9 @@ use crate::FullscreenShader; use super::{Bloom, BLOOM_TEXTURE_FORMAT}; -use bevy_asset::{load_embedded_asset, Handle}; +use bevy_asset::load_embedded_asset; use bevy_ecs::{ + error::BevyError, prelude::{Component, Entity}, resource::Resource, system::{Commands, Query, Res, ResMut}, @@ -29,14 +30,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, + pub specialized_cache: SpecializedCache, } -#[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, @@ -82,28 +82,57 @@ impl FromWorld for BloomDownsamplingPipeline { ..Default::default() }); + let fullscreen_shader = world.resource::().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 { bind_group_layout, sampler, - fullscreen_shader: world.resource::().clone(), - fragment_shader: load_embedded_asset!(world, "bloom.wgsl"), + specialized_cache, } } } -impl SpecializedRenderPipeline for BloomDownsamplingPipeline { - type Key = BloomDownsamplingPipelineKeys; +impl Specializer for BloomDownsamplingSpecializer { + type Key = BloomDownsamplingKey; + + fn specialize( + &self, + key: Self::Key, + descriptor: &mut RenderPipelineDescriptor, + ) -> Result, BevyError> { + descriptor.label = Some(if key.first_downsample { + "bloom_downsampling_pipeline_first".into() + } else { + "bloom_downsampling_pipeline".into() + }); - fn specialize(&self, key: Self::Key) -> RenderPipelineDescriptor { - let layout = vec![self.bind_group_layout.clone()]; + let fragment = descriptor.fragment_mut()?; - let entry_point = if key.first_downsample { + 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, - mut pipelines: ResMut>, - pipeline: Res, + mut pipeline: ResMut, 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.specialized_cache.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.specialized_cache.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(()) } diff --git a/crates/bevy_core_pipeline/src/bloom/mod.rs b/crates/bevy_core_pipeline/src/bloom/mod.rs index d57af1cd018b9..6946b1f417595 100644 --- a/crates/bevy_core_pipeline/src/bloom/mod.rs +++ b/crates/bevy_core_pipeline/src/bloom/mod.rs @@ -57,8 +57,6 @@ impl Plugin for BloomPlugin { return; }; render_app - .init_resource::>() - .init_resource::>() .add_systems( Render, ( diff --git a/crates/bevy_core_pipeline/src/bloom/upsampling_pipeline.rs b/crates/bevy_core_pipeline/src/bloom/upsampling_pipeline.rs index 4a5c4d50f946d..8f25e88cf9ab2 100644 --- a/crates/bevy_core_pipeline/src/bloom/upsampling_pipeline.rs +++ b/crates/bevy_core_pipeline/src/bloom/upsampling_pipeline.rs @@ -3,8 +3,9 @@ use crate::FullscreenShader; use super::{ downsampling_pipeline::BloomUniforms, Bloom, BloomCompositeMode, BLOOM_TEXTURE_FORMAT, }; -use bevy_asset::{load_embedded_asset, Handle}; +use bevy_asset::load_embedded_asset; use bevy_ecs::{ + error::BevyError, prelude::{Component, Entity}, resource::Resource, system::{Commands, Query, Res, ResMut}, @@ -29,16 +30,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, -} - -#[derive(PartialEq, Eq, Hash, Clone)] -pub struct BloomUpsamplingPipelineKeys { - composite_mode: BloomCompositeMode, - final_pipeline: bool, + pub specialized_cache: SpecializedCache, } impl FromWorld for BloomUpsamplingPipeline { @@ -60,18 +52,46 @@ impl FromWorld for BloomUpsamplingPipeline { ), ); + let fullscreen_shader = world.resource::().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()), + ..default() + }), + ..default() + }; + + let specialized_cache = + SpecializedCache::new(BloomUpsamplingSpecializer, None, base_descriptor); + BloomUpsamplingPipeline { bind_group_layout, - fullscreen_shader: world.resource::().clone(), - fragment_shader: load_embedded_asset!(world, "bloom.wgsl"), + specialized_cache, } } } -impl SpecializedRenderPipeline for BloomUpsamplingPipeline { - type Key = BloomUpsamplingPipelineKeys; +pub struct BloomUpsamplingSpecializer; + +#[derive(PartialEq, Eq, Hash, Clone, SpecializerKey)] +pub struct BloomUpsamplingKey { + composite_mode: BloomCompositeMode, + final_pipeline: bool, +} + +impl Specializer for BloomUpsamplingSpecializer { + type Key = BloomUpsamplingKey; - fn specialize(&self, key: Self::Key) -> RenderPipelineDescriptor { + fn specialize( + &self, + key: Self::Key, + descriptor: &mut RenderPipelineDescriptor, + ) -> Result, BevyError> { let texture_format = if key.final_pipeline { ViewTarget::TEXTURE_FORMAT_HDR } else { @@ -110,61 +130,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, - mut pipelines: ResMut>, - pipeline: Res, + mut pipeline: ResMut, views: Query<(Entity, &Bloom)>, -) { +) -> Result<(), BevyError> { for (entity, bloom) in &views { - let pipeline_id = pipelines.specialize( + let pipeline_id = pipeline.specialized_cache.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.specialized_cache.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(()) } diff --git a/crates/bevy_render/src/render_resource/pipeline.rs b/crates/bevy_render/src/render_resource/pipeline.rs index e94cf27cd32c8..d56f74e882c89 100644 --- a/crates/bevy_render/src/render_resource/pipeline.rs +++ b/crates/bevy_render/src/render_resource/pipeline.rs @@ -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>, } +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(vec: &mut Vec, index: usize, filler: T, value: T) { + let num_to_fill = index.saturating_sub(vec.len() - 1); + vec.extend(iter::repeat_n(filler, num_to_fill)); + vec[index] = value; +}