1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126
use bevy_ecs::{
component::Component,
entity::Entity,
prelude::Res,
system::{Query, ResMut, StaticSystemParam, SystemParam, SystemParamItem},
};
use bevy_utils::nonmax::NonMaxU32;
use crate::{
render_phase::{CachedRenderPipelinePhaseItem, DrawFunctionId, RenderPhase},
render_resource::{CachedRenderPipelineId, GpuArrayBuffer, GpuArrayBufferable},
renderer::{RenderDevice, RenderQueue},
};
/// Add this component to mesh entities to disable automatic batching
#[derive(Component)]
pub struct NoAutomaticBatching;
/// Data necessary to be equal for two draw commands to be mergeable
///
/// This is based on the following assumptions:
/// - Only entities with prepared assets (pipelines, materials, meshes) are
/// queued to phases
/// - View bindings are constant across a phase for a given draw function as
/// phases are per-view
/// - `batch_and_prepare_render_phase` is the only system that performs this
/// batching and has sole responsibility for preparing the per-object data.
/// As such the mesh binding and dynamic offsets are assumed to only be
/// variable as a result of the `batch_and_prepare_render_phase` system, e.g.
/// due to having to split data across separate uniform bindings within the
/// same buffer due to the maximum uniform buffer binding size.
#[derive(PartialEq)]
struct BatchMeta<T: PartialEq> {
/// The pipeline id encompasses all pipeline configuration including vertex
/// buffers and layouts, shaders and their specializations, bind group
/// layouts, etc.
pipeline_id: CachedRenderPipelineId,
/// The draw function id defines the RenderCommands that are called to
/// set the pipeline and bindings, and make the draw command
draw_function_id: DrawFunctionId,
dynamic_offset: Option<NonMaxU32>,
user_data: T,
}
impl<T: PartialEq> BatchMeta<T> {
fn new(item: &impl CachedRenderPipelinePhaseItem, user_data: T) -> Self {
BatchMeta {
pipeline_id: item.cached_pipeline(),
draw_function_id: item.draw_function(),
dynamic_offset: item.dynamic_offset(),
user_data,
}
}
}
/// A trait to support getting data used for batching draw commands via phase
/// items.
pub trait GetBatchData {
type Param: SystemParam + 'static;
/// Data used for comparison between phase items. If the pipeline id, draw
/// function id, per-instance data buffer dynamic offset and this data
/// matches, the draws can be batched.
type CompareData: PartialEq;
/// The per-instance data to be inserted into the [`GpuArrayBuffer`]
/// containing these data for all instances.
type BufferData: GpuArrayBufferable + Sync + Send + 'static;
/// Get the per-instance data to be inserted into the [`GpuArrayBuffer`].
/// If the instance can be batched, also return the data used for
/// comparison when deciding whether draws can be batched, else return None
/// for the `CompareData`.
fn get_batch_data(
param: &SystemParamItem<Self::Param>,
query_item: Entity,
) -> Option<(Self::BufferData, Option<Self::CompareData>)>;
}
/// Batch the items in a render phase. This means comparing metadata needed to draw each phase item
/// and trying to combine the draws into a batch.
pub fn batch_and_prepare_render_phase<I: CachedRenderPipelinePhaseItem, F: GetBatchData>(
gpu_array_buffer: ResMut<GpuArrayBuffer<F::BufferData>>,
mut views: Query<&mut RenderPhase<I>>,
param: StaticSystemParam<F::Param>,
) {
let gpu_array_buffer = gpu_array_buffer.into_inner();
let system_param_item = param.into_inner();
let mut process_item = |item: &mut I| {
let (buffer_data, compare_data) = F::get_batch_data(&system_param_item, item.entity())?;
let buffer_index = gpu_array_buffer.push(buffer_data);
let index = buffer_index.index.get();
*item.batch_range_mut() = index..index + 1;
*item.dynamic_offset_mut() = buffer_index.dynamic_offset;
if I::AUTOMATIC_BATCHING {
compare_data.map(|compare_data| BatchMeta::new(item, compare_data))
} else {
None
}
};
for mut phase in &mut views {
let items = phase.items.iter_mut().map(|item| {
let batch_data = process_item(item);
(item.batch_range_mut(), batch_data)
});
items.reduce(|(start_range, prev_batch_meta), (range, batch_meta)| {
if batch_meta.is_some() && prev_batch_meta == batch_meta {
start_range.end = range.end;
(start_range, prev_batch_meta)
} else {
(range, batch_meta)
}
});
}
}
pub fn write_batched_instance_buffer<F: GetBatchData>(
render_device: Res<RenderDevice>,
render_queue: Res<RenderQueue>,
gpu_array_buffer: ResMut<GpuArrayBuffer<F::BufferData>>,
) {
let gpu_array_buffer = gpu_array_buffer.into_inner();
gpu_array_buffer.write_buffer(&render_device, &render_queue);
gpu_array_buffer.clear();
}