Path: blob/main/crates/bevy_pbr/src/render/gpu_preprocess.rs
6600 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::{13core_3d::graph::{Core3d, Node3d},14experimental::mip_generation::ViewDepthPyramid,15prepass::{DepthPrepass, PreviousViewData, PreviousViewUniformOffset, PreviousViewUniforms},16};17use bevy_derive::{Deref, DerefMut};18use bevy_ecs::{19component::Component,20entity::Entity,21prelude::resource_exists,22query::{Has, Or, QueryState, With, Without},23resource::Resource,24schedule::IntoScheduleConfigs as _,25system::{lifetimeless::Read, Commands, Query, Res, ResMut},26world::{FromWorld, World},27};28use bevy_render::{29batching::gpu_preprocessing::{30BatchedInstanceBuffers, GpuOcclusionCullingWorkItemBuffers, GpuPreprocessingMode,31GpuPreprocessingSupport, IndirectBatchSet, IndirectParametersBuffers,32IndirectParametersCpuMetadata, IndirectParametersGpuMetadata, IndirectParametersIndexed,33IndirectParametersNonIndexed, LatePreprocessWorkItemIndirectParameters, PreprocessWorkItem,34PreprocessWorkItemBuffers, UntypedPhaseBatchedInstanceBuffers,35UntypedPhaseIndirectParametersBuffers,36},37diagnostic::RecordDiagnostics,38experimental::occlusion_culling::OcclusionCulling,39render_graph::{Node, NodeRunError, RenderGraphContext, RenderGraphExt},40render_resource::{41binding_types::{storage_buffer, storage_buffer_read_only, texture_2d, uniform_buffer},42BindGroup, BindGroupEntries, BindGroupLayout, BindingResource, Buffer, BufferBinding,43CachedComputePipelineId, ComputePassDescriptor, ComputePipelineDescriptor,44DynamicBindGroupLayoutEntries, PipelineCache, PushConstantRange, RawBufferVec,45ShaderStages, ShaderType, SpecializedComputePipeline, SpecializedComputePipelines,46TextureSampleType, UninitBufferVec,47},48renderer::{RenderContext, RenderDevice, RenderQueue},49settings::WgpuFeatures,50view::{ExtractedView, NoIndirectDrawing, ViewUniform, ViewUniformOffset, ViewUniforms},51Render, RenderApp, RenderSystems,52};53use bevy_shader::Shader;54use bevy_utils::{default, TypeIdMap};55use bitflags::bitflags;56use smallvec::{smallvec, SmallVec};57use tracing::warn;5859use crate::{60graph::NodePbr, MeshCullingData, MeshCullingDataBuffer, MeshInputUniform, MeshUniform,61};6263use super::{ShadowView, ViewLightEntities};6465/// The GPU workgroup size.66const WORKGROUP_SIZE: usize = 64;6768/// A plugin that builds mesh uniforms on GPU.69///70/// This will only be added if the platform supports compute shaders (e.g. not71/// on WebGL 2).72pub struct GpuMeshPreprocessPlugin {73/// Whether we're building [`MeshUniform`]s on GPU.74///75/// This requires compute shader support and so will be forcibly disabled if76/// the platform doesn't support those.77pub use_gpu_instance_buffer_builder: bool,78}7980/// The render node that clears out the GPU-side indirect metadata buffers.81///82/// This is only used when indirect drawing is enabled.83#[derive(Default)]84pub struct ClearIndirectParametersMetadataNode;8586/// The render node for the first mesh preprocessing pass.87///88/// This pass runs a compute shader to cull meshes outside the view frustum (if89/// that wasn't done by the CPU), cull meshes that weren't visible last frame90/// (if occlusion culling is on), transform them, and, if indirect drawing is91/// on, populate indirect draw parameter metadata for the subsequent92/// [`EarlyPrepassBuildIndirectParametersNode`].93pub struct EarlyGpuPreprocessNode {94view_query: QueryState<95(96Read<ExtractedView>,97Option<Read<PreprocessBindGroups>>,98Option<Read<ViewUniformOffset>>,99Has<NoIndirectDrawing>,100Has<OcclusionCulling>,101),102Without<SkipGpuPreprocess>,103>,104main_view_query: QueryState<Read<ViewLightEntities>>,105}106107/// The render node for the second mesh preprocessing pass.108///109/// This pass runs a compute shader to cull meshes outside the view frustum (if110/// that wasn't done by the CPU), cull meshes that were neither visible last111/// frame nor visible this frame (if occlusion culling is on), transform them,112/// and, if indirect drawing is on, populate the indirect draw parameter113/// metadata for the subsequent [`LatePrepassBuildIndirectParametersNode`].114pub struct LateGpuPreprocessNode {115view_query: QueryState<116(117Read<ExtractedView>,118Read<PreprocessBindGroups>,119Read<ViewUniformOffset>,120),121(122Without<SkipGpuPreprocess>,123Without<NoIndirectDrawing>,124With<OcclusionCulling>,125With<DepthPrepass>,126),127>,128}129130/// The render node for the part of the indirect parameter building pass that131/// draws the meshes visible from the previous frame.132///133/// This node runs a compute shader on the output of the134/// [`EarlyGpuPreprocessNode`] in order to transform the135/// [`IndirectParametersGpuMetadata`] into properly-formatted136/// [`IndirectParametersIndexed`] and [`IndirectParametersNonIndexed`].137pub struct EarlyPrepassBuildIndirectParametersNode {138view_query: QueryState<139Read<PreprocessBindGroups>,140(141Without<SkipGpuPreprocess>,142Without<NoIndirectDrawing>,143Or<(With<DepthPrepass>, With<ShadowView>)>,144),145>,146}147148/// The render node for the part of the indirect parameter building pass that149/// draws the meshes that are potentially visible on this frame but weren't150/// visible on the previous frame.151///152/// This node runs a compute shader on the output of the153/// [`LateGpuPreprocessNode`] in order to transform the154/// [`IndirectParametersGpuMetadata`] into properly-formatted155/// [`IndirectParametersIndexed`] and [`IndirectParametersNonIndexed`].156pub struct LatePrepassBuildIndirectParametersNode {157view_query: QueryState<158Read<PreprocessBindGroups>,159(160Without<SkipGpuPreprocess>,161Without<NoIndirectDrawing>,162Or<(With<DepthPrepass>, With<ShadowView>)>,163With<OcclusionCulling>,164),165>,166}167168/// The render node for the part of the indirect parameter building pass that169/// draws all meshes, both those that are newly-visible on this frame and those170/// that were visible last frame.171///172/// This node runs a compute shader on the output of the173/// [`EarlyGpuPreprocessNode`] and [`LateGpuPreprocessNode`] in order to174/// transform the [`IndirectParametersGpuMetadata`] into properly-formatted175/// [`IndirectParametersIndexed`] and [`IndirectParametersNonIndexed`].176pub struct MainBuildIndirectParametersNode {177view_query: QueryState<178Read<PreprocessBindGroups>,179(Without<SkipGpuPreprocess>, Without<NoIndirectDrawing>),180>,181}182183/// The compute shader pipelines for the GPU mesh preprocessing and indirect184/// parameter building passes.185#[derive(Resource)]186pub struct PreprocessPipelines {187/// The pipeline used for CPU culling. This pipeline doesn't populate188/// indirect parameter metadata.189pub direct_preprocess: PreprocessPipeline,190/// The pipeline used for mesh preprocessing when GPU frustum culling is in191/// use, but occlusion culling isn't.192///193/// This pipeline populates indirect parameter metadata.194pub gpu_frustum_culling_preprocess: PreprocessPipeline,195/// The pipeline used for the first phase of occlusion culling.196///197/// This pipeline culls, transforms meshes, and populates indirect parameter198/// metadata.199pub early_gpu_occlusion_culling_preprocess: PreprocessPipeline,200/// The pipeline used for the second phase of occlusion culling.201///202/// This pipeline culls, transforms meshes, and populates indirect parameter203/// metadata.204pub late_gpu_occlusion_culling_preprocess: PreprocessPipeline,205/// The pipeline that builds indirect draw parameters for indexed meshes,206/// when frustum culling is enabled but occlusion culling *isn't* enabled.207pub gpu_frustum_culling_build_indexed_indirect_params: BuildIndirectParametersPipeline,208/// The pipeline that builds indirect draw parameters for non-indexed209/// meshes, when frustum culling is enabled but occlusion culling *isn't*210/// enabled.211pub gpu_frustum_culling_build_non_indexed_indirect_params: BuildIndirectParametersPipeline,212/// Compute shader pipelines for the early prepass phase that draws meshes213/// visible in the previous frame.214pub early_phase: PreprocessPhasePipelines,215/// Compute shader pipelines for the late prepass phase that draws meshes216/// that weren't visible in the previous frame, but became visible this217/// frame.218pub late_phase: PreprocessPhasePipelines,219/// Compute shader pipelines for the main color phase.220pub main_phase: PreprocessPhasePipelines,221}222223/// Compute shader pipelines for a specific phase: early, late, or main.224///225/// The distinction between these phases is relevant for occlusion culling.226#[derive(Clone)]227pub struct PreprocessPhasePipelines {228/// The pipeline that resets the indirect draw counts used in229/// `multi_draw_indirect_count` to 0 in preparation for a new pass.230pub reset_indirect_batch_sets: ResetIndirectBatchSetsPipeline,231/// The pipeline used for indexed indirect parameter building.232///233/// This pipeline converts indirect parameter metadata into indexed indirect234/// parameters.235pub gpu_occlusion_culling_build_indexed_indirect_params: BuildIndirectParametersPipeline,236/// The pipeline used for non-indexed indirect parameter building.237///238/// This pipeline converts indirect parameter metadata into non-indexed239/// indirect parameters.240pub gpu_occlusion_culling_build_non_indexed_indirect_params: BuildIndirectParametersPipeline,241}242243/// The pipeline for the GPU mesh preprocessing shader.244pub struct PreprocessPipeline {245/// The bind group layout for the compute shader.246pub bind_group_layout: BindGroupLayout,247/// The shader asset handle.248pub shader: Handle<Shader>,249/// The pipeline ID for the compute shader.250///251/// This gets filled in `prepare_preprocess_pipelines`.252pub pipeline_id: Option<CachedComputePipelineId>,253}254255/// The pipeline for the batch set count reset shader.256///257/// This shader resets the indirect batch set count to 0 for each view. It runs258/// in between every phase (early, late, and main).259#[derive(Clone)]260pub struct ResetIndirectBatchSetsPipeline {261/// The bind group layout for the compute shader.262pub bind_group_layout: BindGroupLayout,263/// The shader asset handle.264pub shader: Handle<Shader>,265/// The pipeline ID for the compute shader.266///267/// This gets filled in `prepare_preprocess_pipelines`.268pub pipeline_id: Option<CachedComputePipelineId>,269}270271/// The pipeline for the indirect parameter building shader.272#[derive(Clone)]273pub struct BuildIndirectParametersPipeline {274/// The bind group layout for the compute shader.275pub bind_group_layout: BindGroupLayout,276/// The shader asset handle.277pub shader: Handle<Shader>,278/// The pipeline ID for the compute shader.279///280/// This gets filled in `prepare_preprocess_pipelines`.281pub pipeline_id: Option<CachedComputePipelineId>,282}283284bitflags! {285/// Specifies variants of the mesh preprocessing shader.286#[derive(Clone, Copy, PartialEq, Eq, Hash)]287pub struct PreprocessPipelineKey: u8 {288/// Whether GPU frustum culling is in use.289///290/// This `#define`'s `FRUSTUM_CULLING` in the shader.291const FRUSTUM_CULLING = 1;292/// Whether GPU two-phase occlusion culling is in use.293///294/// This `#define`'s `OCCLUSION_CULLING` in the shader.295const OCCLUSION_CULLING = 2;296/// Whether this is the early phase of GPU two-phase occlusion culling.297///298/// This `#define`'s `EARLY_PHASE` in the shader.299const EARLY_PHASE = 4;300}301302/// Specifies variants of the indirect parameter building shader.303#[derive(Clone, Copy, PartialEq, Eq, Hash)]304pub struct BuildIndirectParametersPipelineKey: u8 {305/// Whether the indirect parameter building shader is processing indexed306/// meshes (those that have index buffers).307///308/// This defines `INDEXED` in the shader.309const INDEXED = 1;310/// Whether the GPU and driver supports `multi_draw_indirect_count`.311///312/// This defines `MULTI_DRAW_INDIRECT_COUNT_SUPPORTED` in the shader.313const MULTI_DRAW_INDIRECT_COUNT_SUPPORTED = 2;314/// Whether GPU two-phase occlusion culling is in use.315///316/// This `#define`'s `OCCLUSION_CULLING` in the shader.317const OCCLUSION_CULLING = 4;318/// Whether this is the early phase of GPU two-phase occlusion culling.319///320/// This `#define`'s `EARLY_PHASE` in the shader.321const EARLY_PHASE = 8;322/// Whether this is the late phase of GPU two-phase occlusion culling.323///324/// This `#define`'s `LATE_PHASE` in the shader.325const LATE_PHASE = 16;326/// Whether this is the phase that runs after the early and late phases,327/// and right before the main drawing logic, when GPU two-phase328/// occlusion culling is in use.329///330/// This `#define`'s `MAIN_PHASE` in the shader.331const MAIN_PHASE = 32;332}333}334335/// The compute shader bind group for the mesh preprocessing pass for each336/// render phase.337///338/// This goes on the view. It maps the [`core::any::TypeId`] of a render phase339/// (e.g. [`bevy_core_pipeline::core_3d::Opaque3d`]) to the340/// [`PhasePreprocessBindGroups`] for that phase.341#[derive(Component, Clone, Deref, DerefMut)]342pub struct PreprocessBindGroups(pub TypeIdMap<PhasePreprocessBindGroups>);343344/// The compute shader bind group for the mesh preprocessing step for a single345/// render phase on a single view.346#[derive(Clone)]347pub enum PhasePreprocessBindGroups {348/// The bind group used for the single invocation of the compute shader when349/// indirect drawing is *not* being used.350///351/// Because direct drawing doesn't require splitting the meshes into indexed352/// and non-indexed meshes, there's only one bind group in this case.353Direct(BindGroup),354355/// The bind groups used for the compute shader when indirect drawing is356/// being used, but occlusion culling isn't being used.357///358/// Because indirect drawing requires splitting the meshes into indexed and359/// non-indexed meshes, there are two bind groups here.360IndirectFrustumCulling {361/// The bind group for indexed meshes.362indexed: Option<BindGroup>,363/// The bind group for non-indexed meshes.364non_indexed: Option<BindGroup>,365},366367/// The bind groups used for the compute shader when indirect drawing is368/// being used, but occlusion culling isn't being used.369///370/// Because indirect drawing requires splitting the meshes into indexed and371/// non-indexed meshes, and because occlusion culling requires splitting372/// this phase into early and late versions, there are four bind groups373/// here.374IndirectOcclusionCulling {375/// The bind group for indexed meshes during the early mesh376/// preprocessing phase.377early_indexed: Option<BindGroup>,378/// The bind group for non-indexed meshes during the early mesh379/// preprocessing phase.380early_non_indexed: Option<BindGroup>,381/// The bind group for indexed meshes during the late mesh preprocessing382/// phase.383late_indexed: Option<BindGroup>,384/// The bind group for non-indexed meshes during the late mesh385/// preprocessing phase.386late_non_indexed: Option<BindGroup>,387},388}389390/// The bind groups for the compute shaders that reset indirect draw counts and391/// build indirect parameters.392///393/// There's one set of bind group for each phase. Phases are keyed off their394/// [`core::any::TypeId`].395#[derive(Resource, Default, Deref, DerefMut)]396pub struct BuildIndirectParametersBindGroups(pub TypeIdMap<PhaseBuildIndirectParametersBindGroups>);397398impl BuildIndirectParametersBindGroups {399/// Creates a new, empty [`BuildIndirectParametersBindGroups`] table.400pub fn new() -> BuildIndirectParametersBindGroups {401Self::default()402}403}404405/// The per-phase set of bind groups for the compute shaders that reset indirect406/// draw counts and build indirect parameters.407pub struct PhaseBuildIndirectParametersBindGroups {408/// The bind group for the `reset_indirect_batch_sets.wgsl` shader, for409/// indexed meshes.410reset_indexed_indirect_batch_sets: Option<BindGroup>,411/// The bind group for the `reset_indirect_batch_sets.wgsl` shader, for412/// non-indexed meshes.413reset_non_indexed_indirect_batch_sets: Option<BindGroup>,414/// The bind group for the `build_indirect_params.wgsl` shader, for indexed415/// meshes.416build_indexed_indirect: Option<BindGroup>,417/// The bind group for the `build_indirect_params.wgsl` shader, for418/// non-indexed meshes.419build_non_indexed_indirect: Option<BindGroup>,420}421422/// Stops the `GpuPreprocessNode` attempting to generate the buffer for this view423/// useful to avoid duplicating effort if the bind group is shared between views424#[derive(Component, Default)]425pub struct SkipGpuPreprocess;426427impl Plugin for GpuMeshPreprocessPlugin {428fn build(&self, app: &mut App) {429embedded_asset!(app, "mesh_preprocess.wgsl");430embedded_asset!(app, "reset_indirect_batch_sets.wgsl");431embedded_asset!(app, "build_indirect_params.wgsl");432}433434fn finish(&self, app: &mut App) {435let Some(render_app) = app.get_sub_app_mut(RenderApp) else {436return;437};438439// This plugin does nothing if GPU instance buffer building isn't in440// use.441let gpu_preprocessing_support = render_app.world().resource::<GpuPreprocessingSupport>();442if !self.use_gpu_instance_buffer_builder || !gpu_preprocessing_support.is_available() {443return;444}445446render_app447.init_resource::<PreprocessPipelines>()448.init_resource::<SpecializedComputePipelines<PreprocessPipeline>>()449.init_resource::<SpecializedComputePipelines<ResetIndirectBatchSetsPipeline>>()450.init_resource::<SpecializedComputePipelines<BuildIndirectParametersPipeline>>()451.add_systems(452Render,453(454prepare_preprocess_pipelines.in_set(RenderSystems::Prepare),455prepare_preprocess_bind_groups456.run_if(resource_exists::<BatchedInstanceBuffers<457MeshUniform,458MeshInputUniform459>>)460.in_set(RenderSystems::PrepareBindGroups),461write_mesh_culling_data_buffer.in_set(RenderSystems::PrepareResourcesFlush),462),463)464.add_render_graph_node::<ClearIndirectParametersMetadataNode>(465Core3d,466NodePbr::ClearIndirectParametersMetadata467)468.add_render_graph_node::<EarlyGpuPreprocessNode>(Core3d, NodePbr::EarlyGpuPreprocess)469.add_render_graph_node::<LateGpuPreprocessNode>(Core3d, NodePbr::LateGpuPreprocess)470.add_render_graph_node::<EarlyPrepassBuildIndirectParametersNode>(471Core3d,472NodePbr::EarlyPrepassBuildIndirectParameters,473)474.add_render_graph_node::<LatePrepassBuildIndirectParametersNode>(475Core3d,476NodePbr::LatePrepassBuildIndirectParameters,477)478.add_render_graph_node::<MainBuildIndirectParametersNode>(479Core3d,480NodePbr::MainBuildIndirectParameters,481)482.add_render_graph_edges(483Core3d,484(485NodePbr::ClearIndirectParametersMetadata,486NodePbr::EarlyGpuPreprocess,487NodePbr::EarlyPrepassBuildIndirectParameters,488Node3d::EarlyPrepass,489Node3d::EarlyDeferredPrepass,490Node3d::EarlyDownsampleDepth,491NodePbr::LateGpuPreprocess,492NodePbr::LatePrepassBuildIndirectParameters,493Node3d::LatePrepass,494Node3d::LateDeferredPrepass,495NodePbr::MainBuildIndirectParameters,496Node3d::StartMainPass,497),498).add_render_graph_edges(499Core3d,500(501NodePbr::EarlyPrepassBuildIndirectParameters,502NodePbr::EarlyShadowPass,503Node3d::EarlyDownsampleDepth,504)505).add_render_graph_edges(506Core3d,507(508NodePbr::LatePrepassBuildIndirectParameters,509NodePbr::LateShadowPass,510NodePbr::MainBuildIndirectParameters,511)512);513}514}515516impl Node for ClearIndirectParametersMetadataNode {517fn run<'w>(518&self,519_: &mut RenderGraphContext,520render_context: &mut RenderContext<'w>,521world: &'w World,522) -> Result<(), NodeRunError> {523let Some(indirect_parameters_buffers) = world.get_resource::<IndirectParametersBuffers>()524else {525return Ok(());526};527528// Clear out each indexed and non-indexed GPU-side buffer.529for phase_indirect_parameters_buffers in indirect_parameters_buffers.values() {530if let Some(indexed_gpu_metadata_buffer) = phase_indirect_parameters_buffers531.indexed532.gpu_metadata_buffer()533{534render_context.command_encoder().clear_buffer(535indexed_gpu_metadata_buffer,5360,537Some(538phase_indirect_parameters_buffers.indexed.batch_count() as u64539* size_of::<IndirectParametersGpuMetadata>() as u64,540),541);542}543544if let Some(non_indexed_gpu_metadata_buffer) = phase_indirect_parameters_buffers545.non_indexed546.gpu_metadata_buffer()547{548render_context.command_encoder().clear_buffer(549non_indexed_gpu_metadata_buffer,5500,551Some(552phase_indirect_parameters_buffers.non_indexed.batch_count() as u64553* size_of::<IndirectParametersGpuMetadata>() as u64,554),555);556}557}558559Ok(())560}561}562563impl FromWorld for EarlyGpuPreprocessNode {564fn from_world(world: &mut World) -> Self {565Self {566view_query: QueryState::new(world),567main_view_query: QueryState::new(world),568}569}570}571572impl Node for EarlyGpuPreprocessNode {573fn update(&mut self, world: &mut World) {574self.view_query.update_archetypes(world);575self.main_view_query.update_archetypes(world);576}577578fn run<'w>(579&self,580graph: &mut RenderGraphContext,581render_context: &mut RenderContext<'w>,582world: &'w World,583) -> Result<(), NodeRunError> {584let diagnostics = render_context.diagnostic_recorder();585586// Grab the [`BatchedInstanceBuffers`].587let batched_instance_buffers =588world.resource::<BatchedInstanceBuffers<MeshUniform, MeshInputUniform>>();589590let pipeline_cache = world.resource::<PipelineCache>();591let preprocess_pipelines = world.resource::<PreprocessPipelines>();592593let mut compute_pass =594render_context595.command_encoder()596.begin_compute_pass(&ComputePassDescriptor {597label: Some("early_mesh_preprocessing"),598timestamp_writes: None,599});600let pass_span = diagnostics.time_span(&mut compute_pass, "early_mesh_preprocessing");601602let mut all_views: SmallVec<[_; 8]> = SmallVec::new();603all_views.push(graph.view_entity());604if let Ok(shadow_cascade_views) =605self.main_view_query.get_manual(world, graph.view_entity())606{607all_views.extend(shadow_cascade_views.lights.iter().copied());608}609610// Run the compute passes.611612for view_entity in all_views {613let Ok((614view,615bind_groups,616view_uniform_offset,617no_indirect_drawing,618occlusion_culling,619)) = self.view_query.get_manual(world, view_entity)620else {621continue;622};623624let Some(bind_groups) = bind_groups else {625continue;626};627let Some(view_uniform_offset) = view_uniform_offset else {628continue;629};630631// Select the right pipeline, depending on whether GPU culling is in632// use.633let maybe_pipeline_id = if no_indirect_drawing {634preprocess_pipelines.direct_preprocess.pipeline_id635} else if occlusion_culling {636preprocess_pipelines637.early_gpu_occlusion_culling_preprocess638.pipeline_id639} else {640preprocess_pipelines641.gpu_frustum_culling_preprocess642.pipeline_id643};644645// Fetch the pipeline.646let Some(preprocess_pipeline_id) = maybe_pipeline_id else {647warn!("The build mesh uniforms pipeline wasn't ready");648continue;649};650651let Some(preprocess_pipeline) =652pipeline_cache.get_compute_pipeline(preprocess_pipeline_id)653else {654// This will happen while the pipeline is being compiled and is fine.655continue;656};657658compute_pass.set_pipeline(preprocess_pipeline);659660// Loop over each render phase.661for (phase_type_id, batched_phase_instance_buffers) in662&batched_instance_buffers.phase_instance_buffers663{664// Grab the work item buffers for this view.665let Some(work_item_buffers) = batched_phase_instance_buffers666.work_item_buffers667.get(&view.retained_view_entity)668else {669continue;670};671672// Fetch the bind group for the render phase.673let Some(phase_bind_groups) = bind_groups.get(phase_type_id) else {674continue;675};676677// Make sure the mesh preprocessing shader has access to the678// view info it needs to do culling and motion vector679// computation.680let dynamic_offsets = [view_uniform_offset.offset];681682// Are we drawing directly or indirectly?683match *phase_bind_groups {684PhasePreprocessBindGroups::Direct(ref bind_group) => {685// Invoke the mesh preprocessing shader to transform686// meshes only, but not cull.687let PreprocessWorkItemBuffers::Direct(work_item_buffer) = work_item_buffers688else {689continue;690};691compute_pass.set_bind_group(0, bind_group, &dynamic_offsets);692let workgroup_count = work_item_buffer.len().div_ceil(WORKGROUP_SIZE);693if workgroup_count > 0 {694compute_pass.dispatch_workgroups(workgroup_count as u32, 1, 1);695}696}697698PhasePreprocessBindGroups::IndirectFrustumCulling {699indexed: ref maybe_indexed_bind_group,700non_indexed: ref maybe_non_indexed_bind_group,701}702| PhasePreprocessBindGroups::IndirectOcclusionCulling {703early_indexed: ref maybe_indexed_bind_group,704early_non_indexed: ref maybe_non_indexed_bind_group,705..706} => {707// Invoke the mesh preprocessing shader to transform and708// cull the meshes.709let PreprocessWorkItemBuffers::Indirect {710indexed: indexed_buffer,711non_indexed: non_indexed_buffer,712..713} = work_item_buffers714else {715continue;716};717718// Transform and cull indexed meshes if there are any.719if let Some(indexed_bind_group) = maybe_indexed_bind_group {720if let PreprocessWorkItemBuffers::Indirect {721gpu_occlusion_culling:722Some(GpuOcclusionCullingWorkItemBuffers {723late_indirect_parameters_indexed_offset,724..725}),726..727} = *work_item_buffers728{729compute_pass.set_push_constants(7300,731bytemuck::bytes_of(&late_indirect_parameters_indexed_offset),732);733}734735compute_pass.set_bind_group(0, indexed_bind_group, &dynamic_offsets);736let workgroup_count = indexed_buffer.len().div_ceil(WORKGROUP_SIZE);737if workgroup_count > 0 {738compute_pass.dispatch_workgroups(workgroup_count as u32, 1, 1);739}740}741742// Transform and cull non-indexed meshes if there are any.743if let Some(non_indexed_bind_group) = maybe_non_indexed_bind_group {744if let PreprocessWorkItemBuffers::Indirect {745gpu_occlusion_culling:746Some(GpuOcclusionCullingWorkItemBuffers {747late_indirect_parameters_non_indexed_offset,748..749}),750..751} = *work_item_buffers752{753compute_pass.set_push_constants(7540,755bytemuck::bytes_of(756&late_indirect_parameters_non_indexed_offset,757),758);759}760761compute_pass.set_bind_group(7620,763non_indexed_bind_group,764&dynamic_offsets,765);766let workgroup_count = non_indexed_buffer.len().div_ceil(WORKGROUP_SIZE);767if workgroup_count > 0 {768compute_pass.dispatch_workgroups(workgroup_count as u32, 1, 1);769}770}771}772}773}774}775776pass_span.end(&mut compute_pass);777778Ok(())779}780}781782impl FromWorld for EarlyPrepassBuildIndirectParametersNode {783fn from_world(world: &mut World) -> Self {784Self {785view_query: QueryState::new(world),786}787}788}789790impl FromWorld for LatePrepassBuildIndirectParametersNode {791fn from_world(world: &mut World) -> Self {792Self {793view_query: QueryState::new(world),794}795}796}797798impl FromWorld for MainBuildIndirectParametersNode {799fn from_world(world: &mut World) -> Self {800Self {801view_query: QueryState::new(world),802}803}804}805806impl FromWorld for LateGpuPreprocessNode {807fn from_world(world: &mut World) -> Self {808Self {809view_query: QueryState::new(world),810}811}812}813814impl Node for LateGpuPreprocessNode {815fn update(&mut self, world: &mut World) {816self.view_query.update_archetypes(world);817}818819fn run<'w>(820&self,821_: &mut RenderGraphContext,822render_context: &mut RenderContext<'w>,823world: &'w World,824) -> Result<(), NodeRunError> {825let diagnostics = render_context.diagnostic_recorder();826827// Grab the [`BatchedInstanceBuffers`].828let batched_instance_buffers =829world.resource::<BatchedInstanceBuffers<MeshUniform, MeshInputUniform>>();830831let pipeline_cache = world.resource::<PipelineCache>();832let preprocess_pipelines = world.resource::<PreprocessPipelines>();833834let mut compute_pass =835render_context836.command_encoder()837.begin_compute_pass(&ComputePassDescriptor {838label: Some("late_mesh_preprocessing"),839timestamp_writes: None,840});841let pass_span = diagnostics.time_span(&mut compute_pass, "late_mesh_preprocessing");842843// Run the compute passes.844for (view, bind_groups, view_uniform_offset) in self.view_query.iter_manual(world) {845let maybe_pipeline_id = preprocess_pipelines846.late_gpu_occlusion_culling_preprocess847.pipeline_id;848849// Fetch the pipeline.850let Some(preprocess_pipeline_id) = maybe_pipeline_id else {851warn!("The build mesh uniforms pipeline wasn't ready");852return Ok(());853};854855let Some(preprocess_pipeline) =856pipeline_cache.get_compute_pipeline(preprocess_pipeline_id)857else {858// This will happen while the pipeline is being compiled and is fine.859return Ok(());860};861862compute_pass.set_pipeline(preprocess_pipeline);863864// Loop over each phase. Because we built the phases in parallel,865// each phase has a separate set of instance buffers.866for (phase_type_id, batched_phase_instance_buffers) in867&batched_instance_buffers.phase_instance_buffers868{869let UntypedPhaseBatchedInstanceBuffers {870ref work_item_buffers,871ref late_indexed_indirect_parameters_buffer,872ref late_non_indexed_indirect_parameters_buffer,873..874} = *batched_phase_instance_buffers;875876// Grab the work item buffers for this view.877let Some(phase_work_item_buffers) =878work_item_buffers.get(&view.retained_view_entity)879else {880continue;881};882883let (884PreprocessWorkItemBuffers::Indirect {885gpu_occlusion_culling:886Some(GpuOcclusionCullingWorkItemBuffers {887late_indirect_parameters_indexed_offset,888late_indirect_parameters_non_indexed_offset,889..890}),891..892},893Some(PhasePreprocessBindGroups::IndirectOcclusionCulling {894late_indexed: maybe_late_indexed_bind_group,895late_non_indexed: maybe_late_non_indexed_bind_group,896..897}),898Some(late_indexed_indirect_parameters_buffer),899Some(late_non_indexed_indirect_parameters_buffer),900) = (901phase_work_item_buffers,902bind_groups.get(phase_type_id),903late_indexed_indirect_parameters_buffer.buffer(),904late_non_indexed_indirect_parameters_buffer.buffer(),905)906else {907continue;908};909910let mut dynamic_offsets: SmallVec<[u32; 1]> = smallvec![];911dynamic_offsets.push(view_uniform_offset.offset);912913// If there's no space reserved for work items, then don't914// bother doing the dispatch, as there can't possibly be any915// meshes of the given class (indexed or non-indexed) in this916// phase.917918// Transform and cull indexed meshes if there are any.919if let Some(late_indexed_bind_group) = maybe_late_indexed_bind_group {920compute_pass.set_push_constants(9210,922bytemuck::bytes_of(late_indirect_parameters_indexed_offset),923);924925compute_pass.set_bind_group(0, late_indexed_bind_group, &dynamic_offsets);926compute_pass.dispatch_workgroups_indirect(927late_indexed_indirect_parameters_buffer,928(*late_indirect_parameters_indexed_offset as u64)929* (size_of::<LatePreprocessWorkItemIndirectParameters>() as u64),930);931}932933// Transform and cull non-indexed meshes if there are any.934if let Some(late_non_indexed_bind_group) = maybe_late_non_indexed_bind_group {935compute_pass.set_push_constants(9360,937bytemuck::bytes_of(late_indirect_parameters_non_indexed_offset),938);939940compute_pass.set_bind_group(0, late_non_indexed_bind_group, &dynamic_offsets);941compute_pass.dispatch_workgroups_indirect(942late_non_indexed_indirect_parameters_buffer,943(*late_indirect_parameters_non_indexed_offset as u64)944* (size_of::<LatePreprocessWorkItemIndirectParameters>() as u64),945);946}947}948}949950pass_span.end(&mut compute_pass);951952Ok(())953}954}955956impl Node for EarlyPrepassBuildIndirectParametersNode {957fn update(&mut self, world: &mut World) {958self.view_query.update_archetypes(world);959}960961fn run<'w>(962&self,963_: &mut RenderGraphContext,964render_context: &mut RenderContext<'w>,965world: &'w World,966) -> Result<(), NodeRunError> {967let preprocess_pipelines = world.resource::<PreprocessPipelines>();968969// If there are no views with a depth prepass enabled, we don't need to970// run this.971if self.view_query.iter_manual(world).next().is_none() {972return Ok(());973}974975run_build_indirect_parameters_node(976render_context,977world,978&preprocess_pipelines.early_phase,979"early_prepass_indirect_parameters_building",980)981}982}983984impl Node for LatePrepassBuildIndirectParametersNode {985fn update(&mut self, world: &mut World) {986self.view_query.update_archetypes(world);987}988989fn run<'w>(990&self,991_: &mut RenderGraphContext,992render_context: &mut RenderContext<'w>,993world: &'w World,994) -> Result<(), NodeRunError> {995let preprocess_pipelines = world.resource::<PreprocessPipelines>();996997// If there are no views with occlusion culling enabled, we don't need998// to run this.999if self.view_query.iter_manual(world).next().is_none() {1000return Ok(());1001}10021003run_build_indirect_parameters_node(1004render_context,1005world,1006&preprocess_pipelines.late_phase,1007"late_prepass_indirect_parameters_building",1008)1009}1010}10111012impl Node for MainBuildIndirectParametersNode {1013fn update(&mut self, world: &mut World) {1014self.view_query.update_archetypes(world);1015}10161017fn run<'w>(1018&self,1019_: &mut RenderGraphContext,1020render_context: &mut RenderContext<'w>,1021world: &'w World,1022) -> Result<(), NodeRunError> {1023let preprocess_pipelines = world.resource::<PreprocessPipelines>();10241025run_build_indirect_parameters_node(1026render_context,1027world,1028&preprocess_pipelines.main_phase,1029"main_indirect_parameters_building",1030)1031}1032}10331034fn run_build_indirect_parameters_node(1035render_context: &mut RenderContext,1036world: &World,1037preprocess_phase_pipelines: &PreprocessPhasePipelines,1038label: &'static str,1039) -> Result<(), NodeRunError> {1040let Some(build_indirect_params_bind_groups) =1041world.get_resource::<BuildIndirectParametersBindGroups>()1042else {1043return Ok(());1044};10451046let diagnostics = render_context.diagnostic_recorder();10471048let pipeline_cache = world.resource::<PipelineCache>();1049let indirect_parameters_buffers = world.resource::<IndirectParametersBuffers>();10501051let mut compute_pass =1052render_context1053.command_encoder()1054.begin_compute_pass(&ComputePassDescriptor {1055label: Some(label),1056timestamp_writes: None,1057});1058let pass_span = diagnostics.time_span(&mut compute_pass, label);10591060// Fetch the pipeline.1061let (1062Some(reset_indirect_batch_sets_pipeline_id),1063Some(build_indexed_indirect_params_pipeline_id),1064Some(build_non_indexed_indirect_params_pipeline_id),1065) = (1066preprocess_phase_pipelines1067.reset_indirect_batch_sets1068.pipeline_id,1069preprocess_phase_pipelines1070.gpu_occlusion_culling_build_indexed_indirect_params1071.pipeline_id,1072preprocess_phase_pipelines1073.gpu_occlusion_culling_build_non_indexed_indirect_params1074.pipeline_id,1075)1076else {1077warn!("The build indirect parameters pipelines weren't ready");1078pass_span.end(&mut compute_pass);1079return Ok(());1080};10811082let (1083Some(reset_indirect_batch_sets_pipeline),1084Some(build_indexed_indirect_params_pipeline),1085Some(build_non_indexed_indirect_params_pipeline),1086) = (1087pipeline_cache.get_compute_pipeline(reset_indirect_batch_sets_pipeline_id),1088pipeline_cache.get_compute_pipeline(build_indexed_indirect_params_pipeline_id),1089pipeline_cache.get_compute_pipeline(build_non_indexed_indirect_params_pipeline_id),1090)1091else {1092// This will happen while the pipeline is being compiled and is fine.1093pass_span.end(&mut compute_pass);1094return Ok(());1095};10961097// Loop over each phase. As each has as separate set of buffers, we need to1098// build indirect parameters individually for each phase.1099for (phase_type_id, phase_build_indirect_params_bind_groups) in1100build_indirect_params_bind_groups.iter()1101{1102let Some(phase_indirect_parameters_buffers) =1103indirect_parameters_buffers.get(phase_type_id)1104else {1105continue;1106};11071108// Build indexed indirect parameters.1109if let (1110Some(reset_indexed_indirect_batch_sets_bind_group),1111Some(build_indirect_indexed_params_bind_group),1112) = (1113&phase_build_indirect_params_bind_groups.reset_indexed_indirect_batch_sets,1114&phase_build_indirect_params_bind_groups.build_indexed_indirect,1115) {1116compute_pass.set_pipeline(reset_indirect_batch_sets_pipeline);1117compute_pass.set_bind_group(0, reset_indexed_indirect_batch_sets_bind_group, &[]);1118let workgroup_count = phase_indirect_parameters_buffers1119.batch_set_count(true)1120.div_ceil(WORKGROUP_SIZE);1121if workgroup_count > 0 {1122compute_pass.dispatch_workgroups(workgroup_count as u32, 1, 1);1123}11241125compute_pass.set_pipeline(build_indexed_indirect_params_pipeline);1126compute_pass.set_bind_group(0, build_indirect_indexed_params_bind_group, &[]);1127let workgroup_count = phase_indirect_parameters_buffers1128.indexed1129.batch_count()1130.div_ceil(WORKGROUP_SIZE);1131if workgroup_count > 0 {1132compute_pass.dispatch_workgroups(workgroup_count as u32, 1, 1);1133}1134}11351136// Build non-indexed indirect parameters.1137if let (1138Some(reset_non_indexed_indirect_batch_sets_bind_group),1139Some(build_indirect_non_indexed_params_bind_group),1140) = (1141&phase_build_indirect_params_bind_groups.reset_non_indexed_indirect_batch_sets,1142&phase_build_indirect_params_bind_groups.build_non_indexed_indirect,1143) {1144compute_pass.set_pipeline(reset_indirect_batch_sets_pipeline);1145compute_pass.set_bind_group(0, reset_non_indexed_indirect_batch_sets_bind_group, &[]);1146let workgroup_count = phase_indirect_parameters_buffers1147.batch_set_count(false)1148.div_ceil(WORKGROUP_SIZE);1149if workgroup_count > 0 {1150compute_pass.dispatch_workgroups(workgroup_count as u32, 1, 1);1151}11521153compute_pass.set_pipeline(build_non_indexed_indirect_params_pipeline);1154compute_pass.set_bind_group(0, build_indirect_non_indexed_params_bind_group, &[]);1155let workgroup_count = phase_indirect_parameters_buffers1156.non_indexed1157.batch_count()1158.div_ceil(WORKGROUP_SIZE);1159if workgroup_count > 0 {1160compute_pass.dispatch_workgroups(workgroup_count as u32, 1, 1);1161}1162}1163}11641165pass_span.end(&mut compute_pass);11661167Ok(())1168}11691170impl PreprocessPipelines {1171/// Returns true if the preprocessing and indirect parameters pipelines have1172/// been loaded or false otherwise.1173pub(crate) fn pipelines_are_loaded(1174&self,1175pipeline_cache: &PipelineCache,1176preprocessing_support: &GpuPreprocessingSupport,1177) -> bool {1178match preprocessing_support.max_supported_mode {1179GpuPreprocessingMode::None => false,1180GpuPreprocessingMode::PreprocessingOnly => {1181self.direct_preprocess.is_loaded(pipeline_cache)1182&& self1183.gpu_frustum_culling_preprocess1184.is_loaded(pipeline_cache)1185}1186GpuPreprocessingMode::Culling => {1187self.direct_preprocess.is_loaded(pipeline_cache)1188&& self1189.gpu_frustum_culling_preprocess1190.is_loaded(pipeline_cache)1191&& self1192.early_gpu_occlusion_culling_preprocess1193.is_loaded(pipeline_cache)1194&& self1195.late_gpu_occlusion_culling_preprocess1196.is_loaded(pipeline_cache)1197&& self1198.gpu_frustum_culling_build_indexed_indirect_params1199.is_loaded(pipeline_cache)1200&& self1201.gpu_frustum_culling_build_non_indexed_indirect_params1202.is_loaded(pipeline_cache)1203&& self.early_phase.is_loaded(pipeline_cache)1204&& self.late_phase.is_loaded(pipeline_cache)1205&& self.main_phase.is_loaded(pipeline_cache)1206}1207}1208}1209}12101211impl PreprocessPhasePipelines {1212fn is_loaded(&self, pipeline_cache: &PipelineCache) -> bool {1213self.reset_indirect_batch_sets.is_loaded(pipeline_cache)1214&& self1215.gpu_occlusion_culling_build_indexed_indirect_params1216.is_loaded(pipeline_cache)1217&& self1218.gpu_occlusion_culling_build_non_indexed_indirect_params1219.is_loaded(pipeline_cache)1220}1221}12221223impl PreprocessPipeline {1224fn is_loaded(&self, pipeline_cache: &PipelineCache) -> bool {1225self.pipeline_id1226.is_some_and(|pipeline_id| pipeline_cache.get_compute_pipeline(pipeline_id).is_some())1227}1228}12291230impl ResetIndirectBatchSetsPipeline {1231fn is_loaded(&self, pipeline_cache: &PipelineCache) -> bool {1232self.pipeline_id1233.is_some_and(|pipeline_id| pipeline_cache.get_compute_pipeline(pipeline_id).is_some())1234}1235}12361237impl BuildIndirectParametersPipeline {1238/// Returns true if this pipeline has been loaded into the pipeline cache or1239/// false otherwise.1240fn is_loaded(&self, pipeline_cache: &PipelineCache) -> bool {1241self.pipeline_id1242.is_some_and(|pipeline_id| pipeline_cache.get_compute_pipeline(pipeline_id).is_some())1243}1244}12451246impl SpecializedComputePipeline for PreprocessPipeline {1247type Key = PreprocessPipelineKey;12481249fn specialize(&self, key: Self::Key) -> ComputePipelineDescriptor {1250let mut shader_defs = vec!["WRITE_INDIRECT_PARAMETERS_METADATA".into()];1251if key.contains(PreprocessPipelineKey::FRUSTUM_CULLING) {1252shader_defs.push("INDIRECT".into());1253shader_defs.push("FRUSTUM_CULLING".into());1254}1255if key.contains(PreprocessPipelineKey::OCCLUSION_CULLING) {1256shader_defs.push("OCCLUSION_CULLING".into());1257if key.contains(PreprocessPipelineKey::EARLY_PHASE) {1258shader_defs.push("EARLY_PHASE".into());1259} else {1260shader_defs.push("LATE_PHASE".into());1261}1262}12631264ComputePipelineDescriptor {1265label: Some(1266format!(1267"mesh preprocessing ({})",1268if key.contains(1269PreprocessPipelineKey::OCCLUSION_CULLING1270| PreprocessPipelineKey::EARLY_PHASE1271) {1272"early GPU occlusion culling"1273} else if key.contains(PreprocessPipelineKey::OCCLUSION_CULLING) {1274"late GPU occlusion culling"1275} else if key.contains(PreprocessPipelineKey::FRUSTUM_CULLING) {1276"GPU frustum culling"1277} else {1278"direct"1279}1280)1281.into(),1282),1283layout: vec![self.bind_group_layout.clone()],1284push_constant_ranges: if key.contains(PreprocessPipelineKey::OCCLUSION_CULLING) {1285vec![PushConstantRange {1286stages: ShaderStages::COMPUTE,1287range: 0..4,1288}]1289} else {1290vec![]1291},1292shader: self.shader.clone(),1293shader_defs,1294..default()1295}1296}1297}12981299impl FromWorld for PreprocessPipelines {1300fn from_world(world: &mut World) -> Self {1301let render_device = world.resource::<RenderDevice>();13021303// GPU culling bind group parameters are a superset of those in the CPU1304// culling (direct) shader.1305let direct_bind_group_layout_entries = preprocess_direct_bind_group_layout_entries();1306let gpu_frustum_culling_bind_group_layout_entries = gpu_culling_bind_group_layout_entries();1307let gpu_early_occlusion_culling_bind_group_layout_entries =1308gpu_occlusion_culling_bind_group_layout_entries().extend_with_indices(((130911,1310storage_buffer::<PreprocessWorkItem>(/*has_dynamic_offset=*/ false),1311),));1312let gpu_late_occlusion_culling_bind_group_layout_entries =1313gpu_occlusion_culling_bind_group_layout_entries();13141315let reset_indirect_batch_sets_bind_group_layout_entries =1316DynamicBindGroupLayoutEntries::sequential(1317ShaderStages::COMPUTE,1318(storage_buffer::<IndirectBatchSet>(false),),1319);13201321// Indexed and non-indexed bind group parameters share all the bind1322// group layout entries except the final one.1323let build_indexed_indirect_params_bind_group_layout_entries =1324build_indirect_params_bind_group_layout_entries()1325.extend_sequential((storage_buffer::<IndirectParametersIndexed>(false),));1326let build_non_indexed_indirect_params_bind_group_layout_entries =1327build_indirect_params_bind_group_layout_entries()1328.extend_sequential((storage_buffer::<IndirectParametersNonIndexed>(false),));13291330// Create the bind group layouts.1331let direct_bind_group_layout = render_device.create_bind_group_layout(1332"build mesh uniforms direct bind group layout",1333&direct_bind_group_layout_entries,1334);1335let gpu_frustum_culling_bind_group_layout = render_device.create_bind_group_layout(1336"build mesh uniforms GPU frustum culling bind group layout",1337&gpu_frustum_culling_bind_group_layout_entries,1338);1339let gpu_early_occlusion_culling_bind_group_layout = render_device.create_bind_group_layout(1340"build mesh uniforms GPU early occlusion culling bind group layout",1341&gpu_early_occlusion_culling_bind_group_layout_entries,1342);1343let gpu_late_occlusion_culling_bind_group_layout = render_device.create_bind_group_layout(1344"build mesh uniforms GPU late occlusion culling bind group layout",1345&gpu_late_occlusion_culling_bind_group_layout_entries,1346);1347let reset_indirect_batch_sets_bind_group_layout = render_device.create_bind_group_layout(1348"reset indirect batch sets bind group layout",1349&reset_indirect_batch_sets_bind_group_layout_entries,1350);1351let build_indexed_indirect_params_bind_group_layout = render_device1352.create_bind_group_layout(1353"build indexed indirect parameters bind group layout",1354&build_indexed_indirect_params_bind_group_layout_entries,1355);1356let build_non_indexed_indirect_params_bind_group_layout = render_device1357.create_bind_group_layout(1358"build non-indexed indirect parameters bind group layout",1359&build_non_indexed_indirect_params_bind_group_layout_entries,1360);13611362let preprocess_shader = load_embedded_asset!(world, "mesh_preprocess.wgsl");1363let reset_indirect_batch_sets_shader =1364load_embedded_asset!(world, "reset_indirect_batch_sets.wgsl");1365let build_indirect_params_shader =1366load_embedded_asset!(world, "build_indirect_params.wgsl");13671368let preprocess_phase_pipelines = PreprocessPhasePipelines {1369reset_indirect_batch_sets: ResetIndirectBatchSetsPipeline {1370bind_group_layout: reset_indirect_batch_sets_bind_group_layout.clone(),1371shader: reset_indirect_batch_sets_shader,1372pipeline_id: None,1373},1374gpu_occlusion_culling_build_indexed_indirect_params: BuildIndirectParametersPipeline {1375bind_group_layout: build_indexed_indirect_params_bind_group_layout.clone(),1376shader: build_indirect_params_shader.clone(),1377pipeline_id: None,1378},1379gpu_occlusion_culling_build_non_indexed_indirect_params:1380BuildIndirectParametersPipeline {1381bind_group_layout: build_non_indexed_indirect_params_bind_group_layout.clone(),1382shader: build_indirect_params_shader.clone(),1383pipeline_id: None,1384},1385};13861387PreprocessPipelines {1388direct_preprocess: PreprocessPipeline {1389bind_group_layout: direct_bind_group_layout,1390shader: preprocess_shader.clone(),1391pipeline_id: None,1392},1393gpu_frustum_culling_preprocess: PreprocessPipeline {1394bind_group_layout: gpu_frustum_culling_bind_group_layout,1395shader: preprocess_shader.clone(),1396pipeline_id: None,1397},1398early_gpu_occlusion_culling_preprocess: PreprocessPipeline {1399bind_group_layout: gpu_early_occlusion_culling_bind_group_layout,1400shader: preprocess_shader.clone(),1401pipeline_id: None,1402},1403late_gpu_occlusion_culling_preprocess: PreprocessPipeline {1404bind_group_layout: gpu_late_occlusion_culling_bind_group_layout,1405shader: preprocess_shader,1406pipeline_id: None,1407},1408gpu_frustum_culling_build_indexed_indirect_params: BuildIndirectParametersPipeline {1409bind_group_layout: build_indexed_indirect_params_bind_group_layout.clone(),1410shader: build_indirect_params_shader.clone(),1411pipeline_id: None,1412},1413gpu_frustum_culling_build_non_indexed_indirect_params:1414BuildIndirectParametersPipeline {1415bind_group_layout: build_non_indexed_indirect_params_bind_group_layout.clone(),1416shader: build_indirect_params_shader,1417pipeline_id: None,1418},1419early_phase: preprocess_phase_pipelines.clone(),1420late_phase: preprocess_phase_pipelines.clone(),1421main_phase: preprocess_phase_pipelines.clone(),1422}1423}1424}14251426fn preprocess_direct_bind_group_layout_entries() -> DynamicBindGroupLayoutEntries {1427DynamicBindGroupLayoutEntries::new_with_indices(1428ShaderStages::COMPUTE,1429(1430// `view`1431(14320,1433uniform_buffer::<ViewUniform>(/* has_dynamic_offset= */ true),1434),1435// `current_input`1436(3, storage_buffer_read_only::<MeshInputUniform>(false)),1437// `previous_input`1438(4, storage_buffer_read_only::<MeshInputUniform>(false)),1439// `indices`1440(5, storage_buffer_read_only::<PreprocessWorkItem>(false)),1441// `output`1442(6, storage_buffer::<MeshUniform>(false)),1443),1444)1445}14461447// Returns the first 4 bind group layout entries shared between all invocations1448// of the indirect parameters building shader.1449fn build_indirect_params_bind_group_layout_entries() -> DynamicBindGroupLayoutEntries {1450DynamicBindGroupLayoutEntries::new_with_indices(1451ShaderStages::COMPUTE,1452(1453(0, storage_buffer_read_only::<MeshInputUniform>(false)),1454(14551,1456storage_buffer_read_only::<IndirectParametersCpuMetadata>(false),1457),1458(14592,1460storage_buffer_read_only::<IndirectParametersGpuMetadata>(false),1461),1462(3, storage_buffer::<IndirectBatchSet>(false)),1463),1464)1465}14661467/// A system that specializes the `mesh_preprocess.wgsl` and1468/// `build_indirect_params.wgsl` pipelines if necessary.1469fn gpu_culling_bind_group_layout_entries() -> DynamicBindGroupLayoutEntries {1470// GPU culling bind group parameters are a superset of those in the CPU1471// culling (direct) shader.1472preprocess_direct_bind_group_layout_entries().extend_with_indices((1473// `indirect_parameters_cpu_metadata`1474(14757,1476storage_buffer_read_only::<IndirectParametersCpuMetadata>(1477/* has_dynamic_offset= */ false,1478),1479),1480// `indirect_parameters_gpu_metadata`1481(14828,1483storage_buffer::<IndirectParametersGpuMetadata>(/* has_dynamic_offset= */ false),1484),1485// `mesh_culling_data`1486(14879,1488storage_buffer_read_only::<MeshCullingData>(/* has_dynamic_offset= */ false),1489),1490))1491}14921493fn gpu_occlusion_culling_bind_group_layout_entries() -> DynamicBindGroupLayoutEntries {1494gpu_culling_bind_group_layout_entries().extend_with_indices((1495(14962,1497uniform_buffer::<PreviousViewData>(/*has_dynamic_offset=*/ false),1498),1499(150010,1501texture_2d(TextureSampleType::Float { filterable: true }),1502),1503(150412,1505storage_buffer::<LatePreprocessWorkItemIndirectParameters>(1506/*has_dynamic_offset=*/ false,1507),1508),1509))1510}15111512/// A system that specializes the `mesh_preprocess.wgsl` pipelines if necessary.1513pub fn prepare_preprocess_pipelines(1514pipeline_cache: Res<PipelineCache>,1515render_device: Res<RenderDevice>,1516mut specialized_preprocess_pipelines: ResMut<SpecializedComputePipelines<PreprocessPipeline>>,1517mut specialized_reset_indirect_batch_sets_pipelines: ResMut<1518SpecializedComputePipelines<ResetIndirectBatchSetsPipeline>,1519>,1520mut specialized_build_indirect_parameters_pipelines: ResMut<1521SpecializedComputePipelines<BuildIndirectParametersPipeline>,1522>,1523preprocess_pipelines: ResMut<PreprocessPipelines>,1524gpu_preprocessing_support: Res<GpuPreprocessingSupport>,1525) {1526let preprocess_pipelines = preprocess_pipelines.into_inner();15271528preprocess_pipelines.direct_preprocess.prepare(1529&pipeline_cache,1530&mut specialized_preprocess_pipelines,1531PreprocessPipelineKey::empty(),1532);1533preprocess_pipelines.gpu_frustum_culling_preprocess.prepare(1534&pipeline_cache,1535&mut specialized_preprocess_pipelines,1536PreprocessPipelineKey::FRUSTUM_CULLING,1537);15381539if gpu_preprocessing_support.is_culling_supported() {1540preprocess_pipelines1541.early_gpu_occlusion_culling_preprocess1542.prepare(1543&pipeline_cache,1544&mut specialized_preprocess_pipelines,1545PreprocessPipelineKey::FRUSTUM_CULLING1546| PreprocessPipelineKey::OCCLUSION_CULLING1547| PreprocessPipelineKey::EARLY_PHASE,1548);1549preprocess_pipelines1550.late_gpu_occlusion_culling_preprocess1551.prepare(1552&pipeline_cache,1553&mut specialized_preprocess_pipelines,1554PreprocessPipelineKey::FRUSTUM_CULLING | PreprocessPipelineKey::OCCLUSION_CULLING,1555);1556}15571558let mut build_indirect_parameters_pipeline_key = BuildIndirectParametersPipelineKey::empty();15591560// If the GPU and driver support `multi_draw_indirect_count`, tell the1561// shader that.1562if render_device1563.wgpu_device()1564.features()1565.contains(WgpuFeatures::MULTI_DRAW_INDIRECT_COUNT)1566{1567build_indirect_parameters_pipeline_key1568.insert(BuildIndirectParametersPipelineKey::MULTI_DRAW_INDIRECT_COUNT_SUPPORTED);1569}15701571preprocess_pipelines1572.gpu_frustum_culling_build_indexed_indirect_params1573.prepare(1574&pipeline_cache,1575&mut specialized_build_indirect_parameters_pipelines,1576build_indirect_parameters_pipeline_key | BuildIndirectParametersPipelineKey::INDEXED,1577);1578preprocess_pipelines1579.gpu_frustum_culling_build_non_indexed_indirect_params1580.prepare(1581&pipeline_cache,1582&mut specialized_build_indirect_parameters_pipelines,1583build_indirect_parameters_pipeline_key,1584);15851586if !gpu_preprocessing_support.is_culling_supported() {1587return;1588}15891590for (preprocess_phase_pipelines, build_indirect_parameters_phase_pipeline_key) in [1591(1592&mut preprocess_pipelines.early_phase,1593BuildIndirectParametersPipelineKey::EARLY_PHASE,1594),1595(1596&mut preprocess_pipelines.late_phase,1597BuildIndirectParametersPipelineKey::LATE_PHASE,1598),1599(1600&mut preprocess_pipelines.main_phase,1601BuildIndirectParametersPipelineKey::MAIN_PHASE,1602),1603] {1604preprocess_phase_pipelines1605.reset_indirect_batch_sets1606.prepare(1607&pipeline_cache,1608&mut specialized_reset_indirect_batch_sets_pipelines,1609);1610preprocess_phase_pipelines1611.gpu_occlusion_culling_build_indexed_indirect_params1612.prepare(1613&pipeline_cache,1614&mut specialized_build_indirect_parameters_pipelines,1615build_indirect_parameters_pipeline_key1616| build_indirect_parameters_phase_pipeline_key1617| BuildIndirectParametersPipelineKey::INDEXED1618| BuildIndirectParametersPipelineKey::OCCLUSION_CULLING,1619);1620preprocess_phase_pipelines1621.gpu_occlusion_culling_build_non_indexed_indirect_params1622.prepare(1623&pipeline_cache,1624&mut specialized_build_indirect_parameters_pipelines,1625build_indirect_parameters_pipeline_key1626| build_indirect_parameters_phase_pipeline_key1627| BuildIndirectParametersPipelineKey::OCCLUSION_CULLING,1628);1629}1630}16311632impl PreprocessPipeline {1633fn prepare(1634&mut self,1635pipeline_cache: &PipelineCache,1636pipelines: &mut SpecializedComputePipelines<PreprocessPipeline>,1637key: PreprocessPipelineKey,1638) {1639if self.pipeline_id.is_some() {1640return;1641}16421643let preprocess_pipeline_id = pipelines.specialize(pipeline_cache, self, key);1644self.pipeline_id = Some(preprocess_pipeline_id);1645}1646}16471648impl SpecializedComputePipeline for ResetIndirectBatchSetsPipeline {1649type Key = ();16501651fn specialize(&self, _: Self::Key) -> ComputePipelineDescriptor {1652ComputePipelineDescriptor {1653label: Some("reset indirect batch sets".into()),1654layout: vec![self.bind_group_layout.clone()],1655shader: self.shader.clone(),1656..default()1657}1658}1659}16601661impl SpecializedComputePipeline for BuildIndirectParametersPipeline {1662type Key = BuildIndirectParametersPipelineKey;16631664fn specialize(&self, key: Self::Key) -> ComputePipelineDescriptor {1665let mut shader_defs = vec![];1666if key.contains(BuildIndirectParametersPipelineKey::INDEXED) {1667shader_defs.push("INDEXED".into());1668}1669if key.contains(BuildIndirectParametersPipelineKey::MULTI_DRAW_INDIRECT_COUNT_SUPPORTED) {1670shader_defs.push("MULTI_DRAW_INDIRECT_COUNT_SUPPORTED".into());1671}1672if key.contains(BuildIndirectParametersPipelineKey::OCCLUSION_CULLING) {1673shader_defs.push("OCCLUSION_CULLING".into());1674}1675if key.contains(BuildIndirectParametersPipelineKey::EARLY_PHASE) {1676shader_defs.push("EARLY_PHASE".into());1677}1678if key.contains(BuildIndirectParametersPipelineKey::LATE_PHASE) {1679shader_defs.push("LATE_PHASE".into());1680}1681if key.contains(BuildIndirectParametersPipelineKey::MAIN_PHASE) {1682shader_defs.push("MAIN_PHASE".into());1683}16841685let label = format!(1686"{} build {}indexed indirect parameters",1687if !key.contains(BuildIndirectParametersPipelineKey::OCCLUSION_CULLING) {1688"frustum culling"1689} else if key.contains(BuildIndirectParametersPipelineKey::EARLY_PHASE) {1690"early occlusion culling"1691} else if key.contains(BuildIndirectParametersPipelineKey::LATE_PHASE) {1692"late occlusion culling"1693} else {1694"main occlusion culling"1695},1696if key.contains(BuildIndirectParametersPipelineKey::INDEXED) {1697""1698} else {1699"non-"1700}1701);17021703ComputePipelineDescriptor {1704label: Some(label.into()),1705layout: vec![self.bind_group_layout.clone()],1706shader: self.shader.clone(),1707shader_defs,1708..default()1709}1710}1711}17121713impl ResetIndirectBatchSetsPipeline {1714fn prepare(1715&mut self,1716pipeline_cache: &PipelineCache,1717pipelines: &mut SpecializedComputePipelines<ResetIndirectBatchSetsPipeline>,1718) {1719if self.pipeline_id.is_some() {1720return;1721}17221723let reset_indirect_batch_sets_pipeline_id = pipelines.specialize(pipeline_cache, self, ());1724self.pipeline_id = Some(reset_indirect_batch_sets_pipeline_id);1725}1726}17271728impl BuildIndirectParametersPipeline {1729fn prepare(1730&mut self,1731pipeline_cache: &PipelineCache,1732pipelines: &mut SpecializedComputePipelines<BuildIndirectParametersPipeline>,1733key: BuildIndirectParametersPipelineKey,1734) {1735if self.pipeline_id.is_some() {1736return;1737}17381739let build_indirect_parameters_pipeline_id = pipelines.specialize(pipeline_cache, self, key);1740self.pipeline_id = Some(build_indirect_parameters_pipeline_id);1741}1742}17431744/// A system that attaches the mesh uniform buffers to the bind groups for the1745/// variants of the mesh preprocessing compute shader.1746#[expect(1747clippy::too_many_arguments,1748reason = "it's a system that needs a lot of arguments"1749)]1750pub fn prepare_preprocess_bind_groups(1751mut commands: Commands,1752views: Query<(Entity, &ExtractedView)>,1753view_depth_pyramids: Query<(&ViewDepthPyramid, &PreviousViewUniformOffset)>,1754render_device: Res<RenderDevice>,1755batched_instance_buffers: Res<BatchedInstanceBuffers<MeshUniform, MeshInputUniform>>,1756indirect_parameters_buffers: Res<IndirectParametersBuffers>,1757mesh_culling_data_buffer: Res<MeshCullingDataBuffer>,1758view_uniforms: Res<ViewUniforms>,1759previous_view_uniforms: Res<PreviousViewUniforms>,1760pipelines: Res<PreprocessPipelines>,1761) {1762// Grab the `BatchedInstanceBuffers`.1763let BatchedInstanceBuffers {1764current_input_buffer: current_input_buffer_vec,1765previous_input_buffer: previous_input_buffer_vec,1766phase_instance_buffers,1767} = batched_instance_buffers.into_inner();17681769let (Some(current_input_buffer), Some(previous_input_buffer)) = (1770current_input_buffer_vec.buffer().buffer(),1771previous_input_buffer_vec.buffer().buffer(),1772) else {1773return;1774};17751776// Record whether we have any meshes that are to be drawn indirectly. If we1777// don't, then we can skip building indirect parameters.1778let mut any_indirect = false;17791780// Loop over each view.1781for (view_entity, view) in &views {1782let mut bind_groups = TypeIdMap::default();17831784// Loop over each phase.1785for (phase_type_id, phase_instance_buffers) in phase_instance_buffers {1786let UntypedPhaseBatchedInstanceBuffers {1787data_buffer: ref data_buffer_vec,1788ref work_item_buffers,1789ref late_indexed_indirect_parameters_buffer,1790ref late_non_indexed_indirect_parameters_buffer,1791} = *phase_instance_buffers;17921793let Some(data_buffer) = data_buffer_vec.buffer() else {1794continue;1795};17961797// Grab the indirect parameters buffers for this phase.1798let Some(phase_indirect_parameters_buffers) =1799indirect_parameters_buffers.get(phase_type_id)1800else {1801continue;1802};18031804let Some(work_item_buffers) = work_item_buffers.get(&view.retained_view_entity) else {1805continue;1806};18071808// Create the `PreprocessBindGroupBuilder`.1809let preprocess_bind_group_builder = PreprocessBindGroupBuilder {1810view: view_entity,1811late_indexed_indirect_parameters_buffer,1812late_non_indexed_indirect_parameters_buffer,1813render_device: &render_device,1814phase_indirect_parameters_buffers,1815mesh_culling_data_buffer: &mesh_culling_data_buffer,1816view_uniforms: &view_uniforms,1817previous_view_uniforms: &previous_view_uniforms,1818pipelines: &pipelines,1819current_input_buffer,1820previous_input_buffer,1821data_buffer,1822};18231824// Depending on the type of work items we have, construct the1825// appropriate bind groups.1826let (was_indirect, bind_group) = match *work_item_buffers {1827PreprocessWorkItemBuffers::Direct(ref work_item_buffer) => (1828false,1829preprocess_bind_group_builder1830.create_direct_preprocess_bind_groups(work_item_buffer),1831),18321833PreprocessWorkItemBuffers::Indirect {1834indexed: ref indexed_work_item_buffer,1835non_indexed: ref non_indexed_work_item_buffer,1836gpu_occlusion_culling: Some(ref gpu_occlusion_culling_work_item_buffers),1837} => (1838true,1839preprocess_bind_group_builder1840.create_indirect_occlusion_culling_preprocess_bind_groups(1841&view_depth_pyramids,1842indexed_work_item_buffer,1843non_indexed_work_item_buffer,1844gpu_occlusion_culling_work_item_buffers,1845),1846),18471848PreprocessWorkItemBuffers::Indirect {1849indexed: ref indexed_work_item_buffer,1850non_indexed: ref non_indexed_work_item_buffer,1851gpu_occlusion_culling: None,1852} => (1853true,1854preprocess_bind_group_builder1855.create_indirect_frustum_culling_preprocess_bind_groups(1856indexed_work_item_buffer,1857non_indexed_work_item_buffer,1858),1859),1860};18611862// Write that bind group in.1863if let Some(bind_group) = bind_group {1864any_indirect = any_indirect || was_indirect;1865bind_groups.insert(*phase_type_id, bind_group);1866}1867}18681869// Save the bind groups.1870commands1871.entity(view_entity)1872.insert(PreprocessBindGroups(bind_groups));1873}18741875// Now, if there were any indirect draw commands, create the bind groups for1876// the indirect parameters building shader.1877if any_indirect {1878create_build_indirect_parameters_bind_groups(1879&mut commands,1880&render_device,1881&pipelines,1882current_input_buffer,1883&indirect_parameters_buffers,1884);1885}1886}18871888/// A temporary structure that stores all the information needed to construct1889/// bind groups for the mesh preprocessing shader.1890struct PreprocessBindGroupBuilder<'a> {1891/// The render-world entity corresponding to the current view.1892view: Entity,1893/// The indirect compute dispatch parameters buffer for indexed meshes in1894/// the late prepass.1895late_indexed_indirect_parameters_buffer:1896&'a RawBufferVec<LatePreprocessWorkItemIndirectParameters>,1897/// The indirect compute dispatch parameters buffer for non-indexed meshes1898/// in the late prepass.1899late_non_indexed_indirect_parameters_buffer:1900&'a RawBufferVec<LatePreprocessWorkItemIndirectParameters>,1901/// The device.1902render_device: &'a RenderDevice,1903/// The buffers that store indirect draw parameters.1904phase_indirect_parameters_buffers: &'a UntypedPhaseIndirectParametersBuffers,1905/// The GPU buffer that stores the information needed to cull each mesh.1906mesh_culling_data_buffer: &'a MeshCullingDataBuffer,1907/// The GPU buffer that stores information about the view.1908view_uniforms: &'a ViewUniforms,1909/// The GPU buffer that stores information about the view from last frame.1910previous_view_uniforms: &'a PreviousViewUniforms,1911/// The pipelines for the mesh preprocessing shader.1912pipelines: &'a PreprocessPipelines,1913/// The GPU buffer containing the list of [`MeshInputUniform`]s for the1914/// current frame.1915current_input_buffer: &'a Buffer,1916/// The GPU buffer containing the list of [`MeshInputUniform`]s for the1917/// previous frame.1918previous_input_buffer: &'a Buffer,1919/// The GPU buffer containing the list of [`MeshUniform`]s for the current1920/// frame.1921///1922/// This is the buffer containing the mesh's final transforms that the1923/// shaders will write to.1924data_buffer: &'a Buffer,1925}19261927impl<'a> PreprocessBindGroupBuilder<'a> {1928/// Creates the bind groups for mesh preprocessing when GPU frustum culling1929/// and GPU occlusion culling are both disabled.1930fn create_direct_preprocess_bind_groups(1931&self,1932work_item_buffer: &RawBufferVec<PreprocessWorkItem>,1933) -> Option<PhasePreprocessBindGroups> {1934// Don't use `as_entire_binding()` here; the shader reads the array1935// length and the underlying buffer may be longer than the actual size1936// of the vector.1937let work_item_buffer_size = NonZero::<u64>::try_from(1938work_item_buffer.len() as u64 * u64::from(PreprocessWorkItem::min_size()),1939)1940.ok();19411942Some(PhasePreprocessBindGroups::Direct(1943self.render_device.create_bind_group(1944"preprocess_direct_bind_group",1945&self.pipelines.direct_preprocess.bind_group_layout,1946&BindGroupEntries::with_indices((1947(0, self.view_uniforms.uniforms.binding()?),1948(3, self.current_input_buffer.as_entire_binding()),1949(4, self.previous_input_buffer.as_entire_binding()),1950(19515,1952BindingResource::Buffer(BufferBinding {1953buffer: work_item_buffer.buffer()?,1954offset: 0,1955size: work_item_buffer_size,1956}),1957),1958(6, self.data_buffer.as_entire_binding()),1959)),1960),1961))1962}19631964/// Creates the bind groups for mesh preprocessing when GPU occlusion1965/// culling is enabled.1966fn create_indirect_occlusion_culling_preprocess_bind_groups(1967&self,1968view_depth_pyramids: &Query<(&ViewDepthPyramid, &PreviousViewUniformOffset)>,1969indexed_work_item_buffer: &RawBufferVec<PreprocessWorkItem>,1970non_indexed_work_item_buffer: &RawBufferVec<PreprocessWorkItem>,1971gpu_occlusion_culling_work_item_buffers: &GpuOcclusionCullingWorkItemBuffers,1972) -> Option<PhasePreprocessBindGroups> {1973let GpuOcclusionCullingWorkItemBuffers {1974late_indexed: ref late_indexed_work_item_buffer,1975late_non_indexed: ref late_non_indexed_work_item_buffer,1976..1977} = *gpu_occlusion_culling_work_item_buffers;19781979let (view_depth_pyramid, previous_view_uniform_offset) =1980view_depth_pyramids.get(self.view).ok()?;19811982Some(PhasePreprocessBindGroups::IndirectOcclusionCulling {1983early_indexed: self.create_indirect_occlusion_culling_early_indexed_bind_group(1984view_depth_pyramid,1985previous_view_uniform_offset,1986indexed_work_item_buffer,1987late_indexed_work_item_buffer,1988),19891990early_non_indexed: self.create_indirect_occlusion_culling_early_non_indexed_bind_group(1991view_depth_pyramid,1992previous_view_uniform_offset,1993non_indexed_work_item_buffer,1994late_non_indexed_work_item_buffer,1995),19961997late_indexed: self.create_indirect_occlusion_culling_late_indexed_bind_group(1998view_depth_pyramid,1999previous_view_uniform_offset,2000late_indexed_work_item_buffer,2001),20022003late_non_indexed: self.create_indirect_occlusion_culling_late_non_indexed_bind_group(2004view_depth_pyramid,2005previous_view_uniform_offset,2006late_non_indexed_work_item_buffer,2007),2008})2009}20102011/// Creates the bind group for the first phase of mesh preprocessing of2012/// indexed meshes when GPU occlusion culling is enabled.2013fn create_indirect_occlusion_culling_early_indexed_bind_group(2014&self,2015view_depth_pyramid: &ViewDepthPyramid,2016previous_view_uniform_offset: &PreviousViewUniformOffset,2017indexed_work_item_buffer: &RawBufferVec<PreprocessWorkItem>,2018late_indexed_work_item_buffer: &UninitBufferVec<PreprocessWorkItem>,2019) -> Option<BindGroup> {2020let mesh_culling_data_buffer = self.mesh_culling_data_buffer.buffer()?;2021let view_uniforms_binding = self.view_uniforms.uniforms.binding()?;2022let previous_view_buffer = self.previous_view_uniforms.uniforms.buffer()?;20232024match (2025self.phase_indirect_parameters_buffers2026.indexed2027.cpu_metadata_buffer(),2028self.phase_indirect_parameters_buffers2029.indexed2030.gpu_metadata_buffer(),2031indexed_work_item_buffer.buffer(),2032late_indexed_work_item_buffer.buffer(),2033self.late_indexed_indirect_parameters_buffer.buffer(),2034) {2035(2036Some(indexed_cpu_metadata_buffer),2037Some(indexed_gpu_metadata_buffer),2038Some(indexed_work_item_gpu_buffer),2039Some(late_indexed_work_item_gpu_buffer),2040Some(late_indexed_indirect_parameters_buffer),2041) => {2042// Don't use `as_entire_binding()` here; the shader reads the array2043// length and the underlying buffer may be longer than the actual size2044// of the vector.2045let indexed_work_item_buffer_size = NonZero::<u64>::try_from(2046indexed_work_item_buffer.len() as u642047* u64::from(PreprocessWorkItem::min_size()),2048)2049.ok();20502051Some(2052self.render_device.create_bind_group(2053"preprocess_early_indexed_gpu_occlusion_culling_bind_group",2054&self2055.pipelines2056.early_gpu_occlusion_culling_preprocess2057.bind_group_layout,2058&BindGroupEntries::with_indices((2059(3, self.current_input_buffer.as_entire_binding()),2060(4, self.previous_input_buffer.as_entire_binding()),2061(20625,2063BindingResource::Buffer(BufferBinding {2064buffer: indexed_work_item_gpu_buffer,2065offset: 0,2066size: indexed_work_item_buffer_size,2067}),2068),2069(6, self.data_buffer.as_entire_binding()),2070(7, indexed_cpu_metadata_buffer.as_entire_binding()),2071(8, indexed_gpu_metadata_buffer.as_entire_binding()),2072(9, mesh_culling_data_buffer.as_entire_binding()),2073(0, view_uniforms_binding.clone()),2074(10, &view_depth_pyramid.all_mips),2075(20762,2077BufferBinding {2078buffer: previous_view_buffer,2079offset: previous_view_uniform_offset.offset as u64,2080size: NonZeroU64::new(size_of::<PreviousViewData>() as u64),2081},2082),2083(208411,2085BufferBinding {2086buffer: late_indexed_work_item_gpu_buffer,2087offset: 0,2088size: indexed_work_item_buffer_size,2089},2090),2091(209212,2093BufferBinding {2094buffer: late_indexed_indirect_parameters_buffer,2095offset: 0,2096size: NonZeroU64::new(2097late_indexed_indirect_parameters_buffer.size(),2098),2099},2100),2101)),2102),2103)2104}2105_ => None,2106}2107}21082109/// Creates the bind group for the first phase of mesh preprocessing of2110/// non-indexed meshes when GPU occlusion culling is enabled.2111fn create_indirect_occlusion_culling_early_non_indexed_bind_group(2112&self,2113view_depth_pyramid: &ViewDepthPyramid,2114previous_view_uniform_offset: &PreviousViewUniformOffset,2115non_indexed_work_item_buffer: &RawBufferVec<PreprocessWorkItem>,2116late_non_indexed_work_item_buffer: &UninitBufferVec<PreprocessWorkItem>,2117) -> Option<BindGroup> {2118let mesh_culling_data_buffer = self.mesh_culling_data_buffer.buffer()?;2119let view_uniforms_binding = self.view_uniforms.uniforms.binding()?;2120let previous_view_buffer = self.previous_view_uniforms.uniforms.buffer()?;21212122match (2123self.phase_indirect_parameters_buffers2124.non_indexed2125.cpu_metadata_buffer(),2126self.phase_indirect_parameters_buffers2127.non_indexed2128.gpu_metadata_buffer(),2129non_indexed_work_item_buffer.buffer(),2130late_non_indexed_work_item_buffer.buffer(),2131self.late_non_indexed_indirect_parameters_buffer.buffer(),2132) {2133(2134Some(non_indexed_cpu_metadata_buffer),2135Some(non_indexed_gpu_metadata_buffer),2136Some(non_indexed_work_item_gpu_buffer),2137Some(late_non_indexed_work_item_buffer),2138Some(late_non_indexed_indirect_parameters_buffer),2139) => {2140// Don't use `as_entire_binding()` here; the shader reads the array2141// length and the underlying buffer may be longer than the actual size2142// of the vector.2143let non_indexed_work_item_buffer_size = NonZero::<u64>::try_from(2144non_indexed_work_item_buffer.len() as u642145* u64::from(PreprocessWorkItem::min_size()),2146)2147.ok();21482149Some(2150self.render_device.create_bind_group(2151"preprocess_early_non_indexed_gpu_occlusion_culling_bind_group",2152&self2153.pipelines2154.early_gpu_occlusion_culling_preprocess2155.bind_group_layout,2156&BindGroupEntries::with_indices((2157(3, self.current_input_buffer.as_entire_binding()),2158(4, self.previous_input_buffer.as_entire_binding()),2159(21605,2161BindingResource::Buffer(BufferBinding {2162buffer: non_indexed_work_item_gpu_buffer,2163offset: 0,2164size: non_indexed_work_item_buffer_size,2165}),2166),2167(6, self.data_buffer.as_entire_binding()),2168(7, non_indexed_cpu_metadata_buffer.as_entire_binding()),2169(8, non_indexed_gpu_metadata_buffer.as_entire_binding()),2170(9, mesh_culling_data_buffer.as_entire_binding()),2171(0, view_uniforms_binding.clone()),2172(10, &view_depth_pyramid.all_mips),2173(21742,2175BufferBinding {2176buffer: previous_view_buffer,2177offset: previous_view_uniform_offset.offset as u64,2178size: NonZeroU64::new(size_of::<PreviousViewData>() as u64),2179},2180),2181(218211,2183BufferBinding {2184buffer: late_non_indexed_work_item_buffer,2185offset: 0,2186size: non_indexed_work_item_buffer_size,2187},2188),2189(219012,2191BufferBinding {2192buffer: late_non_indexed_indirect_parameters_buffer,2193offset: 0,2194size: NonZeroU64::new(2195late_non_indexed_indirect_parameters_buffer.size(),2196),2197},2198),2199)),2200),2201)2202}2203_ => None,2204}2205}22062207/// Creates the bind group for the second phase of mesh preprocessing of2208/// indexed meshes when GPU occlusion culling is enabled.2209fn create_indirect_occlusion_culling_late_indexed_bind_group(2210&self,2211view_depth_pyramid: &ViewDepthPyramid,2212previous_view_uniform_offset: &PreviousViewUniformOffset,2213late_indexed_work_item_buffer: &UninitBufferVec<PreprocessWorkItem>,2214) -> Option<BindGroup> {2215let mesh_culling_data_buffer = self.mesh_culling_data_buffer.buffer()?;2216let view_uniforms_binding = self.view_uniforms.uniforms.binding()?;2217let previous_view_buffer = self.previous_view_uniforms.uniforms.buffer()?;22182219match (2220self.phase_indirect_parameters_buffers2221.indexed2222.cpu_metadata_buffer(),2223self.phase_indirect_parameters_buffers2224.indexed2225.gpu_metadata_buffer(),2226late_indexed_work_item_buffer.buffer(),2227self.late_indexed_indirect_parameters_buffer.buffer(),2228) {2229(2230Some(indexed_cpu_metadata_buffer),2231Some(indexed_gpu_metadata_buffer),2232Some(late_indexed_work_item_gpu_buffer),2233Some(late_indexed_indirect_parameters_buffer),2234) => {2235// Don't use `as_entire_binding()` here; the shader reads the array2236// length and the underlying buffer may be longer than the actual size2237// of the vector.2238let late_indexed_work_item_buffer_size = NonZero::<u64>::try_from(2239late_indexed_work_item_buffer.len() as u642240* u64::from(PreprocessWorkItem::min_size()),2241)2242.ok();22432244Some(2245self.render_device.create_bind_group(2246"preprocess_late_indexed_gpu_occlusion_culling_bind_group",2247&self2248.pipelines2249.late_gpu_occlusion_culling_preprocess2250.bind_group_layout,2251&BindGroupEntries::with_indices((2252(3, self.current_input_buffer.as_entire_binding()),2253(4, self.previous_input_buffer.as_entire_binding()),2254(22555,2256BindingResource::Buffer(BufferBinding {2257buffer: late_indexed_work_item_gpu_buffer,2258offset: 0,2259size: late_indexed_work_item_buffer_size,2260}),2261),2262(6, self.data_buffer.as_entire_binding()),2263(7, indexed_cpu_metadata_buffer.as_entire_binding()),2264(8, indexed_gpu_metadata_buffer.as_entire_binding()),2265(9, mesh_culling_data_buffer.as_entire_binding()),2266(0, view_uniforms_binding.clone()),2267(10, &view_depth_pyramid.all_mips),2268(22692,2270BufferBinding {2271buffer: previous_view_buffer,2272offset: previous_view_uniform_offset.offset as u64,2273size: NonZeroU64::new(size_of::<PreviousViewData>() as u64),2274},2275),2276(227712,2278BufferBinding {2279buffer: late_indexed_indirect_parameters_buffer,2280offset: 0,2281size: NonZeroU64::new(2282late_indexed_indirect_parameters_buffer.size(),2283),2284},2285),2286)),2287),2288)2289}2290_ => None,2291}2292}22932294/// Creates the bind group for the second phase of mesh preprocessing of2295/// non-indexed meshes when GPU occlusion culling is enabled.2296fn create_indirect_occlusion_culling_late_non_indexed_bind_group(2297&self,2298view_depth_pyramid: &ViewDepthPyramid,2299previous_view_uniform_offset: &PreviousViewUniformOffset,2300late_non_indexed_work_item_buffer: &UninitBufferVec<PreprocessWorkItem>,2301) -> Option<BindGroup> {2302let mesh_culling_data_buffer = self.mesh_culling_data_buffer.buffer()?;2303let view_uniforms_binding = self.view_uniforms.uniforms.binding()?;2304let previous_view_buffer = self.previous_view_uniforms.uniforms.buffer()?;23052306match (2307self.phase_indirect_parameters_buffers2308.non_indexed2309.cpu_metadata_buffer(),2310self.phase_indirect_parameters_buffers2311.non_indexed2312.gpu_metadata_buffer(),2313late_non_indexed_work_item_buffer.buffer(),2314self.late_non_indexed_indirect_parameters_buffer.buffer(),2315) {2316(2317Some(non_indexed_cpu_metadata_buffer),2318Some(non_indexed_gpu_metadata_buffer),2319Some(non_indexed_work_item_gpu_buffer),2320Some(late_non_indexed_indirect_parameters_buffer),2321) => {2322// Don't use `as_entire_binding()` here; the shader reads the array2323// length and the underlying buffer may be longer than the actual size2324// of the vector.2325let non_indexed_work_item_buffer_size = NonZero::<u64>::try_from(2326late_non_indexed_work_item_buffer.len() as u642327* u64::from(PreprocessWorkItem::min_size()),2328)2329.ok();23302331Some(2332self.render_device.create_bind_group(2333"preprocess_late_non_indexed_gpu_occlusion_culling_bind_group",2334&self2335.pipelines2336.late_gpu_occlusion_culling_preprocess2337.bind_group_layout,2338&BindGroupEntries::with_indices((2339(3, self.current_input_buffer.as_entire_binding()),2340(4, self.previous_input_buffer.as_entire_binding()),2341(23425,2343BindingResource::Buffer(BufferBinding {2344buffer: non_indexed_work_item_gpu_buffer,2345offset: 0,2346size: non_indexed_work_item_buffer_size,2347}),2348),2349(6, self.data_buffer.as_entire_binding()),2350(7, non_indexed_cpu_metadata_buffer.as_entire_binding()),2351(8, non_indexed_gpu_metadata_buffer.as_entire_binding()),2352(9, mesh_culling_data_buffer.as_entire_binding()),2353(0, view_uniforms_binding.clone()),2354(10, &view_depth_pyramid.all_mips),2355(23562,2357BufferBinding {2358buffer: previous_view_buffer,2359offset: previous_view_uniform_offset.offset as u64,2360size: NonZeroU64::new(size_of::<PreviousViewData>() as u64),2361},2362),2363(236412,2365BufferBinding {2366buffer: late_non_indexed_indirect_parameters_buffer,2367offset: 0,2368size: NonZeroU64::new(2369late_non_indexed_indirect_parameters_buffer.size(),2370),2371},2372),2373)),2374),2375)2376}2377_ => None,2378}2379}23802381/// Creates the bind groups for mesh preprocessing when GPU frustum culling2382/// is enabled, but GPU occlusion culling is disabled.2383fn create_indirect_frustum_culling_preprocess_bind_groups(2384&self,2385indexed_work_item_buffer: &RawBufferVec<PreprocessWorkItem>,2386non_indexed_work_item_buffer: &RawBufferVec<PreprocessWorkItem>,2387) -> Option<PhasePreprocessBindGroups> {2388Some(PhasePreprocessBindGroups::IndirectFrustumCulling {2389indexed: self2390.create_indirect_frustum_culling_indexed_bind_group(indexed_work_item_buffer),2391non_indexed: self.create_indirect_frustum_culling_non_indexed_bind_group(2392non_indexed_work_item_buffer,2393),2394})2395}23962397/// Creates the bind group for mesh preprocessing of indexed meshes when GPU2398/// frustum culling is enabled, but GPU occlusion culling is disabled.2399fn create_indirect_frustum_culling_indexed_bind_group(2400&self,2401indexed_work_item_buffer: &RawBufferVec<PreprocessWorkItem>,2402) -> Option<BindGroup> {2403let mesh_culling_data_buffer = self.mesh_culling_data_buffer.buffer()?;2404let view_uniforms_binding = self.view_uniforms.uniforms.binding()?;24052406match (2407self.phase_indirect_parameters_buffers2408.indexed2409.cpu_metadata_buffer(),2410self.phase_indirect_parameters_buffers2411.indexed2412.gpu_metadata_buffer(),2413indexed_work_item_buffer.buffer(),2414) {2415(2416Some(indexed_cpu_metadata_buffer),2417Some(indexed_gpu_metadata_buffer),2418Some(indexed_work_item_gpu_buffer),2419) => {2420// Don't use `as_entire_binding()` here; the shader reads the array2421// length and the underlying buffer may be longer than the actual size2422// of the vector.2423let indexed_work_item_buffer_size = NonZero::<u64>::try_from(2424indexed_work_item_buffer.len() as u642425* u64::from(PreprocessWorkItem::min_size()),2426)2427.ok();24282429Some(2430self.render_device.create_bind_group(2431"preprocess_gpu_indexed_frustum_culling_bind_group",2432&self2433.pipelines2434.gpu_frustum_culling_preprocess2435.bind_group_layout,2436&BindGroupEntries::with_indices((2437(3, self.current_input_buffer.as_entire_binding()),2438(4, self.previous_input_buffer.as_entire_binding()),2439(24405,2441BindingResource::Buffer(BufferBinding {2442buffer: indexed_work_item_gpu_buffer,2443offset: 0,2444size: indexed_work_item_buffer_size,2445}),2446),2447(6, self.data_buffer.as_entire_binding()),2448(7, indexed_cpu_metadata_buffer.as_entire_binding()),2449(8, indexed_gpu_metadata_buffer.as_entire_binding()),2450(9, mesh_culling_data_buffer.as_entire_binding()),2451(0, view_uniforms_binding.clone()),2452)),2453),2454)2455}2456_ => None,2457}2458}24592460/// Creates the bind group for mesh preprocessing of non-indexed meshes when2461/// GPU frustum culling is enabled, but GPU occlusion culling is disabled.2462fn create_indirect_frustum_culling_non_indexed_bind_group(2463&self,2464non_indexed_work_item_buffer: &RawBufferVec<PreprocessWorkItem>,2465) -> Option<BindGroup> {2466let mesh_culling_data_buffer = self.mesh_culling_data_buffer.buffer()?;2467let view_uniforms_binding = self.view_uniforms.uniforms.binding()?;24682469match (2470self.phase_indirect_parameters_buffers2471.non_indexed2472.cpu_metadata_buffer(),2473self.phase_indirect_parameters_buffers2474.non_indexed2475.gpu_metadata_buffer(),2476non_indexed_work_item_buffer.buffer(),2477) {2478(2479Some(non_indexed_cpu_metadata_buffer),2480Some(non_indexed_gpu_metadata_buffer),2481Some(non_indexed_work_item_gpu_buffer),2482) => {2483// Don't use `as_entire_binding()` here; the shader reads the array2484// length and the underlying buffer may be longer than the actual size2485// of the vector.2486let non_indexed_work_item_buffer_size = NonZero::<u64>::try_from(2487non_indexed_work_item_buffer.len() as u642488* u64::from(PreprocessWorkItem::min_size()),2489)2490.ok();24912492Some(2493self.render_device.create_bind_group(2494"preprocess_gpu_non_indexed_frustum_culling_bind_group",2495&self2496.pipelines2497.gpu_frustum_culling_preprocess2498.bind_group_layout,2499&BindGroupEntries::with_indices((2500(3, self.current_input_buffer.as_entire_binding()),2501(4, self.previous_input_buffer.as_entire_binding()),2502(25035,2504BindingResource::Buffer(BufferBinding {2505buffer: non_indexed_work_item_gpu_buffer,2506offset: 0,2507size: non_indexed_work_item_buffer_size,2508}),2509),2510(6, self.data_buffer.as_entire_binding()),2511(7, non_indexed_cpu_metadata_buffer.as_entire_binding()),2512(8, non_indexed_gpu_metadata_buffer.as_entire_binding()),2513(9, mesh_culling_data_buffer.as_entire_binding()),2514(0, view_uniforms_binding.clone()),2515)),2516),2517)2518}2519_ => None,2520}2521}2522}25232524/// A system that creates bind groups from the indirect parameters metadata and2525/// data buffers for the indirect batch set reset shader and the indirect2526/// parameter building shader.2527fn create_build_indirect_parameters_bind_groups(2528commands: &mut Commands,2529render_device: &RenderDevice,2530pipelines: &PreprocessPipelines,2531current_input_buffer: &Buffer,2532indirect_parameters_buffers: &IndirectParametersBuffers,2533) {2534let mut build_indirect_parameters_bind_groups = BuildIndirectParametersBindGroups::new();25352536for (phase_type_id, phase_indirect_parameters_buffer) in indirect_parameters_buffers.iter() {2537build_indirect_parameters_bind_groups.insert(2538*phase_type_id,2539PhaseBuildIndirectParametersBindGroups {2540reset_indexed_indirect_batch_sets: match (phase_indirect_parameters_buffer2541.indexed2542.batch_sets_buffer(),)2543{2544(Some(indexed_batch_sets_buffer),) => Some(2545render_device.create_bind_group(2546"reset_indexed_indirect_batch_sets_bind_group",2547// The early bind group is good for the main phase and late2548// phase too. They bind the same buffers.2549&pipelines2550.early_phase2551.reset_indirect_batch_sets2552.bind_group_layout,2553&BindGroupEntries::sequential((2554indexed_batch_sets_buffer.as_entire_binding(),2555)),2556),2557),2558_ => None,2559},25602561reset_non_indexed_indirect_batch_sets: match (phase_indirect_parameters_buffer2562.non_indexed2563.batch_sets_buffer(),)2564{2565(Some(non_indexed_batch_sets_buffer),) => Some(2566render_device.create_bind_group(2567"reset_non_indexed_indirect_batch_sets_bind_group",2568// The early bind group is good for the main phase and late2569// phase too. They bind the same buffers.2570&pipelines2571.early_phase2572.reset_indirect_batch_sets2573.bind_group_layout,2574&BindGroupEntries::sequential((2575non_indexed_batch_sets_buffer.as_entire_binding(),2576)),2577),2578),2579_ => None,2580},25812582build_indexed_indirect: match (2583phase_indirect_parameters_buffer2584.indexed2585.cpu_metadata_buffer(),2586phase_indirect_parameters_buffer2587.indexed2588.gpu_metadata_buffer(),2589phase_indirect_parameters_buffer.indexed.data_buffer(),2590phase_indirect_parameters_buffer.indexed.batch_sets_buffer(),2591) {2592(2593Some(indexed_indirect_parameters_cpu_metadata_buffer),2594Some(indexed_indirect_parameters_gpu_metadata_buffer),2595Some(indexed_indirect_parameters_data_buffer),2596Some(indexed_batch_sets_buffer),2597) => Some(2598render_device.create_bind_group(2599"build_indexed_indirect_parameters_bind_group",2600// The frustum culling bind group is good for occlusion culling2601// too. They bind the same buffers.2602&pipelines2603.gpu_frustum_culling_build_indexed_indirect_params2604.bind_group_layout,2605&BindGroupEntries::sequential((2606current_input_buffer.as_entire_binding(),2607// Don't use `as_entire_binding` here; the shader reads2608// the length and `RawBufferVec` overallocates.2609BufferBinding {2610buffer: indexed_indirect_parameters_cpu_metadata_buffer,2611offset: 0,2612size: NonZeroU64::new(2613phase_indirect_parameters_buffer.indexed.batch_count()2614as u642615* size_of::<IndirectParametersCpuMetadata>() as u64,2616),2617},2618BufferBinding {2619buffer: indexed_indirect_parameters_gpu_metadata_buffer,2620offset: 0,2621size: NonZeroU64::new(2622phase_indirect_parameters_buffer.indexed.batch_count()2623as u642624* size_of::<IndirectParametersGpuMetadata>() as u64,2625),2626},2627indexed_batch_sets_buffer.as_entire_binding(),2628indexed_indirect_parameters_data_buffer.as_entire_binding(),2629)),2630),2631),2632_ => None,2633},26342635build_non_indexed_indirect: match (2636phase_indirect_parameters_buffer2637.non_indexed2638.cpu_metadata_buffer(),2639phase_indirect_parameters_buffer2640.non_indexed2641.gpu_metadata_buffer(),2642phase_indirect_parameters_buffer.non_indexed.data_buffer(),2643phase_indirect_parameters_buffer2644.non_indexed2645.batch_sets_buffer(),2646) {2647(2648Some(non_indexed_indirect_parameters_cpu_metadata_buffer),2649Some(non_indexed_indirect_parameters_gpu_metadata_buffer),2650Some(non_indexed_indirect_parameters_data_buffer),2651Some(non_indexed_batch_sets_buffer),2652) => Some(2653render_device.create_bind_group(2654"build_non_indexed_indirect_parameters_bind_group",2655// The frustum culling bind group is good for occlusion culling2656// too. They bind the same buffers.2657&pipelines2658.gpu_frustum_culling_build_non_indexed_indirect_params2659.bind_group_layout,2660&BindGroupEntries::sequential((2661current_input_buffer.as_entire_binding(),2662// Don't use `as_entire_binding` here; the shader reads2663// the length and `RawBufferVec` overallocates.2664BufferBinding {2665buffer: non_indexed_indirect_parameters_cpu_metadata_buffer,2666offset: 0,2667size: NonZeroU64::new(2668phase_indirect_parameters_buffer.non_indexed.batch_count()2669as u642670* size_of::<IndirectParametersCpuMetadata>() as u64,2671),2672},2673BufferBinding {2674buffer: non_indexed_indirect_parameters_gpu_metadata_buffer,2675offset: 0,2676size: NonZeroU64::new(2677phase_indirect_parameters_buffer.non_indexed.batch_count()2678as u642679* size_of::<IndirectParametersGpuMetadata>() as u64,2680),2681},2682non_indexed_batch_sets_buffer.as_entire_binding(),2683non_indexed_indirect_parameters_data_buffer.as_entire_binding(),2684)),2685),2686),2687_ => None,2688},2689},2690);2691}26922693commands.insert_resource(build_indirect_parameters_bind_groups);2694}26952696/// Writes the information needed to do GPU mesh culling to the GPU.2697pub fn write_mesh_culling_data_buffer(2698render_device: Res<RenderDevice>,2699render_queue: Res<RenderQueue>,2700mut mesh_culling_data_buffer: ResMut<MeshCullingDataBuffer>,2701) {2702mesh_culling_data_buffer.write_buffer(&render_device, &render_queue);2703}270427052706