Path: blob/main/crates/bevy_render/src/batching/gpu_preprocessing.rs
6596 views
//! Batching functionality when GPU preprocessing is in use.12use core::{any::TypeId, marker::PhantomData, mem};34use bevy_app::{App, Plugin};5use bevy_derive::{Deref, DerefMut};6use bevy_ecs::{7prelude::Entity,8query::{Has, With},9resource::Resource,10schedule::IntoScheduleConfigs as _,11system::{Query, Res, ResMut, StaticSystemParam},12world::{FromWorld, World},13};14use bevy_encase_derive::ShaderType;15use bevy_math::UVec4;16use bevy_platform::collections::{hash_map::Entry, HashMap, HashSet};17use bevy_utils::{default, TypeIdMap};18use bytemuck::{Pod, Zeroable};19use encase::{internal::WriteInto, ShaderSize};20use indexmap::IndexMap;21use nonmax::NonMaxU32;22use tracing::{error, info};23use wgpu::{BindingResource, BufferUsages, DownlevelFlags, Features};2425use crate::{26experimental::occlusion_culling::OcclusionCulling,27render_phase::{28BinnedPhaseItem, BinnedRenderPhaseBatch, BinnedRenderPhaseBatchSet,29BinnedRenderPhaseBatchSets, CachedRenderPipelinePhaseItem, PhaseItem,30PhaseItemBatchSetKey as _, PhaseItemExtraIndex, RenderBin, SortedPhaseItem,31SortedRenderPhase, UnbatchableBinnedEntityIndices, ViewBinnedRenderPhases,32ViewSortedRenderPhases,33},34render_resource::{Buffer, GpuArrayBufferable, RawBufferVec, UninitBufferVec},35renderer::{RenderAdapter, RenderAdapterInfo, RenderDevice, RenderQueue, WgpuWrapper},36sync_world::MainEntity,37view::{ExtractedView, NoIndirectDrawing, RetainedViewEntity},38Render, RenderApp, RenderDebugFlags, RenderSystems,39};4041use super::{BatchMeta, GetBatchData, GetFullBatchData};4243#[derive(Default)]44pub struct BatchingPlugin {45/// Debugging flags that can optionally be set when constructing the renderer.46pub debug_flags: RenderDebugFlags,47}4849impl Plugin for BatchingPlugin {50fn build(&self, app: &mut App) {51let Some(render_app) = app.get_sub_app_mut(RenderApp) else {52return;53};5455render_app56.insert_resource(IndirectParametersBuffers::new(57self.debug_flags58.contains(RenderDebugFlags::ALLOW_COPIES_FROM_INDIRECT_PARAMETERS),59))60.add_systems(61Render,62write_indirect_parameters_buffers.in_set(RenderSystems::PrepareResourcesFlush),63)64.add_systems(65Render,66clear_indirect_parameters_buffers.in_set(RenderSystems::ManageViews),67);68}6970fn finish(&self, app: &mut App) {71let Some(render_app) = app.get_sub_app_mut(RenderApp) else {72return;73};7475render_app.init_resource::<GpuPreprocessingSupport>();76}77}7879/// Records whether GPU preprocessing and/or GPU culling are supported on the80/// device.81///82/// No GPU preprocessing is supported on WebGL because of the lack of compute83/// shader support. GPU preprocessing is supported on DirectX 12, but due to [a84/// `wgpu` limitation] GPU culling is not.85///86/// [a `wgpu` limitation]: https://github.com/gfx-rs/wgpu/issues/247187#[derive(Clone, Copy, PartialEq, Resource)]88pub struct GpuPreprocessingSupport {89/// The maximum amount of GPU preprocessing available on this platform.90pub max_supported_mode: GpuPreprocessingMode,91}9293impl GpuPreprocessingSupport {94/// Returns true if this GPU preprocessing support level isn't `None`.95#[inline]96pub fn is_available(&self) -> bool {97self.max_supported_mode != GpuPreprocessingMode::None98}99100/// Returns the given GPU preprocessing mode, capped to the current101/// preprocessing mode.102pub fn min(&self, mode: GpuPreprocessingMode) -> GpuPreprocessingMode {103match (self.max_supported_mode, mode) {104(GpuPreprocessingMode::None, _) | (_, GpuPreprocessingMode::None) => {105GpuPreprocessingMode::None106}107(mode, GpuPreprocessingMode::Culling) | (GpuPreprocessingMode::Culling, mode) => mode,108(GpuPreprocessingMode::PreprocessingOnly, GpuPreprocessingMode::PreprocessingOnly) => {109GpuPreprocessingMode::PreprocessingOnly110}111}112}113114/// Returns true if GPU culling is supported on this platform.115pub fn is_culling_supported(&self) -> bool {116self.max_supported_mode == GpuPreprocessingMode::Culling117}118}119120/// The amount of GPU preprocessing (compute and indirect draw) that we do.121#[derive(Clone, Copy, PartialEq)]122pub enum GpuPreprocessingMode {123/// No GPU preprocessing is in use at all.124///125/// This is used when GPU compute isn't available.126None,127128/// GPU preprocessing is in use, but GPU culling isn't.129///130/// This is used when the [`NoIndirectDrawing`] component is present on the131/// camera.132PreprocessingOnly,133134/// Both GPU preprocessing and GPU culling are in use.135///136/// This is used by default.137Culling,138}139140/// The GPU buffers holding the data needed to render batches.141///142/// For example, in the 3D PBR pipeline this holds `MeshUniform`s, which are the143/// `BD` type parameter in that mode.144///145/// We have a separate *buffer data input* type (`BDI`) here, which a compute146/// shader is expected to expand to the full buffer data (`BD`) type. GPU147/// uniform building is generally faster and uses less system RAM to VRAM bus148/// bandwidth, but only implemented for some pipelines (for example, not in the149/// 2D pipeline at present) and only when compute shader is available.150#[derive(Resource)]151pub struct BatchedInstanceBuffers<BD, BDI>152where153BD: GpuArrayBufferable + Sync + Send + 'static,154BDI: Pod + Default,155{156/// The uniform data inputs for the current frame.157///158/// These are uploaded during the extraction phase.159pub current_input_buffer: InstanceInputUniformBuffer<BDI>,160161/// The uniform data inputs for the previous frame.162///163/// The indices don't generally line up between `current_input_buffer`164/// and `previous_input_buffer`, because, among other reasons, entities165/// can spawn or despawn between frames. Instead, each current buffer166/// data input uniform is expected to contain the index of the167/// corresponding buffer data input uniform in this list.168pub previous_input_buffer: InstanceInputUniformBuffer<BDI>,169170/// The data needed to render buffers for each phase.171///172/// The keys of this map are the type IDs of each phase: e.g. `Opaque3d`,173/// `AlphaMask3d`, etc.174pub phase_instance_buffers: TypeIdMap<UntypedPhaseBatchedInstanceBuffers<BD>>,175}176177impl<BD, BDI> Default for BatchedInstanceBuffers<BD, BDI>178where179BD: GpuArrayBufferable + Sync + Send + 'static,180BDI: Pod + Sync + Send + Default + 'static,181{182fn default() -> Self {183BatchedInstanceBuffers {184current_input_buffer: InstanceInputUniformBuffer::new(),185previous_input_buffer: InstanceInputUniformBuffer::new(),186phase_instance_buffers: HashMap::default(),187}188}189}190191/// The GPU buffers holding the data needed to render batches for a single192/// phase.193///194/// These are split out per phase so that we can run the phases in parallel.195/// This is the version of the structure that has a type parameter, which196/// enables Bevy's scheduler to run the batching operations for the different197/// phases in parallel.198///199/// See the documentation for [`BatchedInstanceBuffers`] for more information.200#[derive(Resource)]201pub struct PhaseBatchedInstanceBuffers<PI, BD>202where203PI: PhaseItem,204BD: GpuArrayBufferable + Sync + Send + 'static,205{206/// The buffers for this phase.207pub buffers: UntypedPhaseBatchedInstanceBuffers<BD>,208phantom: PhantomData<PI>,209}210211impl<PI, BD> Default for PhaseBatchedInstanceBuffers<PI, BD>212where213PI: PhaseItem,214BD: GpuArrayBufferable + Sync + Send + 'static,215{216fn default() -> Self {217PhaseBatchedInstanceBuffers {218buffers: UntypedPhaseBatchedInstanceBuffers::default(),219phantom: PhantomData,220}221}222}223224/// The GPU buffers holding the data needed to render batches for a single225/// phase, without a type parameter for that phase.226///227/// Since this structure doesn't have a type parameter, it can be placed in228/// [`BatchedInstanceBuffers::phase_instance_buffers`].229pub struct UntypedPhaseBatchedInstanceBuffers<BD>230where231BD: GpuArrayBufferable + Sync + Send + 'static,232{233/// A storage area for the buffer data that the GPU compute shader is234/// expected to write to.235///236/// There will be one entry for each index.237pub data_buffer: UninitBufferVec<BD>,238239/// The index of the buffer data in the current input buffer that240/// corresponds to each instance.241///242/// This is keyed off each view. Each view has a separate buffer.243pub work_item_buffers: HashMap<RetainedViewEntity, PreprocessWorkItemBuffers>,244245/// A buffer that holds the number of indexed meshes that weren't visible in246/// the previous frame, when GPU occlusion culling is in use.247///248/// There's one set of [`LatePreprocessWorkItemIndirectParameters`] per249/// view. Bevy uses this value to determine how many threads to dispatch to250/// check meshes that weren't visible next frame to see if they became newly251/// visible this frame.252pub late_indexed_indirect_parameters_buffer:253RawBufferVec<LatePreprocessWorkItemIndirectParameters>,254255/// A buffer that holds the number of non-indexed meshes that weren't256/// visible in the previous frame, when GPU occlusion culling is in use.257///258/// There's one set of [`LatePreprocessWorkItemIndirectParameters`] per259/// view. Bevy uses this value to determine how many threads to dispatch to260/// check meshes that weren't visible next frame to see if they became newly261/// visible this frame.262pub late_non_indexed_indirect_parameters_buffer:263RawBufferVec<LatePreprocessWorkItemIndirectParameters>,264}265266/// Holds the GPU buffer of instance input data, which is the data about each267/// mesh instance that the CPU provides.268///269/// `BDI` is the *buffer data input* type, which the GPU mesh preprocessing270/// shader is expected to expand to the full *buffer data* type.271pub struct InstanceInputUniformBuffer<BDI>272where273BDI: Pod + Default,274{275/// The buffer containing the data that will be uploaded to the GPU.276buffer: RawBufferVec<BDI>,277278/// Indices of slots that are free within the buffer.279///280/// When adding data, we preferentially overwrite these slots first before281/// growing the buffer itself.282free_uniform_indices: Vec<u32>,283}284285impl<BDI> InstanceInputUniformBuffer<BDI>286where287BDI: Pod + Default,288{289/// Creates a new, empty buffer.290pub fn new() -> InstanceInputUniformBuffer<BDI> {291InstanceInputUniformBuffer {292buffer: RawBufferVec::new(BufferUsages::STORAGE),293free_uniform_indices: vec![],294}295}296297/// Clears the buffer and entity list out.298pub fn clear(&mut self) {299self.buffer.clear();300self.free_uniform_indices.clear();301}302303/// Returns the [`RawBufferVec`] corresponding to this input uniform buffer.304#[inline]305pub fn buffer(&self) -> &RawBufferVec<BDI> {306&self.buffer307}308309/// Adds a new piece of buffered data to the uniform buffer and returns its310/// index.311pub fn add(&mut self, element: BDI) -> u32 {312match self.free_uniform_indices.pop() {313Some(uniform_index) => {314self.buffer.values_mut()[uniform_index as usize] = element;315uniform_index316}317None => self.buffer.push(element) as u32,318}319}320321/// Removes a piece of buffered data from the uniform buffer.322///323/// This simply marks the data as free.324pub fn remove(&mut self, uniform_index: u32) {325self.free_uniform_indices.push(uniform_index);326}327328/// Returns the piece of buffered data at the given index.329///330/// Returns [`None`] if the index is out of bounds or the data is removed.331pub fn get(&self, uniform_index: u32) -> Option<BDI> {332if (uniform_index as usize) >= self.buffer.len()333|| self.free_uniform_indices.contains(&uniform_index)334{335None336} else {337Some(self.get_unchecked(uniform_index))338}339}340341/// Returns the piece of buffered data at the given index.342/// Can return data that has previously been removed.343///344/// # Panics345/// if `uniform_index` is not in bounds of [`Self::buffer`].346pub fn get_unchecked(&self, uniform_index: u32) -> BDI {347self.buffer.values()[uniform_index as usize]348}349350/// Stores a piece of buffered data at the given index.351///352/// # Panics353/// if `uniform_index` is not in bounds of [`Self::buffer`].354pub fn set(&mut self, uniform_index: u32, element: BDI) {355self.buffer.values_mut()[uniform_index as usize] = element;356}357358// Ensures that the buffers are nonempty, which the GPU requires before an359// upload can take place.360pub fn ensure_nonempty(&mut self) {361if self.buffer.is_empty() {362self.buffer.push(default());363}364}365366/// Returns the number of instances in this buffer.367pub fn len(&self) -> usize {368self.buffer.len()369}370371/// Returns true if this buffer has no instances or false if it contains any372/// instances.373pub fn is_empty(&self) -> bool {374self.buffer.is_empty()375}376377/// Consumes this [`InstanceInputUniformBuffer`] and returns the raw buffer378/// ready to be uploaded to the GPU.379pub fn into_buffer(self) -> RawBufferVec<BDI> {380self.buffer381}382}383384impl<BDI> Default for InstanceInputUniformBuffer<BDI>385where386BDI: Pod + Default,387{388fn default() -> Self {389Self::new()390}391}392393/// The buffer of GPU preprocessing work items for a single view.394#[cfg_attr(395not(target_arch = "wasm32"),396expect(397clippy::large_enum_variant,398reason = "See https://github.com/bevyengine/bevy/issues/19220"399)400)]401pub enum PreprocessWorkItemBuffers {402/// The work items we use if we aren't using indirect drawing.403///404/// Because we don't have to separate indexed from non-indexed meshes in405/// direct mode, we only have a single buffer here.406Direct(RawBufferVec<PreprocessWorkItem>),407408/// The buffer of work items we use if we are using indirect drawing.409///410/// We need to separate out indexed meshes from non-indexed meshes in this411/// case because the indirect parameters for these two types of meshes have412/// different sizes.413Indirect {414/// The buffer of work items corresponding to indexed meshes.415indexed: RawBufferVec<PreprocessWorkItem>,416/// The buffer of work items corresponding to non-indexed meshes.417non_indexed: RawBufferVec<PreprocessWorkItem>,418/// The work item buffers we use when GPU occlusion culling is in use.419gpu_occlusion_culling: Option<GpuOcclusionCullingWorkItemBuffers>,420},421}422423/// The work item buffers we use when GPU occlusion culling is in use.424pub struct GpuOcclusionCullingWorkItemBuffers {425/// The buffer of work items corresponding to indexed meshes.426pub late_indexed: UninitBufferVec<PreprocessWorkItem>,427/// The buffer of work items corresponding to non-indexed meshes.428pub late_non_indexed: UninitBufferVec<PreprocessWorkItem>,429/// The offset into the430/// [`UntypedPhaseBatchedInstanceBuffers::late_indexed_indirect_parameters_buffer`]431/// where this view's indirect dispatch counts for indexed meshes live.432pub late_indirect_parameters_indexed_offset: u32,433/// The offset into the434/// [`UntypedPhaseBatchedInstanceBuffers::late_non_indexed_indirect_parameters_buffer`]435/// where this view's indirect dispatch counts for non-indexed meshes live.436pub late_indirect_parameters_non_indexed_offset: u32,437}438439/// A GPU-side data structure that stores the number of workgroups to dispatch440/// for the second phase of GPU occlusion culling.441///442/// The late mesh preprocessing phase checks meshes that weren't visible frame443/// to determine if they're potentially visible this frame.444#[derive(Clone, Copy, ShaderType, Pod, Zeroable)]445#[repr(C)]446pub struct LatePreprocessWorkItemIndirectParameters {447/// The number of workgroups to dispatch.448///449/// This will be equal to `work_item_count / 64`, rounded *up*.450dispatch_x: u32,451/// The number of workgroups along the abstract Y axis to dispatch: always452/// 1.453dispatch_y: u32,454/// The number of workgroups along the abstract Z axis to dispatch: always455/// 1.456dispatch_z: u32,457/// The actual number of work items.458///459/// The GPU indirect dispatch doesn't read this, but it's used internally to460/// determine the actual number of work items that exist in the late461/// preprocessing work item buffer.462work_item_count: u32,463/// Padding to 64-byte boundaries for some hardware.464pad: UVec4,465}466467impl Default for LatePreprocessWorkItemIndirectParameters {468fn default() -> LatePreprocessWorkItemIndirectParameters {469LatePreprocessWorkItemIndirectParameters {470dispatch_x: 0,471dispatch_y: 1,472dispatch_z: 1,473work_item_count: 0,474pad: default(),475}476}477}478479/// Returns the set of work item buffers for the given view, first creating it480/// if necessary.481///482/// Bevy uses work item buffers to tell the mesh preprocessing compute shader483/// which meshes are to be drawn.484///485/// You may need to call this function if you're implementing your own custom486/// render phases. See the `specialized_mesh_pipeline` example.487pub fn get_or_create_work_item_buffer<'a, I>(488work_item_buffers: &'a mut HashMap<RetainedViewEntity, PreprocessWorkItemBuffers>,489view: RetainedViewEntity,490no_indirect_drawing: bool,491enable_gpu_occlusion_culling: bool,492) -> &'a mut PreprocessWorkItemBuffers493where494I: 'static,495{496let preprocess_work_item_buffers = match work_item_buffers.entry(view) {497Entry::Occupied(occupied_entry) => occupied_entry.into_mut(),498Entry::Vacant(vacant_entry) => {499if no_indirect_drawing {500vacant_entry.insert(PreprocessWorkItemBuffers::Direct(RawBufferVec::new(501BufferUsages::STORAGE,502)))503} else {504vacant_entry.insert(PreprocessWorkItemBuffers::Indirect {505indexed: RawBufferVec::new(BufferUsages::STORAGE),506non_indexed: RawBufferVec::new(BufferUsages::STORAGE),507// We fill this in below if `enable_gpu_occlusion_culling`508// is set.509gpu_occlusion_culling: None,510})511}512}513};514515// Initialize the GPU occlusion culling buffers if necessary.516if let PreprocessWorkItemBuffers::Indirect {517ref mut gpu_occlusion_culling,518..519} = *preprocess_work_item_buffers520{521match (522enable_gpu_occlusion_culling,523gpu_occlusion_culling.is_some(),524) {525(false, false) | (true, true) => {}526(false, true) => {527*gpu_occlusion_culling = None;528}529(true, false) => {530*gpu_occlusion_culling = Some(GpuOcclusionCullingWorkItemBuffers {531late_indexed: UninitBufferVec::new(BufferUsages::STORAGE),532late_non_indexed: UninitBufferVec::new(BufferUsages::STORAGE),533late_indirect_parameters_indexed_offset: 0,534late_indirect_parameters_non_indexed_offset: 0,535});536}537}538}539540preprocess_work_item_buffers541}542543/// Initializes work item buffers for a phase in preparation for a new frame.544pub fn init_work_item_buffers(545work_item_buffers: &mut PreprocessWorkItemBuffers,546late_indexed_indirect_parameters_buffer: &'_ mut RawBufferVec<547LatePreprocessWorkItemIndirectParameters,548>,549late_non_indexed_indirect_parameters_buffer: &'_ mut RawBufferVec<550LatePreprocessWorkItemIndirectParameters,551>,552) {553// Add the offsets for indirect parameters that the late phase of mesh554// preprocessing writes to.555if let PreprocessWorkItemBuffers::Indirect {556gpu_occlusion_culling:557Some(GpuOcclusionCullingWorkItemBuffers {558ref mut late_indirect_parameters_indexed_offset,559ref mut late_indirect_parameters_non_indexed_offset,560..561}),562..563} = *work_item_buffers564{565*late_indirect_parameters_indexed_offset = late_indexed_indirect_parameters_buffer566.push(LatePreprocessWorkItemIndirectParameters::default())567as u32;568*late_indirect_parameters_non_indexed_offset = late_non_indexed_indirect_parameters_buffer569.push(LatePreprocessWorkItemIndirectParameters::default())570as u32;571}572}573574impl PreprocessWorkItemBuffers {575/// Adds a new work item to the appropriate buffer.576///577/// `indexed` specifies whether the work item corresponds to an indexed578/// mesh.579pub fn push(&mut self, indexed: bool, preprocess_work_item: PreprocessWorkItem) {580match *self {581PreprocessWorkItemBuffers::Direct(ref mut buffer) => {582buffer.push(preprocess_work_item);583}584PreprocessWorkItemBuffers::Indirect {585indexed: ref mut indexed_buffer,586non_indexed: ref mut non_indexed_buffer,587ref mut gpu_occlusion_culling,588} => {589if indexed {590indexed_buffer.push(preprocess_work_item);591} else {592non_indexed_buffer.push(preprocess_work_item);593}594595if let Some(ref mut gpu_occlusion_culling) = *gpu_occlusion_culling {596if indexed {597gpu_occlusion_culling.late_indexed.add();598} else {599gpu_occlusion_culling.late_non_indexed.add();600}601}602}603}604}605606/// Clears out the GPU work item buffers in preparation for a new frame.607pub fn clear(&mut self) {608match *self {609PreprocessWorkItemBuffers::Direct(ref mut buffer) => {610buffer.clear();611}612PreprocessWorkItemBuffers::Indirect {613indexed: ref mut indexed_buffer,614non_indexed: ref mut non_indexed_buffer,615ref mut gpu_occlusion_culling,616} => {617indexed_buffer.clear();618non_indexed_buffer.clear();619620if let Some(ref mut gpu_occlusion_culling) = *gpu_occlusion_culling {621gpu_occlusion_culling.late_indexed.clear();622gpu_occlusion_culling.late_non_indexed.clear();623gpu_occlusion_culling.late_indirect_parameters_indexed_offset = 0;624gpu_occlusion_culling.late_indirect_parameters_non_indexed_offset = 0;625}626}627}628}629}630631/// One invocation of the preprocessing shader: i.e. one mesh instance in a632/// view.633#[derive(Clone, Copy, Default, Pod, Zeroable, ShaderType)]634#[repr(C)]635pub struct PreprocessWorkItem {636/// The index of the batch input data in the input buffer that the shader637/// reads from.638pub input_index: u32,639640/// In direct mode, the index of the mesh uniform; in indirect mode, the641/// index of the [`IndirectParametersGpuMetadata`].642///643/// In indirect mode, this is the index of the644/// [`IndirectParametersGpuMetadata`] in the645/// `IndirectParametersBuffers::indexed_metadata` or646/// `IndirectParametersBuffers::non_indexed_metadata`.647pub output_or_indirect_parameters_index: u32,648}649650/// The `wgpu` indirect parameters structure that specifies a GPU draw command.651///652/// This is the variant for indexed meshes. We generate the instances of this653/// structure in the `build_indirect_params.wgsl` compute shader.654#[derive(Clone, Copy, Debug, Pod, Zeroable, ShaderType)]655#[repr(C)]656pub struct IndirectParametersIndexed {657/// The number of indices that this mesh has.658pub index_count: u32,659/// The number of instances we are to draw.660pub instance_count: u32,661/// The offset of the first index for this mesh in the index buffer slab.662pub first_index: u32,663/// The offset of the first vertex for this mesh in the vertex buffer slab.664pub base_vertex: u32,665/// The index of the first mesh instance in the `MeshUniform` buffer.666pub first_instance: u32,667}668669/// The `wgpu` indirect parameters structure that specifies a GPU draw command.670///671/// This is the variant for non-indexed meshes. We generate the instances of672/// this structure in the `build_indirect_params.wgsl` compute shader.673#[derive(Clone, Copy, Debug, Pod, Zeroable, ShaderType)]674#[repr(C)]675pub struct IndirectParametersNonIndexed {676/// The number of vertices that this mesh has.677pub vertex_count: u32,678/// The number of instances we are to draw.679pub instance_count: u32,680/// The offset of the first vertex for this mesh in the vertex buffer slab.681pub base_vertex: u32,682/// The index of the first mesh instance in the `Mesh` buffer.683pub first_instance: u32,684}685686/// A structure, initialized on CPU and read on GPU, that contains metadata687/// about each batch.688///689/// Each batch will have one instance of this structure.690#[derive(Clone, Copy, Default, Pod, Zeroable, ShaderType)]691#[repr(C)]692pub struct IndirectParametersCpuMetadata {693/// The index of the first instance of this mesh in the array of694/// `MeshUniform`s.695///696/// Note that this is the *first* output index in this batch. Since each697/// instance of this structure refers to arbitrarily many instances, the698/// `MeshUniform`s corresponding to this batch span the indices699/// `base_output_index..(base_output_index + instance_count)`.700pub base_output_index: u32,701702/// The index of the batch set that this batch belongs to in the703/// [`IndirectBatchSet`] buffer.704///705/// A *batch set* is a set of meshes that may be multi-drawn together.706/// Multiple batches (and therefore multiple instances of707/// [`IndirectParametersGpuMetadata`] structures) can be part of the same708/// batch set.709pub batch_set_index: u32,710}711712/// A structure, written and read GPU, that records how many instances of each713/// mesh are actually to be drawn.714///715/// The GPU mesh preprocessing shader increments the716/// [`Self::early_instance_count`] and [`Self::late_instance_count`] as it717/// determines that meshes are visible. The indirect parameter building shader718/// reads this metadata in order to construct the indirect draw parameters.719///720/// Each batch will have one instance of this structure.721#[derive(Clone, Copy, Default, Pod, Zeroable, ShaderType)]722#[repr(C)]723pub struct IndirectParametersGpuMetadata {724/// The index of the first mesh in this batch in the array of725/// `MeshInputUniform`s.726pub mesh_index: u32,727728/// The number of instances that were judged visible last frame.729///730/// The CPU sets this value to 0, and the GPU mesh preprocessing shader731/// increments it as it culls mesh instances.732pub early_instance_count: u32,733734/// The number of instances that have been judged potentially visible this735/// frame that weren't in the last frame's potentially visible set.736///737/// The CPU sets this value to 0, and the GPU mesh preprocessing shader738/// increments it as it culls mesh instances.739pub late_instance_count: u32,740}741742/// A structure, shared between CPU and GPU, that holds the number of on-GPU743/// indirect draw commands for each *batch set*.744///745/// A *batch set* is a set of meshes that may be multi-drawn together.746///747/// If the current hardware and driver support `multi_draw_indirect_count`, the748/// indirect parameters building shader increments749/// [`Self::indirect_parameters_count`] as it generates indirect parameters. The750/// `multi_draw_indirect_count` command reads751/// [`Self::indirect_parameters_count`] in order to determine how many commands752/// belong to each batch set.753#[derive(Clone, Copy, Default, Pod, Zeroable, ShaderType)]754#[repr(C)]755pub struct IndirectBatchSet {756/// The number of indirect parameter commands (i.e. batches) in this batch757/// set.758///759/// The CPU sets this value to 0 before uploading this structure to GPU. The760/// indirect parameters building shader increments this value as it creates761/// indirect parameters. Then the `multi_draw_indirect_count` command reads762/// this value in order to determine how many indirect draw commands to763/// process.764pub indirect_parameters_count: u32,765766/// The offset within the `IndirectParametersBuffers::indexed_data` or767/// `IndirectParametersBuffers::non_indexed_data` of the first indirect draw768/// command for this batch set.769///770/// The CPU fills out this value.771pub indirect_parameters_base: u32,772}773774/// The buffers containing all the information that indirect draw commands775/// (`multi_draw_indirect`, `multi_draw_indirect_count`) use to draw the scene.776///777/// In addition to the indirect draw buffers themselves, this structure contains778/// the buffers that store [`IndirectParametersGpuMetadata`], which are the779/// structures that culling writes to so that the indirect parameter building780/// pass can determine how many meshes are actually to be drawn.781///782/// These buffers will remain empty if indirect drawing isn't in use.783#[derive(Resource, Deref, DerefMut)]784pub struct IndirectParametersBuffers {785/// A mapping from a phase type ID to the indirect parameters buffers for786/// that phase.787///788/// Examples of phase type IDs are `Opaque3d` and `AlphaMask3d`.789#[deref]790pub buffers: TypeIdMap<UntypedPhaseIndirectParametersBuffers>,791/// If true, this sets the `COPY_SRC` flag on indirect draw parameters so792/// that they can be read back to CPU.793///794/// This is a debugging feature that may reduce performance. It primarily795/// exists for the `occlusion_culling` example.796pub allow_copies_from_indirect_parameter_buffers: bool,797}798799impl IndirectParametersBuffers {800/// Initializes a new [`IndirectParametersBuffers`] resource.801pub fn new(allow_copies_from_indirect_parameter_buffers: bool) -> IndirectParametersBuffers {802IndirectParametersBuffers {803buffers: TypeIdMap::default(),804allow_copies_from_indirect_parameter_buffers,805}806}807}808809/// The buffers containing all the information that indirect draw commands use810/// to draw the scene, for a single phase.811///812/// This is the version of the structure that has a type parameter, so that the813/// batching for different phases can run in parallel.814///815/// See the [`IndirectParametersBuffers`] documentation for more information.816#[derive(Resource)]817pub struct PhaseIndirectParametersBuffers<PI>818where819PI: PhaseItem,820{821/// The indirect draw buffers for the phase.822pub buffers: UntypedPhaseIndirectParametersBuffers,823phantom: PhantomData<PI>,824}825826impl<PI> PhaseIndirectParametersBuffers<PI>827where828PI: PhaseItem,829{830pub fn new(allow_copies_from_indirect_parameter_buffers: bool) -> Self {831PhaseIndirectParametersBuffers {832buffers: UntypedPhaseIndirectParametersBuffers::new(833allow_copies_from_indirect_parameter_buffers,834),835phantom: PhantomData,836}837}838}839840/// The buffers containing all the information that indirect draw commands use841/// to draw the scene, for a single phase.842///843/// This is the version of the structure that doesn't have a type parameter, so844/// that it can be inserted into [`IndirectParametersBuffers::buffers`]845///846/// See the [`IndirectParametersBuffers`] documentation for more information.847pub struct UntypedPhaseIndirectParametersBuffers {848/// Information that indirect draw commands use to draw indexed meshes in849/// the scene.850pub indexed: MeshClassIndirectParametersBuffers<IndirectParametersIndexed>,851/// Information that indirect draw commands use to draw non-indexed meshes852/// in the scene.853pub non_indexed: MeshClassIndirectParametersBuffers<IndirectParametersNonIndexed>,854}855856impl UntypedPhaseIndirectParametersBuffers {857/// Creates the indirect parameters buffers.858pub fn new(859allow_copies_from_indirect_parameter_buffers: bool,860) -> UntypedPhaseIndirectParametersBuffers {861let mut indirect_parameter_buffer_usages = BufferUsages::STORAGE | BufferUsages::INDIRECT;862if allow_copies_from_indirect_parameter_buffers {863indirect_parameter_buffer_usages |= BufferUsages::COPY_SRC;864}865866UntypedPhaseIndirectParametersBuffers {867non_indexed: MeshClassIndirectParametersBuffers::new(868allow_copies_from_indirect_parameter_buffers,869),870indexed: MeshClassIndirectParametersBuffers::new(871allow_copies_from_indirect_parameter_buffers,872),873}874}875876/// Reserves space for `count` new batches.877///878/// The `indexed` parameter specifies whether the meshes that these batches879/// correspond to are indexed or not.880pub fn allocate(&mut self, indexed: bool, count: u32) -> u32 {881if indexed {882self.indexed.allocate(count)883} else {884self.non_indexed.allocate(count)885}886}887888/// Returns the number of batches currently allocated.889///890/// The `indexed` parameter specifies whether the meshes that these batches891/// correspond to are indexed or not.892fn batch_count(&self, indexed: bool) -> usize {893if indexed {894self.indexed.batch_count()895} else {896self.non_indexed.batch_count()897}898}899900/// Returns the number of batch sets currently allocated.901///902/// The `indexed` parameter specifies whether the meshes that these batch903/// sets correspond to are indexed or not.904pub fn batch_set_count(&self, indexed: bool) -> usize {905if indexed {906self.indexed.batch_sets.len()907} else {908self.non_indexed.batch_sets.len()909}910}911912/// Adds a new batch set to `Self::indexed_batch_sets` or913/// `Self::non_indexed_batch_sets` as appropriate.914///915/// `indexed` specifies whether the meshes that these batch sets correspond916/// to are indexed or not. `indirect_parameters_base` specifies the offset917/// within `Self::indexed_data` or `Self::non_indexed_data` of the first918/// batch in this batch set.919#[inline]920pub fn add_batch_set(&mut self, indexed: bool, indirect_parameters_base: u32) {921if indexed {922self.indexed.batch_sets.push(IndirectBatchSet {923indirect_parameters_base,924indirect_parameters_count: 0,925});926} else {927self.non_indexed.batch_sets.push(IndirectBatchSet {928indirect_parameters_base,929indirect_parameters_count: 0,930});931}932}933934/// Returns the index that a newly-added batch set will have.935///936/// The `indexed` parameter specifies whether the meshes in such a batch set937/// are indexed or not.938pub fn get_next_batch_set_index(&self, indexed: bool) -> Option<NonMaxU32> {939NonMaxU32::new(self.batch_set_count(indexed) as u32)940}941942/// Clears out the buffers in preparation for a new frame.943pub fn clear(&mut self) {944self.indexed.clear();945self.non_indexed.clear();946}947}948949/// The buffers containing all the information that indirect draw commands use950/// to draw the scene, for a single mesh class (indexed or non-indexed), for a951/// single phase.952pub struct MeshClassIndirectParametersBuffers<IP>953where954IP: Clone + ShaderSize + WriteInto,955{956/// The GPU buffer that stores the indirect draw parameters for the meshes.957///958/// The indirect parameters building shader writes to this buffer, while the959/// `multi_draw_indirect` or `multi_draw_indirect_count` commands read from960/// it to perform the draws.961data: UninitBufferVec<IP>,962963/// The GPU buffer that holds the data used to construct indirect draw964/// parameters for meshes.965///966/// The GPU mesh preprocessing shader writes to this buffer, and the967/// indirect parameters building shader reads this buffer to construct the968/// indirect draw parameters.969cpu_metadata: RawBufferVec<IndirectParametersCpuMetadata>,970971/// The GPU buffer that holds data built by the GPU used to construct972/// indirect draw parameters for meshes.973///974/// The GPU mesh preprocessing shader writes to this buffer, and the975/// indirect parameters building shader reads this buffer to construct the976/// indirect draw parameters.977gpu_metadata: UninitBufferVec<IndirectParametersGpuMetadata>,978979/// The GPU buffer that holds the number of indirect draw commands for each980/// phase of each view, for meshes.981///982/// The indirect parameters building shader writes to this buffer, and the983/// `multi_draw_indirect_count` command reads from it in order to know how984/// many indirect draw commands to process.985batch_sets: RawBufferVec<IndirectBatchSet>,986}987988impl<IP> MeshClassIndirectParametersBuffers<IP>989where990IP: Clone + ShaderSize + WriteInto,991{992fn new(993allow_copies_from_indirect_parameter_buffers: bool,994) -> MeshClassIndirectParametersBuffers<IP> {995let mut indirect_parameter_buffer_usages = BufferUsages::STORAGE | BufferUsages::INDIRECT;996if allow_copies_from_indirect_parameter_buffers {997indirect_parameter_buffer_usages |= BufferUsages::COPY_SRC;998}9991000MeshClassIndirectParametersBuffers {1001data: UninitBufferVec::new(indirect_parameter_buffer_usages),1002cpu_metadata: RawBufferVec::new(BufferUsages::STORAGE),1003gpu_metadata: UninitBufferVec::new(BufferUsages::STORAGE),1004batch_sets: RawBufferVec::new(indirect_parameter_buffer_usages),1005}1006}10071008/// Returns the GPU buffer that stores the indirect draw parameters for1009/// indexed meshes.1010///1011/// The indirect parameters building shader writes to this buffer, while the1012/// `multi_draw_indirect` or `multi_draw_indirect_count` commands read from1013/// it to perform the draws.1014#[inline]1015pub fn data_buffer(&self) -> Option<&Buffer> {1016self.data.buffer()1017}10181019/// Returns the GPU buffer that holds the CPU-constructed data used to1020/// construct indirect draw parameters for meshes.1021///1022/// The CPU writes to this buffer, and the indirect parameters building1023/// shader reads this buffer to construct the indirect draw parameters.1024#[inline]1025pub fn cpu_metadata_buffer(&self) -> Option<&Buffer> {1026self.cpu_metadata.buffer()1027}10281029/// Returns the GPU buffer that holds the GPU-constructed data used to1030/// construct indirect draw parameters for meshes.1031///1032/// The GPU mesh preprocessing shader writes to this buffer, and the1033/// indirect parameters building shader reads this buffer to construct the1034/// indirect draw parameters.1035#[inline]1036pub fn gpu_metadata_buffer(&self) -> Option<&Buffer> {1037self.gpu_metadata.buffer()1038}10391040/// Returns the GPU buffer that holds the number of indirect draw commands1041/// for each phase of each view.1042///1043/// The indirect parameters building shader writes to this buffer, and the1044/// `multi_draw_indirect_count` command reads from it in order to know how1045/// many indirect draw commands to process.1046#[inline]1047pub fn batch_sets_buffer(&self) -> Option<&Buffer> {1048self.batch_sets.buffer()1049}10501051/// Reserves space for `count` new batches.1052///1053/// This allocates in the [`Self::cpu_metadata`], [`Self::gpu_metadata`],1054/// and [`Self::data`] buffers.1055fn allocate(&mut self, count: u32) -> u32 {1056let length = self.data.len();1057self.cpu_metadata.reserve_internal(count as usize);1058self.gpu_metadata.add_multiple(count as usize);1059for _ in 0..count {1060self.data.add();1061self.cpu_metadata1062.push(IndirectParametersCpuMetadata::default());1063}1064length as u321065}10661067/// Sets the [`IndirectParametersCpuMetadata`] for the mesh at the given1068/// index.1069pub fn set(&mut self, index: u32, value: IndirectParametersCpuMetadata) {1070self.cpu_metadata.set(index, value);1071}10721073/// Returns the number of batches corresponding to meshes that are currently1074/// allocated.1075#[inline]1076pub fn batch_count(&self) -> usize {1077self.data.len()1078}10791080/// Clears out all the buffers in preparation for a new frame.1081pub fn clear(&mut self) {1082self.data.clear();1083self.cpu_metadata.clear();1084self.gpu_metadata.clear();1085self.batch_sets.clear();1086}1087}10881089impl Default for IndirectParametersBuffers {1090fn default() -> Self {1091// By default, we don't allow GPU indirect parameter mapping, since1092// that's a debugging option.1093Self::new(false)1094}1095}10961097impl FromWorld for GpuPreprocessingSupport {1098fn from_world(world: &mut World) -> Self {1099let adapter = world.resource::<RenderAdapter>();1100let device = world.resource::<RenderDevice>();11011102// Filter Android drivers that are incompatible with GPU preprocessing:1103// - We filter out Adreno 730 and earlier GPUs (except 720, as it's newer1104// than 730).1105// - We filter out Mali GPUs with driver versions lower than 48.1106fn is_non_supported_android_device(adapter_info: &RenderAdapterInfo) -> bool {1107crate::get_adreno_model(adapter_info).is_some_and(|model| model != 720 && model <= 730)1108|| crate::get_mali_driver_version(adapter_info).is_some_and(|version| version < 48)1109}11101111let culling_feature_support = device.features().contains(1112Features::INDIRECT_FIRST_INSTANCE1113| Features::MULTI_DRAW_INDIRECT1114| Features::PUSH_CONSTANTS,1115);1116// Depth downsampling for occlusion culling requires 12 textures1117let limit_support = device.limits().max_storage_textures_per_shader_stage >= 12 &&1118// Even if the adapter supports compute, we might be simulating a lack of1119// compute via device limits (see `WgpuSettingsPriority::WebGL2` and1120// `wgpu::Limits::downlevel_webgl2_defaults()`). This will have set all the1121// `max_compute_*` limits to zero, so we arbitrarily pick one as a canary.1122device.limits().max_compute_workgroup_storage_size != 0;11231124let downlevel_support = adapter1125.get_downlevel_capabilities()1126.flags1127.contains(DownlevelFlags::COMPUTE_SHADERS);11281129let adapter_info = RenderAdapterInfo(WgpuWrapper::new(adapter.get_info()));11301131let max_supported_mode = if device.limits().max_compute_workgroup_size_x == 01132|| is_non_supported_android_device(&adapter_info)1133|| adapter_info.backend == wgpu::Backend::Gl1134{1135info!(1136"GPU preprocessing is not supported on this device. \1137Falling back to CPU preprocessing.",1138);1139GpuPreprocessingMode::None1140} else if !(culling_feature_support && limit_support && downlevel_support) {1141info!("Some GPU preprocessing are limited on this device.");1142GpuPreprocessingMode::PreprocessingOnly1143} else {1144info!("GPU preprocessing is fully supported on this device.");1145GpuPreprocessingMode::Culling1146};11471148GpuPreprocessingSupport { max_supported_mode }1149}1150}11511152impl<BD, BDI> BatchedInstanceBuffers<BD, BDI>1153where1154BD: GpuArrayBufferable + Sync + Send + 'static,1155BDI: Pod + Sync + Send + Default + 'static,1156{1157/// Creates new buffers.1158pub fn new() -> Self {1159Self::default()1160}11611162/// Clears out the buffers in preparation for a new frame.1163pub fn clear(&mut self) {1164for phase_instance_buffer in self.phase_instance_buffers.values_mut() {1165phase_instance_buffer.clear();1166}1167}1168}11691170impl<BD> UntypedPhaseBatchedInstanceBuffers<BD>1171where1172BD: GpuArrayBufferable + Sync + Send + 'static,1173{1174pub fn new() -> Self {1175UntypedPhaseBatchedInstanceBuffers {1176data_buffer: UninitBufferVec::new(BufferUsages::STORAGE),1177work_item_buffers: HashMap::default(),1178late_indexed_indirect_parameters_buffer: RawBufferVec::new(1179BufferUsages::STORAGE | BufferUsages::INDIRECT,1180),1181late_non_indexed_indirect_parameters_buffer: RawBufferVec::new(1182BufferUsages::STORAGE | BufferUsages::INDIRECT,1183),1184}1185}11861187/// Returns the binding of the buffer that contains the per-instance data.1188///1189/// This buffer needs to be filled in via a compute shader.1190pub fn instance_data_binding(&self) -> Option<BindingResource<'_>> {1191self.data_buffer1192.buffer()1193.map(|buffer| buffer.as_entire_binding())1194}11951196/// Clears out the buffers in preparation for a new frame.1197pub fn clear(&mut self) {1198self.data_buffer.clear();1199self.late_indexed_indirect_parameters_buffer.clear();1200self.late_non_indexed_indirect_parameters_buffer.clear();12011202// Clear each individual set of buffers, but don't depopulate the hash1203// table. We want to avoid reallocating these vectors every frame.1204for view_work_item_buffers in self.work_item_buffers.values_mut() {1205view_work_item_buffers.clear();1206}1207}1208}12091210impl<BD> Default for UntypedPhaseBatchedInstanceBuffers<BD>1211where1212BD: GpuArrayBufferable + Sync + Send + 'static,1213{1214fn default() -> Self {1215Self::new()1216}1217}12181219/// Information about a render batch that we're building up during a sorted1220/// render phase.1221struct SortedRenderBatch<F>1222where1223F: GetBatchData,1224{1225/// The index of the first phase item in this batch in the list of phase1226/// items.1227phase_item_start_index: u32,12281229/// The index of the first instance in this batch in the instance buffer.1230instance_start_index: u32,12311232/// True if the mesh in question has an index buffer; false otherwise.1233indexed: bool,12341235/// The index of the indirect parameters for this batch in the1236/// [`IndirectParametersBuffers`].1237///1238/// If CPU culling is being used, then this will be `None`.1239indirect_parameters_index: Option<NonMaxU32>,12401241/// Metadata that can be used to determine whether an instance can be placed1242/// into this batch.1243///1244/// If `None`, the item inside is unbatchable.1245meta: Option<BatchMeta<F::CompareData>>,1246}12471248impl<F> SortedRenderBatch<F>1249where1250F: GetBatchData,1251{1252/// Finalizes this batch and updates the [`SortedRenderPhase`] with the1253/// appropriate indices.1254///1255/// `instance_end_index` is the index of the last instance in this batch1256/// plus one.1257fn flush<I>(1258self,1259instance_end_index: u32,1260phase: &mut SortedRenderPhase<I>,1261phase_indirect_parameters_buffers: &mut UntypedPhaseIndirectParametersBuffers,1262) where1263I: CachedRenderPipelinePhaseItem + SortedPhaseItem,1264{1265let (batch_range, batch_extra_index) =1266phase.items[self.phase_item_start_index as usize].batch_range_and_extra_index_mut();1267*batch_range = self.instance_start_index..instance_end_index;1268*batch_extra_index = match self.indirect_parameters_index {1269Some(indirect_parameters_index) => PhaseItemExtraIndex::IndirectParametersIndex {1270range: u32::from(indirect_parameters_index)1271..(u32::from(indirect_parameters_index) + 1),1272batch_set_index: None,1273},1274None => PhaseItemExtraIndex::None,1275};1276if let Some(indirect_parameters_index) = self.indirect_parameters_index {1277phase_indirect_parameters_buffers1278.add_batch_set(self.indexed, indirect_parameters_index.into());1279}1280}1281}12821283/// A system that runs early in extraction and clears out all the1284/// [`BatchedInstanceBuffers`] for the frame.1285///1286/// We have to run this during extraction because, if GPU preprocessing is in1287/// use, the extraction phase will write to the mesh input uniform buffers1288/// directly, so the buffers need to be cleared before then.1289pub fn clear_batched_gpu_instance_buffers<GFBD>(1290gpu_batched_instance_buffers: Option<1291ResMut<BatchedInstanceBuffers<GFBD::BufferData, GFBD::BufferInputData>>,1292>,1293) where1294GFBD: GetFullBatchData,1295{1296// Don't clear the entire table, because that would delete the buffers, and1297// we want to reuse those allocations.1298if let Some(mut gpu_batched_instance_buffers) = gpu_batched_instance_buffers {1299gpu_batched_instance_buffers.clear();1300}1301}13021303/// A system that removes GPU preprocessing work item buffers that correspond to1304/// deleted [`ExtractedView`]s.1305///1306/// This is a separate system from [`clear_batched_gpu_instance_buffers`]1307/// because [`ExtractedView`]s aren't created until after the extraction phase1308/// is completed.1309pub fn delete_old_work_item_buffers<GFBD>(1310mut gpu_batched_instance_buffers: ResMut<1311BatchedInstanceBuffers<GFBD::BufferData, GFBD::BufferInputData>,1312>,1313extracted_views: Query<&ExtractedView>,1314) where1315GFBD: GetFullBatchData,1316{1317let retained_view_entities: HashSet<_> = extracted_views1318.iter()1319.map(|extracted_view| extracted_view.retained_view_entity)1320.collect();1321for phase_instance_buffers in gpu_batched_instance_buffers1322.phase_instance_buffers1323.values_mut()1324{1325phase_instance_buffers1326.work_item_buffers1327.retain(|retained_view_entity, _| {1328retained_view_entities.contains(retained_view_entity)1329});1330}1331}13321333/// Batch the items in a sorted render phase, when GPU instance buffer building1334/// is in use. This means comparing metadata needed to draw each phase item and1335/// trying to combine the draws into a batch.1336pub fn batch_and_prepare_sorted_render_phase<I, GFBD>(1337mut phase_batched_instance_buffers: ResMut<PhaseBatchedInstanceBuffers<I, GFBD::BufferData>>,1338mut phase_indirect_parameters_buffers: ResMut<PhaseIndirectParametersBuffers<I>>,1339mut sorted_render_phases: ResMut<ViewSortedRenderPhases<I>>,1340mut views: Query<(1341&ExtractedView,1342Has<NoIndirectDrawing>,1343Has<OcclusionCulling>,1344)>,1345system_param_item: StaticSystemParam<GFBD::Param>,1346) where1347I: CachedRenderPipelinePhaseItem + SortedPhaseItem,1348GFBD: GetFullBatchData,1349{1350// We only process GPU-built batch data in this function.1351let UntypedPhaseBatchedInstanceBuffers {1352ref mut data_buffer,1353ref mut work_item_buffers,1354ref mut late_indexed_indirect_parameters_buffer,1355ref mut late_non_indexed_indirect_parameters_buffer,1356} = phase_batched_instance_buffers.buffers;13571358for (extracted_view, no_indirect_drawing, gpu_occlusion_culling) in &mut views {1359let Some(phase) = sorted_render_phases.get_mut(&extracted_view.retained_view_entity) else {1360continue;1361};13621363// Create the work item buffer if necessary.1364let work_item_buffer = get_or_create_work_item_buffer::<I>(1365work_item_buffers,1366extracted_view.retained_view_entity,1367no_indirect_drawing,1368gpu_occlusion_culling,1369);13701371// Initialize those work item buffers in preparation for this new frame.1372init_work_item_buffers(1373work_item_buffer,1374late_indexed_indirect_parameters_buffer,1375late_non_indexed_indirect_parameters_buffer,1376);13771378// Walk through the list of phase items, building up batches as we go.1379let mut batch: Option<SortedRenderBatch<GFBD>> = None;13801381for current_index in 0..phase.items.len() {1382// Get the index of the input data, and comparison metadata, for1383// this entity.1384let item = &phase.items[current_index];1385let entity = item.main_entity();1386let item_is_indexed = item.indexed();1387let current_batch_input_index =1388GFBD::get_index_and_compare_data(&system_param_item, entity);13891390// Unpack that index and metadata. Note that it's possible for index1391// and/or metadata to not be present, which signifies that this1392// entity is unbatchable. In that case, we break the batch here.1393// If the index isn't present the item is not part of this pipeline and so will be skipped.1394let Some((current_input_index, current_meta)) = current_batch_input_index else {1395// Break a batch if we need to.1396if let Some(batch) = batch.take() {1397batch.flush(1398data_buffer.len() as u32,1399phase,1400&mut phase_indirect_parameters_buffers.buffers,1401);1402}14031404continue;1405};1406let current_meta =1407current_meta.map(|meta| BatchMeta::new(&phase.items[current_index], meta));14081409// Determine if this entity can be included in the batch we're1410// building up.1411let can_batch = batch.as_ref().is_some_and(|batch| {1412// `None` for metadata indicates that the items are unbatchable.1413match (¤t_meta, &batch.meta) {1414(Some(current_meta), Some(batch_meta)) => current_meta == batch_meta,1415(_, _) => false,1416}1417});14181419// Make space in the data buffer for this instance.1420let output_index = data_buffer.add() as u32;14211422// If we can't batch, break the existing batch and make a new one.1423if !can_batch {1424// Break a batch if we need to.1425if let Some(batch) = batch.take() {1426batch.flush(1427output_index,1428phase,1429&mut phase_indirect_parameters_buffers.buffers,1430);1431}14321433let indirect_parameters_index = if no_indirect_drawing {1434None1435} else if item_is_indexed {1436Some(1437phase_indirect_parameters_buffers1438.buffers1439.indexed1440.allocate(1),1441)1442} else {1443Some(1444phase_indirect_parameters_buffers1445.buffers1446.non_indexed1447.allocate(1),1448)1449};14501451// Start a new batch.1452if let Some(indirect_parameters_index) = indirect_parameters_index {1453GFBD::write_batch_indirect_parameters_metadata(1454item_is_indexed,1455output_index,1456None,1457&mut phase_indirect_parameters_buffers.buffers,1458indirect_parameters_index,1459);1460};14611462batch = Some(SortedRenderBatch {1463phase_item_start_index: current_index as u32,1464instance_start_index: output_index,1465indexed: item_is_indexed,1466indirect_parameters_index: indirect_parameters_index.and_then(NonMaxU32::new),1467meta: current_meta,1468});1469}14701471// Add a new preprocessing work item so that the preprocessing1472// shader will copy the per-instance data over.1473if let Some(batch) = batch.as_ref() {1474work_item_buffer.push(1475item_is_indexed,1476PreprocessWorkItem {1477input_index: current_input_index.into(),1478output_or_indirect_parameters_index: match (1479no_indirect_drawing,1480batch.indirect_parameters_index,1481) {1482(true, _) => output_index,1483(false, Some(indirect_parameters_index)) => {1484indirect_parameters_index.into()1485}1486(false, None) => 0,1487},1488},1489);1490}1491}14921493// Flush the final batch if necessary.1494if let Some(batch) = batch.take() {1495batch.flush(1496data_buffer.len() as u32,1497phase,1498&mut phase_indirect_parameters_buffers.buffers,1499);1500}1501}1502}15031504/// Creates batches for a render phase that uses bins.1505pub fn batch_and_prepare_binned_render_phase<BPI, GFBD>(1506mut phase_batched_instance_buffers: ResMut<PhaseBatchedInstanceBuffers<BPI, GFBD::BufferData>>,1507phase_indirect_parameters_buffers: ResMut<PhaseIndirectParametersBuffers<BPI>>,1508mut binned_render_phases: ResMut<ViewBinnedRenderPhases<BPI>>,1509mut views: Query<1510(1511&ExtractedView,1512Has<NoIndirectDrawing>,1513Has<OcclusionCulling>,1514),1515With<ExtractedView>,1516>,1517param: StaticSystemParam<GFBD::Param>,1518) where1519BPI: BinnedPhaseItem,1520GFBD: GetFullBatchData,1521{1522let system_param_item = param.into_inner();15231524let phase_indirect_parameters_buffers = phase_indirect_parameters_buffers.into_inner();15251526let UntypedPhaseBatchedInstanceBuffers {1527ref mut data_buffer,1528ref mut work_item_buffers,1529ref mut late_indexed_indirect_parameters_buffer,1530ref mut late_non_indexed_indirect_parameters_buffer,1531} = phase_batched_instance_buffers.buffers;15321533for (extracted_view, no_indirect_drawing, gpu_occlusion_culling) in &mut views {1534let Some(phase) = binned_render_phases.get_mut(&extracted_view.retained_view_entity) else {1535continue;1536};15371538// Create the work item buffer if necessary; otherwise, just mark it as1539// used this frame.1540let work_item_buffer = get_or_create_work_item_buffer::<BPI>(1541work_item_buffers,1542extracted_view.retained_view_entity,1543no_indirect_drawing,1544gpu_occlusion_culling,1545);15461547// Initialize those work item buffers in preparation for this new frame.1548init_work_item_buffers(1549work_item_buffer,1550late_indexed_indirect_parameters_buffer,1551late_non_indexed_indirect_parameters_buffer,1552);15531554// Prepare multidrawables.15551556if let (1557&mut BinnedRenderPhaseBatchSets::MultidrawIndirect(ref mut batch_sets),1558&mut PreprocessWorkItemBuffers::Indirect {1559indexed: ref mut indexed_work_item_buffer,1560non_indexed: ref mut non_indexed_work_item_buffer,1561gpu_occlusion_culling: ref mut gpu_occlusion_culling_buffers,1562},1563) = (&mut phase.batch_sets, &mut *work_item_buffer)1564{1565let mut output_index = data_buffer.len() as u32;15661567// Initialize the state for both indexed and non-indexed meshes.1568let mut indexed_preparer: MultidrawableBatchSetPreparer<BPI, GFBD> =1569MultidrawableBatchSetPreparer::new(1570phase_indirect_parameters_buffers.buffers.batch_count(true) as u32,1571phase_indirect_parameters_buffers1572.buffers1573.indexed1574.batch_sets1575.len() as u32,1576);1577let mut non_indexed_preparer: MultidrawableBatchSetPreparer<BPI, GFBD> =1578MultidrawableBatchSetPreparer::new(1579phase_indirect_parameters_buffers.buffers.batch_count(false) as u32,1580phase_indirect_parameters_buffers1581.buffers1582.non_indexed1583.batch_sets1584.len() as u32,1585);15861587// Prepare each batch set.1588for (batch_set_key, bins) in &phase.multidrawable_meshes {1589if batch_set_key.indexed() {1590indexed_preparer.prepare_multidrawable_binned_batch_set(1591bins,1592&mut output_index,1593data_buffer,1594indexed_work_item_buffer,1595&mut phase_indirect_parameters_buffers.buffers.indexed,1596batch_sets,1597);1598} else {1599non_indexed_preparer.prepare_multidrawable_binned_batch_set(1600bins,1601&mut output_index,1602data_buffer,1603non_indexed_work_item_buffer,1604&mut phase_indirect_parameters_buffers.buffers.non_indexed,1605batch_sets,1606);1607}1608}16091610// Reserve space in the occlusion culling buffers, if necessary.1611if let Some(gpu_occlusion_culling_buffers) = gpu_occlusion_culling_buffers {1612gpu_occlusion_culling_buffers1613.late_indexed1614.add_multiple(indexed_preparer.work_item_count);1615gpu_occlusion_culling_buffers1616.late_non_indexed1617.add_multiple(non_indexed_preparer.work_item_count);1618}1619}16201621// Prepare batchables.16221623for (key, bin) in &phase.batchable_meshes {1624let mut batch: Option<BinnedRenderPhaseBatch> = None;1625for (&main_entity, &input_index) in bin.entities() {1626let output_index = data_buffer.add() as u32;16271628match batch {1629Some(ref mut batch) => {1630batch.instance_range.end = output_index + 1;16311632// Append to the current batch.1633//1634// If we're in indirect mode, then we write the first1635// output index of this batch, so that we have a1636// tightly-packed buffer if GPU culling discards some of1637// the instances. Otherwise, we can just write the1638// output index directly.1639work_item_buffer.push(1640key.0.indexed(),1641PreprocessWorkItem {1642input_index: *input_index,1643output_or_indirect_parameters_index: match (1644no_indirect_drawing,1645&batch.extra_index,1646) {1647(true, _) => output_index,1648(1649false,1650PhaseItemExtraIndex::IndirectParametersIndex {1651range: indirect_parameters_range,1652..1653},1654) => indirect_parameters_range.start,1655(false, &PhaseItemExtraIndex::DynamicOffset(_))1656| (false, &PhaseItemExtraIndex::None) => 0,1657},1658},1659);1660}16611662None if !no_indirect_drawing => {1663// Start a new batch, in indirect mode.1664let indirect_parameters_index = phase_indirect_parameters_buffers1665.buffers1666.allocate(key.0.indexed(), 1);1667let batch_set_index = phase_indirect_parameters_buffers1668.buffers1669.get_next_batch_set_index(key.0.indexed());16701671GFBD::write_batch_indirect_parameters_metadata(1672key.0.indexed(),1673output_index,1674batch_set_index,1675&mut phase_indirect_parameters_buffers.buffers,1676indirect_parameters_index,1677);1678work_item_buffer.push(1679key.0.indexed(),1680PreprocessWorkItem {1681input_index: *input_index,1682output_or_indirect_parameters_index: indirect_parameters_index,1683},1684);1685batch = Some(BinnedRenderPhaseBatch {1686representative_entity: (Entity::PLACEHOLDER, main_entity),1687instance_range: output_index..output_index + 1,1688extra_index: PhaseItemExtraIndex::IndirectParametersIndex {1689range: indirect_parameters_index..(indirect_parameters_index + 1),1690batch_set_index: None,1691},1692});1693}16941695None => {1696// Start a new batch, in direct mode.1697work_item_buffer.push(1698key.0.indexed(),1699PreprocessWorkItem {1700input_index: *input_index,1701output_or_indirect_parameters_index: output_index,1702},1703);1704batch = Some(BinnedRenderPhaseBatch {1705representative_entity: (Entity::PLACEHOLDER, main_entity),1706instance_range: output_index..output_index + 1,1707extra_index: PhaseItemExtraIndex::None,1708});1709}1710}1711}17121713if let Some(batch) = batch {1714match phase.batch_sets {1715BinnedRenderPhaseBatchSets::DynamicUniforms(_) => {1716error!("Dynamic uniform batch sets shouldn't be used here");1717}1718BinnedRenderPhaseBatchSets::Direct(ref mut vec) => {1719vec.push(batch);1720}1721BinnedRenderPhaseBatchSets::MultidrawIndirect(ref mut vec) => {1722// The Bevy renderer will never mark a mesh as batchable1723// but not multidrawable if multidraw is in use.1724// However, custom render pipelines might do so, such as1725// the `specialized_mesh_pipeline` example.1726vec.push(BinnedRenderPhaseBatchSet {1727first_batch: batch,1728batch_count: 1,1729bin_key: key.1.clone(),1730index: phase_indirect_parameters_buffers1731.buffers1732.batch_set_count(key.0.indexed())1733as u32,1734});1735}1736}1737}1738}17391740// Prepare unbatchables.1741for (key, unbatchables) in &mut phase.unbatchable_meshes {1742// Allocate the indirect parameters if necessary.1743let mut indirect_parameters_offset = if no_indirect_drawing {1744None1745} else if key.0.indexed() {1746Some(1747phase_indirect_parameters_buffers1748.buffers1749.indexed1750.allocate(unbatchables.entities.len() as u32),1751)1752} else {1753Some(1754phase_indirect_parameters_buffers1755.buffers1756.non_indexed1757.allocate(unbatchables.entities.len() as u32),1758)1759};17601761for main_entity in unbatchables.entities.keys() {1762let Some(input_index) = GFBD::get_binned_index(&system_param_item, *main_entity)1763else {1764continue;1765};1766let output_index = data_buffer.add() as u32;17671768if let Some(ref mut indirect_parameters_index) = indirect_parameters_offset {1769// We're in indirect mode, so add an indirect parameters1770// index.1771GFBD::write_batch_indirect_parameters_metadata(1772key.0.indexed(),1773output_index,1774None,1775&mut phase_indirect_parameters_buffers.buffers,1776*indirect_parameters_index,1777);1778work_item_buffer.push(1779key.0.indexed(),1780PreprocessWorkItem {1781input_index: input_index.into(),1782output_or_indirect_parameters_index: *indirect_parameters_index,1783},1784);1785unbatchables1786.buffer_indices1787.add(UnbatchableBinnedEntityIndices {1788instance_index: *indirect_parameters_index,1789extra_index: PhaseItemExtraIndex::IndirectParametersIndex {1790range: *indirect_parameters_index..(*indirect_parameters_index + 1),1791batch_set_index: None,1792},1793});1794phase_indirect_parameters_buffers1795.buffers1796.add_batch_set(key.0.indexed(), *indirect_parameters_index);1797*indirect_parameters_index += 1;1798} else {1799work_item_buffer.push(1800key.0.indexed(),1801PreprocessWorkItem {1802input_index: input_index.into(),1803output_or_indirect_parameters_index: output_index,1804},1805);1806unbatchables1807.buffer_indices1808.add(UnbatchableBinnedEntityIndices {1809instance_index: output_index,1810extra_index: PhaseItemExtraIndex::None,1811});1812}1813}1814}1815}1816}18171818/// The state that [`batch_and_prepare_binned_render_phase`] uses to construct1819/// multidrawable batch sets.1820///1821/// The [`batch_and_prepare_binned_render_phase`] system maintains two of these:1822/// one for indexed meshes and one for non-indexed meshes.1823struct MultidrawableBatchSetPreparer<BPI, GFBD>1824where1825BPI: BinnedPhaseItem,1826GFBD: GetFullBatchData,1827{1828/// The offset in the indirect parameters buffer at which the next indirect1829/// parameters will be written.1830indirect_parameters_index: u32,1831/// The number of batch sets we've built so far for this mesh class.1832batch_set_index: u32,1833/// The number of work items we've emitted so far for this mesh class.1834work_item_count: usize,1835phantom: PhantomData<(BPI, GFBD)>,1836}18371838impl<BPI, GFBD> MultidrawableBatchSetPreparer<BPI, GFBD>1839where1840BPI: BinnedPhaseItem,1841GFBD: GetFullBatchData,1842{1843/// Creates a new [`MultidrawableBatchSetPreparer`] that will start writing1844/// indirect parameters and batch sets at the given indices.1845#[inline]1846fn new(initial_indirect_parameters_index: u32, initial_batch_set_index: u32) -> Self {1847MultidrawableBatchSetPreparer {1848indirect_parameters_index: initial_indirect_parameters_index,1849batch_set_index: initial_batch_set_index,1850work_item_count: 0,1851phantom: PhantomData,1852}1853}18541855/// Creates batch sets and writes the GPU data needed to draw all visible1856/// entities of one mesh class in the given batch set.1857///1858/// The *mesh class* represents whether the mesh has indices or not.1859#[inline]1860fn prepare_multidrawable_binned_batch_set<IP>(1861&mut self,1862bins: &IndexMap<BPI::BinKey, RenderBin>,1863output_index: &mut u32,1864data_buffer: &mut UninitBufferVec<GFBD::BufferData>,1865indexed_work_item_buffer: &mut RawBufferVec<PreprocessWorkItem>,1866mesh_class_buffers: &mut MeshClassIndirectParametersBuffers<IP>,1867batch_sets: &mut Vec<BinnedRenderPhaseBatchSet<BPI::BinKey>>,1868) where1869IP: Clone + ShaderSize + WriteInto,1870{1871let current_indexed_batch_set_index = self.batch_set_index;1872let current_output_index = *output_index;18731874let indirect_parameters_base = self.indirect_parameters_index;18751876// We're going to write the first entity into the batch set. Do this1877// here so that we can preload the bin into cache as a side effect.1878let Some((first_bin_key, first_bin)) = bins.iter().next() else {1879return;1880};1881let first_bin_len = first_bin.entities().len();1882let first_bin_entity = first_bin1883.entities()1884.keys()1885.next()1886.copied()1887.unwrap_or(MainEntity::from(Entity::PLACEHOLDER));18881889// Traverse the batch set, processing each bin.1890for bin in bins.values() {1891// Record the first output index for this batch, as well as its own1892// index.1893mesh_class_buffers1894.cpu_metadata1895.push(IndirectParametersCpuMetadata {1896base_output_index: *output_index,1897batch_set_index: self.batch_set_index,1898});18991900// Traverse the bin, pushing `PreprocessWorkItem`s for each entity1901// within it. This is a hot loop, so make it as fast as possible.1902for &input_index in bin.entities().values() {1903indexed_work_item_buffer.push(PreprocessWorkItem {1904input_index: *input_index,1905output_or_indirect_parameters_index: self.indirect_parameters_index,1906});1907}19081909// Reserve space for the appropriate number of entities in the data1910// buffer. Also, advance the output index and work item count.1911let bin_entity_count = bin.entities().len();1912data_buffer.add_multiple(bin_entity_count);1913*output_index += bin_entity_count as u32;1914self.work_item_count += bin_entity_count;19151916self.indirect_parameters_index += 1;1917}19181919// Reserve space for the bins in this batch set in the GPU buffers.1920let bin_count = bins.len();1921mesh_class_buffers.gpu_metadata.add_multiple(bin_count);1922mesh_class_buffers.data.add_multiple(bin_count);19231924// Write the information the GPU will need about this batch set.1925mesh_class_buffers.batch_sets.push(IndirectBatchSet {1926indirect_parameters_base,1927indirect_parameters_count: 0,1928});19291930self.batch_set_index += 1;19311932// Record the batch set. The render node later processes this record to1933// render the batches.1934batch_sets.push(BinnedRenderPhaseBatchSet {1935first_batch: BinnedRenderPhaseBatch {1936representative_entity: (Entity::PLACEHOLDER, first_bin_entity),1937instance_range: current_output_index..(current_output_index + first_bin_len as u32),1938extra_index: PhaseItemExtraIndex::maybe_indirect_parameters_index(NonMaxU32::new(1939indirect_parameters_base,1940)),1941},1942bin_key: (*first_bin_key).clone(),1943batch_count: self.indirect_parameters_index - indirect_parameters_base,1944index: current_indexed_batch_set_index,1945});1946}1947}19481949/// A system that gathers up the per-phase GPU buffers and inserts them into the1950/// [`BatchedInstanceBuffers`] and [`IndirectParametersBuffers`] tables.1951///1952/// This runs after the [`batch_and_prepare_binned_render_phase`] or1953/// [`batch_and_prepare_sorted_render_phase`] systems. It takes the per-phase1954/// [`PhaseBatchedInstanceBuffers`] and [`PhaseIndirectParametersBuffers`]1955/// resources and inserts them into the global [`BatchedInstanceBuffers`] and1956/// [`IndirectParametersBuffers`] tables.1957///1958/// This system exists so that the [`batch_and_prepare_binned_render_phase`] and1959/// [`batch_and_prepare_sorted_render_phase`] can run in parallel with one1960/// another. If those two systems manipulated [`BatchedInstanceBuffers`] and1961/// [`IndirectParametersBuffers`] directly, then they wouldn't be able to run in1962/// parallel.1963pub fn collect_buffers_for_phase<PI, GFBD>(1964mut phase_batched_instance_buffers: ResMut<PhaseBatchedInstanceBuffers<PI, GFBD::BufferData>>,1965mut phase_indirect_parameters_buffers: ResMut<PhaseIndirectParametersBuffers<PI>>,1966mut batched_instance_buffers: ResMut<1967BatchedInstanceBuffers<GFBD::BufferData, GFBD::BufferInputData>,1968>,1969mut indirect_parameters_buffers: ResMut<IndirectParametersBuffers>,1970) where1971PI: PhaseItem,1972GFBD: GetFullBatchData + Send + Sync + 'static,1973{1974// Insert the `PhaseBatchedInstanceBuffers` into the global table. Replace1975// the contents of the per-phase resource with the old batched instance1976// buffers in order to reuse allocations.1977let untyped_phase_batched_instance_buffers =1978mem::take(&mut phase_batched_instance_buffers.buffers);1979if let Some(mut old_untyped_phase_batched_instance_buffers) = batched_instance_buffers1980.phase_instance_buffers1981.insert(TypeId::of::<PI>(), untyped_phase_batched_instance_buffers)1982{1983old_untyped_phase_batched_instance_buffers.clear();1984phase_batched_instance_buffers.buffers = old_untyped_phase_batched_instance_buffers;1985}19861987// Insert the `PhaseIndirectParametersBuffers` into the global table.1988// Replace the contents of the per-phase resource with the old indirect1989// parameters buffers in order to reuse allocations.1990let untyped_phase_indirect_parameters_buffers = mem::replace(1991&mut phase_indirect_parameters_buffers.buffers,1992UntypedPhaseIndirectParametersBuffers::new(1993indirect_parameters_buffers.allow_copies_from_indirect_parameter_buffers,1994),1995);1996if let Some(mut old_untyped_phase_indirect_parameters_buffers) = indirect_parameters_buffers1997.insert(1998TypeId::of::<PI>(),1999untyped_phase_indirect_parameters_buffers,2000)2001{2002old_untyped_phase_indirect_parameters_buffers.clear();2003phase_indirect_parameters_buffers.buffers = old_untyped_phase_indirect_parameters_buffers;2004}2005}20062007/// A system that writes all instance buffers to the GPU.2008pub fn write_batched_instance_buffers<GFBD>(2009render_device: Res<RenderDevice>,2010render_queue: Res<RenderQueue>,2011gpu_array_buffer: ResMut<BatchedInstanceBuffers<GFBD::BufferData, GFBD::BufferInputData>>,2012) where2013GFBD: GetFullBatchData,2014{2015let BatchedInstanceBuffers {2016current_input_buffer,2017previous_input_buffer,2018phase_instance_buffers,2019} = gpu_array_buffer.into_inner();20202021current_input_buffer2022.buffer2023.write_buffer(&render_device, &render_queue);2024previous_input_buffer2025.buffer2026.write_buffer(&render_device, &render_queue);20272028for phase_instance_buffers in phase_instance_buffers.values_mut() {2029let UntypedPhaseBatchedInstanceBuffers {2030ref mut data_buffer,2031ref mut work_item_buffers,2032ref mut late_indexed_indirect_parameters_buffer,2033ref mut late_non_indexed_indirect_parameters_buffer,2034} = *phase_instance_buffers;20352036data_buffer.write_buffer(&render_device);2037late_indexed_indirect_parameters_buffer.write_buffer(&render_device, &render_queue);2038late_non_indexed_indirect_parameters_buffer.write_buffer(&render_device, &render_queue);20392040for phase_work_item_buffers in work_item_buffers.values_mut() {2041match *phase_work_item_buffers {2042PreprocessWorkItemBuffers::Direct(ref mut buffer_vec) => {2043buffer_vec.write_buffer(&render_device, &render_queue);2044}2045PreprocessWorkItemBuffers::Indirect {2046ref mut indexed,2047ref mut non_indexed,2048ref mut gpu_occlusion_culling,2049} => {2050indexed.write_buffer(&render_device, &render_queue);2051non_indexed.write_buffer(&render_device, &render_queue);20522053if let Some(GpuOcclusionCullingWorkItemBuffers {2054ref mut late_indexed,2055ref mut late_non_indexed,2056late_indirect_parameters_indexed_offset: _,2057late_indirect_parameters_non_indexed_offset: _,2058}) = *gpu_occlusion_culling2059{2060if !late_indexed.is_empty() {2061late_indexed.write_buffer(&render_device);2062}2063if !late_non_indexed.is_empty() {2064late_non_indexed.write_buffer(&render_device);2065}2066}2067}2068}2069}2070}2071}20722073pub fn clear_indirect_parameters_buffers(2074mut indirect_parameters_buffers: ResMut<IndirectParametersBuffers>,2075) {2076for phase_indirect_parameters_buffers in indirect_parameters_buffers.values_mut() {2077phase_indirect_parameters_buffers.clear();2078}2079}20802081pub fn write_indirect_parameters_buffers(2082render_device: Res<RenderDevice>,2083render_queue: Res<RenderQueue>,2084mut indirect_parameters_buffers: ResMut<IndirectParametersBuffers>,2085) {2086for phase_indirect_parameters_buffers in indirect_parameters_buffers.values_mut() {2087phase_indirect_parameters_buffers2088.indexed2089.data2090.write_buffer(&render_device);2091phase_indirect_parameters_buffers2092.non_indexed2093.data2094.write_buffer(&render_device);20952096phase_indirect_parameters_buffers2097.indexed2098.cpu_metadata2099.write_buffer(&render_device, &render_queue);2100phase_indirect_parameters_buffers2101.non_indexed2102.cpu_metadata2103.write_buffer(&render_device, &render_queue);21042105phase_indirect_parameters_buffers2106.non_indexed2107.gpu_metadata2108.write_buffer(&render_device);2109phase_indirect_parameters_buffers2110.indexed2111.gpu_metadata2112.write_buffer(&render_device);21132114phase_indirect_parameters_buffers2115.indexed2116.batch_sets2117.write_buffer(&render_device, &render_queue);2118phase_indirect_parameters_buffers2119.non_indexed2120.batch_sets2121.write_buffer(&render_device, &render_queue);2122}2123}21242125#[cfg(test)]2126mod tests {2127use super::*;21282129#[test]2130fn instance_buffer_correct_behavior() {2131let mut instance_buffer = InstanceInputUniformBuffer::new();21322133let index = instance_buffer.add(2);2134instance_buffer.remove(index);2135assert_eq!(instance_buffer.get_unchecked(index), 2);2136assert_eq!(instance_buffer.get(index), None);21372138instance_buffer.add(5);2139assert_eq!(instance_buffer.buffer().len(), 1);2140}2141}214221432144