use bevy_ecs::{
component::Component,
prelude::Res,
query::{QueryItem, ReadOnlyWorldQuery},
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},
};
#[derive(Component)]
pub struct NoAutomaticBatching;
#[derive(PartialEq)]
struct BatchMeta<T: PartialEq> {
pipeline_id: CachedRenderPipelineId,
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,
}
}
}
pub trait GetBatchData {
type Param: SystemParam + 'static;
type Query: ReadOnlyWorldQuery;
type QueryFilter: ReadOnlyWorldQuery;
type CompareData: PartialEq;
type BufferData: GpuArrayBufferable + Sync + Send + 'static;
fn get_batch_data(
param: &SystemParamItem<Self::Param>,
query_item: &QueryItem<Self::Query>,
) -> (Self::BufferData, Option<Self::CompareData>);
}
pub fn batch_and_prepare_render_phase<I: CachedRenderPipelinePhaseItem, F: GetBatchData>(
gpu_array_buffer: ResMut<GpuArrayBuffer<F::BufferData>>,
mut views: Query<&mut RenderPhase<I>>,
query: Query<F::Query, F::QueryFilter>,
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 batch_query_item = query.get(item.entity()).ok()?;
let (buffer_data, compare_data) = F::get_batch_data(&system_param_item, &batch_query_item);
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();
}