From b3929ef35babdf06eb8e581ed6dc042d32aea984 Mon Sep 17 00:00:00 2001 From: Emerson Coskey Date: Sat, 5 Jul 2025 11:07:34 -0700 Subject: [PATCH 01/10] migrate bloom --- .../src/bloom/downsampling_pipeline.rs | 103 +++++++-------- crates/bevy_core_pipeline/src/bloom/mod.rs | 2 - .../src/bloom/upsampling_pipeline.rs | 119 +++++++++++------- 3 files changed, 125 insertions(+), 99 deletions(-) diff --git a/crates/bevy_core_pipeline/src/bloom/downsampling_pipeline.rs b/crates/bevy_core_pipeline/src/bloom/downsampling_pipeline.rs index aa8d3d37afc3e..8191147248a14 100644 --- a/crates/bevy_core_pipeline/src/bloom/downsampling_pipeline.rs +++ b/crates/bevy_core_pipeline/src/bloom/downsampling_pipeline.rs @@ -3,6 +3,7 @@ use crate::FullscreenShader; use super::{Bloom, BLOOM_TEXTURE_FORMAT}; use bevy_asset::{load_embedded_asset, Handle}; 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.get_or_insert_default(); - 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..458a19ab7e7a7 100644 --- a/crates/bevy_core_pipeline/src/bloom/upsampling_pipeline.rs +++ b/crates/bevy_core_pipeline/src/bloom/upsampling_pipeline.rs @@ -5,6 +5,7 @@ use super::{ }; use bevy_asset::{load_embedded_asset, Handle}; 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,63 @@ 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()), + 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 { 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; - fn specialize(&self, key: Self::Key) -> RenderPipelineDescriptor { +#[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, + descriptor: &mut RenderPipelineDescriptor, + ) -> Result, BevyError> { let texture_format = if key.final_pipeline { ViewTarget::TEXTURE_FORMAT_HDR } else { @@ -110,28 +147,16 @@ 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() - }), - ..default() + let fragment = descriptor.fragment.get_or_insert_default(); + + if let Some(Some(color_target)) = fragment.targets.first_mut() + && let Some(blend_state) = &mut color_target.blend + { + blend_state.color = color_blend; + color_target.format = texture_format; + Ok(key) + } else { + Err("color target state or blend state missing".into()) } } } @@ -139,32 +164,30 @@ impl SpecializedRenderPipeline for BloomUpsamplingPipeline { 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(()) } From da94108af59933422f4aaf7f4d05cc0ed09a3645 Mon Sep 17 00:00:00 2001 From: Emerson Coskey Date: Sat, 5 Jul 2025 11:22:43 -0700 Subject: [PATCH 02/10] fix unused imports --- crates/bevy_core_pipeline/src/bloom/downsampling_pipeline.rs | 2 +- crates/bevy_core_pipeline/src/bloom/upsampling_pipeline.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/bevy_core_pipeline/src/bloom/downsampling_pipeline.rs b/crates/bevy_core_pipeline/src/bloom/downsampling_pipeline.rs index 8191147248a14..1d26ad94587de 100644 --- a/crates/bevy_core_pipeline/src/bloom/downsampling_pipeline.rs +++ b/crates/bevy_core_pipeline/src/bloom/downsampling_pipeline.rs @@ -1,7 +1,7 @@ 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}, diff --git a/crates/bevy_core_pipeline/src/bloom/upsampling_pipeline.rs b/crates/bevy_core_pipeline/src/bloom/upsampling_pipeline.rs index 458a19ab7e7a7..f798d8b571528 100644 --- a/crates/bevy_core_pipeline/src/bloom/upsampling_pipeline.rs +++ b/crates/bevy_core_pipeline/src/bloom/upsampling_pipeline.rs @@ -3,7 +3,7 @@ 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}, From d1e6506ac101953853e89e4357b12c9b2fcdfe29 Mon Sep 17 00:00:00 2001 From: Emerson Coskey Date: Sat, 5 Jul 2025 14:17:24 -0700 Subject: [PATCH 03/10] add utils to `FragmentState` similar utils for layout etc should be added later as needed --- .../src/bloom/downsampling_pipeline.rs | 5 +++- .../src/bloom/upsampling_pipeline.rs | 30 ++++++++++++------- .../src/render_resource/pipeline.rs | 14 ++++++++- 3 files changed, 37 insertions(+), 12 deletions(-) diff --git a/crates/bevy_core_pipeline/src/bloom/downsampling_pipeline.rs b/crates/bevy_core_pipeline/src/bloom/downsampling_pipeline.rs index 1d26ad94587de..0d227d69a8619 100644 --- a/crates/bevy_core_pipeline/src/bloom/downsampling_pipeline.rs +++ b/crates/bevy_core_pipeline/src/bloom/downsampling_pipeline.rs @@ -124,7 +124,10 @@ impl Specializer for BloomDownsamplingSpecializer { "bloom_downsampling_pipeline".into() }); - let fragment = descriptor.fragment.get_or_insert_default(); + // TODO: should this error? + let Some(fragment) = &mut descriptor.fragment else { + return Ok(key); + }; fragment.entry_point = Some(if key.first_downsample { "downsample_first".into() diff --git a/crates/bevy_core_pipeline/src/bloom/upsampling_pipeline.rs b/crates/bevy_core_pipeline/src/bloom/upsampling_pipeline.rs index f798d8b571528..71a3b315fe25a 100644 --- a/crates/bevy_core_pipeline/src/bloom/upsampling_pipeline.rs +++ b/crates/bevy_core_pipeline/src/bloom/upsampling_pipeline.rs @@ -109,6 +109,10 @@ impl Specializer for BloomUpsamplingSpecializer { key: Self::Key, descriptor: &mut RenderPipelineDescriptor, ) -> Result, BevyError> { + let Some(fragment) = &mut descriptor.fragment else { + return Ok(key); + }; + let texture_format = if key.final_pipeline { ViewTarget::TEXTURE_FORMAT_HDR } else { @@ -147,17 +151,23 @@ impl Specializer for BloomUpsamplingSpecializer { }, }; - let fragment = descriptor.fragment.get_or_insert_default(); + let target = ColorTargetState { + format: texture_format, + blend: Some(BlendState { + // placeholder + color: color_blend, + alpha: BlendComponent { + src_factor: BlendFactor::Zero, + dst_factor: BlendFactor::One, + operation: BlendOperation::Add, + }, + }), + write_mask: ColorWrites::ALL, + }; + + fragment.set_target(0, target); - if let Some(Some(color_target)) = fragment.targets.first_mut() - && let Some(blend_state) = &mut color_target.blend - { - blend_state.color = color_blend; - color_target.format = texture_format; - Ok(key) - } else { - Err("color target state or blend state missing".into()) - } + Ok(key) } } diff --git a/crates/bevy_render/src/render_resource/pipeline.rs b/crates/bevy_render/src/render_resource/pipeline.rs index e94cf27cd32c8..858228453fee3 100644 --- a/crates/bevy_render/src/render_resource/pipeline.rs +++ b/crates/bevy_render/src/render_resource/pipeline.rs @@ -7,7 +7,7 @@ use crate::{ use alloc::borrow::Cow; use bevy_asset::Handle; use bevy_utils::WgpuWrapper; -use core::ops::Deref; +use core::{iter, ops::Deref}; use wgpu::{ ColorTargetState, DepthStencilState, MultisampleState, PrimitiveState, PushConstantRange, }; @@ -137,6 +137,12 @@ pub struct FragmentState { pub targets: Vec>, } +impl FragmentState { + pub fn set_target(&mut self, index: usize, target: ColorTargetState) { + filling_insert_at(&mut self.targets, index, None, Some(target)); + } +} + /// Describes a compute pipeline. #[derive(Clone, Debug, PartialEq, Eq, Default)] pub struct ComputePipelineDescriptor { @@ -153,3 +159,9 @@ 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, } + +fn filling_insert_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; +} From ee33ae11d882277236c60ec5f4a70419ca2b383e Mon Sep 17 00:00:00 2001 From: Emerson Coskey Date: Tue, 8 Jul 2025 19:15:41 -0700 Subject: [PATCH 04/10] error handling --- .../src/bloom/upsampling_pipeline.rs | 6 +----- .../bevy_render/src/render_resource/pipeline.rs | 17 +++++++++++++++-- 2 files changed, 16 insertions(+), 7 deletions(-) diff --git a/crates/bevy_core_pipeline/src/bloom/upsampling_pipeline.rs b/crates/bevy_core_pipeline/src/bloom/upsampling_pipeline.rs index 71a3b315fe25a..8f43761a72a7f 100644 --- a/crates/bevy_core_pipeline/src/bloom/upsampling_pipeline.rs +++ b/crates/bevy_core_pipeline/src/bloom/upsampling_pipeline.rs @@ -109,10 +109,6 @@ impl Specializer for BloomUpsamplingSpecializer { key: Self::Key, descriptor: &mut RenderPipelineDescriptor, ) -> Result, BevyError> { - let Some(fragment) = &mut descriptor.fragment else { - return Ok(key); - }; - let texture_format = if key.final_pipeline { ViewTarget::TEXTURE_FORMAT_HDR } else { @@ -165,7 +161,7 @@ impl Specializer for BloomUpsamplingSpecializer { write_mask: ColorWrites::ALL, }; - fragment.set_target(0, target); + descriptor.get_fragment_mut()?.set_target(0, target); Ok(key) } diff --git a/crates/bevy_render/src/render_resource/pipeline.rs b/crates/bevy_render/src/render_resource/pipeline.rs index 858228453fee3..32e33ab44a2c9 100644 --- a/crates/bevy_render/src/render_resource/pipeline.rs +++ b/crates/bevy_render/src/render_resource/pipeline.rs @@ -8,6 +8,7 @@ use alloc::borrow::Cow; use bevy_asset::Handle; use bevy_utils::WgpuWrapper; 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 get_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. @@ -139,7 +150,7 @@ pub struct FragmentState { impl FragmentState { pub fn set_target(&mut self, index: usize, target: ColorTargetState) { - filling_insert_at(&mut self.targets, index, None, Some(target)); + filling_set_at(&mut self.targets, index, None, Some(target)); } } @@ -160,7 +171,9 @@ pub struct ComputePipelineDescriptor { pub zero_initialize_workgroup_memory: bool, } -fn filling_insert_at(vec: &mut Vec, index: usize, filler: T, value: T) { +// 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; From 6dc699a83e48ea99129fee7f9d3f4caca1103060 Mon Sep 17 00:00:00 2001 From: Emerson Coskey Date: Tue, 8 Jul 2025 19:22:53 -0700 Subject: [PATCH 05/10] fix downsampling specializer --- crates/bevy_core_pipeline/src/bloom/downsampling_pipeline.rs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/crates/bevy_core_pipeline/src/bloom/downsampling_pipeline.rs b/crates/bevy_core_pipeline/src/bloom/downsampling_pipeline.rs index 0d227d69a8619..2c71c24f6033b 100644 --- a/crates/bevy_core_pipeline/src/bloom/downsampling_pipeline.rs +++ b/crates/bevy_core_pipeline/src/bloom/downsampling_pipeline.rs @@ -124,10 +124,7 @@ impl Specializer for BloomDownsamplingSpecializer { "bloom_downsampling_pipeline".into() }); - // TODO: should this error? - let Some(fragment) = &mut descriptor.fragment else { - return Ok(key); - }; + let fragment = descriptor.get_fragment_mut()?; fragment.entry_point = Some(if key.first_downsample { "downsample_first".into() From 9b48405c3e0cf147a994da682594194c8f29f277 Mon Sep 17 00:00:00 2001 From: Emerson Coskey Date: Tue, 8 Jul 2025 19:31:20 -0700 Subject: [PATCH 06/10] rename fragment_mut --- crates/bevy_core_pipeline/src/bloom/downsampling_pipeline.rs | 2 +- crates/bevy_core_pipeline/src/bloom/upsampling_pipeline.rs | 2 +- crates/bevy_render/src/render_resource/pipeline.rs | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/crates/bevy_core_pipeline/src/bloom/downsampling_pipeline.rs b/crates/bevy_core_pipeline/src/bloom/downsampling_pipeline.rs index 2c71c24f6033b..fa50a47c9c0ea 100644 --- a/crates/bevy_core_pipeline/src/bloom/downsampling_pipeline.rs +++ b/crates/bevy_core_pipeline/src/bloom/downsampling_pipeline.rs @@ -124,7 +124,7 @@ impl Specializer for BloomDownsamplingSpecializer { "bloom_downsampling_pipeline".into() }); - let fragment = descriptor.get_fragment_mut()?; + let fragment = descriptor.fragment_mut()?; fragment.entry_point = Some(if key.first_downsample { "downsample_first".into() diff --git a/crates/bevy_core_pipeline/src/bloom/upsampling_pipeline.rs b/crates/bevy_core_pipeline/src/bloom/upsampling_pipeline.rs index 8f43761a72a7f..be440fdff0203 100644 --- a/crates/bevy_core_pipeline/src/bloom/upsampling_pipeline.rs +++ b/crates/bevy_core_pipeline/src/bloom/upsampling_pipeline.rs @@ -161,7 +161,7 @@ impl Specializer for BloomUpsamplingSpecializer { write_mask: ColorWrites::ALL, }; - descriptor.get_fragment_mut()?.set_target(0, target); + descriptor.fragment_mut()?.set_target(0, target); Ok(key) } diff --git a/crates/bevy_render/src/render_resource/pipeline.rs b/crates/bevy_render/src/render_resource/pipeline.rs index 32e33ab44a2c9..d56f74e882c89 100644 --- a/crates/bevy_render/src/render_resource/pipeline.rs +++ b/crates/bevy_render/src/render_resource/pipeline.rs @@ -118,7 +118,7 @@ pub struct RenderPipelineDescriptor { pub struct NoFragmentStateError; impl RenderPipelineDescriptor { - pub fn get_fragment_mut(&mut self) -> Result<&mut FragmentState, NoFragmentStateError> { + pub fn fragment_mut(&mut self) -> Result<&mut FragmentState, NoFragmentStateError> { self.fragment.as_mut().ok_or(NoFragmentStateError) } } From 0a47e68ab585163c8e9721c36697cacbbeb2e56d Mon Sep 17 00:00:00 2001 From: Emerson Coskey Date: Wed, 9 Jul 2025 20:41:23 -0700 Subject: [PATCH 07/10] remove redundant code --- .../src/bloom/upsampling_pipeline.rs | 18 ------------------ 1 file changed, 18 deletions(-) diff --git a/crates/bevy_core_pipeline/src/bloom/upsampling_pipeline.rs b/crates/bevy_core_pipeline/src/bloom/upsampling_pipeline.rs index be440fdff0203..8f25e88cf9ab2 100644 --- a/crates/bevy_core_pipeline/src/bloom/upsampling_pipeline.rs +++ b/crates/bevy_core_pipeline/src/bloom/upsampling_pipeline.rs @@ -61,23 +61,6 @@ impl FromWorld for BloomUpsamplingPipeline { 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() @@ -150,7 +133,6 @@ impl Specializer for BloomUpsamplingSpecializer { let target = ColorTargetState { format: texture_format, blend: Some(BlendState { - // placeholder color: color_blend, alpha: BlendComponent { src_factor: BlendFactor::Zero, From 498b1099a9b59844319cc8fa2999bf3748f6e5e9 Mon Sep 17 00:00:00 2001 From: Emerson Coskey Date: Sun, 13 Jul 2025 13:31:50 -0700 Subject: [PATCH 08/10] fix filling_set_at --- crates/bevy_render/src/render_resource/pipeline.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/bevy_render/src/render_resource/pipeline.rs b/crates/bevy_render/src/render_resource/pipeline.rs index d56f74e882c89..b081798e3da02 100644 --- a/crates/bevy_render/src/render_resource/pipeline.rs +++ b/crates/bevy_render/src/render_resource/pipeline.rs @@ -174,7 +174,7 @@ pub struct ComputePipelineDescriptor { // 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); + let num_to_fill = (index + 1).saturating_sub(vec.len()); vec.extend(iter::repeat_n(filler, num_to_fill)); vec[index] = value; } From f9f0451a86493784c8098e7439242b6e26e7bbb3 Mon Sep 17 00:00:00 2001 From: Emerson Coskey Date: Sun, 13 Jul 2025 20:04:19 -0700 Subject: [PATCH 09/10] migrate auto_exposure --- .../src/auto_exposure/mod.rs | 21 +++-- .../src/auto_exposure/node.rs | 2 +- .../src/auto_exposure/pipeline.rs | 88 ++++++++++++------- 3 files changed, 65 insertions(+), 46 deletions(-) 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..e278b92c70e23 100644 --- a/crates/bevy_core_pipeline/src/auto_exposure/pipeline.rs +++ b/crates/bevy_core_pipeline/src/auto_exposure/pipeline.rs @@ -12,13 +12,16 @@ use bevy_render::{ }; use bevy_utils::default; use core::num::NonZero; +use std::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 +42,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 +54,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) } } From fafd15818425d0c5e392fac3889bff9451c55b01 Mon Sep 17 00:00:00 2001 From: Emerson Coskey Date: Sun, 13 Jul 2025 21:07:50 -0700 Subject: [PATCH 10/10] fix ci --- crates/bevy_core_pipeline/src/auto_exposure/pipeline.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/crates/bevy_core_pipeline/src/auto_exposure/pipeline.rs b/crates/bevy_core_pipeline/src/auto_exposure/pipeline.rs index e278b92c70e23..1a44f23d877e9 100644 --- a/crates/bevy_core_pipeline/src/auto_exposure/pipeline.rs +++ b/crates/bevy_core_pipeline/src/auto_exposure/pipeline.rs @@ -11,8 +11,7 @@ use bevy_render::{ view::ViewUniform, }; use bevy_utils::default; -use core::num::NonZero; -use std::result::Result; +use core::{num::NonZero, result::Result}; #[derive(Resource)] pub struct AutoExposurePipeline {