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 occlusion_culling::{
25 OcclusionCulling, OcclusionCullingSubview, OcclusionCullingSubviewEntities,
26 },
27 render_resource::{
28 binding_types::{sampler, texture_2d, texture_2d_multisampled, texture_storage_2d},
29 BindGroup, BindGroupEntries, BindGroupLayout, BindGroupLayoutDescriptor,
30 BindGroupLayoutEntries, CachedComputePipelineId, ComputePassDescriptor, ComputePipeline,
31 ComputePipelineDescriptor, Extent3d, IntoBinding, PipelineCache, Sampler,
32 SamplerBindingType, SamplerDescriptor, ShaderStages, SpecializedComputePipeline,
33 SpecializedComputePipelines, StorageTextureAccess, TextureAspect, TextureDescriptor,
34 TextureDimension, TextureFormat, TextureSampleType, TextureUsages, TextureView,
35 TextureViewDescriptor, TextureViewDimension,
36 },
37 renderer::{RenderContext, RenderDevice, ViewQuery},
38 texture::TextureCache,
39 view::{ExtractedView, NoIndirectDrawing, ViewDepthTexture},
40};
41use bevy_shader::Shader;
42use bevy_utils::default;
43use bitflags::bitflags;
44
45pub const DEPTH_PYRAMID_MIP_COUNT: usize = 12;
50
51pub fn early_downsample_depth(
64 view: ViewQuery<(
65 &ViewDepthPyramid,
66 &ViewDownsampleDepthBindGroup,
67 &ViewDepthTexture,
68 Option<&OcclusionCullingSubviewEntities>,
69 )>,
70 shadow_view_query: Query<(
71 &ViewDepthPyramid,
72 &ViewDownsampleDepthBindGroup,
73 &OcclusionCullingSubview,
74 )>,
75 downsample_depth_pipelines: Option<Res<DownsampleDepthPipelines>>,
76 pipeline_cache: Res<PipelineCache>,
77 mut ctx: RenderContext,
78) {
79 let Some(downsample_depth_pipelines) = downsample_depth_pipelines.as_deref() else {
80 return;
81 };
82
83 let (
84 view_depth_pyramid,
85 view_downsample_depth_bind_group,
86 view_depth_texture,
87 maybe_view_light_entities,
88 ) = view.into_inner();
89
90 downsample_depth(
92 "early_downsample_depth",
93 &mut ctx,
94 downsample_depth_pipelines,
95 &pipeline_cache,
96 view_depth_pyramid,
97 view_downsample_depth_bind_group,
98 uvec2(
99 view_depth_texture.texture.width(),
100 view_depth_texture.texture.height(),
101 ),
102 view_depth_texture.texture.sample_count(),
103 );
104
105 if let Some(view_light_entities) = maybe_view_light_entities {
107 for &view_light_entity in &view_light_entities.0 {
108 let Ok((view_depth_pyramid, view_downsample_depth_bind_group, occlusion_culling)) =
109 shadow_view_query.get(view_light_entity)
110 else {
111 continue;
112 };
113 downsample_depth(
114 "early_downsample_depth",
115 &mut ctx,
116 downsample_depth_pipelines,
117 &pipeline_cache,
118 view_depth_pyramid,
119 view_downsample_depth_bind_group,
120 UVec2::splat(occlusion_culling.depth_texture_size),
121 1,
122 );
123 }
124 }
125}
126
127pub fn late_downsample_depth(
139 view: ViewQuery<(
140 &ViewDepthPyramid,
141 &ViewDownsampleDepthBindGroup,
142 &ViewDepthTexture,
143 Option<&OcclusionCullingSubviewEntities>,
144 )>,
145 shadow_view_query: Query<(
146 &ViewDepthPyramid,
147 &ViewDownsampleDepthBindGroup,
148 &OcclusionCullingSubview,
149 )>,
150 downsample_depth_pipelines: Option<Res<DownsampleDepthPipelines>>,
151 pipeline_cache: Res<PipelineCache>,
152 mut ctx: RenderContext,
153) {
154 let Some(downsample_depth_pipelines) = downsample_depth_pipelines.as_deref() else {
155 return;
156 };
157
158 let (
159 view_depth_pyramid,
160 view_downsample_depth_bind_group,
161 view_depth_texture,
162 maybe_view_light_entities,
163 ) = view.into_inner();
164
165 downsample_depth(
167 "late_downsample_depth",
168 &mut ctx,
169 downsample_depth_pipelines,
170 &pipeline_cache,
171 view_depth_pyramid,
172 view_downsample_depth_bind_group,
173 uvec2(
174 view_depth_texture.texture.width(),
175 view_depth_texture.texture.height(),
176 ),
177 view_depth_texture.texture.sample_count(),
178 );
179
180 if let Some(view_light_entities) = maybe_view_light_entities {
182 for &view_light_entity in &view_light_entities.0 {
183 let Ok((view_depth_pyramid, view_downsample_depth_bind_group, occlusion_culling)) =
184 shadow_view_query.get(view_light_entity)
185 else {
186 continue;
187 };
188 downsample_depth(
189 "late_downsample_depth",
190 &mut ctx,
191 downsample_depth_pipelines,
192 &pipeline_cache,
193 view_depth_pyramid,
194 view_downsample_depth_bind_group,
195 UVec2::splat(occlusion_culling.depth_texture_size),
196 1,
197 );
198 }
199 }
200}
201
202fn downsample_depth(
205 label: &str,
206 ctx: &mut RenderContext,
207 downsample_depth_pipelines: &DownsampleDepthPipelines,
208 pipeline_cache: &PipelineCache,
209 view_depth_pyramid: &ViewDepthPyramid,
210 view_downsample_depth_bind_group: &ViewDownsampleDepthBindGroup,
211 view_size: UVec2,
212 sample_count: u32,
213) {
214 let (Some(first_downsample_depth_pipeline_id), Some(second_downsample_depth_pipeline_id)) =
221 (if sample_count > 1 {
222 (
223 downsample_depth_pipelines.first_multisample.pipeline_id,
224 downsample_depth_pipelines.second_multisample.pipeline_id,
225 )
226 } else {
227 (
228 downsample_depth_pipelines.first.pipeline_id,
229 downsample_depth_pipelines.second.pipeline_id,
230 )
231 })
232 else {
233 return;
234 };
235
236 let (Some(first_downsample_depth_pipeline), Some(second_downsample_depth_pipeline)) = (
238 pipeline_cache.get_compute_pipeline(first_downsample_depth_pipeline_id),
239 pipeline_cache.get_compute_pipeline(second_downsample_depth_pipeline_id),
240 ) else {
241 return;
242 };
243
244 view_depth_pyramid.downsample_depth_with_ctx(
246 label,
247 ctx,
248 view_size,
249 view_downsample_depth_bind_group,
250 first_downsample_depth_pipeline,
251 second_downsample_depth_pipeline,
252 );
253}
254
255#[derive(Resource)]
257pub struct DownsampleDepthPipeline {
258 pub bind_group_layout: BindGroupLayoutDescriptor,
260 pub pipeline_id: Option<CachedComputePipelineId>,
262 shader: Handle<Shader>,
264}
265
266impl DownsampleDepthPipeline {
267 fn new(
273 bind_group_layout: BindGroupLayoutDescriptor,
274 shader: Handle<Shader>,
275 ) -> DownsampleDepthPipeline {
276 DownsampleDepthPipeline {
277 bind_group_layout,
278 pipeline_id: None,
279 shader,
280 }
281 }
282}
283
284#[derive(Resource)]
286pub struct DownsampleDepthPipelines {
287 pub first: DownsampleDepthPipeline,
290 pub second: DownsampleDepthPipeline,
293 pub first_multisample: DownsampleDepthPipeline,
295 pub second_multisample: DownsampleDepthPipeline,
297 pub sampler: Sampler,
300}
301
302pub fn create_downsample_depth_pipelines(
305 mut commands: Commands,
306 render_device: Res<RenderDevice>,
307 pipeline_cache: Res<PipelineCache>,
308 mut specialized_compute_pipelines: ResMut<SpecializedComputePipelines<DownsampleDepthPipeline>>,
309 gpu_preprocessing_support: Res<GpuPreprocessingSupport>,
310 downsample_depth_shader: Res<DownsampleShaders>,
311 mut has_run: Local<bool>,
312) {
313 if *has_run {
318 return;
319 }
320 *has_run = true;
321
322 if !gpu_preprocessing_support.is_culling_supported() {
323 debug!("Downsample depth is not supported on this platform.");
324 return;
325 }
326
327 let standard_bind_group_layout = create_downsample_depth_bind_group_layout(false);
332 let multisampled_bind_group_layout = create_downsample_depth_bind_group_layout(true);
333
334 let sampler = render_device.create_sampler(&SamplerDescriptor {
336 label: Some("depth pyramid sampler"),
337 ..SamplerDescriptor::default()
338 });
339
340 let mut downsample_depth_pipelines = DownsampleDepthPipelines {
342 first: DownsampleDepthPipeline::new(
343 standard_bind_group_layout.clone(),
344 downsample_depth_shader.depth.clone(),
345 ),
346 second: DownsampleDepthPipeline::new(
347 standard_bind_group_layout.clone(),
348 downsample_depth_shader.depth.clone(),
349 ),
350 first_multisample: DownsampleDepthPipeline::new(
351 multisampled_bind_group_layout.clone(),
352 downsample_depth_shader.depth.clone(),
353 ),
354 second_multisample: DownsampleDepthPipeline::new(
355 multisampled_bind_group_layout.clone(),
356 downsample_depth_shader.depth.clone(),
357 ),
358 sampler,
359 };
360
361 downsample_depth_pipelines.first.pipeline_id = Some(specialized_compute_pipelines.specialize(
364 &pipeline_cache,
365 &downsample_depth_pipelines.first,
366 DownsampleDepthPipelineKey::empty(),
367 ));
368 downsample_depth_pipelines.second.pipeline_id = Some(specialized_compute_pipelines.specialize(
369 &pipeline_cache,
370 &downsample_depth_pipelines.second,
371 DownsampleDepthPipelineKey::SECOND_PHASE,
372 ));
373 downsample_depth_pipelines.first_multisample.pipeline_id =
374 Some(specialized_compute_pipelines.specialize(
375 &pipeline_cache,
376 &downsample_depth_pipelines.first_multisample,
377 DownsampleDepthPipelineKey::MULTISAMPLE,
378 ));
379 downsample_depth_pipelines.second_multisample.pipeline_id =
380 Some(specialized_compute_pipelines.specialize(
381 &pipeline_cache,
382 &downsample_depth_pipelines.second_multisample,
383 DownsampleDepthPipelineKey::SECOND_PHASE | DownsampleDepthPipelineKey::MULTISAMPLE,
384 ));
385
386 commands.insert_resource(downsample_depth_pipelines);
387}
388
389fn create_downsample_depth_bind_group_layout(is_multisampled: bool) -> BindGroupLayoutDescriptor {
391 BindGroupLayoutDescriptor::new(
392 if is_multisampled {
393 "downsample multisample depth bind group layout"
394 } else {
395 "downsample depth bind group layout"
396 },
397 &BindGroupLayoutEntries::sequential(
398 ShaderStages::COMPUTE,
399 (
400 if is_multisampled {
404 texture_2d_multisampled(TextureSampleType::Depth)
405 } else {
406 texture_2d(TextureSampleType::Depth)
407 },
408 texture_storage_2d(TextureFormat::R32Float, StorageTextureAccess::WriteOnly),
410 texture_storage_2d(TextureFormat::R32Float, StorageTextureAccess::WriteOnly),
411 texture_storage_2d(TextureFormat::R32Float, StorageTextureAccess::WriteOnly),
412 texture_storage_2d(TextureFormat::R32Float, StorageTextureAccess::WriteOnly),
413 texture_storage_2d(TextureFormat::R32Float, StorageTextureAccess::WriteOnly),
414 texture_storage_2d(TextureFormat::R32Float, StorageTextureAccess::ReadWrite),
415 texture_storage_2d(TextureFormat::R32Float, StorageTextureAccess::WriteOnly),
416 texture_storage_2d(TextureFormat::R32Float, StorageTextureAccess::WriteOnly),
417 texture_storage_2d(TextureFormat::R32Float, StorageTextureAccess::WriteOnly),
418 texture_storage_2d(TextureFormat::R32Float, StorageTextureAccess::WriteOnly),
419 texture_storage_2d(TextureFormat::R32Float, StorageTextureAccess::WriteOnly),
420 texture_storage_2d(TextureFormat::R32Float, StorageTextureAccess::WriteOnly),
421 sampler(SamplerBindingType::NonFiltering),
422 ),
423 ),
424 )
425}
426
427bitflags! {
428 #[derive(Clone, Copy, PartialEq, Eq, Hash)]
434 pub struct DownsampleDepthPipelineKey: u8 {
435 const MULTISAMPLE = 1;
437 const SECOND_PHASE = 2;
440 }
441}
442
443impl SpecializedComputePipeline for DownsampleDepthPipeline {
444 type Key = DownsampleDepthPipelineKey;
445
446 fn specialize(&self, key: Self::Key) -> ComputePipelineDescriptor {
447 let mut shader_defs = vec![];
448 if key.contains(DownsampleDepthPipelineKey::MULTISAMPLE) {
449 shader_defs.push("MULTISAMPLE".into());
450 }
451
452 let label = format!(
453 "downsample depth{}{} pipeline",
454 if key.contains(DownsampleDepthPipelineKey::MULTISAMPLE) {
455 " multisample"
456 } else {
457 ""
458 },
459 if key.contains(DownsampleDepthPipelineKey::SECOND_PHASE) {
460 " second phase"
461 } else {
462 " first phase"
463 }
464 )
465 .into();
466
467 ComputePipelineDescriptor {
468 label: Some(label),
469 layout: vec![self.bind_group_layout.clone()],
470 immediate_size: 4,
471 shader: self.shader.clone(),
472 shader_defs,
473 entry_point: Some(if key.contains(DownsampleDepthPipelineKey::SECOND_PHASE) {
474 "downsample_depth_second".into()
475 } else {
476 "downsample_depth_first".into()
477 }),
478 ..default()
479 }
480 }
481}
482
483#[derive(Resource, Deref, DerefMut)]
486pub struct DepthPyramidDummyTexture(TextureView);
487
488pub fn init_depth_pyramid_dummy_texture(mut commands: Commands, render_device: Res<RenderDevice>) {
489 commands.insert_resource(DepthPyramidDummyTexture(
490 create_depth_pyramid_dummy_texture(
491 &render_device,
492 "depth pyramid dummy texture",
493 "depth pyramid dummy texture view",
494 ),
495 ));
496}
497
498pub fn create_depth_pyramid_dummy_texture(
501 render_device: &RenderDevice,
502 texture_label: &'static str,
503 texture_view_label: &'static str,
504) -> TextureView {
505 render_device
506 .create_texture(&TextureDescriptor {
507 label: Some(texture_label),
508 size: Extent3d::default(),
509 mip_level_count: 1,
510 sample_count: 1,
511 dimension: TextureDimension::D2,
512 format: TextureFormat::R32Float,
513 usage: TextureUsages::STORAGE_BINDING,
514 view_formats: &[],
515 })
516 .create_view(&TextureViewDescriptor {
517 label: Some(texture_view_label),
518 format: Some(TextureFormat::R32Float),
519 dimension: Some(TextureViewDimension::D2),
520 usage: None,
521 aspect: TextureAspect::All,
522 base_mip_level: 0,
523 mip_level_count: Some(1),
524 base_array_layer: 0,
525 array_layer_count: Some(1),
526 })
527}
528
529#[derive(Component)]
534pub struct ViewDepthPyramid {
535 pub all_mips: TextureView,
537 pub mips: [TextureView; DEPTH_PYRAMID_MIP_COUNT],
539 pub mip_count: u32,
544}
545
546impl ViewDepthPyramid {
547 pub fn new(
549 render_device: &RenderDevice,
550 texture_cache: &mut TextureCache,
551 depth_pyramid_dummy_texture: &TextureView,
552 size: UVec2,
553 texture_label: &'static str,
554 texture_view_label: &'static str,
555 ) -> ViewDepthPyramid {
556 let depth_pyramid_size = Extent3d {
559 width: previous_power_of_two(size.x),
560 height: previous_power_of_two(size.y),
561 depth_or_array_layers: 1,
562 };
563
564 let depth_pyramid_mip_count = depth_pyramid_size.max_mips(TextureDimension::D2);
566
567 let depth_pyramid = texture_cache.get(
569 render_device,
570 TextureDescriptor {
571 label: Some(texture_label),
572 size: depth_pyramid_size,
573 mip_level_count: depth_pyramid_mip_count,
574 sample_count: 1,
575 dimension: TextureDimension::D2,
576 format: TextureFormat::R32Float,
577 usage: TextureUsages::STORAGE_BINDING | TextureUsages::TEXTURE_BINDING,
578 view_formats: &[],
579 },
580 );
581
582 let depth_pyramid_mips = array::from_fn(|i| {
584 if (i as u32) < depth_pyramid_mip_count {
585 depth_pyramid.texture.create_view(&TextureViewDescriptor {
586 label: Some(texture_view_label),
587 format: Some(TextureFormat::R32Float),
588 dimension: Some(TextureViewDimension::D2),
589 usage: None,
590 aspect: TextureAspect::All,
591 base_mip_level: i as u32,
592 mip_level_count: Some(1),
593 base_array_layer: 0,
594 array_layer_count: Some(1),
595 })
596 } else {
597 (*depth_pyramid_dummy_texture).clone()
598 }
599 });
600
601 let depth_pyramid_all_mips = depth_pyramid.default_view.clone();
603
604 Self {
605 all_mips: depth_pyramid_all_mips,
606 mips: depth_pyramid_mips,
607 mip_count: depth_pyramid_mip_count,
608 }
609 }
610
611 pub fn create_bind_group<'a, R>(
614 &'a self,
615 render_device: &RenderDevice,
616 label: &'static str,
617 bind_group_layout: &BindGroupLayout,
618 source_image: R,
619 sampler: &'a Sampler,
620 ) -> BindGroup
621 where
622 R: IntoBinding<'a>,
623 {
624 render_device.create_bind_group(
625 label,
626 bind_group_layout,
627 &BindGroupEntries::sequential((
628 source_image,
629 &self.mips[0],
630 &self.mips[1],
631 &self.mips[2],
632 &self.mips[3],
633 &self.mips[4],
634 &self.mips[5],
635 &self.mips[6],
636 &self.mips[7],
637 &self.mips[8],
638 &self.mips[9],
639 &self.mips[10],
640 &self.mips[11],
641 sampler,
642 )),
643 )
644 }
645
646 pub fn downsample_depth_with_ctx(
647 &self,
648 label: &str,
649 ctx: &mut RenderContext,
650 view_size: UVec2,
651 downsample_depth_bind_group: &BindGroup,
652 downsample_depth_first_pipeline: &ComputePipeline,
653 downsample_depth_second_pipeline: &ComputePipeline,
654 ) {
655 let virtual_view_size = uvec2(
667 (view_size.x + 1).next_power_of_two(),
668 (view_size.y + 1).next_power_of_two(),
669 );
670
671 let command_encoder = ctx.command_encoder();
672 let mut downsample_pass = command_encoder.begin_compute_pass(&ComputePassDescriptor {
673 label: Some(label),
674 timestamp_writes: None,
675 });
676 downsample_pass.set_pipeline(downsample_depth_first_pipeline);
677 downsample_pass.set_immediates(0, &self.mip_count.to_le_bytes());
679 downsample_pass.set_bind_group(0, downsample_depth_bind_group, &[]);
680 downsample_pass.dispatch_workgroups(
681 virtual_view_size.x.div_ceil(64),
682 virtual_view_size.y.div_ceil(64),
683 1,
684 );
685
686 if self.mip_count >= 7 {
687 downsample_pass.set_pipeline(downsample_depth_second_pipeline);
688 downsample_pass.dispatch_workgroups(1, 1, 1);
689 }
690 }
691}
692
693pub fn prepare_view_depth_pyramids(
695 mut commands: Commands,
696 render_device: Res<RenderDevice>,
697 mut texture_cache: ResMut<TextureCache>,
698 depth_pyramid_dummy_texture: Res<DepthPyramidDummyTexture>,
699 views: Query<(Entity, &ExtractedView), (With<OcclusionCulling>, Without<NoIndirectDrawing>)>,
700 stale_views: Query<Entity, (With<ViewDepthPyramid>, Without<OcclusionCulling>)>,
701) {
702 for view_entity in &stale_views {
704 commands
705 .entity(view_entity)
706 .remove::<(ViewDepthPyramid, ViewDownsampleDepthBindGroup)>();
707 }
708
709 for (view_entity, view) in &views {
710 commands.entity(view_entity).insert(ViewDepthPyramid::new(
711 &render_device,
712 &mut texture_cache,
713 &depth_pyramid_dummy_texture,
714 view.viewport.zw(),
715 "view depth pyramid texture",
716 "view depth pyramid texture view",
717 ));
718 }
719}
720
721#[derive(Component, Deref, DerefMut)]
726pub struct ViewDownsampleDepthBindGroup(BindGroup);
727
728pub fn prepare_downsample_depth_view_bind_groups(
731 mut commands: Commands,
732 render_device: Res<RenderDevice>,
733 downsample_depth_pipelines: Res<DownsampleDepthPipelines>,
734 pipeline_cache: Res<PipelineCache>,
735 view_depth_textures: Query<
736 (
737 Entity,
738 &ViewDepthPyramid,
739 Option<&ViewDepthTexture>,
740 Option<&OcclusionCullingSubview>,
741 ),
742 Or<(With<ViewDepthTexture>, With<OcclusionCullingSubview>)>,
743 >,
744) {
745 for (view_entity, view_depth_pyramid, view_depth_texture, shadow_occlusion_culling) in
746 &view_depth_textures
747 {
748 let is_multisampled = view_depth_texture
749 .is_some_and(|view_depth_texture| view_depth_texture.texture.sample_count() > 1);
750 commands
751 .entity(view_entity)
752 .insert(ViewDownsampleDepthBindGroup(
753 view_depth_pyramid.create_bind_group(
754 &render_device,
755 if is_multisampled {
756 "downsample multisample depth bind group"
757 } else {
758 "downsample depth bind group"
759 },
760 &pipeline_cache.get_bind_group_layout(if is_multisampled {
761 &downsample_depth_pipelines
762 .first_multisample
763 .bind_group_layout
764 } else {
765 &downsample_depth_pipelines.first.bind_group_layout
766 }),
767 match (view_depth_texture, shadow_occlusion_culling) {
768 (Some(view_depth_texture), _) => view_depth_texture.view(),
769 (None, Some(shadow_occlusion_culling)) => {
770 &shadow_occlusion_culling.depth_texture_view
771 }
772 (None, None) => panic!("Should never happen"),
773 },
774 &downsample_depth_pipelines.sampler,
775 ),
776 ));
777 }
778}
779
780fn previous_power_of_two(x: u32) -> u32 {
783 1 << (31 - x.leading_zeros())
784}