1use core::array;
7
8use crate::mip_generation::DownsampleShaders;
9
10use bevy_asset::Handle;
11use bevy_derive::{Deref, DerefMut};
12use bevy_ecs::{
13 component::Component,
14 entity::Entity,
15 prelude::Without,
16 query::{Or, With},
17 resource::Resource,
18 system::{Commands, Local, Query, Res, ResMut},
19};
20use bevy_log::debug;
21use bevy_math::{uvec2, UVec2, Vec4Swizzles as _};
22use bevy_render::{
23 batching::gpu_preprocessing::GpuPreprocessingSupport,
24 diagnostic::RecordDiagnostics,
25 occlusion_culling::{
26 OcclusionCulling, OcclusionCullingSubview, OcclusionCullingSubviewEntities,
27 },
28 render_resource::{
29 binding_types::{sampler, texture_2d, texture_2d_multisampled, texture_storage_2d},
30 BindGroup, BindGroupEntries, BindGroupLayout, BindGroupLayoutDescriptor,
31 BindGroupLayoutEntries, CachedComputePipelineId, ComputePassDescriptor, ComputePipeline,
32 ComputePipelineDescriptor, Extent3d, IntoBinding, PipelineCache, Sampler,
33 SamplerBindingType, SamplerDescriptor, ShaderStages, SpecializedComputePipeline,
34 SpecializedComputePipelines, StorageTextureAccess, TextureAspect, TextureDescriptor,
35 TextureDimension, TextureFormat, TextureSampleType, TextureUsages, TextureView,
36 TextureViewDescriptor, TextureViewDimension,
37 },
38 renderer::{RenderContext, RenderDevice, ViewQuery},
39 texture::TextureCache,
40 view::{ExtractedView, NoIndirectDrawing, ViewDepthStencilTexture},
41};
42use bevy_shader::Shader;
43use bevy_utils::default;
44use bitflags::bitflags;
45
46pub const DEPTH_PYRAMID_MIP_COUNT: usize = 12;
51
52pub fn early_downsample_depth(
65 view: ViewQuery<(
66 &ViewDepthPyramid,
67 &ViewDownsampleDepthBindGroup,
68 &ViewDepthStencilTexture,
69 Option<&OcclusionCullingSubviewEntities>,
70 )>,
71 shadow_view_query: Query<(
72 &ViewDepthPyramid,
73 &ViewDownsampleDepthBindGroup,
74 &OcclusionCullingSubview,
75 )>,
76 downsample_depth_pipelines: Option<Res<DownsampleDepthPipelines>>,
77 pipeline_cache: Res<PipelineCache>,
78 mut ctx: RenderContext,
79) {
80 let Some(downsample_depth_pipelines) = downsample_depth_pipelines.as_deref() else {
81 return;
82 };
83
84 let (
85 view_depth_pyramid,
86 view_downsample_depth_bind_group,
87 view_depth_texture,
88 maybe_view_light_entities,
89 ) = view.into_inner();
90
91 let diagnostics = ctx.diagnostic_recorder();
92 let diagnostics = diagnostics.as_deref();
93 let time_span = diagnostics.time_span(
94 ctx.command_encoder(),
95 "mip_generation::early_downsample_depth",
96 );
97
98 downsample_depth(
100 "early_downsample_depth",
101 &mut ctx,
102 downsample_depth_pipelines,
103 &pipeline_cache,
104 view_depth_pyramid,
105 view_downsample_depth_bind_group,
106 uvec2(
107 view_depth_texture.texture().width(),
108 view_depth_texture.texture().height(),
109 ),
110 view_depth_texture.texture().sample_count(),
111 );
112
113 if let Some(view_light_entities) = maybe_view_light_entities {
115 for &view_light_entity in &view_light_entities.0 {
116 let Ok((view_depth_pyramid, view_downsample_depth_bind_group, occlusion_culling)) =
117 shadow_view_query.get(view_light_entity)
118 else {
119 continue;
120 };
121 downsample_depth(
122 "early_downsample_depth",
123 &mut ctx,
124 downsample_depth_pipelines,
125 &pipeline_cache,
126 view_depth_pyramid,
127 view_downsample_depth_bind_group,
128 UVec2::splat(occlusion_culling.depth_texture_size),
129 1,
130 );
131 }
132 }
133 time_span.end(ctx.command_encoder());
134}
135
136pub fn late_downsample_depth(
148 view: ViewQuery<(
149 &ViewDepthPyramid,
150 &ViewDownsampleDepthBindGroup,
151 &ViewDepthStencilTexture,
152 Option<&OcclusionCullingSubviewEntities>,
153 )>,
154 shadow_view_query: Query<(
155 &ViewDepthPyramid,
156 &ViewDownsampleDepthBindGroup,
157 &OcclusionCullingSubview,
158 )>,
159 downsample_depth_pipelines: Option<Res<DownsampleDepthPipelines>>,
160 pipeline_cache: Res<PipelineCache>,
161 mut ctx: RenderContext,
162) {
163 let Some(downsample_depth_pipelines) = downsample_depth_pipelines.as_deref() else {
164 return;
165 };
166
167 let (
168 view_depth_pyramid,
169 view_downsample_depth_bind_group,
170 view_depth_texture,
171 maybe_view_light_entities,
172 ) = view.into_inner();
173
174 downsample_depth(
176 "late_downsample_depth",
177 &mut ctx,
178 downsample_depth_pipelines,
179 &pipeline_cache,
180 view_depth_pyramid,
181 view_downsample_depth_bind_group,
182 uvec2(
183 view_depth_texture.texture().width(),
184 view_depth_texture.texture().height(),
185 ),
186 view_depth_texture.texture().sample_count(),
187 );
188
189 if let Some(view_light_entities) = maybe_view_light_entities {
191 for &view_light_entity in &view_light_entities.0 {
192 let Ok((view_depth_pyramid, view_downsample_depth_bind_group, occlusion_culling)) =
193 shadow_view_query.get(view_light_entity)
194 else {
195 continue;
196 };
197 downsample_depth(
198 "late_downsample_depth",
199 &mut ctx,
200 downsample_depth_pipelines,
201 &pipeline_cache,
202 view_depth_pyramid,
203 view_downsample_depth_bind_group,
204 UVec2::splat(occlusion_culling.depth_texture_size),
205 1,
206 );
207 }
208 }
209}
210
211fn downsample_depth(
214 label: &str,
215 ctx: &mut RenderContext,
216 downsample_depth_pipelines: &DownsampleDepthPipelines,
217 pipeline_cache: &PipelineCache,
218 view_depth_pyramid: &ViewDepthPyramid,
219 view_downsample_depth_bind_group: &ViewDownsampleDepthBindGroup,
220 view_size: UVec2,
221 sample_count: u32,
222) {
223 let (Some(first_downsample_depth_pipeline_id), Some(second_downsample_depth_pipeline_id)) =
230 (if sample_count > 1 {
231 (
232 downsample_depth_pipelines.first_multisample.pipeline_id,
233 downsample_depth_pipelines.second_multisample.pipeline_id,
234 )
235 } else {
236 (
237 downsample_depth_pipelines.first.pipeline_id,
238 downsample_depth_pipelines.second.pipeline_id,
239 )
240 })
241 else {
242 return;
243 };
244
245 let (Some(first_downsample_depth_pipeline), Some(second_downsample_depth_pipeline)) = (
247 pipeline_cache.get_compute_pipeline(first_downsample_depth_pipeline_id),
248 pipeline_cache.get_compute_pipeline(second_downsample_depth_pipeline_id),
249 ) else {
250 return;
251 };
252
253 view_depth_pyramid.downsample_depth_with_ctx(
255 label,
256 ctx,
257 view_size,
258 view_downsample_depth_bind_group,
259 first_downsample_depth_pipeline,
260 second_downsample_depth_pipeline,
261 );
262}
263
264#[derive(Resource)]
266pub struct DownsampleDepthPipeline {
267 pub bind_group_layout: BindGroupLayoutDescriptor,
269 pub pipeline_id: Option<CachedComputePipelineId>,
271 shader: Handle<Shader>,
273}
274
275impl DownsampleDepthPipeline {
276 fn new(
282 bind_group_layout: BindGroupLayoutDescriptor,
283 shader: Handle<Shader>,
284 ) -> DownsampleDepthPipeline {
285 DownsampleDepthPipeline {
286 bind_group_layout,
287 pipeline_id: None,
288 shader,
289 }
290 }
291}
292
293#[derive(Resource)]
295pub struct DownsampleDepthPipelines {
296 pub first: DownsampleDepthPipeline,
299 pub second: DownsampleDepthPipeline,
302 pub first_multisample: DownsampleDepthPipeline,
304 pub second_multisample: DownsampleDepthPipeline,
306 pub sampler: Sampler,
309}
310
311pub fn create_downsample_depth_pipelines(
314 mut commands: Commands,
315 render_device: Res<RenderDevice>,
316 pipeline_cache: Res<PipelineCache>,
317 mut specialized_compute_pipelines: ResMut<SpecializedComputePipelines<DownsampleDepthPipeline>>,
318 gpu_preprocessing_support: Res<GpuPreprocessingSupport>,
319 downsample_depth_shader: Res<DownsampleShaders>,
320 mut has_run: Local<bool>,
321) {
322 if *has_run {
327 return;
328 }
329 *has_run = true;
330
331 if !gpu_preprocessing_support.is_culling_supported() {
332 debug!("Downsample depth is not supported on this platform.");
333 return;
334 }
335
336 let standard_bind_group_layout = create_downsample_depth_bind_group_layout(false);
341 let multisampled_bind_group_layout = create_downsample_depth_bind_group_layout(true);
342
343 let sampler = render_device.create_sampler(&SamplerDescriptor {
345 label: Some("depth pyramid sampler"),
346 ..SamplerDescriptor::default()
347 });
348
349 let mut downsample_depth_pipelines = DownsampleDepthPipelines {
351 first: DownsampleDepthPipeline::new(
352 standard_bind_group_layout.clone(),
353 downsample_depth_shader.depth.clone(),
354 ),
355 second: DownsampleDepthPipeline::new(
356 standard_bind_group_layout.clone(),
357 downsample_depth_shader.depth.clone(),
358 ),
359 first_multisample: DownsampleDepthPipeline::new(
360 multisampled_bind_group_layout.clone(),
361 downsample_depth_shader.depth.clone(),
362 ),
363 second_multisample: DownsampleDepthPipeline::new(
364 multisampled_bind_group_layout.clone(),
365 downsample_depth_shader.depth.clone(),
366 ),
367 sampler,
368 };
369
370 downsample_depth_pipelines.first.pipeline_id = Some(specialized_compute_pipelines.specialize(
373 &pipeline_cache,
374 &downsample_depth_pipelines.first,
375 DownsampleDepthPipelineKey::empty(),
376 ));
377 downsample_depth_pipelines.second.pipeline_id = Some(specialized_compute_pipelines.specialize(
378 &pipeline_cache,
379 &downsample_depth_pipelines.second,
380 DownsampleDepthPipelineKey::SECOND_PHASE,
381 ));
382 downsample_depth_pipelines.first_multisample.pipeline_id =
383 Some(specialized_compute_pipelines.specialize(
384 &pipeline_cache,
385 &downsample_depth_pipelines.first_multisample,
386 DownsampleDepthPipelineKey::MULTISAMPLE,
387 ));
388 downsample_depth_pipelines.second_multisample.pipeline_id =
389 Some(specialized_compute_pipelines.specialize(
390 &pipeline_cache,
391 &downsample_depth_pipelines.second_multisample,
392 DownsampleDepthPipelineKey::SECOND_PHASE | DownsampleDepthPipelineKey::MULTISAMPLE,
393 ));
394
395 commands.insert_resource(downsample_depth_pipelines);
396}
397
398fn create_downsample_depth_bind_group_layout(is_multisampled: bool) -> BindGroupLayoutDescriptor {
400 BindGroupLayoutDescriptor::new(
401 if is_multisampled {
402 "downsample multisample depth bind group layout"
403 } else {
404 "downsample depth bind group layout"
405 },
406 &BindGroupLayoutEntries::sequential(
407 ShaderStages::COMPUTE,
408 (
409 if is_multisampled {
413 texture_2d_multisampled(TextureSampleType::Depth)
414 } else {
415 texture_2d(TextureSampleType::Depth)
416 },
417 texture_storage_2d(TextureFormat::R32Float, StorageTextureAccess::WriteOnly),
419 texture_storage_2d(TextureFormat::R32Float, StorageTextureAccess::WriteOnly),
420 texture_storage_2d(TextureFormat::R32Float, StorageTextureAccess::WriteOnly),
421 texture_storage_2d(TextureFormat::R32Float, StorageTextureAccess::WriteOnly),
422 texture_storage_2d(TextureFormat::R32Float, StorageTextureAccess::WriteOnly),
423 texture_storage_2d(TextureFormat::R32Float, StorageTextureAccess::ReadWrite),
424 texture_storage_2d(TextureFormat::R32Float, StorageTextureAccess::WriteOnly),
425 texture_storage_2d(TextureFormat::R32Float, StorageTextureAccess::WriteOnly),
426 texture_storage_2d(TextureFormat::R32Float, StorageTextureAccess::WriteOnly),
427 texture_storage_2d(TextureFormat::R32Float, StorageTextureAccess::WriteOnly),
428 texture_storage_2d(TextureFormat::R32Float, StorageTextureAccess::WriteOnly),
429 texture_storage_2d(TextureFormat::R32Float, StorageTextureAccess::WriteOnly),
430 sampler(SamplerBindingType::NonFiltering),
431 ),
432 ),
433 )
434}
435
436bitflags! {
437 #[derive(Clone, Copy, PartialEq, Eq, Hash)]
443 pub struct DownsampleDepthPipelineKey: u8 {
444 const MULTISAMPLE = 1;
446 const SECOND_PHASE = 2;
449 }
450}
451
452impl SpecializedComputePipeline for DownsampleDepthPipeline {
453 type Key = DownsampleDepthPipelineKey;
454
455 fn specialize(&self, key: Self::Key) -> ComputePipelineDescriptor {
456 let mut shader_defs = vec![];
457 if key.contains(DownsampleDepthPipelineKey::MULTISAMPLE) {
458 shader_defs.push("MULTISAMPLE".into());
459 }
460
461 let label = format!(
462 "downsample depth{}{} pipeline",
463 if key.contains(DownsampleDepthPipelineKey::MULTISAMPLE) {
464 " multisample"
465 } else {
466 ""
467 },
468 if key.contains(DownsampleDepthPipelineKey::SECOND_PHASE) {
469 " second phase"
470 } else {
471 " first phase"
472 }
473 )
474 .into();
475
476 ComputePipelineDescriptor {
477 label: Some(label),
478 layout: vec![self.bind_group_layout.clone()],
479 immediate_size: 4,
480 shader: self.shader.clone(),
481 shader_defs,
482 entry_point: Some(if key.contains(DownsampleDepthPipelineKey::SECOND_PHASE) {
483 "downsample_depth_second".into()
484 } else {
485 "downsample_depth_first".into()
486 }),
487 ..default()
488 }
489 }
490}
491
492#[derive(Resource, Deref, DerefMut)]
495pub struct DepthPyramidDummyTexture(TextureView);
496
497pub fn init_depth_pyramid_dummy_texture(mut commands: Commands, render_device: Res<RenderDevice>) {
498 commands.insert_resource(DepthPyramidDummyTexture(
499 create_depth_pyramid_dummy_texture(
500 &render_device,
501 "depth pyramid dummy texture",
502 "depth pyramid dummy texture view",
503 ),
504 ));
505}
506
507pub fn create_depth_pyramid_dummy_texture(
510 render_device: &RenderDevice,
511 texture_label: &'static str,
512 texture_view_label: &'static str,
513) -> TextureView {
514 render_device
515 .create_texture(&TextureDescriptor {
516 label: Some(texture_label),
517 size: Extent3d::default(),
518 mip_level_count: 1,
519 sample_count: 1,
520 dimension: TextureDimension::D2,
521 format: TextureFormat::R32Float,
522 usage: TextureUsages::STORAGE_BINDING,
523 view_formats: &[],
524 })
525 .create_view(&TextureViewDescriptor {
526 label: Some(texture_view_label),
527 format: Some(TextureFormat::R32Float),
528 dimension: Some(TextureViewDimension::D2),
529 usage: None,
530 aspect: TextureAspect::All,
531 base_mip_level: 0,
532 mip_level_count: Some(1),
533 base_array_layer: 0,
534 array_layer_count: Some(1),
535 })
536}
537
538#[derive(Component)]
543pub struct ViewDepthPyramid {
544 pub all_mips: TextureView,
546 pub mips: [TextureView; DEPTH_PYRAMID_MIP_COUNT],
548 pub mip_count: u32,
553}
554
555impl ViewDepthPyramid {
556 pub fn new(
558 render_device: &RenderDevice,
559 texture_cache: &mut TextureCache,
560 depth_pyramid_dummy_texture: &TextureView,
561 size: UVec2,
562 texture_label: &'static str,
563 texture_view_label: &'static str,
564 ) -> ViewDepthPyramid {
565 let depth_pyramid_size = Extent3d {
568 width: previous_power_of_two(size.x),
569 height: previous_power_of_two(size.y),
570 depth_or_array_layers: 1,
571 };
572
573 let depth_pyramid_mip_count = depth_pyramid_size.max_mips(TextureDimension::D2);
575
576 let depth_pyramid = texture_cache.get(
578 render_device,
579 TextureDescriptor {
580 label: Some(texture_label),
581 size: depth_pyramid_size,
582 mip_level_count: depth_pyramid_mip_count,
583 sample_count: 1,
584 dimension: TextureDimension::D2,
585 format: TextureFormat::R32Float,
586 usage: TextureUsages::STORAGE_BINDING | TextureUsages::TEXTURE_BINDING,
587 view_formats: &[],
588 },
589 );
590
591 let depth_pyramid_mips = array::from_fn(|i| {
593 if (i as u32) < depth_pyramid_mip_count {
594 depth_pyramid.texture.create_view(&TextureViewDescriptor {
595 label: Some(texture_view_label),
596 format: Some(TextureFormat::R32Float),
597 dimension: Some(TextureViewDimension::D2),
598 usage: None,
599 aspect: TextureAspect::All,
600 base_mip_level: i as u32,
601 mip_level_count: Some(1),
602 base_array_layer: 0,
603 array_layer_count: Some(1),
604 })
605 } else {
606 (*depth_pyramid_dummy_texture).clone()
607 }
608 });
609
610 let depth_pyramid_all_mips = depth_pyramid.default_view.clone();
612
613 Self {
614 all_mips: depth_pyramid_all_mips,
615 mips: depth_pyramid_mips,
616 mip_count: depth_pyramid_mip_count,
617 }
618 }
619
620 pub fn create_bind_group<'a, R>(
623 &'a self,
624 render_device: &RenderDevice,
625 label: &'static str,
626 bind_group_layout: &BindGroupLayout,
627 source_image: R,
628 sampler: &'a Sampler,
629 ) -> BindGroup
630 where
631 R: IntoBinding<'a>,
632 {
633 render_device.create_bind_group(
634 label,
635 bind_group_layout,
636 &BindGroupEntries::sequential((
637 source_image,
638 &self.mips[0],
639 &self.mips[1],
640 &self.mips[2],
641 &self.mips[3],
642 &self.mips[4],
643 &self.mips[5],
644 &self.mips[6],
645 &self.mips[7],
646 &self.mips[8],
647 &self.mips[9],
648 &self.mips[10],
649 &self.mips[11],
650 sampler,
651 )),
652 )
653 }
654
655 pub fn downsample_depth_with_ctx(
656 &self,
657 label: &str,
658 ctx: &mut RenderContext,
659 view_size: UVec2,
660 downsample_depth_bind_group: &BindGroup,
661 downsample_depth_first_pipeline: &ComputePipeline,
662 downsample_depth_second_pipeline: &ComputePipeline,
663 ) {
664 let virtual_view_size = uvec2(
676 (view_size.x + 1).next_power_of_two(),
677 (view_size.y + 1).next_power_of_two(),
678 );
679
680 let command_encoder = ctx.command_encoder();
681 let mut downsample_pass = command_encoder.begin_compute_pass(&ComputePassDescriptor {
682 label: Some(label),
683 timestamp_writes: None,
684 });
685 downsample_pass.set_pipeline(downsample_depth_first_pipeline);
686 downsample_pass.set_immediates(0, &self.mip_count.to_le_bytes());
688 downsample_pass.set_bind_group(0, downsample_depth_bind_group, &[]);
689 downsample_pass.dispatch_workgroups(
690 virtual_view_size.x.div_ceil(64),
691 virtual_view_size.y.div_ceil(64),
692 1,
693 );
694
695 if self.mip_count >= 7 {
696 downsample_pass.set_pipeline(downsample_depth_second_pipeline);
697 downsample_pass.dispatch_workgroups(1, 1, 1);
698 }
699 }
700}
701
702pub fn prepare_view_depth_pyramids(
704 mut commands: Commands,
705 render_device: Res<RenderDevice>,
706 mut texture_cache: ResMut<TextureCache>,
707 depth_pyramid_dummy_texture: Res<DepthPyramidDummyTexture>,
708 views: Query<(Entity, &ExtractedView), (With<OcclusionCulling>, Without<NoIndirectDrawing>)>,
709 stale_views: Query<Entity, (With<ViewDepthPyramid>, Without<OcclusionCulling>)>,
710) {
711 for view_entity in &stale_views {
713 commands
714 .entity(view_entity)
715 .remove::<(ViewDepthPyramid, ViewDownsampleDepthBindGroup)>();
716 }
717
718 for (view_entity, view) in &views {
719 commands.entity(view_entity).insert(ViewDepthPyramid::new(
720 &render_device,
721 &mut texture_cache,
722 &depth_pyramid_dummy_texture,
723 view.viewport.zw(),
724 "view depth pyramid texture",
725 "view depth pyramid texture view",
726 ));
727 }
728}
729
730#[derive(Component, Deref, DerefMut)]
735pub struct ViewDownsampleDepthBindGroup(BindGroup);
736
737pub fn prepare_downsample_depth_view_bind_groups(
740 mut commands: Commands,
741 render_device: Res<RenderDevice>,
742 downsample_depth_pipelines: Res<DownsampleDepthPipelines>,
743 pipeline_cache: Res<PipelineCache>,
744 view_depth_textures: Query<
745 (
746 Entity,
747 &ViewDepthPyramid,
748 Option<&ViewDepthStencilTexture>,
749 Option<&OcclusionCullingSubview>,
750 ),
751 Or<(With<ViewDepthStencilTexture>, With<OcclusionCullingSubview>)>,
752 >,
753) {
754 for (view_entity, view_depth_pyramid, view_depth_texture, shadow_occlusion_culling) in
755 &view_depth_textures
756 {
757 let depth_view =
758 view_depth_texture.map(|t| t.attachment.depth_stencil_views().depth_only_view());
759 let source_image = match (depth_view, shadow_occlusion_culling) {
760 (Some(Some(depth_view)), _) => depth_view,
761 (Some(None), _) => {
762 continue;
764 }
765 (None, Some(shadow_occlusion_culling)) => &shadow_occlusion_culling.depth_texture_view,
766 (None, None) => {
767 continue;
769 }
770 };
771 let is_multisampled = view_depth_texture
772 .is_some_and(|view_depth_texture| view_depth_texture.texture().sample_count() > 1);
773 commands
774 .entity(view_entity)
775 .insert(ViewDownsampleDepthBindGroup(
776 view_depth_pyramid.create_bind_group(
777 &render_device,
778 if is_multisampled {
779 "downsample multisample depth bind group"
780 } else {
781 "downsample depth bind group"
782 },
783 &pipeline_cache.get_bind_group_layout(if is_multisampled {
784 &downsample_depth_pipelines
785 .first_multisample
786 .bind_group_layout
787 } else {
788 &downsample_depth_pipelines.first.bind_group_layout
789 }),
790 source_image,
791 &downsample_depth_pipelines.sampler,
792 ),
793 ));
794 }
795}
796
797fn previous_power_of_two(x: u32) -> u32 {
800 1 << (31 - x.leading_zeros())
801}