use bevy_ecs::{1component::Component,2entity::Entity,3system::{ResMut, SystemParam, SystemParamItem},4};5use bytemuck::Pod;6use gpu_preprocessing::UntypedPhaseIndirectParametersBuffers;7use nonmax::NonMaxU32;89use crate::{10render_phase::{11BinnedPhaseItem, CachedRenderPipelinePhaseItem, DrawFunctionId, PhaseItemExtraIndex,12SortedPhaseItem, SortedRenderPhase, ViewBinnedRenderPhases,13},14render_resource::{CachedRenderPipelineId, GpuArrayBufferable},15sync_world::MainEntity,16};1718pub mod gpu_preprocessing;19pub mod no_gpu_preprocessing;2021/// Add this component to mesh entities to disable automatic batching22#[derive(Component, Default)]23pub struct NoAutomaticBatching;2425/// Data necessary to be equal for two draw commands to be mergeable26///27/// This is based on the following assumptions:28/// - Only entities with prepared assets (pipelines, materials, meshes) are29/// queued to phases30/// - View bindings are constant across a phase for a given draw function as31/// phases are per-view32/// - `batch_and_prepare_render_phase` is the only system that performs this33/// batching and has sole responsibility for preparing the per-object data.34/// As such the mesh binding and dynamic offsets are assumed to only be35/// variable as a result of the `batch_and_prepare_render_phase` system, e.g.36/// due to having to split data across separate uniform bindings within the37/// same buffer due to the maximum uniform buffer binding size.38#[derive(PartialEq)]39struct BatchMeta<T: PartialEq> {40/// The pipeline id encompasses all pipeline configuration including vertex41/// buffers and layouts, shaders and their specializations, bind group42/// layouts, etc.43pipeline_id: CachedRenderPipelineId,44/// The draw function id defines the `RenderCommands` that are called to45/// set the pipeline and bindings, and make the draw command46draw_function_id: DrawFunctionId,47dynamic_offset: Option<NonMaxU32>,48user_data: T,49}5051impl<T: PartialEq> BatchMeta<T> {52fn new(item: &impl CachedRenderPipelinePhaseItem, user_data: T) -> Self {53BatchMeta {54pipeline_id: item.cached_pipeline(),55draw_function_id: item.draw_function(),56dynamic_offset: match item.extra_index() {57PhaseItemExtraIndex::DynamicOffset(dynamic_offset) => {58NonMaxU32::new(dynamic_offset)59}60PhaseItemExtraIndex::None | PhaseItemExtraIndex::IndirectParametersIndex { .. } => {61None62}63},64user_data,65}66}67}6869/// A trait to support getting data used for batching draw commands via phase70/// items.71///72/// This is a simple version that only allows for sorting, not binning, as well73/// as only CPU processing, not GPU preprocessing. For these fancier features,74/// see [`GetFullBatchData`].75pub trait GetBatchData {76/// The system parameters [`GetBatchData::get_batch_data`] needs in77/// order to compute the batch data.78type Param: SystemParam + 'static;79/// Data used for comparison between phase items. If the pipeline id, draw80/// function id, per-instance data buffer dynamic offset and this data81/// matches, the draws can be batched.82type CompareData: PartialEq;83/// The per-instance data to be inserted into the84/// [`crate::render_resource::GpuArrayBuffer`] containing these data for all85/// instances.86type BufferData: GpuArrayBufferable + Sync + Send + 'static;87/// Get the per-instance data to be inserted into the88/// [`crate::render_resource::GpuArrayBuffer`]. If the instance can be89/// batched, also return the data used for comparison when deciding whether90/// draws can be batched, else return None for the `CompareData`.91///92/// This is only called when building instance data on CPU. In the GPU93/// instance data building path, we use94/// [`GetFullBatchData::get_index_and_compare_data`] instead.95fn get_batch_data(96param: &SystemParamItem<Self::Param>,97query_item: (Entity, MainEntity),98) -> Option<(Self::BufferData, Option<Self::CompareData>)>;99}100101/// A trait to support getting data used for batching draw commands via phase102/// items.103///104/// This version allows for binning and GPU preprocessing.105pub trait GetFullBatchData: GetBatchData {106/// The per-instance data that was inserted into the107/// [`crate::render_resource::BufferVec`] during extraction.108type BufferInputData: Pod + Default + Sync + Send;109110/// Get the per-instance data to be inserted into the111/// [`crate::render_resource::GpuArrayBuffer`].112///113/// This is only called when building uniforms on CPU. In the GPU instance114/// buffer building path, we use115/// [`GetFullBatchData::get_index_and_compare_data`] instead.116fn get_binned_batch_data(117param: &SystemParamItem<Self::Param>,118query_item: MainEntity,119) -> Option<Self::BufferData>;120121/// Returns the index of the [`GetFullBatchData::BufferInputData`] that the122/// GPU preprocessing phase will use.123///124/// We already inserted the [`GetFullBatchData::BufferInputData`] during the125/// extraction phase before we got here, so this function shouldn't need to126/// look up any render data. If CPU instance buffer building is in use, this127/// function will never be called.128fn get_index_and_compare_data(129param: &SystemParamItem<Self::Param>,130query_item: MainEntity,131) -> Option<(NonMaxU32, Option<Self::CompareData>)>;132133/// Returns the index of the [`GetFullBatchData::BufferInputData`] that the134/// GPU preprocessing phase will use.135///136/// We already inserted the [`GetFullBatchData::BufferInputData`] during the137/// extraction phase before we got here, so this function shouldn't need to138/// look up any render data.139///140/// This function is currently only called for unbatchable entities when GPU141/// instance buffer building is in use. For batchable entities, the uniform142/// index is written during queuing (e.g. in `queue_material_meshes`). In143/// the case of CPU instance buffer building, the CPU writes the uniforms,144/// so there's no index to return.145fn get_binned_index(146param: &SystemParamItem<Self::Param>,147query_item: MainEntity,148) -> Option<NonMaxU32>;149150/// Writes the [`gpu_preprocessing::IndirectParametersGpuMetadata`]151/// necessary to draw this batch into the given metadata buffer at the given152/// index.153///154/// This is only used if GPU culling is enabled (which requires GPU155/// preprocessing).156///157/// * `indexed` is true if the mesh is indexed or false if it's non-indexed.158///159/// * `base_output_index` is the index of the first mesh instance in this160/// batch in the `MeshUniform` output buffer.161///162/// * `batch_set_index` is the index of the batch set in the163/// [`gpu_preprocessing::IndirectBatchSet`] buffer, if this batch belongs to164/// a batch set.165///166/// * `indirect_parameters_buffers` is the buffer in which to write the167/// metadata.168///169/// * `indirect_parameters_offset` is the index in that buffer at which to170/// write the metadata.171fn write_batch_indirect_parameters_metadata(172indexed: bool,173base_output_index: u32,174batch_set_index: Option<NonMaxU32>,175indirect_parameters_buffers: &mut UntypedPhaseIndirectParametersBuffers,176indirect_parameters_offset: u32,177);178}179180/// Sorts a render phase that uses bins.181pub fn sort_binned_render_phase<BPI>(mut phases: ResMut<ViewBinnedRenderPhases<BPI>>)182where183BPI: BinnedPhaseItem,184{185for phase in phases.values_mut() {186phase.multidrawable_meshes.sort_unstable_keys();187phase.batchable_meshes.sort_unstable_keys();188phase.unbatchable_meshes.sort_unstable_keys();189phase.non_mesh_items.sort_unstable_keys();190}191}192193/// Batches the items in a sorted render phase.194///195/// This means comparing metadata needed to draw each phase item and trying to196/// combine the draws into a batch.197///198/// This is common code factored out from199/// [`gpu_preprocessing::batch_and_prepare_sorted_render_phase`] and200/// [`no_gpu_preprocessing::batch_and_prepare_sorted_render_phase`].201fn batch_and_prepare_sorted_render_phase<I, GBD>(202phase: &mut SortedRenderPhase<I>,203mut process_item: impl FnMut(&mut I) -> Option<GBD::CompareData>,204) where205I: CachedRenderPipelinePhaseItem + SortedPhaseItem,206GBD: GetBatchData,207{208let items = phase.items.iter_mut().map(|item| {209let batch_data = match process_item(item) {210Some(compare_data) if I::AUTOMATIC_BATCHING => Some(BatchMeta::new(item, compare_data)),211_ => None,212};213(item.batch_range_mut(), batch_data)214});215216items.reduce(|(start_range, prev_batch_meta), (range, batch_meta)| {217if batch_meta.is_some() && prev_batch_meta == batch_meta {218start_range.end = range.end;219(start_range, prev_batch_meta)220} else {221(range, batch_meta)222}223});224}225226227