bevy_render/batching/
mod.rs

1use bevy_ecs::{
2    component::Component,
3    entity::Entity,
4    system::{ResMut, SystemParam, SystemParamItem},
5};
6use bytemuck::Pod;
7use nonmax::NonMaxU32;
8
9use self::gpu_preprocessing::IndirectParametersBuffer;
10use crate::sync_world::MainEntity;
11use crate::{
12    render_phase::{
13        BinnedPhaseItem, CachedRenderPipelinePhaseItem, DrawFunctionId, SortedPhaseItem,
14        SortedRenderPhase, ViewBinnedRenderPhases,
15    },
16    render_resource::{CachedRenderPipelineId, GpuArrayBufferable},
17};
18
19pub mod gpu_preprocessing;
20pub mod no_gpu_preprocessing;
21
22/// Add this component to mesh entities to disable automatic batching
23#[derive(Component)]
24pub struct NoAutomaticBatching;
25
26/// Data necessary to be equal for two draw commands to be mergeable
27///
28/// This is based on the following assumptions:
29/// - Only entities with prepared assets (pipelines, materials, meshes) are
30///   queued to phases
31/// - View bindings are constant across a phase for a given draw function as
32///   phases are per-view
33/// - `batch_and_prepare_render_phase` is the only system that performs this
34///   batching and has sole responsibility for preparing the per-object data.
35///   As such the mesh binding and dynamic offsets are assumed to only be
36///   variable as a result of the `batch_and_prepare_render_phase` system, e.g.
37///   due to having to split data across separate uniform bindings within the
38///   same buffer due to the maximum uniform buffer binding size.
39#[derive(PartialEq)]
40struct BatchMeta<T: PartialEq> {
41    /// The pipeline id encompasses all pipeline configuration including vertex
42    /// buffers and layouts, shaders and their specializations, bind group
43    /// layouts, etc.
44    pipeline_id: CachedRenderPipelineId,
45    /// The draw function id defines the `RenderCommands` that are called to
46    /// set the pipeline and bindings, and make the draw command
47    draw_function_id: DrawFunctionId,
48    dynamic_offset: Option<NonMaxU32>,
49    user_data: T,
50}
51
52impl<T: PartialEq> BatchMeta<T> {
53    fn new(item: &impl CachedRenderPipelinePhaseItem, user_data: T) -> Self {
54        BatchMeta {
55            pipeline_id: item.cached_pipeline(),
56            draw_function_id: item.draw_function(),
57            dynamic_offset: item.extra_index().as_dynamic_offset(),
58            user_data,
59        }
60    }
61}
62
63/// A trait to support getting data used for batching draw commands via phase
64/// items.
65///
66/// This is a simple version that only allows for sorting, not binning, as well
67/// as only CPU processing, not GPU preprocessing. For these fancier features,
68/// see [`GetFullBatchData`].
69pub trait GetBatchData {
70    /// The system parameters [`GetBatchData::get_batch_data`] needs in
71    /// order to compute the batch data.
72    type Param: SystemParam + 'static;
73    /// Data used for comparison between phase items. If the pipeline id, draw
74    /// function id, per-instance data buffer dynamic offset and this data
75    /// matches, the draws can be batched.
76    type CompareData: PartialEq;
77    /// The per-instance data to be inserted into the
78    /// [`crate::render_resource::GpuArrayBuffer`] containing these data for all
79    /// instances.
80    type BufferData: GpuArrayBufferable + Sync + Send + 'static;
81    /// Get the per-instance data to be inserted into the
82    /// [`crate::render_resource::GpuArrayBuffer`]. If the instance can be
83    /// batched, also return the data used for comparison when deciding whether
84    /// draws can be batched, else return None for the `CompareData`.
85    ///
86    /// This is only called when building instance data on CPU. In the GPU
87    /// instance data building path, we use
88    /// [`GetFullBatchData::get_index_and_compare_data`] instead.
89    fn get_batch_data(
90        param: &SystemParamItem<Self::Param>,
91        query_item: (Entity, MainEntity),
92    ) -> Option<(Self::BufferData, Option<Self::CompareData>)>;
93}
94
95/// A trait to support getting data used for batching draw commands via phase
96/// items.
97///
98/// This version allows for binning and GPU preprocessing.
99pub trait GetFullBatchData: GetBatchData {
100    /// The per-instance data that was inserted into the
101    /// [`crate::render_resource::BufferVec`] during extraction.
102    type BufferInputData: Pod + Sync + Send;
103
104    /// Get the per-instance data to be inserted into the
105    /// [`crate::render_resource::GpuArrayBuffer`].
106    ///
107    /// This is only called when building uniforms on CPU. In the GPU instance
108    /// buffer building path, we use
109    /// [`GetFullBatchData::get_index_and_compare_data`] instead.
110    fn get_binned_batch_data(
111        param: &SystemParamItem<Self::Param>,
112        query_item: (Entity, MainEntity),
113    ) -> Option<Self::BufferData>;
114
115    /// Returns the index of the [`GetFullBatchData::BufferInputData`] that the
116    /// GPU preprocessing phase will use.
117    ///
118    /// We already inserted the [`GetFullBatchData::BufferInputData`] during the
119    /// extraction phase before we got here, so this function shouldn't need to
120    /// look up any render data. If CPU instance buffer building is in use, this
121    /// function will never be called.
122    fn get_index_and_compare_data(
123        param: &SystemParamItem<Self::Param>,
124        query_item: (Entity, MainEntity),
125    ) -> Option<(NonMaxU32, Option<Self::CompareData>)>;
126
127    /// Returns the index of the [`GetFullBatchData::BufferInputData`] that the
128    /// GPU preprocessing phase will use, for the binning path.
129    ///
130    /// We already inserted the [`GetFullBatchData::BufferInputData`] during the
131    /// extraction phase before we got here, so this function shouldn't need to
132    /// look up any render data. If CPU instance buffer building is in use, this
133    /// function will never be called.
134    fn get_binned_index(
135        param: &SystemParamItem<Self::Param>,
136        query_item: (Entity, MainEntity),
137    ) -> Option<NonMaxU32>;
138
139    /// Pushes [`gpu_preprocessing::IndirectParameters`] necessary to draw this
140    /// batch onto the given [`IndirectParametersBuffer`], and returns its
141    /// index.
142    ///
143    /// This is only used if GPU culling is enabled (which requires GPU
144    /// preprocessing).
145    fn get_batch_indirect_parameters_index(
146        param: &SystemParamItem<Self::Param>,
147        indirect_parameters_buffer: &mut IndirectParametersBuffer,
148        entity: (Entity, MainEntity),
149        instance_index: u32,
150    ) -> Option<NonMaxU32>;
151}
152
153/// Sorts a render phase that uses bins.
154pub fn sort_binned_render_phase<BPI>(mut phases: ResMut<ViewBinnedRenderPhases<BPI>>)
155where
156    BPI: BinnedPhaseItem,
157{
158    for phase in phases.values_mut() {
159        phase.batchable_mesh_keys.sort_unstable();
160        phase.unbatchable_mesh_keys.sort_unstable();
161    }
162}
163
164/// Batches the items in a sorted render phase.
165///
166/// This means comparing metadata needed to draw each phase item and trying to
167/// combine the draws into a batch.
168///
169/// This is common code factored out from
170/// [`gpu_preprocessing::batch_and_prepare_sorted_render_phase`] and
171/// [`no_gpu_preprocessing::batch_and_prepare_sorted_render_phase`].
172fn batch_and_prepare_sorted_render_phase<I, GBD>(
173    phase: &mut SortedRenderPhase<I>,
174    mut process_item: impl FnMut(&mut I) -> Option<GBD::CompareData>,
175) where
176    I: CachedRenderPipelinePhaseItem + SortedPhaseItem,
177    GBD: GetBatchData,
178{
179    let items = phase.items.iter_mut().map(|item| {
180        let batch_data = match process_item(item) {
181            Some(compare_data) if I::AUTOMATIC_BATCHING => Some(BatchMeta::new(item, compare_data)),
182            _ => None,
183        };
184        (item.batch_range_mut(), batch_data)
185    });
186
187    items.reduce(|(start_range, prev_batch_meta), (range, batch_meta)| {
188        if batch_meta.is_some() && prev_batch_meta == batch_meta {
189            start_range.end = range.end;
190            (start_range, prev_batch_meta)
191        } else {
192            (range, batch_meta)
193        }
194    });
195}