Currently, invocations of `batch_and_prepare_binned_render_phase` and `batch_and_prepare_sorted_render_phase` can't run in parallel because they write to scene-global GPU buffers. After PR #17698, `batch_and_prepare_binned_render_phase` started accounting for the lion's share of the CPU time, causing us to be strongly CPU bound on scenes like Caldera when occlusion culling was on (because of the overhead of batching for the Z-prepass). Although I eventually plan to optimize `batch_and_prepare_binned_render_phase`, we can obtain significant wins now by parallelizing that system across phases. This commit splits all GPU buffers that `batch_and_prepare_binned_render_phase` and `batch_and_prepare_sorted_render_phase` touches into separate buffers for each phase so that the scheduler will run those phases in parallel. At the end of batch preparation, we gather the render phases up into a single resource with a new *collection* phase. Because we already run mesh preprocessing separately for each phase in order to make occlusion culling work, this is actually a cleaner separation. For example, mesh output indices (the unique ID that identifies each mesh instance on GPU) are now guaranteed to be sequential starting from 0, which will simplify the forthcoming work to remove them in favor of the compute dispatch ID. On Caldera, this brings the frame time down to approximately 9.1 ms with occlusion culling on. 
2540 lines
106 KiB
Rust
2540 lines
106 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::{
|
|
DeferredPrepass, DepthPrepass, PreviousViewData, PreviousViewUniformOffset,
|
|
PreviousViewUniforms,
|
|
},
|
|
};
|
|
use bevy_derive::{Deref, DerefMut};
|
|
use bevy_ecs::{
|
|
component::Component,
|
|
entity::Entity,
|
|
prelude::resource_exists,
|
|
query::{Has, QueryState, With, Without},
|
|
resource::Resource,
|
|
schedule::IntoSystemConfigs as _,
|
|
system::{lifetimeless::Read, Commands, Query, Res, ResMut},
|
|
world::{FromWorld, World},
|
|
};
|
|
use bevy_render::batching::gpu_preprocessing::UntypedPhaseIndirectParametersBuffers;
|
|
use bevy_render::{
|
|
batching::gpu_preprocessing::{
|
|
BatchedInstanceBuffers, GpuOcclusionCullingWorkItemBuffers, GpuPreprocessingSupport,
|
|
IndirectBatchSet, IndirectParametersBuffers, IndirectParametersIndexed,
|
|
IndirectParametersMetadata, 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,
|
|
BufferVec, 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::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 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>,
|
|
Without<DeferredPrepass>,
|
|
),
|
|
>,
|
|
}
|
|
|
|
/// 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
|
|
/// [`IndirectParametersMetadata`] into properly-formatted
|
|
/// [`IndirectParametersIndexed`] and [`IndirectParametersNonIndexed`].
|
|
pub struct EarlyPrepassBuildIndirectParametersNode {
|
|
view_query: QueryState<
|
|
Read<PreprocessBindGroups>,
|
|
(
|
|
Without<SkipGpuPreprocess>,
|
|
Without<NoIndirectDrawing>,
|
|
With<DepthPrepass>,
|
|
Without<DeferredPrepass>,
|
|
),
|
|
>,
|
|
}
|
|
|
|
/// 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
|
|
/// [`IndirectParametersMetadata`] into properly-formatted
|
|
/// [`IndirectParametersIndexed`] and [`IndirectParametersNonIndexed`].
|
|
pub struct LatePrepassBuildIndirectParametersNode {
|
|
view_query: QueryState<
|
|
Read<PreprocessBindGroups>,
|
|
(
|
|
Without<SkipGpuPreprocess>,
|
|
Without<NoIndirectDrawing>,
|
|
With<DepthPrepass>,
|
|
With<OcclusionCulling>,
|
|
Without<DeferredPrepass>,
|
|
),
|
|
>,
|
|
}
|
|
|
|
/// 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 [`IndirectParametersMetadata`] 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::<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::EarlyGpuPreprocess,
|
|
NodePbr::EarlyPrepassBuildIndirectParameters,
|
|
Node3d::EarlyPrepass,
|
|
Node3d::EarlyDownsampleDepth,
|
|
NodePbr::LateGpuPreprocess,
|
|
NodePbr::LatePrepassBuildIndirectParameters,
|
|
Node3d::LatePrepass,
|
|
NodePbr::MainBuildIndirectParameters,
|
|
// Shadows don't currently support occlusion culling, so we
|
|
// treat shadows as effectively the main phase for our
|
|
// purposes.
|
|
NodePbr::ShadowPass,
|
|
),
|
|
)
|
|
.add_render_graph_edge(
|
|
Core3d,
|
|
NodePbr::MainBuildIndirectParameters,
|
|
Node3d::DeferredPrepass,
|
|
);
|
|
}
|
|
}
|
|
|
|
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;
|
|
};
|
|
|
|
// If we're drawing indirectly, make sure the mesh preprocessing
|
|
// shader has access to the view info it needs to do culling.
|
|
let mut dynamic_offsets: SmallVec<[u32; 1]> = smallvec![];
|
|
if !no_indirect_drawing {
|
|
dynamic_offsets.push(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: ref maybe_late_indexed_bind_group,
|
|
late_non_indexed: ref 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,
|
|
(
|
|
// `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 3 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::<IndirectParametersMetadata>(false),
|
|
),
|
|
(2, 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`
|
|
(
|
|
7,
|
|
storage_buffer::<IndirectParametersMetadata>(/* has_dynamic_offset= */ false),
|
|
),
|
|
// `mesh_culling_data`
|
|
(
|
|
8,
|
|
storage_buffer_read_only::<MeshCullingData>(/* has_dynamic_offset= */ false),
|
|
),
|
|
// `view`
|
|
(
|
|
0,
|
|
uniform_buffer::<ViewUniform>(/* has_dynamic_offset= */ true),
|
|
),
|
|
))
|
|
}
|
|
|
|
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: ref current_input_buffer_vec,
|
|
previous_input_buffer: ref previous_input_buffer_vec,
|
|
ref 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: &BufferVec<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((
|
|
(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: &BufferVec<PreprocessWorkItem>,
|
|
non_indexed_work_item_buffer: &BufferVec<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: &BufferVec<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_metadata_buffer(),
|
|
indexed_work_item_buffer.buffer(),
|
|
late_indexed_work_item_buffer.buffer(),
|
|
self.late_indexed_indirect_parameters_buffer.buffer(),
|
|
) {
|
|
(
|
|
Some(indexed_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_metadata_buffer.as_entire_binding()),
|
|
(8, 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: &BufferVec<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_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_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_metadata_buffer.as_entire_binding()),
|
|
(8, 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_metadata_buffer(),
|
|
late_indexed_work_item_buffer.buffer(),
|
|
self.late_indexed_indirect_parameters_buffer.buffer(),
|
|
) {
|
|
(
|
|
Some(indexed_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_metadata_buffer.as_entire_binding()),
|
|
(8, 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_metadata_buffer(),
|
|
late_non_indexed_work_item_buffer.buffer(),
|
|
self.late_non_indexed_indirect_parameters_buffer.buffer(),
|
|
) {
|
|
(
|
|
Some(non_indexed_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_metadata_buffer.as_entire_binding()),
|
|
(8, 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: &BufferVec<PreprocessWorkItem>,
|
|
non_indexed_work_item_buffer: &BufferVec<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: &BufferVec<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_metadata_buffer(),
|
|
indexed_work_item_buffer.buffer(),
|
|
) {
|
|
(Some(indexed_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_metadata_buffer.as_entire_binding()),
|
|
(8, 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: &BufferVec<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_metadata_buffer(),
|
|
non_indexed_work_item_buffer.buffer(),
|
|
) {
|
|
(Some(non_indexed_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_metadata_buffer.as_entire_binding()),
|
|
(8, 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_metadata_buffer(),
|
|
phase_indirect_parameters_buffer.indexed_data_buffer(),
|
|
phase_indirect_parameters_buffer.indexed_batch_sets_buffer(),
|
|
) {
|
|
(
|
|
Some(indexed_indirect_parameters_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_metadata_buffer,
|
|
offset: 0,
|
|
size: NonZeroU64::new(
|
|
phase_indirect_parameters_buffer.indexed_batch_count()
|
|
as u64
|
|
* size_of::<IndirectParametersMetadata>() 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_metadata_buffer(),
|
|
phase_indirect_parameters_buffer.non_indexed_data_buffer(),
|
|
phase_indirect_parameters_buffer.non_indexed_batch_sets_buffer(),
|
|
) {
|
|
(
|
|
Some(non_indexed_indirect_parameters_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_metadata_buffer,
|
|
offset: 0,
|
|
size: NonZeroU64::new(
|
|
phase_indirect_parameters_buffer.non_indexed_batch_count()
|
|
as u64
|
|
* size_of::<IndirectParametersMetadata>() 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);
|
|
}
|