mod draw;
mod draw_state;
mod rangefinder;
use bevy_app::{App, Plugin};
use bevy_derive::{Deref, DerefMut};
use bevy_ecs::change_detection::Tick;
use bevy_ecs::entity::EntityHash;
use bevy_platform::collections::{hash_map::Entry, HashMap};
use bevy_utils::default;
pub use draw::*;
pub use draw_state::*;
use encase::{internal::WriteInto, ShaderSize};
use fixedbitset::{Block, FixedBitSet};
use indexmap::IndexMap;
use nonmax::NonMaxU32;
pub use rangefinder::*;
use wgpu::Features;
use crate::batching::gpu_preprocessing::{
GpuPreprocessingMode, GpuPreprocessingSupport, PhaseBatchedInstanceBuffers,
PhaseIndirectParametersBuffers,
};
use crate::renderer::RenderDevice;
use crate::sync_world::{MainEntity, MainEntityHashMap};
use crate::view::RetainedViewEntity;
use crate::RenderDebugFlags;
use crate::{
batching::{
self,
gpu_preprocessing::{self, BatchedInstanceBuffers},
no_gpu_preprocessing::{self, BatchedInstanceBuffer},
GetFullBatchData,
},
render_resource::{CachedRenderPipelineId, GpuArrayBufferIndex, PipelineCache},
Render, RenderApp, RenderSystems,
};
use bevy_ecs::intern::Interned;
use bevy_ecs::{
define_label,
prelude::*,
system::{lifetimeless::SRes, SystemParamItem},
};
use bevy_render::renderer::RenderAdapterInfo;
pub use bevy_render_macros::ShaderLabel;
use core::{fmt::Debug, hash::Hash, iter, marker::PhantomData, ops::Range, slice::SliceIndex};
use smallvec::SmallVec;
use tracing::warn;
define_label!(
#[diagnostic::on_unimplemented(
note = "consider annotating `{Self}` with `#[derive(ShaderLabel)]`"
)]
ShaderLabel,
SHADER_LABEL_INTERNER
);
pub type InternedShaderLabel = Interned<dyn ShaderLabel>;
pub use bevy_render_macros::DrawFunctionLabel;
define_label!(
#[diagnostic::on_unimplemented(
note = "consider annotating `{Self}` with `#[derive(DrawFunctionLabel)]`"
)]
DrawFunctionLabel,
DRAW_FUNCTION_LABEL_INTERNER
);
pub type InternedDrawFunctionLabel = Interned<dyn DrawFunctionLabel>;
#[derive(Resource, Deref, DerefMut)]
pub struct ViewBinnedRenderPhases<BPI>(pub HashMap<RetainedViewEntity, BinnedRenderPhase<BPI>>)
where
BPI: BinnedPhaseItem;
pub struct BinnedRenderPhase<BPI>
where
BPI: BinnedPhaseItem,
{
pub multidrawable_meshes: IndexMap<BPI::BatchSetKey, IndexMap<BPI::BinKey, RenderBin>>,
pub batchable_meshes: IndexMap<(BPI::BatchSetKey, BPI::BinKey), RenderBin>,
pub unbatchable_meshes: IndexMap<(BPI::BatchSetKey, BPI::BinKey), UnbatchableBinnedEntities>,
pub non_mesh_items: IndexMap<(BPI::BatchSetKey, BPI::BinKey), NonMeshEntities>,
pub(crate) batch_sets: BinnedRenderPhaseBatchSets<BPI::BinKey>,
cached_entity_bin_keys: IndexMap<MainEntity, CachedBinnedEntity<BPI>, EntityHash>,
valid_cached_entity_bin_keys: FixedBitSet,
entities_that_changed_bins: Vec<EntityThatChangedBins<BPI>>,
gpu_preprocessing_mode: GpuPreprocessingMode,
}
#[derive(Default)]
pub struct RenderBin {
entities: IndexMap<MainEntity, InputUniformIndex, EntityHash>,
}
struct EntityThatChangedBins<BPI>
where
BPI: BinnedPhaseItem,
{
main_entity: MainEntity,
old_cached_binned_entity: CachedBinnedEntity<BPI>,
}
pub struct CachedBinnedEntity<BPI>
where
BPI: BinnedPhaseItem,
{
pub cached_bin_key: Option<CachedBinKey<BPI>>,
pub change_tick: Tick,
}
pub struct CachedBinKey<BPI>
where
BPI: BinnedPhaseItem,
{
pub batch_set_key: BPI::BatchSetKey,
pub bin_key: BPI::BinKey,
pub phase_type: BinnedRenderPhaseType,
}
impl<BPI> Clone for CachedBinnedEntity<BPI>
where
BPI: BinnedPhaseItem,
{
fn clone(&self) -> Self {
CachedBinnedEntity {
cached_bin_key: self.cached_bin_key.clone(),
change_tick: self.change_tick,
}
}
}
impl<BPI> Clone for CachedBinKey<BPI>
where
BPI: BinnedPhaseItem,
{
fn clone(&self) -> Self {
CachedBinKey {
batch_set_key: self.batch_set_key.clone(),
bin_key: self.bin_key.clone(),
phase_type: self.phase_type,
}
}
}
impl<BPI> PartialEq for CachedBinKey<BPI>
where
BPI: BinnedPhaseItem,
{
fn eq(&self, other: &Self) -> bool {
self.batch_set_key == other.batch_set_key
&& self.bin_key == other.bin_key
&& self.phase_type == other.phase_type
}
}
pub enum BinnedRenderPhaseBatchSets<BK> {
DynamicUniforms(Vec<SmallVec<[BinnedRenderPhaseBatch; 1]>>),
Direct(Vec<BinnedRenderPhaseBatch>),
MultidrawIndirect(Vec<BinnedRenderPhaseBatchSet<BK>>),
}
pub struct BinnedRenderPhaseBatchSet<BK> {
pub(crate) first_batch: BinnedRenderPhaseBatch,
pub(crate) bin_key: BK,
pub(crate) batch_count: u32,
pub(crate) index: u32,
}
impl<BK> BinnedRenderPhaseBatchSets<BK> {
fn clear(&mut self) {
match *self {
BinnedRenderPhaseBatchSets::DynamicUniforms(ref mut vec) => vec.clear(),
BinnedRenderPhaseBatchSets::Direct(ref mut vec) => vec.clear(),
BinnedRenderPhaseBatchSets::MultidrawIndirect(ref mut vec) => vec.clear(),
}
}
}
#[derive(Debug)]
pub struct BinnedRenderPhaseBatch {
pub representative_entity: (Entity, MainEntity),
pub instance_range: Range<u32>,
pub extra_index: PhaseItemExtraIndex,
}
pub struct UnbatchableBinnedEntities {
pub entities: MainEntityHashMap<Entity>,
pub(crate) buffer_indices: UnbatchableBinnedEntityIndexSet,
}
pub struct NonMeshEntities {
pub entities: MainEntityHashMap<Entity>,
}
#[derive(Default)]
pub(crate) enum UnbatchableBinnedEntityIndexSet {
#[default]
NoEntities,
Sparse {
instance_range: Range<u32>,
first_indirect_parameters_index: Option<NonMaxU32>,
},
Dense(Vec<UnbatchableBinnedEntityIndices>),
}
#[derive(Clone)]
pub(crate) struct UnbatchableBinnedEntityIndices {
pub(crate) instance_index: u32,
pub(crate) extra_index: PhaseItemExtraIndex,
}
#[derive(Clone, Copy, PartialEq, Debug)]
pub enum BinnedRenderPhaseType {
MultidrawableMesh,
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 prepare_for_new_frame(
&mut self,
retained_view_entity: RetainedViewEntity,
gpu_preprocessing: GpuPreprocessingMode,
) {
match self.entry(retained_view_entity) {
Entry::Occupied(mut entry) => entry.get_mut().prepare_for_new_frame(),
Entry::Vacant(entry) => {
entry.insert(BinnedRenderPhase::<BPI>::new(gpu_preprocessing));
}
}
}
}
#[derive(Clone, Copy, PartialEq, Default, Deref, DerefMut)]
#[repr(transparent)]
pub struct InputUniformIndex(pub u32);
impl<BPI> BinnedRenderPhase<BPI>
where
BPI: BinnedPhaseItem,
{
pub fn add(
&mut self,
batch_set_key: BPI::BatchSetKey,
bin_key: BPI::BinKey,
(entity, main_entity): (Entity, MainEntity),
input_uniform_index: InputUniformIndex,
mut phase_type: BinnedRenderPhaseType,
change_tick: Tick,
) {
if self.gpu_preprocessing_mode == GpuPreprocessingMode::PreprocessingOnly
&& phase_type == BinnedRenderPhaseType::MultidrawableMesh
{
phase_type = BinnedRenderPhaseType::BatchableMesh;
}
match phase_type {
BinnedRenderPhaseType::MultidrawableMesh => {
match self.multidrawable_meshes.entry(batch_set_key.clone()) {
indexmap::map::Entry::Occupied(mut entry) => {
entry
.get_mut()
.entry(bin_key.clone())
.or_default()
.insert(main_entity, input_uniform_index);
}
indexmap::map::Entry::Vacant(entry) => {
let mut new_batch_set = IndexMap::default();
new_batch_set.insert(
bin_key.clone(),
RenderBin::from_entity(main_entity, input_uniform_index),
);
entry.insert(new_batch_set);
}
}
}
BinnedRenderPhaseType::BatchableMesh => {
match self
.batchable_meshes
.entry((batch_set_key.clone(), bin_key.clone()).clone())
{
indexmap::map::Entry::Occupied(mut entry) => {
entry.get_mut().insert(main_entity, input_uniform_index);
}
indexmap::map::Entry::Vacant(entry) => {
entry.insert(RenderBin::from_entity(main_entity, input_uniform_index));
}
}
}
BinnedRenderPhaseType::UnbatchableMesh => {
match self
.unbatchable_meshes
.entry((batch_set_key.clone(), bin_key.clone()))
{
indexmap::map::Entry::Occupied(mut entry) => {
entry.get_mut().entities.insert(main_entity, entity);
}
indexmap::map::Entry::Vacant(entry) => {
let mut entities = MainEntityHashMap::default();
entities.insert(main_entity, entity);
entry.insert(UnbatchableBinnedEntities {
entities,
buffer_indices: default(),
});
}
}
}
BinnedRenderPhaseType::NonMesh => {
match self
.non_mesh_items
.entry((batch_set_key.clone(), bin_key.clone()).clone())
{
indexmap::map::Entry::Occupied(mut entry) => {
entry.get_mut().entities.insert(main_entity, entity);
}
indexmap::map::Entry::Vacant(entry) => {
let mut entities = MainEntityHashMap::default();
entities.insert(main_entity, entity);
entry.insert(NonMeshEntities { entities });
}
}
}
}
self.update_cache(
main_entity,
Some(CachedBinKey {
batch_set_key,
bin_key,
phase_type,
}),
change_tick,
);
}
pub fn update_cache(
&mut self,
main_entity: MainEntity,
cached_bin_key: Option<CachedBinKey<BPI>>,
change_tick: Tick,
) {
let new_cached_binned_entity = CachedBinnedEntity {
cached_bin_key,
change_tick,
};
let (index, old_cached_binned_entity) = self
.cached_entity_bin_keys
.insert_full(main_entity, new_cached_binned_entity.clone());
if let Some(old_cached_binned_entity) = old_cached_binned_entity
&& old_cached_binned_entity.cached_bin_key != new_cached_binned_entity.cached_bin_key
{
self.entities_that_changed_bins.push(EntityThatChangedBins {
main_entity,
old_cached_binned_entity,
});
}
self.valid_cached_entity_bin_keys.grow_and_insert(index);
}
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();
let render_device = world.resource::<RenderDevice>();
let render_adapter_info = world.resource::<RenderAdapterInfo>();
let multi_draw_indirect_count_supported = render_device
.features()
.contains(Features::MULTI_DRAW_INDIRECT_COUNT)
&& !matches!(render_adapter_info.backend, wgpu::Backend::Dx12);
match self.batch_sets {
BinnedRenderPhaseBatchSets::DynamicUniforms(ref batch_sets) => {
debug_assert_eq!(self.batchable_meshes.len(), batch_sets.len());
for ((batch_set_key, bin_key), batch_set) in
self.batchable_meshes.keys().zip(batch_sets.iter())
{
for batch in batch_set {
let binned_phase_item = BPI::new(
batch_set_key.clone(),
bin_key.clone(),
batch.representative_entity,
batch.instance_range.clone(),
batch.extra_index.clone(),
);
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)?;
}
}
}
BinnedRenderPhaseBatchSets::Direct(ref batch_set) => {
for (batch, (batch_set_key, bin_key)) in
batch_set.iter().zip(self.batchable_meshes.keys())
{
let binned_phase_item = BPI::new(
batch_set_key.clone(),
bin_key.clone(),
batch.representative_entity,
batch.instance_range.clone(),
batch.extra_index.clone(),
);
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)?;
}
}
BinnedRenderPhaseBatchSets::MultidrawIndirect(ref batch_sets) => {
for (batch_set_key, batch_set) in self
.multidrawable_meshes
.keys()
.chain(
self.batchable_meshes
.keys()
.map(|(batch_set_key, _)| batch_set_key),
)
.zip(batch_sets.iter())
{
let batch = &batch_set.first_batch;
let batch_set_index = if multi_draw_indirect_count_supported {
NonMaxU32::new(batch_set.index)
} else {
None
};
let binned_phase_item = BPI::new(
batch_set_key.clone(),
batch_set.bin_key.clone(),
batch.representative_entity,
batch.instance_range.clone(),
match batch.extra_index {
PhaseItemExtraIndex::None => PhaseItemExtraIndex::None,
PhaseItemExtraIndex::DynamicOffset(ref dynamic_offset) => {
PhaseItemExtraIndex::DynamicOffset(*dynamic_offset)
}
PhaseItemExtraIndex::IndirectParametersIndex { ref range, .. } => {
PhaseItemExtraIndex::IndirectParametersIndex {
range: range.start..(range.start + batch_set.batch_count),
batch_set_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 (batch_set_key, bin_key) in self.unbatchable_meshes.keys() {
let unbatchable_entities =
&self.unbatchable_meshes[&(batch_set_key.clone(), bin_key.clone())];
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) => {
let first_indirect_parameters_index_for_entity =
u32::from(*first_indirect_parameters_index)
+ entity_index as u32;
PhaseItemExtraIndex::IndirectParametersIndex {
range: first_indirect_parameters_index_for_entity
..(first_indirect_parameters_index_for_entity + 1),
batch_set_index: None,
}
}
},
},
UnbatchableBinnedEntityIndexSet::Dense(dynamic_offsets) => {
dynamic_offsets[entity_index].clone()
}
};
let binned_phase_item = BPI::new(
batch_set_key.clone(),
bin_key.clone(),
(*entity.1, *entity.0),
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 ((batch_set_key, bin_key), non_mesh_entities) in &self.non_mesh_items {
for (main_entity, entity) in non_mesh_entities.entities.iter() {
let binned_phase_item = BPI::new(
batch_set_key.clone(),
bin_key.clone(),
(*entity, *main_entity),
0..1,
PhaseItemExtraIndex::None,
);
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.multidrawable_meshes.is_empty()
&& self.batchable_meshes.is_empty()
&& self.unbatchable_meshes.is_empty()
&& self.non_mesh_items.is_empty()
}
pub fn prepare_for_new_frame(&mut self) {
self.batch_sets.clear();
self.valid_cached_entity_bin_keys.clear();
self.valid_cached_entity_bin_keys
.grow(self.cached_entity_bin_keys.len());
self.valid_cached_entity_bin_keys
.set_range(self.cached_entity_bin_keys.len().., true);
self.entities_that_changed_bins.clear();
for unbatchable_bin in self.unbatchable_meshes.values_mut() {
unbatchable_bin.buffer_indices.clear();
}
}
pub fn validate_cached_entity(
&mut self,
visible_entity: MainEntity,
current_change_tick: Tick,
) -> bool {
if let indexmap::map::Entry::Occupied(entry) =
self.cached_entity_bin_keys.entry(visible_entity)
&& entry.get().change_tick == current_change_tick
{
self.valid_cached_entity_bin_keys.insert(entry.index());
return true;
}
false
}
pub fn sweep_old_entities(&mut self) {
for index in ReverseFixedBitSetZeroesIterator::new(&self.valid_cached_entity_bin_keys) {
let Some((entity, cached_binned_entity)) =
self.cached_entity_bin_keys.swap_remove_index(index)
else {
continue;
};
if let Some(ref cached_bin_key) = cached_binned_entity.cached_bin_key {
remove_entity_from_bin(
entity,
cached_bin_key,
&mut self.multidrawable_meshes,
&mut self.batchable_meshes,
&mut self.unbatchable_meshes,
&mut self.non_mesh_items,
);
}
}
for entity_that_changed_bins in self.entities_that_changed_bins.drain(..) {
let Some(ref old_cached_bin_key) = entity_that_changed_bins
.old_cached_binned_entity
.cached_bin_key
else {
continue;
};
remove_entity_from_bin(
entity_that_changed_bins.main_entity,
old_cached_bin_key,
&mut self.multidrawable_meshes,
&mut self.batchable_meshes,
&mut self.unbatchable_meshes,
&mut self.non_mesh_items,
);
}
}
}
fn remove_entity_from_bin<BPI>(
entity: MainEntity,
entity_bin_key: &CachedBinKey<BPI>,
multidrawable_meshes: &mut IndexMap<BPI::BatchSetKey, IndexMap<BPI::BinKey, RenderBin>>,
batchable_meshes: &mut IndexMap<(BPI::BatchSetKey, BPI::BinKey), RenderBin>,
unbatchable_meshes: &mut IndexMap<(BPI::BatchSetKey, BPI::BinKey), UnbatchableBinnedEntities>,
non_mesh_items: &mut IndexMap<(BPI::BatchSetKey, BPI::BinKey), NonMeshEntities>,
) where
BPI: BinnedPhaseItem,
{
match entity_bin_key.phase_type {
BinnedRenderPhaseType::MultidrawableMesh => {
if let indexmap::map::Entry::Occupied(mut batch_set_entry) =
multidrawable_meshes.entry(entity_bin_key.batch_set_key.clone())
{
if let indexmap::map::Entry::Occupied(mut bin_entry) = batch_set_entry
.get_mut()
.entry(entity_bin_key.bin_key.clone())
{
bin_entry.get_mut().remove(entity);
if bin_entry.get_mut().is_empty() {
bin_entry.swap_remove();
}
}
if batch_set_entry.get_mut().is_empty() {
batch_set_entry.swap_remove();
}
}
}
BinnedRenderPhaseType::BatchableMesh => {
if let indexmap::map::Entry::Occupied(mut bin_entry) = batchable_meshes.entry((
entity_bin_key.batch_set_key.clone(),
entity_bin_key.bin_key.clone(),
)) {
bin_entry.get_mut().remove(entity);
if bin_entry.get_mut().is_empty() {
bin_entry.swap_remove();
}
}
}
BinnedRenderPhaseType::UnbatchableMesh => {
if let indexmap::map::Entry::Occupied(mut bin_entry) = unbatchable_meshes.entry((
entity_bin_key.batch_set_key.clone(),
entity_bin_key.bin_key.clone(),
)) {
bin_entry.get_mut().entities.remove(&entity);
if bin_entry.get_mut().entities.is_empty() {
bin_entry.swap_remove();
}
}
}
BinnedRenderPhaseType::NonMesh => {
if let indexmap::map::Entry::Occupied(mut bin_entry) = non_mesh_items.entry((
entity_bin_key.batch_set_key.clone(),
entity_bin_key.bin_key.clone(),
)) {
bin_entry.get_mut().entities.remove(&entity);
if bin_entry.get_mut().entities.is_empty() {
bin_entry.swap_remove();
}
}
}
}
}
impl<BPI> BinnedRenderPhase<BPI>
where
BPI: BinnedPhaseItem,
{
fn new(gpu_preprocessing: GpuPreprocessingMode) -> Self {
Self {
multidrawable_meshes: IndexMap::default(),
batchable_meshes: IndexMap::default(),
unbatchable_meshes: IndexMap::default(),
non_mesh_items: IndexMap::default(),
batch_sets: match gpu_preprocessing {
GpuPreprocessingMode::Culling => {
BinnedRenderPhaseBatchSets::MultidrawIndirect(vec![])
}
GpuPreprocessingMode::PreprocessingOnly => {
BinnedRenderPhaseBatchSets::Direct(vec![])
}
GpuPreprocessingMode::None => BinnedRenderPhaseBatchSets::DynamicUniforms(vec![]),
},
cached_entity_bin_keys: IndexMap::default(),
valid_cached_entity_bin_keys: FixedBitSet::new(),
entities_that_changed_bins: vec![],
gpu_preprocessing_mode: gpu_preprocessing,
}
}
}
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),
} => {
let first_indirect_parameters_index_for_this_batch =
u32::from(*first_indirect_parameters_index) + entity_index;
Some(UnbatchableBinnedEntityIndices {
instance_index: instance_range.start + entity_index,
extra_index: PhaseItemExtraIndex::IndirectParametersIndex {
range: first_indirect_parameters_index_for_this_batch
..(first_indirect_parameters_index_for_this_batch + 1),
batch_set_index: None,
},
})
}
UnbatchableBinnedEntityIndexSet::Dense(indices) => {
indices.get(entity_index as usize).cloned()
}
}
}
}
pub struct BinnedRenderPhasePlugin<BPI, GFBD>
where
BPI: BinnedPhaseItem,
GFBD: GetFullBatchData,
{
pub debug_flags: RenderDebugFlags,
phantom: PhantomData<(BPI, GFBD)>,
}
impl<BPI, GFBD> BinnedRenderPhasePlugin<BPI, GFBD>
where
BPI: BinnedPhaseItem,
GFBD: GetFullBatchData,
{
pub fn new(debug_flags: RenderDebugFlags) -> Self {
Self {
debug_flags,
phantom: 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>>()
.init_resource::<PhaseBatchedInstanceBuffers<BPI, GFBD::BufferData>>()
.insert_resource(PhaseIndirectParametersBuffers::<BPI>::new(
self.debug_flags
.contains(RenderDebugFlags::ALLOW_COPIES_FROM_INDIRECT_PARAMETERS),
))
.add_systems(
Render,
(
batching::sort_binned_render_phase::<BPI>.in_set(RenderSystems::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(RenderSystems::PrepareResources),
sweep_old_entities::<BPI>.in_set(RenderSystems::QueueSweep),
gpu_preprocessing::collect_buffers_for_phase::<BPI, GFBD>
.run_if(
resource_exists::<
BatchedInstanceBuffers<GFBD::BufferData, GFBD::BufferInputData>,
>,
)
.in_set(RenderSystems::PrepareResourcesCollectPhaseBuffers),
),
);
}
}
#[derive(Resource, Deref, DerefMut)]
pub struct ViewSortedRenderPhases<SPI>(pub HashMap<RetainedViewEntity, 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, retained_view_entity: RetainedViewEntity) {
match self.entry(retained_view_entity) {
Entry::Occupied(mut entry) => entry.get_mut().clear(),
Entry::Vacant(entry) => {
entry.insert(default());
}
}
}
}
pub struct SortedRenderPhasePlugin<SPI, GFBD>
where
SPI: SortedPhaseItem,
GFBD: GetFullBatchData,
{
pub debug_flags: RenderDebugFlags,
phantom: PhantomData<(SPI, GFBD)>,
}
impl<SPI, GFBD> SortedRenderPhasePlugin<SPI, GFBD>
where
SPI: SortedPhaseItem,
GFBD: GetFullBatchData,
{
pub fn new(debug_flags: RenderDebugFlags) -> Self {
Self {
debug_flags,
phantom: 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>>()
.init_resource::<PhaseBatchedInstanceBuffers<SPI, GFBD::BufferData>>()
.insert_resource(PhaseIndirectParametersBuffers::<SPI>::new(
self.debug_flags
.contains(RenderDebugFlags::ALLOW_COPIES_FROM_INDIRECT_PARAMETERS),
))
.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(RenderSystems::PrepareResources),
gpu_preprocessing::collect_buffers_for_phase::<SPI, GFBD>
.run_if(
resource_exists::<
BatchedInstanceBuffers<GFBD::BufferData, GFBD::BufferInputData>,
>,
)
.in_set(RenderSystems::PrepareResourcesCollectPhaseBuffers),
),
);
}
}
impl UnbatchableBinnedEntityIndexSet {
pub fn add(&mut self, indices: UnbatchableBinnedEntityIndices) {
match self {
UnbatchableBinnedEntityIndexSet::NoEntities => {
match indices.extra_index {
PhaseItemExtraIndex::DynamicOffset(_) => {
*self = UnbatchableBinnedEntityIndexSet::Dense(vec![indices]);
}
PhaseItemExtraIndex::None => {
*self = UnbatchableBinnedEntityIndexSet::Sparse {
instance_range: indices.instance_index..indices.instance_index + 1,
first_indirect_parameters_index: None,
}
}
PhaseItemExtraIndex::IndirectParametersIndex {
range: ref indirect_parameters_index,
..
} => {
*self = UnbatchableBinnedEntityIndexSet::Sparse {
instance_range: indices.instance_index..indices.instance_index + 1,
first_indirect_parameters_index: NonMaxU32::new(
indirect_parameters_index.start,
),
}
}
}
}
UnbatchableBinnedEntityIndexSet::Sparse {
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| match indices.extra_index {
PhaseItemExtraIndex::IndirectParametersIndex {
range: ref this_range,
..
} => {
u32::from(first_indirect_parameters_index) + instance_range.end
- instance_range.start
== this_range.start
}
PhaseItemExtraIndex::DynamicOffset(_) | PhaseItemExtraIndex::None => {
false
}
},
)) =>
{
instance_range.end += 1;
}
UnbatchableBinnedEntityIndexSet::Sparse { instance_range, .. } => {
warn!(
"Unbatchable binned entity index set was demoted from sparse to dense. \
This is a bug in the renderer. Please report it.",
);
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(dense_indices) => {
dense_indices.push(indices);
}
}
}
fn clear(&mut self) {
match self {
UnbatchableBinnedEntityIndexSet::Dense(dense_indices) => dense_indices.clear(),
UnbatchableBinnedEntityIndexSet::Sparse { .. } => {
*self = UnbatchableBinnedEntityIndexSet::NoEntities;
}
_ => {}
}
}
}
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, PartialEq, Eq, Hash, Debug)]
pub enum PhaseItemExtraIndex {
None,
DynamicOffset(u32),
IndirectParametersIndex {
range: Range<u32>,
batch_set_index: Option<NonMaxU32>,
},
}
impl PhaseItemExtraIndex {
pub fn maybe_indirect_parameters_index(
indirect_parameters_index: Option<NonMaxU32>,
) -> PhaseItemExtraIndex {
match indirect_parameters_index {
Some(indirect_parameters_index) => PhaseItemExtraIndex::IndirectParametersIndex {
range: u32::from(indirect_parameters_index)
..(u32::from(indirect_parameters_index) + 1),
batch_set_index: None,
},
None => PhaseItemExtraIndex::None,
}
}
pub fn maybe_dynamic_offset(dynamic_offset: Option<NonMaxU32>) -> PhaseItemExtraIndex {
match dynamic_offset {
Some(dynamic_offset) => PhaseItemExtraIndex::DynamicOffset(dynamic_offset.into()),
None => PhaseItemExtraIndex::None,
}
}
}
pub trait BinnedPhaseItem: PhaseItem {
type BinKey: Clone + Send + Sync + PartialEq + Eq + Ord + Hash;
type BatchSetKey: PhaseItemBatchSetKey;
fn new(
batch_set_key: Self::BatchSetKey,
bin_key: Self::BinKey,
representative_entity: (Entity, MainEntity),
batch_range: Range<u32>,
extra_index: PhaseItemExtraIndex,
) -> Self;
}
pub trait PhaseItemBatchSetKey: Clone + Send + Sync + PartialEq + Eq + Ord + Hash {
fn indexed(&self) -> bool;
}
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);
}
fn indexed(&self) -> bool;
}
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();
}
}
pub fn sweep_old_entities<BPI>(mut render_phases: ResMut<ViewBinnedRenderPhases<BPI>>)
where
BPI: BinnedPhaseItem,
{
for phase in render_phases.0.values_mut() {
phase.sweep_old_entities();
}
}
impl BinnedRenderPhaseType {
pub fn mesh(
batchable: bool,
gpu_preprocessing_support: &GpuPreprocessingSupport,
) -> BinnedRenderPhaseType {
match (batchable, gpu_preprocessing_support.max_supported_mode) {
(true, GpuPreprocessingMode::Culling) => BinnedRenderPhaseType::MultidrawableMesh,
(true, _) => BinnedRenderPhaseType::BatchableMesh,
(false, _) => BinnedRenderPhaseType::UnbatchableMesh,
}
}
}
impl RenderBin {
fn from_entity(entity: MainEntity, uniform_index: InputUniformIndex) -> RenderBin {
let mut entities = IndexMap::default();
entities.insert(entity, uniform_index);
RenderBin { entities }
}
fn insert(&mut self, entity: MainEntity, uniform_index: InputUniformIndex) {
self.entities.insert(entity, uniform_index);
}
fn remove(&mut self, entity_to_remove: MainEntity) {
self.entities.swap_remove(&entity_to_remove);
}
fn is_empty(&self) -> bool {
self.entities.is_empty()
}
#[inline]
pub fn entities(&self) -> &IndexMap<MainEntity, InputUniformIndex, EntityHash> {
&self.entities
}
}
#[derive(Debug)]
struct ReverseFixedBitSetZeroesIterator<'a> {
bitset: &'a FixedBitSet,
bit_index: isize,
}
impl<'a> ReverseFixedBitSetZeroesIterator<'a> {
fn new(bitset: &'a FixedBitSet) -> ReverseFixedBitSetZeroesIterator<'a> {
ReverseFixedBitSetZeroesIterator {
bitset,
bit_index: (bitset.len() as isize) - 1,
}
}
}
impl<'a> Iterator for ReverseFixedBitSetZeroesIterator<'a> {
type Item = usize;
fn next(&mut self) -> Option<usize> {
while self.bit_index >= 0 {
let block_index = self.bit_index / (Block::BITS as isize);
let bit_pos = self.bit_index % (Block::BITS as isize);
let mut block = self.bitset.as_slice()[block_index as usize];
if bit_pos + 1 < (Block::BITS as isize) {
block |= (!0) << (bit_pos + 1);
}
let pos = (Block::BITS as isize) - (block.leading_ones() as isize) - 1;
if pos != -1 {
let result = block_index * (Block::BITS as isize) + pos;
self.bit_index = result - 1;
return Some(result as usize);
}
self.bit_index = block_index * (Block::BITS as isize) - 1;
}
None
}
}
#[cfg(test)]
mod test {
use super::ReverseFixedBitSetZeroesIterator;
use fixedbitset::FixedBitSet;
use proptest::{collection::vec, prop_assert_eq, proptest};
proptest! {
#[test]
fn reverse_fixed_bit_set_zeroes_iterator(
bits in vec(0usize..1024usize, 0usize..1024usize),
size in 0usize..1024usize,
) {
let mut bitset = FixedBitSet::new();
bitset.grow(size);
for bit in bits {
if bit < size {
bitset.set(bit, true);
}
}
let mut iter = ReverseFixedBitSetZeroesIterator::new(&bitset);
for bit_index in (0..size).rev() {
if !bitset.contains(bit_index) {
prop_assert_eq!(iter.next(), Some(bit_index));
}
}
prop_assert_eq!(iter.next(), None);
}
}
}