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())?;
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 in &phase.batchable_mesh_values[key] {
110                let Some(buffer_data) = GFBD::get_binned_batch_data(&system_param_item, entity)
111                else {
112                    continue;
113                };
114                let instance = gpu_array_buffer.push(buffer_data);
115
116                // If the dynamic offset has changed, flush the batch.
117                //
118                // This is the only time we ever have more than one batch per
119                // bin. Note that dynamic offsets are only used on platforms
120                // with no storage buffers.
121                if !batch_set.last().is_some_and(|batch| {
122                    batch.instance_range.end == instance.index
123                        && batch.extra_index
124                            == PhaseItemExtraIndex::maybe_dynamic_offset(instance.dynamic_offset)
125                }) {
126                    batch_set.push(BinnedRenderPhaseBatch {
127                        representative_entity: entity,
128                        instance_range: instance.index..instance.index,
129                        extra_index: PhaseItemExtraIndex::maybe_dynamic_offset(
130                            instance.dynamic_offset,
131                        ),
132                    });
133                }
134
135                if let Some(batch) = batch_set.last_mut() {
136                    batch.instance_range.end = instance.index + 1;
137                }
138            }
139
140            phase.batch_sets.push(batch_set);
141        }
142
143        // Prepare unbatchables.
144        for key in &phase.unbatchable_mesh_keys {
145            let unbatchables = phase.unbatchable_mesh_values.get_mut(key).unwrap();
146            for &entity in &unbatchables.entities {
147                let Some(buffer_data) = GFBD::get_binned_batch_data(&system_param_item, entity)
148                else {
149                    continue;
150                };
151                let instance = gpu_array_buffer.push(buffer_data);
152                unbatchables.buffer_indices.add(instance.into());
153            }
154        }
155    }
156}
157
158/// Writes the instance buffer data to the GPU.
159pub fn write_batched_instance_buffer<GBD>(
160    render_device: Res<RenderDevice>,
161    render_queue: Res<RenderQueue>,
162    mut cpu_batched_instance_buffer: ResMut<BatchedInstanceBuffer<GBD::BufferData>>,
163) where
164    GBD: GetBatchData,
165{
166    cpu_batched_instance_buffer.write_buffer(&render_device, &render_queue);
167}