bevy/crates/bevy_pbr/src/render/gpu_preprocess.rs
newclarityex ecccd57417
Generic system config (#17962)
# Objective
Prevents duplicate implementation between IntoSystemConfigs and
IntoSystemSetConfigs using a generic, adds a NodeType trait for more
config flexibility (opening the door to implement
https://github.com/bevyengine/bevy/issues/14195?).

## Solution
Followed writeup by @ItsDoot:
https://hackmd.io/@doot/rJeefFHc1x

Removes IntoSystemConfigs and IntoSystemSetConfigs, instead using
IntoNodeConfigs with generics.

## Testing
Pending

---

## Showcase
N/A

## Migration Guide
SystemSetConfigs -> NodeConfigs<InternedSystemSet>
SystemConfigs -> NodeConfigs<ScheduleSystem>
IntoSystemSetConfigs -> IntoNodeConfigs<InternedSystemSet, M>
IntoSystemConfigs -> IntoNodeConfigs<ScheduleSystem, M>

---------

Co-authored-by: Christian Hughes <9044780+ItsDoot@users.noreply.github.com>
Co-authored-by: Alice Cecile <alice.i.cecile@gmail.com>
2025-03-12 00:12:30 +00:00

2690 lines
112 KiB
Rust

//! GPU mesh preprocessing.
//!
//! This is an optional pass that uses a compute shader to reduce the amount of
//! data that has to be transferred from the CPU to the GPU. When enabled,
//! instead of transferring [`MeshUniform`]s to the GPU, we transfer the smaller
//! [`MeshInputUniform`]s instead and use the GPU to calculate the remaining
//! derived fields in [`MeshUniform`].
use core::num::{NonZero, NonZeroU64};
use bevy_app::{App, Plugin};
use bevy_asset::{load_internal_asset, weak_handle, Handle};
use bevy_core_pipeline::{
core_3d::graph::{Core3d, Node3d},
experimental::mip_generation::ViewDepthPyramid,
prepass::{DepthPrepass, PreviousViewData, PreviousViewUniformOffset, PreviousViewUniforms},
};
use bevy_derive::{Deref, DerefMut};
use bevy_ecs::{
component::Component,
entity::Entity,
prelude::resource_exists,
query::{Has, Or, QueryState, With, Without},
resource::Resource,
schedule::IntoScheduleConfigs as _,
system::{lifetimeless::Read, Commands, Query, Res, ResMut},
world::{FromWorld, World},
};
use bevy_render::batching::gpu_preprocessing::{
IndirectParametersGpuMetadata, UntypedPhaseIndirectParametersBuffers,
};
use bevy_render::{
batching::gpu_preprocessing::{
BatchedInstanceBuffers, GpuOcclusionCullingWorkItemBuffers, GpuPreprocessingSupport,
IndirectBatchSet, IndirectParametersBuffers, IndirectParametersCpuMetadata,
IndirectParametersIndexed, IndirectParametersNonIndexed,
LatePreprocessWorkItemIndirectParameters, PreprocessWorkItem, PreprocessWorkItemBuffers,
UntypedPhaseBatchedInstanceBuffers,
},
experimental::occlusion_culling::OcclusionCulling,
render_graph::{Node, NodeRunError, RenderGraphApp, RenderGraphContext},
render_resource::{
binding_types::{storage_buffer, storage_buffer_read_only, texture_2d, uniform_buffer},
BindGroup, BindGroupEntries, BindGroupLayout, BindingResource, Buffer, BufferBinding,
CachedComputePipelineId, ComputePassDescriptor, ComputePipelineDescriptor,
DynamicBindGroupLayoutEntries, PipelineCache, PushConstantRange, RawBufferVec, Shader,
ShaderStages, ShaderType, SpecializedComputePipeline, SpecializedComputePipelines,
TextureSampleType, UninitBufferVec,
},
renderer::{RenderContext, RenderDevice, RenderQueue},
settings::WgpuFeatures,
view::{ExtractedView, NoIndirectDrawing, ViewUniform, ViewUniformOffset, ViewUniforms},
Render, RenderApp, RenderSet,
};
use bevy_utils::TypeIdMap;
use bitflags::bitflags;
use smallvec::{smallvec, SmallVec};
use tracing::warn;
use crate::{
graph::NodePbr, MeshCullingData, MeshCullingDataBuffer, MeshInputUniform, MeshUniform,
};
use super::{ShadowView, ViewLightEntities};
/// The handle to the `mesh_preprocess.wgsl` compute shader.
pub const MESH_PREPROCESS_SHADER_HANDLE: Handle<Shader> =
weak_handle!("c8579292-cf92-43b5-9c5a-ec5bd4e44d12");
/// The handle to the `mesh_preprocess_types.wgsl` compute shader.
pub const MESH_PREPROCESS_TYPES_SHADER_HANDLE: Handle<Shader> =
weak_handle!("06f797ef-a106-4098-9a2e-20a73aa182e2");
/// The handle to the `reset_indirect_batch_sets.wgsl` compute shader.
pub const RESET_INDIRECT_BATCH_SETS_SHADER_HANDLE: Handle<Shader> =
weak_handle!("045fb176-58e2-4e76-b241-7688d761bb23");
/// The handle to the `build_indirect_params.wgsl` compute shader.
pub const BUILD_INDIRECT_PARAMS_SHADER_HANDLE: Handle<Shader> =
weak_handle!("133b01f0-3eaf-4590-9ee9-f0cf91a00b71");
/// The GPU workgroup size.
const WORKGROUP_SIZE: usize = 64;
/// A plugin that builds mesh uniforms on GPU.
///
/// This will only be added if the platform supports compute shaders (e.g. not
/// on WebGL 2).
pub struct GpuMeshPreprocessPlugin {
/// Whether we're building [`MeshUniform`]s on GPU.
///
/// This requires compute shader support and so will be forcibly disabled if
/// the platform doesn't support those.
pub use_gpu_instance_buffer_builder: bool,
}
/// The render node that clears out the GPU-side indirect metadata buffers.
///
/// This is only used when indirect drawing is enabled.
#[derive(Default)]
pub struct ClearIndirectParametersMetadataNode;
/// The render node for the first mesh preprocessing pass.
///
/// This pass runs a compute shader to cull meshes outside the view frustum (if
/// that wasn't done by the CPU), cull meshes that weren't visible last frame
/// (if occlusion culling is on), transform them, and, if indirect drawing is
/// on, populate indirect draw parameter metadata for the subsequent
/// [`EarlyPrepassBuildIndirectParametersNode`].
pub struct EarlyGpuPreprocessNode {
view_query: QueryState<
(
Read<ExtractedView>,
Option<Read<PreprocessBindGroups>>,
Option<Read<ViewUniformOffset>>,
Has<NoIndirectDrawing>,
Has<OcclusionCulling>,
),
Without<SkipGpuPreprocess>,
>,
main_view_query: QueryState<Read<ViewLightEntities>>,
}
/// The render node for the second mesh preprocessing pass.
///
/// This pass runs a compute shader to cull meshes outside the view frustum (if
/// that wasn't done by the CPU), cull meshes that were neither visible last
/// frame nor visible this frame (if occlusion culling is on), transform them,
/// and, if indirect drawing is on, populate the indirect draw parameter
/// metadata for the subsequent [`LatePrepassBuildIndirectParametersNode`].
pub struct LateGpuPreprocessNode {
view_query: QueryState<
(
Read<ExtractedView>,
Read<PreprocessBindGroups>,
Read<ViewUniformOffset>,
),
(
Without<SkipGpuPreprocess>,
Without<NoIndirectDrawing>,
With<OcclusionCulling>,
With<DepthPrepass>,
),
>,
}
/// The render node for the part of the indirect parameter building pass that
/// draws the meshes visible from the previous frame.
///
/// This node runs a compute shader on the output of the
/// [`EarlyGpuPreprocessNode`] in order to transform the
/// [`IndirectParametersGpuMetadata`] into properly-formatted
/// [`IndirectParametersIndexed`] and [`IndirectParametersNonIndexed`].
pub struct EarlyPrepassBuildIndirectParametersNode {
view_query: QueryState<
Read<PreprocessBindGroups>,
(
Without<SkipGpuPreprocess>,
Without<NoIndirectDrawing>,
Or<(With<DepthPrepass>, With<ShadowView>)>,
),
>,
}
/// The render node for the part of the indirect parameter building pass that
/// draws the meshes that are potentially visible on this frame but weren't
/// visible on the previous frame.
///
/// This node runs a compute shader on the output of the
/// [`LateGpuPreprocessNode`] in order to transform the
/// [`IndirectParametersGpuMetadata`] into properly-formatted
/// [`IndirectParametersIndexed`] and [`IndirectParametersNonIndexed`].
pub struct LatePrepassBuildIndirectParametersNode {
view_query: QueryState<
Read<PreprocessBindGroups>,
(
Without<SkipGpuPreprocess>,
Without<NoIndirectDrawing>,
Or<(With<DepthPrepass>, With<ShadowView>)>,
With<OcclusionCulling>,
),
>,
}
/// The render node for the part of the indirect parameter building pass that
/// draws all meshes, both those that are newly-visible on this frame and those
/// that were visible last frame.
///
/// This node runs a compute shader on the output of the
/// [`EarlyGpuPreprocessNode`] and [`LateGpuPreprocessNode`] in order to
/// transform the [`IndirectParametersGpuMetadata`] into properly-formatted
/// [`IndirectParametersIndexed`] and [`IndirectParametersNonIndexed`].
pub struct MainBuildIndirectParametersNode {
view_query: QueryState<
Read<PreprocessBindGroups>,
(Without<SkipGpuPreprocess>, Without<NoIndirectDrawing>),
>,
}
/// The compute shader pipelines for the GPU mesh preprocessing and indirect
/// parameter building passes.
#[derive(Resource)]
pub struct PreprocessPipelines {
/// The pipeline used for CPU culling. This pipeline doesn't populate
/// indirect parameter metadata.
pub direct_preprocess: PreprocessPipeline,
/// The pipeline used for mesh preprocessing when GPU frustum culling is in
/// use, but occlusion culling isn't.
///
/// This pipeline populates indirect parameter metadata.
pub gpu_frustum_culling_preprocess: PreprocessPipeline,
/// The pipeline used for the first phase of occlusion culling.
///
/// This pipeline culls, transforms meshes, and populates indirect parameter
/// metadata.
pub early_gpu_occlusion_culling_preprocess: PreprocessPipeline,
/// The pipeline used for the second phase of occlusion culling.
///
/// This pipeline culls, transforms meshes, and populates indirect parameter
/// metadata.
pub late_gpu_occlusion_culling_preprocess: PreprocessPipeline,
/// The pipeline that builds indirect draw parameters for indexed meshes,
/// when frustum culling is enabled but occlusion culling *isn't* enabled.
pub gpu_frustum_culling_build_indexed_indirect_params: BuildIndirectParametersPipeline,
/// The pipeline that builds indirect draw parameters for non-indexed
/// meshes, when frustum culling is enabled but occlusion culling *isn't*
/// enabled.
pub gpu_frustum_culling_build_non_indexed_indirect_params: BuildIndirectParametersPipeline,
/// Compute shader pipelines for the early prepass phase that draws meshes
/// visible in the previous frame.
pub early_phase: PreprocessPhasePipelines,
/// Compute shader pipelines for the late prepass phase that draws meshes
/// that weren't visible in the previous frame, but became visible this
/// frame.
pub late_phase: PreprocessPhasePipelines,
/// Compute shader pipelines for the main color phase.
pub main_phase: PreprocessPhasePipelines,
}
/// Compute shader pipelines for a specific phase: early, late, or main.
///
/// The distinction between these phases is relevant for occlusion culling.
#[derive(Clone)]
pub struct PreprocessPhasePipelines {
/// The pipeline that resets the indirect draw counts used in
/// `multi_draw_indirect_count` to 0 in preparation for a new pass.
pub reset_indirect_batch_sets: ResetIndirectBatchSetsPipeline,
/// The pipeline used for indexed indirect parameter building.
///
/// This pipeline converts indirect parameter metadata into indexed indirect
/// parameters.
pub gpu_occlusion_culling_build_indexed_indirect_params: BuildIndirectParametersPipeline,
/// The pipeline used for non-indexed indirect parameter building.
///
/// This pipeline converts indirect parameter metadata into non-indexed
/// indirect parameters.
pub gpu_occlusion_culling_build_non_indexed_indirect_params: BuildIndirectParametersPipeline,
}
/// The pipeline for the GPU mesh preprocessing shader.
pub struct PreprocessPipeline {
/// The bind group layout for the compute shader.
pub bind_group_layout: BindGroupLayout,
/// The pipeline ID for the compute shader.
///
/// This gets filled in `prepare_preprocess_pipelines`.
pub pipeline_id: Option<CachedComputePipelineId>,
}
/// The pipeline for the batch set count reset shader.
///
/// This shader resets the indirect batch set count to 0 for each view. It runs
/// in between every phase (early, late, and main).
#[derive(Clone)]
pub struct ResetIndirectBatchSetsPipeline {
/// The bind group layout for the compute shader.
pub bind_group_layout: BindGroupLayout,
/// The pipeline ID for the compute shader.
///
/// This gets filled in `prepare_preprocess_pipelines`.
pub pipeline_id: Option<CachedComputePipelineId>,
}
/// The pipeline for the indirect parameter building shader.
#[derive(Clone)]
pub struct BuildIndirectParametersPipeline {
/// The bind group layout for the compute shader.
pub bind_group_layout: BindGroupLayout,
/// The pipeline ID for the compute shader.
///
/// This gets filled in `prepare_preprocess_pipelines`.
pub pipeline_id: Option<CachedComputePipelineId>,
}
bitflags! {
/// Specifies variants of the mesh preprocessing shader.
#[derive(Clone, Copy, PartialEq, Eq, Hash)]
pub struct PreprocessPipelineKey: u8 {
/// Whether GPU frustum culling is in use.
///
/// This `#define`'s `FRUSTUM_CULLING` in the shader.
const FRUSTUM_CULLING = 1;
/// Whether GPU two-phase occlusion culling is in use.
///
/// This `#define`'s `OCCLUSION_CULLING` in the shader.
const OCCLUSION_CULLING = 2;
/// Whether this is the early phase of GPU two-phase occlusion culling.
///
/// This `#define`'s `EARLY_PHASE` in the shader.
const EARLY_PHASE = 4;
}
/// Specifies variants of the indirect parameter building shader.
#[derive(Clone, Copy, PartialEq, Eq, Hash)]
pub struct BuildIndirectParametersPipelineKey: u8 {
/// Whether the indirect parameter building shader is processing indexed
/// meshes (those that have index buffers).
///
/// This defines `INDEXED` in the shader.
const INDEXED = 1;
/// Whether the GPU and driver supports `multi_draw_indirect_count`.
///
/// This defines `MULTI_DRAW_INDIRECT_COUNT_SUPPORTED` in the shader.
const MULTI_DRAW_INDIRECT_COUNT_SUPPORTED = 2;
/// Whether GPU two-phase occlusion culling is in use.
///
/// This `#define`'s `OCCLUSION_CULLING` in the shader.
const OCCLUSION_CULLING = 4;
/// Whether this is the early phase of GPU two-phase occlusion culling.
///
/// This `#define`'s `EARLY_PHASE` in the shader.
const EARLY_PHASE = 8;
/// Whether this is the late phase of GPU two-phase occlusion culling.
///
/// This `#define`'s `LATE_PHASE` in the shader.
const LATE_PHASE = 16;
/// Whether this is the phase that runs after the early and late phases,
/// and right before the main drawing logic, when GPU two-phase
/// occlusion culling is in use.
///
/// This `#define`'s `MAIN_PHASE` in the shader.
const MAIN_PHASE = 32;
}
}
/// The compute shader bind group for the mesh preprocessing pass for each
/// render phase.
///
/// This goes on the view. It maps the [`core::any::TypeId`] of a render phase
/// (e.g. [`bevy_core_pipeline::core_3d::Opaque3d`]) to the
/// [`PhasePreprocessBindGroups`] for that phase.
#[derive(Component, Clone, Deref, DerefMut)]
pub struct PreprocessBindGroups(pub TypeIdMap<PhasePreprocessBindGroups>);
/// The compute shader bind group for the mesh preprocessing step for a single
/// render phase on a single view.
#[derive(Clone)]
pub enum PhasePreprocessBindGroups {
/// The bind group used for the single invocation of the compute shader when
/// indirect drawing is *not* being used.
///
/// Because direct drawing doesn't require splitting the meshes into indexed
/// and non-indexed meshes, there's only one bind group in this case.
Direct(BindGroup),
/// The bind groups used for the compute shader when indirect drawing is
/// being used, but occlusion culling isn't being used.
///
/// Because indirect drawing requires splitting the meshes into indexed and
/// non-indexed meshes, there are two bind groups here.
IndirectFrustumCulling {
/// The bind group for indexed meshes.
indexed: Option<BindGroup>,
/// The bind group for non-indexed meshes.
non_indexed: Option<BindGroup>,
},
/// The bind groups used for the compute shader when indirect drawing is
/// being used, but occlusion culling isn't being used.
///
/// Because indirect drawing requires splitting the meshes into indexed and
/// non-indexed meshes, and because occlusion culling requires splitting
/// this phase into early and late versions, there are four bind groups
/// here.
IndirectOcclusionCulling {
/// The bind group for indexed meshes during the early mesh
/// preprocessing phase.
early_indexed: Option<BindGroup>,
/// The bind group for non-indexed meshes during the early mesh
/// preprocessing phase.
early_non_indexed: Option<BindGroup>,
/// The bind group for indexed meshes during the late mesh preprocessing
/// phase.
late_indexed: Option<BindGroup>,
/// The bind group for non-indexed meshes during the late mesh
/// preprocessing phase.
late_non_indexed: Option<BindGroup>,
},
}
/// The bind groups for the compute shaders that reset indirect draw counts and
/// build indirect parameters.
///
/// There's one set of bind group for each phase. Phases are keyed off their
/// [`core::any::TypeId`].
#[derive(Resource, Default, Deref, DerefMut)]
pub struct BuildIndirectParametersBindGroups(pub TypeIdMap<PhaseBuildIndirectParametersBindGroups>);
impl BuildIndirectParametersBindGroups {
/// Creates a new, empty [`BuildIndirectParametersBindGroups`] table.
pub fn new() -> BuildIndirectParametersBindGroups {
Self::default()
}
}
/// The per-phase set of bind groups for the compute shaders that reset indirect
/// draw counts and build indirect parameters.
pub struct PhaseBuildIndirectParametersBindGroups {
/// The bind group for the `reset_indirect_batch_sets.wgsl` shader, for
/// indexed meshes.
reset_indexed_indirect_batch_sets: Option<BindGroup>,
/// The bind group for the `reset_indirect_batch_sets.wgsl` shader, for
/// non-indexed meshes.
reset_non_indexed_indirect_batch_sets: Option<BindGroup>,
/// The bind group for the `build_indirect_params.wgsl` shader, for indexed
/// meshes.
build_indexed_indirect: Option<BindGroup>,
/// The bind group for the `build_indirect_params.wgsl` shader, for
/// non-indexed meshes.
build_non_indexed_indirect: Option<BindGroup>,
}
/// Stops the `GpuPreprocessNode` attempting to generate the buffer for this view
/// useful to avoid duplicating effort if the bind group is shared between views
#[derive(Component, Default)]
pub struct SkipGpuPreprocess;
impl Plugin for GpuMeshPreprocessPlugin {
fn build(&self, app: &mut App) {
load_internal_asset!(
app,
MESH_PREPROCESS_SHADER_HANDLE,
"mesh_preprocess.wgsl",
Shader::from_wgsl
);
load_internal_asset!(
app,
RESET_INDIRECT_BATCH_SETS_SHADER_HANDLE,
"reset_indirect_batch_sets.wgsl",
Shader::from_wgsl
);
load_internal_asset!(
app,
BUILD_INDIRECT_PARAMS_SHADER_HANDLE,
"build_indirect_params.wgsl",
Shader::from_wgsl
);
load_internal_asset!(
app,
BUILD_INDIRECT_PARAMS_SHADER_HANDLE,
"build_indirect_params.wgsl",
Shader::from_wgsl
);
load_internal_asset!(
app,
BUILD_INDIRECT_PARAMS_SHADER_HANDLE,
"build_indirect_params.wgsl",
Shader::from_wgsl
);
}
fn finish(&self, app: &mut App) {
let Some(render_app) = app.get_sub_app_mut(RenderApp) else {
return;
};
// This plugin does nothing if GPU instance buffer building isn't in
// use.
let gpu_preprocessing_support = render_app.world().resource::<GpuPreprocessingSupport>();
if !self.use_gpu_instance_buffer_builder || !gpu_preprocessing_support.is_available() {
return;
}
render_app
.init_resource::<PreprocessPipelines>()
.init_resource::<SpecializedComputePipelines<PreprocessPipeline>>()
.init_resource::<SpecializedComputePipelines<ResetIndirectBatchSetsPipeline>>()
.init_resource::<SpecializedComputePipelines<BuildIndirectParametersPipeline>>()
.add_systems(
Render,
(
prepare_preprocess_pipelines.in_set(RenderSet::Prepare),
prepare_preprocess_bind_groups
.run_if(resource_exists::<BatchedInstanceBuffers<
MeshUniform,
MeshInputUniform
>>)
.in_set(RenderSet::PrepareBindGroups),
write_mesh_culling_data_buffer.in_set(RenderSet::PrepareResourcesFlush),
),
)
.add_render_graph_node::<ClearIndirectParametersMetadataNode>(
Core3d,
NodePbr::ClearIndirectParametersMetadata
)
.add_render_graph_node::<EarlyGpuPreprocessNode>(Core3d, NodePbr::EarlyGpuPreprocess)
.add_render_graph_node::<LateGpuPreprocessNode>(Core3d, NodePbr::LateGpuPreprocess)
.add_render_graph_node::<EarlyPrepassBuildIndirectParametersNode>(
Core3d,
NodePbr::EarlyPrepassBuildIndirectParameters,
)
.add_render_graph_node::<LatePrepassBuildIndirectParametersNode>(
Core3d,
NodePbr::LatePrepassBuildIndirectParameters,
)
.add_render_graph_node::<MainBuildIndirectParametersNode>(
Core3d,
NodePbr::MainBuildIndirectParameters,
)
.add_render_graph_edges(
Core3d,
(
NodePbr::ClearIndirectParametersMetadata,
NodePbr::EarlyGpuPreprocess,
NodePbr::EarlyPrepassBuildIndirectParameters,
Node3d::EarlyPrepass,
Node3d::EarlyDeferredPrepass,
Node3d::EarlyDownsampleDepth,
NodePbr::LateGpuPreprocess,
NodePbr::LatePrepassBuildIndirectParameters,
Node3d::LatePrepass,
Node3d::LateDeferredPrepass,
NodePbr::MainBuildIndirectParameters,
Node3d::StartMainPass,
),
).add_render_graph_edges(
Core3d,
(
NodePbr::EarlyPrepassBuildIndirectParameters,
NodePbr::EarlyShadowPass,
Node3d::EarlyDownsampleDepth,
)
).add_render_graph_edges(
Core3d,
(
NodePbr::LatePrepassBuildIndirectParameters,
NodePbr::LateShadowPass,
NodePbr::MainBuildIndirectParameters,
)
);
}
}
impl Node for ClearIndirectParametersMetadataNode {
fn run<'w>(
&self,
_: &mut RenderGraphContext,
render_context: &mut RenderContext<'w>,
world: &'w World,
) -> Result<(), NodeRunError> {
let Some(indirect_parameters_buffers) = world.get_resource::<IndirectParametersBuffers>()
else {
return Ok(());
};
// Clear out each indexed and non-indexed GPU-side buffer.
for phase_indirect_parameters_buffers in indirect_parameters_buffers.values() {
if let Some(indexed_gpu_metadata_buffer) = phase_indirect_parameters_buffers
.indexed
.gpu_metadata_buffer()
{
render_context.command_encoder().clear_buffer(
indexed_gpu_metadata_buffer,
0,
Some(
phase_indirect_parameters_buffers.indexed.batch_count() as u64
* size_of::<IndirectParametersGpuMetadata>() as u64,
),
);
}
if let Some(non_indexed_gpu_metadata_buffer) = phase_indirect_parameters_buffers
.non_indexed
.gpu_metadata_buffer()
{
render_context.command_encoder().clear_buffer(
non_indexed_gpu_metadata_buffer,
0,
Some(
phase_indirect_parameters_buffers.non_indexed.batch_count() as u64
* size_of::<IndirectParametersGpuMetadata>() as u64,
),
);
}
}
Ok(())
}
}
impl FromWorld for EarlyGpuPreprocessNode {
fn from_world(world: &mut World) -> Self {
Self {
view_query: QueryState::new(world),
main_view_query: QueryState::new(world),
}
}
}
impl Node for EarlyGpuPreprocessNode {
fn update(&mut self, world: &mut World) {
self.view_query.update_archetypes(world);
self.main_view_query.update_archetypes(world);
}
fn run<'w>(
&self,
graph: &mut RenderGraphContext,
render_context: &mut RenderContext<'w>,
world: &'w World,
) -> Result<(), NodeRunError> {
// Grab the [`BatchedInstanceBuffers`].
let batched_instance_buffers =
world.resource::<BatchedInstanceBuffers<MeshUniform, MeshInputUniform>>();
let pipeline_cache = world.resource::<PipelineCache>();
let preprocess_pipelines = world.resource::<PreprocessPipelines>();
let mut compute_pass =
render_context
.command_encoder()
.begin_compute_pass(&ComputePassDescriptor {
label: Some("early mesh preprocessing"),
timestamp_writes: None,
});
let mut all_views: SmallVec<[_; 8]> = SmallVec::new();
all_views.push(graph.view_entity());
if let Ok(shadow_cascade_views) =
self.main_view_query.get_manual(world, graph.view_entity())
{
all_views.extend(shadow_cascade_views.lights.iter().copied());
}
// Run the compute passes.
for view_entity in all_views {
let Ok((
view,
bind_groups,
view_uniform_offset,
no_indirect_drawing,
occlusion_culling,
)) = self.view_query.get_manual(world, view_entity)
else {
continue;
};
let Some(bind_groups) = bind_groups else {
continue;
};
let Some(view_uniform_offset) = view_uniform_offset else {
continue;
};
// Select the right pipeline, depending on whether GPU culling is in
// use.
let maybe_pipeline_id = if no_indirect_drawing {
preprocess_pipelines.direct_preprocess.pipeline_id
} else if occlusion_culling {
preprocess_pipelines
.early_gpu_occlusion_culling_preprocess
.pipeline_id
} else {
preprocess_pipelines
.gpu_frustum_culling_preprocess
.pipeline_id
};
// Fetch the pipeline.
let Some(preprocess_pipeline_id) = maybe_pipeline_id else {
warn!("The build mesh uniforms pipeline wasn't ready");
continue;
};
let Some(preprocess_pipeline) =
pipeline_cache.get_compute_pipeline(preprocess_pipeline_id)
else {
// This will happen while the pipeline is being compiled and is fine.
continue;
};
compute_pass.set_pipeline(preprocess_pipeline);
// Loop over each render phase.
for (phase_type_id, batched_phase_instance_buffers) in
&batched_instance_buffers.phase_instance_buffers
{
// Grab the work item buffers for this view.
let Some(work_item_buffers) = batched_phase_instance_buffers
.work_item_buffers
.get(&view.retained_view_entity)
else {
continue;
};
// Fetch the bind group for the render phase.
let Some(phase_bind_groups) = bind_groups.get(phase_type_id) else {
continue;
};
// Make sure the mesh preprocessing shader has access to the
// view info it needs to do culling and motion vector
// computation.
let dynamic_offsets = [view_uniform_offset.offset];
// Are we drawing directly or indirectly?
match *phase_bind_groups {
PhasePreprocessBindGroups::Direct(ref bind_group) => {
// Invoke the mesh preprocessing shader to transform
// meshes only, but not cull.
let PreprocessWorkItemBuffers::Direct(work_item_buffer) = work_item_buffers
else {
continue;
};
compute_pass.set_bind_group(0, bind_group, &dynamic_offsets);
let workgroup_count = work_item_buffer.len().div_ceil(WORKGROUP_SIZE);
if workgroup_count > 0 {
compute_pass.dispatch_workgroups(workgroup_count as u32, 1, 1);
}
}
PhasePreprocessBindGroups::IndirectFrustumCulling {
indexed: ref maybe_indexed_bind_group,
non_indexed: ref maybe_non_indexed_bind_group,
}
| PhasePreprocessBindGroups::IndirectOcclusionCulling {
early_indexed: ref maybe_indexed_bind_group,
early_non_indexed: ref maybe_non_indexed_bind_group,
..
} => {
// Invoke the mesh preprocessing shader to transform and
// cull the meshes.
let PreprocessWorkItemBuffers::Indirect {
indexed: indexed_buffer,
non_indexed: non_indexed_buffer,
..
} = work_item_buffers
else {
continue;
};
// Transform and cull indexed meshes if there are any.
if let Some(indexed_bind_group) = maybe_indexed_bind_group {
if let PreprocessWorkItemBuffers::Indirect {
gpu_occlusion_culling:
Some(GpuOcclusionCullingWorkItemBuffers {
late_indirect_parameters_indexed_offset,
..
}),
..
} = *work_item_buffers
{
compute_pass.set_push_constants(
0,
bytemuck::bytes_of(&late_indirect_parameters_indexed_offset),
);
}
compute_pass.set_bind_group(0, indexed_bind_group, &dynamic_offsets);
let workgroup_count = indexed_buffer.len().div_ceil(WORKGROUP_SIZE);
if workgroup_count > 0 {
compute_pass.dispatch_workgroups(workgroup_count as u32, 1, 1);
}
}
// Transform and cull non-indexed meshes if there are any.
if let Some(non_indexed_bind_group) = maybe_non_indexed_bind_group {
if let PreprocessWorkItemBuffers::Indirect {
gpu_occlusion_culling:
Some(GpuOcclusionCullingWorkItemBuffers {
late_indirect_parameters_non_indexed_offset,
..
}),
..
} = *work_item_buffers
{
compute_pass.set_push_constants(
0,
bytemuck::bytes_of(
&late_indirect_parameters_non_indexed_offset,
),
);
}
compute_pass.set_bind_group(
0,
non_indexed_bind_group,
&dynamic_offsets,
);
let workgroup_count = non_indexed_buffer.len().div_ceil(WORKGROUP_SIZE);
if workgroup_count > 0 {
compute_pass.dispatch_workgroups(workgroup_count as u32, 1, 1);
}
}
}
}
}
}
Ok(())
}
}
impl FromWorld for EarlyPrepassBuildIndirectParametersNode {
fn from_world(world: &mut World) -> Self {
Self {
view_query: QueryState::new(world),
}
}
}
impl FromWorld for LatePrepassBuildIndirectParametersNode {
fn from_world(world: &mut World) -> Self {
Self {
view_query: QueryState::new(world),
}
}
}
impl FromWorld for MainBuildIndirectParametersNode {
fn from_world(world: &mut World) -> Self {
Self {
view_query: QueryState::new(world),
}
}
}
impl FromWorld for LateGpuPreprocessNode {
fn from_world(world: &mut World) -> Self {
Self {
view_query: QueryState::new(world),
}
}
}
impl Node for LateGpuPreprocessNode {
fn update(&mut self, world: &mut World) {
self.view_query.update_archetypes(world);
}
fn run<'w>(
&self,
_: &mut RenderGraphContext,
render_context: &mut RenderContext<'w>,
world: &'w World,
) -> Result<(), NodeRunError> {
// Grab the [`BatchedInstanceBuffers`].
let batched_instance_buffers =
world.resource::<BatchedInstanceBuffers<MeshUniform, MeshInputUniform>>();
let pipeline_cache = world.resource::<PipelineCache>();
let preprocess_pipelines = world.resource::<PreprocessPipelines>();
let mut compute_pass =
render_context
.command_encoder()
.begin_compute_pass(&ComputePassDescriptor {
label: Some("late mesh preprocessing"),
timestamp_writes: None,
});
// Run the compute passes.
for (view, bind_groups, view_uniform_offset) in self.view_query.iter_manual(world) {
let maybe_pipeline_id = preprocess_pipelines
.late_gpu_occlusion_culling_preprocess
.pipeline_id;
// Fetch the pipeline.
let Some(preprocess_pipeline_id) = maybe_pipeline_id else {
warn!("The build mesh uniforms pipeline wasn't ready");
return Ok(());
};
let Some(preprocess_pipeline) =
pipeline_cache.get_compute_pipeline(preprocess_pipeline_id)
else {
// This will happen while the pipeline is being compiled and is fine.
return Ok(());
};
compute_pass.set_pipeline(preprocess_pipeline);
// Loop over each phase. Because we built the phases in parallel,
// each phase has a separate set of instance buffers.
for (phase_type_id, batched_phase_instance_buffers) in
&batched_instance_buffers.phase_instance_buffers
{
let UntypedPhaseBatchedInstanceBuffers {
ref work_item_buffers,
ref late_indexed_indirect_parameters_buffer,
ref late_non_indexed_indirect_parameters_buffer,
..
} = *batched_phase_instance_buffers;
// Grab the work item buffers for this view.
let Some(phase_work_item_buffers) =
work_item_buffers.get(&view.retained_view_entity)
else {
continue;
};
let (
PreprocessWorkItemBuffers::Indirect {
gpu_occlusion_culling:
Some(GpuOcclusionCullingWorkItemBuffers {
late_indirect_parameters_indexed_offset,
late_indirect_parameters_non_indexed_offset,
..
}),
..
},
Some(PhasePreprocessBindGroups::IndirectOcclusionCulling {
late_indexed: maybe_late_indexed_bind_group,
late_non_indexed: maybe_late_non_indexed_bind_group,
..
}),
Some(late_indexed_indirect_parameters_buffer),
Some(late_non_indexed_indirect_parameters_buffer),
) = (
phase_work_item_buffers,
bind_groups.get(phase_type_id),
late_indexed_indirect_parameters_buffer.buffer(),
late_non_indexed_indirect_parameters_buffer.buffer(),
)
else {
continue;
};
let mut dynamic_offsets: SmallVec<[u32; 1]> = smallvec![];
dynamic_offsets.push(view_uniform_offset.offset);
// If there's no space reserved for work items, then don't
// bother doing the dispatch, as there can't possibly be any
// meshes of the given class (indexed or non-indexed) in this
// phase.
// Transform and cull indexed meshes if there are any.
if let Some(late_indexed_bind_group) = maybe_late_indexed_bind_group {
compute_pass.set_push_constants(
0,
bytemuck::bytes_of(late_indirect_parameters_indexed_offset),
);
compute_pass.set_bind_group(0, late_indexed_bind_group, &dynamic_offsets);
compute_pass.dispatch_workgroups_indirect(
late_indexed_indirect_parameters_buffer,
(*late_indirect_parameters_indexed_offset as u64)
* (size_of::<LatePreprocessWorkItemIndirectParameters>() as u64),
);
}
// Transform and cull non-indexed meshes if there are any.
if let Some(late_non_indexed_bind_group) = maybe_late_non_indexed_bind_group {
compute_pass.set_push_constants(
0,
bytemuck::bytes_of(late_indirect_parameters_non_indexed_offset),
);
compute_pass.set_bind_group(0, late_non_indexed_bind_group, &dynamic_offsets);
compute_pass.dispatch_workgroups_indirect(
late_non_indexed_indirect_parameters_buffer,
(*late_indirect_parameters_non_indexed_offset as u64)
* (size_of::<LatePreprocessWorkItemIndirectParameters>() as u64),
);
}
}
}
Ok(())
}
}
impl Node for EarlyPrepassBuildIndirectParametersNode {
fn update(&mut self, world: &mut World) {
self.view_query.update_archetypes(world);
}
fn run<'w>(
&self,
_: &mut RenderGraphContext,
render_context: &mut RenderContext<'w>,
world: &'w World,
) -> Result<(), NodeRunError> {
let preprocess_pipelines = world.resource::<PreprocessPipelines>();
// If there are no views with a depth prepass enabled, we don't need to
// run this.
if self.view_query.iter_manual(world).next().is_none() {
return Ok(());
}
run_build_indirect_parameters_node(
render_context,
world,
&preprocess_pipelines.early_phase,
"early prepass indirect parameters building",
)
}
}
impl Node for LatePrepassBuildIndirectParametersNode {
fn update(&mut self, world: &mut World) {
self.view_query.update_archetypes(world);
}
fn run<'w>(
&self,
_: &mut RenderGraphContext,
render_context: &mut RenderContext<'w>,
world: &'w World,
) -> Result<(), NodeRunError> {
let preprocess_pipelines = world.resource::<PreprocessPipelines>();
// If there are no views with occlusion culling enabled, we don't need
// to run this.
if self.view_query.iter_manual(world).next().is_none() {
return Ok(());
}
run_build_indirect_parameters_node(
render_context,
world,
&preprocess_pipelines.late_phase,
"late prepass indirect parameters building",
)
}
}
impl Node for MainBuildIndirectParametersNode {
fn update(&mut self, world: &mut World) {
self.view_query.update_archetypes(world);
}
fn run<'w>(
&self,
_: &mut RenderGraphContext,
render_context: &mut RenderContext<'w>,
world: &'w World,
) -> Result<(), NodeRunError> {
let preprocess_pipelines = world.resource::<PreprocessPipelines>();
run_build_indirect_parameters_node(
render_context,
world,
&preprocess_pipelines.main_phase,
"main indirect parameters building",
)
}
}
fn run_build_indirect_parameters_node(
render_context: &mut RenderContext,
world: &World,
preprocess_phase_pipelines: &PreprocessPhasePipelines,
label: &'static str,
) -> Result<(), NodeRunError> {
let Some(build_indirect_params_bind_groups) =
world.get_resource::<BuildIndirectParametersBindGroups>()
else {
return Ok(());
};
let pipeline_cache = world.resource::<PipelineCache>();
let indirect_parameters_buffers = world.resource::<IndirectParametersBuffers>();
let mut compute_pass =
render_context
.command_encoder()
.begin_compute_pass(&ComputePassDescriptor {
label: Some(label),
timestamp_writes: None,
});
// Fetch the pipeline.
let (
Some(reset_indirect_batch_sets_pipeline_id),
Some(build_indexed_indirect_params_pipeline_id),
Some(build_non_indexed_indirect_params_pipeline_id),
) = (
preprocess_phase_pipelines
.reset_indirect_batch_sets
.pipeline_id,
preprocess_phase_pipelines
.gpu_occlusion_culling_build_indexed_indirect_params
.pipeline_id,
preprocess_phase_pipelines
.gpu_occlusion_culling_build_non_indexed_indirect_params
.pipeline_id,
)
else {
warn!("The build indirect parameters pipelines weren't ready");
return Ok(());
};
let (
Some(reset_indirect_batch_sets_pipeline),
Some(build_indexed_indirect_params_pipeline),
Some(build_non_indexed_indirect_params_pipeline),
) = (
pipeline_cache.get_compute_pipeline(reset_indirect_batch_sets_pipeline_id),
pipeline_cache.get_compute_pipeline(build_indexed_indirect_params_pipeline_id),
pipeline_cache.get_compute_pipeline(build_non_indexed_indirect_params_pipeline_id),
)
else {
// This will happen while the pipeline is being compiled and is fine.
return Ok(());
};
// Loop over each phase. As each has as separate set of buffers, we need to
// build indirect parameters individually for each phase.
for (phase_type_id, phase_build_indirect_params_bind_groups) in
build_indirect_params_bind_groups.iter()
{
let Some(phase_indirect_parameters_buffers) =
indirect_parameters_buffers.get(phase_type_id)
else {
continue;
};
// Build indexed indirect parameters.
if let (
Some(reset_indexed_indirect_batch_sets_bind_group),
Some(build_indirect_indexed_params_bind_group),
) = (
&phase_build_indirect_params_bind_groups.reset_indexed_indirect_batch_sets,
&phase_build_indirect_params_bind_groups.build_indexed_indirect,
) {
compute_pass.set_pipeline(reset_indirect_batch_sets_pipeline);
compute_pass.set_bind_group(0, reset_indexed_indirect_batch_sets_bind_group, &[]);
let workgroup_count = phase_indirect_parameters_buffers
.batch_set_count(true)
.div_ceil(WORKGROUP_SIZE);
if workgroup_count > 0 {
compute_pass.dispatch_workgroups(workgroup_count as u32, 1, 1);
}
compute_pass.set_pipeline(build_indexed_indirect_params_pipeline);
compute_pass.set_bind_group(0, build_indirect_indexed_params_bind_group, &[]);
let workgroup_count = phase_indirect_parameters_buffers
.indexed
.batch_count()
.div_ceil(WORKGROUP_SIZE);
if workgroup_count > 0 {
compute_pass.dispatch_workgroups(workgroup_count as u32, 1, 1);
}
}
// Build non-indexed indirect parameters.
if let (
Some(reset_non_indexed_indirect_batch_sets_bind_group),
Some(build_indirect_non_indexed_params_bind_group),
) = (
&phase_build_indirect_params_bind_groups.reset_non_indexed_indirect_batch_sets,
&phase_build_indirect_params_bind_groups.build_non_indexed_indirect,
) {
compute_pass.set_pipeline(reset_indirect_batch_sets_pipeline);
compute_pass.set_bind_group(0, reset_non_indexed_indirect_batch_sets_bind_group, &[]);
let workgroup_count = phase_indirect_parameters_buffers
.batch_set_count(false)
.div_ceil(WORKGROUP_SIZE);
if workgroup_count > 0 {
compute_pass.dispatch_workgroups(workgroup_count as u32, 1, 1);
}
compute_pass.set_pipeline(build_non_indexed_indirect_params_pipeline);
compute_pass.set_bind_group(0, build_indirect_non_indexed_params_bind_group, &[]);
let workgroup_count = phase_indirect_parameters_buffers
.non_indexed
.batch_count()
.div_ceil(WORKGROUP_SIZE);
if workgroup_count > 0 {
compute_pass.dispatch_workgroups(workgroup_count as u32, 1, 1);
}
}
}
Ok(())
}
impl PreprocessPipelines {
/// Returns true if the preprocessing and indirect parameters pipelines have
/// been loaded or false otherwise.
pub(crate) fn pipelines_are_loaded(&self, pipeline_cache: &PipelineCache) -> bool {
self.direct_preprocess.is_loaded(pipeline_cache)
&& self
.gpu_frustum_culling_preprocess
.is_loaded(pipeline_cache)
&& self
.early_gpu_occlusion_culling_preprocess
.is_loaded(pipeline_cache)
&& self
.late_gpu_occlusion_culling_preprocess
.is_loaded(pipeline_cache)
&& self
.gpu_frustum_culling_build_indexed_indirect_params
.is_loaded(pipeline_cache)
&& self
.gpu_frustum_culling_build_non_indexed_indirect_params
.is_loaded(pipeline_cache)
&& self.early_phase.is_loaded(pipeline_cache)
&& self.late_phase.is_loaded(pipeline_cache)
&& self.main_phase.is_loaded(pipeline_cache)
}
}
impl PreprocessPhasePipelines {
fn is_loaded(&self, pipeline_cache: &PipelineCache) -> bool {
self.reset_indirect_batch_sets.is_loaded(pipeline_cache)
&& self
.gpu_occlusion_culling_build_indexed_indirect_params
.is_loaded(pipeline_cache)
&& self
.gpu_occlusion_culling_build_non_indexed_indirect_params
.is_loaded(pipeline_cache)
}
}
impl PreprocessPipeline {
fn is_loaded(&self, pipeline_cache: &PipelineCache) -> bool {
self.pipeline_id
.is_some_and(|pipeline_id| pipeline_cache.get_compute_pipeline(pipeline_id).is_some())
}
}
impl ResetIndirectBatchSetsPipeline {
fn is_loaded(&self, pipeline_cache: &PipelineCache) -> bool {
self.pipeline_id
.is_some_and(|pipeline_id| pipeline_cache.get_compute_pipeline(pipeline_id).is_some())
}
}
impl BuildIndirectParametersPipeline {
/// Returns true if this pipeline has been loaded into the pipeline cache or
/// false otherwise.
fn is_loaded(&self, pipeline_cache: &PipelineCache) -> bool {
self.pipeline_id
.is_some_and(|pipeline_id| pipeline_cache.get_compute_pipeline(pipeline_id).is_some())
}
}
impl SpecializedComputePipeline for PreprocessPipeline {
type Key = PreprocessPipelineKey;
fn specialize(&self, key: Self::Key) -> ComputePipelineDescriptor {
let mut shader_defs = vec!["WRITE_INDIRECT_PARAMETERS_METADATA".into()];
if key.contains(PreprocessPipelineKey::FRUSTUM_CULLING) {
shader_defs.push("INDIRECT".into());
shader_defs.push("FRUSTUM_CULLING".into());
}
if key.contains(PreprocessPipelineKey::OCCLUSION_CULLING) {
shader_defs.push("OCCLUSION_CULLING".into());
if key.contains(PreprocessPipelineKey::EARLY_PHASE) {
shader_defs.push("EARLY_PHASE".into());
} else {
shader_defs.push("LATE_PHASE".into());
}
}
ComputePipelineDescriptor {
label: Some(
format!(
"mesh preprocessing ({})",
if key.contains(
PreprocessPipelineKey::OCCLUSION_CULLING
| PreprocessPipelineKey::EARLY_PHASE
) {
"early GPU occlusion culling"
} else if key.contains(PreprocessPipelineKey::OCCLUSION_CULLING) {
"late GPU occlusion culling"
} else if key.contains(PreprocessPipelineKey::FRUSTUM_CULLING) {
"GPU frustum culling"
} else {
"direct"
}
)
.into(),
),
layout: vec![self.bind_group_layout.clone()],
push_constant_ranges: if key.contains(PreprocessPipelineKey::OCCLUSION_CULLING) {
vec![PushConstantRange {
stages: ShaderStages::COMPUTE,
range: 0..4,
}]
} else {
vec![]
},
shader: MESH_PREPROCESS_SHADER_HANDLE,
shader_defs,
entry_point: "main".into(),
zero_initialize_workgroup_memory: false,
}
}
}
impl FromWorld for PreprocessPipelines {
fn from_world(world: &mut World) -> Self {
let render_device = world.resource::<RenderDevice>();
// GPU culling bind group parameters are a superset of those in the CPU
// culling (direct) shader.
let direct_bind_group_layout_entries = preprocess_direct_bind_group_layout_entries();
let gpu_frustum_culling_bind_group_layout_entries = gpu_culling_bind_group_layout_entries();
let gpu_early_occlusion_culling_bind_group_layout_entries =
gpu_occlusion_culling_bind_group_layout_entries().extend_with_indices(((
11,
storage_buffer::<PreprocessWorkItem>(/*has_dynamic_offset=*/ false),
),));
let gpu_late_occlusion_culling_bind_group_layout_entries =
gpu_occlusion_culling_bind_group_layout_entries();
let reset_indirect_batch_sets_bind_group_layout_entries =
DynamicBindGroupLayoutEntries::sequential(
ShaderStages::COMPUTE,
(storage_buffer::<IndirectBatchSet>(false),),
);
// Indexed and non-indexed bind group parameters share all the bind
// group layout entries except the final one.
let build_indexed_indirect_params_bind_group_layout_entries =
build_indirect_params_bind_group_layout_entries()
.extend_sequential((storage_buffer::<IndirectParametersIndexed>(false),));
let build_non_indexed_indirect_params_bind_group_layout_entries =
build_indirect_params_bind_group_layout_entries()
.extend_sequential((storage_buffer::<IndirectParametersNonIndexed>(false),));
// Create the bind group layouts.
let direct_bind_group_layout = render_device.create_bind_group_layout(
"build mesh uniforms direct bind group layout",
&direct_bind_group_layout_entries,
);
let gpu_frustum_culling_bind_group_layout = render_device.create_bind_group_layout(
"build mesh uniforms GPU frustum culling bind group layout",
&gpu_frustum_culling_bind_group_layout_entries,
);
let gpu_early_occlusion_culling_bind_group_layout = render_device.create_bind_group_layout(
"build mesh uniforms GPU early occlusion culling bind group layout",
&gpu_early_occlusion_culling_bind_group_layout_entries,
);
let gpu_late_occlusion_culling_bind_group_layout = render_device.create_bind_group_layout(
"build mesh uniforms GPU late occlusion culling bind group layout",
&gpu_late_occlusion_culling_bind_group_layout_entries,
);
let reset_indirect_batch_sets_bind_group_layout = render_device.create_bind_group_layout(
"reset indirect batch sets bind group layout",
&reset_indirect_batch_sets_bind_group_layout_entries,
);
let build_indexed_indirect_params_bind_group_layout = render_device
.create_bind_group_layout(
"build indexed indirect parameters bind group layout",
&build_indexed_indirect_params_bind_group_layout_entries,
);
let build_non_indexed_indirect_params_bind_group_layout = render_device
.create_bind_group_layout(
"build non-indexed indirect parameters bind group layout",
&build_non_indexed_indirect_params_bind_group_layout_entries,
);
let preprocess_phase_pipelines = PreprocessPhasePipelines {
reset_indirect_batch_sets: ResetIndirectBatchSetsPipeline {
bind_group_layout: reset_indirect_batch_sets_bind_group_layout.clone(),
pipeline_id: None,
},
gpu_occlusion_culling_build_indexed_indirect_params: BuildIndirectParametersPipeline {
bind_group_layout: build_indexed_indirect_params_bind_group_layout.clone(),
pipeline_id: None,
},
gpu_occlusion_culling_build_non_indexed_indirect_params:
BuildIndirectParametersPipeline {
bind_group_layout: build_non_indexed_indirect_params_bind_group_layout.clone(),
pipeline_id: None,
},
};
PreprocessPipelines {
direct_preprocess: PreprocessPipeline {
bind_group_layout: direct_bind_group_layout,
pipeline_id: None,
},
gpu_frustum_culling_preprocess: PreprocessPipeline {
bind_group_layout: gpu_frustum_culling_bind_group_layout,
pipeline_id: None,
},
early_gpu_occlusion_culling_preprocess: PreprocessPipeline {
bind_group_layout: gpu_early_occlusion_culling_bind_group_layout,
pipeline_id: None,
},
late_gpu_occlusion_culling_preprocess: PreprocessPipeline {
bind_group_layout: gpu_late_occlusion_culling_bind_group_layout,
pipeline_id: None,
},
gpu_frustum_culling_build_indexed_indirect_params: BuildIndirectParametersPipeline {
bind_group_layout: build_indexed_indirect_params_bind_group_layout.clone(),
pipeline_id: None,
},
gpu_frustum_culling_build_non_indexed_indirect_params:
BuildIndirectParametersPipeline {
bind_group_layout: build_non_indexed_indirect_params_bind_group_layout.clone(),
pipeline_id: None,
},
early_phase: preprocess_phase_pipelines.clone(),
late_phase: preprocess_phase_pipelines.clone(),
main_phase: preprocess_phase_pipelines.clone(),
}
}
}
fn preprocess_direct_bind_group_layout_entries() -> DynamicBindGroupLayoutEntries {
DynamicBindGroupLayoutEntries::new_with_indices(
ShaderStages::COMPUTE,
(
// `view`
(
0,
uniform_buffer::<ViewUniform>(/* has_dynamic_offset= */ true),
),
// `current_input`
(3, storage_buffer_read_only::<MeshInputUniform>(false)),
// `previous_input`
(4, storage_buffer_read_only::<MeshInputUniform>(false)),
// `indices`
(5, storage_buffer_read_only::<PreprocessWorkItem>(false)),
// `output`
(6, storage_buffer::<MeshUniform>(false)),
),
)
}
// Returns the first 4 bind group layout entries shared between all invocations
// of the indirect parameters building shader.
fn build_indirect_params_bind_group_layout_entries() -> DynamicBindGroupLayoutEntries {
DynamicBindGroupLayoutEntries::new_with_indices(
ShaderStages::COMPUTE,
(
(0, storage_buffer_read_only::<MeshInputUniform>(false)),
(
1,
storage_buffer_read_only::<IndirectParametersCpuMetadata>(false),
),
(
2,
storage_buffer_read_only::<IndirectParametersGpuMetadata>(false),
),
(3, storage_buffer::<IndirectBatchSet>(false)),
),
)
}
/// A system that specializes the `mesh_preprocess.wgsl` and
/// `build_indirect_params.wgsl` pipelines if necessary.
fn gpu_culling_bind_group_layout_entries() -> DynamicBindGroupLayoutEntries {
// GPU culling bind group parameters are a superset of those in the CPU
// culling (direct) shader.
preprocess_direct_bind_group_layout_entries().extend_with_indices((
// `indirect_parameters_cpu_metadata`
(
7,
storage_buffer_read_only::<IndirectParametersCpuMetadata>(
/* has_dynamic_offset= */ false,
),
),
// `indirect_parameters_gpu_metadata`
(
8,
storage_buffer::<IndirectParametersGpuMetadata>(/* has_dynamic_offset= */ false),
),
// `mesh_culling_data`
(
9,
storage_buffer_read_only::<MeshCullingData>(/* has_dynamic_offset= */ false),
),
))
}
fn gpu_occlusion_culling_bind_group_layout_entries() -> DynamicBindGroupLayoutEntries {
gpu_culling_bind_group_layout_entries().extend_with_indices((
(
2,
uniform_buffer::<PreviousViewData>(/*has_dynamic_offset=*/ false),
),
(
10,
texture_2d(TextureSampleType::Float { filterable: true }),
),
(
12,
storage_buffer::<LatePreprocessWorkItemIndirectParameters>(
/*has_dynamic_offset=*/ false,
),
),
))
}
/// A system that specializes the `mesh_preprocess.wgsl` pipelines if necessary.
pub fn prepare_preprocess_pipelines(
pipeline_cache: Res<PipelineCache>,
render_device: Res<RenderDevice>,
mut specialized_preprocess_pipelines: ResMut<SpecializedComputePipelines<PreprocessPipeline>>,
mut specialized_reset_indirect_batch_sets_pipelines: ResMut<
SpecializedComputePipelines<ResetIndirectBatchSetsPipeline>,
>,
mut specialized_build_indirect_parameters_pipelines: ResMut<
SpecializedComputePipelines<BuildIndirectParametersPipeline>,
>,
preprocess_pipelines: ResMut<PreprocessPipelines>,
) {
let preprocess_pipelines = preprocess_pipelines.into_inner();
preprocess_pipelines.direct_preprocess.prepare(
&pipeline_cache,
&mut specialized_preprocess_pipelines,
PreprocessPipelineKey::empty(),
);
preprocess_pipelines.gpu_frustum_culling_preprocess.prepare(
&pipeline_cache,
&mut specialized_preprocess_pipelines,
PreprocessPipelineKey::FRUSTUM_CULLING,
);
preprocess_pipelines
.early_gpu_occlusion_culling_preprocess
.prepare(
&pipeline_cache,
&mut specialized_preprocess_pipelines,
PreprocessPipelineKey::FRUSTUM_CULLING
| PreprocessPipelineKey::OCCLUSION_CULLING
| PreprocessPipelineKey::EARLY_PHASE,
);
preprocess_pipelines
.late_gpu_occlusion_culling_preprocess
.prepare(
&pipeline_cache,
&mut specialized_preprocess_pipelines,
PreprocessPipelineKey::FRUSTUM_CULLING | PreprocessPipelineKey::OCCLUSION_CULLING,
);
let mut build_indirect_parameters_pipeline_key = BuildIndirectParametersPipelineKey::empty();
// If the GPU and driver support `multi_draw_indirect_count`, tell the
// shader that.
if render_device
.wgpu_device()
.features()
.contains(WgpuFeatures::MULTI_DRAW_INDIRECT_COUNT)
{
build_indirect_parameters_pipeline_key
.insert(BuildIndirectParametersPipelineKey::MULTI_DRAW_INDIRECT_COUNT_SUPPORTED);
}
preprocess_pipelines
.gpu_frustum_culling_build_indexed_indirect_params
.prepare(
&pipeline_cache,
&mut specialized_build_indirect_parameters_pipelines,
build_indirect_parameters_pipeline_key | BuildIndirectParametersPipelineKey::INDEXED,
);
preprocess_pipelines
.gpu_frustum_culling_build_non_indexed_indirect_params
.prepare(
&pipeline_cache,
&mut specialized_build_indirect_parameters_pipelines,
build_indirect_parameters_pipeline_key,
);
for (preprocess_phase_pipelines, build_indirect_parameters_phase_pipeline_key) in [
(
&mut preprocess_pipelines.early_phase,
BuildIndirectParametersPipelineKey::EARLY_PHASE,
),
(
&mut preprocess_pipelines.late_phase,
BuildIndirectParametersPipelineKey::LATE_PHASE,
),
(
&mut preprocess_pipelines.main_phase,
BuildIndirectParametersPipelineKey::MAIN_PHASE,
),
] {
preprocess_phase_pipelines
.reset_indirect_batch_sets
.prepare(
&pipeline_cache,
&mut specialized_reset_indirect_batch_sets_pipelines,
);
preprocess_phase_pipelines
.gpu_occlusion_culling_build_indexed_indirect_params
.prepare(
&pipeline_cache,
&mut specialized_build_indirect_parameters_pipelines,
build_indirect_parameters_pipeline_key
| build_indirect_parameters_phase_pipeline_key
| BuildIndirectParametersPipelineKey::INDEXED
| BuildIndirectParametersPipelineKey::OCCLUSION_CULLING,
);
preprocess_phase_pipelines
.gpu_occlusion_culling_build_non_indexed_indirect_params
.prepare(
&pipeline_cache,
&mut specialized_build_indirect_parameters_pipelines,
build_indirect_parameters_pipeline_key
| build_indirect_parameters_phase_pipeline_key
| BuildIndirectParametersPipelineKey::OCCLUSION_CULLING,
);
}
}
impl PreprocessPipeline {
fn prepare(
&mut self,
pipeline_cache: &PipelineCache,
pipelines: &mut SpecializedComputePipelines<PreprocessPipeline>,
key: PreprocessPipelineKey,
) {
if self.pipeline_id.is_some() {
return;
}
let preprocess_pipeline_id = pipelines.specialize(pipeline_cache, self, key);
self.pipeline_id = Some(preprocess_pipeline_id);
}
}
impl SpecializedComputePipeline for ResetIndirectBatchSetsPipeline {
type Key = ();
fn specialize(&self, _: Self::Key) -> ComputePipelineDescriptor {
ComputePipelineDescriptor {
label: Some("reset indirect batch sets".into()),
layout: vec![self.bind_group_layout.clone()],
push_constant_ranges: vec![],
shader: RESET_INDIRECT_BATCH_SETS_SHADER_HANDLE,
shader_defs: vec![],
entry_point: "main".into(),
zero_initialize_workgroup_memory: false,
}
}
}
impl SpecializedComputePipeline for BuildIndirectParametersPipeline {
type Key = BuildIndirectParametersPipelineKey;
fn specialize(&self, key: Self::Key) -> ComputePipelineDescriptor {
let mut shader_defs = vec![];
if key.contains(BuildIndirectParametersPipelineKey::INDEXED) {
shader_defs.push("INDEXED".into());
}
if key.contains(BuildIndirectParametersPipelineKey::MULTI_DRAW_INDIRECT_COUNT_SUPPORTED) {
shader_defs.push("MULTI_DRAW_INDIRECT_COUNT_SUPPORTED".into());
}
if key.contains(BuildIndirectParametersPipelineKey::OCCLUSION_CULLING) {
shader_defs.push("OCCLUSION_CULLING".into());
}
if key.contains(BuildIndirectParametersPipelineKey::EARLY_PHASE) {
shader_defs.push("EARLY_PHASE".into());
}
if key.contains(BuildIndirectParametersPipelineKey::LATE_PHASE) {
shader_defs.push("LATE_PHASE".into());
}
if key.contains(BuildIndirectParametersPipelineKey::MAIN_PHASE) {
shader_defs.push("MAIN_PHASE".into());
}
let label = format!(
"{} build {}indexed indirect parameters",
if !key.contains(BuildIndirectParametersPipelineKey::OCCLUSION_CULLING) {
"frustum culling"
} else if key.contains(BuildIndirectParametersPipelineKey::EARLY_PHASE) {
"early occlusion culling"
} else if key.contains(BuildIndirectParametersPipelineKey::LATE_PHASE) {
"late occlusion culling"
} else {
"main occlusion culling"
},
if key.contains(BuildIndirectParametersPipelineKey::INDEXED) {
""
} else {
"non-"
}
);
ComputePipelineDescriptor {
label: Some(label.into()),
layout: vec![self.bind_group_layout.clone()],
push_constant_ranges: vec![],
shader: BUILD_INDIRECT_PARAMS_SHADER_HANDLE,
shader_defs,
entry_point: "main".into(),
zero_initialize_workgroup_memory: false,
}
}
}
impl ResetIndirectBatchSetsPipeline {
fn prepare(
&mut self,
pipeline_cache: &PipelineCache,
pipelines: &mut SpecializedComputePipelines<ResetIndirectBatchSetsPipeline>,
) {
if self.pipeline_id.is_some() {
return;
}
let reset_indirect_batch_sets_pipeline_id = pipelines.specialize(pipeline_cache, self, ());
self.pipeline_id = Some(reset_indirect_batch_sets_pipeline_id);
}
}
impl BuildIndirectParametersPipeline {
fn prepare(
&mut self,
pipeline_cache: &PipelineCache,
pipelines: &mut SpecializedComputePipelines<BuildIndirectParametersPipeline>,
key: BuildIndirectParametersPipelineKey,
) {
if self.pipeline_id.is_some() {
return;
}
let build_indirect_parameters_pipeline_id = pipelines.specialize(pipeline_cache, self, key);
self.pipeline_id = Some(build_indirect_parameters_pipeline_id);
}
}
/// A system that attaches the mesh uniform buffers to the bind groups for the
/// variants of the mesh preprocessing compute shader.
#[expect(
clippy::too_many_arguments,
reason = "it's a system that needs a lot of arguments"
)]
pub fn prepare_preprocess_bind_groups(
mut commands: Commands,
views: Query<(Entity, &ExtractedView)>,
view_depth_pyramids: Query<(&ViewDepthPyramid, &PreviousViewUniformOffset)>,
render_device: Res<RenderDevice>,
batched_instance_buffers: Res<BatchedInstanceBuffers<MeshUniform, MeshInputUniform>>,
indirect_parameters_buffers: Res<IndirectParametersBuffers>,
mesh_culling_data_buffer: Res<MeshCullingDataBuffer>,
view_uniforms: Res<ViewUniforms>,
previous_view_uniforms: Res<PreviousViewUniforms>,
pipelines: Res<PreprocessPipelines>,
) {
// Grab the `BatchedInstanceBuffers`.
let BatchedInstanceBuffers {
current_input_buffer: current_input_buffer_vec,
previous_input_buffer: previous_input_buffer_vec,
phase_instance_buffers,
} = batched_instance_buffers.into_inner();
let (Some(current_input_buffer), Some(previous_input_buffer)) = (
current_input_buffer_vec.buffer().buffer(),
previous_input_buffer_vec.buffer().buffer(),
) else {
return;
};
// Record whether we have any meshes that are to be drawn indirectly. If we
// don't, then we can skip building indirect parameters.
let mut any_indirect = false;
// Loop over each view.
for (view_entity, view) in &views {
let mut bind_groups = TypeIdMap::default();
// Loop over each phase.
for (phase_type_id, phase_instance_buffers) in phase_instance_buffers {
let UntypedPhaseBatchedInstanceBuffers {
data_buffer: ref data_buffer_vec,
ref work_item_buffers,
ref late_indexed_indirect_parameters_buffer,
ref late_non_indexed_indirect_parameters_buffer,
} = *phase_instance_buffers;
let Some(data_buffer) = data_buffer_vec.buffer() else {
continue;
};
// Grab the indirect parameters buffers for this phase.
let Some(phase_indirect_parameters_buffers) =
indirect_parameters_buffers.get(phase_type_id)
else {
continue;
};
let Some(work_item_buffers) = work_item_buffers.get(&view.retained_view_entity) else {
continue;
};
// Create the `PreprocessBindGroupBuilder`.
let preprocess_bind_group_builder = PreprocessBindGroupBuilder {
view: view_entity,
late_indexed_indirect_parameters_buffer,
late_non_indexed_indirect_parameters_buffer,
render_device: &render_device,
phase_indirect_parameters_buffers,
mesh_culling_data_buffer: &mesh_culling_data_buffer,
view_uniforms: &view_uniforms,
previous_view_uniforms: &previous_view_uniforms,
pipelines: &pipelines,
current_input_buffer,
previous_input_buffer,
data_buffer,
};
// Depending on the type of work items we have, construct the
// appropriate bind groups.
let (was_indirect, bind_group) = match *work_item_buffers {
PreprocessWorkItemBuffers::Direct(ref work_item_buffer) => (
false,
preprocess_bind_group_builder
.create_direct_preprocess_bind_groups(work_item_buffer),
),
PreprocessWorkItemBuffers::Indirect {
indexed: ref indexed_work_item_buffer,
non_indexed: ref non_indexed_work_item_buffer,
gpu_occlusion_culling: Some(ref gpu_occlusion_culling_work_item_buffers),
} => (
true,
preprocess_bind_group_builder
.create_indirect_occlusion_culling_preprocess_bind_groups(
&view_depth_pyramids,
indexed_work_item_buffer,
non_indexed_work_item_buffer,
gpu_occlusion_culling_work_item_buffers,
),
),
PreprocessWorkItemBuffers::Indirect {
indexed: ref indexed_work_item_buffer,
non_indexed: ref non_indexed_work_item_buffer,
gpu_occlusion_culling: None,
} => (
true,
preprocess_bind_group_builder
.create_indirect_frustum_culling_preprocess_bind_groups(
indexed_work_item_buffer,
non_indexed_work_item_buffer,
),
),
};
// Write that bind group in.
if let Some(bind_group) = bind_group {
any_indirect = any_indirect || was_indirect;
bind_groups.insert(*phase_type_id, bind_group);
}
}
// Save the bind groups.
commands
.entity(view_entity)
.insert(PreprocessBindGroups(bind_groups));
}
// Now, if there were any indirect draw commands, create the bind groups for
// the indirect parameters building shader.
if any_indirect {
create_build_indirect_parameters_bind_groups(
&mut commands,
&render_device,
&pipelines,
current_input_buffer,
&indirect_parameters_buffers,
);
}
}
/// A temporary structure that stores all the information needed to construct
/// bind groups for the mesh preprocessing shader.
struct PreprocessBindGroupBuilder<'a> {
/// The render-world entity corresponding to the current view.
view: Entity,
/// The indirect compute dispatch parameters buffer for indexed meshes in
/// the late prepass.
late_indexed_indirect_parameters_buffer:
&'a RawBufferVec<LatePreprocessWorkItemIndirectParameters>,
/// The indirect compute dispatch parameters buffer for non-indexed meshes
/// in the late prepass.
late_non_indexed_indirect_parameters_buffer:
&'a RawBufferVec<LatePreprocessWorkItemIndirectParameters>,
/// The device.
render_device: &'a RenderDevice,
/// The buffers that store indirect draw parameters.
phase_indirect_parameters_buffers: &'a UntypedPhaseIndirectParametersBuffers,
/// The GPU buffer that stores the information needed to cull each mesh.
mesh_culling_data_buffer: &'a MeshCullingDataBuffer,
/// The GPU buffer that stores information about the view.
view_uniforms: &'a ViewUniforms,
/// The GPU buffer that stores information about the view from last frame.
previous_view_uniforms: &'a PreviousViewUniforms,
/// The pipelines for the mesh preprocessing shader.
pipelines: &'a PreprocessPipelines,
/// The GPU buffer containing the list of [`MeshInputUniform`]s for the
/// current frame.
current_input_buffer: &'a Buffer,
/// The GPU buffer containing the list of [`MeshInputUniform`]s for the
/// previous frame.
previous_input_buffer: &'a Buffer,
/// The GPU buffer containing the list of [`MeshUniform`]s for the current
/// frame.
///
/// This is the buffer containing the mesh's final transforms that the
/// shaders will write to.
data_buffer: &'a Buffer,
}
impl<'a> PreprocessBindGroupBuilder<'a> {
/// Creates the bind groups for mesh preprocessing when GPU frustum culling
/// and GPU occlusion culling are both disabled.
fn create_direct_preprocess_bind_groups(
&self,
work_item_buffer: &RawBufferVec<PreprocessWorkItem>,
) -> Option<PhasePreprocessBindGroups> {
// Don't use `as_entire_binding()` here; the shader reads the array
// length and the underlying buffer may be longer than the actual size
// of the vector.
let work_item_buffer_size = NonZero::<u64>::try_from(
work_item_buffer.len() as u64 * u64::from(PreprocessWorkItem::min_size()),
)
.ok();
Some(PhasePreprocessBindGroups::Direct(
self.render_device.create_bind_group(
"preprocess_direct_bind_group",
&self.pipelines.direct_preprocess.bind_group_layout,
&BindGroupEntries::with_indices((
(0, self.view_uniforms.uniforms.binding()?),
(3, self.current_input_buffer.as_entire_binding()),
(4, self.previous_input_buffer.as_entire_binding()),
(
5,
BindingResource::Buffer(BufferBinding {
buffer: work_item_buffer.buffer()?,
offset: 0,
size: work_item_buffer_size,
}),
),
(6, self.data_buffer.as_entire_binding()),
)),
),
))
}
/// Creates the bind groups for mesh preprocessing when GPU occlusion
/// culling is enabled.
fn create_indirect_occlusion_culling_preprocess_bind_groups(
&self,
view_depth_pyramids: &Query<(&ViewDepthPyramid, &PreviousViewUniformOffset)>,
indexed_work_item_buffer: &RawBufferVec<PreprocessWorkItem>,
non_indexed_work_item_buffer: &RawBufferVec<PreprocessWorkItem>,
gpu_occlusion_culling_work_item_buffers: &GpuOcclusionCullingWorkItemBuffers,
) -> Option<PhasePreprocessBindGroups> {
let GpuOcclusionCullingWorkItemBuffers {
late_indexed: ref late_indexed_work_item_buffer,
late_non_indexed: ref late_non_indexed_work_item_buffer,
..
} = *gpu_occlusion_culling_work_item_buffers;
let (view_depth_pyramid, previous_view_uniform_offset) =
view_depth_pyramids.get(self.view).ok()?;
Some(PhasePreprocessBindGroups::IndirectOcclusionCulling {
early_indexed: self.create_indirect_occlusion_culling_early_indexed_bind_group(
view_depth_pyramid,
previous_view_uniform_offset,
indexed_work_item_buffer,
late_indexed_work_item_buffer,
),
early_non_indexed: self.create_indirect_occlusion_culling_early_non_indexed_bind_group(
view_depth_pyramid,
previous_view_uniform_offset,
non_indexed_work_item_buffer,
late_non_indexed_work_item_buffer,
),
late_indexed: self.create_indirect_occlusion_culling_late_indexed_bind_group(
view_depth_pyramid,
previous_view_uniform_offset,
late_indexed_work_item_buffer,
),
late_non_indexed: self.create_indirect_occlusion_culling_late_non_indexed_bind_group(
view_depth_pyramid,
previous_view_uniform_offset,
late_non_indexed_work_item_buffer,
),
})
}
/// Creates the bind group for the first phase of mesh preprocessing of
/// indexed meshes when GPU occlusion culling is enabled.
fn create_indirect_occlusion_culling_early_indexed_bind_group(
&self,
view_depth_pyramid: &ViewDepthPyramid,
previous_view_uniform_offset: &PreviousViewUniformOffset,
indexed_work_item_buffer: &RawBufferVec<PreprocessWorkItem>,
late_indexed_work_item_buffer: &UninitBufferVec<PreprocessWorkItem>,
) -> Option<BindGroup> {
let mesh_culling_data_buffer = self.mesh_culling_data_buffer.buffer()?;
let view_uniforms_binding = self.view_uniforms.uniforms.binding()?;
let previous_view_buffer = self.previous_view_uniforms.uniforms.buffer()?;
match (
self.phase_indirect_parameters_buffers
.indexed
.cpu_metadata_buffer(),
self.phase_indirect_parameters_buffers
.indexed
.gpu_metadata_buffer(),
indexed_work_item_buffer.buffer(),
late_indexed_work_item_buffer.buffer(),
self.late_indexed_indirect_parameters_buffer.buffer(),
) {
(
Some(indexed_cpu_metadata_buffer),
Some(indexed_gpu_metadata_buffer),
Some(indexed_work_item_gpu_buffer),
Some(late_indexed_work_item_gpu_buffer),
Some(late_indexed_indirect_parameters_buffer),
) => {
// Don't use `as_entire_binding()` here; the shader reads the array
// length and the underlying buffer may be longer than the actual size
// of the vector.
let indexed_work_item_buffer_size = NonZero::<u64>::try_from(
indexed_work_item_buffer.len() as u64
* u64::from(PreprocessWorkItem::min_size()),
)
.ok();
Some(
self.render_device.create_bind_group(
"preprocess_early_indexed_gpu_occlusion_culling_bind_group",
&self
.pipelines
.early_gpu_occlusion_culling_preprocess
.bind_group_layout,
&BindGroupEntries::with_indices((
(3, self.current_input_buffer.as_entire_binding()),
(4, self.previous_input_buffer.as_entire_binding()),
(
5,
BindingResource::Buffer(BufferBinding {
buffer: indexed_work_item_gpu_buffer,
offset: 0,
size: indexed_work_item_buffer_size,
}),
),
(6, self.data_buffer.as_entire_binding()),
(7, indexed_cpu_metadata_buffer.as_entire_binding()),
(8, indexed_gpu_metadata_buffer.as_entire_binding()),
(9, mesh_culling_data_buffer.as_entire_binding()),
(0, view_uniforms_binding.clone()),
(10, &view_depth_pyramid.all_mips),
(
2,
BufferBinding {
buffer: previous_view_buffer,
offset: previous_view_uniform_offset.offset as u64,
size: NonZeroU64::new(size_of::<PreviousViewData>() as u64),
},
),
(
11,
BufferBinding {
buffer: late_indexed_work_item_gpu_buffer,
offset: 0,
size: indexed_work_item_buffer_size,
},
),
(
12,
BufferBinding {
buffer: late_indexed_indirect_parameters_buffer,
offset: 0,
size: NonZeroU64::new(
late_indexed_indirect_parameters_buffer.size(),
),
},
),
)),
),
)
}
_ => None,
}
}
/// Creates the bind group for the first phase of mesh preprocessing of
/// non-indexed meshes when GPU occlusion culling is enabled.
fn create_indirect_occlusion_culling_early_non_indexed_bind_group(
&self,
view_depth_pyramid: &ViewDepthPyramid,
previous_view_uniform_offset: &PreviousViewUniformOffset,
non_indexed_work_item_buffer: &RawBufferVec<PreprocessWorkItem>,
late_non_indexed_work_item_buffer: &UninitBufferVec<PreprocessWorkItem>,
) -> Option<BindGroup> {
let mesh_culling_data_buffer = self.mesh_culling_data_buffer.buffer()?;
let view_uniforms_binding = self.view_uniforms.uniforms.binding()?;
let previous_view_buffer = self.previous_view_uniforms.uniforms.buffer()?;
match (
self.phase_indirect_parameters_buffers
.non_indexed
.cpu_metadata_buffer(),
self.phase_indirect_parameters_buffers
.non_indexed
.gpu_metadata_buffer(),
non_indexed_work_item_buffer.buffer(),
late_non_indexed_work_item_buffer.buffer(),
self.late_non_indexed_indirect_parameters_buffer.buffer(),
) {
(
Some(non_indexed_cpu_metadata_buffer),
Some(non_indexed_gpu_metadata_buffer),
Some(non_indexed_work_item_gpu_buffer),
Some(late_non_indexed_work_item_buffer),
Some(late_non_indexed_indirect_parameters_buffer),
) => {
// Don't use `as_entire_binding()` here; the shader reads the array
// length and the underlying buffer may be longer than the actual size
// of the vector.
let non_indexed_work_item_buffer_size = NonZero::<u64>::try_from(
non_indexed_work_item_buffer.len() as u64
* u64::from(PreprocessWorkItem::min_size()),
)
.ok();
Some(
self.render_device.create_bind_group(
"preprocess_early_non_indexed_gpu_occlusion_culling_bind_group",
&self
.pipelines
.early_gpu_occlusion_culling_preprocess
.bind_group_layout,
&BindGroupEntries::with_indices((
(3, self.current_input_buffer.as_entire_binding()),
(4, self.previous_input_buffer.as_entire_binding()),
(
5,
BindingResource::Buffer(BufferBinding {
buffer: non_indexed_work_item_gpu_buffer,
offset: 0,
size: non_indexed_work_item_buffer_size,
}),
),
(6, self.data_buffer.as_entire_binding()),
(7, non_indexed_cpu_metadata_buffer.as_entire_binding()),
(8, non_indexed_gpu_metadata_buffer.as_entire_binding()),
(9, mesh_culling_data_buffer.as_entire_binding()),
(0, view_uniforms_binding.clone()),
(10, &view_depth_pyramid.all_mips),
(
2,
BufferBinding {
buffer: previous_view_buffer,
offset: previous_view_uniform_offset.offset as u64,
size: NonZeroU64::new(size_of::<PreviousViewData>() as u64),
},
),
(
11,
BufferBinding {
buffer: late_non_indexed_work_item_buffer,
offset: 0,
size: non_indexed_work_item_buffer_size,
},
),
(
12,
BufferBinding {
buffer: late_non_indexed_indirect_parameters_buffer,
offset: 0,
size: NonZeroU64::new(
late_non_indexed_indirect_parameters_buffer.size(),
),
},
),
)),
),
)
}
_ => None,
}
}
/// Creates the bind group for the second phase of mesh preprocessing of
/// indexed meshes when GPU occlusion culling is enabled.
fn create_indirect_occlusion_culling_late_indexed_bind_group(
&self,
view_depth_pyramid: &ViewDepthPyramid,
previous_view_uniform_offset: &PreviousViewUniformOffset,
late_indexed_work_item_buffer: &UninitBufferVec<PreprocessWorkItem>,
) -> Option<BindGroup> {
let mesh_culling_data_buffer = self.mesh_culling_data_buffer.buffer()?;
let view_uniforms_binding = self.view_uniforms.uniforms.binding()?;
let previous_view_buffer = self.previous_view_uniforms.uniforms.buffer()?;
match (
self.phase_indirect_parameters_buffers
.indexed
.cpu_metadata_buffer(),
self.phase_indirect_parameters_buffers
.indexed
.gpu_metadata_buffer(),
late_indexed_work_item_buffer.buffer(),
self.late_indexed_indirect_parameters_buffer.buffer(),
) {
(
Some(indexed_cpu_metadata_buffer),
Some(indexed_gpu_metadata_buffer),
Some(late_indexed_work_item_gpu_buffer),
Some(late_indexed_indirect_parameters_buffer),
) => {
// Don't use `as_entire_binding()` here; the shader reads the array
// length and the underlying buffer may be longer than the actual size
// of the vector.
let late_indexed_work_item_buffer_size = NonZero::<u64>::try_from(
late_indexed_work_item_buffer.len() as u64
* u64::from(PreprocessWorkItem::min_size()),
)
.ok();
Some(
self.render_device.create_bind_group(
"preprocess_late_indexed_gpu_occlusion_culling_bind_group",
&self
.pipelines
.late_gpu_occlusion_culling_preprocess
.bind_group_layout,
&BindGroupEntries::with_indices((
(3, self.current_input_buffer.as_entire_binding()),
(4, self.previous_input_buffer.as_entire_binding()),
(
5,
BindingResource::Buffer(BufferBinding {
buffer: late_indexed_work_item_gpu_buffer,
offset: 0,
size: late_indexed_work_item_buffer_size,
}),
),
(6, self.data_buffer.as_entire_binding()),
(7, indexed_cpu_metadata_buffer.as_entire_binding()),
(8, indexed_gpu_metadata_buffer.as_entire_binding()),
(9, mesh_culling_data_buffer.as_entire_binding()),
(0, view_uniforms_binding.clone()),
(10, &view_depth_pyramid.all_mips),
(
2,
BufferBinding {
buffer: previous_view_buffer,
offset: previous_view_uniform_offset.offset as u64,
size: NonZeroU64::new(size_of::<PreviousViewData>() as u64),
},
),
(
12,
BufferBinding {
buffer: late_indexed_indirect_parameters_buffer,
offset: 0,
size: NonZeroU64::new(
late_indexed_indirect_parameters_buffer.size(),
),
},
),
)),
),
)
}
_ => None,
}
}
/// Creates the bind group for the second phase of mesh preprocessing of
/// non-indexed meshes when GPU occlusion culling is enabled.
fn create_indirect_occlusion_culling_late_non_indexed_bind_group(
&self,
view_depth_pyramid: &ViewDepthPyramid,
previous_view_uniform_offset: &PreviousViewUniformOffset,
late_non_indexed_work_item_buffer: &UninitBufferVec<PreprocessWorkItem>,
) -> Option<BindGroup> {
let mesh_culling_data_buffer = self.mesh_culling_data_buffer.buffer()?;
let view_uniforms_binding = self.view_uniforms.uniforms.binding()?;
let previous_view_buffer = self.previous_view_uniforms.uniforms.buffer()?;
match (
self.phase_indirect_parameters_buffers
.non_indexed
.cpu_metadata_buffer(),
self.phase_indirect_parameters_buffers
.non_indexed
.gpu_metadata_buffer(),
late_non_indexed_work_item_buffer.buffer(),
self.late_non_indexed_indirect_parameters_buffer.buffer(),
) {
(
Some(non_indexed_cpu_metadata_buffer),
Some(non_indexed_gpu_metadata_buffer),
Some(non_indexed_work_item_gpu_buffer),
Some(late_non_indexed_indirect_parameters_buffer),
) => {
// Don't use `as_entire_binding()` here; the shader reads the array
// length and the underlying buffer may be longer than the actual size
// of the vector.
let non_indexed_work_item_buffer_size = NonZero::<u64>::try_from(
late_non_indexed_work_item_buffer.len() as u64
* u64::from(PreprocessWorkItem::min_size()),
)
.ok();
Some(
self.render_device.create_bind_group(
"preprocess_late_non_indexed_gpu_occlusion_culling_bind_group",
&self
.pipelines
.late_gpu_occlusion_culling_preprocess
.bind_group_layout,
&BindGroupEntries::with_indices((
(3, self.current_input_buffer.as_entire_binding()),
(4, self.previous_input_buffer.as_entire_binding()),
(
5,
BindingResource::Buffer(BufferBinding {
buffer: non_indexed_work_item_gpu_buffer,
offset: 0,
size: non_indexed_work_item_buffer_size,
}),
),
(6, self.data_buffer.as_entire_binding()),
(7, non_indexed_cpu_metadata_buffer.as_entire_binding()),
(8, non_indexed_gpu_metadata_buffer.as_entire_binding()),
(9, mesh_culling_data_buffer.as_entire_binding()),
(0, view_uniforms_binding.clone()),
(10, &view_depth_pyramid.all_mips),
(
2,
BufferBinding {
buffer: previous_view_buffer,
offset: previous_view_uniform_offset.offset as u64,
size: NonZeroU64::new(size_of::<PreviousViewData>() as u64),
},
),
(
12,
BufferBinding {
buffer: late_non_indexed_indirect_parameters_buffer,
offset: 0,
size: NonZeroU64::new(
late_non_indexed_indirect_parameters_buffer.size(),
),
},
),
)),
),
)
}
_ => None,
}
}
/// Creates the bind groups for mesh preprocessing when GPU frustum culling
/// is enabled, but GPU occlusion culling is disabled.
fn create_indirect_frustum_culling_preprocess_bind_groups(
&self,
indexed_work_item_buffer: &RawBufferVec<PreprocessWorkItem>,
non_indexed_work_item_buffer: &RawBufferVec<PreprocessWorkItem>,
) -> Option<PhasePreprocessBindGroups> {
Some(PhasePreprocessBindGroups::IndirectFrustumCulling {
indexed: self
.create_indirect_frustum_culling_indexed_bind_group(indexed_work_item_buffer),
non_indexed: self.create_indirect_frustum_culling_non_indexed_bind_group(
non_indexed_work_item_buffer,
),
})
}
/// Creates the bind group for mesh preprocessing of indexed meshes when GPU
/// frustum culling is enabled, but GPU occlusion culling is disabled.
fn create_indirect_frustum_culling_indexed_bind_group(
&self,
indexed_work_item_buffer: &RawBufferVec<PreprocessWorkItem>,
) -> Option<BindGroup> {
let mesh_culling_data_buffer = self.mesh_culling_data_buffer.buffer()?;
let view_uniforms_binding = self.view_uniforms.uniforms.binding()?;
match (
self.phase_indirect_parameters_buffers
.indexed
.cpu_metadata_buffer(),
self.phase_indirect_parameters_buffers
.indexed
.gpu_metadata_buffer(),
indexed_work_item_buffer.buffer(),
) {
(
Some(indexed_cpu_metadata_buffer),
Some(indexed_gpu_metadata_buffer),
Some(indexed_work_item_gpu_buffer),
) => {
// Don't use `as_entire_binding()` here; the shader reads the array
// length and the underlying buffer may be longer than the actual size
// of the vector.
let indexed_work_item_buffer_size = NonZero::<u64>::try_from(
indexed_work_item_buffer.len() as u64
* u64::from(PreprocessWorkItem::min_size()),
)
.ok();
Some(
self.render_device.create_bind_group(
"preprocess_gpu_indexed_frustum_culling_bind_group",
&self
.pipelines
.gpu_frustum_culling_preprocess
.bind_group_layout,
&BindGroupEntries::with_indices((
(3, self.current_input_buffer.as_entire_binding()),
(4, self.previous_input_buffer.as_entire_binding()),
(
5,
BindingResource::Buffer(BufferBinding {
buffer: indexed_work_item_gpu_buffer,
offset: 0,
size: indexed_work_item_buffer_size,
}),
),
(6, self.data_buffer.as_entire_binding()),
(7, indexed_cpu_metadata_buffer.as_entire_binding()),
(8, indexed_gpu_metadata_buffer.as_entire_binding()),
(9, mesh_culling_data_buffer.as_entire_binding()),
(0, view_uniforms_binding.clone()),
)),
),
)
}
_ => None,
}
}
/// Creates the bind group for mesh preprocessing of non-indexed meshes when
/// GPU frustum culling is enabled, but GPU occlusion culling is disabled.
fn create_indirect_frustum_culling_non_indexed_bind_group(
&self,
non_indexed_work_item_buffer: &RawBufferVec<PreprocessWorkItem>,
) -> Option<BindGroup> {
let mesh_culling_data_buffer = self.mesh_culling_data_buffer.buffer()?;
let view_uniforms_binding = self.view_uniforms.uniforms.binding()?;
match (
self.phase_indirect_parameters_buffers
.non_indexed
.cpu_metadata_buffer(),
self.phase_indirect_parameters_buffers
.non_indexed
.gpu_metadata_buffer(),
non_indexed_work_item_buffer.buffer(),
) {
(
Some(non_indexed_cpu_metadata_buffer),
Some(non_indexed_gpu_metadata_buffer),
Some(non_indexed_work_item_gpu_buffer),
) => {
// Don't use `as_entire_binding()` here; the shader reads the array
// length and the underlying buffer may be longer than the actual size
// of the vector.
let non_indexed_work_item_buffer_size = NonZero::<u64>::try_from(
non_indexed_work_item_buffer.len() as u64
* u64::from(PreprocessWorkItem::min_size()),
)
.ok();
Some(
self.render_device.create_bind_group(
"preprocess_gpu_non_indexed_frustum_culling_bind_group",
&self
.pipelines
.gpu_frustum_culling_preprocess
.bind_group_layout,
&BindGroupEntries::with_indices((
(3, self.current_input_buffer.as_entire_binding()),
(4, self.previous_input_buffer.as_entire_binding()),
(
5,
BindingResource::Buffer(BufferBinding {
buffer: non_indexed_work_item_gpu_buffer,
offset: 0,
size: non_indexed_work_item_buffer_size,
}),
),
(6, self.data_buffer.as_entire_binding()),
(7, non_indexed_cpu_metadata_buffer.as_entire_binding()),
(8, non_indexed_gpu_metadata_buffer.as_entire_binding()),
(9, mesh_culling_data_buffer.as_entire_binding()),
(0, view_uniforms_binding.clone()),
)),
),
)
}
_ => None,
}
}
}
/// A system that creates bind groups from the indirect parameters metadata and
/// data buffers for the indirect batch set reset shader and the indirect
/// parameter building shader.
fn create_build_indirect_parameters_bind_groups(
commands: &mut Commands,
render_device: &RenderDevice,
pipelines: &PreprocessPipelines,
current_input_buffer: &Buffer,
indirect_parameters_buffers: &IndirectParametersBuffers,
) {
let mut build_indirect_parameters_bind_groups = BuildIndirectParametersBindGroups::new();
for (phase_type_id, phase_indirect_parameters_buffer) in indirect_parameters_buffers.iter() {
build_indirect_parameters_bind_groups.insert(
*phase_type_id,
PhaseBuildIndirectParametersBindGroups {
reset_indexed_indirect_batch_sets: match (phase_indirect_parameters_buffer
.indexed
.batch_sets_buffer(),)
{
(Some(indexed_batch_sets_buffer),) => Some(
render_device.create_bind_group(
"reset_indexed_indirect_batch_sets_bind_group",
// The early bind group is good for the main phase and late
// phase too. They bind the same buffers.
&pipelines
.early_phase
.reset_indirect_batch_sets
.bind_group_layout,
&BindGroupEntries::sequential((
indexed_batch_sets_buffer.as_entire_binding(),
)),
),
),
_ => None,
},
reset_non_indexed_indirect_batch_sets: match (phase_indirect_parameters_buffer
.non_indexed
.batch_sets_buffer(),)
{
(Some(non_indexed_batch_sets_buffer),) => Some(
render_device.create_bind_group(
"reset_non_indexed_indirect_batch_sets_bind_group",
// The early bind group is good for the main phase and late
// phase too. They bind the same buffers.
&pipelines
.early_phase
.reset_indirect_batch_sets
.bind_group_layout,
&BindGroupEntries::sequential((
non_indexed_batch_sets_buffer.as_entire_binding(),
)),
),
),
_ => None,
},
build_indexed_indirect: match (
phase_indirect_parameters_buffer
.indexed
.cpu_metadata_buffer(),
phase_indirect_parameters_buffer
.indexed
.gpu_metadata_buffer(),
phase_indirect_parameters_buffer.indexed.data_buffer(),
phase_indirect_parameters_buffer.indexed.batch_sets_buffer(),
) {
(
Some(indexed_indirect_parameters_cpu_metadata_buffer),
Some(indexed_indirect_parameters_gpu_metadata_buffer),
Some(indexed_indirect_parameters_data_buffer),
Some(indexed_batch_sets_buffer),
) => Some(
render_device.create_bind_group(
"build_indexed_indirect_parameters_bind_group",
// The frustum culling bind group is good for occlusion culling
// too. They bind the same buffers.
&pipelines
.gpu_frustum_culling_build_indexed_indirect_params
.bind_group_layout,
&BindGroupEntries::sequential((
current_input_buffer.as_entire_binding(),
// Don't use `as_entire_binding` here; the shader reads
// the length and `RawBufferVec` overallocates.
BufferBinding {
buffer: indexed_indirect_parameters_cpu_metadata_buffer,
offset: 0,
size: NonZeroU64::new(
phase_indirect_parameters_buffer.indexed.batch_count()
as u64
* size_of::<IndirectParametersCpuMetadata>() as u64,
),
},
BufferBinding {
buffer: indexed_indirect_parameters_gpu_metadata_buffer,
offset: 0,
size: NonZeroU64::new(
phase_indirect_parameters_buffer.indexed.batch_count()
as u64
* size_of::<IndirectParametersGpuMetadata>() as u64,
),
},
indexed_batch_sets_buffer.as_entire_binding(),
indexed_indirect_parameters_data_buffer.as_entire_binding(),
)),
),
),
_ => None,
},
build_non_indexed_indirect: match (
phase_indirect_parameters_buffer
.non_indexed
.cpu_metadata_buffer(),
phase_indirect_parameters_buffer
.non_indexed
.gpu_metadata_buffer(),
phase_indirect_parameters_buffer.non_indexed.data_buffer(),
phase_indirect_parameters_buffer
.non_indexed
.batch_sets_buffer(),
) {
(
Some(non_indexed_indirect_parameters_cpu_metadata_buffer),
Some(non_indexed_indirect_parameters_gpu_metadata_buffer),
Some(non_indexed_indirect_parameters_data_buffer),
Some(non_indexed_batch_sets_buffer),
) => Some(
render_device.create_bind_group(
"build_non_indexed_indirect_parameters_bind_group",
// The frustum culling bind group is good for occlusion culling
// too. They bind the same buffers.
&pipelines
.gpu_frustum_culling_build_non_indexed_indirect_params
.bind_group_layout,
&BindGroupEntries::sequential((
current_input_buffer.as_entire_binding(),
// Don't use `as_entire_binding` here; the shader reads
// the length and `RawBufferVec` overallocates.
BufferBinding {
buffer: non_indexed_indirect_parameters_cpu_metadata_buffer,
offset: 0,
size: NonZeroU64::new(
phase_indirect_parameters_buffer.non_indexed.batch_count()
as u64
* size_of::<IndirectParametersCpuMetadata>() as u64,
),
},
BufferBinding {
buffer: non_indexed_indirect_parameters_gpu_metadata_buffer,
offset: 0,
size: NonZeroU64::new(
phase_indirect_parameters_buffer.non_indexed.batch_count()
as u64
* size_of::<IndirectParametersGpuMetadata>() as u64,
),
},
non_indexed_batch_sets_buffer.as_entire_binding(),
non_indexed_indirect_parameters_data_buffer.as_entire_binding(),
)),
),
),
_ => None,
},
},
);
}
commands.insert_resource(build_indirect_parameters_bind_groups);
}
/// Writes the information needed to do GPU mesh culling to the GPU.
pub fn write_mesh_culling_data_buffer(
render_device: Res<RenderDevice>,
render_queue: Res<RenderQueue>,
mut mesh_culling_data_buffer: ResMut<MeshCullingDataBuffer>,
) {
mesh_culling_data_buffer.write_buffer(&render_device, &render_queue);
}