Skip to main content

bevy_core_pipeline/mip_generation/experimental/
depth.rs

1//! Generation of hierarchical Z buffers for occlusion culling.
2//!
3//! Currently, this module only supports generation of hierarchical Z buffers
4//! for occlusion culling.
5
6use 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
45/// The maximum number of mip levels that we can produce.
46///
47/// 2^12 is 4096, so that's the maximum size of the depth buffer that we
48/// support.
49pub const DEPTH_PYRAMID_MIP_COUNT: usize = 12;
50
51/// Produces a hierarchical Z-buffer (depth pyramid) for occlusion culling.
52///
53/// This runs the single-pass downsampling (SPD) shader with the *min* filter in
54/// order to generate a series of mipmaps for the Z buffer. The resulting
55/// hierarchical Z-buffer can be used for occlusion culling.
56///
57/// The *early* downsample depth pass is the first hierarchical Z-buffer stage,
58/// which runs after the early prepass and before the late prepass. It prepares
59/// the Z-buffer for the bounding box tests that the late mesh preprocessing
60/// stage will perform.
61///
62/// This system won't do anything if occlusion culling isn't on.
63pub 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 for the main Z-buffer.
91    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    // Downsample depth for shadow maps that have occlusion culling enabled.
106    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
127/// Produces a hierarchical Z-buffer (depth pyramid) for occlusion culling.
128///
129/// This runs the single-pass downsampling (SPD) shader with the *min* filter in
130/// order to generate a series of mipmaps for the Z buffer. The resulting
131/// hierarchical Z-buffer can be used for occlusion culling.
132///
133/// The *late* downsample depth pass runs at the end of the main phase. It
134/// prepares the Z-buffer for the occlusion culling that the early mesh
135/// preprocessing phase of the *next* frame will perform.
136///
137/// This system won't do anything if occlusion culling isn't on.
138pub 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 for the main Z-buffer.
166    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    // Downsample depth for shadow maps that have occlusion culling enabled.
181    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
202/// Produces a depth pyramid from the current depth buffer for a single view.
203/// The resulting depth pyramid can be used for occlusion testing.
204fn 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    // Despite the name "single-pass downsampling", we actually need two
215    // passes because of the lack of `coherent` buffers in WGPU/WGSL.
216    // Between each pass, there's an implicit synchronization barrier.
217
218    // Fetch the appropriate pipeline ID, depending on whether the depth
219    // buffer is multisampled or not.
220    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    // Fetch the pipelines for the two passes.
237    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    // Run the depth downsampling.
245    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/// A single depth downsample pipeline.
256#[derive(Resource)]
257pub struct DownsampleDepthPipeline {
258    /// The bind group layout for this pipeline.
259    pub bind_group_layout: BindGroupLayoutDescriptor,
260    /// A handle that identifies the compiled shader.
261    pub pipeline_id: Option<CachedComputePipelineId>,
262    /// The shader asset handle.
263    shader: Handle<Shader>,
264}
265
266impl DownsampleDepthPipeline {
267    /// Creates a new [`DownsampleDepthPipeline`] from a bind group layout and the downsample
268    /// shader.
269    ///
270    /// This doesn't actually specialize the pipeline; that must be done
271    /// afterward.
272    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/// Stores all depth buffer downsampling pipelines.
285#[derive(Resource)]
286pub struct DownsampleDepthPipelines {
287    /// The first pass of the pipeline, when the depth buffer is *not*
288    /// multisampled.
289    pub first: DownsampleDepthPipeline,
290    /// The second pass of the pipeline, when the depth buffer is *not*
291    /// multisampled.
292    pub second: DownsampleDepthPipeline,
293    /// The first pass of the pipeline, when the depth buffer is multisampled.
294    pub first_multisample: DownsampleDepthPipeline,
295    /// The second pass of the pipeline, when the depth buffer is multisampled.
296    pub second_multisample: DownsampleDepthPipeline,
297    /// The sampler that the depth downsampling shader uses to sample the depth
298    /// buffer.
299    pub sampler: Sampler,
300}
301
302/// Creates the [`DownsampleDepthPipelines`] if downsampling is supported on the
303/// current platform.
304pub 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    // Only run once.
314    // We can't use a `resource_exists` or similar run condition here because
315    // this function might fail to create downsample depth pipelines if the
316    // current platform doesn't support compute shaders.
317    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    // Create the bind group layouts. The bind group layouts are identical
328    // between the first and second passes, so the only thing we need to
329    // treat specially is the type of the first mip level (non-multisampled
330    // or multisampled).
331    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    // Create the depth pyramid sampler. This is shared among all shaders.
335    let sampler = render_device.create_sampler(&SamplerDescriptor {
336        label: Some("depth pyramid sampler"),
337        ..SamplerDescriptor::default()
338    });
339
340    // Initialize the pipelines.
341    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    // Specialize each pipeline with the appropriate
362    // `DownsampleDepthPipelineKey`.
363    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
389/// Creates a single bind group layout for the downsample depth pass.
390fn 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                // We only care about the multisample status of the depth buffer
401                // for the first mip level. After the first mip level is
402                // sampled, we drop to a single sample.
403                if is_multisampled {
404                    texture_2d_multisampled(TextureSampleType::Depth)
405                } else {
406                    texture_2d(TextureSampleType::Depth)
407                },
408                // All the mip levels follow:
409                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    /// Uniquely identifies a configuration of the downsample depth shader.
429    ///
430    /// Note that meshlets maintain their downsample depth shaders on their own
431    /// and don't use this infrastructure; thus there's no flag for meshlets in
432    /// here, even though the shader has defines for it.
433    #[derive(Clone, Copy, PartialEq, Eq, Hash)]
434    pub struct DownsampleDepthPipelineKey: u8 {
435        /// True if the depth buffer is multisampled.
436        const MULTISAMPLE = 1;
437        /// True if this shader is the second phase of the downsample depth
438        /// process; false if this shader is the first phase.
439        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/// Stores a placeholder texture that can be bound to a depth pyramid binding if
484/// no depth pyramid is needed.
485#[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
498/// Creates a placeholder texture that can be bound to a depth pyramid binding
499/// if no depth pyramid is needed.
500pub 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/// Stores a hierarchical Z-buffer for a view, which is a series of mipmaps
530/// useful for efficient occlusion culling.
531///
532/// This will only be present on a view when occlusion culling is enabled.
533#[derive(Component)]
534pub struct ViewDepthPyramid {
535    /// A texture view containing the entire depth texture.
536    pub all_mips: TextureView,
537    /// A series of texture views containing one mip level each.
538    pub mips: [TextureView; DEPTH_PYRAMID_MIP_COUNT],
539    /// The total number of mipmap levels.
540    ///
541    /// This is the base-2 logarithm of the greatest dimension of the depth
542    /// buffer, rounded up.
543    pub mip_count: u32,
544}
545
546impl ViewDepthPyramid {
547    /// Allocates a new depth pyramid for a depth buffer with the given size.
548    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        // Calculate the size of the depth pyramid. This is the size of the
557        // depth buffer rounded down to the previous power of two.
558        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        // Calculate the number of mip levels we need.
565        let depth_pyramid_mip_count = depth_pyramid_size.max_mips(TextureDimension::D2);
566
567        // Create the depth pyramid.
568        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        // Create individual views for each level of the depth pyramid.
583        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        // Create the view for the depth pyramid as a whole.
602        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    /// Creates a bind group that allows the depth buffer to be attached to the
612    /// `downsample_depth.wgsl` shader.
613    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        // We need to make sure that every mip level the single-pass
656        // downsampling (SPD) shader sees has lengths that are powers of two for
657        // correct conservative depth buffer downsampling. To do this, we
658        // maintain the fiction that we're downsampling a depth buffer scaled up
659        // so that it has side lengths rounded up to the next power of two. (If
660        // the depth buffer already has a side length that's a power of two,
661        // then we double it anyway; this ensures that we don't lose any
662        // precision in the top level of the depth pyramid.) The
663        // `downsample_depth` shader's `load_mip_0` function returns the value
664        // that sampling such a depth buffer would yield, without actually
665        // having to construct such a scaled depth buffer.
666        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        // Pass the mip count as an immediate, for simplicity.
678        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
693/// Creates depth pyramids for views that have occlusion culling enabled.
694pub 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    // Remove old resources when occlusion culling gets disabled
703    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/// The bind group that we use to attach the depth buffer and depth pyramid for
722/// a view to the `downsample_depth.wgsl` shader.
723///
724/// This will only be present for a view if occlusion culling is enabled.
725#[derive(Component, Deref, DerefMut)]
726pub struct ViewDownsampleDepthBindGroup(BindGroup);
727
728/// Creates the [`ViewDownsampleDepthBindGroup`]s for all views with occlusion
729/// culling enabled.
730pub 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
780/// Returns the previous power of two of x, or, if x is exactly a power of two,
781/// returns x unchanged.
782fn previous_power_of_two(x: u32) -> u32 {
783    1 << (31 - x.leading_zeros())
784}