Skip to main content

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