Path: blob/main/crates/bevy_render/src/render_phase/mod.rs
6596 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::component::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 crate::{54batching::{55self,56gpu_preprocessing::{self, BatchedInstanceBuffers},57no_gpu_preprocessing::{self, BatchedInstanceBuffer},58GetFullBatchData,59},60render_resource::{CachedRenderPipelineId, GpuArrayBufferIndex, PipelineCache},61Render, RenderApp, RenderSystems,62};63use bevy_ecs::intern::Interned;64use bevy_ecs::{65define_label,66prelude::*,67system::{lifetimeless::SRes, SystemParamItem},68};69use bevy_render::renderer::RenderAdapterInfo;70pub use bevy_render_macros::ShaderLabel;71use core::{fmt::Debug, hash::Hash, iter, marker::PhantomData, ops::Range, slice::SliceIndex};72use smallvec::SmallVec;73use tracing::warn;7475define_label!(76#[diagnostic::on_unimplemented(77note = "consider annotating `{Self}` with `#[derive(ShaderLabel)]`"78)]79/// Labels used to uniquely identify types of material shaders80ShaderLabel,81SHADER_LABEL_INTERNER82);8384/// A shorthand for `Interned<dyn RenderSubGraph>`.85pub type InternedShaderLabel = Interned<dyn ShaderLabel>;8687pub use bevy_render_macros::DrawFunctionLabel;8889define_label!(90#[diagnostic::on_unimplemented(91note = "consider annotating `{Self}` with `#[derive(DrawFunctionLabel)]`"92)]93/// Labels used to uniquely identify types of material shaders94DrawFunctionLabel,95DRAW_FUNCTION_LABEL_INTERNER96);9798pub type InternedDrawFunctionLabel = Interned<dyn DrawFunctionLabel>;99100/// Stores the rendering instructions for a single phase that uses bins in all101/// views.102///103/// They're cleared out every frame, but storing them in a resource like this104/// allows us to reuse allocations.105#[derive(Resource, Deref, DerefMut)]106pub struct ViewBinnedRenderPhases<BPI>(pub HashMap<RetainedViewEntity, BinnedRenderPhase<BPI>>)107where108BPI: BinnedPhaseItem;109110/// A collection of all rendering instructions, that will be executed by the GPU, for a111/// single render phase for a single view.112///113/// Each view (camera, or shadow-casting light, etc.) can have one or multiple render phases.114/// They are used to queue entities for rendering.115/// Multiple phases might be required due to different sorting/batching behaviors116/// (e.g. opaque: front to back, transparent: back to front) or because one phase depends on117/// the rendered texture of the previous phase (e.g. for screen-space reflections).118/// All [`PhaseItem`]s are then rendered using a single [`TrackedRenderPass`].119/// The render pass might be reused for multiple phases to reduce GPU overhead.120///121/// This flavor of render phase is used for phases in which the ordering is less122/// critical: for example, `Opaque3d`. It's generally faster than the123/// alternative [`SortedRenderPhase`].124pub struct BinnedRenderPhase<BPI>125where126BPI: BinnedPhaseItem,127{128/// The multidrawable bins.129///130/// Each batch set key maps to a *batch set*, which in this case is a set of131/// meshes that can be drawn together in one multidraw call. Each batch set132/// is subdivided into *bins*, each of which represents a particular mesh.133/// Each bin contains the entity IDs of instances of that mesh.134///135/// So, for example, if there are two cubes and a sphere present in the136/// scene, we would generally have one batch set containing two bins,137/// assuming that the cubes and sphere meshes are allocated together and use138/// the same pipeline. The first bin, corresponding to the cubes, will have139/// two entities in it. The second bin, corresponding to the sphere, will140/// have one entity in it.141pub multidrawable_meshes: IndexMap<BPI::BatchSetKey, IndexMap<BPI::BinKey, RenderBin>>,142143/// The bins corresponding to batchable items that aren't multidrawable.144///145/// For multidrawable entities, use `multidrawable_meshes`; for146/// unbatchable entities, use `unbatchable_values`.147pub batchable_meshes: IndexMap<(BPI::BatchSetKey, BPI::BinKey), RenderBin>,148149/// The unbatchable bins.150///151/// Each entity here is rendered in a separate drawcall.152pub unbatchable_meshes: IndexMap<(BPI::BatchSetKey, BPI::BinKey), UnbatchableBinnedEntities>,153154/// Items in the bin that aren't meshes at all.155///156/// Bevy itself doesn't place anything in this list, but plugins or your app157/// can in order to execute custom drawing commands. Draw functions for each158/// entity are simply called in order at rendering time.159///160/// See the `custom_phase_item` example for an example of how to use this.161pub non_mesh_items: IndexMap<(BPI::BatchSetKey, BPI::BinKey), NonMeshEntities>,162163/// Information on each batch set.164///165/// A *batch set* is a set of entities that will be batched together unless166/// we're on a platform that doesn't support storage buffers (e.g. WebGL 2)167/// and differing dynamic uniform indices force us to break batches. On168/// platforms that support storage buffers, a batch set always consists of169/// at most one batch.170///171/// Multidrawable entities come first, then batchable entities, then172/// unbatchable entities.173pub(crate) batch_sets: BinnedRenderPhaseBatchSets<BPI::BinKey>,174175/// The batch and bin key for each entity.176///177/// We retain these so that, when the entity changes,178/// [`Self::sweep_old_entities`] can quickly find the bin it was located in179/// and remove it.180cached_entity_bin_keys: IndexMap<MainEntity, CachedBinnedEntity<BPI>, EntityHash>,181182/// The set of indices in [`Self::cached_entity_bin_keys`] that are183/// confirmed to be up to date.184///185/// Note that each bit in this bit set refers to an *index* in the186/// [`IndexMap`] (i.e. a bucket in the hash table). They aren't entity IDs.187valid_cached_entity_bin_keys: FixedBitSet,188189/// The set of entities that changed bins this frame.190///191/// An entity will only be present in this list if it was in one bin on the192/// previous frame and is in a new bin on this frame. Each list entry193/// specifies the bin the entity used to be in. We use this in order to194/// remove the entity from the old bin during195/// [`BinnedRenderPhase::sweep_old_entities`].196entities_that_changed_bins: Vec<EntityThatChangedBins<BPI>>,197/// The gpu preprocessing mode configured for the view this phase is associated198/// with.199gpu_preprocessing_mode: GpuPreprocessingMode,200}201202/// All entities that share a mesh and a material and can be batched as part of203/// a [`BinnedRenderPhase`].204#[derive(Default)]205pub struct RenderBin {206/// A list of the entities in each bin, along with their cached207/// [`InputUniformIndex`].208entities: IndexMap<MainEntity, InputUniformIndex, EntityHash>,209}210211/// Information that we track about an entity that was in one bin on the212/// previous frame and is in a different bin this frame.213struct EntityThatChangedBins<BPI>214where215BPI: BinnedPhaseItem,216{217/// The entity.218main_entity: MainEntity,219/// The key that identifies the bin that this entity used to be in.220old_cached_binned_entity: CachedBinnedEntity<BPI>,221}222223/// Information that we keep about an entity currently within a bin.224pub struct CachedBinnedEntity<BPI>225where226BPI: BinnedPhaseItem,227{228/// Information that we use to identify a cached entity in a bin.229pub cached_bin_key: Option<CachedBinKey<BPI>>,230/// The last modified tick of the entity.231///232/// We use this to detect when the entity needs to be invalidated.233pub change_tick: Tick,234}235236/// Information that we use to identify a cached entity in a bin.237pub struct CachedBinKey<BPI>238where239BPI: BinnedPhaseItem,240{241/// The key of the batch set containing the entity.242pub batch_set_key: BPI::BatchSetKey,243/// The key of the bin containing the entity.244pub bin_key: BPI::BinKey,245/// The type of render phase that we use to render the entity: multidraw,246/// plain batch, etc.247pub phase_type: BinnedRenderPhaseType,248}249250impl<BPI> Clone for CachedBinnedEntity<BPI>251where252BPI: BinnedPhaseItem,253{254fn clone(&self) -> Self {255CachedBinnedEntity {256cached_bin_key: self.cached_bin_key.clone(),257change_tick: self.change_tick,258}259}260}261262impl<BPI> Clone for CachedBinKey<BPI>263where264BPI: BinnedPhaseItem,265{266fn clone(&self) -> Self {267CachedBinKey {268batch_set_key: self.batch_set_key.clone(),269bin_key: self.bin_key.clone(),270phase_type: self.phase_type,271}272}273}274275impl<BPI> PartialEq for CachedBinKey<BPI>276where277BPI: BinnedPhaseItem,278{279fn eq(&self, other: &Self) -> bool {280self.batch_set_key == other.batch_set_key281&& self.bin_key == other.bin_key282&& self.phase_type == other.phase_type283}284}285286/// How we store and render the batch sets.287///288/// Each one of these corresponds to a [`GpuPreprocessingMode`].289pub enum BinnedRenderPhaseBatchSets<BK> {290/// Batches are grouped into batch sets based on dynamic uniforms.291///292/// This corresponds to [`GpuPreprocessingMode::None`].293DynamicUniforms(Vec<SmallVec<[BinnedRenderPhaseBatch; 1]>>),294295/// Batches are never grouped into batch sets.296///297/// This corresponds to [`GpuPreprocessingMode::PreprocessingOnly`].298Direct(Vec<BinnedRenderPhaseBatch>),299300/// Batches are grouped together into batch sets based on their ability to301/// be multi-drawn together.302///303/// This corresponds to [`GpuPreprocessingMode::Culling`].304MultidrawIndirect(Vec<BinnedRenderPhaseBatchSet<BK>>),305}306307/// A group of entities that will be batched together into a single multi-draw308/// call.309pub struct BinnedRenderPhaseBatchSet<BK> {310/// The first batch in this batch set.311pub(crate) first_batch: BinnedRenderPhaseBatch,312/// The key of the bin that the first batch corresponds to.313pub(crate) bin_key: BK,314/// The number of batches.315pub(crate) batch_count: u32,316/// The index of the batch set in the GPU buffer.317pub(crate) index: u32,318}319320impl<BK> BinnedRenderPhaseBatchSets<BK> {321fn clear(&mut self) {322match *self {323BinnedRenderPhaseBatchSets::DynamicUniforms(ref mut vec) => vec.clear(),324BinnedRenderPhaseBatchSets::Direct(ref mut vec) => vec.clear(),325BinnedRenderPhaseBatchSets::MultidrawIndirect(ref mut vec) => vec.clear(),326}327}328}329330/// Information about a single batch of entities rendered using binned phase331/// items.332#[derive(Debug)]333pub struct BinnedRenderPhaseBatch {334/// An entity that's *representative* of this batch.335///336/// Bevy uses this to fetch the mesh. It can be any entity in the batch.337pub representative_entity: (Entity, MainEntity),338/// The range of instance indices in this batch.339pub instance_range: Range<u32>,340341/// The dynamic offset of the batch.342///343/// Note that dynamic offsets are only used on platforms that don't support344/// storage buffers.345pub extra_index: PhaseItemExtraIndex,346}347348/// Information about the unbatchable entities in a bin.349pub struct UnbatchableBinnedEntities {350/// The entities.351pub entities: MainEntityHashMap<Entity>,352353/// The GPU array buffer indices of each unbatchable binned entity.354pub(crate) buffer_indices: UnbatchableBinnedEntityIndexSet,355}356357/// Information about [`BinnedRenderPhaseType::NonMesh`] entities.358pub struct NonMeshEntities {359/// The entities.360pub entities: MainEntityHashMap<Entity>,361}362363/// Stores instance indices and dynamic offsets for unbatchable entities in a364/// binned render phase.365///366/// This is conceptually `Vec<UnbatchableBinnedEntityDynamicOffset>`, but it367/// avoids the overhead of storing dynamic offsets on platforms that support368/// them. In other words, this allows a fast path that avoids allocation on369/// platforms that aren't WebGL 2.370#[derive(Default)]371372pub(crate) enum UnbatchableBinnedEntityIndexSet {373/// There are no unbatchable entities in this bin (yet).374#[default]375NoEntities,376377/// The instances for all unbatchable entities in this bin are contiguous,378/// and there are no dynamic uniforms.379///380/// This is the typical case on platforms other than WebGL 2. We special381/// case this to avoid allocation on those platforms.382Sparse {383/// The range of indices.384instance_range: Range<u32>,385/// The index of the first indirect instance parameters.386///387/// The other indices immediately follow these.388first_indirect_parameters_index: Option<NonMaxU32>,389},390391/// Dynamic uniforms are present for unbatchable entities in this bin.392///393/// We fall back to this on WebGL 2.394Dense(Vec<UnbatchableBinnedEntityIndices>),395}396397/// The instance index and dynamic offset (if present) for an unbatchable entity.398///399/// This is only useful on platforms that don't support storage buffers.400#[derive(Clone)]401pub(crate) struct UnbatchableBinnedEntityIndices {402/// The instance index.403pub(crate) instance_index: u32,404/// The [`PhaseItemExtraIndex`], if present.405pub(crate) extra_index: PhaseItemExtraIndex,406}407408/// Identifies the list within [`BinnedRenderPhase`] that a phase item is to be409/// placed in.410#[derive(Clone, Copy, PartialEq, Debug)]411pub enum BinnedRenderPhaseType {412/// The item is a mesh that's eligible for multi-draw indirect rendering and413/// can be batched with other meshes of the same type.414MultidrawableMesh,415416/// The item is a mesh that can be batched with other meshes of the same type and417/// drawn in a single draw call.418BatchableMesh,419420/// The item is a mesh that's eligible for indirect rendering, but can't be421/// batched with other meshes of the same type.422UnbatchableMesh,423424/// The item isn't a mesh at all.425///426/// Bevy will simply invoke the drawing commands for such items one after427/// another, with no further processing.428///429/// The engine itself doesn't enqueue any items of this type, but it's430/// available for use in your application and/or plugins.431NonMesh,432}433434impl<T> From<GpuArrayBufferIndex<T>> for UnbatchableBinnedEntityIndices435where436T: Clone + ShaderSize + WriteInto,437{438fn from(value: GpuArrayBufferIndex<T>) -> Self {439UnbatchableBinnedEntityIndices {440instance_index: value.index,441extra_index: PhaseItemExtraIndex::maybe_dynamic_offset(value.dynamic_offset),442}443}444}445446impl<BPI> Default for ViewBinnedRenderPhases<BPI>447where448BPI: BinnedPhaseItem,449{450fn default() -> Self {451Self(default())452}453}454455impl<BPI> ViewBinnedRenderPhases<BPI>456where457BPI: BinnedPhaseItem,458{459pub fn prepare_for_new_frame(460&mut self,461retained_view_entity: RetainedViewEntity,462gpu_preprocessing: GpuPreprocessingMode,463) {464match self.entry(retained_view_entity) {465Entry::Occupied(mut entry) => entry.get_mut().prepare_for_new_frame(),466Entry::Vacant(entry) => {467entry.insert(BinnedRenderPhase::<BPI>::new(gpu_preprocessing));468}469}470}471}472473/// The index of the uniform describing this object in the GPU buffer, when GPU474/// preprocessing is enabled.475///476/// For example, for 3D meshes, this is the index of the `MeshInputUniform` in477/// the buffer.478///479/// This field is ignored if GPU preprocessing isn't in use, such as (currently)480/// in the case of 2D meshes. In that case, it can be safely set to481/// [`core::default::Default::default`].482#[derive(Clone, Copy, PartialEq, Default, Deref, DerefMut)]483#[repr(transparent)]484pub struct InputUniformIndex(pub u32);485486impl<BPI> BinnedRenderPhase<BPI>487where488BPI: BinnedPhaseItem,489{490/// Bins a new entity.491///492/// The `phase_type` parameter specifies whether the entity is a493/// preprocessable mesh and whether it can be binned with meshes of the same494/// type.495pub fn add(496&mut self,497batch_set_key: BPI::BatchSetKey,498bin_key: BPI::BinKey,499(entity, main_entity): (Entity, MainEntity),500input_uniform_index: InputUniformIndex,501mut phase_type: BinnedRenderPhaseType,502change_tick: Tick,503) {504// If the user has overridden indirect drawing for this view, we need to505// force the phase type to be batchable instead.506if self.gpu_preprocessing_mode == GpuPreprocessingMode::PreprocessingOnly507&& phase_type == BinnedRenderPhaseType::MultidrawableMesh508{509phase_type = BinnedRenderPhaseType::BatchableMesh;510}511512match phase_type {513BinnedRenderPhaseType::MultidrawableMesh => {514match self.multidrawable_meshes.entry(batch_set_key.clone()) {515indexmap::map::Entry::Occupied(mut entry) => {516entry517.get_mut()518.entry(bin_key.clone())519.or_default()520.insert(main_entity, input_uniform_index);521}522indexmap::map::Entry::Vacant(entry) => {523let mut new_batch_set = IndexMap::default();524new_batch_set.insert(525bin_key.clone(),526RenderBin::from_entity(main_entity, input_uniform_index),527);528entry.insert(new_batch_set);529}530}531}532533BinnedRenderPhaseType::BatchableMesh => {534match self535.batchable_meshes536.entry((batch_set_key.clone(), bin_key.clone()).clone())537{538indexmap::map::Entry::Occupied(mut entry) => {539entry.get_mut().insert(main_entity, input_uniform_index);540}541indexmap::map::Entry::Vacant(entry) => {542entry.insert(RenderBin::from_entity(main_entity, input_uniform_index));543}544}545}546547BinnedRenderPhaseType::UnbatchableMesh => {548match self549.unbatchable_meshes550.entry((batch_set_key.clone(), bin_key.clone()))551{552indexmap::map::Entry::Occupied(mut entry) => {553entry.get_mut().entities.insert(main_entity, entity);554}555indexmap::map::Entry::Vacant(entry) => {556let mut entities = MainEntityHashMap::default();557entities.insert(main_entity, entity);558entry.insert(UnbatchableBinnedEntities {559entities,560buffer_indices: default(),561});562}563}564}565566BinnedRenderPhaseType::NonMesh => {567// We don't process these items further.568match self569.non_mesh_items570.entry((batch_set_key.clone(), bin_key.clone()).clone())571{572indexmap::map::Entry::Occupied(mut entry) => {573entry.get_mut().entities.insert(main_entity, entity);574}575indexmap::map::Entry::Vacant(entry) => {576let mut entities = MainEntityHashMap::default();577entities.insert(main_entity, entity);578entry.insert(NonMeshEntities { entities });579}580}581}582}583584// Update the cache.585self.update_cache(586main_entity,587Some(CachedBinKey {588batch_set_key,589bin_key,590phase_type,591}),592change_tick,593);594}595596/// Inserts an entity into the cache with the given change tick.597pub fn update_cache(598&mut self,599main_entity: MainEntity,600cached_bin_key: Option<CachedBinKey<BPI>>,601change_tick: Tick,602) {603let new_cached_binned_entity = CachedBinnedEntity {604cached_bin_key,605change_tick,606};607608let (index, old_cached_binned_entity) = self609.cached_entity_bin_keys610.insert_full(main_entity, new_cached_binned_entity.clone());611612// If the entity changed bins, record its old bin so that we can remove613// the entity from it.614if let Some(old_cached_binned_entity) = old_cached_binned_entity615&& old_cached_binned_entity.cached_bin_key != new_cached_binned_entity.cached_bin_key616{617self.entities_that_changed_bins.push(EntityThatChangedBins {618main_entity,619old_cached_binned_entity,620});621}622623// Mark the entity as valid.624self.valid_cached_entity_bin_keys.grow_and_insert(index);625}626627/// Encodes the GPU commands needed to render all entities in this phase.628pub fn render<'w>(629&self,630render_pass: &mut TrackedRenderPass<'w>,631world: &'w World,632view: Entity,633) -> Result<(), DrawError> {634{635let draw_functions = world.resource::<DrawFunctions<BPI>>();636let mut draw_functions = draw_functions.write();637draw_functions.prepare(world);638// Make sure to drop the reader-writer lock here to avoid recursive639// locks.640}641642self.render_batchable_meshes(render_pass, world, view)?;643self.render_unbatchable_meshes(render_pass, world, view)?;644self.render_non_meshes(render_pass, world, view)?;645646Ok(())647}648649/// Renders all batchable meshes queued in this phase.650fn render_batchable_meshes<'w>(651&self,652render_pass: &mut TrackedRenderPass<'w>,653world: &'w World,654view: Entity,655) -> Result<(), DrawError> {656let draw_functions = world.resource::<DrawFunctions<BPI>>();657let mut draw_functions = draw_functions.write();658659let render_device = world.resource::<RenderDevice>();660let render_adapter_info = world.resource::<RenderAdapterInfo>();661let multi_draw_indirect_count_supported = render_device662.features()663.contains(Features::MULTI_DRAW_INDIRECT_COUNT)664// TODO: https://github.com/gfx-rs/wgpu/issues/7974665&& !matches!(render_adapter_info.backend, wgpu::Backend::Dx12);666667match self.batch_sets {668BinnedRenderPhaseBatchSets::DynamicUniforms(ref batch_sets) => {669debug_assert_eq!(self.batchable_meshes.len(), batch_sets.len());670671for ((batch_set_key, bin_key), batch_set) in672self.batchable_meshes.keys().zip(batch_sets.iter())673{674for batch in batch_set {675let binned_phase_item = BPI::new(676batch_set_key.clone(),677bin_key.clone(),678batch.representative_entity,679batch.instance_range.clone(),680batch.extra_index.clone(),681);682683// Fetch the draw function.684let Some(draw_function) =685draw_functions.get_mut(binned_phase_item.draw_function())686else {687continue;688};689690draw_function.draw(world, render_pass, view, &binned_phase_item)?;691}692}693}694695BinnedRenderPhaseBatchSets::Direct(ref batch_set) => {696for (batch, (batch_set_key, bin_key)) in697batch_set.iter().zip(self.batchable_meshes.keys())698{699let binned_phase_item = BPI::new(700batch_set_key.clone(),701bin_key.clone(),702batch.representative_entity,703batch.instance_range.clone(),704batch.extra_index.clone(),705);706707// Fetch the draw function.708let Some(draw_function) =709draw_functions.get_mut(binned_phase_item.draw_function())710else {711continue;712};713714draw_function.draw(world, render_pass, view, &binned_phase_item)?;715}716}717718BinnedRenderPhaseBatchSets::MultidrawIndirect(ref batch_sets) => {719for (batch_set_key, batch_set) in self720.multidrawable_meshes721.keys()722.chain(723self.batchable_meshes724.keys()725.map(|(batch_set_key, _)| batch_set_key),726)727.zip(batch_sets.iter())728{729let batch = &batch_set.first_batch;730731let batch_set_index = if multi_draw_indirect_count_supported {732NonMaxU32::new(batch_set.index)733} else {734None735};736737let binned_phase_item = BPI::new(738batch_set_key.clone(),739batch_set.bin_key.clone(),740batch.representative_entity,741batch.instance_range.clone(),742match batch.extra_index {743PhaseItemExtraIndex::None => PhaseItemExtraIndex::None,744PhaseItemExtraIndex::DynamicOffset(ref dynamic_offset) => {745PhaseItemExtraIndex::DynamicOffset(*dynamic_offset)746}747PhaseItemExtraIndex::IndirectParametersIndex { ref range, .. } => {748PhaseItemExtraIndex::IndirectParametersIndex {749range: range.start..(range.start + batch_set.batch_count),750batch_set_index,751}752}753},754);755756// Fetch the draw function.757let Some(draw_function) =758draw_functions.get_mut(binned_phase_item.draw_function())759else {760continue;761};762763draw_function.draw(world, render_pass, view, &binned_phase_item)?;764}765}766}767768Ok(())769}770771/// Renders all unbatchable meshes queued in this phase.772fn render_unbatchable_meshes<'w>(773&self,774render_pass: &mut TrackedRenderPass<'w>,775world: &'w World,776view: Entity,777) -> Result<(), DrawError> {778let draw_functions = world.resource::<DrawFunctions<BPI>>();779let mut draw_functions = draw_functions.write();780781for (batch_set_key, bin_key) in self.unbatchable_meshes.keys() {782let unbatchable_entities =783&self.unbatchable_meshes[&(batch_set_key.clone(), bin_key.clone())];784for (entity_index, entity) in unbatchable_entities.entities.iter().enumerate() {785let unbatchable_dynamic_offset = match &unbatchable_entities.buffer_indices {786UnbatchableBinnedEntityIndexSet::NoEntities => {787// Shouldn't happen…788continue;789}790UnbatchableBinnedEntityIndexSet::Sparse {791instance_range,792first_indirect_parameters_index,793} => UnbatchableBinnedEntityIndices {794instance_index: instance_range.start + entity_index as u32,795extra_index: match first_indirect_parameters_index {796None => PhaseItemExtraIndex::None,797Some(first_indirect_parameters_index) => {798let first_indirect_parameters_index_for_entity =799u32::from(*first_indirect_parameters_index)800+ entity_index as u32;801PhaseItemExtraIndex::IndirectParametersIndex {802range: first_indirect_parameters_index_for_entity803..(first_indirect_parameters_index_for_entity + 1),804batch_set_index: None,805}806}807},808},809UnbatchableBinnedEntityIndexSet::Dense(dynamic_offsets) => {810dynamic_offsets[entity_index].clone()811}812};813814let binned_phase_item = BPI::new(815batch_set_key.clone(),816bin_key.clone(),817(*entity.1, *entity.0),818unbatchable_dynamic_offset.instance_index819..(unbatchable_dynamic_offset.instance_index + 1),820unbatchable_dynamic_offset.extra_index,821);822823// Fetch the draw function.824let Some(draw_function) = draw_functions.get_mut(binned_phase_item.draw_function())825else {826continue;827};828829draw_function.draw(world, render_pass, view, &binned_phase_item)?;830}831}832Ok(())833}834835/// Renders all objects of type [`BinnedRenderPhaseType::NonMesh`].836///837/// These will have been added by plugins or the application.838fn render_non_meshes<'w>(839&self,840render_pass: &mut TrackedRenderPass<'w>,841world: &'w World,842view: Entity,843) -> Result<(), DrawError> {844let draw_functions = world.resource::<DrawFunctions<BPI>>();845let mut draw_functions = draw_functions.write();846847for ((batch_set_key, bin_key), non_mesh_entities) in &self.non_mesh_items {848for (main_entity, entity) in non_mesh_entities.entities.iter() {849// Come up with a fake batch range and extra index. The draw850// function is expected to manage any sort of batching logic itself.851let binned_phase_item = BPI::new(852batch_set_key.clone(),853bin_key.clone(),854(*entity, *main_entity),8550..1,856PhaseItemExtraIndex::None,857);858859let Some(draw_function) = draw_functions.get_mut(binned_phase_item.draw_function())860else {861continue;862};863864draw_function.draw(world, render_pass, view, &binned_phase_item)?;865}866}867868Ok(())869}870871pub fn is_empty(&self) -> bool {872self.multidrawable_meshes.is_empty()873&& self.batchable_meshes.is_empty()874&& self.unbatchable_meshes.is_empty()875&& self.non_mesh_items.is_empty()876}877878pub fn prepare_for_new_frame(&mut self) {879self.batch_sets.clear();880881self.valid_cached_entity_bin_keys.clear();882self.valid_cached_entity_bin_keys883.grow(self.cached_entity_bin_keys.len());884self.valid_cached_entity_bin_keys885.set_range(self.cached_entity_bin_keys.len().., true);886887self.entities_that_changed_bins.clear();888889for unbatchable_bin in self.unbatchable_meshes.values_mut() {890unbatchable_bin.buffer_indices.clear();891}892}893894/// Checks to see whether the entity is in a bin and returns true if it's895/// both in a bin and up to date.896///897/// If this function returns true, we also add the entry to the898/// `valid_cached_entity_bin_keys` list.899pub fn validate_cached_entity(900&mut self,901visible_entity: MainEntity,902current_change_tick: Tick,903) -> bool {904if let indexmap::map::Entry::Occupied(entry) =905self.cached_entity_bin_keys.entry(visible_entity)906&& entry.get().change_tick == current_change_tick907{908self.valid_cached_entity_bin_keys.insert(entry.index());909return true;910}911912false913}914915/// Removes all entities not marked as clean from the bins.916///917/// During `queue_material_meshes`, we process all visible entities and mark918/// each as clean as we come to it. Then, in [`sweep_old_entities`], we call919/// this method, which removes entities that aren't marked as clean from the920/// bins.921pub fn sweep_old_entities(&mut self) {922// Search for entities not marked as valid. We have to do this in923// reverse order because `swap_remove_index` will potentially invalidate924// all indices after the one we remove.925for index in ReverseFixedBitSetZeroesIterator::new(&self.valid_cached_entity_bin_keys) {926let Some((entity, cached_binned_entity)) =927self.cached_entity_bin_keys.swap_remove_index(index)928else {929continue;930};931932if let Some(ref cached_bin_key) = cached_binned_entity.cached_bin_key {933remove_entity_from_bin(934entity,935cached_bin_key,936&mut self.multidrawable_meshes,937&mut self.batchable_meshes,938&mut self.unbatchable_meshes,939&mut self.non_mesh_items,940);941}942}943944// If an entity changed bins, we need to remove it from its old bin.945for entity_that_changed_bins in self.entities_that_changed_bins.drain(..) {946let Some(ref old_cached_bin_key) = entity_that_changed_bins947.old_cached_binned_entity948.cached_bin_key949else {950continue;951};952remove_entity_from_bin(953entity_that_changed_bins.main_entity,954old_cached_bin_key,955&mut self.multidrawable_meshes,956&mut self.batchable_meshes,957&mut self.unbatchable_meshes,958&mut self.non_mesh_items,959);960}961}962}963964/// Removes an entity from a bin.965///966/// If this makes the bin empty, this function removes the bin as well.967///968/// This is a standalone function instead of a method on [`BinnedRenderPhase`]969/// for borrow check reasons.970fn remove_entity_from_bin<BPI>(971entity: MainEntity,972entity_bin_key: &CachedBinKey<BPI>,973multidrawable_meshes: &mut IndexMap<BPI::BatchSetKey, IndexMap<BPI::BinKey, RenderBin>>,974batchable_meshes: &mut IndexMap<(BPI::BatchSetKey, BPI::BinKey), RenderBin>,975unbatchable_meshes: &mut IndexMap<(BPI::BatchSetKey, BPI::BinKey), UnbatchableBinnedEntities>,976non_mesh_items: &mut IndexMap<(BPI::BatchSetKey, BPI::BinKey), NonMeshEntities>,977) where978BPI: BinnedPhaseItem,979{980match entity_bin_key.phase_type {981BinnedRenderPhaseType::MultidrawableMesh => {982if let indexmap::map::Entry::Occupied(mut batch_set_entry) =983multidrawable_meshes.entry(entity_bin_key.batch_set_key.clone())984{985if let indexmap::map::Entry::Occupied(mut bin_entry) = batch_set_entry986.get_mut()987.entry(entity_bin_key.bin_key.clone())988{989bin_entry.get_mut().remove(entity);990991// If the bin is now empty, remove the bin.992if bin_entry.get_mut().is_empty() {993bin_entry.swap_remove();994}995}996997// If the batch set is now empty, remove it. This will perturb998// the order, but that's OK because we're going to sort the bin999// afterwards.1000if batch_set_entry.get_mut().is_empty() {1001batch_set_entry.swap_remove();1002}1003}1004}10051006BinnedRenderPhaseType::BatchableMesh => {1007if let indexmap::map::Entry::Occupied(mut bin_entry) = batchable_meshes.entry((1008entity_bin_key.batch_set_key.clone(),1009entity_bin_key.bin_key.clone(),1010)) {1011bin_entry.get_mut().remove(entity);10121013// If the bin is now empty, remove the bin.1014if bin_entry.get_mut().is_empty() {1015bin_entry.swap_remove();1016}1017}1018}10191020BinnedRenderPhaseType::UnbatchableMesh => {1021if let indexmap::map::Entry::Occupied(mut bin_entry) = unbatchable_meshes.entry((1022entity_bin_key.batch_set_key.clone(),1023entity_bin_key.bin_key.clone(),1024)) {1025bin_entry.get_mut().entities.remove(&entity);10261027// If the bin is now empty, remove the bin.1028if bin_entry.get_mut().entities.is_empty() {1029bin_entry.swap_remove();1030}1031}1032}10331034BinnedRenderPhaseType::NonMesh => {1035if let indexmap::map::Entry::Occupied(mut bin_entry) = non_mesh_items.entry((1036entity_bin_key.batch_set_key.clone(),1037entity_bin_key.bin_key.clone(),1038)) {1039bin_entry.get_mut().entities.remove(&entity);10401041// If the bin is now empty, remove the bin.1042if bin_entry.get_mut().entities.is_empty() {1043bin_entry.swap_remove();1044}1045}1046}1047}1048}10491050impl<BPI> BinnedRenderPhase<BPI>1051where1052BPI: BinnedPhaseItem,1053{1054fn new(gpu_preprocessing: GpuPreprocessingMode) -> Self {1055Self {1056multidrawable_meshes: IndexMap::default(),1057batchable_meshes: IndexMap::default(),1058unbatchable_meshes: IndexMap::default(),1059non_mesh_items: IndexMap::default(),1060batch_sets: match gpu_preprocessing {1061GpuPreprocessingMode::Culling => {1062BinnedRenderPhaseBatchSets::MultidrawIndirect(vec![])1063}1064GpuPreprocessingMode::PreprocessingOnly => {1065BinnedRenderPhaseBatchSets::Direct(vec![])1066}1067GpuPreprocessingMode::None => BinnedRenderPhaseBatchSets::DynamicUniforms(vec![]),1068},1069cached_entity_bin_keys: IndexMap::default(),1070valid_cached_entity_bin_keys: FixedBitSet::new(),1071entities_that_changed_bins: vec![],1072gpu_preprocessing_mode: gpu_preprocessing,1073}1074}1075}10761077impl UnbatchableBinnedEntityIndexSet {1078/// Returns the [`UnbatchableBinnedEntityIndices`] for the given entity.1079fn indices_for_entity_index(1080&self,1081entity_index: u32,1082) -> Option<UnbatchableBinnedEntityIndices> {1083match self {1084UnbatchableBinnedEntityIndexSet::NoEntities => None,1085UnbatchableBinnedEntityIndexSet::Sparse { instance_range, .. }1086if entity_index >= instance_range.len() as u32 =>1087{1088None1089}1090UnbatchableBinnedEntityIndexSet::Sparse {1091instance_range,1092first_indirect_parameters_index: None,1093} => Some(UnbatchableBinnedEntityIndices {1094instance_index: instance_range.start + entity_index,1095extra_index: PhaseItemExtraIndex::None,1096}),1097UnbatchableBinnedEntityIndexSet::Sparse {1098instance_range,1099first_indirect_parameters_index: Some(first_indirect_parameters_index),1100} => {1101let first_indirect_parameters_index_for_this_batch =1102u32::from(*first_indirect_parameters_index) + entity_index;1103Some(UnbatchableBinnedEntityIndices {1104instance_index: instance_range.start + entity_index,1105extra_index: PhaseItemExtraIndex::IndirectParametersIndex {1106range: first_indirect_parameters_index_for_this_batch1107..(first_indirect_parameters_index_for_this_batch + 1),1108batch_set_index: None,1109},1110})1111}1112UnbatchableBinnedEntityIndexSet::Dense(indices) => {1113indices.get(entity_index as usize).cloned()1114}1115}1116}1117}11181119/// A convenient abstraction for adding all the systems necessary for a binned1120/// render phase to the render app.1121///1122/// This is the version used when the pipeline supports GPU preprocessing: e.g.1123/// 3D PBR meshes.1124pub struct BinnedRenderPhasePlugin<BPI, GFBD>1125where1126BPI: BinnedPhaseItem,1127GFBD: GetFullBatchData,1128{1129/// Debugging flags that can optionally be set when constructing the renderer.1130pub debug_flags: RenderDebugFlags,1131phantom: PhantomData<(BPI, GFBD)>,1132}11331134impl<BPI, GFBD> BinnedRenderPhasePlugin<BPI, GFBD>1135where1136BPI: BinnedPhaseItem,1137GFBD: GetFullBatchData,1138{1139pub fn new(debug_flags: RenderDebugFlags) -> Self {1140Self {1141debug_flags,1142phantom: PhantomData,1143}1144}1145}11461147impl<BPI, GFBD> Plugin for BinnedRenderPhasePlugin<BPI, GFBD>1148where1149BPI: BinnedPhaseItem,1150GFBD: GetFullBatchData + Sync + Send + 'static,1151{1152fn build(&self, app: &mut App) {1153let Some(render_app) = app.get_sub_app_mut(RenderApp) else {1154return;1155};11561157render_app1158.init_resource::<ViewBinnedRenderPhases<BPI>>()1159.init_resource::<PhaseBatchedInstanceBuffers<BPI, GFBD::BufferData>>()1160.insert_resource(PhaseIndirectParametersBuffers::<BPI>::new(1161self.debug_flags1162.contains(RenderDebugFlags::ALLOW_COPIES_FROM_INDIRECT_PARAMETERS),1163))1164.add_systems(1165Render,1166(1167batching::sort_binned_render_phase::<BPI>.in_set(RenderSystems::PhaseSort),1168(1169no_gpu_preprocessing::batch_and_prepare_binned_render_phase::<BPI, GFBD>1170.run_if(resource_exists::<BatchedInstanceBuffer<GFBD::BufferData>>),1171gpu_preprocessing::batch_and_prepare_binned_render_phase::<BPI, GFBD>1172.run_if(1173resource_exists::<1174BatchedInstanceBuffers<GFBD::BufferData, GFBD::BufferInputData>,1175>,1176),1177)1178.in_set(RenderSystems::PrepareResources),1179sweep_old_entities::<BPI>.in_set(RenderSystems::QueueSweep),1180gpu_preprocessing::collect_buffers_for_phase::<BPI, GFBD>1181.run_if(1182resource_exists::<1183BatchedInstanceBuffers<GFBD::BufferData, GFBD::BufferInputData>,1184>,1185)1186.in_set(RenderSystems::PrepareResourcesCollectPhaseBuffers),1187),1188);1189}1190}11911192/// Stores the rendering instructions for a single phase that sorts items in all1193/// views.1194///1195/// They're cleared out every frame, but storing them in a resource like this1196/// allows us to reuse allocations.1197#[derive(Resource, Deref, DerefMut)]1198pub struct ViewSortedRenderPhases<SPI>(pub HashMap<RetainedViewEntity, SortedRenderPhase<SPI>>)1199where1200SPI: SortedPhaseItem;12011202impl<SPI> Default for ViewSortedRenderPhases<SPI>1203where1204SPI: SortedPhaseItem,1205{1206fn default() -> Self {1207Self(default())1208}1209}12101211impl<SPI> ViewSortedRenderPhases<SPI>1212where1213SPI: SortedPhaseItem,1214{1215pub fn insert_or_clear(&mut self, retained_view_entity: RetainedViewEntity) {1216match self.entry(retained_view_entity) {1217Entry::Occupied(mut entry) => entry.get_mut().clear(),1218Entry::Vacant(entry) => {1219entry.insert(default());1220}1221}1222}1223}12241225/// A convenient abstraction for adding all the systems necessary for a sorted1226/// render phase to the render app.1227///1228/// This is the version used when the pipeline supports GPU preprocessing: e.g.1229/// 3D PBR meshes.1230pub struct SortedRenderPhasePlugin<SPI, GFBD>1231where1232SPI: SortedPhaseItem,1233GFBD: GetFullBatchData,1234{1235/// Debugging flags that can optionally be set when constructing the renderer.1236pub debug_flags: RenderDebugFlags,1237phantom: PhantomData<(SPI, GFBD)>,1238}12391240impl<SPI, GFBD> SortedRenderPhasePlugin<SPI, GFBD>1241where1242SPI: SortedPhaseItem,1243GFBD: GetFullBatchData,1244{1245pub fn new(debug_flags: RenderDebugFlags) -> Self {1246Self {1247debug_flags,1248phantom: PhantomData,1249}1250}1251}12521253impl<SPI, GFBD> Plugin for SortedRenderPhasePlugin<SPI, GFBD>1254where1255SPI: SortedPhaseItem + CachedRenderPipelinePhaseItem,1256GFBD: GetFullBatchData + Sync + Send + 'static,1257{1258fn build(&self, app: &mut App) {1259let Some(render_app) = app.get_sub_app_mut(RenderApp) else {1260return;1261};12621263render_app1264.init_resource::<ViewSortedRenderPhases<SPI>>()1265.init_resource::<PhaseBatchedInstanceBuffers<SPI, GFBD::BufferData>>()1266.insert_resource(PhaseIndirectParametersBuffers::<SPI>::new(1267self.debug_flags1268.contains(RenderDebugFlags::ALLOW_COPIES_FROM_INDIRECT_PARAMETERS),1269))1270.add_systems(1271Render,1272(1273(1274no_gpu_preprocessing::batch_and_prepare_sorted_render_phase::<SPI, GFBD>1275.run_if(resource_exists::<BatchedInstanceBuffer<GFBD::BufferData>>),1276gpu_preprocessing::batch_and_prepare_sorted_render_phase::<SPI, GFBD>1277.run_if(1278resource_exists::<1279BatchedInstanceBuffers<GFBD::BufferData, GFBD::BufferInputData>,1280>,1281),1282)1283.in_set(RenderSystems::PrepareResources),1284gpu_preprocessing::collect_buffers_for_phase::<SPI, GFBD>1285.run_if(1286resource_exists::<1287BatchedInstanceBuffers<GFBD::BufferData, GFBD::BufferInputData>,1288>,1289)1290.in_set(RenderSystems::PrepareResourcesCollectPhaseBuffers),1291),1292);1293}1294}12951296impl UnbatchableBinnedEntityIndexSet {1297/// Adds a new entity to the list of unbatchable binned entities.1298pub fn add(&mut self, indices: UnbatchableBinnedEntityIndices) {1299match self {1300UnbatchableBinnedEntityIndexSet::NoEntities => {1301match indices.extra_index {1302PhaseItemExtraIndex::DynamicOffset(_) => {1303// This is the first entity we've seen, and we don't have1304// compute shaders. Initialize an array.1305*self = UnbatchableBinnedEntityIndexSet::Dense(vec![indices]);1306}1307PhaseItemExtraIndex::None => {1308// This is the first entity we've seen, and we have compute1309// shaders. Initialize the fast path.1310*self = UnbatchableBinnedEntityIndexSet::Sparse {1311instance_range: indices.instance_index..indices.instance_index + 1,1312first_indirect_parameters_index: None,1313}1314}1315PhaseItemExtraIndex::IndirectParametersIndex {1316range: ref indirect_parameters_index,1317..1318} => {1319// This is the first entity we've seen, and we have compute1320// shaders. Initialize the fast path.1321*self = UnbatchableBinnedEntityIndexSet::Sparse {1322instance_range: indices.instance_index..indices.instance_index + 1,1323first_indirect_parameters_index: NonMaxU32::new(1324indirect_parameters_index.start,1325),1326}1327}1328}1329}13301331UnbatchableBinnedEntityIndexSet::Sparse {1332instance_range,1333first_indirect_parameters_index,1334} if instance_range.end == indices.instance_index1335&& ((first_indirect_parameters_index.is_none()1336&& indices.extra_index == PhaseItemExtraIndex::None)1337|| first_indirect_parameters_index.is_some_and(1338|first_indirect_parameters_index| match indices.extra_index {1339PhaseItemExtraIndex::IndirectParametersIndex {1340range: ref this_range,1341..1342} => {1343u32::from(first_indirect_parameters_index) + instance_range.end1344- instance_range.start1345== this_range.start1346}1347PhaseItemExtraIndex::DynamicOffset(_) | PhaseItemExtraIndex::None => {1348false1349}1350},1351)) =>1352{1353// This is the normal case on non-WebGL 2.1354instance_range.end += 1;1355}13561357UnbatchableBinnedEntityIndexSet::Sparse { instance_range, .. } => {1358// We thought we were in non-WebGL 2 mode, but we got a dynamic1359// offset or non-contiguous index anyway. This shouldn't happen,1360// but let's go ahead and do the sensible thing anyhow: demote1361// the compressed `NoDynamicOffsets` field to the full1362// `DynamicOffsets` array.1363warn!(1364"Unbatchable binned entity index set was demoted from sparse to dense. \1365This is a bug in the renderer. Please report it.",1366);1367let new_dynamic_offsets = (0..instance_range.len() as u32)1368.flat_map(|entity_index| self.indices_for_entity_index(entity_index))1369.chain(iter::once(indices))1370.collect();1371*self = UnbatchableBinnedEntityIndexSet::Dense(new_dynamic_offsets);1372}13731374UnbatchableBinnedEntityIndexSet::Dense(dense_indices) => {1375dense_indices.push(indices);1376}1377}1378}13791380/// Clears the unbatchable binned entity index set.1381fn clear(&mut self) {1382match self {1383UnbatchableBinnedEntityIndexSet::Dense(dense_indices) => dense_indices.clear(),1384UnbatchableBinnedEntityIndexSet::Sparse { .. } => {1385*self = UnbatchableBinnedEntityIndexSet::NoEntities;1386}1387_ => {}1388}1389}1390}13911392/// A collection of all items to be rendered that will be encoded to GPU1393/// commands for a single render phase for a single view.1394///1395/// Each view (camera, or shadow-casting light, etc.) can have one or multiple render phases.1396/// They are used to queue entities for rendering.1397/// Multiple phases might be required due to different sorting/batching behaviors1398/// (e.g. opaque: front to back, transparent: back to front) or because one phase depends on1399/// the rendered texture of the previous phase (e.g. for screen-space reflections).1400/// All [`PhaseItem`]s are then rendered using a single [`TrackedRenderPass`].1401/// The render pass might be reused for multiple phases to reduce GPU overhead.1402///1403/// This flavor of render phase is used only for meshes that need to be sorted1404/// back-to-front, such as transparent meshes. For items that don't need strict1405/// sorting, [`BinnedRenderPhase`] is preferred, for performance.1406pub struct SortedRenderPhase<I>1407where1408I: SortedPhaseItem,1409{1410/// The items within this [`SortedRenderPhase`].1411pub items: Vec<I>,1412}14131414impl<I> Default for SortedRenderPhase<I>1415where1416I: SortedPhaseItem,1417{1418fn default() -> Self {1419Self { items: Vec::new() }1420}1421}14221423impl<I> SortedRenderPhase<I>1424where1425I: SortedPhaseItem,1426{1427/// Adds a [`PhaseItem`] to this render phase.1428#[inline]1429pub fn add(&mut self, item: I) {1430self.items.push(item);1431}14321433/// Removes all [`PhaseItem`]s from this render phase.1434#[inline]1435pub fn clear(&mut self) {1436self.items.clear();1437}14381439/// Sorts all of its [`PhaseItem`]s.1440pub fn sort(&mut self) {1441I::sort(&mut self.items);1442}14431444/// An [`Iterator`] through the associated [`Entity`] for each [`PhaseItem`] in order.1445#[inline]1446pub fn iter_entities(&'_ self) -> impl Iterator<Item = Entity> + '_ {1447self.items.iter().map(PhaseItem::entity)1448}14491450/// Renders all of its [`PhaseItem`]s using their corresponding draw functions.1451pub fn render<'w>(1452&self,1453render_pass: &mut TrackedRenderPass<'w>,1454world: &'w World,1455view: Entity,1456) -> Result<(), DrawError> {1457self.render_range(render_pass, world, view, ..)1458}14591460/// Renders all [`PhaseItem`]s in the provided `range` (based on their index in `self.items`) using their corresponding draw functions.1461pub fn render_range<'w>(1462&self,1463render_pass: &mut TrackedRenderPass<'w>,1464world: &'w World,1465view: Entity,1466range: impl SliceIndex<[I], Output = [I]>,1467) -> Result<(), DrawError> {1468let items = self1469.items1470.get(range)1471.expect("`Range` provided to `render_range()` is out of bounds");14721473let draw_functions = world.resource::<DrawFunctions<I>>();1474let mut draw_functions = draw_functions.write();1475draw_functions.prepare(world);14761477let mut index = 0;1478while index < items.len() {1479let item = &items[index];1480let batch_range = item.batch_range();1481if batch_range.is_empty() {1482index += 1;1483} else {1484let draw_function = draw_functions.get_mut(item.draw_function()).unwrap();1485draw_function.draw(world, render_pass, view, item)?;1486index += batch_range.len();1487}1488}1489Ok(())1490}1491}14921493/// An item (entity of the render world) which will be drawn to a texture or the screen,1494/// as part of a render phase.1495///1496/// The data required for rendering an entity is extracted from the main world in the1497/// [`ExtractSchedule`](crate::ExtractSchedule).1498/// Then it has to be queued up for rendering during the [`RenderSystems::Queue`],1499/// by adding a corresponding phase item to a render phase.1500/// Afterwards it will be possibly sorted and rendered automatically in the1501/// [`RenderSystems::PhaseSort`] and [`RenderSystems::Render`], respectively.1502///1503/// `PhaseItem`s come in two flavors: [`BinnedPhaseItem`]s and1504/// [`SortedPhaseItem`]s.1505///1506/// * Binned phase items have a `BinKey` which specifies what bin they're to be1507/// placed in. All items in the same bin are eligible to be batched together.1508/// The `BinKey`s are sorted, but the individual bin items aren't. Binned phase1509/// items are good for opaque meshes, in which the order of rendering isn't1510/// important. Generally, binned phase items are faster than sorted phase items.1511///1512/// * Sorted phase items, on the other hand, are placed into one large buffer1513/// and then sorted all at once. This is needed for transparent meshes, which1514/// have to be sorted back-to-front to render with the painter's algorithm.1515/// These types of phase items are generally slower than binned phase items.1516pub trait PhaseItem: Sized + Send + Sync + 'static {1517/// Whether or not this `PhaseItem` should be subjected to automatic batching. (Default: `true`)1518const AUTOMATIC_BATCHING: bool = true;15191520/// The corresponding entity that will be drawn.1521///1522/// This is used to fetch the render data of the entity, required by the draw function,1523/// from the render world .1524fn entity(&self) -> Entity;15251526/// The main world entity represented by this `PhaseItem`.1527fn main_entity(&self) -> MainEntity;15281529/// Specifies the [`Draw`] function used to render the item.1530fn draw_function(&self) -> DrawFunctionId;15311532/// The range of instances that the batch covers. After doing a batched draw, batch range1533/// length phase items will be skipped. This design is to avoid having to restructure the1534/// render phase unnecessarily.1535fn batch_range(&self) -> &Range<u32>;1536fn batch_range_mut(&mut self) -> &mut Range<u32>;15371538/// Returns the [`PhaseItemExtraIndex`].1539///1540/// If present, this is either a dynamic offset or an indirect parameters1541/// index.1542fn extra_index(&self) -> PhaseItemExtraIndex;15431544/// Returns a pair of mutable references to both the batch range and extra1545/// index.1546fn batch_range_and_extra_index_mut(&mut self) -> (&mut Range<u32>, &mut PhaseItemExtraIndex);1547}15481549/// The "extra index" associated with some [`PhaseItem`]s, alongside the1550/// indirect instance index.1551///1552/// Sometimes phase items require another index in addition to the range of1553/// instances they already have. These can be:1554///1555/// * The *dynamic offset*: a `wgpu` dynamic offset into the uniform buffer of1556/// instance data. This is used on platforms that don't support storage1557/// buffers, to work around uniform buffer size limitations.1558///1559/// * The *indirect parameters index*: an index into the buffer that specifies1560/// the indirect parameters for this [`PhaseItem`]'s drawcall. This is used when1561/// indirect mode is on (as used for GPU culling).1562///1563/// Note that our indirect draw functionality requires storage buffers, so it's1564/// impossible to have both a dynamic offset and an indirect parameters index.1565/// This convenient fact allows us to pack both indices into a single `u32`.1566#[derive(Clone, PartialEq, Eq, Hash, Debug)]1567pub enum PhaseItemExtraIndex {1568/// No extra index is present.1569None,1570/// A `wgpu` dynamic offset into the uniform buffer of instance data. This1571/// is used on platforms that don't support storage buffers, to work around1572/// uniform buffer size limitations.1573DynamicOffset(u32),1574/// An index into the buffer that specifies the indirect parameters for this1575/// [`PhaseItem`]'s drawcall. This is used when indirect mode is on (as used1576/// for GPU culling).1577IndirectParametersIndex {1578/// The range of indirect parameters within the indirect parameters array.1579///1580/// If we're using `multi_draw_indirect_count`, this specifies the1581/// maximum range of indirect parameters within that array. If batches1582/// are ultimately culled out on the GPU, the actual number of draw1583/// commands might be lower than the length of this range.1584range: Range<u32>,1585/// If `multi_draw_indirect_count` is in use, and this phase item is1586/// part of a batch set, specifies the index of the batch set that this1587/// phase item is a part of.1588///1589/// If `multi_draw_indirect_count` isn't in use, or this phase item1590/// isn't part of a batch set, this is `None`.1591batch_set_index: Option<NonMaxU32>,1592},1593}15941595impl PhaseItemExtraIndex {1596/// Returns either an indirect parameters index or1597/// [`PhaseItemExtraIndex::None`], as appropriate.1598pub fn maybe_indirect_parameters_index(1599indirect_parameters_index: Option<NonMaxU32>,1600) -> PhaseItemExtraIndex {1601match indirect_parameters_index {1602Some(indirect_parameters_index) => PhaseItemExtraIndex::IndirectParametersIndex {1603range: u32::from(indirect_parameters_index)1604..(u32::from(indirect_parameters_index) + 1),1605batch_set_index: None,1606},1607None => PhaseItemExtraIndex::None,1608}1609}16101611/// Returns either a dynamic offset index or [`PhaseItemExtraIndex::None`],1612/// as appropriate.1613pub fn maybe_dynamic_offset(dynamic_offset: Option<NonMaxU32>) -> PhaseItemExtraIndex {1614match dynamic_offset {1615Some(dynamic_offset) => PhaseItemExtraIndex::DynamicOffset(dynamic_offset.into()),1616None => PhaseItemExtraIndex::None,1617}1618}1619}16201621/// Represents phase items that are placed into bins. The `BinKey` specifies1622/// which bin they're to be placed in. Bin keys are sorted, and items within the1623/// same bin are eligible to be batched together. The elements within the bins1624/// aren't themselves sorted.1625///1626/// An example of a binned phase item is `Opaque3d`, for which the rendering1627/// order isn't critical.1628pub trait BinnedPhaseItem: PhaseItem {1629/// The key used for binning [`PhaseItem`]s into bins. Order the members of1630/// [`BinnedPhaseItem::BinKey`] by the order of binding for best1631/// performance. For example, pipeline id, draw function id, mesh asset id,1632/// lowest variable bind group id such as the material bind group id, and1633/// its dynamic offsets if any, next bind group and offsets, etc. This1634/// reduces the need for rebinding between bins and improves performance.1635type BinKey: Clone + Send + Sync + PartialEq + Eq + Ord + Hash;16361637/// The key used to combine batches into batch sets.1638///1639/// A *batch set* is a set of meshes that can potentially be multi-drawn1640/// together.1641type BatchSetKey: PhaseItemBatchSetKey;16421643/// Creates a new binned phase item from the key and per-entity data.1644///1645/// Unlike [`SortedPhaseItem`]s, this is generally called "just in time"1646/// before rendering. The resulting phase item isn't stored in any data1647/// structures, resulting in significant memory savings.1648fn new(1649batch_set_key: Self::BatchSetKey,1650bin_key: Self::BinKey,1651representative_entity: (Entity, MainEntity),1652batch_range: Range<u32>,1653extra_index: PhaseItemExtraIndex,1654) -> Self;1655}16561657/// A key used to combine batches into batch sets.1658///1659/// A *batch set* is a set of meshes that can potentially be multi-drawn1660/// together.1661pub trait PhaseItemBatchSetKey: Clone + Send + Sync + PartialEq + Eq + Ord + Hash {1662/// Returns true if this batch set key describes indexed meshes or false if1663/// it describes non-indexed meshes.1664///1665/// Bevy uses this in order to determine which kind of indirect draw1666/// parameters to use, if indirect drawing is enabled.1667fn indexed(&self) -> bool;1668}16691670/// Represents phase items that must be sorted. The `SortKey` specifies the1671/// order that these items are drawn in. These are placed into a single array,1672/// and the array as a whole is then sorted.1673///1674/// An example of a sorted phase item is `Transparent3d`, which must be sorted1675/// back to front in order to correctly render with the painter's algorithm.1676pub trait SortedPhaseItem: PhaseItem {1677/// The type used for ordering the items. The smallest values are drawn first.1678/// This order can be calculated using the [`ViewRangefinder3d`],1679/// based on the view-space `Z` value of the corresponding view matrix.1680type SortKey: Ord;16811682/// Determines the order in which the items are drawn.1683fn sort_key(&self) -> Self::SortKey;16841685/// Sorts a slice of phase items into render order. Generally if the same type1686/// is batched this should use a stable sort like [`slice::sort_by_key`].1687/// In almost all other cases, this should not be altered from the default,1688/// which uses an unstable sort, as this provides the best balance of CPU and GPU1689/// performance.1690///1691/// Implementers can optionally not sort the list at all. This is generally advisable if and1692/// only if the renderer supports a depth prepass, which is by default not supported by1693/// the rest of Bevy's first party rendering crates. Even then, this may have a negative1694/// impact on GPU-side performance due to overdraw.1695///1696/// It's advised to always profile for performance changes when changing this implementation.1697#[inline]1698fn sort(items: &mut [Self]) {1699items.sort_unstable_by_key(Self::sort_key);1700}17011702/// Whether this phase item targets indexed meshes (those with both vertex1703/// and index buffers as opposed to just vertex buffers).1704///1705/// Bevy needs this information in order to properly group phase items1706/// together for multi-draw indirect, because the GPU layout of indirect1707/// commands differs between indexed and non-indexed meshes.1708///1709/// If you're implementing a custom phase item that doesn't describe a mesh,1710/// you can safely return false here.1711fn indexed(&self) -> bool;1712}17131714/// A [`PhaseItem`] item, that automatically sets the appropriate render pipeline,1715/// cached in the [`PipelineCache`].1716///1717/// You can use the [`SetItemPipeline`] render command to set the pipeline for this item.1718pub trait CachedRenderPipelinePhaseItem: PhaseItem {1719/// The id of the render pipeline, cached in the [`PipelineCache`], that will be used to draw1720/// this phase item.1721fn cached_pipeline(&self) -> CachedRenderPipelineId;1722}17231724/// A [`RenderCommand`] that sets the pipeline for the [`CachedRenderPipelinePhaseItem`].1725pub struct SetItemPipeline;17261727impl<P: CachedRenderPipelinePhaseItem> RenderCommand<P> for SetItemPipeline {1728type Param = SRes<PipelineCache>;1729type ViewQuery = ();1730type ItemQuery = ();1731#[inline]1732fn render<'w>(1733item: &P,1734_view: (),1735_entity: Option<()>,1736pipeline_cache: SystemParamItem<'w, '_, Self::Param>,1737pass: &mut TrackedRenderPass<'w>,1738) -> RenderCommandResult {1739if let Some(pipeline) = pipeline_cache1740.into_inner()1741.get_render_pipeline(item.cached_pipeline())1742{1743pass.set_render_pipeline(pipeline);1744RenderCommandResult::Success1745} else {1746RenderCommandResult::Skip1747}1748}1749}17501751/// This system sorts the [`PhaseItem`]s of all [`SortedRenderPhase`]s of this1752/// type.1753pub fn sort_phase_system<I>(mut render_phases: ResMut<ViewSortedRenderPhases<I>>)1754where1755I: SortedPhaseItem,1756{1757for phase in render_phases.values_mut() {1758phase.sort();1759}1760}17611762/// Removes entities that became invisible or changed phases from the bins.1763///1764/// This must run after queuing.1765pub fn sweep_old_entities<BPI>(mut render_phases: ResMut<ViewBinnedRenderPhases<BPI>>)1766where1767BPI: BinnedPhaseItem,1768{1769for phase in render_phases.0.values_mut() {1770phase.sweep_old_entities();1771}1772}17731774impl BinnedRenderPhaseType {1775pub fn mesh(1776batchable: bool,1777gpu_preprocessing_support: &GpuPreprocessingSupport,1778) -> BinnedRenderPhaseType {1779match (batchable, gpu_preprocessing_support.max_supported_mode) {1780(true, GpuPreprocessingMode::Culling) => BinnedRenderPhaseType::MultidrawableMesh,1781(true, _) => BinnedRenderPhaseType::BatchableMesh,1782(false, _) => BinnedRenderPhaseType::UnbatchableMesh,1783}1784}1785}17861787impl RenderBin {1788/// Creates a [`RenderBin`] containing a single entity.1789fn from_entity(entity: MainEntity, uniform_index: InputUniformIndex) -> RenderBin {1790let mut entities = IndexMap::default();1791entities.insert(entity, uniform_index);1792RenderBin { entities }1793}17941795/// Inserts an entity into the bin.1796fn insert(&mut self, entity: MainEntity, uniform_index: InputUniformIndex) {1797self.entities.insert(entity, uniform_index);1798}17991800/// Removes an entity from the bin.1801fn remove(&mut self, entity_to_remove: MainEntity) {1802self.entities.swap_remove(&entity_to_remove);1803}18041805/// Returns true if the bin contains no entities.1806fn is_empty(&self) -> bool {1807self.entities.is_empty()1808}18091810/// Returns the [`IndexMap`] containing all the entities in the bin, along1811/// with the cached [`InputUniformIndex`] of each.1812#[inline]1813pub fn entities(&self) -> &IndexMap<MainEntity, InputUniformIndex, EntityHash> {1814&self.entities1815}1816}18171818/// An iterator that efficiently finds the indices of all zero bits in a1819/// [`FixedBitSet`] and returns them in reverse order.1820///1821/// [`FixedBitSet`] doesn't natively offer this functionality, so we have to1822/// implement it ourselves.1823#[derive(Debug)]1824struct ReverseFixedBitSetZeroesIterator<'a> {1825/// The bit set.1826bitset: &'a FixedBitSet,1827/// The next bit index we're going to scan when [`Iterator::next`] is1828/// called.1829bit_index: isize,1830}18311832impl<'a> ReverseFixedBitSetZeroesIterator<'a> {1833fn new(bitset: &'a FixedBitSet) -> ReverseFixedBitSetZeroesIterator<'a> {1834ReverseFixedBitSetZeroesIterator {1835bitset,1836bit_index: (bitset.len() as isize) - 1,1837}1838}1839}18401841impl<'a> Iterator for ReverseFixedBitSetZeroesIterator<'a> {1842type Item = usize;18431844fn next(&mut self) -> Option<usize> {1845while self.bit_index >= 0 {1846// Unpack the bit index into block and bit.1847let block_index = self.bit_index / (Block::BITS as isize);1848let bit_pos = self.bit_index % (Block::BITS as isize);18491850// Grab the block. Mask off all bits above the one we're scanning1851// from by setting them all to 1.1852let mut block = self.bitset.as_slice()[block_index as usize];1853if bit_pos + 1 < (Block::BITS as isize) {1854block |= (!0) << (bit_pos + 1);1855}18561857// Search for the next unset bit. Note that the `leading_ones`1858// function counts from the MSB to the LSB, so we need to flip it to1859// get the bit number.1860let pos = (Block::BITS as isize) - (block.leading_ones() as isize) - 1;18611862// If we found an unset bit, return it.1863if pos != -1 {1864let result = block_index * (Block::BITS as isize) + pos;1865self.bit_index = result - 1;1866return Some(result as usize);1867}18681869// Otherwise, go to the previous block.1870self.bit_index = block_index * (Block::BITS as isize) - 1;1871}18721873None1874}1875}18761877#[cfg(test)]1878mod test {1879use super::ReverseFixedBitSetZeroesIterator;1880use fixedbitset::FixedBitSet;1881use proptest::{collection::vec, prop_assert_eq, proptest};18821883proptest! {1884#[test]1885fn reverse_fixed_bit_set_zeroes_iterator(1886bits in vec(0usize..1024usize, 0usize..1024usize),1887size in 0usize..1024usize,1888) {1889// Build a random bit set.1890let mut bitset = FixedBitSet::new();1891bitset.grow(size);1892for bit in bits {1893if bit < size {1894bitset.set(bit, true);1895}1896}18971898// Iterate over the bit set backwards in a naive way, and check that1899// that iteration sequence corresponds to the optimized one.1900let mut iter = ReverseFixedBitSetZeroesIterator::new(&bitset);1901for bit_index in (0..size).rev() {1902if !bitset.contains(bit_index) {1903prop_assert_eq!(iter.next(), Some(bit_index));1904}1905}19061907prop_assert_eq!(iter.next(), None);1908}1909}1910}191119121913