
Conceptually, bins are ordered hash maps. We currently implement these as a list of keys with an associated hash map. But we already have a data type that implements ordered hash maps directly: `IndexMap`. This patch switches Bevy to use `IndexMap`s for bins. Because we're memory bound, this doesn't affect performance much, but it is cleaner.
183 lines
6.8 KiB
Rust
183 lines
6.8 KiB
Rust
//! Batching functionality when GPU preprocessing isn't in use.
|
|
|
|
use bevy_derive::{Deref, DerefMut};
|
|
use bevy_ecs::entity::Entity;
|
|
use bevy_ecs::resource::Resource;
|
|
use bevy_ecs::system::{Res, ResMut, StaticSystemParam};
|
|
use smallvec::{smallvec, SmallVec};
|
|
use tracing::error;
|
|
use wgpu::BindingResource;
|
|
|
|
use crate::{
|
|
render_phase::{
|
|
BinnedPhaseItem, BinnedRenderPhaseBatch, BinnedRenderPhaseBatchSets,
|
|
CachedRenderPipelinePhaseItem, PhaseItemExtraIndex, SortedPhaseItem,
|
|
ViewBinnedRenderPhases, ViewSortedRenderPhases,
|
|
},
|
|
render_resource::{GpuArrayBuffer, GpuArrayBufferable},
|
|
renderer::{RenderDevice, RenderQueue},
|
|
};
|
|
|
|
use super::{GetBatchData, GetFullBatchData};
|
|
|
|
/// The GPU buffers holding the data needed to render batches.
|
|
///
|
|
/// For example, in the 3D PBR pipeline this holds `MeshUniform`s, which are the
|
|
/// `BD` type parameter in that mode.
|
|
#[derive(Resource, Deref, DerefMut)]
|
|
pub struct BatchedInstanceBuffer<BD>(pub GpuArrayBuffer<BD>)
|
|
where
|
|
BD: GpuArrayBufferable + Sync + Send + 'static;
|
|
|
|
impl<BD> BatchedInstanceBuffer<BD>
|
|
where
|
|
BD: GpuArrayBufferable + Sync + Send + 'static,
|
|
{
|
|
/// Creates a new buffer.
|
|
pub fn new(render_device: &RenderDevice) -> Self {
|
|
BatchedInstanceBuffer(GpuArrayBuffer::new(render_device))
|
|
}
|
|
|
|
/// Returns the binding of the buffer that contains the per-instance data.
|
|
///
|
|
/// If we're in the GPU instance buffer building mode, this buffer needs to
|
|
/// be filled in via a compute shader.
|
|
pub fn instance_data_binding(&self) -> Option<BindingResource> {
|
|
self.binding()
|
|
}
|
|
}
|
|
|
|
/// A system that clears out the [`BatchedInstanceBuffer`] for the frame.
|
|
///
|
|
/// This needs to run before the CPU batched instance buffers are used.
|
|
pub fn clear_batched_cpu_instance_buffers<GBD>(
|
|
cpu_batched_instance_buffer: Option<ResMut<BatchedInstanceBuffer<GBD::BufferData>>>,
|
|
) where
|
|
GBD: GetBatchData,
|
|
{
|
|
if let Some(mut cpu_batched_instance_buffer) = cpu_batched_instance_buffer {
|
|
cpu_batched_instance_buffer.clear();
|
|
}
|
|
}
|
|
|
|
/// Batch the items in a sorted render phase, when GPU instance buffer building
|
|
/// isn't in use. This means comparing metadata needed to draw each phase item
|
|
/// and trying to combine the draws into a batch.
|
|
pub fn batch_and_prepare_sorted_render_phase<I, GBD>(
|
|
batched_instance_buffer: ResMut<BatchedInstanceBuffer<GBD::BufferData>>,
|
|
mut phases: ResMut<ViewSortedRenderPhases<I>>,
|
|
param: StaticSystemParam<GBD::Param>,
|
|
) where
|
|
I: CachedRenderPipelinePhaseItem + SortedPhaseItem,
|
|
GBD: GetBatchData,
|
|
{
|
|
let system_param_item = param.into_inner();
|
|
|
|
// We only process CPU-built batch data in this function.
|
|
let batched_instance_buffer = batched_instance_buffer.into_inner();
|
|
|
|
for phase in phases.values_mut() {
|
|
super::batch_and_prepare_sorted_render_phase::<I, GBD>(phase, |item| {
|
|
let (buffer_data, compare_data) =
|
|
GBD::get_batch_data(&system_param_item, (item.entity(), item.main_entity()))?;
|
|
let buffer_index = batched_instance_buffer.push(buffer_data);
|
|
|
|
let index = buffer_index.index;
|
|
let (batch_range, extra_index) = item.batch_range_and_extra_index_mut();
|
|
*batch_range = index..index + 1;
|
|
*extra_index = PhaseItemExtraIndex::maybe_dynamic_offset(buffer_index.dynamic_offset);
|
|
|
|
compare_data
|
|
});
|
|
}
|
|
}
|
|
|
|
/// Creates batches for a render phase that uses bins, when GPU batch data
|
|
/// building isn't in use.
|
|
pub fn batch_and_prepare_binned_render_phase<BPI, GFBD>(
|
|
gpu_array_buffer: ResMut<BatchedInstanceBuffer<GFBD::BufferData>>,
|
|
mut phases: ResMut<ViewBinnedRenderPhases<BPI>>,
|
|
param: StaticSystemParam<GFBD::Param>,
|
|
) where
|
|
BPI: BinnedPhaseItem,
|
|
GFBD: GetFullBatchData,
|
|
{
|
|
let gpu_array_buffer = gpu_array_buffer.into_inner();
|
|
let system_param_item = param.into_inner();
|
|
|
|
for phase in phases.values_mut() {
|
|
// Prepare batchables.
|
|
|
|
for bin in phase.batchable_meshes.values_mut() {
|
|
let mut batch_set: SmallVec<[BinnedRenderPhaseBatch; 1]> = smallvec![];
|
|
for main_entity in bin.entities().keys() {
|
|
let Some(buffer_data) =
|
|
GFBD::get_binned_batch_data(&system_param_item, *main_entity)
|
|
else {
|
|
continue;
|
|
};
|
|
let instance = gpu_array_buffer.push(buffer_data);
|
|
|
|
// If the dynamic offset has changed, flush the batch.
|
|
//
|
|
// This is the only time we ever have more than one batch per
|
|
// bin. Note that dynamic offsets are only used on platforms
|
|
// with no storage buffers.
|
|
if !batch_set.last().is_some_and(|batch| {
|
|
batch.instance_range.end == instance.index
|
|
&& batch.extra_index
|
|
== PhaseItemExtraIndex::maybe_dynamic_offset(instance.dynamic_offset)
|
|
}) {
|
|
batch_set.push(BinnedRenderPhaseBatch {
|
|
representative_entity: (Entity::PLACEHOLDER, *main_entity),
|
|
instance_range: instance.index..instance.index,
|
|
extra_index: PhaseItemExtraIndex::maybe_dynamic_offset(
|
|
instance.dynamic_offset,
|
|
),
|
|
});
|
|
}
|
|
|
|
if let Some(batch) = batch_set.last_mut() {
|
|
batch.instance_range.end = instance.index + 1;
|
|
}
|
|
}
|
|
|
|
match phase.batch_sets {
|
|
BinnedRenderPhaseBatchSets::DynamicUniforms(ref mut batch_sets) => {
|
|
batch_sets.push(batch_set);
|
|
}
|
|
BinnedRenderPhaseBatchSets::Direct(_)
|
|
| BinnedRenderPhaseBatchSets::MultidrawIndirect { .. } => {
|
|
error!(
|
|
"Dynamic uniform batch sets should be used when GPU preprocessing is off"
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Prepare unbatchables.
|
|
for unbatchables in phase.unbatchable_meshes.values_mut() {
|
|
for main_entity in unbatchables.entities.keys() {
|
|
let Some(buffer_data) =
|
|
GFBD::get_binned_batch_data(&system_param_item, *main_entity)
|
|
else {
|
|
continue;
|
|
};
|
|
let instance = gpu_array_buffer.push(buffer_data);
|
|
unbatchables.buffer_indices.add(instance.into());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Writes the instance buffer data to the GPU.
|
|
pub fn write_batched_instance_buffer<GBD>(
|
|
render_device: Res<RenderDevice>,
|
|
render_queue: Res<RenderQueue>,
|
|
mut cpu_batched_instance_buffer: ResMut<BatchedInstanceBuffer<GBD::BufferData>>,
|
|
) where
|
|
GBD: GetBatchData,
|
|
{
|
|
cpu_batched_instance_buffer.write_buffer(&render_device, &render_queue);
|
|
}
|