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 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 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#[derive(Component, Reflect, Clone)]
117pub struct BloomSettings {
118 pub threshold: f32,
122
123 pub knee: f32,
125
126 pub scale: f32,
128
129 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 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 BindGroupLayoutEntry {
347 binding: 1,
348 ty: BindingType::Sampler(SamplerBindingType::Filtering),
349 visibility: ShaderStages::FRAGMENT,
350 count: None,
351 },
352 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 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 BindGroupLayoutEntry {
383 binding: 1,
384 ty: BindingType::Sampler(SamplerBindingType::Filtering),
385 visibility: ShaderStages::FRAGMENT,
386 count: None,
387 },
388 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 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}