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