Path: blob/main/crates/bevy_render/src/render_phase/mod.rs
9550 views
//! The modular rendering abstraction responsible for queuing, preparing, sorting and drawing1//! entities as part of separate render phases.2//!3//! In Bevy each view (camera, or shadow-casting light, etc.) has one or multiple render phases4//! (e.g. opaque, transparent, shadow, etc).5//! They are used to queue entities for rendering.6//! Multiple phases might be required due to different sorting/batching behaviors7//! (e.g. opaque: front to back, transparent: back to front) or because one phase depends on8//! the rendered texture of the previous phase (e.g. for screen-space reflections).9//!10//! To draw an entity, a corresponding [`PhaseItem`] has to be added to one or multiple of these11//! render phases for each view that it is visible in.12//! This must be done in the [`RenderSystems::Queue`].13//! After that the render phase sorts them in the [`RenderSystems::PhaseSort`].14//! Finally the items are rendered using a single [`TrackedRenderPass`], during15//! the [`RenderSystems::Render`].16//!17//! Therefore each phase item is assigned a [`Draw`] function.18//! These set up the state of the [`TrackedRenderPass`] (i.e. select the19//! [`RenderPipeline`](crate::render_resource::RenderPipeline), configure the20//! [`BindGroup`](crate::render_resource::BindGroup)s, etc.) and then issue a draw call,21//! for the corresponding item.22//!23//! The [`Draw`] function trait can either be implemented directly or such a function can be24//! created by composing multiple [`RenderCommand`]s.2526mod draw;27mod draw_state;28mod rangefinder;2930use bevy_app::{App, Plugin};31use bevy_derive::{Deref, DerefMut};32use bevy_ecs::change_detection::Tick;33use bevy_ecs::entity::EntityHash;34use bevy_platform::collections::{hash_map::Entry, HashMap};35use bevy_utils::default;36pub use draw::*;37pub use draw_state::*;38use encase::{internal::WriteInto, ShaderSize};39use fixedbitset::{Block, FixedBitSet};40use indexmap::IndexMap;41use nonmax::NonMaxU32;42pub use rangefinder::*;43use wgpu::Features;4445use crate::batching::gpu_preprocessing::{46GpuPreprocessingMode, GpuPreprocessingSupport, PhaseBatchedInstanceBuffers,47PhaseIndirectParametersBuffers,48};49use crate::renderer::RenderDevice;50use crate::sync_world::{MainEntity, MainEntityHashMap};51use crate::view::RetainedViewEntity;52use crate::RenderDebugFlags;53use bevy_material::descriptor::CachedRenderPipelineId;5455use crate::{56batching::{57self,58gpu_preprocessing::{self, BatchedInstanceBuffers},59no_gpu_preprocessing::{self, BatchedInstanceBuffer},60GetFullBatchData,61},62render_resource::{GpuArrayBufferIndex, PipelineCache},63Render, RenderApp, RenderSystems,64};65use bevy_ecs::{66prelude::*,67system::{lifetimeless::SRes, SystemParamItem},68};69use bevy_log::warn;70pub use bevy_material::labels::DrawFunctionId;71pub use bevy_material_macros::DrawFunctionLabel;72pub use bevy_material_macros::ShaderLabel;73use bevy_render::renderer::RenderAdapterInfo;74use core::{fmt::Debug, hash::Hash, iter, marker::PhantomData, ops::Range, slice::SliceIndex};75use smallvec::SmallVec;7677/// Stores the rendering instructions for a single phase that uses bins in all78/// views.79///80/// They're cleared out every frame, but storing them in a resource like this81/// allows us to reuse allocations.82#[derive(Resource, Deref, DerefMut)]83pub struct ViewBinnedRenderPhases<BPI>(pub HashMap<RetainedViewEntity, BinnedRenderPhase<BPI>>)84where85BPI: BinnedPhaseItem;8687/// A collection of all rendering instructions, that will be executed by the GPU, for a88/// single render phase for a single view.89///90/// Each view (camera, or shadow-casting light, etc.) can have one or multiple render phases.91/// They are used to queue entities for rendering.92/// Multiple phases might be required due to different sorting/batching behaviors93/// (e.g. opaque: front to back, transparent: back to front) or because one phase depends on94/// the rendered texture of the previous phase (e.g. for screen-space reflections).95/// All [`PhaseItem`]s are then rendered using a single [`TrackedRenderPass`].96/// The render pass might be reused for multiple phases to reduce GPU overhead.97///98/// This flavor of render phase is used for phases in which the ordering is less99/// critical: for example, `Opaque3d`. It's generally faster than the100/// alternative [`SortedRenderPhase`].101pub struct BinnedRenderPhase<BPI>102where103BPI: BinnedPhaseItem,104{105/// The multidrawable bins.106///107/// Each batch set key maps to a *batch set*, which in this case is a set of108/// meshes that can be drawn together in one multidraw call. Each batch set109/// is subdivided into *bins*, each of which represents a particular mesh.110/// Each bin contains the entity IDs of instances of that mesh.111///112/// So, for example, if there are two cubes and a sphere present in the113/// scene, we would generally have one batch set containing two bins,114/// assuming that the cubes and sphere meshes are allocated together and use115/// the same pipeline. The first bin, corresponding to the cubes, will have116/// two entities in it. The second bin, corresponding to the sphere, will117/// have one entity in it.118pub multidrawable_meshes: IndexMap<BPI::BatchSetKey, IndexMap<BPI::BinKey, RenderBin>>,119120/// The bins corresponding to batchable items that aren't multidrawable.121///122/// For multidrawable entities, use `multidrawable_meshes`; for123/// unbatchable entities, use `unbatchable_values`.124pub batchable_meshes: IndexMap<(BPI::BatchSetKey, BPI::BinKey), RenderBin>,125126/// The unbatchable bins.127///128/// Each entity here is rendered in a separate drawcall.129pub unbatchable_meshes: IndexMap<(BPI::BatchSetKey, BPI::BinKey), UnbatchableBinnedEntities>,130131/// Items in the bin that aren't meshes at all.132///133/// Bevy itself doesn't place anything in this list, but plugins or your app134/// can in order to execute custom drawing commands. Draw functions for each135/// entity are simply called in order at rendering time.136///137/// See the `custom_phase_item` example for an example of how to use this.138pub non_mesh_items: IndexMap<(BPI::BatchSetKey, BPI::BinKey), NonMeshEntities>,139140/// Information on each batch set.141///142/// A *batch set* is a set of entities that will be batched together unless143/// we're on a platform that doesn't support storage buffers (e.g. WebGL 2)144/// and differing dynamic uniform indices force us to break batches. On145/// platforms that support storage buffers, a batch set always consists of146/// at most one batch.147///148/// Multidrawable entities come first, then batchable entities, then149/// unbatchable entities.150pub(crate) batch_sets: BinnedRenderPhaseBatchSets<BPI::BinKey>,151152/// The batch and bin key for each entity.153///154/// We retain these so that, when the entity changes,155/// [`Self::sweep_old_entities`] can quickly find the bin it was located in156/// and remove it.157cached_entity_bin_keys: IndexMap<MainEntity, CachedBinnedEntity<BPI>, EntityHash>,158159/// The set of indices in [`Self::cached_entity_bin_keys`] that are160/// confirmed to be up to date.161///162/// Note that each bit in this bit set refers to an *index* in the163/// [`IndexMap`] (i.e. a bucket in the hash table). They aren't entity IDs.164valid_cached_entity_bin_keys: FixedBitSet,165166/// The set of entities that changed bins this frame.167///168/// An entity will only be present in this list if it was in one bin on the169/// previous frame and is in a new bin on this frame. Each list entry170/// specifies the bin the entity used to be in. We use this in order to171/// remove the entity from the old bin during172/// [`BinnedRenderPhase::sweep_old_entities`].173entities_that_changed_bins: Vec<EntityThatChangedBins<BPI>>,174/// The gpu preprocessing mode configured for the view this phase is associated175/// with.176gpu_preprocessing_mode: GpuPreprocessingMode,177}178179/// All entities that share a mesh and a material and can be batched as part of180/// a [`BinnedRenderPhase`].181#[derive(Default)]182pub struct RenderBin {183/// A list of the entities in each bin, along with their cached184/// [`InputUniformIndex`].185entities: IndexMap<MainEntity, InputUniformIndex, EntityHash>,186}187188/// Information that we track about an entity that was in one bin on the189/// previous frame and is in a different bin this frame.190struct EntityThatChangedBins<BPI>191where192BPI: BinnedPhaseItem,193{194/// The entity.195main_entity: MainEntity,196/// The key that identifies the bin that this entity used to be in.197old_cached_binned_entity: CachedBinnedEntity<BPI>,198}199200/// Information that we keep about an entity currently within a bin.201pub struct CachedBinnedEntity<BPI>202where203BPI: BinnedPhaseItem,204{205/// Information that we use to identify a cached entity in a bin.206pub cached_bin_key: Option<CachedBinKey<BPI>>,207/// The last modified tick of the entity.208///209/// We use this to detect when the entity needs to be invalidated.210pub change_tick: Tick,211}212213/// Information that we use to identify a cached entity in a bin.214pub struct CachedBinKey<BPI>215where216BPI: BinnedPhaseItem,217{218/// The key of the batch set containing the entity.219pub batch_set_key: BPI::BatchSetKey,220/// The key of the bin containing the entity.221pub bin_key: BPI::BinKey,222/// The type of render phase that we use to render the entity: multidraw,223/// plain batch, etc.224pub phase_type: BinnedRenderPhaseType,225}226227impl<BPI> Clone for CachedBinnedEntity<BPI>228where229BPI: BinnedPhaseItem,230{231fn clone(&self) -> Self {232CachedBinnedEntity {233cached_bin_key: self.cached_bin_key.clone(),234change_tick: self.change_tick,235}236}237}238239impl<BPI> Clone for CachedBinKey<BPI>240where241BPI: BinnedPhaseItem,242{243fn clone(&self) -> Self {244CachedBinKey {245batch_set_key: self.batch_set_key.clone(),246bin_key: self.bin_key.clone(),247phase_type: self.phase_type,248}249}250}251252impl<BPI> PartialEq for CachedBinKey<BPI>253where254BPI: BinnedPhaseItem,255{256fn eq(&self, other: &Self) -> bool {257self.batch_set_key == other.batch_set_key258&& self.bin_key == other.bin_key259&& self.phase_type == other.phase_type260}261}262263/// How we store and render the batch sets.264///265/// Each one of these corresponds to a [`GpuPreprocessingMode`].266pub enum BinnedRenderPhaseBatchSets<BK> {267/// Batches are grouped into batch sets based on dynamic uniforms.268///269/// This corresponds to [`GpuPreprocessingMode::None`].270DynamicUniforms(Vec<SmallVec<[BinnedRenderPhaseBatch; 1]>>),271272/// Batches are never grouped into batch sets.273///274/// This corresponds to [`GpuPreprocessingMode::PreprocessingOnly`].275Direct(Vec<BinnedRenderPhaseBatch>),276277/// Batches are grouped together into batch sets based on their ability to278/// be multi-drawn together.279///280/// This corresponds to [`GpuPreprocessingMode::Culling`].281MultidrawIndirect(Vec<BinnedRenderPhaseBatchSet<BK>>),282}283284/// A group of entities that will be batched together into a single multi-draw285/// call.286pub struct BinnedRenderPhaseBatchSet<BK> {287/// The first batch in this batch set.288pub(crate) first_batch: BinnedRenderPhaseBatch,289/// The key of the bin that the first batch corresponds to.290pub(crate) bin_key: BK,291/// The number of batches.292pub(crate) batch_count: u32,293/// The index of the batch set in the GPU buffer.294pub(crate) index: u32,295}296297impl<BK> BinnedRenderPhaseBatchSets<BK> {298fn clear(&mut self) {299match *self {300BinnedRenderPhaseBatchSets::DynamicUniforms(ref mut vec) => vec.clear(),301BinnedRenderPhaseBatchSets::Direct(ref mut vec) => vec.clear(),302BinnedRenderPhaseBatchSets::MultidrawIndirect(ref mut vec) => vec.clear(),303}304}305}306307/// Information about a single batch of entities rendered using binned phase308/// items.309#[derive(Debug)]310pub struct BinnedRenderPhaseBatch {311/// An entity that's *representative* of this batch.312///313/// Bevy uses this to fetch the mesh. It can be any entity in the batch.314pub representative_entity: (Entity, MainEntity),315/// The range of instance indices in this batch.316pub instance_range: Range<u32>,317318/// The dynamic offset of the batch.319///320/// Note that dynamic offsets are only used on platforms that don't support321/// storage buffers.322pub extra_index: PhaseItemExtraIndex,323}324325/// Information about the unbatchable entities in a bin.326pub struct UnbatchableBinnedEntities {327/// The entities.328pub entities: MainEntityHashMap<Entity>,329330/// The GPU array buffer indices of each unbatchable binned entity.331pub(crate) buffer_indices: UnbatchableBinnedEntityIndexSet,332}333334/// Information about [`BinnedRenderPhaseType::NonMesh`] entities.335pub struct NonMeshEntities {336/// The entities.337pub entities: MainEntityHashMap<Entity>,338}339340/// Stores instance indices and dynamic offsets for unbatchable entities in a341/// binned render phase.342///343/// This is conceptually `Vec<UnbatchableBinnedEntityDynamicOffset>`, but it344/// avoids the overhead of storing dynamic offsets on platforms that support345/// them. In other words, this allows a fast path that avoids allocation on346/// platforms that aren't WebGL 2.347#[derive(Default)]348349pub(crate) enum UnbatchableBinnedEntityIndexSet {350/// There are no unbatchable entities in this bin (yet).351#[default]352NoEntities,353354/// The instances for all unbatchable entities in this bin are contiguous,355/// and there are no dynamic uniforms.356///357/// This is the typical case on platforms other than WebGL 2. We special358/// case this to avoid allocation on those platforms.359Sparse {360/// The range of indices.361instance_range: Range<u32>,362/// The index of the first indirect instance parameters.363///364/// The other indices immediately follow these.365first_indirect_parameters_index: Option<NonMaxU32>,366},367368/// Dynamic uniforms are present for unbatchable entities in this bin.369///370/// We fall back to this on WebGL 2.371Dense(Vec<UnbatchableBinnedEntityIndices>),372}373374/// The instance index and dynamic offset (if present) for an unbatchable entity.375///376/// This is only useful on platforms that don't support storage buffers.377#[derive(Clone)]378pub(crate) struct UnbatchableBinnedEntityIndices {379/// The instance index.380pub(crate) instance_index: u32,381/// The [`PhaseItemExtraIndex`], if present.382pub(crate) extra_index: PhaseItemExtraIndex,383}384385/// Identifies the list within [`BinnedRenderPhase`] that a phase item is to be386/// placed in.387#[derive(Clone, Copy, PartialEq, Debug)]388pub enum BinnedRenderPhaseType {389/// The item is a mesh that's eligible for multi-draw indirect rendering and390/// can be batched with other meshes of the same type.391MultidrawableMesh,392393/// The item is a mesh that can be batched with other meshes of the same type and394/// drawn in a single draw call.395BatchableMesh,396397/// The item is a mesh that's eligible for indirect rendering, but can't be398/// batched with other meshes of the same type.399UnbatchableMesh,400401/// The item isn't a mesh at all.402///403/// Bevy will simply invoke the drawing commands for such items one after404/// another, with no further processing.405///406/// The engine itself doesn't enqueue any items of this type, but it's407/// available for use in your application and/or plugins.408NonMesh,409}410411impl<T> From<GpuArrayBufferIndex<T>> for UnbatchableBinnedEntityIndices412where413T: Clone + ShaderSize + WriteInto,414{415fn from(value: GpuArrayBufferIndex<T>) -> Self {416UnbatchableBinnedEntityIndices {417instance_index: value.index,418extra_index: PhaseItemExtraIndex::maybe_dynamic_offset(value.dynamic_offset),419}420}421}422423impl<BPI> Default for ViewBinnedRenderPhases<BPI>424where425BPI: BinnedPhaseItem,426{427fn default() -> Self {428Self(default())429}430}431432impl<BPI> ViewBinnedRenderPhases<BPI>433where434BPI: BinnedPhaseItem,435{436pub fn prepare_for_new_frame(437&mut self,438retained_view_entity: RetainedViewEntity,439gpu_preprocessing: GpuPreprocessingMode,440) {441match self.entry(retained_view_entity) {442Entry::Occupied(mut entry) => entry.get_mut().prepare_for_new_frame(),443Entry::Vacant(entry) => {444entry.insert(BinnedRenderPhase::<BPI>::new(gpu_preprocessing));445}446}447}448}449450/// The index of the uniform describing this object in the GPU buffer, when GPU451/// preprocessing is enabled.452///453/// For example, for 3D meshes, this is the index of the `MeshInputUniform` in454/// the buffer.455///456/// This field is ignored if GPU preprocessing isn't in use, such as (currently)457/// in the case of 2D meshes. In that case, it can be safely set to458/// [`core::default::Default::default`].459#[derive(Clone, Copy, PartialEq, Default, Deref, DerefMut)]460#[repr(transparent)]461pub struct InputUniformIndex(pub u32);462463impl<BPI> BinnedRenderPhase<BPI>464where465BPI: BinnedPhaseItem,466{467/// Bins a new entity.468///469/// The `phase_type` parameter specifies whether the entity is a470/// preprocessable mesh and whether it can be binned with meshes of the same471/// type.472pub fn add(473&mut self,474batch_set_key: BPI::BatchSetKey,475bin_key: BPI::BinKey,476(entity, main_entity): (Entity, MainEntity),477input_uniform_index: InputUniformIndex,478mut phase_type: BinnedRenderPhaseType,479change_tick: Tick,480) {481// If the user has overridden indirect drawing for this view, we need to482// force the phase type to be batchable instead.483if self.gpu_preprocessing_mode == GpuPreprocessingMode::PreprocessingOnly484&& phase_type == BinnedRenderPhaseType::MultidrawableMesh485{486phase_type = BinnedRenderPhaseType::BatchableMesh;487}488489match phase_type {490BinnedRenderPhaseType::MultidrawableMesh => {491match self.multidrawable_meshes.entry(batch_set_key.clone()) {492indexmap::map::Entry::Occupied(mut entry) => {493entry494.get_mut()495.entry(bin_key.clone())496.or_default()497.insert(main_entity, input_uniform_index);498}499indexmap::map::Entry::Vacant(entry) => {500let mut new_batch_set = IndexMap::default();501new_batch_set.insert(502bin_key.clone(),503RenderBin::from_entity(main_entity, input_uniform_index),504);505entry.insert(new_batch_set);506}507}508}509510BinnedRenderPhaseType::BatchableMesh => {511match self512.batchable_meshes513.entry((batch_set_key.clone(), bin_key.clone()).clone())514{515indexmap::map::Entry::Occupied(mut entry) => {516entry.get_mut().insert(main_entity, input_uniform_index);517}518indexmap::map::Entry::Vacant(entry) => {519entry.insert(RenderBin::from_entity(main_entity, input_uniform_index));520}521}522}523524BinnedRenderPhaseType::UnbatchableMesh => {525match self526.unbatchable_meshes527.entry((batch_set_key.clone(), bin_key.clone()))528{529indexmap::map::Entry::Occupied(mut entry) => {530entry.get_mut().entities.insert(main_entity, entity);531}532indexmap::map::Entry::Vacant(entry) => {533let mut entities = MainEntityHashMap::default();534entities.insert(main_entity, entity);535entry.insert(UnbatchableBinnedEntities {536entities,537buffer_indices: default(),538});539}540}541}542543BinnedRenderPhaseType::NonMesh => {544// We don't process these items further.545match self546.non_mesh_items547.entry((batch_set_key.clone(), bin_key.clone()).clone())548{549indexmap::map::Entry::Occupied(mut entry) => {550entry.get_mut().entities.insert(main_entity, entity);551}552indexmap::map::Entry::Vacant(entry) => {553let mut entities = MainEntityHashMap::default();554entities.insert(main_entity, entity);555entry.insert(NonMeshEntities { entities });556}557}558}559}560561// Update the cache.562self.update_cache(563main_entity,564Some(CachedBinKey {565batch_set_key,566bin_key,567phase_type,568}),569change_tick,570);571}572573/// Inserts an entity into the cache with the given change tick.574pub fn update_cache(575&mut self,576main_entity: MainEntity,577cached_bin_key: Option<CachedBinKey<BPI>>,578change_tick: Tick,579) {580let new_cached_binned_entity = CachedBinnedEntity {581cached_bin_key,582change_tick,583};584585let (index, old_cached_binned_entity) = self586.cached_entity_bin_keys587.insert_full(main_entity, new_cached_binned_entity.clone());588589// If the entity changed bins, record its old bin so that we can remove590// the entity from it.591if let Some(old_cached_binned_entity) = old_cached_binned_entity592&& old_cached_binned_entity.cached_bin_key != new_cached_binned_entity.cached_bin_key593{594self.entities_that_changed_bins.push(EntityThatChangedBins {595main_entity,596old_cached_binned_entity,597});598}599600// Mark the entity as valid.601self.valid_cached_entity_bin_keys.grow_and_insert(index);602}603604/// Encodes the GPU commands needed to render all entities in this phase.605pub fn render<'w>(606&self,607render_pass: &mut TrackedRenderPass<'w>,608world: &'w World,609view: Entity,610) -> Result<(), DrawError> {611{612let draw_functions = world.resource::<DrawFunctions<BPI>>();613let mut draw_functions = draw_functions.write();614draw_functions.prepare(world);615// Make sure to drop the reader-writer lock here to avoid recursive616// locks.617}618619self.render_batchable_meshes(render_pass, world, view)?;620self.render_unbatchable_meshes(render_pass, world, view)?;621self.render_non_meshes(render_pass, world, view)?;622623Ok(())624}625626/// Renders all batchable meshes queued in this phase.627fn render_batchable_meshes<'w>(628&self,629render_pass: &mut TrackedRenderPass<'w>,630world: &'w World,631view: Entity,632) -> Result<(), DrawError> {633let draw_functions = world.resource::<DrawFunctions<BPI>>();634let mut draw_functions = draw_functions.write();635636let render_device = world.resource::<RenderDevice>();637let render_adapter_info = world.resource::<RenderAdapterInfo>();638let multi_draw_indirect_count_supported = render_device639.features()640.contains(Features::MULTI_DRAW_INDIRECT_COUNT)641// TODO: https://github.com/gfx-rs/wgpu/issues/7974642&& !matches!(render_adapter_info.backend, wgpu::Backend::Dx12);643644match self.batch_sets {645BinnedRenderPhaseBatchSets::DynamicUniforms(ref batch_sets) => {646debug_assert_eq!(self.batchable_meshes.len(), batch_sets.len());647648for ((batch_set_key, bin_key), batch_set) in649self.batchable_meshes.keys().zip(batch_sets.iter())650{651for batch in batch_set {652let binned_phase_item = BPI::new(653batch_set_key.clone(),654bin_key.clone(),655batch.representative_entity,656batch.instance_range.clone(),657batch.extra_index.clone(),658);659660// Fetch the draw function.661let Some(draw_function) =662draw_functions.get_mut(binned_phase_item.draw_function())663else {664continue;665};666667draw_function.draw(world, render_pass, view, &binned_phase_item)?;668}669}670}671672BinnedRenderPhaseBatchSets::Direct(ref batch_set) => {673for (batch, (batch_set_key, bin_key)) in674batch_set.iter().zip(self.batchable_meshes.keys())675{676let binned_phase_item = BPI::new(677batch_set_key.clone(),678bin_key.clone(),679batch.representative_entity,680batch.instance_range.clone(),681batch.extra_index.clone(),682);683684// Fetch the draw function.685let Some(draw_function) =686draw_functions.get_mut(binned_phase_item.draw_function())687else {688continue;689};690691draw_function.draw(world, render_pass, view, &binned_phase_item)?;692}693}694695BinnedRenderPhaseBatchSets::MultidrawIndirect(ref batch_sets) => {696for (batch_set_key, batch_set) in self697.multidrawable_meshes698.keys()699.chain(700self.batchable_meshes701.keys()702.map(|(batch_set_key, _)| batch_set_key),703)704.zip(batch_sets.iter())705{706let batch = &batch_set.first_batch;707708let batch_set_index = if multi_draw_indirect_count_supported {709NonMaxU32::new(batch_set.index)710} else {711None712};713714let binned_phase_item = BPI::new(715batch_set_key.clone(),716batch_set.bin_key.clone(),717batch.representative_entity,718batch.instance_range.clone(),719match batch.extra_index {720PhaseItemExtraIndex::None => PhaseItemExtraIndex::None,721PhaseItemExtraIndex::DynamicOffset(ref dynamic_offset) => {722PhaseItemExtraIndex::DynamicOffset(*dynamic_offset)723}724PhaseItemExtraIndex::IndirectParametersIndex { ref range, .. } => {725PhaseItemExtraIndex::IndirectParametersIndex {726range: range.start..(range.start + batch_set.batch_count),727batch_set_index,728}729}730},731);732733// Fetch the draw function.734let Some(draw_function) =735draw_functions.get_mut(binned_phase_item.draw_function())736else {737continue;738};739740draw_function.draw(world, render_pass, view, &binned_phase_item)?;741}742}743}744745Ok(())746}747748/// Renders all unbatchable meshes queued in this phase.749fn render_unbatchable_meshes<'w>(750&self,751render_pass: &mut TrackedRenderPass<'w>,752world: &'w World,753view: Entity,754) -> Result<(), DrawError> {755let draw_functions = world.resource::<DrawFunctions<BPI>>();756let mut draw_functions = draw_functions.write();757758for (batch_set_key, bin_key) in self.unbatchable_meshes.keys() {759let unbatchable_entities =760&self.unbatchable_meshes[&(batch_set_key.clone(), bin_key.clone())];761for (entity_index, entity) in unbatchable_entities.entities.iter().enumerate() {762let unbatchable_dynamic_offset = match &unbatchable_entities.buffer_indices {763UnbatchableBinnedEntityIndexSet::NoEntities => {764// Shouldn't happen…765continue;766}767UnbatchableBinnedEntityIndexSet::Sparse {768instance_range,769first_indirect_parameters_index,770} => UnbatchableBinnedEntityIndices {771instance_index: instance_range.start + entity_index as u32,772extra_index: match first_indirect_parameters_index {773None => PhaseItemExtraIndex::None,774Some(first_indirect_parameters_index) => {775let first_indirect_parameters_index_for_entity =776u32::from(*first_indirect_parameters_index)777+ entity_index as u32;778PhaseItemExtraIndex::IndirectParametersIndex {779range: first_indirect_parameters_index_for_entity780..(first_indirect_parameters_index_for_entity + 1),781batch_set_index: None,782}783}784},785},786UnbatchableBinnedEntityIndexSet::Dense(dynamic_offsets) => {787dynamic_offsets[entity_index].clone()788}789};790791let binned_phase_item = BPI::new(792batch_set_key.clone(),793bin_key.clone(),794(*entity.1, *entity.0),795unbatchable_dynamic_offset.instance_index796..(unbatchable_dynamic_offset.instance_index + 1),797unbatchable_dynamic_offset.extra_index,798);799800// Fetch the draw function.801let Some(draw_function) = draw_functions.get_mut(binned_phase_item.draw_function())802else {803continue;804};805806draw_function.draw(world, render_pass, view, &binned_phase_item)?;807}808}809Ok(())810}811812/// Renders all objects of type [`BinnedRenderPhaseType::NonMesh`].813///814/// These will have been added by plugins or the application.815fn render_non_meshes<'w>(816&self,817render_pass: &mut TrackedRenderPass<'w>,818world: &'w World,819view: Entity,820) -> Result<(), DrawError> {821let draw_functions = world.resource::<DrawFunctions<BPI>>();822let mut draw_functions = draw_functions.write();823824for ((batch_set_key, bin_key), non_mesh_entities) in &self.non_mesh_items {825for (main_entity, entity) in non_mesh_entities.entities.iter() {826// Come up with a fake batch range and extra index. The draw827// function is expected to manage any sort of batching logic itself.828let binned_phase_item = BPI::new(829batch_set_key.clone(),830bin_key.clone(),831(*entity, *main_entity),8320..1,833PhaseItemExtraIndex::None,834);835836let Some(draw_function) = draw_functions.get_mut(binned_phase_item.draw_function())837else {838continue;839};840841draw_function.draw(world, render_pass, view, &binned_phase_item)?;842}843}844845Ok(())846}847848pub fn is_empty(&self) -> bool {849self.multidrawable_meshes.is_empty()850&& self.batchable_meshes.is_empty()851&& self.unbatchable_meshes.is_empty()852&& self.non_mesh_items.is_empty()853}854855pub fn prepare_for_new_frame(&mut self) {856self.batch_sets.clear();857858self.valid_cached_entity_bin_keys.clear();859self.valid_cached_entity_bin_keys860.grow(self.cached_entity_bin_keys.len());861self.valid_cached_entity_bin_keys862.set_range(self.cached_entity_bin_keys.len().., true);863864self.entities_that_changed_bins.clear();865866for unbatchable_bin in self.unbatchable_meshes.values_mut() {867unbatchable_bin.buffer_indices.clear();868}869}870871/// Checks to see whether the entity is in a bin and returns true if it's872/// both in a bin and up to date.873///874/// If this function returns true, we also add the entry to the875/// `valid_cached_entity_bin_keys` list.876pub fn validate_cached_entity(877&mut self,878visible_entity: MainEntity,879current_change_tick: Tick,880) -> bool {881if let indexmap::map::Entry::Occupied(entry) =882self.cached_entity_bin_keys.entry(visible_entity)883&& entry.get().change_tick == current_change_tick884{885self.valid_cached_entity_bin_keys.insert(entry.index());886return true;887}888889false890}891892/// Removes all entities not marked as clean from the bins.893///894/// During `queue_material_meshes`, we process all visible entities and mark895/// each as clean as we come to it. Then, in [`sweep_old_entities`], we call896/// this method, which removes entities that aren't marked as clean from the897/// bins.898pub fn sweep_old_entities(&mut self) {899// Search for entities not marked as valid. We have to do this in900// reverse order because `swap_remove_index` will potentially invalidate901// all indices after the one we remove.902for index in ReverseFixedBitSetZeroesIterator::new(&self.valid_cached_entity_bin_keys) {903let Some((entity, cached_binned_entity)) =904self.cached_entity_bin_keys.swap_remove_index(index)905else {906continue;907};908909if let Some(ref cached_bin_key) = cached_binned_entity.cached_bin_key {910remove_entity_from_bin(911entity,912cached_bin_key,913&mut self.multidrawable_meshes,914&mut self.batchable_meshes,915&mut self.unbatchable_meshes,916&mut self.non_mesh_items,917);918}919}920921// If an entity changed bins, we need to remove it from its old bin.922for entity_that_changed_bins in self.entities_that_changed_bins.drain(..) {923let Some(ref old_cached_bin_key) = entity_that_changed_bins924.old_cached_binned_entity925.cached_bin_key926else {927continue;928};929remove_entity_from_bin(930entity_that_changed_bins.main_entity,931old_cached_bin_key,932&mut self.multidrawable_meshes,933&mut self.batchable_meshes,934&mut self.unbatchable_meshes,935&mut self.non_mesh_items,936);937}938}939}940941/// Removes an entity from a bin.942///943/// If this makes the bin empty, this function removes the bin as well.944///945/// This is a standalone function instead of a method on [`BinnedRenderPhase`]946/// for borrow check reasons.947fn remove_entity_from_bin<BPI>(948entity: MainEntity,949entity_bin_key: &CachedBinKey<BPI>,950multidrawable_meshes: &mut IndexMap<BPI::BatchSetKey, IndexMap<BPI::BinKey, RenderBin>>,951batchable_meshes: &mut IndexMap<(BPI::BatchSetKey, BPI::BinKey), RenderBin>,952unbatchable_meshes: &mut IndexMap<(BPI::BatchSetKey, BPI::BinKey), UnbatchableBinnedEntities>,953non_mesh_items: &mut IndexMap<(BPI::BatchSetKey, BPI::BinKey), NonMeshEntities>,954) where955BPI: BinnedPhaseItem,956{957match entity_bin_key.phase_type {958BinnedRenderPhaseType::MultidrawableMesh => {959if let indexmap::map::Entry::Occupied(mut batch_set_entry) =960multidrawable_meshes.entry(entity_bin_key.batch_set_key.clone())961{962if let indexmap::map::Entry::Occupied(mut bin_entry) = batch_set_entry963.get_mut()964.entry(entity_bin_key.bin_key.clone())965{966bin_entry.get_mut().remove(entity);967968// If the bin is now empty, remove the bin.969if bin_entry.get_mut().is_empty() {970bin_entry.swap_remove();971}972}973974// If the batch set is now empty, remove it. This will perturb975// the order, but that's OK because we're going to sort the bin976// afterwards.977if batch_set_entry.get_mut().is_empty() {978batch_set_entry.swap_remove();979}980}981}982983BinnedRenderPhaseType::BatchableMesh => {984if let indexmap::map::Entry::Occupied(mut bin_entry) = batchable_meshes.entry((985entity_bin_key.batch_set_key.clone(),986entity_bin_key.bin_key.clone(),987)) {988bin_entry.get_mut().remove(entity);989990// If the bin is now empty, remove the bin.991if bin_entry.get_mut().is_empty() {992bin_entry.swap_remove();993}994}995}996997BinnedRenderPhaseType::UnbatchableMesh => {998if let indexmap::map::Entry::Occupied(mut bin_entry) = unbatchable_meshes.entry((999entity_bin_key.batch_set_key.clone(),1000entity_bin_key.bin_key.clone(),1001)) {1002bin_entry.get_mut().entities.remove(&entity);10031004// If the bin is now empty, remove the bin.1005if bin_entry.get_mut().entities.is_empty() {1006bin_entry.swap_remove();1007}1008}1009}10101011BinnedRenderPhaseType::NonMesh => {1012if let indexmap::map::Entry::Occupied(mut bin_entry) = non_mesh_items.entry((1013entity_bin_key.batch_set_key.clone(),1014entity_bin_key.bin_key.clone(),1015)) {1016bin_entry.get_mut().entities.remove(&entity);10171018// If the bin is now empty, remove the bin.1019if bin_entry.get_mut().entities.is_empty() {1020bin_entry.swap_remove();1021}1022}1023}1024}1025}10261027impl<BPI> BinnedRenderPhase<BPI>1028where1029BPI: BinnedPhaseItem,1030{1031fn new(gpu_preprocessing: GpuPreprocessingMode) -> Self {1032Self {1033multidrawable_meshes: IndexMap::default(),1034batchable_meshes: IndexMap::default(),1035unbatchable_meshes: IndexMap::default(),1036non_mesh_items: IndexMap::default(),1037batch_sets: match gpu_preprocessing {1038GpuPreprocessingMode::Culling => {1039BinnedRenderPhaseBatchSets::MultidrawIndirect(vec![])1040}1041GpuPreprocessingMode::PreprocessingOnly => {1042BinnedRenderPhaseBatchSets::Direct(vec![])1043}1044GpuPreprocessingMode::None => BinnedRenderPhaseBatchSets::DynamicUniforms(vec![]),1045},1046cached_entity_bin_keys: IndexMap::default(),1047valid_cached_entity_bin_keys: FixedBitSet::new(),1048entities_that_changed_bins: vec![],1049gpu_preprocessing_mode: gpu_preprocessing,1050}1051}1052}10531054impl UnbatchableBinnedEntityIndexSet {1055/// Returns the [`UnbatchableBinnedEntityIndices`] for the given entity.1056fn indices_for_entity_index(1057&self,1058entity_index: u32,1059) -> Option<UnbatchableBinnedEntityIndices> {1060match self {1061UnbatchableBinnedEntityIndexSet::NoEntities => None,1062UnbatchableBinnedEntityIndexSet::Sparse { instance_range, .. }1063if entity_index >= instance_range.len() as u32 =>1064{1065None1066}1067UnbatchableBinnedEntityIndexSet::Sparse {1068instance_range,1069first_indirect_parameters_index: None,1070} => Some(UnbatchableBinnedEntityIndices {1071instance_index: instance_range.start + entity_index,1072extra_index: PhaseItemExtraIndex::None,1073}),1074UnbatchableBinnedEntityIndexSet::Sparse {1075instance_range,1076first_indirect_parameters_index: Some(first_indirect_parameters_index),1077} => {1078let first_indirect_parameters_index_for_this_batch =1079u32::from(*first_indirect_parameters_index) + entity_index;1080Some(UnbatchableBinnedEntityIndices {1081instance_index: instance_range.start + entity_index,1082extra_index: PhaseItemExtraIndex::IndirectParametersIndex {1083range: first_indirect_parameters_index_for_this_batch1084..(first_indirect_parameters_index_for_this_batch + 1),1085batch_set_index: None,1086},1087})1088}1089UnbatchableBinnedEntityIndexSet::Dense(indices) => {1090indices.get(entity_index as usize).cloned()1091}1092}1093}1094}10951096/// A convenient abstraction for adding all the systems necessary for a binned1097/// render phase to the render app.1098///1099/// This is the version used when the pipeline supports GPU preprocessing: e.g.1100/// 3D PBR meshes.1101pub struct BinnedRenderPhasePlugin<BPI, GFBD>1102where1103BPI: BinnedPhaseItem,1104GFBD: GetFullBatchData,1105{1106/// Debugging flags that can optionally be set when constructing the renderer.1107pub debug_flags: RenderDebugFlags,1108phantom: PhantomData<(BPI, GFBD)>,1109}11101111impl<BPI, GFBD> BinnedRenderPhasePlugin<BPI, GFBD>1112where1113BPI: BinnedPhaseItem,1114GFBD: GetFullBatchData,1115{1116pub fn new(debug_flags: RenderDebugFlags) -> Self {1117Self {1118debug_flags,1119phantom: PhantomData,1120}1121}1122}11231124impl<BPI, GFBD> Plugin for BinnedRenderPhasePlugin<BPI, GFBD>1125where1126BPI: BinnedPhaseItem,1127GFBD: GetFullBatchData + Sync + Send + 'static,1128{1129fn build(&self, app: &mut App) {1130let Some(render_app) = app.get_sub_app_mut(RenderApp) else {1131return;1132};11331134render_app1135.init_resource::<ViewBinnedRenderPhases<BPI>>()1136.allow_ambiguous_resource::<ViewBinnedRenderPhases<BPI>>()1137.init_resource::<PhaseBatchedInstanceBuffers<BPI, GFBD::BufferData>>()1138.insert_resource(PhaseIndirectParametersBuffers::<BPI>::new(1139self.debug_flags1140.contains(RenderDebugFlags::ALLOW_COPIES_FROM_INDIRECT_PARAMETERS),1141))1142.add_systems(1143Render,1144(1145batching::sort_binned_render_phase::<BPI>.in_set(RenderSystems::PhaseSort),1146(1147no_gpu_preprocessing::batch_and_prepare_binned_render_phase::<BPI, GFBD>1148.run_if(resource_exists::<BatchedInstanceBuffer<GFBD::BufferData>>),1149gpu_preprocessing::batch_and_prepare_binned_render_phase::<BPI, GFBD>1150.run_if(1151resource_exists::<1152BatchedInstanceBuffers<GFBD::BufferData, GFBD::BufferInputData>,1153>,1154),1155)1156.in_set(RenderSystems::PrepareResourcesBatchPhases),1157sweep_old_entities::<BPI>.in_set(RenderSystems::QueueSweep),1158gpu_preprocessing::collect_buffers_for_phase::<BPI, GFBD>1159.run_if(1160resource_exists::<1161BatchedInstanceBuffers<GFBD::BufferData, GFBD::BufferInputData>,1162>,1163)1164.in_set(RenderSystems::PrepareResourcesCollectPhaseBuffers),1165),1166);1167}1168}11691170/// Stores the rendering instructions for a single phase that sorts items in all1171/// views.1172///1173/// They're cleared out every frame, but storing them in a resource like this1174/// allows us to reuse allocations.1175#[derive(Resource, Deref, DerefMut)]1176pub struct ViewSortedRenderPhases<SPI>(pub HashMap<RetainedViewEntity, SortedRenderPhase<SPI>>)1177where1178SPI: SortedPhaseItem;11791180impl<SPI> Default for ViewSortedRenderPhases<SPI>1181where1182SPI: SortedPhaseItem,1183{1184fn default() -> Self {1185Self(default())1186}1187}11881189impl<SPI> ViewSortedRenderPhases<SPI>1190where1191SPI: SortedPhaseItem,1192{1193pub fn insert_or_clear(&mut self, retained_view_entity: RetainedViewEntity) {1194match self.entry(retained_view_entity) {1195Entry::Occupied(mut entry) => entry.get_mut().clear(),1196Entry::Vacant(entry) => {1197entry.insert(default());1198}1199}1200}1201}12021203/// A convenient abstraction for adding all the systems necessary for a sorted1204/// render phase to the render app.1205///1206/// This is the version used when the pipeline supports GPU preprocessing: e.g.1207/// 3D PBR meshes.1208pub struct SortedRenderPhasePlugin<SPI, GFBD>1209where1210SPI: SortedPhaseItem,1211GFBD: GetFullBatchData,1212{1213/// Debugging flags that can optionally be set when constructing the renderer.1214pub debug_flags: RenderDebugFlags,1215phantom: PhantomData<(SPI, GFBD)>,1216}12171218impl<SPI, GFBD> SortedRenderPhasePlugin<SPI, GFBD>1219where1220SPI: SortedPhaseItem,1221GFBD: GetFullBatchData,1222{1223pub fn new(debug_flags: RenderDebugFlags) -> Self {1224Self {1225debug_flags,1226phantom: PhantomData,1227}1228}1229}12301231impl<SPI, GFBD> Plugin for SortedRenderPhasePlugin<SPI, GFBD>1232where1233SPI: SortedPhaseItem + CachedRenderPipelinePhaseItem,1234GFBD: GetFullBatchData + Sync + Send + 'static,1235{1236fn build(&self, app: &mut App) {1237let Some(render_app) = app.get_sub_app_mut(RenderApp) else {1238return;1239};12401241render_app1242.init_resource::<ViewSortedRenderPhases<SPI>>()1243.allow_ambiguous_resource::<ViewSortedRenderPhases<SPI>>()1244.init_resource::<PhaseBatchedInstanceBuffers<SPI, GFBD::BufferData>>()1245.insert_resource(PhaseIndirectParametersBuffers::<SPI>::new(1246self.debug_flags1247.contains(RenderDebugFlags::ALLOW_COPIES_FROM_INDIRECT_PARAMETERS),1248))1249.add_systems(1250Render,1251(1252(1253no_gpu_preprocessing::batch_and_prepare_sorted_render_phase::<SPI, GFBD>1254.run_if(resource_exists::<BatchedInstanceBuffer<GFBD::BufferData>>),1255gpu_preprocessing::batch_and_prepare_sorted_render_phase::<SPI, GFBD>1256.run_if(1257resource_exists::<1258BatchedInstanceBuffers<GFBD::BufferData, GFBD::BufferInputData>,1259>,1260),1261)1262.in_set(RenderSystems::PrepareResourcesBatchPhases),1263gpu_preprocessing::collect_buffers_for_phase::<SPI, GFBD>1264.run_if(1265resource_exists::<1266BatchedInstanceBuffers<GFBD::BufferData, GFBD::BufferInputData>,1267>,1268)1269.in_set(RenderSystems::PrepareResourcesCollectPhaseBuffers),1270),1271);1272}1273}12741275impl UnbatchableBinnedEntityIndexSet {1276/// Adds a new entity to the list of unbatchable binned entities.1277pub fn add(&mut self, indices: UnbatchableBinnedEntityIndices) {1278match self {1279UnbatchableBinnedEntityIndexSet::NoEntities => {1280match indices.extra_index {1281PhaseItemExtraIndex::DynamicOffset(_) => {1282// This is the first entity we've seen, and we don't have1283// compute shaders. Initialize an array.1284*self = UnbatchableBinnedEntityIndexSet::Dense(vec![indices]);1285}1286PhaseItemExtraIndex::None => {1287// This is the first entity we've seen, and we have compute1288// shaders. Initialize the fast path.1289*self = UnbatchableBinnedEntityIndexSet::Sparse {1290instance_range: indices.instance_index..indices.instance_index + 1,1291first_indirect_parameters_index: None,1292}1293}1294PhaseItemExtraIndex::IndirectParametersIndex {1295range: ref indirect_parameters_index,1296..1297} => {1298// This is the first entity we've seen, and we have compute1299// shaders. Initialize the fast path.1300*self = UnbatchableBinnedEntityIndexSet::Sparse {1301instance_range: indices.instance_index..indices.instance_index + 1,1302first_indirect_parameters_index: NonMaxU32::new(1303indirect_parameters_index.start,1304),1305}1306}1307}1308}13091310UnbatchableBinnedEntityIndexSet::Sparse {1311instance_range,1312first_indirect_parameters_index,1313} if instance_range.end == indices.instance_index1314&& ((first_indirect_parameters_index.is_none()1315&& indices.extra_index == PhaseItemExtraIndex::None)1316|| first_indirect_parameters_index.is_some_and(1317|first_indirect_parameters_index| match indices.extra_index {1318PhaseItemExtraIndex::IndirectParametersIndex {1319range: ref this_range,1320..1321} => {1322u32::from(first_indirect_parameters_index) + instance_range.end1323- instance_range.start1324== this_range.start1325}1326PhaseItemExtraIndex::DynamicOffset(_) | PhaseItemExtraIndex::None => {1327false1328}1329},1330)) =>1331{1332// This is the normal case on non-WebGL 2.1333instance_range.end += 1;1334}13351336UnbatchableBinnedEntityIndexSet::Sparse { instance_range, .. } => {1337// We thought we were in non-WebGL 2 mode, but we got a dynamic1338// offset or non-contiguous index anyway. This shouldn't happen,1339// but let's go ahead and do the sensible thing anyhow: demote1340// the compressed `NoDynamicOffsets` field to the full1341// `DynamicOffsets` array.1342warn!(1343"Unbatchable binned entity index set was demoted from sparse to dense. \1344This is a bug in the renderer. Please report it.",1345);1346let new_dynamic_offsets = (0..instance_range.len() as u32)1347.flat_map(|entity_index| self.indices_for_entity_index(entity_index))1348.chain(iter::once(indices))1349.collect();1350*self = UnbatchableBinnedEntityIndexSet::Dense(new_dynamic_offsets);1351}13521353UnbatchableBinnedEntityIndexSet::Dense(dense_indices) => {1354dense_indices.push(indices);1355}1356}1357}13581359/// Clears the unbatchable binned entity index set.1360fn clear(&mut self) {1361match self {1362UnbatchableBinnedEntityIndexSet::Dense(dense_indices) => dense_indices.clear(),1363UnbatchableBinnedEntityIndexSet::Sparse { .. } => {1364*self = UnbatchableBinnedEntityIndexSet::NoEntities;1365}1366_ => {}1367}1368}1369}13701371/// A collection of all items to be rendered that will be encoded to GPU1372/// commands for a single render phase for a single view.1373///1374/// Each view (camera, or shadow-casting light, etc.) can have one or multiple render phases.1375/// They are used to queue entities for rendering.1376/// Multiple phases might be required due to different sorting/batching behaviors1377/// (e.g. opaque: front to back, transparent: back to front) or because one phase depends on1378/// the rendered texture of the previous phase (e.g. for screen-space reflections).1379/// All [`PhaseItem`]s are then rendered using a single [`TrackedRenderPass`].1380/// The render pass might be reused for multiple phases to reduce GPU overhead.1381///1382/// This flavor of render phase is used only for meshes that need to be sorted1383/// back-to-front, such as transparent meshes. For items that don't need strict1384/// sorting, [`BinnedRenderPhase`] is preferred, for performance.1385pub struct SortedRenderPhase<I>1386where1387I: SortedPhaseItem,1388{1389/// The items within this [`SortedRenderPhase`].1390pub items: Vec<I>,1391}13921393impl<I> Default for SortedRenderPhase<I>1394where1395I: SortedPhaseItem,1396{1397fn default() -> Self {1398Self { items: Vec::new() }1399}1400}14011402impl<I> SortedRenderPhase<I>1403where1404I: SortedPhaseItem,1405{1406/// Adds a [`PhaseItem`] to this render phase.1407#[inline]1408pub fn add(&mut self, item: I) {1409self.items.push(item);1410}14111412/// Removes all [`PhaseItem`]s from this render phase.1413#[inline]1414pub fn clear(&mut self) {1415self.items.clear();1416}14171418/// Sorts all of its [`PhaseItem`]s.1419pub fn sort(&mut self) {1420I::sort(&mut self.items);1421}14221423/// An [`Iterator`] through the associated [`Entity`] for each [`PhaseItem`] in order.1424#[inline]1425pub fn iter_entities(&'_ self) -> impl Iterator<Item = Entity> + '_ {1426self.items.iter().map(PhaseItem::entity)1427}14281429/// Renders all of its [`PhaseItem`]s using their corresponding draw functions.1430pub fn render<'w>(1431&self,1432render_pass: &mut TrackedRenderPass<'w>,1433world: &'w World,1434view: Entity,1435) -> Result<(), DrawError> {1436self.render_range(render_pass, world, view, ..)1437}14381439/// Renders all [`PhaseItem`]s in the provided `range` (based on their index in `self.items`) using their corresponding draw functions.1440pub fn render_range<'w>(1441&self,1442render_pass: &mut TrackedRenderPass<'w>,1443world: &'w World,1444view: Entity,1445range: impl SliceIndex<[I], Output = [I]>,1446) -> Result<(), DrawError> {1447let items = self1448.items1449.get(range)1450.expect("`Range` provided to `render_range()` is out of bounds");14511452let draw_functions = world.resource::<DrawFunctions<I>>();1453let mut draw_functions = draw_functions.write();1454draw_functions.prepare(world);14551456let mut index = 0;1457while index < items.len() {1458let item = &items[index];1459let batch_range = item.batch_range();1460if batch_range.is_empty() {1461index += 1;1462} else {1463let draw_function = draw_functions.get_mut(item.draw_function()).unwrap();1464draw_function.draw(world, render_pass, view, item)?;1465index += batch_range.len();1466}1467}1468Ok(())1469}1470}14711472/// An item (entity of the render world) which will be drawn to a texture or the screen,1473/// as part of a render phase.1474///1475/// The data required for rendering an entity is extracted from the main world in the1476/// [`ExtractSchedule`](crate::ExtractSchedule).1477/// Then it has to be queued up for rendering during the [`RenderSystems::Queue`],1478/// by adding a corresponding phase item to a render phase.1479/// Afterwards it will be possibly sorted and rendered automatically in the1480/// [`RenderSystems::PhaseSort`] and [`RenderSystems::Render`], respectively.1481///1482/// `PhaseItem`s come in two flavors: [`BinnedPhaseItem`]s and1483/// [`SortedPhaseItem`]s.1484///1485/// * Binned phase items have a `BinKey` which specifies what bin they're to be1486/// placed in. All items in the same bin are eligible to be batched together.1487/// The `BinKey`s are sorted, but the individual bin items aren't. Binned phase1488/// items are good for opaque meshes, in which the order of rendering isn't1489/// important. Generally, binned phase items are faster than sorted phase items.1490///1491/// * Sorted phase items, on the other hand, are placed into one large buffer1492/// and then sorted all at once. This is needed for transparent meshes, which1493/// have to be sorted back-to-front to render with the painter's algorithm.1494/// These types of phase items are generally slower than binned phase items.1495pub trait PhaseItem: Sized + Send + Sync + 'static {1496/// Whether or not this `PhaseItem` should be subjected to automatic batching. (Default: `true`)1497const AUTOMATIC_BATCHING: bool = true;14981499/// The corresponding entity that will be drawn.1500///1501/// This is used to fetch the render data of the entity, required by the draw function,1502/// from the render world .1503fn entity(&self) -> Entity;15041505/// The main world entity represented by this `PhaseItem`.1506fn main_entity(&self) -> MainEntity;15071508/// Specifies the [`Draw`] function used to render the item.1509fn draw_function(&self) -> DrawFunctionId;15101511/// The range of instances that the batch covers. After doing a batched draw, batch range1512/// length phase items will be skipped. This design is to avoid having to restructure the1513/// render phase unnecessarily.1514fn batch_range(&self) -> &Range<u32>;1515fn batch_range_mut(&mut self) -> &mut Range<u32>;15161517/// Returns the [`PhaseItemExtraIndex`].1518///1519/// If present, this is either a dynamic offset or an indirect parameters1520/// index.1521fn extra_index(&self) -> PhaseItemExtraIndex;15221523/// Returns a pair of mutable references to both the batch range and extra1524/// index.1525fn batch_range_and_extra_index_mut(&mut self) -> (&mut Range<u32>, &mut PhaseItemExtraIndex);1526}15271528/// The "extra index" associated with some [`PhaseItem`]s, alongside the1529/// indirect instance index.1530///1531/// Sometimes phase items require another index in addition to the range of1532/// instances they already have. These can be:1533///1534/// * The *dynamic offset*: a `wgpu` dynamic offset into the uniform buffer of1535/// instance data. This is used on platforms that don't support storage1536/// buffers, to work around uniform buffer size limitations.1537///1538/// * The *indirect parameters index*: an index into the buffer that specifies1539/// the indirect parameters for this [`PhaseItem`]'s drawcall. This is used when1540/// indirect mode is on (as used for GPU culling).1541///1542/// Note that our indirect draw functionality requires storage buffers, so it's1543/// impossible to have both a dynamic offset and an indirect parameters index.1544/// This convenient fact allows us to pack both indices into a single `u32`.1545#[derive(Clone, PartialEq, Eq, Hash, Debug)]1546pub enum PhaseItemExtraIndex {1547/// No extra index is present.1548None,1549/// A `wgpu` dynamic offset into the uniform buffer of instance data. This1550/// is used on platforms that don't support storage buffers, to work around1551/// uniform buffer size limitations.1552DynamicOffset(u32),1553/// An index into the buffer that specifies the indirect parameters for this1554/// [`PhaseItem`]'s drawcall. This is used when indirect mode is on (as used1555/// for GPU culling).1556IndirectParametersIndex {1557/// The range of indirect parameters within the indirect parameters array.1558///1559/// If we're using `multi_draw_indirect_count`, this specifies the1560/// maximum range of indirect parameters within that array. If batches1561/// are ultimately culled out on the GPU, the actual number of draw1562/// commands might be lower than the length of this range.1563range: Range<u32>,1564/// If `multi_draw_indirect_count` is in use, and this phase item is1565/// part of a batch set, specifies the index of the batch set that this1566/// phase item is a part of.1567///1568/// If `multi_draw_indirect_count` isn't in use, or this phase item1569/// isn't part of a batch set, this is `None`.1570batch_set_index: Option<NonMaxU32>,1571},1572}15731574impl PhaseItemExtraIndex {1575/// Returns either an indirect parameters index or1576/// [`PhaseItemExtraIndex::None`], as appropriate.1577pub fn maybe_indirect_parameters_index(1578indirect_parameters_index: Option<NonMaxU32>,1579) -> PhaseItemExtraIndex {1580match indirect_parameters_index {1581Some(indirect_parameters_index) => PhaseItemExtraIndex::IndirectParametersIndex {1582range: u32::from(indirect_parameters_index)1583..(u32::from(indirect_parameters_index) + 1),1584batch_set_index: None,1585},1586None => PhaseItemExtraIndex::None,1587}1588}15891590/// Returns either a dynamic offset index or [`PhaseItemExtraIndex::None`],1591/// as appropriate.1592pub fn maybe_dynamic_offset(dynamic_offset: Option<NonMaxU32>) -> PhaseItemExtraIndex {1593match dynamic_offset {1594Some(dynamic_offset) => PhaseItemExtraIndex::DynamicOffset(dynamic_offset.into()),1595None => PhaseItemExtraIndex::None,1596}1597}1598}15991600/// Represents phase items that are placed into bins. The `BinKey` specifies1601/// which bin they're to be placed in. Bin keys are sorted, and items within the1602/// same bin are eligible to be batched together. The elements within the bins1603/// aren't themselves sorted.1604///1605/// An example of a binned phase item is `Opaque3d`, for which the rendering1606/// order isn't critical.1607pub trait BinnedPhaseItem: PhaseItem {1608/// The key used for binning [`PhaseItem`]s into bins. Order the members of1609/// [`BinnedPhaseItem::BinKey`] by the order of binding for best1610/// performance. For example, pipeline id, draw function id, mesh asset id,1611/// lowest variable bind group id such as the material bind group id, and1612/// its dynamic offsets if any, next bind group and offsets, etc. This1613/// reduces the need for rebinding between bins and improves performance.1614type BinKey: Clone + Send + Sync + PartialEq + Eq + Ord + Hash;16151616/// The key used to combine batches into batch sets.1617///1618/// A *batch set* is a set of meshes that can potentially be multi-drawn1619/// together.1620type BatchSetKey: PhaseItemBatchSetKey;16211622/// Creates a new binned phase item from the key and per-entity data.1623///1624/// Unlike [`SortedPhaseItem`]s, this is generally called "just in time"1625/// before rendering. The resulting phase item isn't stored in any data1626/// structures, resulting in significant memory savings.1627fn new(1628batch_set_key: Self::BatchSetKey,1629bin_key: Self::BinKey,1630representative_entity: (Entity, MainEntity),1631batch_range: Range<u32>,1632extra_index: PhaseItemExtraIndex,1633) -> Self;1634}16351636/// A key used to combine batches into batch sets.1637///1638/// A *batch set* is a set of meshes that can potentially be multi-drawn1639/// together.1640pub trait PhaseItemBatchSetKey: Clone + Send + Sync + PartialEq + Eq + Ord + Hash {1641/// Returns true if this batch set key describes indexed meshes or false if1642/// it describes non-indexed meshes.1643///1644/// Bevy uses this in order to determine which kind of indirect draw1645/// parameters to use, if indirect drawing is enabled.1646fn indexed(&self) -> bool;1647}16481649/// Represents phase items that must be sorted. The `SortKey` specifies the1650/// order that these items are drawn in. These are placed into a single array,1651/// and the array as a whole is then sorted.1652///1653/// An example of a sorted phase item is `Transparent3d`, which must be sorted1654/// back to front in order to correctly render with the painter's algorithm.1655pub trait SortedPhaseItem: PhaseItem {1656/// The type used for ordering the items. The smallest values are drawn first.1657/// This order can be calculated using the [`ViewRangefinder3d`],1658/// based on the view-space `Z` value of the corresponding view matrix.1659type SortKey: Ord;16601661/// Determines the order in which the items are drawn.1662fn sort_key(&self) -> Self::SortKey;16631664/// Sorts a slice of phase items into render order. Generally if the same type1665/// is batched this should use a stable sort like [`slice::sort_by_key`].1666/// In almost all other cases, this should not be altered from the default,1667/// which uses an unstable sort, as this provides the best balance of CPU and GPU1668/// performance.1669///1670/// Implementers can optionally not sort the list at all. This is generally advisable if and1671/// only if the renderer supports a depth prepass, which is by default not supported by1672/// the rest of Bevy's first party rendering crates. Even then, this may have a negative1673/// impact on GPU-side performance due to overdraw.1674///1675/// It's advised to always profile for performance changes when changing this implementation.1676#[inline]1677fn sort(items: &mut [Self]) {1678items.sort_unstable_by_key(Self::sort_key);1679}16801681/// Whether this phase item targets indexed meshes (those with both vertex1682/// and index buffers as opposed to just vertex buffers).1683///1684/// Bevy needs this information in order to properly group phase items1685/// together for multi-draw indirect, because the GPU layout of indirect1686/// commands differs between indexed and non-indexed meshes.1687///1688/// If you're implementing a custom phase item that doesn't describe a mesh,1689/// you can safely return false here.1690fn indexed(&self) -> bool;1691}16921693/// A [`PhaseItem`] item, that automatically sets the appropriate render pipeline,1694/// cached in the [`PipelineCache`].1695///1696/// You can use the [`SetItemPipeline`] render command to set the pipeline for this item.1697pub trait CachedRenderPipelinePhaseItem: PhaseItem {1698/// The id of the render pipeline, cached in the [`PipelineCache`], that will be used to draw1699/// this phase item.1700fn cached_pipeline(&self) -> CachedRenderPipelineId;1701}17021703/// A [`RenderCommand`] that sets the pipeline for the [`CachedRenderPipelinePhaseItem`].1704pub struct SetItemPipeline;17051706impl<P: CachedRenderPipelinePhaseItem> RenderCommand<P> for SetItemPipeline {1707type Param = SRes<PipelineCache>;1708type ViewQuery = ();1709type ItemQuery = ();1710#[inline]1711fn render<'w>(1712item: &P,1713_view: (),1714_entity: Option<()>,1715pipeline_cache: SystemParamItem<'w, '_, Self::Param>,1716pass: &mut TrackedRenderPass<'w>,1717) -> RenderCommandResult {1718if let Some(pipeline) = pipeline_cache1719.into_inner()1720.get_render_pipeline(item.cached_pipeline())1721{1722pass.set_render_pipeline(pipeline);1723RenderCommandResult::Success1724} else {1725RenderCommandResult::Skip1726}1727}1728}17291730/// This system sorts the [`PhaseItem`]s of all [`SortedRenderPhase`]s of this1731/// type.1732pub fn sort_phase_system<I>(mut render_phases: ResMut<ViewSortedRenderPhases<I>>)1733where1734I: SortedPhaseItem,1735{1736for phase in render_phases.values_mut() {1737phase.sort();1738}1739}17401741/// Removes entities that became invisible or changed phases from the bins.1742///1743/// This must run after queuing.1744pub fn sweep_old_entities<BPI>(mut render_phases: ResMut<ViewBinnedRenderPhases<BPI>>)1745where1746BPI: BinnedPhaseItem,1747{1748for phase in render_phases.0.values_mut() {1749phase.sweep_old_entities();1750}1751}17521753impl BinnedRenderPhaseType {1754pub fn mesh(1755batchable: bool,1756gpu_preprocessing_support: &GpuPreprocessingSupport,1757) -> BinnedRenderPhaseType {1758match (batchable, gpu_preprocessing_support.max_supported_mode) {1759(true, GpuPreprocessingMode::Culling) => BinnedRenderPhaseType::MultidrawableMesh,1760(true, _) => BinnedRenderPhaseType::BatchableMesh,1761(false, _) => BinnedRenderPhaseType::UnbatchableMesh,1762}1763}1764}17651766impl RenderBin {1767/// Creates a [`RenderBin`] containing a single entity.1768fn from_entity(entity: MainEntity, uniform_index: InputUniformIndex) -> RenderBin {1769let mut entities = IndexMap::default();1770entities.insert(entity, uniform_index);1771RenderBin { entities }1772}17731774/// Inserts an entity into the bin.1775fn insert(&mut self, entity: MainEntity, uniform_index: InputUniformIndex) {1776self.entities.insert(entity, uniform_index);1777}17781779/// Removes an entity from the bin.1780fn remove(&mut self, entity_to_remove: MainEntity) {1781self.entities.swap_remove(&entity_to_remove);1782}17831784/// Returns true if the bin contains no entities.1785fn is_empty(&self) -> bool {1786self.entities.is_empty()1787}17881789/// Returns the [`IndexMap`] containing all the entities in the bin, along1790/// with the cached [`InputUniformIndex`] of each.1791#[inline]1792pub fn entities(&self) -> &IndexMap<MainEntity, InputUniformIndex, EntityHash> {1793&self.entities1794}1795}17961797/// An iterator that efficiently finds the indices of all zero bits in a1798/// [`FixedBitSet`] and returns them in reverse order.1799///1800/// [`FixedBitSet`] doesn't natively offer this functionality, so we have to1801/// implement it ourselves.1802#[derive(Debug)]1803struct ReverseFixedBitSetZeroesIterator<'a> {1804/// The bit set.1805bitset: &'a FixedBitSet,1806/// The next bit index we're going to scan when [`Iterator::next`] is1807/// called.1808bit_index: isize,1809}18101811impl<'a> ReverseFixedBitSetZeroesIterator<'a> {1812fn new(bitset: &'a FixedBitSet) -> ReverseFixedBitSetZeroesIterator<'a> {1813ReverseFixedBitSetZeroesIterator {1814bitset,1815bit_index: (bitset.len() as isize) - 1,1816}1817}1818}18191820impl<'a> Iterator for ReverseFixedBitSetZeroesIterator<'a> {1821type Item = usize;18221823fn next(&mut self) -> Option<usize> {1824while self.bit_index >= 0 {1825// Unpack the bit index into block and bit.1826let block_index = self.bit_index / (Block::BITS as isize);1827let bit_pos = self.bit_index % (Block::BITS as isize);18281829// Grab the block. Mask off all bits above the one we're scanning1830// from by setting them all to 1.1831let mut block = self.bitset.as_slice()[block_index as usize];1832if bit_pos + 1 < (Block::BITS as isize) {1833block |= (!0) << (bit_pos + 1);1834}18351836// Search for the next unset bit. Note that the `leading_ones`1837// function counts from the MSB to the LSB, so we need to flip it to1838// get the bit number.1839let pos = (Block::BITS as isize) - (block.leading_ones() as isize) - 1;18401841// If we found an unset bit, return it.1842if pos != -1 {1843let result = block_index * (Block::BITS as isize) + pos;1844self.bit_index = result - 1;1845return Some(result as usize);1846}18471848// Otherwise, go to the previous block.1849self.bit_index = block_index * (Block::BITS as isize) - 1;1850}18511852None1853}1854}18551856#[cfg(test)]1857mod test {1858use super::ReverseFixedBitSetZeroesIterator;1859use fixedbitset::FixedBitSet;1860use proptest::{collection::vec, prop_assert_eq, proptest};18611862proptest! {1863#[test]1864fn reverse_fixed_bit_set_zeroes_iterator(1865bits in vec(0usize..1024usize, 0usize..1024usize),1866size in 0usize..1024usize,1867) {1868// Build a random bit set.1869let mut bitset = FixedBitSet::new();1870bitset.grow(size);1871for bit in bits {1872if bit < size {1873bitset.set(bit, true);1874}1875}18761877// Iterate over the bit set backwards in a naive way, and check that1878// that iteration sequence corresponds to the optimized one.1879let mut iter = ReverseFixedBitSetZeroesIterator::new(&bitset);1880for bit_index in (0..size).rev() {1881if !bitset.contains(bit_index) {1882prop_assert_eq!(iter.next(), Some(bit_index));1883}1884}18851886prop_assert_eq!(iter.next(), None);1887}1888}1889}189018911892