diff --git a/crates/bevy_core_pipeline/src/auto_exposure/mod.rs b/crates/bevy_core_pipeline/src/auto_exposure/mod.rs index 7a33df99d897d..67347d80ba95b 100644 --- a/crates/bevy_core_pipeline/src/auto_exposure/mod.rs +++ b/crates/bevy_core_pipeline/src/auto_exposure/mod.rs @@ -5,9 +5,7 @@ use bevy_render::{ extract_component::ExtractComponentPlugin, render_asset::RenderAssetPlugin, render_graph::RenderGraphExt, - render_resource::{ - Buffer, BufferDescriptor, BufferUsages, PipelineCache, SpecializedComputePipelines, - }, + render_resource::{Buffer, BufferDescriptor, BufferUsages, PipelineCache}, renderer::RenderDevice, ExtractSchedule, Render, RenderApp, RenderSystems, }; @@ -59,7 +57,6 @@ impl Plugin for AutoExposurePlugin { }; render_app - .init_resource::>() .init_resource::() .add_systems(ExtractSchedule, extract_buffers) .add_systems( @@ -104,15 +101,16 @@ impl FromWorld for AutoExposureResources { fn queue_view_auto_exposure_pipelines( mut commands: Commands, pipeline_cache: Res, - mut compute_pipelines: ResMut>, - pipeline: Res, + mut auto_exposure_pipeline: ResMut, view_targets: Query<(Entity, &AutoExposure)>, -) { +) -> Result<(), BevyError> { for (entity, auto_exposure) in view_targets.iter() { - let histogram_pipeline = - compute_pipelines.specialize(&pipeline_cache, &pipeline, AutoExposurePass::Histogram); - let average_pipeline = - compute_pipelines.specialize(&pipeline_cache, &pipeline, AutoExposurePass::Average); + let histogram_pipeline = auto_exposure_pipeline + .variants + .specialize(&pipeline_cache, AutoExposurePass::Histogram)?; + let average_pipeline = auto_exposure_pipeline + .variants + .specialize(&pipeline_cache, AutoExposurePass::Average)?; commands.entity(entity).insert(ViewAutoExposurePipeline { histogram_pipeline, @@ -121,4 +119,5 @@ fn queue_view_auto_exposure_pipelines( metering_mask: auto_exposure.metering_mask.clone(), }); } + Ok(()) } diff --git a/crates/bevy_core_pipeline/src/auto_exposure/node.rs b/crates/bevy_core_pipeline/src/auto_exposure/node.rs index 222efe5c62bd0..adf20f592d1df 100644 --- a/crates/bevy_core_pipeline/src/auto_exposure/node.rs +++ b/crates/bevy_core_pipeline/src/auto_exposure/node.rs @@ -100,7 +100,7 @@ impl Node for AutoExposureNode { let compute_bind_group = render_context.render_device().create_bind_group( None, - &pipeline.histogram_layout, + &pipeline.layout, &BindGroupEntries::sequential(( &globals_buffer.buffer, &auto_exposure_buffers.settings, diff --git a/crates/bevy_core_pipeline/src/auto_exposure/pipeline.rs b/crates/bevy_core_pipeline/src/auto_exposure/pipeline.rs index 4a2afa939edaf..1a44f23d877e9 100644 --- a/crates/bevy_core_pipeline/src/auto_exposure/pipeline.rs +++ b/crates/bevy_core_pipeline/src/auto_exposure/pipeline.rs @@ -11,14 +11,16 @@ use bevy_render::{ view::ViewUniform, }; use bevy_utils::default; -use core::num::NonZero; +use core::{num::NonZero, result::Result}; #[derive(Resource)] pub struct AutoExposurePipeline { - pub histogram_layout: BindGroupLayout, - pub histogram_shader: Handle, + pub layout: BindGroupLayout, + pub variants: SpecializedCache, } +pub struct AutoExposureSpecializer; + #[derive(Component)] pub struct ViewAutoExposurePipeline { pub histogram_pipeline: CachedComputePipelineId, @@ -39,7 +41,7 @@ pub struct AutoExposureUniform { pub(super) exponential_transition_distance: f32, } -#[derive(PartialEq, Eq, Hash, Clone)] +#[derive(PartialEq, Eq, Hash, Clone, SpecializerKey)] pub enum AutoExposurePass { Histogram, Average, @@ -51,43 +53,60 @@ impl FromWorld for AutoExposurePipeline { fn from_world(world: &mut World) -> Self { let render_device = world.resource::(); - Self { - histogram_layout: render_device.create_bind_group_layout( - "compute histogram bind group", - &BindGroupLayoutEntries::sequential( - ShaderStages::COMPUTE, - ( - uniform_buffer::(false), - uniform_buffer::(false), - texture_2d(TextureSampleType::Float { filterable: false }), - texture_2d(TextureSampleType::Float { filterable: false }), - texture_1d(TextureSampleType::Float { filterable: false }), - uniform_buffer::(false), - storage_buffer_sized(false, NonZero::::new(HISTOGRAM_BIN_COUNT * 4)), - storage_buffer_sized(false, NonZero::::new(4)), - storage_buffer::(true), - ), + let layout = render_device.create_bind_group_layout( + "compute histogram bind group", + &BindGroupLayoutEntries::sequential( + ShaderStages::COMPUTE, + ( + uniform_buffer::(false), + uniform_buffer::(false), + texture_2d(TextureSampleType::Float { filterable: false }), + texture_2d(TextureSampleType::Float { filterable: false }), + texture_1d(TextureSampleType::Float { filterable: false }), + uniform_buffer::(false), + storage_buffer_sized(false, NonZero::::new(HISTOGRAM_BIN_COUNT * 4)), + storage_buffer_sized(false, NonZero::::new(4)), + storage_buffer::(true), ), ), - histogram_shader: load_embedded_asset!(world, "auto_exposure.wgsl"), - } + ); + + let shader = load_embedded_asset!(world, "auto_exposure.wgsl"); + + let base_descriptor = ComputePipelineDescriptor { + layout: vec![layout.clone()], + shader, + ..default() + }; + + let variants = SpecializedCache::new(AutoExposureSpecializer, None, base_descriptor); + + Self { layout, variants } } } -impl SpecializedComputePipeline for AutoExposurePipeline { +impl Specializer for AutoExposureSpecializer { type Key = AutoExposurePass; - fn specialize(&self, pass: AutoExposurePass) -> ComputePipelineDescriptor { - ComputePipelineDescriptor { - label: Some("luminance compute pipeline".into()), - layout: vec![self.histogram_layout.clone()], - shader: self.histogram_shader.clone(), - shader_defs: vec![], - entry_point: Some(match pass { - AutoExposurePass::Histogram => "compute_histogram".into(), - AutoExposurePass::Average => "compute_average".into(), - }), - ..default() - } + fn specialize( + &self, + key: Self::Key, + descriptor: &mut ComputePipelineDescriptor, + ) -> Result, BevyError> { + let (label, entry_point) = match key { + AutoExposurePass::Histogram => ( + "auto_exposure_compute_histogram".into(), + "compute_histogram".into(), + ), + AutoExposurePass::Average => ( + "auto_exposure_compute_average".into(), + "compute_average".into(), + ), + }; + + descriptor.label = Some(label); + descriptor.entry_point = Some(entry_point); + + Ok(key) } } 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..b081798e3da02 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 + 1).saturating_sub(vec.len()); + vec.extend(iter::repeat_n(filler, num_to_fill)); + vec[index] = value; +}