mod draw;
mod draw_state;
mod rangefinder;
use bevy_app::{App, Plugin};
use bevy_derive::{Deref, DerefMut};
use bevy_utils::{default, hashbrown::hash_map::Entry, HashMap};
pub use draw::*;
pub use draw_state::*;
use encase::{internal::WriteInto, ShaderSize};
use nonmax::NonMaxU32;
pub use rangefinder::*;
use crate::sync_world::MainEntity;
use crate::{
batching::{
self,
gpu_preprocessing::{self, BatchedInstanceBuffers},
no_gpu_preprocessing::{self, BatchedInstanceBuffer},
GetFullBatchData,
},
render_resource::{CachedRenderPipelineId, GpuArrayBufferIndex, PipelineCache},
Render, RenderApp, RenderSet,
};
use bevy_ecs::{
entity::EntityHashMap,
prelude::*,
system::{lifetimeless::SRes, SystemParamItem},
};
use core::{
fmt::{self, Debug, Formatter},
hash::Hash,
iter,
marker::PhantomData,
ops::Range,
slice::SliceIndex,
};
use smallvec::SmallVec;
#[derive(Resource, Deref, DerefMut)]
pub struct ViewBinnedRenderPhases<BPI>(pub EntityHashMap<BinnedRenderPhase<BPI>>)
where
BPI: BinnedPhaseItem;
pub struct BinnedRenderPhase<BPI>
where
BPI: BinnedPhaseItem,
{
pub batchable_mesh_keys: Vec<BPI::BinKey>,
pub batchable_mesh_values: HashMap<BPI::BinKey, Vec<(Entity, MainEntity)>>,
pub unbatchable_mesh_keys: Vec<BPI::BinKey>,
pub unbatchable_mesh_values: HashMap<BPI::BinKey, UnbatchableBinnedEntities>,
pub non_mesh_items: Vec<(BPI::BinKey, (Entity, MainEntity))>,
pub(crate) batch_sets: Vec<SmallVec<[BinnedRenderPhaseBatch; 1]>>,
}
#[derive(Debug)]
pub struct BinnedRenderPhaseBatch {
pub representative_entity: (Entity, MainEntity),
pub instance_range: Range<u32>,
pub extra_index: PhaseItemExtraIndex,
}
pub struct UnbatchableBinnedEntities {
pub entities: Vec<(Entity, MainEntity)>,
pub(crate) buffer_indices: UnbatchableBinnedEntityIndexSet,
}
#[derive(Default)]
pub(crate) enum UnbatchableBinnedEntityIndexSet {
#[default]
NoEntities,
Sparse {
instance_range: Range<u32>,
first_indirect_parameters_index: Option<NonMaxU32>,
},
Dense(Vec<UnbatchableBinnedEntityIndices>),
}
#[derive(Clone, Copy)]
pub(crate) struct UnbatchableBinnedEntityIndices {
pub(crate) instance_index: u32,
pub(crate) extra_index: PhaseItemExtraIndex,
}
#[derive(Clone, Copy, PartialEq, Debug)]
pub enum BinnedRenderPhaseType {
BatchableMesh,
UnbatchableMesh,
NonMesh,
}
impl<T> From<GpuArrayBufferIndex<T>> for UnbatchableBinnedEntityIndices
where
T: Clone + ShaderSize + WriteInto,
{
fn from(value: GpuArrayBufferIndex<T>) -> Self {
UnbatchableBinnedEntityIndices {
instance_index: value.index,
extra_index: PhaseItemExtraIndex::maybe_dynamic_offset(value.dynamic_offset),
}
}
}
impl<BPI> Default for ViewBinnedRenderPhases<BPI>
where
BPI: BinnedPhaseItem,
{
fn default() -> Self {
Self(default())
}
}
impl<BPI> ViewBinnedRenderPhases<BPI>
where
BPI: BinnedPhaseItem,
{
pub fn insert_or_clear(&mut self, entity: Entity) {
match self.entry(entity) {
Entry::Occupied(mut entry) => entry.get_mut().clear(),
Entry::Vacant(entry) => {
entry.insert(default());
}
}
}
}
impl<BPI> BinnedRenderPhase<BPI>
where
BPI: BinnedPhaseItem,
{
pub fn add(
&mut self,
key: BPI::BinKey,
entity: (Entity, MainEntity),
phase_type: BinnedRenderPhaseType,
) {
match phase_type {
BinnedRenderPhaseType::BatchableMesh => {
match self.batchable_mesh_values.entry(key.clone()) {
Entry::Occupied(mut entry) => entry.get_mut().push(entity),
Entry::Vacant(entry) => {
self.batchable_mesh_keys.push(key);
entry.insert(vec![entity]);
}
}
}
BinnedRenderPhaseType::UnbatchableMesh => {
match self.unbatchable_mesh_values.entry(key.clone()) {
Entry::Occupied(mut entry) => entry.get_mut().entities.push(entity),
Entry::Vacant(entry) => {
self.unbatchable_mesh_keys.push(key);
entry.insert(UnbatchableBinnedEntities {
entities: vec![entity],
buffer_indices: default(),
});
}
}
}
BinnedRenderPhaseType::NonMesh => {
self.non_mesh_items.push((key, entity));
}
}
}
pub fn render<'w>(
&self,
render_pass: &mut TrackedRenderPass<'w>,
world: &'w World,
view: Entity,
) -> Result<(), DrawError> {
{
let draw_functions = world.resource::<DrawFunctions<BPI>>();
let mut draw_functions = draw_functions.write();
draw_functions.prepare(world);
}
self.render_batchable_meshes(render_pass, world, view)?;
self.render_unbatchable_meshes(render_pass, world, view)?;
self.render_non_meshes(render_pass, world, view)?;
Ok(())
}
fn render_batchable_meshes<'w>(
&self,
render_pass: &mut TrackedRenderPass<'w>,
world: &'w World,
view: Entity,
) -> Result<(), DrawError> {
let draw_functions = world.resource::<DrawFunctions<BPI>>();
let mut draw_functions = draw_functions.write();
debug_assert_eq!(self.batchable_mesh_keys.len(), self.batch_sets.len());
for (key, batch_set) in self.batchable_mesh_keys.iter().zip(self.batch_sets.iter()) {
for batch in batch_set {
let binned_phase_item = BPI::new(
key.clone(),
batch.representative_entity,
batch.instance_range.clone(),
batch.extra_index,
);
let Some(draw_function) = draw_functions.get_mut(binned_phase_item.draw_function())
else {
continue;
};
draw_function.draw(world, render_pass, view, &binned_phase_item)?;
}
}
Ok(())
}
fn render_unbatchable_meshes<'w>(
&self,
render_pass: &mut TrackedRenderPass<'w>,
world: &'w World,
view: Entity,
) -> Result<(), DrawError> {
let draw_functions = world.resource::<DrawFunctions<BPI>>();
let mut draw_functions = draw_functions.write();
for key in &self.unbatchable_mesh_keys {
let unbatchable_entities = &self.unbatchable_mesh_values[key];
for (entity_index, &entity) in unbatchable_entities.entities.iter().enumerate() {
let unbatchable_dynamic_offset = match &unbatchable_entities.buffer_indices {
UnbatchableBinnedEntityIndexSet::NoEntities => {
continue;
}
UnbatchableBinnedEntityIndexSet::Sparse {
instance_range,
first_indirect_parameters_index,
} => UnbatchableBinnedEntityIndices {
instance_index: instance_range.start + entity_index as u32,
extra_index: match first_indirect_parameters_index {
None => PhaseItemExtraIndex::NONE,
Some(first_indirect_parameters_index) => {
PhaseItemExtraIndex::indirect_parameters_index(
u32::from(*first_indirect_parameters_index)
+ entity_index as u32,
)
}
},
},
UnbatchableBinnedEntityIndexSet::Dense(ref dynamic_offsets) => {
dynamic_offsets[entity_index]
}
};
let binned_phase_item = BPI::new(
key.clone(),
entity,
unbatchable_dynamic_offset.instance_index
..(unbatchable_dynamic_offset.instance_index + 1),
unbatchable_dynamic_offset.extra_index,
);
let Some(draw_function) = draw_functions.get_mut(binned_phase_item.draw_function())
else {
continue;
};
draw_function.draw(world, render_pass, view, &binned_phase_item)?;
}
}
Ok(())
}
fn render_non_meshes<'w>(
&self,
render_pass: &mut TrackedRenderPass<'w>,
world: &'w World,
view: Entity,
) -> Result<(), DrawError> {
let draw_functions = world.resource::<DrawFunctions<BPI>>();
let mut draw_functions = draw_functions.write();
for &(ref key, entity) in &self.non_mesh_items {
let binned_phase_item = BPI::new(key.clone(), entity, 0..1, PhaseItemExtraIndex(0));
let Some(draw_function) = draw_functions.get_mut(binned_phase_item.draw_function())
else {
continue;
};
draw_function.draw(world, render_pass, view, &binned_phase_item)?;
}
Ok(())
}
pub fn is_empty(&self) -> bool {
self.batchable_mesh_keys.is_empty()
&& self.unbatchable_mesh_keys.is_empty()
&& self.non_mesh_items.is_empty()
}
pub fn clear(&mut self) {
self.batchable_mesh_keys.clear();
self.batchable_mesh_values.clear();
self.unbatchable_mesh_keys.clear();
self.unbatchable_mesh_values.clear();
self.non_mesh_items.clear();
self.batch_sets.clear();
}
}
impl<BPI> Default for BinnedRenderPhase<BPI>
where
BPI: BinnedPhaseItem,
{
fn default() -> Self {
Self {
batchable_mesh_keys: vec![],
batchable_mesh_values: HashMap::default(),
unbatchable_mesh_keys: vec![],
unbatchable_mesh_values: HashMap::default(),
non_mesh_items: vec![],
batch_sets: vec![],
}
}
}
impl UnbatchableBinnedEntityIndexSet {
fn indices_for_entity_index(
&self,
entity_index: u32,
) -> Option<UnbatchableBinnedEntityIndices> {
match self {
UnbatchableBinnedEntityIndexSet::NoEntities => None,
UnbatchableBinnedEntityIndexSet::Sparse { instance_range, .. }
if entity_index >= instance_range.len() as u32 =>
{
None
}
UnbatchableBinnedEntityIndexSet::Sparse {
instance_range,
first_indirect_parameters_index: None,
} => Some(UnbatchableBinnedEntityIndices {
instance_index: instance_range.start + entity_index,
extra_index: PhaseItemExtraIndex::NONE,
}),
UnbatchableBinnedEntityIndexSet::Sparse {
instance_range,
first_indirect_parameters_index: Some(first_indirect_parameters_index),
} => Some(UnbatchableBinnedEntityIndices {
instance_index: instance_range.start + entity_index,
extra_index: PhaseItemExtraIndex::indirect_parameters_index(
u32::from(*first_indirect_parameters_index) + entity_index,
),
}),
UnbatchableBinnedEntityIndexSet::Dense(ref indices) => {
indices.get(entity_index as usize).copied()
}
}
}
}
pub struct BinnedRenderPhasePlugin<BPI, GFBD>(PhantomData<(BPI, GFBD)>)
where
BPI: BinnedPhaseItem,
GFBD: GetFullBatchData;
impl<BPI, GFBD> Default for BinnedRenderPhasePlugin<BPI, GFBD>
where
BPI: BinnedPhaseItem,
GFBD: GetFullBatchData,
{
fn default() -> Self {
Self(PhantomData)
}
}
impl<BPI, GFBD> Plugin for BinnedRenderPhasePlugin<BPI, GFBD>
where
BPI: BinnedPhaseItem,
GFBD: GetFullBatchData + Sync + Send + 'static,
{
fn build(&self, app: &mut App) {
let Some(render_app) = app.get_sub_app_mut(RenderApp) else {
return;
};
render_app
.init_resource::<ViewBinnedRenderPhases<BPI>>()
.add_systems(
Render,
(
batching::sort_binned_render_phase::<BPI>.in_set(RenderSet::PhaseSort),
(
no_gpu_preprocessing::batch_and_prepare_binned_render_phase::<BPI, GFBD>
.run_if(resource_exists::<BatchedInstanceBuffer<GFBD::BufferData>>),
gpu_preprocessing::batch_and_prepare_binned_render_phase::<BPI, GFBD>
.run_if(
resource_exists::<
BatchedInstanceBuffers<GFBD::BufferData, GFBD::BufferInputData>,
>,
),
)
.in_set(RenderSet::PrepareResources),
),
);
}
}
#[derive(Resource, Deref, DerefMut)]
pub struct ViewSortedRenderPhases<SPI>(pub EntityHashMap<SortedRenderPhase<SPI>>)
where
SPI: SortedPhaseItem;
impl<SPI> Default for ViewSortedRenderPhases<SPI>
where
SPI: SortedPhaseItem,
{
fn default() -> Self {
Self(default())
}
}
impl<SPI> ViewSortedRenderPhases<SPI>
where
SPI: SortedPhaseItem,
{
pub fn insert_or_clear(&mut self, entity: Entity) {
match self.entry(entity) {
Entry::Occupied(mut entry) => entry.get_mut().clear(),
Entry::Vacant(entry) => {
entry.insert(default());
}
}
}
}
pub struct SortedRenderPhasePlugin<SPI, GFBD>(PhantomData<(SPI, GFBD)>)
where
SPI: SortedPhaseItem,
GFBD: GetFullBatchData;
impl<SPI, GFBD> Default for SortedRenderPhasePlugin<SPI, GFBD>
where
SPI: SortedPhaseItem,
GFBD: GetFullBatchData,
{
fn default() -> Self {
Self(PhantomData)
}
}
impl<SPI, GFBD> Plugin for SortedRenderPhasePlugin<SPI, GFBD>
where
SPI: SortedPhaseItem + CachedRenderPipelinePhaseItem,
GFBD: GetFullBatchData + Sync + Send + 'static,
{
fn build(&self, app: &mut App) {
let Some(render_app) = app.get_sub_app_mut(RenderApp) else {
return;
};
render_app
.init_resource::<ViewSortedRenderPhases<SPI>>()
.add_systems(
Render,
(
no_gpu_preprocessing::batch_and_prepare_sorted_render_phase::<SPI, GFBD>
.run_if(resource_exists::<BatchedInstanceBuffer<GFBD::BufferData>>),
gpu_preprocessing::batch_and_prepare_sorted_render_phase::<SPI, GFBD>.run_if(
resource_exists::<
BatchedInstanceBuffers<GFBD::BufferData, GFBD::BufferInputData>,
>,
),
)
.in_set(RenderSet::PrepareResources),
);
}
}
impl UnbatchableBinnedEntityIndexSet {
pub fn add(&mut self, indices: UnbatchableBinnedEntityIndices) {
match self {
UnbatchableBinnedEntityIndexSet::NoEntities => {
if indices.extra_index.is_dynamic_offset() {
*self = UnbatchableBinnedEntityIndexSet::Dense(vec![indices]);
} else {
*self = UnbatchableBinnedEntityIndexSet::Sparse {
instance_range: indices.instance_index..indices.instance_index + 1,
first_indirect_parameters_index: indices
.extra_index
.as_indirect_parameters_index()
.and_then(|index| NonMaxU32::try_from(index).ok()),
}
}
}
UnbatchableBinnedEntityIndexSet::Sparse {
ref mut instance_range,
first_indirect_parameters_index,
} if instance_range.end == indices.instance_index
&& ((first_indirect_parameters_index.is_none()
&& indices.extra_index == PhaseItemExtraIndex::NONE)
|| first_indirect_parameters_index.is_some_and(
|first_indirect_parameters_index| {
Some(
u32::from(first_indirect_parameters_index) + instance_range.end
- instance_range.start,
) == indices.extra_index.as_indirect_parameters_index()
},
)) =>
{
instance_range.end += 1;
}
UnbatchableBinnedEntityIndexSet::Sparse { instance_range, .. } => {
let new_dynamic_offsets = (0..instance_range.len() as u32)
.flat_map(|entity_index| self.indices_for_entity_index(entity_index))
.chain(iter::once(indices))
.collect();
*self = UnbatchableBinnedEntityIndexSet::Dense(new_dynamic_offsets);
}
UnbatchableBinnedEntityIndexSet::Dense(ref mut dense_indices) => {
dense_indices.push(indices);
}
}
}
}
pub struct SortedRenderPhase<I>
where
I: SortedPhaseItem,
{
pub items: Vec<I>,
}
impl<I> Default for SortedRenderPhase<I>
where
I: SortedPhaseItem,
{
fn default() -> Self {
Self { items: Vec::new() }
}
}
impl<I> SortedRenderPhase<I>
where
I: SortedPhaseItem,
{
#[inline]
pub fn add(&mut self, item: I) {
self.items.push(item);
}
#[inline]
pub fn clear(&mut self) {
self.items.clear();
}
pub fn sort(&mut self) {
I::sort(&mut self.items);
}
#[inline]
pub fn iter_entities(&'_ self) -> impl Iterator<Item = Entity> + '_ {
self.items.iter().map(PhaseItem::entity)
}
pub fn render<'w>(
&self,
render_pass: &mut TrackedRenderPass<'w>,
world: &'w World,
view: Entity,
) -> Result<(), DrawError> {
self.render_range(render_pass, world, view, ..)
}
pub fn render_range<'w>(
&self,
render_pass: &mut TrackedRenderPass<'w>,
world: &'w World,
view: Entity,
range: impl SliceIndex<[I], Output = [I]>,
) -> Result<(), DrawError> {
let items = self
.items
.get(range)
.expect("`Range` provided to `render_range()` is out of bounds");
let draw_functions = world.resource::<DrawFunctions<I>>();
let mut draw_functions = draw_functions.write();
draw_functions.prepare(world);
let mut index = 0;
while index < items.len() {
let item = &items[index];
let batch_range = item.batch_range();
if batch_range.is_empty() {
index += 1;
} else {
let draw_function = draw_functions.get_mut(item.draw_function()).unwrap();
draw_function.draw(world, render_pass, view, item)?;
index += batch_range.len();
}
}
Ok(())
}
}
pub trait PhaseItem: Sized + Send + Sync + 'static {
const AUTOMATIC_BATCHING: bool = true;
fn entity(&self) -> Entity;
fn main_entity(&self) -> MainEntity;
fn draw_function(&self) -> DrawFunctionId;
fn batch_range(&self) -> &Range<u32>;
fn batch_range_mut(&mut self) -> &mut Range<u32>;
fn extra_index(&self) -> PhaseItemExtraIndex;
fn batch_range_and_extra_index_mut(&mut self) -> (&mut Range<u32>, &mut PhaseItemExtraIndex);
}
#[derive(Clone, Copy, PartialEq, Eq, Hash)]
pub struct PhaseItemExtraIndex(pub u32);
impl Debug for PhaseItemExtraIndex {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
if self.is_dynamic_offset() {
write!(f, "DynamicOffset({})", self.offset())
} else if self.is_indirect_parameters_index() {
write!(f, "IndirectParametersIndex({})", self.offset())
} else {
write!(f, "None")
}
}
}
impl PhaseItemExtraIndex {
pub const INDIRECT_PARAMETER_INDEX: u32 = 1 << 31;
pub const OFFSET_MASK: u32 = Self::INDIRECT_PARAMETER_INDEX - 1;
pub const FLAGS_MASK: u32 = !Self::OFFSET_MASK;
pub const NONE: PhaseItemExtraIndex = PhaseItemExtraIndex(u32::MAX);
#[inline]
fn offset(&self) -> u32 {
self.0 & Self::OFFSET_MASK
}
#[inline]
fn is_dynamic_offset(&self) -> bool {
*self != Self::NONE && (self.0 & Self::INDIRECT_PARAMETER_INDEX) == 0
}
#[inline]
fn is_indirect_parameters_index(&self) -> bool {
*self != Self::NONE && (self.0 & Self::INDIRECT_PARAMETER_INDEX) != 0
}
#[inline]
pub fn indirect_parameters_index(indirect_parameter_index: u32) -> PhaseItemExtraIndex {
debug_assert_eq!(indirect_parameter_index & Self::FLAGS_MASK, 0);
PhaseItemExtraIndex(indirect_parameter_index | Self::INDIRECT_PARAMETER_INDEX)
}
#[inline]
pub fn maybe_indirect_parameters_index(
maybe_indirect_parameters_index: Option<NonMaxU32>,
) -> PhaseItemExtraIndex {
match maybe_indirect_parameters_index {
Some(indirect_parameters_index) => {
Self::indirect_parameters_index(indirect_parameters_index.into())
}
None => PhaseItemExtraIndex::NONE,
}
}
#[inline]
pub fn dynamic_offset(dynamic_offset: u32) -> PhaseItemExtraIndex {
debug_assert_eq!(dynamic_offset & Self::FLAGS_MASK, 0);
PhaseItemExtraIndex(dynamic_offset)
}
#[inline]
pub fn maybe_dynamic_offset(maybe_dynamic_offset: Option<NonMaxU32>) -> PhaseItemExtraIndex {
match maybe_dynamic_offset {
Some(dynamic_offset) => Self::dynamic_offset(dynamic_offset.into()),
None => PhaseItemExtraIndex::NONE,
}
}
#[inline]
pub fn as_dynamic_offset(&self) -> Option<NonMaxU32> {
if self.is_dynamic_offset() {
NonMaxU32::try_from(self.0 & Self::OFFSET_MASK).ok()
} else {
None
}
}
#[inline]
pub fn as_indirect_parameters_index(&self) -> Option<u32> {
if self.is_indirect_parameters_index() {
Some(self.0 & Self::OFFSET_MASK)
} else {
None
}
}
}
pub trait BinnedPhaseItem: PhaseItem {
type BinKey: Clone + Send + Sync + Eq + Ord + Hash;
fn new(
key: Self::BinKey,
representative_entity: (Entity, MainEntity),
batch_range: Range<u32>,
extra_index: PhaseItemExtraIndex,
) -> Self;
}
pub trait SortedPhaseItem: PhaseItem {
type SortKey: Ord;
fn sort_key(&self) -> Self::SortKey;
#[inline]
fn sort(items: &mut [Self]) {
items.sort_unstable_by_key(Self::sort_key);
}
}
pub trait CachedRenderPipelinePhaseItem: PhaseItem {
fn cached_pipeline(&self) -> CachedRenderPipelineId;
}
pub struct SetItemPipeline;
impl<P: CachedRenderPipelinePhaseItem> RenderCommand<P> for SetItemPipeline {
type Param = SRes<PipelineCache>;
type ViewQuery = ();
type ItemQuery = ();
#[inline]
fn render<'w>(
item: &P,
_view: (),
_entity: Option<()>,
pipeline_cache: SystemParamItem<'w, '_, Self::Param>,
pass: &mut TrackedRenderPass<'w>,
) -> RenderCommandResult {
if let Some(pipeline) = pipeline_cache
.into_inner()
.get_render_pipeline(item.cached_pipeline())
{
pass.set_render_pipeline(pipeline);
RenderCommandResult::Success
} else {
RenderCommandResult::Skip
}
}
}
pub fn sort_phase_system<I>(mut render_phases: ResMut<ViewSortedRenderPhases<I>>)
where
I: SortedPhaseItem,
{
for phase in render_phases.values_mut() {
phase.sort();
}
}
impl BinnedRenderPhaseType {
pub fn mesh(batchable: bool) -> BinnedRenderPhaseType {
if batchable {
BinnedRenderPhaseType::BatchableMesh
} else {
BinnedRenderPhaseType::UnbatchableMesh
}
}
}