bevy_bad_sdr_bloom/
lib.rs

1use bevy::app::{App, Plugin};
2use bevy::asset::{load_internal_asset, HandleUntyped};
3use bevy::core_pipeline::{
4    core_2d, core_3d, fullscreen_vertex_shader::fullscreen_shader_vertex_state,
5};
6use bevy::ecs::{
7    prelude::{Component, Entity},
8    query::{QueryState, With},
9    system::{Commands, Query, Res, ResMut, Resource},
10    world::{FromWorld, World},
11};
12use bevy::math::UVec2;
13use bevy::prelude::{IntoSystemAppConfig, IntoSystemConfig};
14use bevy::reflect::{Reflect, TypeUuid};
15use bevy::render::{
16    camera::ExtractedCamera,
17    prelude::Camera,
18    render_graph::{Node, NodeRunError, RenderGraph, RenderGraphContext, SlotInfo, SlotType},
19    render_resource::*,
20    renderer::{RenderContext, RenderDevice, RenderQueue},
21    texture::{CachedTexture, TextureCache},
22    view::ViewTarget,
23    Extract, RenderApp,
24};
25use bevy::render::{ExtractSchedule, RenderSet};
26#[cfg(feature = "trace")]
27use bevy::utils::tracing::info_span;
28use bevy::utils::HashMap;
29
30const BLOOM_SHADER_HANDLE: HandleUntyped =
31    HandleUntyped::weak_from_u64(Shader::TYPE_UUID, 8661849338007983600);
32
33pub struct BloomPlugin;
34
35impl Plugin for BloomPlugin {
36    fn build(&self, app: &mut App) {
37        load_internal_asset!(app, BLOOM_SHADER_HANDLE, "bloom.wgsl", Shader::from_wgsl);
38
39        app.register_type::<BloomSettings>();
40
41        let render_app = match app.get_sub_app_mut(RenderApp) {
42            Ok(render_app) => render_app,
43            Err(_) => return,
44        };
45
46        render_app
47            .init_resource::<BloomPipelines>()
48            .init_resource::<BloomUniforms>()
49            .add_system(extract_bloom_settings.in_schedule(ExtractSchedule))
50            .add_system(prepare_bloom_textures.in_set(RenderSet::Prepare))
51            .add_system(prepare_bloom_uniforms.in_set(RenderSet::Prepare))
52            .add_system(queue_bloom_bind_groups.in_set(RenderSet::Queue));
53
54        {
55            let bloom_node = BloomNode::new(&mut render_app.world);
56            let mut graph = render_app.world.resource_mut::<RenderGraph>();
57            let draw_3d_graph = graph
58                .get_sub_graph_mut(crate::core_3d::graph::NAME)
59                .unwrap();
60            draw_3d_graph.add_node(core_3d::graph::node::BLOOM, bloom_node);
61            draw_3d_graph.add_slot_edge(
62                draw_3d_graph.input_node().id,
63                crate::core_3d::graph::input::VIEW_ENTITY,
64                core_3d::graph::node::BLOOM,
65                BloomNode::IN_VIEW,
66            );
67            // MAIN_PASS -> BLOOM -> TONEMAPPING
68            draw_3d_graph.add_node_edge(
69                crate::core_3d::graph::node::MAIN_PASS,
70                core_3d::graph::node::BLOOM,
71            );
72            draw_3d_graph.add_node_edge(
73                core_3d::graph::node::BLOOM,
74                crate::core_3d::graph::node::TONEMAPPING,
75            );
76        }
77
78        {
79            let bloom_node = BloomNode::new(&mut render_app.world);
80            let mut graph = render_app.world.resource_mut::<RenderGraph>();
81            let draw_2d_graph = graph
82                .get_sub_graph_mut(crate::core_2d::graph::NAME)
83                .unwrap();
84            draw_2d_graph.add_node(core_2d::graph::node::BLOOM, bloom_node);
85            draw_2d_graph.add_slot_edge(
86                draw_2d_graph.input_node().id,
87                crate::core_2d::graph::input::VIEW_ENTITY,
88                core_2d::graph::node::BLOOM,
89                BloomNode::IN_VIEW,
90            );
91            // MAIN_PASS -> BLOOM -> TONEMAPPING
92            draw_2d_graph.add_node_edge(
93                crate::core_2d::graph::node::MAIN_PASS,
94                core_2d::graph::node::BLOOM,
95            );
96            draw_2d_graph.add_node_edge(
97                core_2d::graph::node::BLOOM,
98                crate::core_2d::graph::node::TONEMAPPING,
99            );
100        }
101    }
102}
103
104/// Applies a bloom effect to a HDR-enabled 2d or 3d camera.
105///
106/// Bloom causes bright objects to "glow", emitting a halo of light around them.
107///
108/// Often used in conjunction with `bevy_pbr::StandardMaterial::emissive`.
109///
110/// Note: This light is not "real" in the way directional or point lights are.
111///
112/// Bloom will not cast shadows or bend around other objects - it is purely a post-processing
113/// effect overlaid on top of the already-rendered scene.
114///
115/// See also <https://en.wikipedia.org/wiki/Bloom_(shader_effect)>.
116#[derive(Component, Reflect, Clone)]
117pub struct BloomSettings {
118    /// Baseline of the threshold curve (default: 1.0).
119    ///
120    /// RGB values under the threshold curve will not have bloom applied.
121    pub threshold: f32,
122
123    /// Knee of the threshold curve (default: 0.1).
124    pub knee: f32,
125
126    /// Scale used when upsampling (default: 1.0).
127    pub scale: f32,
128
129    /// Intensity of the bloom effect (default: 0.3).
130    pub intensity: f32,
131}
132
133impl Default for BloomSettings {
134    fn default() -> Self {
135        Self {
136            threshold: 1.0,
137            knee: 0.1,
138            scale: 1.0,
139            intensity: 0.3,
140        }
141    }
142}
143
144pub struct BloomNode {
145    view_query: QueryState<(
146        &'static ExtractedCamera,
147        &'static ViewTarget,
148        &'static BloomTextures,
149        &'static BloomBindGroups,
150        &'static BloomUniformIndex,
151    )>,
152}
153
154impl BloomNode {
155    pub const IN_VIEW: &'static str = "view";
156
157    pub fn new(world: &mut World) -> Self {
158        Self {
159            view_query: QueryState::new(world),
160        }
161    }
162}
163
164impl Node for BloomNode {
165    fn input(&self) -> Vec<SlotInfo> {
166        vec![SlotInfo::new(Self::IN_VIEW, SlotType::Entity)]
167    }
168
169    fn update(&mut self, world: &mut World) {
170        self.view_query.update_archetypes(world);
171    }
172
173    fn run(
174        &self,
175        graph: &mut RenderGraphContext,
176        render_context: &mut RenderContext,
177        world: &World,
178    ) -> Result<(), NodeRunError> {
179        #[cfg(feature = "trace")]
180        let _bloom_span = info_span!("bloom").entered();
181
182        let pipelines = world.resource::<BloomPipelines>();
183        let pipeline_cache = world.resource::<PipelineCache>();
184        let view_entity = graph.get_input_entity(Self::IN_VIEW)?;
185        let (camera, view_target, textures, bind_groups, uniform_index) =
186            match self.view_query.get_manual(world, view_entity) {
187                Ok(result) => result,
188                _ => return Ok(()),
189            };
190        let (
191            downsampling_prefilter_pipeline,
192            downsampling_pipeline,
193            upsampling_pipeline,
194            upsampling_final_pipeline,
195        ) = match (
196            pipeline_cache.get_render_pipeline(pipelines.downsampling_prefilter_pipeline),
197            pipeline_cache.get_render_pipeline(pipelines.downsampling_pipeline),
198            pipeline_cache.get_render_pipeline(pipelines.upsampling_pipeline),
199            pipeline_cache.get_render_pipeline(pipelines.upsampling_final_pipeline),
200        ) {
201            (Some(p1), Some(p2), Some(p3), Some(p4)) => (p1, p2, p3, p4),
202            _ => return Ok(()),
203        };
204
205        {
206            let view = &BloomTextures::texture_view(&textures.textures_a[0]);
207
208            let mut prefilter_pass =
209                render_context.begin_tracked_render_pass(RenderPassDescriptor {
210                    label: Some("bloom_prefilter_pass"),
211                    color_attachments: &[Some(RenderPassColorAttachment {
212                        view,
213                        resolve_target: None,
214                        ops: Operations::default(),
215                    })],
216                    depth_stencil_attachment: None,
217                });
218
219            prefilter_pass.set_render_pipeline(downsampling_prefilter_pipeline);
220            prefilter_pass.set_bind_group(0, &bind_groups.prefilter_bind_group, &[uniform_index.0]);
221            if let Some(viewport) = camera.viewport.as_ref() {
222                prefilter_pass.set_camera_viewport(viewport);
223            }
224            prefilter_pass.draw(0..3, 0..1);
225        }
226
227        for mip in 1..textures.mip_count {
228            let view = &BloomTextures::texture_view(&textures.textures_a[mip as usize]);
229
230            let mut downsampling_pass =
231                render_context.begin_tracked_render_pass(RenderPassDescriptor {
232                    label: Some("bloom_downsampling_pass"),
233                    color_attachments: &[Some(RenderPassColorAttachment {
234                        view,
235                        resolve_target: None,
236                        ops: Operations::default(),
237                    })],
238                    depth_stencil_attachment: None,
239                });
240
241            downsampling_pass.set_render_pipeline(downsampling_pipeline);
242            downsampling_pass.set_bind_group(
243                0,
244                &bind_groups.downsampling_bind_groups[mip as usize - 1],
245                &[uniform_index.0],
246            );
247            if let Some(viewport) = camera.viewport.as_ref() {
248                downsampling_pass.set_camera_viewport(viewport);
249            }
250            downsampling_pass.draw(0..3, 0..1);
251        }
252
253        for mip in (1..textures.mip_count).rev() {
254            let view = &BloomTextures::texture_view(&textures.textures_b[mip as usize - 1]);
255            let mut upsampling_pass =
256                render_context.begin_tracked_render_pass(RenderPassDescriptor {
257                    label: Some("bloom_upsampling_pass"),
258                    color_attachments: &[Some(RenderPassColorAttachment {
259                        view,
260                        resolve_target: None,
261                        ops: Operations::default(),
262                    })],
263                    depth_stencil_attachment: None,
264                });
265
266            upsampling_pass.set_render_pipeline(upsampling_pipeline);
267            upsampling_pass.set_bind_group(
268                0,
269                &bind_groups.upsampling_bind_groups[mip as usize - 1],
270                &[uniform_index.0],
271            );
272            if let Some(viewport) = camera.viewport.as_ref() {
273                upsampling_pass.set_camera_viewport(viewport);
274            }
275            upsampling_pass.draw(0..3, 0..1);
276        }
277
278        {
279            let mut upsampling_final_pass =
280                render_context.begin_tracked_render_pass(RenderPassDescriptor {
281                    label: Some("bloom_upsampling_final_pass"),
282                    color_attachments: &[Some(view_target.get_unsampled_color_attachment(
283                        Operations {
284                            load: LoadOp::Load,
285                            store: true,
286                        },
287                    ))],
288                    depth_stencil_attachment: None,
289                });
290
291            upsampling_final_pass.set_render_pipeline(upsampling_final_pipeline);
292            upsampling_final_pass.set_bind_group(
293                0,
294                &bind_groups.upsampling_final_bind_group,
295                &[uniform_index.0],
296            );
297            if let Some(viewport) = camera.viewport.as_ref() {
298                upsampling_final_pass.set_camera_viewport(viewport);
299            }
300            upsampling_final_pass.draw(0..3, 0..1);
301        }
302
303        Ok(())
304    }
305}
306
307#[derive(Resource)]
308struct BloomPipelines {
309    downsampling_prefilter_pipeline: CachedRenderPipelineId,
310    downsampling_pipeline: CachedRenderPipelineId,
311    upsampling_pipeline: CachedRenderPipelineId,
312    upsampling_final_pipeline: CachedRenderPipelineId,
313    sampler: Sampler,
314    downsampling_bind_group_layout: BindGroupLayout,
315    upsampling_bind_group_layout: BindGroupLayout,
316}
317
318impl FromWorld for BloomPipelines {
319    fn from_world(world: &mut World) -> Self {
320        let render_device = world.resource::<RenderDevice>();
321
322        let sampler = render_device.create_sampler(&SamplerDescriptor {
323            min_filter: FilterMode::Linear,
324            mag_filter: FilterMode::Linear,
325            address_mode_u: AddressMode::ClampToEdge,
326            address_mode_v: AddressMode::ClampToEdge,
327            ..Default::default()
328        });
329
330        let downsampling_bind_group_layout =
331            render_device.create_bind_group_layout(&BindGroupLayoutDescriptor {
332                label: Some("bloom_downsampling_bind_group_layout"),
333                entries: &[
334                    // Upsampled input texture (downsampled for final upsample)
335                    BindGroupLayoutEntry {
336                        binding: 0,
337                        ty: BindingType::Texture {
338                            sample_type: TextureSampleType::Float { filterable: true },
339                            view_dimension: TextureViewDimension::D2,
340                            multisampled: false,
341                        },
342                        visibility: ShaderStages::FRAGMENT,
343                        count: None,
344                    },
345                    // Sampler
346                    BindGroupLayoutEntry {
347                        binding: 1,
348                        ty: BindingType::Sampler(SamplerBindingType::Filtering),
349                        visibility: ShaderStages::FRAGMENT,
350                        count: None,
351                    },
352                    // Bloom settings
353                    BindGroupLayoutEntry {
354                        binding: 2,
355                        ty: BindingType::Buffer {
356                            ty: BufferBindingType::Uniform,
357                            has_dynamic_offset: true,
358                            min_binding_size: Some(BloomUniform::min_size()),
359                        },
360                        visibility: ShaderStages::FRAGMENT,
361                        count: None,
362                    },
363                ],
364            });
365
366        let upsampling_bind_group_layout =
367            render_device.create_bind_group_layout(&BindGroupLayoutDescriptor {
368                label: Some("bloom_upsampling_bind_group_layout"),
369                entries: &[
370                    // Downsampled input texture
371                    BindGroupLayoutEntry {
372                        binding: 0,
373                        ty: BindingType::Texture {
374                            sample_type: TextureSampleType::Float { filterable: true },
375                            view_dimension: TextureViewDimension::D2,
376                            multisampled: false,
377                        },
378                        visibility: ShaderStages::FRAGMENT,
379                        count: None,
380                    },
381                    // Sampler
382                    BindGroupLayoutEntry {
383                        binding: 1,
384                        ty: BindingType::Sampler(SamplerBindingType::Filtering),
385                        visibility: ShaderStages::FRAGMENT,
386                        count: None,
387                    },
388                    // Bloom settings
389                    BindGroupLayoutEntry {
390                        binding: 2,
391                        ty: BindingType::Buffer {
392                            ty: BufferBindingType::Uniform,
393                            has_dynamic_offset: true,
394                            min_binding_size: Some(BloomUniform::min_size()),
395                        },
396                        visibility: ShaderStages::FRAGMENT,
397                        count: None,
398                    },
399                    // Upsampled input texture
400                    BindGroupLayoutEntry {
401                        binding: 3,
402                        ty: BindingType::Texture {
403                            sample_type: TextureSampleType::Float { filterable: true },
404                            view_dimension: TextureViewDimension::D2,
405                            multisampled: false,
406                        },
407                        visibility: ShaderStages::FRAGMENT,
408                        count: None,
409                    },
410                ],
411            });
412
413        let pipeline_cache = world.resource::<PipelineCache>();
414
415        let downsampling_prefilter_pipeline =
416            pipeline_cache.queue_render_pipeline(RenderPipelineDescriptor {
417                label: Some("bloom_downsampling_prefilter_pipeline".into()),
418                layout: vec![downsampling_bind_group_layout.clone()],
419                vertex: fullscreen_shader_vertex_state(),
420                fragment: Some(FragmentState {
421                    shader: BLOOM_SHADER_HANDLE.typed::<Shader>(),
422                    shader_defs: vec![],
423                    entry_point: "downsample_prefilter".into(),
424                    targets: vec![Some(ColorTargetState {
425                        format: TextureFormat::Rgba8UnormSrgb,
426                        blend: None,
427                        write_mask: ColorWrites::ALL,
428                    })],
429                }),
430                primitive: PrimitiveState::default(),
431                depth_stencil: None,
432                multisample: MultisampleState::default(),
433                push_constant_ranges: vec![],
434            });
435
436        let downsampling_pipeline =
437            pipeline_cache.queue_render_pipeline(RenderPipelineDescriptor {
438                label: Some("bloom_downsampling_pipeline".into()),
439                layout: vec![downsampling_bind_group_layout.clone()],
440                vertex: fullscreen_shader_vertex_state(),
441                fragment: Some(FragmentState {
442                    shader: BLOOM_SHADER_HANDLE.typed::<Shader>(),
443                    shader_defs: vec![],
444                    entry_point: "downsample".into(),
445                    targets: vec![Some(ColorTargetState {
446                        format: TextureFormat::Rgba8UnormSrgb,
447                        blend: None,
448                        write_mask: ColorWrites::ALL,
449                    })],
450                }),
451                primitive: PrimitiveState::default(),
452                depth_stencil: None,
453                multisample: MultisampleState::default(),
454                push_constant_ranges: vec![],
455            });
456
457        let upsampling_pipeline = pipeline_cache.queue_render_pipeline(RenderPipelineDescriptor {
458            label: Some("bloom_upsampling_pipeline".into()),
459            layout: vec![upsampling_bind_group_layout.clone()],
460            vertex: fullscreen_shader_vertex_state(),
461            fragment: Some(FragmentState {
462                shader: BLOOM_SHADER_HANDLE.typed::<Shader>(),
463                shader_defs: vec![],
464                entry_point: "upsample".into(),
465                targets: vec![Some(ColorTargetState {
466                    format: TextureFormat::Rgba8UnormSrgb,
467                    blend: None,
468                    write_mask: ColorWrites::ALL,
469                })],
470            }),
471            primitive: PrimitiveState::default(),
472            depth_stencil: None,
473            multisample: MultisampleState::default(),
474            push_constant_ranges: vec![],
475        });
476
477        let upsampling_final_pipeline =
478            pipeline_cache.queue_render_pipeline(RenderPipelineDescriptor {
479                label: Some("bloom_upsampling_final_pipeline".into()),
480                layout: vec![downsampling_bind_group_layout.clone()],
481                vertex: fullscreen_shader_vertex_state(),
482                fragment: Some(FragmentState {
483                    shader: BLOOM_SHADER_HANDLE.typed::<Shader>(),
484                    shader_defs: vec![],
485                    entry_point: "upsample_final".into(),
486                    targets: vec![Some(ColorTargetState {
487                        format: TextureFormat::Rgba8UnormSrgb,
488                        blend: Some(BlendState {
489                            color: BlendComponent {
490                                src_factor: BlendFactor::One,
491                                dst_factor: BlendFactor::One,
492                                operation: BlendOperation::Add,
493                            },
494                            alpha: BlendComponent::REPLACE,
495                        }),
496                        write_mask: ColorWrites::ALL,
497                    })],
498                }),
499                primitive: PrimitiveState::default(),
500                depth_stencil: None,
501                multisample: MultisampleState::default(),
502                push_constant_ranges: vec![],
503            });
504
505        BloomPipelines {
506            downsampling_prefilter_pipeline,
507            downsampling_pipeline,
508            upsampling_pipeline,
509            upsampling_final_pipeline,
510            sampler,
511            downsampling_bind_group_layout,
512            upsampling_bind_group_layout,
513        }
514    }
515}
516
517fn extract_bloom_settings(
518    mut commands: Commands,
519    cameras: Extract<Query<(Entity, &Camera, &BloomSettings), With<Camera>>>,
520) {
521    for (entity, camera, bloom_settings) in &cameras {
522        if camera.is_active {
523            commands.get_or_spawn(entity).insert(bloom_settings.clone());
524        }
525    }
526}
527
528#[derive(Component)]
529struct BloomTextures {
530    textures_a: Vec<CachedTexture>,
531    textures_b: Vec<CachedTexture>,
532    mip_count: u32,
533}
534
535impl BloomTextures {
536    fn texture_view(texture: &CachedTexture) -> TextureView {
537        texture.texture.create_view(&TextureViewDescriptor {
538            ..Default::default()
539        })
540    }
541}
542
543fn prepare_bloom_textures(
544    mut commands: Commands,
545    mut texture_cache: ResMut<TextureCache>,
546    render_device: Res<RenderDevice>,
547    views: Query<(Entity, &ExtractedCamera), With<BloomSettings>>,
548) {
549    let mut texture_as = HashMap::default();
550    let mut texture_bs = HashMap::default();
551    for (entity, camera) in &views {
552        if let Some(UVec2 {
553            x: width,
554            y: height,
555        }) = camera.physical_viewport_size
556        {
557            let min_view = width.min(height) / 2;
558            let mip_count = calculate_mip_count(min_view);
559
560            let mut texture_descriptor = TextureDescriptor {
561                label: None,
562                size: Extent3d {
563                    width: (width / 2).max(1),
564                    height: (height / 2).max(1),
565                    depth_or_array_layers: 1,
566                },
567                mip_level_count: 1,
568                sample_count: 1,
569                dimension: TextureDimension::D2,
570                format: TextureFormat::Rgba8UnormSrgb,
571                usage: TextureUsages::RENDER_ATTACHMENT | TextureUsages::TEXTURE_BINDING,
572                view_formats: &[],
573            };
574
575            let texture_a_vec = texture_as
576                .entry(camera.target.clone())
577                .or_insert_with(|| {
578                    let mut texture_a_vec = vec![];
579                    for i in 0..mip_count {
580                        let label = format!("bloom_texture_a_{i}");
581                        texture_descriptor.label = Some(Box::leak(label.into_boxed_str()));
582                        texture_a_vec
583                            .push(texture_cache.get(&render_device, texture_descriptor.clone()));
584                    }
585                    texture_a_vec
586                })
587                .clone();
588
589            let texture_b_vec = texture_bs
590                .entry(camera.target.clone())
591                .or_insert_with(|| {
592                    let mut texture_b_vec = vec![];
593                    for i in 0..mip_count {
594                        let label = format!("bloom_texture_b_{i}");
595                        texture_descriptor.label = Some(Box::leak(label.into_boxed_str()));
596                        texture_b_vec
597                            .push(texture_cache.get(&render_device, texture_descriptor.clone()));
598                    }
599                    texture_b_vec
600                })
601                .clone();
602
603            commands.entity(entity).insert(BloomTextures {
604                textures_a: texture_a_vec,
605                textures_b: texture_b_vec,
606                mip_count,
607            });
608        }
609    }
610}
611
612#[derive(ShaderType)]
613struct BloomUniform {
614    threshold: f32,
615    knee: f32,
616    scale: f32,
617    intensity: f32,
618}
619
620#[derive(Resource, Default)]
621struct BloomUniforms {
622    uniforms: DynamicUniformBuffer<BloomUniform>,
623}
624
625#[derive(Component)]
626struct BloomUniformIndex(u32);
627
628fn prepare_bloom_uniforms(
629    mut commands: Commands,
630    render_device: Res<RenderDevice>,
631    render_queue: Res<RenderQueue>,
632    mut bloom_uniforms: ResMut<BloomUniforms>,
633    bloom_query: Query<(Entity, &ExtractedCamera, &BloomSettings)>,
634) {
635    bloom_uniforms.uniforms.clear();
636
637    let entities = bloom_query
638        .iter()
639        .filter_map(|(entity, camera, settings)| {
640            let size = match camera.physical_viewport_size {
641                Some(size) => size,
642                None => return None,
643            };
644            let min_view = size.x.min(size.y) / 2;
645            let mip_count = calculate_mip_count(min_view);
646            let scale = (min_view / 2u32.pow(mip_count)) as f32 / 8.0;
647
648            let uniform = BloomUniform {
649                threshold: settings.threshold,
650                knee: settings.knee,
651                scale: settings.scale * scale,
652                intensity: settings.intensity,
653            };
654            let index = bloom_uniforms.uniforms.push(uniform);
655            Some((entity, (BloomUniformIndex(index))))
656        })
657        .collect::<Vec<_>>();
658    commands.insert_or_spawn_batch(entities);
659
660    bloom_uniforms
661        .uniforms
662        .write_buffer(&render_device, &render_queue);
663}
664
665#[derive(Component)]
666struct BloomBindGroups {
667    prefilter_bind_group: BindGroup,
668    downsampling_bind_groups: Box<[BindGroup]>,
669    upsampling_bind_groups: Box<[BindGroup]>,
670    upsampling_final_bind_group: BindGroup,
671}
672
673fn queue_bloom_bind_groups(
674    mut commands: Commands,
675    render_device: Res<RenderDevice>,
676    pipelines: Res<BloomPipelines>,
677    uniforms: Res<BloomUniforms>,
678    views: Query<(Entity, &ViewTarget, &BloomTextures)>,
679) {
680    if let Some(uniforms) = uniforms.uniforms.binding() {
681        for (entity, view_target, textures) in &views {
682            let prefilter_bind_group = render_device.create_bind_group(&BindGroupDescriptor {
683                label: Some("bloom_prefilter_bind_group"),
684                layout: &pipelines.downsampling_bind_group_layout,
685                entries: &[
686                    BindGroupEntry {
687                        binding: 0,
688                        resource: BindingResource::TextureView(view_target.main_texture()),
689                    },
690                    BindGroupEntry {
691                        binding: 1,
692                        resource: BindingResource::Sampler(&pipelines.sampler),
693                    },
694                    BindGroupEntry {
695                        binding: 2,
696                        resource: uniforms.clone(),
697                    },
698                ],
699            });
700
701            let bind_group_count = textures.mip_count as usize - 1;
702
703            let mut downsampling_bind_groups = Vec::with_capacity(bind_group_count);
704            for mip in 1..textures.mip_count {
705                let bind_group = render_device.create_bind_group(&BindGroupDescriptor {
706                    label: Some("bloom_downsampling_bind_group"),
707                    layout: &pipelines.downsampling_bind_group_layout,
708                    entries: &[
709                        BindGroupEntry {
710                            binding: 0,
711                            resource: BindingResource::TextureView(&BloomTextures::texture_view(
712                                &textures.textures_a[mip as usize - 1],
713                            )),
714                        },
715                        BindGroupEntry {
716                            binding: 1,
717                            resource: BindingResource::Sampler(&pipelines.sampler),
718                        },
719                        BindGroupEntry {
720                            binding: 2,
721                            resource: uniforms.clone(),
722                        },
723                    ],
724                });
725
726                downsampling_bind_groups.push(bind_group);
727            }
728
729            let mut upsampling_bind_groups = Vec::with_capacity(bind_group_count);
730            for mip in 1..textures.mip_count {
731                let up = BloomTextures::texture_view(&textures.textures_a[mip as usize - 1]);
732                let org = BloomTextures::texture_view(if mip == textures.mip_count - 1 {
733                    &textures.textures_a[mip as usize]
734                } else {
735                    &textures.textures_b[mip as usize]
736                });
737
738                let bind_group = render_device.create_bind_group(&BindGroupDescriptor {
739                    label: Some("bloom_upsampling_bind_group"),
740                    layout: &pipelines.upsampling_bind_group_layout,
741                    entries: &[
742                        BindGroupEntry {
743                            binding: 0,
744                            resource: BindingResource::TextureView(&org),
745                        },
746                        BindGroupEntry {
747                            binding: 1,
748                            resource: BindingResource::Sampler(&pipelines.sampler),
749                        },
750                        BindGroupEntry {
751                            binding: 2,
752                            resource: uniforms.clone(),
753                        },
754                        BindGroupEntry {
755                            binding: 3,
756                            resource: BindingResource::TextureView(&up),
757                        },
758                    ],
759                });
760
761                upsampling_bind_groups.push(bind_group);
762            }
763
764            let upsampling_final_bind_group =
765                render_device.create_bind_group(&BindGroupDescriptor {
766                    label: Some("bloom_upsampling_final_bind_group"),
767                    layout: &pipelines.downsampling_bind_group_layout,
768                    entries: &[
769                        BindGroupEntry {
770                            binding: 0,
771                            resource: BindingResource::TextureView(&BloomTextures::texture_view(
772                                &textures.textures_b[0],
773                            )),
774                        },
775                        BindGroupEntry {
776                            binding: 1,
777                            resource: BindingResource::Sampler(&pipelines.sampler),
778                        },
779                        BindGroupEntry {
780                            binding: 2,
781                            resource: uniforms.clone(),
782                        },
783                    ],
784                });
785
786            commands.entity(entity).insert(BloomBindGroups {
787                prefilter_bind_group,
788                downsampling_bind_groups: downsampling_bind_groups.into_boxed_slice(),
789                upsampling_bind_groups: upsampling_bind_groups.into_boxed_slice(),
790                upsampling_final_bind_group,
791            });
792        }
793    }
794}
795
796fn calculate_mip_count(min_view: u32) -> u32 {
797    ((min_view as f32).log2().round() as i32 - 3).max(1) as u32
798}