Path: blob/main/crates/bevy_pbr/src/render/gpu_preprocess.rs
9330 views
//! GPU mesh preprocessing.1//!2//! This is an optional pass that uses a compute shader to reduce the amount of3//! data that has to be transferred from the CPU to the GPU. When enabled,4//! instead of transferring [`MeshUniform`]s to the GPU, we transfer the smaller5//! [`MeshInputUniform`]s instead and use the GPU to calculate the remaining6//! derived fields in [`MeshUniform`].78use core::num::{NonZero, NonZeroU64};910use bevy_app::{App, Plugin};11use bevy_asset::{embedded_asset, load_embedded_asset, Handle};12use bevy_core_pipeline::{13deferred::node::late_deferred_prepass,14mip_generation::experimental::depth::{early_downsample_depth, ViewDepthPyramid},15prepass::{16node::{early_prepass, late_prepass},17DepthPrepass, PreviousViewData, PreviousViewUniformOffset, PreviousViewUniforms,18},19schedule::{Core3d, Core3dSystems},20};21use bevy_derive::{Deref, DerefMut};22use bevy_ecs::{23component::Component,24entity::Entity,25prelude::resource_exists,26query::{Has, Or, With, Without},27resource::Resource,28schedule::{common_conditions::any_match_filter, IntoScheduleConfigs as _},29system::{Commands, Query, Res, ResMut},30world::{FromWorld, World},31};32use bevy_log::warn_once;33use bevy_render::{34batching::gpu_preprocessing::{35BatchedInstanceBuffers, GpuOcclusionCullingWorkItemBuffers, GpuPreprocessingMode,36GpuPreprocessingSupport, IndirectBatchSet, IndirectParametersBuffers,37IndirectParametersCpuMetadata, IndirectParametersGpuMetadata, IndirectParametersIndexed,38IndirectParametersNonIndexed, LatePreprocessWorkItemIndirectParameters, PreprocessWorkItem,39PreprocessWorkItemBuffers, UntypedPhaseBatchedInstanceBuffers,40UntypedPhaseIndirectParametersBuffers,41},42diagnostic::RecordDiagnostics as _,43occlusion_culling::OcclusionCulling,44render_resource::{45binding_types::{storage_buffer, storage_buffer_read_only, texture_2d, uniform_buffer},46BindGroup, BindGroupEntries, BindGroupLayoutDescriptor, BindingResource, Buffer,47BufferBinding, CachedComputePipelineId, ComputePassDescriptor, ComputePipelineDescriptor,48DynamicBindGroupLayoutEntries, PipelineCache, RawBufferVec, ShaderStages, ShaderType,49SpecializedComputePipeline, SpecializedComputePipelines, TextureSampleType,50UninitBufferVec,51},52renderer::{RenderContext, RenderDevice, RenderQueue, ViewQuery},53settings::WgpuFeatures,54view::{ExtractedView, NoIndirectDrawing, ViewUniform, ViewUniformOffset, ViewUniforms},55Render, RenderApp, RenderSystems,56};57use bevy_shader::Shader;58use bevy_utils::{default, TypeIdMap};59use bitflags::bitflags;60use smallvec::{smallvec, SmallVec};61use tracing::warn;6263use crate::{MeshCullingData, MeshCullingDataBuffer, MeshInputUniform, MeshUniform};6465use super::{ShadowView, ViewLightEntities};6667/// The GPU workgroup size.68const WORKGROUP_SIZE: usize = 64;6970/// A plugin that builds mesh uniforms on GPU.71///72/// This will only be added if the platform supports compute shaders (e.g. not73/// on WebGL 2).74pub struct GpuMeshPreprocessPlugin {75/// Whether we're building [`MeshUniform`]s on GPU.76///77/// This requires compute shader support and so will be forcibly disabled if78/// the platform doesn't support those.79pub use_gpu_instance_buffer_builder: bool,80}8182/// The compute shader pipelines for the GPU mesh preprocessing and indirect83/// parameter building passes.84#[derive(Resource)]85pub struct PreprocessPipelines {86/// The pipeline used for CPU culling. This pipeline doesn't populate87/// indirect parameter metadata.88pub direct_preprocess: PreprocessPipeline,89/// The pipeline used for mesh preprocessing when GPU frustum culling is in90/// use, but occlusion culling isn't.91///92/// This pipeline populates indirect parameter metadata.93pub gpu_frustum_culling_preprocess: PreprocessPipeline,94/// The pipeline used for the first phase of occlusion culling.95///96/// This pipeline culls, transforms meshes, and populates indirect parameter97/// metadata.98pub early_gpu_occlusion_culling_preprocess: PreprocessPipeline,99/// The pipeline used for the second phase of occlusion culling.100///101/// This pipeline culls, transforms meshes, and populates indirect parameter102/// metadata.103pub late_gpu_occlusion_culling_preprocess: PreprocessPipeline,104/// The pipeline that builds indirect draw parameters for indexed meshes,105/// when frustum culling is enabled but occlusion culling *isn't* enabled.106pub gpu_frustum_culling_build_indexed_indirect_params: BuildIndirectParametersPipeline,107/// The pipeline that builds indirect draw parameters for non-indexed108/// meshes, when frustum culling is enabled but occlusion culling *isn't*109/// enabled.110pub gpu_frustum_culling_build_non_indexed_indirect_params: BuildIndirectParametersPipeline,111/// Compute shader pipelines for the early prepass phase that draws meshes112/// visible in the previous frame.113pub early_phase: PreprocessPhasePipelines,114/// Compute shader pipelines for the late prepass phase that draws meshes115/// that weren't visible in the previous frame, but became visible this116/// frame.117pub late_phase: PreprocessPhasePipelines,118/// Compute shader pipelines for the main color phase.119pub main_phase: PreprocessPhasePipelines,120}121122/// Compute shader pipelines for a specific phase: early, late, or main.123///124/// The distinction between these phases is relevant for occlusion culling.125#[derive(Clone)]126pub struct PreprocessPhasePipelines {127/// The pipeline that resets the indirect draw counts used in128/// `multi_draw_indirect_count` to 0 in preparation for a new pass.129pub reset_indirect_batch_sets: ResetIndirectBatchSetsPipeline,130/// The pipeline used for indexed indirect parameter building.131///132/// This pipeline converts indirect parameter metadata into indexed indirect133/// parameters.134pub gpu_occlusion_culling_build_indexed_indirect_params: BuildIndirectParametersPipeline,135/// The pipeline used for non-indexed indirect parameter building.136///137/// This pipeline converts indirect parameter metadata into non-indexed138/// indirect parameters.139pub gpu_occlusion_culling_build_non_indexed_indirect_params: BuildIndirectParametersPipeline,140}141142/// The pipeline for the GPU mesh preprocessing shader.143pub struct PreprocessPipeline {144/// The bind group layout for the compute shader.145pub bind_group_layout: BindGroupLayoutDescriptor,146/// The shader asset handle.147pub shader: Handle<Shader>,148/// The pipeline ID for the compute shader.149///150/// This gets filled in `prepare_preprocess_pipelines`.151pub pipeline_id: Option<CachedComputePipelineId>,152}153154/// The pipeline for the batch set count reset shader.155///156/// This shader resets the indirect batch set count to 0 for each view. It runs157/// in between every phase (early, late, and main).158#[derive(Clone)]159pub struct ResetIndirectBatchSetsPipeline {160/// The bind group layout for the compute shader.161pub bind_group_layout: BindGroupLayoutDescriptor,162/// The shader asset handle.163pub shader: Handle<Shader>,164/// The pipeline ID for the compute shader.165///166/// This gets filled in `prepare_preprocess_pipelines`.167pub pipeline_id: Option<CachedComputePipelineId>,168}169170/// The pipeline for the indirect parameter building shader.171#[derive(Clone)]172pub struct BuildIndirectParametersPipeline {173/// The bind group layout for the compute shader.174pub bind_group_layout: BindGroupLayoutDescriptor,175/// The shader asset handle.176pub shader: Handle<Shader>,177/// The pipeline ID for the compute shader.178///179/// This gets filled in `prepare_preprocess_pipelines`.180pub pipeline_id: Option<CachedComputePipelineId>,181}182183bitflags! {184/// Specifies variants of the mesh preprocessing shader.185#[derive(Clone, Copy, PartialEq, Eq, Hash)]186pub struct PreprocessPipelineKey: u8 {187/// Whether GPU frustum culling is in use.188///189/// This `#define`'s `FRUSTUM_CULLING` in the shader.190const FRUSTUM_CULLING = 1;191/// Whether GPU two-phase occlusion culling is in use.192///193/// This `#define`'s `OCCLUSION_CULLING` in the shader.194const OCCLUSION_CULLING = 2;195/// Whether this is the early phase of GPU two-phase occlusion culling.196///197/// This `#define`'s `EARLY_PHASE` in the shader.198const EARLY_PHASE = 4;199}200201/// Specifies variants of the indirect parameter building shader.202#[derive(Clone, Copy, PartialEq, Eq, Hash)]203pub struct BuildIndirectParametersPipelineKey: u8 {204/// Whether the indirect parameter building shader is processing indexed205/// meshes (those that have index buffers).206///207/// This defines `INDEXED` in the shader.208const INDEXED = 1;209/// Whether the GPU and driver supports `multi_draw_indirect_count`.210///211/// This defines `MULTI_DRAW_INDIRECT_COUNT_SUPPORTED` in the shader.212const MULTI_DRAW_INDIRECT_COUNT_SUPPORTED = 2;213/// Whether GPU two-phase occlusion culling is in use.214///215/// This `#define`'s `OCCLUSION_CULLING` in the shader.216const OCCLUSION_CULLING = 4;217/// Whether this is the early phase of GPU two-phase occlusion culling.218///219/// This `#define`'s `EARLY_PHASE` in the shader.220const EARLY_PHASE = 8;221/// Whether this is the late phase of GPU two-phase occlusion culling.222///223/// This `#define`'s `LATE_PHASE` in the shader.224const LATE_PHASE = 16;225/// Whether this is the phase that runs after the early and late phases,226/// and right before the main drawing logic, when GPU two-phase227/// occlusion culling is in use.228///229/// This `#define`'s `MAIN_PHASE` in the shader.230const MAIN_PHASE = 32;231}232}233234/// The compute shader bind group for the mesh preprocessing pass for each235/// render phase.236///237/// This goes on the view. It maps the [`core::any::TypeId`] of a render phase238/// (e.g. [`bevy_core_pipeline::core_3d::Opaque3d`]) to the239/// [`PhasePreprocessBindGroups`] for that phase.240#[derive(Component, Clone, Deref, DerefMut)]241pub struct PreprocessBindGroups(pub TypeIdMap<PhasePreprocessBindGroups>);242243/// The compute shader bind group for the mesh preprocessing step for a single244/// render phase on a single view.245#[derive(Clone)]246pub enum PhasePreprocessBindGroups {247/// The bind group used for the single invocation of the compute shader when248/// indirect drawing is *not* being used.249///250/// Because direct drawing doesn't require splitting the meshes into indexed251/// and non-indexed meshes, there's only one bind group in this case.252Direct(BindGroup),253254/// The bind groups used for the compute shader when indirect drawing is255/// being used, but occlusion culling isn't being used.256///257/// Because indirect drawing requires splitting the meshes into indexed and258/// non-indexed meshes, there are two bind groups here.259IndirectFrustumCulling {260/// The bind group for indexed meshes.261indexed: Option<BindGroup>,262/// The bind group for non-indexed meshes.263non_indexed: Option<BindGroup>,264},265266/// The bind groups used for the compute shader when indirect drawing is267/// being used, but occlusion culling isn't being used.268///269/// Because indirect drawing requires splitting the meshes into indexed and270/// non-indexed meshes, and because occlusion culling requires splitting271/// this phase into early and late versions, there are four bind groups272/// here.273IndirectOcclusionCulling {274/// The bind group for indexed meshes during the early mesh275/// preprocessing phase.276early_indexed: Option<BindGroup>,277/// The bind group for non-indexed meshes during the early mesh278/// preprocessing phase.279early_non_indexed: Option<BindGroup>,280/// The bind group for indexed meshes during the late mesh preprocessing281/// phase.282late_indexed: Option<BindGroup>,283/// The bind group for non-indexed meshes during the late mesh284/// preprocessing phase.285late_non_indexed: Option<BindGroup>,286},287}288289/// The bind groups for the compute shaders that reset indirect draw counts and290/// build indirect parameters.291///292/// There's one set of bind group for each phase. Phases are keyed off their293/// [`core::any::TypeId`].294#[derive(Resource, Default, Deref, DerefMut)]295pub struct BuildIndirectParametersBindGroups(pub TypeIdMap<PhaseBuildIndirectParametersBindGroups>);296297impl BuildIndirectParametersBindGroups {298/// Creates a new, empty [`BuildIndirectParametersBindGroups`] table.299pub fn new() -> BuildIndirectParametersBindGroups {300Self::default()301}302}303304/// The per-phase set of bind groups for the compute shaders that reset indirect305/// draw counts and build indirect parameters.306pub struct PhaseBuildIndirectParametersBindGroups {307/// The bind group for the `reset_indirect_batch_sets.wgsl` shader, for308/// indexed meshes.309reset_indexed_indirect_batch_sets: Option<BindGroup>,310/// The bind group for the `reset_indirect_batch_sets.wgsl` shader, for311/// non-indexed meshes.312reset_non_indexed_indirect_batch_sets: Option<BindGroup>,313/// The bind group for the `build_indirect_params.wgsl` shader, for indexed314/// meshes.315build_indexed_indirect: Option<BindGroup>,316/// The bind group for the `build_indirect_params.wgsl` shader, for317/// non-indexed meshes.318build_non_indexed_indirect: Option<BindGroup>,319}320321/// Stops the `GpuPreprocessNode` attempting to generate the buffer for this view322/// useful to avoid duplicating effort if the bind group is shared between views323#[derive(Component, Default)]324pub struct SkipGpuPreprocess;325326impl Plugin for GpuMeshPreprocessPlugin {327fn build(&self, app: &mut App) {328embedded_asset!(app, "mesh_preprocess.wgsl");329embedded_asset!(app, "reset_indirect_batch_sets.wgsl");330embedded_asset!(app, "build_indirect_params.wgsl");331}332333fn finish(&self, app: &mut App) {334let Some(render_app) = app.get_sub_app_mut(RenderApp) else {335return;336};337338// This plugin does nothing if GPU instance buffer building isn't in339// use.340let gpu_preprocessing_support = render_app.world().resource::<GpuPreprocessingSupport>();341if !self.use_gpu_instance_buffer_builder || !gpu_preprocessing_support.is_available() {342return;343}344345render_app346.init_resource::<PreprocessPipelines>()347.init_resource::<SpecializedComputePipelines<PreprocessPipeline>>()348.init_resource::<SpecializedComputePipelines<ResetIndirectBatchSetsPipeline>>()349.init_resource::<SpecializedComputePipelines<BuildIndirectParametersPipeline>>()350.add_systems(351Render,352(353prepare_preprocess_pipelines.in_set(RenderSystems::Prepare),354prepare_preprocess_bind_groups355.run_if(resource_exists::<BatchedInstanceBuffers<356MeshUniform,357MeshInputUniform358>>)359.in_set(RenderSystems::PrepareBindGroups),360write_mesh_culling_data_buffer.in_set(RenderSystems::PrepareResourcesFlush),361),362)363.add_systems(364Core3d,365(366(367clear_indirect_parameters_metadata,368early_gpu_preprocess,369early_prepass_build_indirect_parameters.run_if(any_match_filter::<(370With<PreprocessBindGroups>,371Without<SkipGpuPreprocess>,372Without<NoIndirectDrawing>,373Or<(With<DepthPrepass>, With<ShadowView>)>,374)>),375)376.chain()377.before(early_prepass),378(379late_gpu_preprocess,380late_prepass_build_indirect_parameters.run_if(any_match_filter::<(381With<PreprocessBindGroups>,382Without<SkipGpuPreprocess>,383Without<NoIndirectDrawing>,384Or<(With<DepthPrepass>, With<ShadowView>)>,385With<OcclusionCulling>,386)>),387)388.chain()389.after(early_downsample_depth)390.before(late_prepass),391main_build_indirect_parameters392.run_if(any_match_filter::<(393With<PreprocessBindGroups>,394Without<SkipGpuPreprocess>,395Without<NoIndirectDrawing>,396)>)397.after(late_prepass_build_indirect_parameters)398.after(late_deferred_prepass)399.before(Core3dSystems::MainPass),400),401);402}403}404405pub fn clear_indirect_parameters_metadata(406indirect_parameters_buffers: Option<Res<IndirectParametersBuffers>>,407mut ctx: RenderContext,408) {409let Some(indirect_parameters_buffers) = indirect_parameters_buffers else {410return;411};412413// Clear out each indexed and non-indexed GPU-side buffer.414for phase_indirect_parameters_buffers in indirect_parameters_buffers.values() {415if let Some(indexed_gpu_metadata_buffer) = phase_indirect_parameters_buffers416.indexed417.gpu_metadata_buffer()418{419ctx.command_encoder().clear_buffer(420indexed_gpu_metadata_buffer,4210,422Some(423phase_indirect_parameters_buffers.indexed.batch_count() as u64424* size_of::<IndirectParametersGpuMetadata>() as u64,425),426);427}428429if let Some(non_indexed_gpu_metadata_buffer) = phase_indirect_parameters_buffers430.non_indexed431.gpu_metadata_buffer()432{433ctx.command_encoder().clear_buffer(434non_indexed_gpu_metadata_buffer,4350,436Some(437phase_indirect_parameters_buffers.non_indexed.batch_count() as u64438* size_of::<IndirectParametersGpuMetadata>() as u64,439),440);441}442}443}444445pub fn early_gpu_preprocess(446current_view: ViewQuery<Option<&ViewLightEntities>, Without<SkipGpuPreprocess>>,447view_query: Query<448(449&ExtractedView,450Option<&PreprocessBindGroups>,451Option<&ViewUniformOffset>,452Has<NoIndirectDrawing>,453Has<OcclusionCulling>,454),455Without<SkipGpuPreprocess>,456>,457batched_instance_buffers: Res<BatchedInstanceBuffers<MeshUniform, MeshInputUniform>>,458pipeline_cache: Res<PipelineCache>,459preprocess_pipelines: Res<PreprocessPipelines>,460mut ctx: RenderContext,461) {462let diagnostics = ctx.diagnostic_recorder();463let diagnostics = diagnostics.as_deref();464465let command_encoder = ctx.command_encoder();466467let mut compute_pass = command_encoder.begin_compute_pass(&ComputePassDescriptor {468label: Some("early_mesh_preprocessing"),469timestamp_writes: None,470});471472let pass_span = diagnostics.pass_span(&mut compute_pass, "early_mesh_preprocessing");473474let view_entity = current_view.entity();475let shadow_cascade_views = current_view.into_inner();476let mut all_views: SmallVec<[_; 8]> = SmallVec::new();477all_views.push(view_entity);478if let Some(shadow_cascade_views) = shadow_cascade_views {479all_views.extend(shadow_cascade_views.lights.iter().copied());480}481482// Run the compute passes.483for view_entity in all_views {484let Ok((view, bind_groups, view_uniform_offset, no_indirect_drawing, occlusion_culling)) =485view_query.get(view_entity)486else {487continue;488};489490let Some(bind_groups) = bind_groups else {491continue;492};493let Some(view_uniform_offset) = view_uniform_offset else {494continue;495};496497// Select the right pipeline, depending on whether GPU culling is in498// use.499let maybe_pipeline_id = if no_indirect_drawing {500preprocess_pipelines.direct_preprocess.pipeline_id501} else if occlusion_culling {502preprocess_pipelines503.early_gpu_occlusion_culling_preprocess504.pipeline_id505} else {506preprocess_pipelines507.gpu_frustum_culling_preprocess508.pipeline_id509};510511// Fetch the pipeline.512let Some(preprocess_pipeline_id) = maybe_pipeline_id else {513warn!("The build mesh uniforms pipeline wasn't ready");514continue;515};516517let Some(preprocess_pipeline) = pipeline_cache.get_compute_pipeline(preprocess_pipeline_id)518else {519// This will happen while the pipeline is being compiled and is fine.520continue;521};522523compute_pass.set_pipeline(preprocess_pipeline);524525// Loop over each render phase.526for (phase_type_id, batched_phase_instance_buffers) in527&batched_instance_buffers.phase_instance_buffers528{529// Grab the work item buffers for this view.530let Some(work_item_buffers) = batched_phase_instance_buffers531.work_item_buffers532.get(&view.retained_view_entity)533else {534continue;535};536537// Fetch the bind group for the render phase.538let Some(phase_bind_groups) = bind_groups.get(phase_type_id) else {539continue;540};541542// Make sure the mesh preprocessing shader has access to the543// view info it needs to do culling and motion vector544// computation.545let dynamic_offsets = [view_uniform_offset.offset];546547// Are we drawing directly or indirectly?548match *phase_bind_groups {549PhasePreprocessBindGroups::Direct(ref bind_group) => {550// Invoke the mesh preprocessing shader to transform551// meshes only, but not cull.552let PreprocessWorkItemBuffers::Direct(work_item_buffer) = work_item_buffers553else {554continue;555};556compute_pass.set_bind_group(0, bind_group, &dynamic_offsets);557let workgroup_count = work_item_buffer.len().div_ceil(WORKGROUP_SIZE);558if workgroup_count > 0 {559compute_pass.dispatch_workgroups(workgroup_count as u32, 1, 1);560}561}562563PhasePreprocessBindGroups::IndirectFrustumCulling {564indexed: ref maybe_indexed_bind_group,565non_indexed: ref maybe_non_indexed_bind_group,566}567| PhasePreprocessBindGroups::IndirectOcclusionCulling {568early_indexed: ref maybe_indexed_bind_group,569early_non_indexed: ref maybe_non_indexed_bind_group,570..571} => {572// Invoke the mesh preprocessing shader to transform and573// cull the meshes.574let PreprocessWorkItemBuffers::Indirect {575indexed: indexed_buffer,576non_indexed: non_indexed_buffer,577..578} = work_item_buffers579else {580continue;581};582583// Transform and cull indexed meshes if there are any.584if let Some(indexed_bind_group) = maybe_indexed_bind_group {585if let PreprocessWorkItemBuffers::Indirect {586gpu_occlusion_culling:587Some(GpuOcclusionCullingWorkItemBuffers {588late_indirect_parameters_indexed_offset,589..590}),591..592} = *work_item_buffers593{594compute_pass.set_immediates(5950,596bytemuck::bytes_of(&late_indirect_parameters_indexed_offset),597);598}599600compute_pass.set_bind_group(0, indexed_bind_group, &dynamic_offsets);601let workgroup_count = indexed_buffer.len().div_ceil(WORKGROUP_SIZE);602if workgroup_count > 0 {603compute_pass.dispatch_workgroups(workgroup_count as u32, 1, 1);604}605}606607// Transform and cull non-indexed meshes if there are any.608if let Some(non_indexed_bind_group) = maybe_non_indexed_bind_group {609if let PreprocessWorkItemBuffers::Indirect {610gpu_occlusion_culling:611Some(GpuOcclusionCullingWorkItemBuffers {612late_indirect_parameters_non_indexed_offset,613..614}),615..616} = *work_item_buffers617{618compute_pass.set_immediates(6190,620bytemuck::bytes_of(&late_indirect_parameters_non_indexed_offset),621);622}623624compute_pass.set_bind_group(0, non_indexed_bind_group, &dynamic_offsets);625let workgroup_count = non_indexed_buffer.len().div_ceil(WORKGROUP_SIZE);626if workgroup_count > 0 {627compute_pass.dispatch_workgroups(workgroup_count as u32, 1, 1);628}629}630}631}632}633}634635pass_span.end(&mut compute_pass);636}637638pub fn late_gpu_preprocess(639current_view: ViewQuery<640(&ExtractedView, &PreprocessBindGroups, &ViewUniformOffset),641(642Without<SkipGpuPreprocess>,643Without<NoIndirectDrawing>,644With<OcclusionCulling>,645With<DepthPrepass>,646),647>,648batched_instance_buffers: Res<BatchedInstanceBuffers<MeshUniform, MeshInputUniform>>,649pipeline_cache: Res<PipelineCache>,650preprocess_pipelines: Res<PreprocessPipelines>,651mut ctx: RenderContext,652) {653let (view, bind_groups, view_uniform_offset) = current_view.into_inner();654655// Fetch the pipeline BEFORE starting diagnostic spans to avoid panic on early return656let maybe_pipeline_id = preprocess_pipelines657.late_gpu_occlusion_culling_preprocess658.pipeline_id;659660let Some(preprocess_pipeline_id) = maybe_pipeline_id else {661warn_once!("The build mesh uniforms pipeline wasn't ready");662return;663};664665let Some(preprocess_pipeline) = pipeline_cache.get_compute_pipeline(preprocess_pipeline_id)666else {667// This will happen while the pipeline is being compiled and is fine.668return;669};670671let diagnostics = ctx.diagnostic_recorder();672let diagnostics = diagnostics.as_deref();673674let command_encoder = ctx.command_encoder();675676let mut compute_pass = command_encoder.begin_compute_pass(&ComputePassDescriptor {677label: Some("late_mesh_preprocessing"),678timestamp_writes: None,679});680681let pass_span = diagnostics.pass_span(&mut compute_pass, "late_mesh_preprocessing");682683compute_pass.set_pipeline(preprocess_pipeline);684685// Loop over each phase. Because we built the phases in parallel,686// each phase has a separate set of instance buffers.687for (phase_type_id, batched_phase_instance_buffers) in688&batched_instance_buffers.phase_instance_buffers689{690let UntypedPhaseBatchedInstanceBuffers {691ref work_item_buffers,692ref late_indexed_indirect_parameters_buffer,693ref late_non_indexed_indirect_parameters_buffer,694..695} = *batched_phase_instance_buffers;696697// Grab the work item buffers for this view.698let Some(phase_work_item_buffers) = work_item_buffers.get(&view.retained_view_entity)699else {700continue;701};702703let (704PreprocessWorkItemBuffers::Indirect {705gpu_occlusion_culling:706Some(GpuOcclusionCullingWorkItemBuffers {707late_indirect_parameters_indexed_offset,708late_indirect_parameters_non_indexed_offset,709..710}),711..712},713Some(PhasePreprocessBindGroups::IndirectOcclusionCulling {714late_indexed: maybe_late_indexed_bind_group,715late_non_indexed: maybe_late_non_indexed_bind_group,716..717}),718Some(late_indexed_indirect_parameters_buffer),719Some(late_non_indexed_indirect_parameters_buffer),720) = (721phase_work_item_buffers,722bind_groups.get(phase_type_id),723late_indexed_indirect_parameters_buffer.buffer(),724late_non_indexed_indirect_parameters_buffer.buffer(),725)726else {727continue;728};729730let mut dynamic_offsets: SmallVec<[u32; 1]> = smallvec![];731dynamic_offsets.push(view_uniform_offset.offset);732733// If there's no space reserved for work items, then don't734// bother doing the dispatch, as there can't possibly be any735// meshes of the given class (indexed or non-indexed) in this736// phase.737738// Transform and cull indexed meshes if there are any.739if let Some(late_indexed_bind_group) = maybe_late_indexed_bind_group {740compute_pass.set_immediates(7410,742bytemuck::bytes_of(late_indirect_parameters_indexed_offset),743);744745compute_pass.set_bind_group(0, late_indexed_bind_group, &dynamic_offsets);746compute_pass.dispatch_workgroups_indirect(747late_indexed_indirect_parameters_buffer,748(*late_indirect_parameters_indexed_offset as u64)749* (size_of::<LatePreprocessWorkItemIndirectParameters>() as u64),750);751}752753// Transform and cull non-indexed meshes if there are any.754if let Some(late_non_indexed_bind_group) = maybe_late_non_indexed_bind_group {755compute_pass.set_immediates(7560,757bytemuck::bytes_of(late_indirect_parameters_non_indexed_offset),758);759760compute_pass.set_bind_group(0, late_non_indexed_bind_group, &dynamic_offsets);761compute_pass.dispatch_workgroups_indirect(762late_non_indexed_indirect_parameters_buffer,763(*late_indirect_parameters_non_indexed_offset as u64)764* (size_of::<LatePreprocessWorkItemIndirectParameters>() as u64),765);766}767}768769pass_span.end(&mut compute_pass);770}771772pub fn early_prepass_build_indirect_parameters(773preprocess_pipelines: Res<PreprocessPipelines>,774build_indirect_params_bind_groups: Option<Res<BuildIndirectParametersBindGroups>>,775pipeline_cache: Res<PipelineCache>,776indirect_parameters_buffers: Option<Res<IndirectParametersBuffers>>,777mut ctx: RenderContext,778) {779run_build_indirect_parameters(780&mut ctx,781build_indirect_params_bind_groups.as_deref(),782&pipeline_cache,783indirect_parameters_buffers.as_deref(),784&preprocess_pipelines.early_phase,785"early_prepass_indirect_parameters_building",786);787}788789pub fn late_prepass_build_indirect_parameters(790preprocess_pipelines: Res<PreprocessPipelines>,791build_indirect_params_bind_groups: Option<Res<BuildIndirectParametersBindGroups>>,792pipeline_cache: Res<PipelineCache>,793indirect_parameters_buffers: Option<Res<IndirectParametersBuffers>>,794mut ctx: RenderContext,795) {796run_build_indirect_parameters(797&mut ctx,798build_indirect_params_bind_groups.as_deref(),799&pipeline_cache,800indirect_parameters_buffers.as_deref(),801&preprocess_pipelines.late_phase,802"late_prepass_indirect_parameters_building",803);804}805806pub fn main_build_indirect_parameters(807preprocess_pipelines: Res<PreprocessPipelines>,808build_indirect_params_bind_groups: Option<Res<BuildIndirectParametersBindGroups>>,809pipeline_cache: Res<PipelineCache>,810indirect_parameters_buffers: Option<Res<IndirectParametersBuffers>>,811mut ctx: RenderContext,812) {813run_build_indirect_parameters(814&mut ctx,815build_indirect_params_bind_groups.as_deref(),816&pipeline_cache,817indirect_parameters_buffers.as_deref(),818&preprocess_pipelines.main_phase,819"main_indirect_parameters_building",820);821}822823fn run_build_indirect_parameters(824ctx: &mut RenderContext,825build_indirect_params_bind_groups: Option<&BuildIndirectParametersBindGroups>,826pipeline_cache: &PipelineCache,827indirect_parameters_buffers: Option<&IndirectParametersBuffers>,828preprocess_phase_pipelines: &PreprocessPhasePipelines,829label: &'static str,830) {831let Some(build_indirect_params_bind_groups) = build_indirect_params_bind_groups else {832return;833};834835let Some(indirect_parameters_buffers) = indirect_parameters_buffers else {836return;837};838839let command_encoder = ctx.command_encoder();840841let mut compute_pass = command_encoder.begin_compute_pass(&ComputePassDescriptor {842label: Some(label),843timestamp_writes: None,844});845846// Fetch the pipeline.847let (848Some(reset_indirect_batch_sets_pipeline_id),849Some(build_indexed_indirect_params_pipeline_id),850Some(build_non_indexed_indirect_params_pipeline_id),851) = (852preprocess_phase_pipelines853.reset_indirect_batch_sets854.pipeline_id,855preprocess_phase_pipelines856.gpu_occlusion_culling_build_indexed_indirect_params857.pipeline_id,858preprocess_phase_pipelines859.gpu_occlusion_culling_build_non_indexed_indirect_params860.pipeline_id,861)862else {863warn!("The build indirect parameters pipelines weren't ready");864return;865};866867let (868Some(reset_indirect_batch_sets_pipeline),869Some(build_indexed_indirect_params_pipeline),870Some(build_non_indexed_indirect_params_pipeline),871) = (872pipeline_cache.get_compute_pipeline(reset_indirect_batch_sets_pipeline_id),873pipeline_cache.get_compute_pipeline(build_indexed_indirect_params_pipeline_id),874pipeline_cache.get_compute_pipeline(build_non_indexed_indirect_params_pipeline_id),875)876else {877// This will happen while the pipeline is being compiled and is fine.878return;879};880881// Loop over each phase. As each has as separate set of buffers, we need to882// build indirect parameters individually for each phase.883for (phase_type_id, phase_build_indirect_params_bind_groups) in884build_indirect_params_bind_groups.iter()885{886let Some(phase_indirect_parameters_buffers) =887indirect_parameters_buffers.get(phase_type_id)888else {889continue;890};891892// Build indexed indirect parameters.893if let (894Some(reset_indexed_indirect_batch_sets_bind_group),895Some(build_indirect_indexed_params_bind_group),896) = (897&phase_build_indirect_params_bind_groups.reset_indexed_indirect_batch_sets,898&phase_build_indirect_params_bind_groups.build_indexed_indirect,899) {900compute_pass.set_pipeline(reset_indirect_batch_sets_pipeline);901compute_pass.set_bind_group(0, reset_indexed_indirect_batch_sets_bind_group, &[]);902let workgroup_count = phase_indirect_parameters_buffers903.batch_set_count(true)904.div_ceil(WORKGROUP_SIZE);905if workgroup_count > 0 {906compute_pass.dispatch_workgroups(workgroup_count as u32, 1, 1);907}908909compute_pass.set_pipeline(build_indexed_indirect_params_pipeline);910compute_pass.set_bind_group(0, build_indirect_indexed_params_bind_group, &[]);911let workgroup_count = phase_indirect_parameters_buffers912.indexed913.batch_count()914.div_ceil(WORKGROUP_SIZE);915if workgroup_count > 0 {916compute_pass.dispatch_workgroups(workgroup_count as u32, 1, 1);917}918}919920// Build non-indexed indirect parameters.921if let (922Some(reset_non_indexed_indirect_batch_sets_bind_group),923Some(build_indirect_non_indexed_params_bind_group),924) = (925&phase_build_indirect_params_bind_groups.reset_non_indexed_indirect_batch_sets,926&phase_build_indirect_params_bind_groups.build_non_indexed_indirect,927) {928compute_pass.set_pipeline(reset_indirect_batch_sets_pipeline);929compute_pass.set_bind_group(0, reset_non_indexed_indirect_batch_sets_bind_group, &[]);930let workgroup_count = phase_indirect_parameters_buffers931.batch_set_count(false)932.div_ceil(WORKGROUP_SIZE);933if workgroup_count > 0 {934compute_pass.dispatch_workgroups(workgroup_count as u32, 1, 1);935}936937compute_pass.set_pipeline(build_non_indexed_indirect_params_pipeline);938compute_pass.set_bind_group(0, build_indirect_non_indexed_params_bind_group, &[]);939let workgroup_count = phase_indirect_parameters_buffers940.non_indexed941.batch_count()942.div_ceil(WORKGROUP_SIZE);943if workgroup_count > 0 {944compute_pass.dispatch_workgroups(workgroup_count as u32, 1, 1);945}946}947}948}949950impl PreprocessPipelines {951/// Returns true if the preprocessing and indirect parameters pipelines have952/// been loaded or false otherwise.953pub(crate) fn pipelines_are_loaded(954&self,955pipeline_cache: &PipelineCache,956preprocessing_support: &GpuPreprocessingSupport,957) -> bool {958match preprocessing_support.max_supported_mode {959GpuPreprocessingMode::None => false,960GpuPreprocessingMode::PreprocessingOnly => {961self.direct_preprocess.is_loaded(pipeline_cache)962&& self963.gpu_frustum_culling_preprocess964.is_loaded(pipeline_cache)965}966GpuPreprocessingMode::Culling => {967self.direct_preprocess.is_loaded(pipeline_cache)968&& self969.gpu_frustum_culling_preprocess970.is_loaded(pipeline_cache)971&& self972.early_gpu_occlusion_culling_preprocess973.is_loaded(pipeline_cache)974&& self975.late_gpu_occlusion_culling_preprocess976.is_loaded(pipeline_cache)977&& self978.gpu_frustum_culling_build_indexed_indirect_params979.is_loaded(pipeline_cache)980&& self981.gpu_frustum_culling_build_non_indexed_indirect_params982.is_loaded(pipeline_cache)983&& self.early_phase.is_loaded(pipeline_cache)984&& self.late_phase.is_loaded(pipeline_cache)985&& self.main_phase.is_loaded(pipeline_cache)986}987}988}989}990991impl PreprocessPhasePipelines {992fn is_loaded(&self, pipeline_cache: &PipelineCache) -> bool {993self.reset_indirect_batch_sets.is_loaded(pipeline_cache)994&& self995.gpu_occlusion_culling_build_indexed_indirect_params996.is_loaded(pipeline_cache)997&& self998.gpu_occlusion_culling_build_non_indexed_indirect_params999.is_loaded(pipeline_cache)1000}1001}10021003impl PreprocessPipeline {1004fn is_loaded(&self, pipeline_cache: &PipelineCache) -> bool {1005self.pipeline_id1006.is_some_and(|pipeline_id| pipeline_cache.get_compute_pipeline(pipeline_id).is_some())1007}1008}10091010impl ResetIndirectBatchSetsPipeline {1011fn is_loaded(&self, pipeline_cache: &PipelineCache) -> bool {1012self.pipeline_id1013.is_some_and(|pipeline_id| pipeline_cache.get_compute_pipeline(pipeline_id).is_some())1014}1015}10161017impl BuildIndirectParametersPipeline {1018/// Returns true if this pipeline has been loaded into the pipeline cache or1019/// false otherwise.1020fn is_loaded(&self, pipeline_cache: &PipelineCache) -> bool {1021self.pipeline_id1022.is_some_and(|pipeline_id| pipeline_cache.get_compute_pipeline(pipeline_id).is_some())1023}1024}10251026impl SpecializedComputePipeline for PreprocessPipeline {1027type Key = PreprocessPipelineKey;10281029fn specialize(&self, key: Self::Key) -> ComputePipelineDescriptor {1030let mut shader_defs = vec!["WRITE_INDIRECT_PARAMETERS_METADATA".into()];1031if key.contains(PreprocessPipelineKey::FRUSTUM_CULLING) {1032shader_defs.push("INDIRECT".into());1033shader_defs.push("FRUSTUM_CULLING".into());1034}1035if key.contains(PreprocessPipelineKey::OCCLUSION_CULLING) {1036shader_defs.push("OCCLUSION_CULLING".into());1037if key.contains(PreprocessPipelineKey::EARLY_PHASE) {1038shader_defs.push("EARLY_PHASE".into());1039} else {1040shader_defs.push("LATE_PHASE".into());1041}1042}10431044ComputePipelineDescriptor {1045label: Some(1046format!(1047"mesh preprocessing ({})",1048if key.contains(1049PreprocessPipelineKey::OCCLUSION_CULLING1050| PreprocessPipelineKey::EARLY_PHASE1051) {1052"early GPU occlusion culling"1053} else if key.contains(PreprocessPipelineKey::OCCLUSION_CULLING) {1054"late GPU occlusion culling"1055} else if key.contains(PreprocessPipelineKey::FRUSTUM_CULLING) {1056"GPU frustum culling"1057} else {1058"direct"1059}1060)1061.into(),1062),1063layout: vec![self.bind_group_layout.clone()],1064immediate_size: if key.contains(PreprocessPipelineKey::OCCLUSION_CULLING) {106541066} else {106701068},1069shader: self.shader.clone(),1070shader_defs,1071..default()1072}1073}1074}10751076impl FromWorld for PreprocessPipelines {1077fn from_world(world: &mut World) -> Self {1078// GPU culling bind group parameters are a superset of those in the CPU1079// culling (direct) shader.1080let direct_bind_group_layout_entries = preprocess_direct_bind_group_layout_entries();1081let gpu_frustum_culling_bind_group_layout_entries = gpu_culling_bind_group_layout_entries();1082let gpu_early_occlusion_culling_bind_group_layout_entries =1083gpu_occlusion_culling_bind_group_layout_entries().extend_with_indices((1084(108511,1086storage_buffer::<PreprocessWorkItem>(/*has_dynamic_offset=*/ false),1087),1088(108912,1090storage_buffer::<LatePreprocessWorkItemIndirectParameters>(1091/*has_dynamic_offset=*/ false,1092),1093),1094));1095let gpu_late_occlusion_culling_bind_group_layout_entries =1096gpu_occlusion_culling_bind_group_layout_entries().extend_with_indices(((109712,1098storage_buffer_read_only::<LatePreprocessWorkItemIndirectParameters>(1099/*has_dynamic_offset=*/ false,1100),1101),));11021103let reset_indirect_batch_sets_bind_group_layout_entries =1104DynamicBindGroupLayoutEntries::sequential(1105ShaderStages::COMPUTE,1106(storage_buffer::<IndirectBatchSet>(false),),1107);11081109// Indexed and non-indexed bind group parameters share all the bind1110// group layout entries except the final one.1111let build_indexed_indirect_params_bind_group_layout_entries =1112build_indirect_params_bind_group_layout_entries()1113.extend_sequential((storage_buffer::<IndirectParametersIndexed>(false),));1114let build_non_indexed_indirect_params_bind_group_layout_entries =1115build_indirect_params_bind_group_layout_entries()1116.extend_sequential((storage_buffer::<IndirectParametersNonIndexed>(false),));11171118// Create the bind group layouts.1119let direct_bind_group_layout = BindGroupLayoutDescriptor::new(1120"build mesh uniforms direct bind group layout",1121&direct_bind_group_layout_entries,1122);1123let gpu_frustum_culling_bind_group_layout = BindGroupLayoutDescriptor::new(1124"build mesh uniforms GPU frustum culling bind group layout",1125&gpu_frustum_culling_bind_group_layout_entries,1126);1127let gpu_early_occlusion_culling_bind_group_layout = BindGroupLayoutDescriptor::new(1128"build mesh uniforms GPU early occlusion culling bind group layout",1129&gpu_early_occlusion_culling_bind_group_layout_entries,1130);1131let gpu_late_occlusion_culling_bind_group_layout = BindGroupLayoutDescriptor::new(1132"build mesh uniforms GPU late occlusion culling bind group layout",1133&gpu_late_occlusion_culling_bind_group_layout_entries,1134);1135let reset_indirect_batch_sets_bind_group_layout = BindGroupLayoutDescriptor::new(1136"reset indirect batch sets bind group layout",1137&reset_indirect_batch_sets_bind_group_layout_entries,1138);1139let build_indexed_indirect_params_bind_group_layout = BindGroupLayoutDescriptor::new(1140"build indexed indirect parameters bind group layout",1141&build_indexed_indirect_params_bind_group_layout_entries,1142);1143let build_non_indexed_indirect_params_bind_group_layout = BindGroupLayoutDescriptor::new(1144"build non-indexed indirect parameters bind group layout",1145&build_non_indexed_indirect_params_bind_group_layout_entries,1146);11471148let preprocess_shader = load_embedded_asset!(world, "mesh_preprocess.wgsl");1149let reset_indirect_batch_sets_shader =1150load_embedded_asset!(world, "reset_indirect_batch_sets.wgsl");1151let build_indirect_params_shader =1152load_embedded_asset!(world, "build_indirect_params.wgsl");11531154let preprocess_phase_pipelines = PreprocessPhasePipelines {1155reset_indirect_batch_sets: ResetIndirectBatchSetsPipeline {1156bind_group_layout: reset_indirect_batch_sets_bind_group_layout.clone(),1157shader: reset_indirect_batch_sets_shader,1158pipeline_id: None,1159},1160gpu_occlusion_culling_build_indexed_indirect_params: BuildIndirectParametersPipeline {1161bind_group_layout: build_indexed_indirect_params_bind_group_layout.clone(),1162shader: build_indirect_params_shader.clone(),1163pipeline_id: None,1164},1165gpu_occlusion_culling_build_non_indexed_indirect_params:1166BuildIndirectParametersPipeline {1167bind_group_layout: build_non_indexed_indirect_params_bind_group_layout.clone(),1168shader: build_indirect_params_shader.clone(),1169pipeline_id: None,1170},1171};11721173PreprocessPipelines {1174direct_preprocess: PreprocessPipeline {1175bind_group_layout: direct_bind_group_layout,1176shader: preprocess_shader.clone(),1177pipeline_id: None,1178},1179gpu_frustum_culling_preprocess: PreprocessPipeline {1180bind_group_layout: gpu_frustum_culling_bind_group_layout,1181shader: preprocess_shader.clone(),1182pipeline_id: None,1183},1184early_gpu_occlusion_culling_preprocess: PreprocessPipeline {1185bind_group_layout: gpu_early_occlusion_culling_bind_group_layout,1186shader: preprocess_shader.clone(),1187pipeline_id: None,1188},1189late_gpu_occlusion_culling_preprocess: PreprocessPipeline {1190bind_group_layout: gpu_late_occlusion_culling_bind_group_layout,1191shader: preprocess_shader,1192pipeline_id: None,1193},1194gpu_frustum_culling_build_indexed_indirect_params: BuildIndirectParametersPipeline {1195bind_group_layout: build_indexed_indirect_params_bind_group_layout.clone(),1196shader: build_indirect_params_shader.clone(),1197pipeline_id: None,1198},1199gpu_frustum_culling_build_non_indexed_indirect_params:1200BuildIndirectParametersPipeline {1201bind_group_layout: build_non_indexed_indirect_params_bind_group_layout.clone(),1202shader: build_indirect_params_shader,1203pipeline_id: None,1204},1205early_phase: preprocess_phase_pipelines.clone(),1206late_phase: preprocess_phase_pipelines.clone(),1207main_phase: preprocess_phase_pipelines.clone(),1208}1209}1210}12111212fn preprocess_direct_bind_group_layout_entries() -> DynamicBindGroupLayoutEntries {1213DynamicBindGroupLayoutEntries::new_with_indices(1214ShaderStages::COMPUTE,1215(1216// `view`1217(12180,1219uniform_buffer::<ViewUniform>(/* has_dynamic_offset= */ true),1220),1221// `current_input`1222(3, storage_buffer_read_only::<MeshInputUniform>(false)),1223// `previous_input`1224(4, storage_buffer_read_only::<MeshInputUniform>(false)),1225// `indices`1226(5, storage_buffer_read_only::<PreprocessWorkItem>(false)),1227// `output`1228(6, storage_buffer::<MeshUniform>(false)),1229),1230)1231}12321233// Returns the first 4 bind group layout entries shared between all invocations1234// of the indirect parameters building shader.1235fn build_indirect_params_bind_group_layout_entries() -> DynamicBindGroupLayoutEntries {1236DynamicBindGroupLayoutEntries::new_with_indices(1237ShaderStages::COMPUTE,1238(1239(0, storage_buffer_read_only::<MeshInputUniform>(false)),1240(12411,1242storage_buffer_read_only::<IndirectParametersCpuMetadata>(false),1243),1244(12452,1246storage_buffer_read_only::<IndirectParametersGpuMetadata>(false),1247),1248(3, storage_buffer::<IndirectBatchSet>(false)),1249),1250)1251}12521253/// A system that specializes the `mesh_preprocess.wgsl` and1254/// `build_indirect_params.wgsl` pipelines if necessary.1255fn gpu_culling_bind_group_layout_entries() -> DynamicBindGroupLayoutEntries {1256// GPU culling bind group parameters are a superset of those in the CPU1257// culling (direct) shader.1258preprocess_direct_bind_group_layout_entries().extend_with_indices((1259// `indirect_parameters_cpu_metadata`1260(12617,1262storage_buffer_read_only::<IndirectParametersCpuMetadata>(1263/* has_dynamic_offset= */ false,1264),1265),1266// `indirect_parameters_gpu_metadata`1267(12688,1269storage_buffer::<IndirectParametersGpuMetadata>(/* has_dynamic_offset= */ false),1270),1271// `mesh_culling_data`1272(12739,1274storage_buffer_read_only::<MeshCullingData>(/* has_dynamic_offset= */ false),1275),1276))1277}12781279fn gpu_occlusion_culling_bind_group_layout_entries() -> DynamicBindGroupLayoutEntries {1280gpu_culling_bind_group_layout_entries().extend_with_indices((1281(12822,1283uniform_buffer::<PreviousViewData>(/*has_dynamic_offset=*/ false),1284),1285(128610,1287texture_2d(TextureSampleType::Float { filterable: true }),1288),1289))1290}12911292/// A system that specializes the `mesh_preprocess.wgsl` pipelines if necessary.1293pub fn prepare_preprocess_pipelines(1294pipeline_cache: Res<PipelineCache>,1295render_device: Res<RenderDevice>,1296mut specialized_preprocess_pipelines: ResMut<SpecializedComputePipelines<PreprocessPipeline>>,1297mut specialized_reset_indirect_batch_sets_pipelines: ResMut<1298SpecializedComputePipelines<ResetIndirectBatchSetsPipeline>,1299>,1300mut specialized_build_indirect_parameters_pipelines: ResMut<1301SpecializedComputePipelines<BuildIndirectParametersPipeline>,1302>,1303preprocess_pipelines: ResMut<PreprocessPipelines>,1304gpu_preprocessing_support: Res<GpuPreprocessingSupport>,1305) {1306let preprocess_pipelines = preprocess_pipelines.into_inner();13071308preprocess_pipelines.direct_preprocess.prepare(1309&pipeline_cache,1310&mut specialized_preprocess_pipelines,1311PreprocessPipelineKey::empty(),1312);1313preprocess_pipelines.gpu_frustum_culling_preprocess.prepare(1314&pipeline_cache,1315&mut specialized_preprocess_pipelines,1316PreprocessPipelineKey::FRUSTUM_CULLING,1317);13181319if gpu_preprocessing_support.is_culling_supported() {1320preprocess_pipelines1321.early_gpu_occlusion_culling_preprocess1322.prepare(1323&pipeline_cache,1324&mut specialized_preprocess_pipelines,1325PreprocessPipelineKey::FRUSTUM_CULLING1326| PreprocessPipelineKey::OCCLUSION_CULLING1327| PreprocessPipelineKey::EARLY_PHASE,1328);1329preprocess_pipelines1330.late_gpu_occlusion_culling_preprocess1331.prepare(1332&pipeline_cache,1333&mut specialized_preprocess_pipelines,1334PreprocessPipelineKey::FRUSTUM_CULLING | PreprocessPipelineKey::OCCLUSION_CULLING,1335);1336}13371338let mut build_indirect_parameters_pipeline_key = BuildIndirectParametersPipelineKey::empty();13391340// If the GPU and driver support `multi_draw_indirect_count`, tell the1341// shader that.1342if render_device1343.wgpu_device()1344.features()1345.contains(WgpuFeatures::MULTI_DRAW_INDIRECT_COUNT)1346{1347build_indirect_parameters_pipeline_key1348.insert(BuildIndirectParametersPipelineKey::MULTI_DRAW_INDIRECT_COUNT_SUPPORTED);1349}13501351preprocess_pipelines1352.gpu_frustum_culling_build_indexed_indirect_params1353.prepare(1354&pipeline_cache,1355&mut specialized_build_indirect_parameters_pipelines,1356build_indirect_parameters_pipeline_key | BuildIndirectParametersPipelineKey::INDEXED,1357);1358preprocess_pipelines1359.gpu_frustum_culling_build_non_indexed_indirect_params1360.prepare(1361&pipeline_cache,1362&mut specialized_build_indirect_parameters_pipelines,1363build_indirect_parameters_pipeline_key,1364);13651366if !gpu_preprocessing_support.is_culling_supported() {1367return;1368}13691370for (preprocess_phase_pipelines, build_indirect_parameters_phase_pipeline_key) in [1371(1372&mut preprocess_pipelines.early_phase,1373BuildIndirectParametersPipelineKey::EARLY_PHASE,1374),1375(1376&mut preprocess_pipelines.late_phase,1377BuildIndirectParametersPipelineKey::LATE_PHASE,1378),1379(1380&mut preprocess_pipelines.main_phase,1381BuildIndirectParametersPipelineKey::MAIN_PHASE,1382),1383] {1384preprocess_phase_pipelines1385.reset_indirect_batch_sets1386.prepare(1387&pipeline_cache,1388&mut specialized_reset_indirect_batch_sets_pipelines,1389);1390preprocess_phase_pipelines1391.gpu_occlusion_culling_build_indexed_indirect_params1392.prepare(1393&pipeline_cache,1394&mut specialized_build_indirect_parameters_pipelines,1395build_indirect_parameters_pipeline_key1396| build_indirect_parameters_phase_pipeline_key1397| BuildIndirectParametersPipelineKey::INDEXED1398| BuildIndirectParametersPipelineKey::OCCLUSION_CULLING,1399);1400preprocess_phase_pipelines1401.gpu_occlusion_culling_build_non_indexed_indirect_params1402.prepare(1403&pipeline_cache,1404&mut specialized_build_indirect_parameters_pipelines,1405build_indirect_parameters_pipeline_key1406| build_indirect_parameters_phase_pipeline_key1407| BuildIndirectParametersPipelineKey::OCCLUSION_CULLING,1408);1409}1410}14111412impl PreprocessPipeline {1413fn prepare(1414&mut self,1415pipeline_cache: &PipelineCache,1416pipelines: &mut SpecializedComputePipelines<PreprocessPipeline>,1417key: PreprocessPipelineKey,1418) {1419if self.pipeline_id.is_some() {1420return;1421}14221423let preprocess_pipeline_id = pipelines.specialize(pipeline_cache, self, key);1424self.pipeline_id = Some(preprocess_pipeline_id);1425}1426}14271428impl SpecializedComputePipeline for ResetIndirectBatchSetsPipeline {1429type Key = ();14301431fn specialize(&self, _: Self::Key) -> ComputePipelineDescriptor {1432ComputePipelineDescriptor {1433label: Some("reset indirect batch sets".into()),1434layout: vec![self.bind_group_layout.clone()],1435shader: self.shader.clone(),1436..default()1437}1438}1439}14401441impl SpecializedComputePipeline for BuildIndirectParametersPipeline {1442type Key = BuildIndirectParametersPipelineKey;14431444fn specialize(&self, key: Self::Key) -> ComputePipelineDescriptor {1445let mut shader_defs = vec![];1446if key.contains(BuildIndirectParametersPipelineKey::INDEXED) {1447shader_defs.push("INDEXED".into());1448}1449if key.contains(BuildIndirectParametersPipelineKey::MULTI_DRAW_INDIRECT_COUNT_SUPPORTED) {1450shader_defs.push("MULTI_DRAW_INDIRECT_COUNT_SUPPORTED".into());1451}1452if key.contains(BuildIndirectParametersPipelineKey::OCCLUSION_CULLING) {1453shader_defs.push("OCCLUSION_CULLING".into());1454}1455if key.contains(BuildIndirectParametersPipelineKey::EARLY_PHASE) {1456shader_defs.push("EARLY_PHASE".into());1457}1458if key.contains(BuildIndirectParametersPipelineKey::LATE_PHASE) {1459shader_defs.push("LATE_PHASE".into());1460}1461if key.contains(BuildIndirectParametersPipelineKey::MAIN_PHASE) {1462shader_defs.push("MAIN_PHASE".into());1463}14641465let label = format!(1466"{} build {}indexed indirect parameters",1467if !key.contains(BuildIndirectParametersPipelineKey::OCCLUSION_CULLING) {1468"frustum culling"1469} else if key.contains(BuildIndirectParametersPipelineKey::EARLY_PHASE) {1470"early occlusion culling"1471} else if key.contains(BuildIndirectParametersPipelineKey::LATE_PHASE) {1472"late occlusion culling"1473} else {1474"main occlusion culling"1475},1476if key.contains(BuildIndirectParametersPipelineKey::INDEXED) {1477""1478} else {1479"non-"1480}1481);14821483ComputePipelineDescriptor {1484label: Some(label.into()),1485layout: vec![self.bind_group_layout.clone()],1486shader: self.shader.clone(),1487shader_defs,1488..default()1489}1490}1491}14921493impl ResetIndirectBatchSetsPipeline {1494fn prepare(1495&mut self,1496pipeline_cache: &PipelineCache,1497pipelines: &mut SpecializedComputePipelines<ResetIndirectBatchSetsPipeline>,1498) {1499if self.pipeline_id.is_some() {1500return;1501}15021503let reset_indirect_batch_sets_pipeline_id = pipelines.specialize(pipeline_cache, self, ());1504self.pipeline_id = Some(reset_indirect_batch_sets_pipeline_id);1505}1506}15071508impl BuildIndirectParametersPipeline {1509fn prepare(1510&mut self,1511pipeline_cache: &PipelineCache,1512pipelines: &mut SpecializedComputePipelines<BuildIndirectParametersPipeline>,1513key: BuildIndirectParametersPipelineKey,1514) {1515if self.pipeline_id.is_some() {1516return;1517}15181519let build_indirect_parameters_pipeline_id = pipelines.specialize(pipeline_cache, self, key);1520self.pipeline_id = Some(build_indirect_parameters_pipeline_id);1521}1522}15231524/// A system that attaches the mesh uniform buffers to the bind groups for the1525/// variants of the mesh preprocessing compute shader.1526#[expect(1527clippy::too_many_arguments,1528reason = "it's a system that needs a lot of arguments"1529)]1530pub fn prepare_preprocess_bind_groups(1531mut commands: Commands,1532views: Query<(Entity, &ExtractedView)>,1533view_depth_pyramids: Query<(&ViewDepthPyramid, &PreviousViewUniformOffset)>,1534render_device: Res<RenderDevice>,1535pipeline_cache: Res<PipelineCache>,1536batched_instance_buffers: Res<BatchedInstanceBuffers<MeshUniform, MeshInputUniform>>,1537indirect_parameters_buffers: Res<IndirectParametersBuffers>,1538mesh_culling_data_buffer: Res<MeshCullingDataBuffer>,1539view_uniforms: Res<ViewUniforms>,1540previous_view_uniforms: Res<PreviousViewUniforms>,1541pipelines: Res<PreprocessPipelines>,1542) {1543// Grab the `BatchedInstanceBuffers`.1544let BatchedInstanceBuffers {1545current_input_buffer: current_input_buffer_vec,1546previous_input_buffer: previous_input_buffer_vec,1547phase_instance_buffers,1548} = batched_instance_buffers.into_inner();15491550let (Some(current_input_buffer), Some(previous_input_buffer)) = (1551current_input_buffer_vec.buffer().buffer(),1552previous_input_buffer_vec.buffer().buffer(),1553) else {1554return;1555};15561557// Record whether we have any meshes that are to be drawn indirectly. If we1558// don't, then we can skip building indirect parameters.1559let mut any_indirect = false;15601561// Loop over each view.1562for (view_entity, view) in &views {1563let mut bind_groups = TypeIdMap::default();15641565// Loop over each phase.1566for (phase_type_id, phase_instance_buffers) in phase_instance_buffers {1567let UntypedPhaseBatchedInstanceBuffers {1568data_buffer: ref data_buffer_vec,1569ref work_item_buffers,1570ref late_indexed_indirect_parameters_buffer,1571ref late_non_indexed_indirect_parameters_buffer,1572} = *phase_instance_buffers;15731574let Some(data_buffer) = data_buffer_vec.buffer() else {1575continue;1576};15771578// Grab the indirect parameters buffers for this phase.1579let Some(phase_indirect_parameters_buffers) =1580indirect_parameters_buffers.get(phase_type_id)1581else {1582continue;1583};15841585let Some(work_item_buffers) = work_item_buffers.get(&view.retained_view_entity) else {1586continue;1587};15881589// Create the `PreprocessBindGroupBuilder`.1590let preprocess_bind_group_builder = PreprocessBindGroupBuilder {1591view: view_entity,1592late_indexed_indirect_parameters_buffer,1593late_non_indexed_indirect_parameters_buffer,1594render_device: &render_device,1595pipeline_cache: &pipeline_cache,1596phase_indirect_parameters_buffers,1597mesh_culling_data_buffer: &mesh_culling_data_buffer,1598view_uniforms: &view_uniforms,1599previous_view_uniforms: &previous_view_uniforms,1600pipelines: &pipelines,1601current_input_buffer,1602previous_input_buffer,1603data_buffer,1604};16051606// Depending on the type of work items we have, construct the1607// appropriate bind groups.1608let (was_indirect, bind_group) = match *work_item_buffers {1609PreprocessWorkItemBuffers::Direct(ref work_item_buffer) => (1610false,1611preprocess_bind_group_builder1612.create_direct_preprocess_bind_groups(work_item_buffer),1613),16141615PreprocessWorkItemBuffers::Indirect {1616indexed: ref indexed_work_item_buffer,1617non_indexed: ref non_indexed_work_item_buffer,1618gpu_occlusion_culling: Some(ref gpu_occlusion_culling_work_item_buffers),1619} => (1620true,1621preprocess_bind_group_builder1622.create_indirect_occlusion_culling_preprocess_bind_groups(1623&view_depth_pyramids,1624indexed_work_item_buffer,1625non_indexed_work_item_buffer,1626gpu_occlusion_culling_work_item_buffers,1627),1628),16291630PreprocessWorkItemBuffers::Indirect {1631indexed: ref indexed_work_item_buffer,1632non_indexed: ref non_indexed_work_item_buffer,1633gpu_occlusion_culling: None,1634} => (1635true,1636preprocess_bind_group_builder1637.create_indirect_frustum_culling_preprocess_bind_groups(1638indexed_work_item_buffer,1639non_indexed_work_item_buffer,1640),1641),1642};16431644// Write that bind group in.1645if let Some(bind_group) = bind_group {1646any_indirect = any_indirect || was_indirect;1647bind_groups.insert(*phase_type_id, bind_group);1648}1649}16501651// Save the bind groups.1652commands1653.entity(view_entity)1654.insert(PreprocessBindGroups(bind_groups));1655}16561657// Now, if there were any indirect draw commands, create the bind groups for1658// the indirect parameters building shader.1659if any_indirect {1660create_build_indirect_parameters_bind_groups(1661&mut commands,1662&render_device,1663&pipeline_cache,1664&pipelines,1665current_input_buffer,1666&indirect_parameters_buffers,1667);1668}1669}16701671/// A temporary structure that stores all the information needed to construct1672/// bind groups for the mesh preprocessing shader.1673struct PreprocessBindGroupBuilder<'a> {1674/// The render-world entity corresponding to the current view.1675view: Entity,1676/// The indirect compute dispatch parameters buffer for indexed meshes in1677/// the late prepass.1678late_indexed_indirect_parameters_buffer:1679&'a RawBufferVec<LatePreprocessWorkItemIndirectParameters>,1680/// The indirect compute dispatch parameters buffer for non-indexed meshes1681/// in the late prepass.1682late_non_indexed_indirect_parameters_buffer:1683&'a RawBufferVec<LatePreprocessWorkItemIndirectParameters>,1684/// The device.1685render_device: &'a RenderDevice,1686/// The pipeline cache1687pipeline_cache: &'a PipelineCache,1688/// The buffers that store indirect draw parameters.1689phase_indirect_parameters_buffers: &'a UntypedPhaseIndirectParametersBuffers,1690/// The GPU buffer that stores the information needed to cull each mesh.1691mesh_culling_data_buffer: &'a MeshCullingDataBuffer,1692/// The GPU buffer that stores information about the view.1693view_uniforms: &'a ViewUniforms,1694/// The GPU buffer that stores information about the view from last frame.1695previous_view_uniforms: &'a PreviousViewUniforms,1696/// The pipelines for the mesh preprocessing shader.1697pipelines: &'a PreprocessPipelines,1698/// The GPU buffer containing the list of [`MeshInputUniform`]s for the1699/// current frame.1700current_input_buffer: &'a Buffer,1701/// The GPU buffer containing the list of [`MeshInputUniform`]s for the1702/// previous frame.1703previous_input_buffer: &'a Buffer,1704/// The GPU buffer containing the list of [`MeshUniform`]s for the current1705/// frame.1706///1707/// This is the buffer containing the mesh's final transforms that the1708/// shaders will write to.1709data_buffer: &'a Buffer,1710}17111712impl<'a> PreprocessBindGroupBuilder<'a> {1713/// Creates the bind groups for mesh preprocessing when GPU frustum culling1714/// and GPU occlusion culling are both disabled.1715fn create_direct_preprocess_bind_groups(1716&self,1717work_item_buffer: &RawBufferVec<PreprocessWorkItem>,1718) -> Option<PhasePreprocessBindGroups> {1719// Don't use `as_entire_binding()` here; the shader reads the array1720// length and the underlying buffer may be longer than the actual size1721// of the vector.1722let work_item_buffer_size = NonZero::<u64>::try_from(1723work_item_buffer.len() as u64 * u64::from(PreprocessWorkItem::min_size()),1724)1725.ok();17261727Some(PhasePreprocessBindGroups::Direct(1728self.render_device.create_bind_group(1729"preprocess_direct_bind_group",1730&self1731.pipeline_cache1732.get_bind_group_layout(&self.pipelines.direct_preprocess.bind_group_layout),1733&BindGroupEntries::with_indices((1734(0, self.view_uniforms.uniforms.binding()?),1735(3, self.current_input_buffer.as_entire_binding()),1736(4, self.previous_input_buffer.as_entire_binding()),1737(17385,1739BindingResource::Buffer(BufferBinding {1740buffer: work_item_buffer.buffer()?,1741offset: 0,1742size: work_item_buffer_size,1743}),1744),1745(6, self.data_buffer.as_entire_binding()),1746)),1747),1748))1749}17501751/// Creates the bind groups for mesh preprocessing when GPU occlusion1752/// culling is enabled.1753fn create_indirect_occlusion_culling_preprocess_bind_groups(1754&self,1755view_depth_pyramids: &Query<(&ViewDepthPyramid, &PreviousViewUniformOffset)>,1756indexed_work_item_buffer: &RawBufferVec<PreprocessWorkItem>,1757non_indexed_work_item_buffer: &RawBufferVec<PreprocessWorkItem>,1758gpu_occlusion_culling_work_item_buffers: &GpuOcclusionCullingWorkItemBuffers,1759) -> Option<PhasePreprocessBindGroups> {1760let GpuOcclusionCullingWorkItemBuffers {1761late_indexed: ref late_indexed_work_item_buffer,1762late_non_indexed: ref late_non_indexed_work_item_buffer,1763..1764} = *gpu_occlusion_culling_work_item_buffers;17651766let (view_depth_pyramid, previous_view_uniform_offset) =1767view_depth_pyramids.get(self.view).ok()?;17681769Some(PhasePreprocessBindGroups::IndirectOcclusionCulling {1770early_indexed: self.create_indirect_occlusion_culling_early_indexed_bind_group(1771view_depth_pyramid,1772previous_view_uniform_offset,1773indexed_work_item_buffer,1774late_indexed_work_item_buffer,1775),17761777early_non_indexed: self.create_indirect_occlusion_culling_early_non_indexed_bind_group(1778view_depth_pyramid,1779previous_view_uniform_offset,1780non_indexed_work_item_buffer,1781late_non_indexed_work_item_buffer,1782),17831784late_indexed: self.create_indirect_occlusion_culling_late_indexed_bind_group(1785view_depth_pyramid,1786previous_view_uniform_offset,1787late_indexed_work_item_buffer,1788),17891790late_non_indexed: self.create_indirect_occlusion_culling_late_non_indexed_bind_group(1791view_depth_pyramid,1792previous_view_uniform_offset,1793late_non_indexed_work_item_buffer,1794),1795})1796}17971798/// Creates the bind group for the first phase of mesh preprocessing of1799/// indexed meshes when GPU occlusion culling is enabled.1800fn create_indirect_occlusion_culling_early_indexed_bind_group(1801&self,1802view_depth_pyramid: &ViewDepthPyramid,1803previous_view_uniform_offset: &PreviousViewUniformOffset,1804indexed_work_item_buffer: &RawBufferVec<PreprocessWorkItem>,1805late_indexed_work_item_buffer: &UninitBufferVec<PreprocessWorkItem>,1806) -> Option<BindGroup> {1807let mesh_culling_data_buffer = self.mesh_culling_data_buffer.buffer()?;1808let view_uniforms_binding = self.view_uniforms.uniforms.binding()?;1809let previous_view_buffer = self.previous_view_uniforms.uniforms.buffer()?;18101811match (1812self.phase_indirect_parameters_buffers1813.indexed1814.cpu_metadata_buffer(),1815self.phase_indirect_parameters_buffers1816.indexed1817.gpu_metadata_buffer(),1818indexed_work_item_buffer.buffer(),1819late_indexed_work_item_buffer.buffer(),1820self.late_indexed_indirect_parameters_buffer.buffer(),1821) {1822(1823Some(indexed_cpu_metadata_buffer),1824Some(indexed_gpu_metadata_buffer),1825Some(indexed_work_item_gpu_buffer),1826Some(late_indexed_work_item_gpu_buffer),1827Some(late_indexed_indirect_parameters_buffer),1828) => {1829// Don't use `as_entire_binding()` here; the shader reads the array1830// length and the underlying buffer may be longer than the actual size1831// of the vector.1832let indexed_work_item_buffer_size = NonZero::<u64>::try_from(1833indexed_work_item_buffer.len() as u641834* u64::from(PreprocessWorkItem::min_size()),1835)1836.ok();18371838Some(1839self.render_device.create_bind_group(1840"preprocess_early_indexed_gpu_occlusion_culling_bind_group",1841&self.pipeline_cache.get_bind_group_layout(1842&self1843.pipelines1844.early_gpu_occlusion_culling_preprocess1845.bind_group_layout,1846),1847&BindGroupEntries::with_indices((1848(3, self.current_input_buffer.as_entire_binding()),1849(4, self.previous_input_buffer.as_entire_binding()),1850(18515,1852BindingResource::Buffer(BufferBinding {1853buffer: indexed_work_item_gpu_buffer,1854offset: 0,1855size: indexed_work_item_buffer_size,1856}),1857),1858(6, self.data_buffer.as_entire_binding()),1859(7, indexed_cpu_metadata_buffer.as_entire_binding()),1860(8, indexed_gpu_metadata_buffer.as_entire_binding()),1861(9, mesh_culling_data_buffer.as_entire_binding()),1862(0, view_uniforms_binding.clone()),1863(10, &view_depth_pyramid.all_mips),1864(18652,1866BufferBinding {1867buffer: previous_view_buffer,1868offset: previous_view_uniform_offset.offset as u64,1869size: NonZeroU64::new(size_of::<PreviousViewData>() as u64),1870},1871),1872(187311,1874BufferBinding {1875buffer: late_indexed_work_item_gpu_buffer,1876offset: 0,1877size: indexed_work_item_buffer_size,1878},1879),1880(188112,1882BufferBinding {1883buffer: late_indexed_indirect_parameters_buffer,1884offset: 0,1885size: NonZeroU64::new(1886late_indexed_indirect_parameters_buffer.size(),1887),1888},1889),1890)),1891),1892)1893}1894_ => None,1895}1896}18971898/// Creates the bind group for the first phase of mesh preprocessing of1899/// non-indexed meshes when GPU occlusion culling is enabled.1900fn create_indirect_occlusion_culling_early_non_indexed_bind_group(1901&self,1902view_depth_pyramid: &ViewDepthPyramid,1903previous_view_uniform_offset: &PreviousViewUniformOffset,1904non_indexed_work_item_buffer: &RawBufferVec<PreprocessWorkItem>,1905late_non_indexed_work_item_buffer: &UninitBufferVec<PreprocessWorkItem>,1906) -> Option<BindGroup> {1907let mesh_culling_data_buffer = self.mesh_culling_data_buffer.buffer()?;1908let view_uniforms_binding = self.view_uniforms.uniforms.binding()?;1909let previous_view_buffer = self.previous_view_uniforms.uniforms.buffer()?;19101911match (1912self.phase_indirect_parameters_buffers1913.non_indexed1914.cpu_metadata_buffer(),1915self.phase_indirect_parameters_buffers1916.non_indexed1917.gpu_metadata_buffer(),1918non_indexed_work_item_buffer.buffer(),1919late_non_indexed_work_item_buffer.buffer(),1920self.late_non_indexed_indirect_parameters_buffer.buffer(),1921) {1922(1923Some(non_indexed_cpu_metadata_buffer),1924Some(non_indexed_gpu_metadata_buffer),1925Some(non_indexed_work_item_gpu_buffer),1926Some(late_non_indexed_work_item_buffer),1927Some(late_non_indexed_indirect_parameters_buffer),1928) => {1929// Don't use `as_entire_binding()` here; the shader reads the array1930// length and the underlying buffer may be longer than the actual size1931// of the vector.1932let non_indexed_work_item_buffer_size = NonZero::<u64>::try_from(1933non_indexed_work_item_buffer.len() as u641934* u64::from(PreprocessWorkItem::min_size()),1935)1936.ok();19371938Some(1939self.render_device.create_bind_group(1940"preprocess_early_non_indexed_gpu_occlusion_culling_bind_group",1941&self.pipeline_cache.get_bind_group_layout(1942&self1943.pipelines1944.early_gpu_occlusion_culling_preprocess1945.bind_group_layout,1946),1947&BindGroupEntries::with_indices((1948(3, self.current_input_buffer.as_entire_binding()),1949(4, self.previous_input_buffer.as_entire_binding()),1950(19515,1952BindingResource::Buffer(BufferBinding {1953buffer: non_indexed_work_item_gpu_buffer,1954offset: 0,1955size: non_indexed_work_item_buffer_size,1956}),1957),1958(6, self.data_buffer.as_entire_binding()),1959(7, non_indexed_cpu_metadata_buffer.as_entire_binding()),1960(8, non_indexed_gpu_metadata_buffer.as_entire_binding()),1961(9, mesh_culling_data_buffer.as_entire_binding()),1962(0, view_uniforms_binding.clone()),1963(10, &view_depth_pyramid.all_mips),1964(19652,1966BufferBinding {1967buffer: previous_view_buffer,1968offset: previous_view_uniform_offset.offset as u64,1969size: NonZeroU64::new(size_of::<PreviousViewData>() as u64),1970},1971),1972(197311,1974BufferBinding {1975buffer: late_non_indexed_work_item_buffer,1976offset: 0,1977size: non_indexed_work_item_buffer_size,1978},1979),1980(198112,1982BufferBinding {1983buffer: late_non_indexed_indirect_parameters_buffer,1984offset: 0,1985size: NonZeroU64::new(1986late_non_indexed_indirect_parameters_buffer.size(),1987),1988},1989),1990)),1991),1992)1993}1994_ => None,1995}1996}19971998/// Creates the bind group for the second phase of mesh preprocessing of1999/// indexed meshes when GPU occlusion culling is enabled.2000fn create_indirect_occlusion_culling_late_indexed_bind_group(2001&self,2002view_depth_pyramid: &ViewDepthPyramid,2003previous_view_uniform_offset: &PreviousViewUniformOffset,2004late_indexed_work_item_buffer: &UninitBufferVec<PreprocessWorkItem>,2005) -> Option<BindGroup> {2006let mesh_culling_data_buffer = self.mesh_culling_data_buffer.buffer()?;2007let view_uniforms_binding = self.view_uniforms.uniforms.binding()?;2008let previous_view_buffer = self.previous_view_uniforms.uniforms.buffer()?;20092010match (2011self.phase_indirect_parameters_buffers2012.indexed2013.cpu_metadata_buffer(),2014self.phase_indirect_parameters_buffers2015.indexed2016.gpu_metadata_buffer(),2017late_indexed_work_item_buffer.buffer(),2018self.late_indexed_indirect_parameters_buffer.buffer(),2019) {2020(2021Some(indexed_cpu_metadata_buffer),2022Some(indexed_gpu_metadata_buffer),2023Some(late_indexed_work_item_gpu_buffer),2024Some(late_indexed_indirect_parameters_buffer),2025) => {2026// Don't use `as_entire_binding()` here; the shader reads the array2027// length and the underlying buffer may be longer than the actual size2028// of the vector.2029let late_indexed_work_item_buffer_size = NonZero::<u64>::try_from(2030late_indexed_work_item_buffer.len() as u642031* u64::from(PreprocessWorkItem::min_size()),2032)2033.ok();20342035Some(2036self.render_device.create_bind_group(2037"preprocess_late_indexed_gpu_occlusion_culling_bind_group",2038&self.pipeline_cache.get_bind_group_layout(2039&self2040.pipelines2041.late_gpu_occlusion_culling_preprocess2042.bind_group_layout,2043),2044&BindGroupEntries::with_indices((2045(3, self.current_input_buffer.as_entire_binding()),2046(4, self.previous_input_buffer.as_entire_binding()),2047(20485,2049BindingResource::Buffer(BufferBinding {2050buffer: late_indexed_work_item_gpu_buffer,2051offset: 0,2052size: late_indexed_work_item_buffer_size,2053}),2054),2055(6, self.data_buffer.as_entire_binding()),2056(7, indexed_cpu_metadata_buffer.as_entire_binding()),2057(8, indexed_gpu_metadata_buffer.as_entire_binding()),2058(9, mesh_culling_data_buffer.as_entire_binding()),2059(0, view_uniforms_binding.clone()),2060(10, &view_depth_pyramid.all_mips),2061(20622,2063BufferBinding {2064buffer: previous_view_buffer,2065offset: previous_view_uniform_offset.offset as u64,2066size: NonZeroU64::new(size_of::<PreviousViewData>() as u64),2067},2068),2069(207012,2071BufferBinding {2072buffer: late_indexed_indirect_parameters_buffer,2073offset: 0,2074size: NonZeroU64::new(2075late_indexed_indirect_parameters_buffer.size(),2076),2077},2078),2079)),2080),2081)2082}2083_ => None,2084}2085}20862087/// Creates the bind group for the second phase of mesh preprocessing of2088/// non-indexed meshes when GPU occlusion culling is enabled.2089fn create_indirect_occlusion_culling_late_non_indexed_bind_group(2090&self,2091view_depth_pyramid: &ViewDepthPyramid,2092previous_view_uniform_offset: &PreviousViewUniformOffset,2093late_non_indexed_work_item_buffer: &UninitBufferVec<PreprocessWorkItem>,2094) -> Option<BindGroup> {2095let mesh_culling_data_buffer = self.mesh_culling_data_buffer.buffer()?;2096let view_uniforms_binding = self.view_uniforms.uniforms.binding()?;2097let previous_view_buffer = self.previous_view_uniforms.uniforms.buffer()?;20982099match (2100self.phase_indirect_parameters_buffers2101.non_indexed2102.cpu_metadata_buffer(),2103self.phase_indirect_parameters_buffers2104.non_indexed2105.gpu_metadata_buffer(),2106late_non_indexed_work_item_buffer.buffer(),2107self.late_non_indexed_indirect_parameters_buffer.buffer(),2108) {2109(2110Some(non_indexed_cpu_metadata_buffer),2111Some(non_indexed_gpu_metadata_buffer),2112Some(non_indexed_work_item_gpu_buffer),2113Some(late_non_indexed_indirect_parameters_buffer),2114) => {2115// Don't use `as_entire_binding()` here; the shader reads the array2116// length and the underlying buffer may be longer than the actual size2117// of the vector.2118let non_indexed_work_item_buffer_size = NonZero::<u64>::try_from(2119late_non_indexed_work_item_buffer.len() as u642120* u64::from(PreprocessWorkItem::min_size()),2121)2122.ok();21232124Some(2125self.render_device.create_bind_group(2126"preprocess_late_non_indexed_gpu_occlusion_culling_bind_group",2127&self.pipeline_cache.get_bind_group_layout(2128&self2129.pipelines2130.late_gpu_occlusion_culling_preprocess2131.bind_group_layout,2132),2133&BindGroupEntries::with_indices((2134(3, self.current_input_buffer.as_entire_binding()),2135(4, self.previous_input_buffer.as_entire_binding()),2136(21375,2138BindingResource::Buffer(BufferBinding {2139buffer: non_indexed_work_item_gpu_buffer,2140offset: 0,2141size: non_indexed_work_item_buffer_size,2142}),2143),2144(6, self.data_buffer.as_entire_binding()),2145(7, non_indexed_cpu_metadata_buffer.as_entire_binding()),2146(8, non_indexed_gpu_metadata_buffer.as_entire_binding()),2147(9, mesh_culling_data_buffer.as_entire_binding()),2148(0, view_uniforms_binding.clone()),2149(10, &view_depth_pyramid.all_mips),2150(21512,2152BufferBinding {2153buffer: previous_view_buffer,2154offset: previous_view_uniform_offset.offset as u64,2155size: NonZeroU64::new(size_of::<PreviousViewData>() as u64),2156},2157),2158(215912,2160BufferBinding {2161buffer: late_non_indexed_indirect_parameters_buffer,2162offset: 0,2163size: NonZeroU64::new(2164late_non_indexed_indirect_parameters_buffer.size(),2165),2166},2167),2168)),2169),2170)2171}2172_ => None,2173}2174}21752176/// Creates the bind groups for mesh preprocessing when GPU frustum culling2177/// is enabled, but GPU occlusion culling is disabled.2178fn create_indirect_frustum_culling_preprocess_bind_groups(2179&self,2180indexed_work_item_buffer: &RawBufferVec<PreprocessWorkItem>,2181non_indexed_work_item_buffer: &RawBufferVec<PreprocessWorkItem>,2182) -> Option<PhasePreprocessBindGroups> {2183Some(PhasePreprocessBindGroups::IndirectFrustumCulling {2184indexed: self2185.create_indirect_frustum_culling_indexed_bind_group(indexed_work_item_buffer),2186non_indexed: self.create_indirect_frustum_culling_non_indexed_bind_group(2187non_indexed_work_item_buffer,2188),2189})2190}21912192/// Creates the bind group for mesh preprocessing of indexed meshes when GPU2193/// frustum culling is enabled, but GPU occlusion culling is disabled.2194fn create_indirect_frustum_culling_indexed_bind_group(2195&self,2196indexed_work_item_buffer: &RawBufferVec<PreprocessWorkItem>,2197) -> Option<BindGroup> {2198let mesh_culling_data_buffer = self.mesh_culling_data_buffer.buffer()?;2199let view_uniforms_binding = self.view_uniforms.uniforms.binding()?;22002201match (2202self.phase_indirect_parameters_buffers2203.indexed2204.cpu_metadata_buffer(),2205self.phase_indirect_parameters_buffers2206.indexed2207.gpu_metadata_buffer(),2208indexed_work_item_buffer.buffer(),2209) {2210(2211Some(indexed_cpu_metadata_buffer),2212Some(indexed_gpu_metadata_buffer),2213Some(indexed_work_item_gpu_buffer),2214) => {2215// Don't use `as_entire_binding()` here; the shader reads the array2216// length and the underlying buffer may be longer than the actual size2217// of the vector.2218let indexed_work_item_buffer_size = NonZero::<u64>::try_from(2219indexed_work_item_buffer.len() as u642220* u64::from(PreprocessWorkItem::min_size()),2221)2222.ok();22232224Some(2225self.render_device.create_bind_group(2226"preprocess_gpu_indexed_frustum_culling_bind_group",2227&self.pipeline_cache.get_bind_group_layout(2228&self2229.pipelines2230.gpu_frustum_culling_preprocess2231.bind_group_layout,2232),2233&BindGroupEntries::with_indices((2234(3, self.current_input_buffer.as_entire_binding()),2235(4, self.previous_input_buffer.as_entire_binding()),2236(22375,2238BindingResource::Buffer(BufferBinding {2239buffer: indexed_work_item_gpu_buffer,2240offset: 0,2241size: indexed_work_item_buffer_size,2242}),2243),2244(6, self.data_buffer.as_entire_binding()),2245(7, indexed_cpu_metadata_buffer.as_entire_binding()),2246(8, indexed_gpu_metadata_buffer.as_entire_binding()),2247(9, mesh_culling_data_buffer.as_entire_binding()),2248(0, view_uniforms_binding.clone()),2249)),2250),2251)2252}2253_ => None,2254}2255}22562257/// Creates the bind group for mesh preprocessing of non-indexed meshes when2258/// GPU frustum culling is enabled, but GPU occlusion culling is disabled.2259fn create_indirect_frustum_culling_non_indexed_bind_group(2260&self,2261non_indexed_work_item_buffer: &RawBufferVec<PreprocessWorkItem>,2262) -> Option<BindGroup> {2263let mesh_culling_data_buffer = self.mesh_culling_data_buffer.buffer()?;2264let view_uniforms_binding = self.view_uniforms.uniforms.binding()?;22652266match (2267self.phase_indirect_parameters_buffers2268.non_indexed2269.cpu_metadata_buffer(),2270self.phase_indirect_parameters_buffers2271.non_indexed2272.gpu_metadata_buffer(),2273non_indexed_work_item_buffer.buffer(),2274) {2275(2276Some(non_indexed_cpu_metadata_buffer),2277Some(non_indexed_gpu_metadata_buffer),2278Some(non_indexed_work_item_gpu_buffer),2279) => {2280// Don't use `as_entire_binding()` here; the shader reads the array2281// length and the underlying buffer may be longer than the actual size2282// of the vector.2283let non_indexed_work_item_buffer_size = NonZero::<u64>::try_from(2284non_indexed_work_item_buffer.len() as u642285* u64::from(PreprocessWorkItem::min_size()),2286)2287.ok();22882289Some(2290self.render_device.create_bind_group(2291"preprocess_gpu_non_indexed_frustum_culling_bind_group",2292&self.pipeline_cache.get_bind_group_layout(2293&self2294.pipelines2295.gpu_frustum_culling_preprocess2296.bind_group_layout,2297),2298&BindGroupEntries::with_indices((2299(3, self.current_input_buffer.as_entire_binding()),2300(4, self.previous_input_buffer.as_entire_binding()),2301(23025,2303BindingResource::Buffer(BufferBinding {2304buffer: non_indexed_work_item_gpu_buffer,2305offset: 0,2306size: non_indexed_work_item_buffer_size,2307}),2308),2309(6, self.data_buffer.as_entire_binding()),2310(7, non_indexed_cpu_metadata_buffer.as_entire_binding()),2311(8, non_indexed_gpu_metadata_buffer.as_entire_binding()),2312(9, mesh_culling_data_buffer.as_entire_binding()),2313(0, view_uniforms_binding.clone()),2314)),2315),2316)2317}2318_ => None,2319}2320}2321}23222323/// A system that creates bind groups from the indirect parameters metadata and2324/// data buffers for the indirect batch set reset shader and the indirect2325/// parameter building shader.2326fn create_build_indirect_parameters_bind_groups(2327commands: &mut Commands,2328render_device: &RenderDevice,2329pipeline_cache: &PipelineCache,2330pipelines: &PreprocessPipelines,2331current_input_buffer: &Buffer,2332indirect_parameters_buffers: &IndirectParametersBuffers,2333) {2334let mut build_indirect_parameters_bind_groups = BuildIndirectParametersBindGroups::new();23352336for (phase_type_id, phase_indirect_parameters_buffer) in indirect_parameters_buffers.iter() {2337build_indirect_parameters_bind_groups.insert(2338*phase_type_id,2339PhaseBuildIndirectParametersBindGroups {2340reset_indexed_indirect_batch_sets: match (phase_indirect_parameters_buffer2341.indexed2342.batch_sets_buffer(),)2343{2344(Some(indexed_batch_sets_buffer),) => Some(2345render_device.create_bind_group(2346"reset_indexed_indirect_batch_sets_bind_group",2347// The early bind group is good for the main phase and late2348// phase too. They bind the same buffers.2349&pipeline_cache.get_bind_group_layout(2350&pipelines2351.early_phase2352.reset_indirect_batch_sets2353.bind_group_layout,2354),2355&BindGroupEntries::sequential((2356indexed_batch_sets_buffer.as_entire_binding(),2357)),2358),2359),2360_ => None,2361},23622363reset_non_indexed_indirect_batch_sets: match (phase_indirect_parameters_buffer2364.non_indexed2365.batch_sets_buffer(),)2366{2367(Some(non_indexed_batch_sets_buffer),) => Some(2368render_device.create_bind_group(2369"reset_non_indexed_indirect_batch_sets_bind_group",2370// The early bind group is good for the main phase and late2371// phase too. They bind the same buffers.2372&pipeline_cache.get_bind_group_layout(2373&pipelines2374.early_phase2375.reset_indirect_batch_sets2376.bind_group_layout,2377),2378&BindGroupEntries::sequential((2379non_indexed_batch_sets_buffer.as_entire_binding(),2380)),2381),2382),2383_ => None,2384},23852386build_indexed_indirect: match (2387phase_indirect_parameters_buffer2388.indexed2389.cpu_metadata_buffer(),2390phase_indirect_parameters_buffer2391.indexed2392.gpu_metadata_buffer(),2393phase_indirect_parameters_buffer.indexed.data_buffer(),2394phase_indirect_parameters_buffer.indexed.batch_sets_buffer(),2395) {2396(2397Some(indexed_indirect_parameters_cpu_metadata_buffer),2398Some(indexed_indirect_parameters_gpu_metadata_buffer),2399Some(indexed_indirect_parameters_data_buffer),2400Some(indexed_batch_sets_buffer),2401) => Some(2402render_device.create_bind_group(2403"build_indexed_indirect_parameters_bind_group",2404// The frustum culling bind group is good for occlusion culling2405// too. They bind the same buffers.2406&pipeline_cache.get_bind_group_layout(2407&pipelines2408.gpu_frustum_culling_build_indexed_indirect_params2409.bind_group_layout,2410),2411&BindGroupEntries::sequential((2412current_input_buffer.as_entire_binding(),2413// Don't use `as_entire_binding` here; the shader reads2414// the length and `RawBufferVec` overallocates.2415BufferBinding {2416buffer: indexed_indirect_parameters_cpu_metadata_buffer,2417offset: 0,2418size: NonZeroU64::new(2419phase_indirect_parameters_buffer.indexed.batch_count()2420as u642421* size_of::<IndirectParametersCpuMetadata>() as u64,2422),2423},2424BufferBinding {2425buffer: indexed_indirect_parameters_gpu_metadata_buffer,2426offset: 0,2427size: NonZeroU64::new(2428phase_indirect_parameters_buffer.indexed.batch_count()2429as u642430* size_of::<IndirectParametersGpuMetadata>() as u64,2431),2432},2433indexed_batch_sets_buffer.as_entire_binding(),2434indexed_indirect_parameters_data_buffer.as_entire_binding(),2435)),2436),2437),2438_ => None,2439},24402441build_non_indexed_indirect: match (2442phase_indirect_parameters_buffer2443.non_indexed2444.cpu_metadata_buffer(),2445phase_indirect_parameters_buffer2446.non_indexed2447.gpu_metadata_buffer(),2448phase_indirect_parameters_buffer.non_indexed.data_buffer(),2449phase_indirect_parameters_buffer2450.non_indexed2451.batch_sets_buffer(),2452) {2453(2454Some(non_indexed_indirect_parameters_cpu_metadata_buffer),2455Some(non_indexed_indirect_parameters_gpu_metadata_buffer),2456Some(non_indexed_indirect_parameters_data_buffer),2457Some(non_indexed_batch_sets_buffer),2458) => Some(2459render_device.create_bind_group(2460"build_non_indexed_indirect_parameters_bind_group",2461// The frustum culling bind group is good for occlusion culling2462// too. They bind the same buffers.2463&pipeline_cache.get_bind_group_layout(2464&pipelines2465.gpu_frustum_culling_build_non_indexed_indirect_params2466.bind_group_layout,2467),2468&BindGroupEntries::sequential((2469current_input_buffer.as_entire_binding(),2470// Don't use `as_entire_binding` here; the shader reads2471// the length and `RawBufferVec` overallocates.2472BufferBinding {2473buffer: non_indexed_indirect_parameters_cpu_metadata_buffer,2474offset: 0,2475size: NonZeroU64::new(2476phase_indirect_parameters_buffer.non_indexed.batch_count()2477as u642478* size_of::<IndirectParametersCpuMetadata>() as u64,2479),2480},2481BufferBinding {2482buffer: non_indexed_indirect_parameters_gpu_metadata_buffer,2483offset: 0,2484size: NonZeroU64::new(2485phase_indirect_parameters_buffer.non_indexed.batch_count()2486as u642487* size_of::<IndirectParametersGpuMetadata>() as u64,2488),2489},2490non_indexed_batch_sets_buffer.as_entire_binding(),2491non_indexed_indirect_parameters_data_buffer.as_entire_binding(),2492)),2493),2494),2495_ => None,2496},2497},2498);2499}25002501commands.insert_resource(build_indirect_parameters_bind_groups);2502}25032504/// Writes the information needed to do GPU mesh culling to the GPU.2505pub fn write_mesh_culling_data_buffer(2506render_device: Res<RenderDevice>,2507render_queue: Res<RenderQueue>,2508mut mesh_culling_data_buffer: ResMut<MeshCullingDataBuffer>,2509) {2510mesh_culling_data_buffer.write_buffer(&render_device, &render_queue);2511}251225132514