bevy_render/batching/
no_gpu_preprocessing.rs

1//! Batching functionality when GPU preprocessing isn't in use.
2
3use bevy_derive::{Deref, DerefMut};
4use bevy_ecs::system::{Res, ResMut, Resource, StaticSystemParam};
5use smallvec::{smallvec, SmallVec};
6use wgpu::BindingResource;
7
8use crate::{
9    render_phase::{
10        BinnedPhaseItem, BinnedRenderPhaseBatch, CachedRenderPipelinePhaseItem,
11        PhaseItemExtraIndex, SortedPhaseItem, ViewBinnedRenderPhases, ViewSortedRenderPhases,
12    },
13    render_resource::{GpuArrayBuffer, GpuArrayBufferable},
14    renderer::{RenderDevice, RenderQueue},
15};
16
17use super::{GetBatchData, GetFullBatchData};
18
19/// The GPU buffers holding the data needed to render batches.
20///
21/// For example, in the 3D PBR pipeline this holds `MeshUniform`s, which are the
22/// `BD` type parameter in that mode.
23#[derive(Resource, Deref, DerefMut)]
24pub struct BatchedInstanceBuffer<BD>(pub GpuArrayBuffer<BD>)
25where
26    BD: GpuArrayBufferable + Sync + Send + 'static;
27
28impl<BD> BatchedInstanceBuffer<BD>
29where
30    BD: GpuArrayBufferable + Sync + Send + 'static,
31{
32    /// Creates a new buffer.
33    pub fn new(render_device: &RenderDevice) -> Self {
34        BatchedInstanceBuffer(GpuArrayBuffer::new(render_device))
35    }
36
37    /// Returns the binding of the buffer that contains the per-instance data.
38    ///
39    /// If we're in the GPU instance buffer building mode, this buffer needs to
40    /// be filled in via a compute shader.
41    pub fn instance_data_binding(&self) -> Option<BindingResource> {
42        self.binding()
43    }
44}
45
46/// A system that clears out the [`BatchedInstanceBuffer`] for the frame.
47///
48/// This needs to run before the CPU batched instance buffers are used.
49pub fn clear_batched_cpu_instance_buffers<GBD>(
50    cpu_batched_instance_buffer: Option<ResMut<BatchedInstanceBuffer<GBD::BufferData>>>,
51) where
52    GBD: GetBatchData,
53{
54    if let Some(mut cpu_batched_instance_buffer) = cpu_batched_instance_buffer {
55        cpu_batched_instance_buffer.clear();
56    }
57}
58
59/// Batch the items in a sorted render phase, when GPU instance buffer building
60/// isn't in use. This means comparing metadata needed to draw each phase item
61/// and trying to combine the draws into a batch.
62pub fn batch_and_prepare_sorted_render_phase<I, GBD>(
63    batched_instance_buffer: ResMut<BatchedInstanceBuffer<GBD::BufferData>>,
64    mut phases: ResMut<ViewSortedRenderPhases<I>>,
65    param: StaticSystemParam<GBD::Param>,
66) where
67    I: CachedRenderPipelinePhaseItem + SortedPhaseItem,
68    GBD: GetBatchData,
69{
70    let system_param_item = param.into_inner();
71
72    // We only process CPU-built batch data in this function.
73    let batched_instance_buffer = batched_instance_buffer.into_inner();
74
75    for phase in phases.values_mut() {
76        super::batch_and_prepare_sorted_render_phase::<I, GBD>(phase, |item| {
77            let (buffer_data, compare_data) =
78                GBD::get_batch_data(&system_param_item, (item.entity(), item.main_entity()))?;
79            let buffer_index = batched_instance_buffer.push(buffer_data);
80
81            let index = buffer_index.index;
82            let (batch_range, extra_index) = item.batch_range_and_extra_index_mut();
83            *batch_range = index..index + 1;
84            *extra_index = PhaseItemExtraIndex::maybe_dynamic_offset(buffer_index.dynamic_offset);
85
86            compare_data
87        });
88    }
89}
90
91/// Creates batches for a render phase that uses bins, when GPU batch data
92/// building isn't in use.
93pub fn batch_and_prepare_binned_render_phase<BPI, GFBD>(
94    gpu_array_buffer: ResMut<BatchedInstanceBuffer<GFBD::BufferData>>,
95    mut phases: ResMut<ViewBinnedRenderPhases<BPI>>,
96    param: StaticSystemParam<GFBD::Param>,
97) where
98    BPI: BinnedPhaseItem,
99    GFBD: GetFullBatchData,
100{
101    let gpu_array_buffer = gpu_array_buffer.into_inner();
102    let system_param_item = param.into_inner();
103
104    for phase in phases.values_mut() {
105        // Prepare batchables.
106
107        for key in &phase.batchable_mesh_keys {
108            let mut batch_set: SmallVec<[BinnedRenderPhaseBatch; 1]> = smallvec![];
109            for &(entity, main_entity) in &phase.batchable_mesh_values[key] {
110                let Some(buffer_data) =
111                    GFBD::get_binned_batch_data(&system_param_item, (entity, main_entity))
112                else {
113                    continue;
114                };
115                let instance = gpu_array_buffer.push(buffer_data);
116
117                // If the dynamic offset has changed, flush the batch.
118                //
119                // This is the only time we ever have more than one batch per
120                // bin. Note that dynamic offsets are only used on platforms
121                // with no storage buffers.
122                if !batch_set.last().is_some_and(|batch| {
123                    batch.instance_range.end == instance.index
124                        && batch.extra_index
125                            == PhaseItemExtraIndex::maybe_dynamic_offset(instance.dynamic_offset)
126                }) {
127                    batch_set.push(BinnedRenderPhaseBatch {
128                        representative_entity: (entity, main_entity),
129                        instance_range: instance.index..instance.index,
130                        extra_index: PhaseItemExtraIndex::maybe_dynamic_offset(
131                            instance.dynamic_offset,
132                        ),
133                    });
134                }
135
136                if let Some(batch) = batch_set.last_mut() {
137                    batch.instance_range.end = instance.index + 1;
138                }
139            }
140
141            phase.batch_sets.push(batch_set);
142        }
143
144        // Prepare unbatchables.
145        for key in &phase.unbatchable_mesh_keys {
146            let unbatchables = phase.unbatchable_mesh_values.get_mut(key).unwrap();
147            for &entity in &unbatchables.entities {
148                let Some(buffer_data) = GFBD::get_binned_batch_data(&system_param_item, entity)
149                else {
150                    continue;
151                };
152                let instance = gpu_array_buffer.push(buffer_data);
153                unbatchables.buffer_indices.add(instance.into());
154            }
155        }
156    }
157}
158
159/// Writes the instance buffer data to the GPU.
160pub fn write_batched_instance_buffer<GBD>(
161    render_device: Res<RenderDevice>,
162    render_queue: Res<RenderQueue>,
163    mut cpu_batched_instance_buffer: ResMut<BatchedInstanceBuffer<GBD::BufferData>>,
164) where
165    GBD: GetBatchData,
166{
167    cpu_batched_instance_buffer.write_buffer(&render_device, &render_queue);
168}