Skip to main content

bevy_render/batching/
mod.rs

1use bevy_ecs::{
2    component::Component,
3    entity::Entity,
4    system::{ResMut, SystemParam, SystemParamItem},
5};
6use gpu_preprocessing::UntypedPhaseIndirectParametersBuffers;
7use nonmax::NonMaxU32;
8
9use bevy_material::{descriptor::CachedRenderPipelineId, labels::DrawFunctionId};
10
11use crate::{
12    batching::gpu_preprocessing::BufferDataInput,
13    render_phase::{
14        BinnedPhaseItem, CachedRenderPipelinePhaseItem, PhaseItemExtraIndex, SortedPhaseItem,
15        SortedRenderPhase, ViewBinnedRenderPhases,
16    },
17    render_resource::GpuArrayBufferable,
18    sync_world::MainEntity,
19};
20
21pub mod gpu_preprocessing;
22pub mod no_gpu_preprocessing;
23
24/// Add this component to mesh entities to disable automatic batching
25#[derive(Component, Default, Clone, Copy)]
26pub struct NoAutomaticBatching;
27
28/// Data necessary to be equal for two draw commands to be mergeable
29///
30/// This is based on the following assumptions:
31/// - Only entities with prepared assets (pipelines, materials, meshes) are
32///   queued to phases
33/// - View bindings are constant across a phase for a given draw function as
34///   phases are per-view
35/// - `batch_and_prepare_render_phase` is the only system that performs this
36///   batching and has sole responsibility for preparing the per-object data.
37///   As such the mesh binding and dynamic offsets are assumed to only be
38///   variable as a result of the `batch_and_prepare_render_phase` system, e.g.
39///   due to having to split data across separate uniform bindings within the
40///   same buffer due to the maximum uniform buffer binding size.
41#[derive(PartialEq)]
42struct BatchSetMeta<T: PartialEq> {
43    /// The pipeline id encompasses all pipeline configuration including vertex
44    /// buffers and layouts, shaders and their specializations, bind group
45    /// layouts, etc.
46    pipeline_id: CachedRenderPipelineId,
47    /// The draw function id defines the `RenderCommands` that are called to
48    /// set the pipeline and bindings, and make the draw command
49    draw_function_id: DrawFunctionId,
50    dynamic_offset: Option<NonMaxU32>,
51    user_data: T,
52}
53
54impl<T: PartialEq> BatchSetMeta<T> {
55    fn new(item: &impl CachedRenderPipelinePhaseItem, user_data: T) -> Self {
56        BatchSetMeta {
57            pipeline_id: item.cached_pipeline(),
58            draw_function_id: item.draw_function(),
59            dynamic_offset: match item.extra_index() {
60                PhaseItemExtraIndex::DynamicOffset(dynamic_offset) => {
61                    NonMaxU32::new(dynamic_offset)
62                }
63                PhaseItemExtraIndex::None | PhaseItemExtraIndex::IndirectParametersIndex { .. } => {
64                    None
65                }
66            },
67            user_data,
68        }
69    }
70}
71
72/// A trait to support getting data used for batching draw commands via phase
73/// items.
74///
75/// This is a simple version that only allows for sorting, not binning, as well
76/// as only CPU processing, not GPU preprocessing. For these fancier features,
77/// see [`GetFullBatchData`].
78pub trait GetBatchData {
79    /// The system parameters [`GetBatchData::get_batch_data`] needs in
80    /// order to compute the batch data.
81    type Param: SystemParam + 'static;
82    /// Data used for comparison between phase items to decide whether items can
83    /// be batched.
84    ///
85    /// If this data, and the [`Self::BatchSetCompareData`], are identical to
86    /// those of the previous phase item, the items can be batched together.
87    type BatchCompareData: PartialEq;
88    /// Data used for comparison between phase items to decide whether items can
89    /// be grouped in the same batch set (i.e. multi-drawn).
90    ///
91    /// If this data is identical to that of the previous phase items, and the
92    /// current platform supports multi-draw, the items can be multi-drawn
93    /// together.
94    type BatchSetCompareData: PartialEq;
95    /// The per-instance data to be inserted into the
96    /// [`crate::render_resource::GpuArrayBuffer`] containing these data for all
97    /// instances.
98    type BufferData: GpuArrayBufferable + Sync + Send + 'static;
99    /// Get the per-instance data to be inserted into the
100    /// [`crate::render_resource::GpuArrayBuffer`]. If the instance can be
101    /// batched, also return the data used for comparison when deciding whether
102    /// draws can be batched, else return None for the `CompareData`.
103    ///
104    /// This is only called when building instance data on CPU. In the GPU
105    /// instance data building path, we use
106    /// [`GetFullBatchData::get_index_and_compare_data`] instead.
107    fn get_batch_data(
108        param: &SystemParamItem<Self::Param>,
109        query_item: (Entity, MainEntity),
110    ) -> Option<(
111        Self::BufferData,
112        Option<(Self::BatchSetCompareData, Self::BatchCompareData)>,
113    )>;
114}
115
116/// A trait to support getting data used for batching draw commands via phase
117/// items.
118///
119/// This version allows for binning and GPU preprocessing.
120pub trait GetFullBatchData: GetBatchData {
121    /// The per-instance data that was inserted into the
122    /// [`crate::render_resource::BufferVec`] during extraction.
123    type BufferInputData: BufferDataInput;
124
125    /// Get the per-instance data to be inserted into the
126    /// [`crate::render_resource::GpuArrayBuffer`].
127    ///
128    /// This is only called when building uniforms on CPU. In the GPU instance
129    /// buffer building path, we use
130    /// [`GetFullBatchData::get_index_and_compare_data`] instead.
131    fn get_binned_batch_data(
132        param: &SystemParamItem<Self::Param>,
133        query_item: MainEntity,
134    ) -> Option<Self::BufferData>;
135
136    /// Returns the index of the [`GetFullBatchData::BufferInputData`] that the
137    /// GPU preprocessing phase will use.
138    ///
139    /// We already inserted the [`GetFullBatchData::BufferInputData`] during the
140    /// extraction phase before we got here, so this function shouldn't need to
141    /// look up any render data. If CPU instance buffer building is in use, this
142    /// function will never be called.
143    fn get_index_and_compare_data(
144        param: &SystemParamItem<Self::Param>,
145        query_item: MainEntity,
146    ) -> Option<(
147        NonMaxU32,
148        Option<(Self::BatchSetCompareData, Self::BatchCompareData)>,
149    )>;
150
151    /// Returns the index of the [`GetFullBatchData::BufferInputData`] that the
152    /// GPU preprocessing phase will use.
153    ///
154    /// We already inserted the [`GetFullBatchData::BufferInputData`] during the
155    /// extraction phase before we got here, so this function shouldn't need to
156    /// look up any render data.
157    ///
158    /// This function is currently only called for unbatchable entities when GPU
159    /// instance buffer building is in use. For batchable entities, the uniform
160    /// index is written during queuing (e.g. in `queue_material_meshes`). In
161    /// the case of CPU instance buffer building, the CPU writes the uniforms,
162    /// so there's no index to return.
163    fn get_binned_index(
164        param: &SystemParamItem<Self::Param>,
165        query_item: MainEntity,
166    ) -> Option<NonMaxU32>;
167
168    /// Writes the [`gpu_preprocessing::IndirectParametersMetadata`] necessary
169    /// to draw this batch into the given metadata buffer at the given index.
170    ///
171    /// This is only used if GPU culling is enabled (which requires GPU
172    /// preprocessing), and only for phase items that don't support GPU uniform
173    /// allocation yet.
174    ///
175    /// * `indexed` is true if the mesh is indexed or false if it's non-indexed.
176    ///
177    /// * `base_output_index` is the index of the first mesh instance in this
178    ///   batch in the `MeshUniform` output buffer.
179    ///
180    /// * `batch_set_index` is the index of the batch set in the
181    ///   [`gpu_preprocessing::IndirectBatchSet`] buffer, if this batch belongs to
182    ///   a batch set.
183    ///
184    /// * `indirect_parameters_buffers` is the buffer in which to write the
185    ///   metadata.
186    ///
187    /// * `indirect_parameters_offset` is the index in that buffer at which to
188    ///   write the metadata.
189    fn write_batch_indirect_parameters_metadata(
190        indexed: bool,
191        base_output_index: u32,
192        batch_set_index: Option<NonMaxU32>,
193        indirect_parameters_buffers: &mut UntypedPhaseIndirectParametersBuffers,
194        indirect_parameters_offset: u32,
195    );
196}
197
198/// Sorts a render phase that uses bins.
199pub fn sort_binned_render_phase<BPI>(mut phases: ResMut<ViewBinnedRenderPhases<BPI>>)
200where
201    BPI: BinnedPhaseItem,
202{
203    for phase in phases.values_mut() {
204        phase.multidrawable_meshes.sort_unstable_keys();
205        phase.batchable_meshes.sort_unstable_keys();
206        phase.unbatchable_meshes.sort_unstable_keys();
207        phase.non_mesh_items.sort_unstable_keys();
208    }
209}
210
211/// Batches the items in a sorted render phase.
212///
213/// This means comparing metadata needed to draw each phase item and trying to
214/// combine the draws into a batch.
215///
216/// This is common code factored out from
217/// [`gpu_preprocessing::batch_and_prepare_sorted_render_phase`] and
218/// [`no_gpu_preprocessing::batch_and_prepare_sorted_render_phase`].
219fn batch_and_prepare_sorted_render_phase<I, GBD>(
220    phase: &mut SortedRenderPhase<I>,
221    mut process_item: impl FnMut(&mut I) -> Option<(GBD::BatchSetCompareData, GBD::BatchCompareData)>,
222) where
223    I: CachedRenderPipelinePhaseItem + SortedPhaseItem,
224    GBD: GetBatchData,
225{
226    let items = phase.items.values_mut().map(|item| {
227        let batch_data = match process_item(item) {
228            Some(compare_data) if I::AUTOMATIC_BATCHING => {
229                Some(BatchSetMeta::new(item, compare_data))
230            }
231            _ => None,
232        };
233        (item.batch_range_mut(), batch_data)
234    });
235
236    items.reduce(|(start_range, prev_batch_meta), (range, batch_meta)| {
237        if batch_meta.is_some() && prev_batch_meta == batch_meta {
238            start_range.end = range.end;
239            (start_range, prev_batch_meta)
240        } else {
241            (range, batch_meta)
242        }
243    });
244}