use bevy_ecs::{1component::Component,2entity::Entity,3system::{ResMut, SystemParam, SystemParamItem},4};5use bytemuck::Pod;6use gpu_preprocessing::UntypedPhaseIndirectParametersBuffers;7use nonmax::NonMaxU32;89use bevy_material::{descriptor::CachedRenderPipelineId, labels::DrawFunctionId};1011use crate::{12render_phase::{13BinnedPhaseItem, CachedRenderPipelinePhaseItem, PhaseItemExtraIndex, SortedPhaseItem,14SortedRenderPhase, ViewBinnedRenderPhases,15},16render_resource::GpuArrayBufferable,17sync_world::MainEntity,18};1920pub mod gpu_preprocessing;21pub mod no_gpu_preprocessing;2223/// Add this component to mesh entities to disable automatic batching24#[derive(Component, Default, Clone, Copy)]25pub struct NoAutomaticBatching;2627/// Data necessary to be equal for two draw commands to be mergeable28///29/// This is based on the following assumptions:30/// - Only entities with prepared assets (pipelines, materials, meshes) are31/// queued to phases32/// - View bindings are constant across a phase for a given draw function as33/// phases are per-view34/// - `batch_and_prepare_render_phase` is the only system that performs this35/// batching and has sole responsibility for preparing the per-object data.36/// As such the mesh binding and dynamic offsets are assumed to only be37/// variable as a result of the `batch_and_prepare_render_phase` system, e.g.38/// due to having to split data across separate uniform bindings within the39/// same buffer due to the maximum uniform buffer binding size.40#[derive(PartialEq)]41struct BatchMeta<T: PartialEq> {42/// The pipeline id encompasses all pipeline configuration including vertex43/// buffers and layouts, shaders and their specializations, bind group44/// layouts, etc.45pipeline_id: CachedRenderPipelineId,46/// The draw function id defines the `RenderCommands` that are called to47/// set the pipeline and bindings, and make the draw command48draw_function_id: DrawFunctionId,49dynamic_offset: Option<NonMaxU32>,50user_data: T,51}5253impl<T: PartialEq> BatchMeta<T> {54fn new(item: &impl CachedRenderPipelinePhaseItem, user_data: T) -> Self {55BatchMeta {56pipeline_id: item.cached_pipeline(),57draw_function_id: item.draw_function(),58dynamic_offset: match item.extra_index() {59PhaseItemExtraIndex::DynamicOffset(dynamic_offset) => {60NonMaxU32::new(dynamic_offset)61}62PhaseItemExtraIndex::None | PhaseItemExtraIndex::IndirectParametersIndex { .. } => {63None64}65},66user_data,67}68}69}7071/// A trait to support getting data used for batching draw commands via phase72/// items.73///74/// This is a simple version that only allows for sorting, not binning, as well75/// as only CPU processing, not GPU preprocessing. For these fancier features,76/// see [`GetFullBatchData`].77pub trait GetBatchData {78/// The system parameters [`GetBatchData::get_batch_data`] needs in79/// order to compute the batch data.80type Param: SystemParam + 'static;81/// Data used for comparison between phase items. If the pipeline id, draw82/// function id, per-instance data buffer dynamic offset and this data83/// matches, the draws can be batched.84type CompareData: PartialEq;85/// The per-instance data to be inserted into the86/// [`crate::render_resource::GpuArrayBuffer`] containing these data for all87/// instances.88type BufferData: GpuArrayBufferable + Sync + Send + 'static;89/// Get the per-instance data to be inserted into the90/// [`crate::render_resource::GpuArrayBuffer`]. If the instance can be91/// batched, also return the data used for comparison when deciding whether92/// draws can be batched, else return None for the `CompareData`.93///94/// This is only called when building instance data on CPU. In the GPU95/// instance data building path, we use96/// [`GetFullBatchData::get_index_and_compare_data`] instead.97fn get_batch_data(98param: &SystemParamItem<Self::Param>,99query_item: (Entity, MainEntity),100) -> Option<(Self::BufferData, Option<Self::CompareData>)>;101}102103/// A trait to support getting data used for batching draw commands via phase104/// items.105///106/// This version allows for binning and GPU preprocessing.107pub trait GetFullBatchData: GetBatchData {108/// The per-instance data that was inserted into the109/// [`crate::render_resource::BufferVec`] during extraction.110type BufferInputData: Pod + Default + Sync + Send;111112/// Get the per-instance data to be inserted into the113/// [`crate::render_resource::GpuArrayBuffer`].114///115/// This is only called when building uniforms on CPU. In the GPU instance116/// buffer building path, we use117/// [`GetFullBatchData::get_index_and_compare_data`] instead.118fn get_binned_batch_data(119param: &SystemParamItem<Self::Param>,120query_item: MainEntity,121) -> Option<Self::BufferData>;122123/// Returns the index of the [`GetFullBatchData::BufferInputData`] that the124/// GPU preprocessing phase will use.125///126/// We already inserted the [`GetFullBatchData::BufferInputData`] during the127/// extraction phase before we got here, so this function shouldn't need to128/// look up any render data. If CPU instance buffer building is in use, this129/// function will never be called.130fn get_index_and_compare_data(131param: &SystemParamItem<Self::Param>,132query_item: MainEntity,133) -> Option<(NonMaxU32, Option<Self::CompareData>)>;134135/// Returns the index of the [`GetFullBatchData::BufferInputData`] that the136/// GPU preprocessing phase will use.137///138/// We already inserted the [`GetFullBatchData::BufferInputData`] during the139/// extraction phase before we got here, so this function shouldn't need to140/// look up any render data.141///142/// This function is currently only called for unbatchable entities when GPU143/// instance buffer building is in use. For batchable entities, the uniform144/// index is written during queuing (e.g. in `queue_material_meshes`). In145/// the case of CPU instance buffer building, the CPU writes the uniforms,146/// so there's no index to return.147fn get_binned_index(148param: &SystemParamItem<Self::Param>,149query_item: MainEntity,150) -> Option<NonMaxU32>;151152/// Writes the [`gpu_preprocessing::IndirectParametersGpuMetadata`]153/// necessary to draw this batch into the given metadata buffer at the given154/// index.155///156/// This is only used if GPU culling is enabled (which requires GPU157/// preprocessing).158///159/// * `indexed` is true if the mesh is indexed or false if it's non-indexed.160///161/// * `base_output_index` is the index of the first mesh instance in this162/// batch in the `MeshUniform` output buffer.163///164/// * `batch_set_index` is the index of the batch set in the165/// [`gpu_preprocessing::IndirectBatchSet`] buffer, if this batch belongs to166/// a batch set.167///168/// * `indirect_parameters_buffers` is the buffer in which to write the169/// metadata.170///171/// * `indirect_parameters_offset` is the index in that buffer at which to172/// write the metadata.173fn write_batch_indirect_parameters_metadata(174indexed: bool,175base_output_index: u32,176batch_set_index: Option<NonMaxU32>,177indirect_parameters_buffers: &mut UntypedPhaseIndirectParametersBuffers,178indirect_parameters_offset: u32,179);180}181182/// Sorts a render phase that uses bins.183pub fn sort_binned_render_phase<BPI>(mut phases: ResMut<ViewBinnedRenderPhases<BPI>>)184where185BPI: BinnedPhaseItem,186{187for phase in phases.values_mut() {188phase.multidrawable_meshes.sort_unstable_keys();189phase.batchable_meshes.sort_unstable_keys();190phase.unbatchable_meshes.sort_unstable_keys();191phase.non_mesh_items.sort_unstable_keys();192}193}194195/// Batches the items in a sorted render phase.196///197/// This means comparing metadata needed to draw each phase item and trying to198/// combine the draws into a batch.199///200/// This is common code factored out from201/// [`gpu_preprocessing::batch_and_prepare_sorted_render_phase`] and202/// [`no_gpu_preprocessing::batch_and_prepare_sorted_render_phase`].203fn batch_and_prepare_sorted_render_phase<I, GBD>(204phase: &mut SortedRenderPhase<I>,205mut process_item: impl FnMut(&mut I) -> Option<GBD::CompareData>,206) where207I: CachedRenderPipelinePhaseItem + SortedPhaseItem,208GBD: GetBatchData,209{210let items = phase.items.iter_mut().map(|item| {211let batch_data = match process_item(item) {212Some(compare_data) if I::AUTOMATIC_BATCHING => Some(BatchMeta::new(item, compare_data)),213_ => None,214};215(item.batch_range_mut(), batch_data)216});217218items.reduce(|(start_range, prev_batch_meta), (range, batch_meta)| {219if batch_meta.is_some() && prev_batch_meta == batch_meta {220start_range.end = range.end;221(start_range, prev_batch_meta)222} else {223(range, batch_meta)224}225});226}227228229