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    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
46/// The maximum number of mip levels that we can produce.
47///
48/// 2^12 is 4096, so that's the maximum size of the depth buffer that we
49/// support.
50pub const DEPTH_PYRAMID_MIP_COUNT: usize = 12;
51
52/// Produces a hierarchical Z-buffer (depth pyramid) for occlusion culling.
53///
54/// This runs the single-pass downsampling (SPD) shader with the *min* filter in
55/// order to generate a series of mipmaps for the Z buffer. The resulting
56/// hierarchical Z-buffer can be used for occlusion culling.
57///
58/// The *early* downsample depth pass is the first hierarchical Z-buffer stage,
59/// which runs after the early prepass and before the late prepass. It prepares
60/// the Z-buffer for the bounding box tests that the late mesh preprocessing
61/// stage will perform.
62///
63/// This system won't do anything if occlusion culling isn't on.
64pub 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 for the main Z-buffer.
99    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    // Downsample depth for shadow maps that have occlusion culling enabled.
114    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
136/// Produces a hierarchical Z-buffer (depth pyramid) for occlusion culling.
137///
138/// This runs the single-pass downsampling (SPD) shader with the *min* filter in
139/// order to generate a series of mipmaps for the Z buffer. The resulting
140/// hierarchical Z-buffer can be used for occlusion culling.
141///
142/// The *late* downsample depth pass runs at the end of the main phase. It
143/// prepares the Z-buffer for the occlusion culling that the early mesh
144/// preprocessing phase of the *next* frame will perform.
145///
146/// This system won't do anything if occlusion culling isn't on.
147pub 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 for the main Z-buffer.
175    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    // Downsample depth for shadow maps that have occlusion culling enabled.
190    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
211/// Produces a depth pyramid from the current depth buffer for a single view.
212/// The resulting depth pyramid can be used for occlusion testing.
213fn 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    // Despite the name "single-pass downsampling", we actually need two
224    // passes because of the lack of `coherent` buffers in WGPU/WGSL.
225    // Between each pass, there's an implicit synchronization barrier.
226
227    // Fetch the appropriate pipeline ID, depending on whether the depth
228    // buffer is multisampled or not.
229    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    // Fetch the pipelines for the two passes.
246    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    // Run the depth downsampling.
254    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/// A single depth downsample pipeline.
265#[derive(Resource)]
266pub struct DownsampleDepthPipeline {
267    /// The bind group layout for this pipeline.
268    pub bind_group_layout: BindGroupLayoutDescriptor,
269    /// A handle that identifies the compiled shader.
270    pub pipeline_id: Option<CachedComputePipelineId>,
271    /// The shader asset handle.
272    shader: Handle<Shader>,
273}
274
275impl DownsampleDepthPipeline {
276    /// Creates a new [`DownsampleDepthPipeline`] from a bind group layout and the downsample
277    /// shader.
278    ///
279    /// This doesn't actually specialize the pipeline; that must be done
280    /// afterward.
281    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/// Stores all depth buffer downsampling pipelines.
294#[derive(Resource)]
295pub struct DownsampleDepthPipelines {
296    /// The first pass of the pipeline, when the depth buffer is *not*
297    /// multisampled.
298    pub first: DownsampleDepthPipeline,
299    /// The second pass of the pipeline, when the depth buffer is *not*
300    /// multisampled.
301    pub second: DownsampleDepthPipeline,
302    /// The first pass of the pipeline, when the depth buffer is multisampled.
303    pub first_multisample: DownsampleDepthPipeline,
304    /// The second pass of the pipeline, when the depth buffer is multisampled.
305    pub second_multisample: DownsampleDepthPipeline,
306    /// The sampler that the depth downsampling shader uses to sample the depth
307    /// buffer.
308    pub sampler: Sampler,
309}
310
311/// Creates the [`DownsampleDepthPipelines`] if downsampling is supported on the
312/// current platform.
313pub 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    // Only run once.
323    // We can't use a `resource_exists` or similar run condition here because
324    // this function might fail to create downsample depth pipelines if the
325    // current platform doesn't support compute shaders.
326    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    // Create the bind group layouts. The bind group layouts are identical
337    // between the first and second passes, so the only thing we need to
338    // treat specially is the type of the first mip level (non-multisampled
339    // or multisampled).
340    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    // Create the depth pyramid sampler. This is shared among all shaders.
344    let sampler = render_device.create_sampler(&SamplerDescriptor {
345        label: Some("depth pyramid sampler"),
346        ..SamplerDescriptor::default()
347    });
348
349    // Initialize the pipelines.
350    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    // Specialize each pipeline with the appropriate
371    // `DownsampleDepthPipelineKey`.
372    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
398/// Creates a single bind group layout for the downsample depth pass.
399fn 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                // We only care about the multisample status of the depth buffer
410                // for the first mip level. After the first mip level is
411                // sampled, we drop to a single sample.
412                if is_multisampled {
413                    texture_2d_multisampled(TextureSampleType::Depth)
414                } else {
415                    texture_2d(TextureSampleType::Depth)
416                },
417                // All the mip levels follow:
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                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    /// Uniquely identifies a configuration of the downsample depth shader.
438    ///
439    /// Note that meshlets maintain their downsample depth shaders on their own
440    /// and don't use this infrastructure; thus there's no flag for meshlets in
441    /// here, even though the shader has defines for it.
442    #[derive(Clone, Copy, PartialEq, Eq, Hash)]
443    pub struct DownsampleDepthPipelineKey: u8 {
444        /// True if the depth buffer is multisampled.
445        const MULTISAMPLE = 1;
446        /// True if this shader is the second phase of the downsample depth
447        /// process; false if this shader is the first phase.
448        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/// Stores a placeholder texture that can be bound to a depth pyramid binding if
493/// no depth pyramid is needed.
494#[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
507/// Creates a placeholder texture that can be bound to a depth pyramid binding
508/// if no depth pyramid is needed.
509pub 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/// Stores a hierarchical Z-buffer for a view, which is a series of mipmaps
539/// useful for efficient occlusion culling.
540///
541/// This will only be present on a view when occlusion culling is enabled.
542#[derive(Component)]
543pub struct ViewDepthPyramid {
544    /// A texture view containing the entire depth texture.
545    pub all_mips: TextureView,
546    /// A series of texture views containing one mip level each.
547    pub mips: [TextureView; DEPTH_PYRAMID_MIP_COUNT],
548    /// The total number of mipmap levels.
549    ///
550    /// This is the base-2 logarithm of the greatest dimension of the depth
551    /// buffer, rounded up.
552    pub mip_count: u32,
553}
554
555impl ViewDepthPyramid {
556    /// Allocates a new depth pyramid for a depth buffer with the given size.
557    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        // Calculate the size of the depth pyramid. This is the size of the
566        // depth buffer rounded down to the previous power of two.
567        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        // Calculate the number of mip levels we need.
574        let depth_pyramid_mip_count = depth_pyramid_size.max_mips(TextureDimension::D2);
575
576        // Create the depth pyramid.
577        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        // Create individual views for each level of the depth pyramid.
592        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        // Create the view for the depth pyramid as a whole.
611        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    /// Creates a bind group that allows the depth buffer to be attached to the
621    /// `downsample_depth.wesl` shader.
622    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        // We need to make sure that every mip level the single-pass
665        // downsampling (SPD) shader sees has lengths that are powers of two for
666        // correct conservative depth buffer downsampling. To do this, we
667        // maintain the fiction that we're downsampling a depth buffer scaled up
668        // so that it has side lengths rounded up to the next power of two. (If
669        // the depth buffer already has a side length that's a power of two,
670        // then we double it anyway; this ensures that we don't lose any
671        // precision in the top level of the depth pyramid.) The
672        // `downsample_depth` shader's `load_mip_0` function returns the value
673        // that sampling such a depth buffer would yield, without actually
674        // having to construct such a scaled depth buffer.
675        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        // Pass the mip count as an immediate, for simplicity.
687        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
702/// Creates depth pyramids for views that have occlusion culling enabled.
703pub 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    // Remove old resources when occlusion culling gets disabled
712    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/// The bind group that we use to attach the depth buffer and depth pyramid for
731/// a view to the `downsample_depth.wesl` shader.
732///
733/// This will only be present for a view if occlusion culling is enabled.
734#[derive(Component, Deref, DerefMut)]
735pub struct ViewDownsampleDepthBindGroup(BindGroup);
736
737/// Creates the [`ViewDownsampleDepthBindGroup`]s for all views with occlusion
738/// culling enabled.
739pub 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                // Depth texture doesn't has depth aspect
763                continue;
764            }
765            (None, Some(shadow_occlusion_culling)) => &shadow_occlusion_culling.depth_texture_view,
766            (None, None) => {
767                // No depth texture
768                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
797/// Returns the previous power of two of x, or, if x is exactly a power of two,
798/// returns x unchanged.
799fn previous_power_of_two(x: u32) -> u32 {
800    1 << (31 - x.leading_zeros())
801}