bevy_render/render_phase/mod.rs
1//! The modular rendering abstraction responsible for queuing, preparing, sorting and drawing
2//! entities as part of separate render phases.
3//!
4//! In Bevy each view (camera, or shadow-casting light, etc.) has one or multiple render phases
5//! (e.g. opaque, transparent, shadow, etc).
6//! They are used to queue entities for rendering.
7//! Multiple phases might be required due to different sorting/batching behaviors
8//! (e.g. opaque: front to back, transparent: back to front) or because one phase depends on
9//! the rendered texture of the previous phase (e.g. for screen-space reflections).
10//!
11//! To draw an entity, a corresponding [`PhaseItem`] has to be added to one or multiple of these
12//! render phases for each view that it is visible in.
13//! This must be done in the [`RenderSystems::Queue`].
14//! After that the render phase sorts them in the [`RenderSystems::PhaseSort`].
15//! Finally the items are rendered using a single [`TrackedRenderPass`], during
16//! the [`RenderSystems::Render`].
17//!
18//! Therefore each phase item is assigned a [`Draw`] function.
19//! These set up the state of the [`TrackedRenderPass`] (i.e. select the
20//! [`RenderPipeline`](crate::render_resource::RenderPipeline), configure the
21//! [`BindGroup`](crate::render_resource::BindGroup)s, etc.) and then issue a draw call,
22//! for the corresponding item.
23//!
24//! The [`Draw`] function trait can either be implemented directly or such a function can be
25//! created by composing multiple [`RenderCommand`]s.
26
27mod draw;
28mod draw_state;
29mod rangefinder;
30
31use bevy_app::{App, Plugin};
32use bevy_derive::{Deref, DerefMut};
33use bevy_ecs::entity::EntityHash;
34use bevy_platform::collections::{hash_map::Entry, HashMap};
35use bevy_utils::default;
36use bytemuck::{Pod, Zeroable};
37pub use draw::*;
38pub use draw_state::*;
39use encase::ShaderType;
40use encase::{internal::WriteInto, ShaderSize};
41use indexmap::IndexMap;
42use nonmax::NonMaxU32;
43pub use rangefinder::*;
44use wgpu::{BufferUsages, Features};
45
46use crate::batching::gpu_preprocessing::{
47 GpuPreprocessingMode, GpuPreprocessingSupport, PhaseBatchedInstanceBuffers,
48 PhaseIndirectParametersBuffers,
49};
50use crate::render_resource::RawBufferVec;
51use crate::renderer::RenderDevice;
52use crate::sync_world::{MainEntity, MainEntityHashMap};
53use crate::view::{ExtractedView, RetainedViewEntity};
54use crate::RenderDebugFlags;
55use bevy_material::descriptor::CachedRenderPipelineId;
56
57use crate::{
58 batching::{
59 self,
60 gpu_preprocessing::{self, BatchedInstanceBuffers},
61 no_gpu_preprocessing::{self, BatchedInstanceBuffer},
62 GetFullBatchData,
63 },
64 render_resource::{GpuArrayBufferIndex, PipelineCache},
65 GpuResourceAppExt, Render, RenderApp, RenderSystems,
66};
67use bevy_ecs::{
68 prelude::*,
69 system::{lifetimeless::SRes, SystemParamItem},
70};
71use bevy_log::warn;
72pub use bevy_material::labels::DrawFunctionId;
73pub use bevy_material_macros::DrawFunctionLabel;
74pub use bevy_material_macros::ShaderLabel;
75use bevy_render::renderer::RenderAdapterInfo;
76use core::{
77 fmt::Debug,
78 hash::Hash,
79 iter,
80 marker::PhantomData,
81 mem,
82 ops::{Range, RangeBounds},
83};
84use smallvec::SmallVec;
85
86/// Stores the rendering instructions for a single phase that uses bins in all
87/// views.
88///
89/// They're cleared out every frame, but storing them in a resource like this
90/// allows us to reuse allocations.
91#[derive(Resource, Deref, DerefMut)]
92pub struct ViewBinnedRenderPhases<BPI>(pub HashMap<RetainedViewEntity, BinnedRenderPhase<BPI>>)
93where
94 BPI: BinnedPhaseItem;
95
96/// A collection of all rendering instructions, that will be executed by the GPU, for a
97/// single render phase for a single view.
98///
99/// Each view (camera, or shadow-casting light, etc.) can have one or multiple render phases.
100/// They are used to queue entities for rendering.
101/// Multiple phases might be required due to different sorting/batching behaviors
102/// (e.g. opaque: front to back, transparent: back to front) or because one phase depends on
103/// the rendered texture of the previous phase (e.g. for screen-space reflections).
104/// All [`PhaseItem`]s are then rendered using a single [`TrackedRenderPass`].
105/// The render pass might be reused for multiple phases to reduce GPU overhead.
106///
107/// This flavor of render phase is used for phases in which the ordering is less
108/// critical: for example, `Opaque3d`. It's generally faster than the
109/// alternative [`SortedRenderPhase`].
110pub struct BinnedRenderPhase<BPI>
111where
112 BPI: BinnedPhaseItem,
113{
114 /// The multidrawable bins.
115 ///
116 /// Each batch set key maps to a *batch set*, which in this case is a set of
117 /// meshes that can be drawn together in one multidraw call. Each batch set
118 /// is subdivided into *bins*, each of which represents a particular mesh.
119 /// Each bin contains the entity IDs of instances of that mesh.
120 ///
121 /// So, for example, if there are two cubes and a sphere present in the
122 /// scene, we would generally have one batch set containing two bins,
123 /// assuming that the cubes and sphere meshes are allocated together and use
124 /// the same pipeline. The first bin, corresponding to the cubes, will have
125 /// two entities in it. The second bin, corresponding to the sphere, will
126 /// have one entity in it.
127 pub multidrawable_meshes: IndexMap<BPI::BatchSetKey, RenderMultidrawableBatchSet<BPI>>,
128
129 /// The bins corresponding to batchable items that aren't multidrawable.
130 ///
131 /// For multidrawable entities, use `multidrawable_meshes`; for
132 /// unbatchable entities, use `unbatchable_values`.
133 pub batchable_meshes: IndexMap<(BPI::BatchSetKey, BPI::BinKey), RenderBin>,
134
135 /// The unbatchable bins.
136 ///
137 /// Each entity here is rendered in a separate drawcall.
138 pub unbatchable_meshes: IndexMap<(BPI::BatchSetKey, BPI::BinKey), UnbatchableBinnedEntities>,
139
140 /// Items in the bin that aren't meshes at all.
141 ///
142 /// Bevy itself doesn't place anything in this list, but plugins or your app
143 /// can in order to execute custom drawing commands. Draw functions for each
144 /// entity are simply called in order at rendering time.
145 ///
146 /// See the `custom_phase_item` example for an example of how to use this.
147 pub non_mesh_items: IndexMap<(BPI::BatchSetKey, BPI::BinKey), NonMeshEntities>,
148
149 /// Information on each batch set.
150 ///
151 /// A *batch set* is a set of entities that will be batched together unless
152 /// we're on a platform that doesn't support storage buffers (e.g. WebGL 2)
153 /// and differing dynamic uniform indices force us to break batches. On
154 /// platforms that support storage buffers, a batch set always consists of
155 /// at most one batch.
156 ///
157 /// Multidrawable entities come first, then batchable entities, then
158 /// unbatchable entities.
159 pub(crate) batch_sets: BinnedRenderPhaseBatchSets<BPI::BinKey>,
160
161 /// The batch and bin key for each entity.
162 ///
163 /// We retain these so that, when the entity changes, the methods that
164 /// remove items from bins can quickly find the bin each entity was located
165 /// in in order to remove it.
166 cached_entity_bin_keys: MainEntityHashMap<CachedBinnedEntity<BPI>>,
167
168 /// The gpu preprocessing mode configured for the view this phase is associated
169 /// with.
170 gpu_preprocessing_mode: GpuPreprocessingMode,
171}
172
173/// All entities that share a mesh and a material and can be batched as part of
174/// a [`BinnedRenderPhase`].
175#[derive(Default)]
176pub struct RenderBin {
177 /// A list of the entities in each bin, along with their cached
178 /// [`InputUniformIndex`].
179 entities: IndexMap<MainEntity, InputUniformIndex, EntityHash>,
180}
181
182/// Information about each bin that the [`RenderMultidrawableBatchSet`]
183/// maintains on the CPU.
184#[derive(Default)]
185pub struct RenderMultidrawableBin {
186 /// The [`RenderBinnedMeshInstanceIndex`] of each entity in this bin.
187 ///
188 /// Note that [`RenderBinnedMeshInstanceIndex`]es aren't stable from frame
189 /// to frame. They can change as entities are added and removed.
190 pub(crate) entity_to_binned_mesh_instance_index:
191 MainEntityHashMap<RenderBinnedMeshInstanceIndex>,
192}
193
194impl RenderMultidrawableBin {
195 /// Creates a new, empty [`RenderMultidrawableBin`].
196 fn new() -> RenderMultidrawableBin {
197 RenderMultidrawableBin {
198 entity_to_binned_mesh_instance_index: HashMap::default(),
199 }
200 }
201
202 /// Returns true if this bin has no entities in it.
203 fn is_empty(&self) -> bool {
204 self.entity_to_binned_mesh_instance_index.is_empty()
205 }
206}
207
208/// The index of a mesh instance in the
209/// [`RenderMultidrawableBatchSetGpuBuffers::render_binned_mesh_instance_buffer`]
210/// and [`RenderMultidrawableBatchSet::render_binned_mesh_instances_cpu`]
211/// arrays.
212///
213/// These two arrays are parallel and always have the same length.
214///
215/// These binned mesh instance indices aren't stable from frame to frame; they
216/// can change as entities are added and removed from bins. To reference a mesh
217/// instance in a stable manner, simply use [`MainEntity`].
218#[derive(Clone, Copy, Debug, Deref, DerefMut)]
219pub(crate) struct RenderBinnedMeshInstanceIndex(pub(crate) u32);
220
221/// The GPU buffers that go along with [`RenderMultidrawableBatchSet`].
222///
223/// The bin unpacking shader uses these in order to produce
224/// `PreprocessWorkItem`s.
225///
226/// See the diagram in [`RenderMultidrawableBatchSet`] for a visual explanation
227/// of this data structure.
228pub struct RenderMultidrawableBatchSetGpuBuffers {
229 /// A mapping from each binned mesh instance
230 /// (`RenderBinnedMeshInstanceIndex`) to its input uniform index
231 /// ([`InputUniformIndex`]) and bin index (`RenderBinIndex`).
232 pub render_binned_mesh_instance_buffer: RawBufferVec<GpuRenderBinnedMeshInstance>,
233 /// A mapping from each `RenderBinnedMeshInstanceIndex` to the offset of its
234 /// indirect draw parameters.
235 pub bin_index_to_indirect_parameters_offset_buffer: RawBufferVec<u32>,
236}
237
238/// Information about each binned mesh instance that the
239/// [`RenderMultidrawableBatchSet`] keeps on CPU.
240#[derive(Clone, Copy)]
241pub(crate) struct CpuRenderBinnedMeshInstance {
242 /// The entity associated with this mesh instance.
243 pub(crate) main_entity: MainEntity,
244
245 /// The index of the bin that the entity is in.
246 ///
247 /// Note that bin indices are stable from frame to frame.
248 bin_index: RenderBinIndex,
249}
250
251impl Default for CpuRenderBinnedMeshInstance {
252 fn default() -> Self {
253 CpuRenderBinnedMeshInstance {
254 main_entity: MainEntity::from(Entity::PLACEHOLDER),
255 bin_index: RenderBinIndex::default(),
256 }
257 }
258}
259
260impl RenderMultidrawableBatchSetGpuBuffers {
261 /// Creates a new set of GPU buffers for a multidrawable batch set.
262 fn new() -> RenderMultidrawableBatchSetGpuBuffers {
263 let mut render_bin_entry_buffer = RawBufferVec::new(BufferUsages::STORAGE);
264 render_bin_entry_buffer.set_label(Some("render bin entry buffer"));
265 let mut bin_index_to_indirect_parameters_offset_buffer =
266 RawBufferVec::new(BufferUsages::STORAGE);
267 bin_index_to_indirect_parameters_offset_buffer
268 .set_label(Some("bin index to indirect parameters offset buffer"));
269
270 RenderMultidrawableBatchSetGpuBuffers {
271 render_binned_mesh_instance_buffer: render_bin_entry_buffer,
272 bin_index_to_indirect_parameters_offset_buffer,
273 }
274 }
275
276 /// Inserts an entity into the GPU buffers.
277 fn insert(
278 &mut self,
279 bin: &mut RenderMultidrawableBin,
280 cpu_binned_mesh_instance_buffer: &mut Vec<CpuRenderBinnedMeshInstance>,
281 main_entity: MainEntity,
282 input_uniform_index: InputUniformIndex,
283 bin_index: RenderBinIndex,
284 ) {
285 // Creates a `GpuRenderBinnedMeshInstance`.
286 let gpu_render_bin_entry = GpuRenderBinnedMeshInstance {
287 input_uniform_index: input_uniform_index.0,
288 bin_index: bin_index.0,
289 };
290
291 // Fetch the index of this entity in the
292 // `render_binned_mesh_instance_buffer`. If there isn't one, then
293 // allocate one.
294 let render_binned_mesh_instance_buffer_index =
295 match bin.entity_to_binned_mesh_instance_index.entry(main_entity) {
296 Entry::Occupied(occupied_entry) => *occupied_entry.get(),
297 Entry::Vacant(vacant_entry) => {
298 let render_bin_buffer_index = RenderBinnedMeshInstanceIndex(
299 self.render_binned_mesh_instance_buffer
300 .push(GpuRenderBinnedMeshInstance::default())
301 as u32,
302 );
303 cpu_binned_mesh_instance_buffer.push(CpuRenderBinnedMeshInstance::default());
304 vacant_entry.insert(render_bin_buffer_index);
305 render_bin_buffer_index
306 }
307 };
308
309 // Place the entry in the instance buffer at the proper spot. Also, save
310 // the entity and bin index in the CPU-side array.
311 self.render_binned_mesh_instance_buffer.values_mut()
312 [render_binned_mesh_instance_buffer_index.0 as usize] = gpu_render_bin_entry;
313 cpu_binned_mesh_instance_buffer[render_binned_mesh_instance_buffer_index.0 as usize] =
314 CpuRenderBinnedMeshInstance {
315 main_entity,
316 bin_index,
317 };
318
319 // The GPU-side `render_binned_mesh_instance_buffer` and the CPU-side
320 // `cpu_binned_mesh_instance_buffer` are parallel arrays and must have
321 // the same length, so assert that in debug mode.
322 debug_assert_eq!(
323 self.render_binned_mesh_instance_buffer.len(),
324 cpu_binned_mesh_instance_buffer.len()
325 );
326 }
327
328 /// Removes an entity from a bin.
329 ///
330 /// The entity must be present in the bin, or a panic will occur.
331 ///
332 /// Because binned mesh instances are tightly packed in the buffers, we use
333 /// `swap_remove`, which swaps the last element to fill the place of the
334 /// entity that was removed. This might change the
335 /// [`RenderBinnedMeshInstanceIndex`] of some *other* entity, requiring the
336 /// caller to perform additional bookkeeping. This method returns the index
337 /// of the displaced entity, if there was one.
338 #[must_use]
339 fn remove(
340 &mut self,
341 bin: &mut RenderMultidrawableBin,
342 cpu_binned_mesh_instance_buffer: &mut Vec<CpuRenderBinnedMeshInstance>,
343 entity_to_remove: MainEntity,
344 ) -> Option<(RenderBinnedMeshInstanceIndex, CpuRenderBinnedMeshInstance)> {
345 // Remove the entity from the `entity_to_binned_mesh_instance_index`
346 // map.
347 let old_index = bin
348 .entity_to_binned_mesh_instance_index
349 .remove(&entity_to_remove)
350 .expect("Entity not in bin");
351
352 // Remove the entity from the reverse
353 // `render_binned_mesh_instance_buffer` list, as well
354 // as the parallel `render_binned_mesh_instance_buffer`. Because binned
355 // mesh instance indices must be contiguous, this requires use of
356 // `swap_remove`.
357 cpu_binned_mesh_instance_buffer.swap_remove(old_index.0 as usize);
358 self.render_binned_mesh_instance_buffer
359 .swap_remove(old_index.0 as usize);
360
361 // Both `render_binned_mesh_instance_buffer` and
362 // `cpu_binned_mesh_instance_buffer` must be parallel arrays, so assert
363 // that they have the same length.
364 debug_assert_eq!(
365 cpu_binned_mesh_instance_buffer.len(),
366 self.render_binned_mesh_instance_buffer.len()
367 );
368
369 // If an entity was displaced (i.e. has a new binned mesh instance index
370 // now), then return that to the caller so that they can perform
371 // whatever bookkeeping is necessary.
372 cpu_binned_mesh_instance_buffer
373 .get(old_index.0 as usize)
374 .map(|entity_indices| (old_index, *entity_indices))
375 }
376}
377
378/// The index of a bin in a [`RenderMultidrawableBatchSet`].
379///
380/// This bin index is stable from frame to frame for bins that have at least one
381/// mesh instance in them, though it can be reused if bins are deleted.
382#[derive(Clone, Copy, Default, PartialEq, Debug, Pod, Zeroable, Deref, DerefMut)]
383#[repr(transparent)]
384pub(crate) struct RenderBinIndex(pub(crate) u32);
385
386/// A collection of mesh instances that can be drawn together, sorted into bins.
387///
388/// This data structure stores a list of entity indices corresponding to mesh
389/// instances, along with the bins they live in. Each bin contains the offset of
390/// the indirect parameters needed to draw that bin.
391///
392/// This data structure consists of both CPU and GPU parts. A schematic diagram
393/// of the data structure is as follows:
394///
395/// ```text
396/// ┌─
397/// │ ─────┬──────────────┬─────
398/// │ │ Mesh Inst. 2 │
399/// │ Binned Mesh ... ├──────────────┤ ...
400/// │ Instances │ Entity 8 │
401/// │ ─────┴───┬──────────┴─────
402/// │ │
403/// │ │ ┌───────────────────────────────┐
404/// │ │ │ │
405/// │ ▼ ▼ │
406/// │ ┌───────┬───────┬───────┬───── │
407/// │ Bins │ Bin 0 │ Bin 1 │ Bin 2 │ ... │
408/// │ └───────┴───┬───┴───────┴───── │
409/// │ │ │
410/// CPU │ │ │
411/// │ Entity-to- │ ┌──────────┬──────────┬───── │
412/// │ Binned-Mesh- └─►│ Entity 3 │ Entity 8 │ ... │
413/// │ Instance- └──────────┴──────┬───┴───── │
414/// │ Index │ │
415/// │ │ │
416/// │ │ │
417/// │ │ │
418/// │ │ │
419/// │ Indirect- ┌───────┬───────┬───────┬───── │ │
420/// │ Parameters- │ IPO 0 │ IPO 1 │ IPO 2 │ ... │ │
421/// │ Offset-to- └───────┴───────┴───────┴───── │ │
422/// │ Bin-Index ▲ │ │
423/// │ │ │ │
424/// └─ │ │ │
425/// ┌───────┘ │ │
426/// ┌─ │ │ │
427/// │ │ │ │
428/// │ Bin-to- ▼ │ │
429/// │ Indirect- ┌───────┬───────┬───────┬───── │ │
430/// │ Parameters- │ Bin 0 │ Bin 1 │ Bin 2 │ ... │ │
431/// │ Offset └───────┴───────┴───────┴───── │ │
432/// GPU │ Buffer │ │
433/// │ │ │
434/// │ ▼ │
435/// │ Binned Mesh ─────┬──────────────┬──────────────┬───── │
436/// │ Instance ... │ Mesh Inst. 1 │ Mesh Inst. 2 │ ... │
437/// │ Buffer ─────┴──────────────┴───────────┬──┴───── │
438/// │ │ │
439/// └─ └────────────┘
440/// ```
441pub struct RenderMultidrawableBatchSet<BPI>
442where
443 BPI: BinnedPhaseItem,
444{
445 /// The GPU buffers that store the instances in this batch set.
446 pub(crate) gpu_buffers: RenderMultidrawableBatchSetGpuBuffers,
447
448 /// A mapping from the phase item's bin key to the index of the
449 /// corresponding bin.
450 pub(crate) bin_key_to_bin_index: HashMap<BPI::BinKey, RenderBinIndex>,
451
452 /// The actual entities within each bin, indexed by [`RenderBinIndex`].
453 ///
454 /// This list isn't tightly packed.
455 bins: Vec<Option<RenderMultidrawableBin>>,
456
457 /// A list of unused [`RenderBinIndex`]es waiting to be reused.
458 ///
459 /// Each [`RenderBinIndex`] in this list corresponds to an empty bin.
460 bin_free_list: Vec<RenderBinIndex>,
461
462 /// A mapping from the indirect parameters offset to the index of each bin.
463 ///
464 /// The *indirect parameters offset* is the index of the GPU indirect draw
465 /// command for the bin, relative to the first such index for this batch
466 /// set.
467 indirect_parameters_offset_to_bin_index: Vec<RenderBinIndex>,
468
469 /// Information about each binned mesh instance kept on CPU.
470 pub(crate) render_binned_mesh_instances_cpu: Vec<CpuRenderBinnedMeshInstance>,
471}
472
473impl<BPI> RenderMultidrawableBatchSet<BPI>
474where
475 BPI: BinnedPhaseItem,
476{
477 /// Creates a new [`RenderMultidrawableBatchSet`] containing an empty set of
478 /// bins.
479 fn new() -> RenderMultidrawableBatchSet<BPI> {
480 RenderMultidrawableBatchSet {
481 gpu_buffers: RenderMultidrawableBatchSetGpuBuffers::new(),
482 bin_key_to_bin_index: HashMap::default(),
483 bins: vec![],
484 bin_free_list: vec![],
485 indirect_parameters_offset_to_bin_index: vec![],
486 render_binned_mesh_instances_cpu: vec![],
487 }
488 }
489
490 /// Returns the first entity in the first bin (if there is one).
491 pub(crate) fn representative_entity(&self) -> Option<MainEntity> {
492 let first_bin_index = self.bin_key_to_bin_index.values().next()?;
493 let first_bin = self.bin(*first_bin_index).expect("Bin should be present");
494 first_bin
495 .entity_to_binned_mesh_instance_index
496 .keys()
497 .next()
498 .copied()
499 }
500
501 /// Returns the [`RenderMultidrawableBin`] for the given [`RenderBinIndex`].
502 pub(crate) fn bin(&self, bin_index: RenderBinIndex) -> Option<&RenderMultidrawableBin> {
503 self.bins
504 .get(bin_index.0 as usize)
505 .and_then(|bin| bin.as_ref())
506 }
507
508 /// Inserts an entity with the given uniform index into the bin with the
509 /// given key.
510 fn insert(
511 &mut self,
512 bin_key: BPI::BinKey,
513 main_entity: MainEntity,
514 input_uniform_index: InputUniformIndex,
515 ) {
516 let bin_index;
517 match self.bin_key_to_bin_index.entry(bin_key) {
518 Entry::Occupied(occupied_entry) => {
519 bin_index = *occupied_entry.get();
520 }
521 Entry::Vacant(vacant_entry) => {
522 // Create a bin. First, allocate a bin index.
523 bin_index = self
524 .bin_free_list
525 .pop()
526 .unwrap_or(RenderBinIndex(self.bins.len() as u32));
527
528 // Initialize the bin at that index.
529 if bin_index.0 as usize == self.bins.len() {
530 self.bins.push(Some(RenderMultidrawableBin::new()));
531 } else {
532 debug_assert!(self.bins[bin_index.0 as usize].is_none());
533 self.bins[bin_index.0 as usize] = Some(RenderMultidrawableBin::new());
534 }
535 vacant_entry.insert(bin_index);
536
537 // Grab an indirect parameters offset.
538 self.allocate_indirect_parameters(bin_index);
539 }
540 }
541
542 // Update the GPU buffers.
543 let bin = self.bins[bin_index.0 as usize].as_mut().unwrap();
544 self.gpu_buffers.insert(
545 bin,
546 &mut self.render_binned_mesh_instances_cpu,
547 main_entity,
548 input_uniform_index,
549 bin_index,
550 );
551 }
552
553 /// Removes the given entity from the bin with the given key.
554 ///
555 /// The given entity must be present in that bin.
556 fn remove(&mut self, main_entity: MainEntity, bin_key: &BPI::BinKey) {
557 // Fetch the bin index.
558 let bin_index = *self
559 .bin_key_to_bin_index
560 .get(bin_key)
561 .expect("Bin key not present");
562 let bin = self.bins[bin_index.0 as usize].as_mut().unwrap();
563
564 let maybe_displaced_entity_indices =
565 self.gpu_buffers
566 .remove(bin, &mut self.render_binned_mesh_instances_cpu, main_entity);
567 if let Some((old_render_bin_buffer_index, displaced_entity_indices)) =
568 maybe_displaced_entity_indices
569 {
570 self.bins[displaced_entity_indices.bin_index.0 as usize]
571 .as_mut()
572 .expect("Bin not present")
573 .entity_to_binned_mesh_instance_index
574 .insert(
575 displaced_entity_indices.main_entity,
576 old_render_bin_buffer_index,
577 );
578 }
579
580 self.remove_bin_if_empty(bin_key, bin_index);
581 }
582
583 /// Allocates an indirect parameters slot for a new bin.
584 fn allocate_indirect_parameters(&mut self, bin_index: RenderBinIndex) {
585 // Indirect parameters must be tightly packed, so we always add one to
586 // the end of the list. Record the bin index for the new indirect
587 // parameters offset.
588 let indirect_parameters_offset = self.indirect_parameters_offset_to_bin_index.len() as u32;
589 self.indirect_parameters_offset_to_bin_index.push(bin_index);
590
591 // Update the reverse mapping from bin index to indirect parameters offset.
592 if bin_index.0 as usize
593 == self
594 .gpu_buffers
595 .bin_index_to_indirect_parameters_offset_buffer
596 .len()
597 {
598 self.gpu_buffers
599 .bin_index_to_indirect_parameters_offset_buffer
600 .push(indirect_parameters_offset);
601 } else {
602 self.gpu_buffers
603 .bin_index_to_indirect_parameters_offset_buffer
604 .values_mut()[bin_index.0 as usize] = indirect_parameters_offset;
605 }
606 }
607
608 /// A helper method that removes a bin if it just became empty.
609 fn remove_bin_if_empty(&mut self, bin_key: &BPI::BinKey, bin_index: RenderBinIndex) {
610 // Is the bin empty? If not, bail.
611 let bin = self.bins[bin_index.0 as usize].as_mut().unwrap();
612 if !bin.is_empty() {
613 return;
614 }
615
616 // Remove the bin.
617 self.bin_key_to_bin_index.remove(bin_key);
618 self.bin_free_list.push(bin_index);
619 self.bins[bin_index.0 as usize] = None;
620
621 // Remove the indirect parameters offset corresponding to the bin. Note
622 // that indirect parameters must be tightly packed. Thus we must use
623 // `swap_remove`.
624 let indirect_parameters_offset = mem::replace(
625 &mut self
626 .gpu_buffers
627 .bin_index_to_indirect_parameters_offset_buffer
628 .values_mut()[bin_index.0 as usize],
629 u32::MAX,
630 );
631 let removed_bin_index = self
632 .indirect_parameters_offset_to_bin_index
633 .swap_remove(indirect_parameters_offset as usize);
634 debug_assert_eq!(bin_index, removed_bin_index);
635
636 // `swap_remove` may have changed the indirect parameter index of some
637 // other bin (specifically, the one that was previously at the end of
638 // the `Self::indirect_parameters_offset_to_bin_index` list). If it did,
639 // then we need to update the
640 // `Self::bin_index_to_indirect_parameters_offset_buffer` table to
641 // reflect the new offset of that displaced bin.
642 if let Some(displaced_bin_index) = self
643 .indirect_parameters_offset_to_bin_index
644 .get(indirect_parameters_offset as usize)
645 {
646 self.gpu_buffers
647 .bin_index_to_indirect_parameters_offset_buffer
648 .set(displaced_bin_index.0, indirect_parameters_offset);
649 }
650 }
651
652 fn is_empty(&self) -> bool {
653 self.bin_free_list.len() == self.bins.len()
654 }
655
656 pub(crate) fn bin_count(&self) -> usize {
657 self.bin_key_to_bin_index.len()
658 }
659}
660
661/// A single mesh instance in a bin.
662///
663/// This is a data structure shared between CPU and GPU. It is *not* sorted in
664/// the [`RenderMultidrawableBatchSetGpuBuffers`]: mesh instances in any given
665/// bin are not guaranteed to be adjacent.
666#[derive(Clone, Copy, Default, Pod, Zeroable, ShaderType)]
667#[repr(C)]
668pub struct GpuRenderBinnedMeshInstance {
669 /// The index of the `MeshInputUniform` in the buffer.
670 ///
671 /// This should be an [`InputUniformIndex`], but `encase` doesn't support
672 /// newtype structs.
673 pub(crate) input_uniform_index: u32,
674
675 /// The index of the bin in this batch set.
676 ///
677 /// This is the index tracked by [`RenderMultidrawableBatchSet::bins`].
678 ///
679 /// This should be a [`RenderBinIndex`], but `encase` doesn't support that
680 bin_index: u32,
681}
682
683/// Information that we keep about an entity currently within a bin.
684pub struct CachedBinnedEntity<BPI>
685where
686 BPI: BinnedPhaseItem,
687{
688 /// Information that we use to identify a cached entity in a bin.
689 pub cached_bin_key: Option<CachedBinKey<BPI>>,
690}
691
692/// Information that we use to identify a cached entity in a bin.
693pub struct CachedBinKey<BPI>
694where
695 BPI: BinnedPhaseItem,
696{
697 /// The key of the batch set containing the entity.
698 pub batch_set_key: BPI::BatchSetKey,
699 /// The key of the bin containing the entity.
700 pub bin_key: BPI::BinKey,
701 /// The type of render phase that we use to render the entity: multidraw,
702 /// plain batch, etc.
703 pub phase_type: BinnedRenderPhaseType,
704}
705
706impl<BPI> Clone for CachedBinnedEntity<BPI>
707where
708 BPI: BinnedPhaseItem,
709{
710 fn clone(&self) -> Self {
711 CachedBinnedEntity {
712 cached_bin_key: self.cached_bin_key.clone(),
713 }
714 }
715}
716
717impl<BPI> Clone for CachedBinKey<BPI>
718where
719 BPI: BinnedPhaseItem,
720{
721 fn clone(&self) -> Self {
722 CachedBinKey {
723 batch_set_key: self.batch_set_key.clone(),
724 bin_key: self.bin_key.clone(),
725 phase_type: self.phase_type,
726 }
727 }
728}
729
730impl<BPI> PartialEq for CachedBinKey<BPI>
731where
732 BPI: BinnedPhaseItem,
733{
734 fn eq(&self, other: &Self) -> bool {
735 self.batch_set_key == other.batch_set_key
736 && self.bin_key == other.bin_key
737 && self.phase_type == other.phase_type
738 }
739}
740
741/// How we store and render the batch sets.
742///
743/// Each one of these corresponds to a [`GpuPreprocessingMode`].
744pub enum BinnedRenderPhaseBatchSets<BK> {
745 /// Batches are grouped into batch sets based on dynamic uniforms.
746 ///
747 /// This corresponds to [`GpuPreprocessingMode::None`].
748 DynamicUniforms(Vec<SmallVec<[BinnedRenderPhaseBatch; 1]>>),
749
750 /// Batches are never grouped into batch sets.
751 ///
752 /// This corresponds to [`GpuPreprocessingMode::PreprocessingOnly`].
753 Direct(Vec<BinnedRenderPhaseBatch>),
754
755 /// Batches are grouped together into batch sets based on their ability to
756 /// be multi-drawn together.
757 ///
758 /// This corresponds to [`GpuPreprocessingMode::Culling`].
759 MultidrawIndirect(Vec<BinnedRenderPhaseBatchSet<BK>>),
760}
761
762/// A group of entities that will be batched together into a single multi-draw
763/// call.
764pub struct BinnedRenderPhaseBatchSet<BK> {
765 /// The first batch in this batch set.
766 pub(crate) first_batch: BinnedRenderPhaseBatch,
767 /// The key of the bin that the first batch corresponds to.
768 pub(crate) bin_key: BK,
769 /// The number of batches.
770 pub(crate) batch_count: u32,
771 /// The index of the batch set in the GPU buffer.
772 pub(crate) index: u32,
773 /// The index of the first preprocessing work item for this batch set in the
774 /// preprocessing work item buffer.
775 pub(crate) first_work_item_index: u32,
776}
777
778impl<BK> BinnedRenderPhaseBatchSets<BK> {
779 fn clear(&mut self) {
780 match *self {
781 BinnedRenderPhaseBatchSets::DynamicUniforms(ref mut vec) => vec.clear(),
782 BinnedRenderPhaseBatchSets::Direct(ref mut vec) => vec.clear(),
783 BinnedRenderPhaseBatchSets::MultidrawIndirect(ref mut vec) => vec.clear(),
784 }
785 }
786}
787
788/// Information about a single batch of entities rendered using binned phase
789/// items.
790#[derive(Debug)]
791pub struct BinnedRenderPhaseBatch {
792 /// An entity that's *representative* of this batch.
793 ///
794 /// Bevy uses this to fetch the mesh. It can be any entity in the batch.
795 pub representative_entity: (Entity, MainEntity),
796 /// The range of instance indices in this batch.
797 pub instance_range: Range<u32>,
798
799 /// The dynamic offset of the batch.
800 ///
801 /// Note that dynamic offsets are only used on platforms that don't support
802 /// storage buffers.
803 pub extra_index: PhaseItemExtraIndex,
804}
805
806/// Information about the unbatchable entities in a bin.
807pub struct UnbatchableBinnedEntities {
808 /// The entities.
809 pub entities: MainEntityHashMap<Entity>,
810
811 /// The GPU array buffer indices of each unbatchable binned entity.
812 pub(crate) buffer_indices: UnbatchableBinnedEntityIndexSet,
813}
814
815/// Information about [`BinnedRenderPhaseType::NonMesh`] entities.
816pub struct NonMeshEntities {
817 /// The entities.
818 pub entities: MainEntityHashMap<Entity>,
819}
820
821/// Stores instance indices and dynamic offsets for unbatchable entities in a
822/// binned render phase.
823///
824/// This is conceptually `Vec<UnbatchableBinnedEntityDynamicOffset>`, but it
825/// avoids the overhead of storing dynamic offsets on platforms that support
826/// them. In other words, this allows a fast path that avoids allocation on
827/// platforms that aren't WebGL 2.
828#[derive(Default)]
829
830pub(crate) enum UnbatchableBinnedEntityIndexSet {
831 /// There are no unbatchable entities in this bin (yet).
832 #[default]
833 NoEntities,
834
835 /// The instances for all unbatchable entities in this bin are contiguous,
836 /// and there are no dynamic uniforms.
837 ///
838 /// This is the typical case on platforms other than WebGL 2. We special
839 /// case this to avoid allocation on those platforms.
840 Sparse {
841 /// The range of indices.
842 instance_range: Range<u32>,
843 /// The index of the first indirect instance parameters.
844 ///
845 /// The other indices immediately follow these.
846 first_indirect_parameters_index: Option<NonMaxU32>,
847 },
848
849 /// Dynamic uniforms are present for unbatchable entities in this bin.
850 ///
851 /// We fall back to this on WebGL 2.
852 Dense(Vec<UnbatchableBinnedEntityIndices>),
853}
854
855/// The instance index and dynamic offset (if present) for an unbatchable entity.
856///
857/// This is only useful on platforms that don't support storage buffers.
858#[derive(Clone)]
859pub(crate) struct UnbatchableBinnedEntityIndices {
860 /// The instance index.
861 pub(crate) instance_index: u32,
862 /// The [`PhaseItemExtraIndex`], if present.
863 pub(crate) extra_index: PhaseItemExtraIndex,
864}
865
866/// Identifies the list within [`BinnedRenderPhase`] that a phase item is to be
867/// placed in.
868#[derive(Clone, Copy, PartialEq, Debug)]
869pub enum BinnedRenderPhaseType {
870 /// The item is a mesh that's eligible for multi-draw indirect rendering and
871 /// can be batched with other meshes of the same type.
872 MultidrawableMesh,
873
874 /// The item is a mesh that can be batched with other meshes of the same type and
875 /// drawn in a single draw call.
876 BatchableMesh,
877
878 /// The item is a mesh that's eligible for indirect rendering, but can't be
879 /// batched with other meshes of the same type.
880 UnbatchableMesh,
881
882 /// The item isn't a mesh at all.
883 ///
884 /// Bevy will simply invoke the drawing commands for such items one after
885 /// another, with no further processing.
886 ///
887 /// The engine itself doesn't enqueue any items of this type, but it's
888 /// available for use in your application and/or plugins.
889 NonMesh,
890}
891
892impl<T> From<GpuArrayBufferIndex<T>> for UnbatchableBinnedEntityIndices
893where
894 T: Clone + ShaderSize + WriteInto,
895{
896 fn from(value: GpuArrayBufferIndex<T>) -> Self {
897 UnbatchableBinnedEntityIndices {
898 instance_index: value.index,
899 extra_index: PhaseItemExtraIndex::maybe_dynamic_offset(value.dynamic_offset),
900 }
901 }
902}
903
904impl<BPI> Default for ViewBinnedRenderPhases<BPI>
905where
906 BPI: BinnedPhaseItem,
907{
908 fn default() -> Self {
909 Self(default())
910 }
911}
912
913impl<BPI> ViewBinnedRenderPhases<BPI>
914where
915 BPI: BinnedPhaseItem,
916{
917 pub fn prepare_for_new_frame(
918 &mut self,
919 retained_view_entity: RetainedViewEntity,
920 gpu_preprocessing: GpuPreprocessingMode,
921 ) {
922 match self.entry(retained_view_entity) {
923 Entry::Occupied(mut entry) => entry.get_mut().prepare_for_new_frame(),
924 Entry::Vacant(entry) => {
925 entry.insert(BinnedRenderPhase::<BPI>::new(gpu_preprocessing));
926 }
927 }
928 }
929}
930
931/// The index of the uniform describing this object in the GPU buffer, when GPU
932/// preprocessing is enabled.
933///
934/// For example, for 3D meshes, this is the index of the `MeshInputUniform` in
935/// the buffer.
936///
937/// This field is ignored if GPU preprocessing isn't in use, such as (currently)
938/// in the case of 2D meshes. In that case, it can be safely set to
939/// [`core::default::Default::default`].
940#[derive(Clone, Copy, PartialEq, Default, Deref, DerefMut, Debug, Pod, Zeroable)]
941#[repr(transparent)]
942pub struct InputUniformIndex(pub u32);
943
944impl<BPI> BinnedRenderPhase<BPI>
945where
946 BPI: BinnedPhaseItem,
947{
948 /// Bins a new entity.
949 ///
950 /// The `phase_type` parameter specifies whether the entity is a
951 /// preprocessable mesh and whether it can be binned with meshes of the same
952 /// type.
953 pub fn add(
954 &mut self,
955 batch_set_key: BPI::BatchSetKey,
956 bin_key: BPI::BinKey,
957 (entity, main_entity): (Entity, MainEntity),
958 input_uniform_index: InputUniformIndex,
959 mut phase_type: BinnedRenderPhaseType,
960 ) {
961 // If the user has overridden indirect drawing for this view, we need to
962 // force the phase type to be batchable instead.
963 if self.gpu_preprocessing_mode == GpuPreprocessingMode::PreprocessingOnly
964 && phase_type == BinnedRenderPhaseType::MultidrawableMesh
965 {
966 phase_type = BinnedRenderPhaseType::BatchableMesh;
967 }
968
969 match phase_type {
970 BinnedRenderPhaseType::MultidrawableMesh => {
971 match self.multidrawable_meshes.entry(batch_set_key.clone()) {
972 indexmap::map::Entry::Occupied(mut entry) => {
973 entry
974 .get_mut()
975 .insert(bin_key.clone(), main_entity, input_uniform_index);
976 }
977 indexmap::map::Entry::Vacant(entry) => {
978 let mut new_batch_set = RenderMultidrawableBatchSet::new();
979 new_batch_set.insert(bin_key.clone(), main_entity, input_uniform_index);
980 entry.insert(new_batch_set);
981 }
982 }
983 }
984
985 BinnedRenderPhaseType::BatchableMesh => {
986 match self
987 .batchable_meshes
988 .entry((batch_set_key.clone(), bin_key.clone()).clone())
989 {
990 indexmap::map::Entry::Occupied(mut entry) => {
991 entry.get_mut().insert(main_entity, input_uniform_index);
992 }
993 indexmap::map::Entry::Vacant(entry) => {
994 entry.insert(RenderBin::from_entity(main_entity, input_uniform_index));
995 }
996 }
997 }
998
999 BinnedRenderPhaseType::UnbatchableMesh => {
1000 match self
1001 .unbatchable_meshes
1002 .entry((batch_set_key.clone(), bin_key.clone()))
1003 {
1004 indexmap::map::Entry::Occupied(mut entry) => {
1005 entry.get_mut().entities.insert(main_entity, entity);
1006 }
1007 indexmap::map::Entry::Vacant(entry) => {
1008 let mut entities = MainEntityHashMap::default();
1009 entities.insert(main_entity, entity);
1010 entry.insert(UnbatchableBinnedEntities {
1011 entities,
1012 buffer_indices: default(),
1013 });
1014 }
1015 }
1016 }
1017
1018 BinnedRenderPhaseType::NonMesh => {
1019 // We don't process these items further.
1020 match self
1021 .non_mesh_items
1022 .entry((batch_set_key.clone(), bin_key.clone()).clone())
1023 {
1024 indexmap::map::Entry::Occupied(mut entry) => {
1025 entry.get_mut().entities.insert(main_entity, entity);
1026 }
1027 indexmap::map::Entry::Vacant(entry) => {
1028 let mut entities = MainEntityHashMap::default();
1029 entities.insert(main_entity, entity);
1030 entry.insert(NonMeshEntities { entities });
1031 }
1032 }
1033 }
1034 }
1035
1036 // Update the cache.
1037 self.update_cache(
1038 main_entity,
1039 Some(CachedBinKey {
1040 batch_set_key,
1041 bin_key,
1042 phase_type,
1043 }),
1044 );
1045 }
1046
1047 /// Inserts an entity into the cache with the given change tick.
1048 pub fn update_cache(
1049 &mut self,
1050 main_entity: MainEntity,
1051 cached_bin_key: Option<CachedBinKey<BPI>>,
1052 ) {
1053 let new_cached_binned_entity = CachedBinnedEntity { cached_bin_key };
1054 self.cached_entity_bin_keys
1055 .insert(main_entity, new_cached_binned_entity);
1056 }
1057
1058 /// Encodes the GPU commands needed to render all entities in this phase.
1059 pub fn render<'w>(
1060 &self,
1061 render_pass: &mut TrackedRenderPass<'w>,
1062 world: &'w World,
1063 view: Entity,
1064 ) -> Result<(), DrawError> {
1065 {
1066 let draw_functions = world.resource::<DrawFunctions<BPI>>();
1067 let mut draw_functions = draw_functions.write();
1068 draw_functions.prepare(world);
1069 // Make sure to drop the reader-writer lock here to avoid recursive
1070 // locks.
1071 }
1072
1073 self.render_batchable_meshes(render_pass, world, view)?;
1074 self.render_unbatchable_meshes(render_pass, world, view)?;
1075 self.render_non_meshes(render_pass, world, view)?;
1076
1077 Ok(())
1078 }
1079
1080 /// Renders all batchable meshes queued in this phase.
1081 fn render_batchable_meshes<'w>(
1082 &self,
1083 render_pass: &mut TrackedRenderPass<'w>,
1084 world: &'w World,
1085 view: Entity,
1086 ) -> Result<(), DrawError> {
1087 let draw_functions = world.resource::<DrawFunctions<BPI>>();
1088 let mut draw_functions = draw_functions.write();
1089
1090 let render_device = world.resource::<RenderDevice>();
1091 let render_adapter_info = world.resource::<RenderAdapterInfo>();
1092 let multi_draw_indirect_count_supported = render_device
1093 .features()
1094 .contains(Features::MULTI_DRAW_INDIRECT_COUNT)
1095 // TODO: https://github.com/gfx-rs/wgpu/issues/7974
1096 && !matches!(render_adapter_info.backend, wgpu::Backend::Dx12);
1097
1098 match self.batch_sets {
1099 BinnedRenderPhaseBatchSets::DynamicUniforms(ref batch_sets) => {
1100 debug_assert_eq!(self.batchable_meshes.len(), batch_sets.len());
1101
1102 for ((batch_set_key, bin_key), batch_set) in
1103 self.batchable_meshes.keys().zip(batch_sets.iter())
1104 {
1105 for batch in batch_set {
1106 let binned_phase_item = BPI::new(
1107 batch_set_key.clone(),
1108 bin_key.clone(),
1109 batch.representative_entity,
1110 batch.instance_range.clone(),
1111 batch.extra_index.clone(),
1112 );
1113
1114 // Fetch the draw function.
1115 let Some(draw_function) =
1116 draw_functions.get_mut(binned_phase_item.draw_function())
1117 else {
1118 continue;
1119 };
1120
1121 draw_function.draw(world, render_pass, view, &binned_phase_item)?;
1122 }
1123 }
1124 }
1125
1126 BinnedRenderPhaseBatchSets::Direct(ref batch_set) => {
1127 for (batch, (batch_set_key, bin_key)) in
1128 batch_set.iter().zip(self.batchable_meshes.keys())
1129 {
1130 let binned_phase_item = BPI::new(
1131 batch_set_key.clone(),
1132 bin_key.clone(),
1133 batch.representative_entity,
1134 batch.instance_range.clone(),
1135 batch.extra_index.clone(),
1136 );
1137
1138 // Fetch the draw function.
1139 let Some(draw_function) =
1140 draw_functions.get_mut(binned_phase_item.draw_function())
1141 else {
1142 continue;
1143 };
1144
1145 draw_function.draw(world, render_pass, view, &binned_phase_item)?;
1146 }
1147 }
1148
1149 BinnedRenderPhaseBatchSets::MultidrawIndirect(ref batch_sets) => {
1150 for (batch_set_key, batch_set) in self
1151 .multidrawable_meshes
1152 .keys()
1153 .chain(
1154 self.batchable_meshes
1155 .keys()
1156 .map(|(batch_set_key, _)| batch_set_key),
1157 )
1158 .zip(batch_sets.iter())
1159 {
1160 let batch = &batch_set.first_batch;
1161
1162 let batch_set_index = if multi_draw_indirect_count_supported {
1163 NonMaxU32::new(batch_set.index)
1164 } else {
1165 None
1166 };
1167
1168 let binned_phase_item = BPI::new(
1169 batch_set_key.clone(),
1170 batch_set.bin_key.clone(),
1171 batch.representative_entity,
1172 batch.instance_range.clone(),
1173 match batch.extra_index {
1174 PhaseItemExtraIndex::None => PhaseItemExtraIndex::None,
1175 PhaseItemExtraIndex::DynamicOffset(ref dynamic_offset) => {
1176 PhaseItemExtraIndex::DynamicOffset(*dynamic_offset)
1177 }
1178 PhaseItemExtraIndex::IndirectParametersIndex { ref range, .. } => {
1179 PhaseItemExtraIndex::IndirectParametersIndex {
1180 range: range.start..(range.start + batch_set.batch_count),
1181 batch_set_index,
1182 }
1183 }
1184 },
1185 );
1186
1187 // Fetch the draw function.
1188 let Some(draw_function) =
1189 draw_functions.get_mut(binned_phase_item.draw_function())
1190 else {
1191 continue;
1192 };
1193
1194 draw_function.draw(world, render_pass, view, &binned_phase_item)?;
1195 }
1196 }
1197 }
1198
1199 Ok(())
1200 }
1201
1202 /// Renders all unbatchable meshes queued in this phase.
1203 fn render_unbatchable_meshes<'w>(
1204 &self,
1205 render_pass: &mut TrackedRenderPass<'w>,
1206 world: &'w World,
1207 view: Entity,
1208 ) -> Result<(), DrawError> {
1209 let draw_functions = world.resource::<DrawFunctions<BPI>>();
1210 let mut draw_functions = draw_functions.write();
1211
1212 for (batch_set_key, bin_key) in self.unbatchable_meshes.keys() {
1213 let unbatchable_entities =
1214 &self.unbatchable_meshes[&(batch_set_key.clone(), bin_key.clone())];
1215 for (entity_index, entity) in unbatchable_entities.entities.iter().enumerate() {
1216 let unbatchable_dynamic_offset = match &unbatchable_entities.buffer_indices {
1217 UnbatchableBinnedEntityIndexSet::NoEntities => {
1218 // Shouldn't happen…
1219 continue;
1220 }
1221 UnbatchableBinnedEntityIndexSet::Sparse {
1222 instance_range,
1223 first_indirect_parameters_index,
1224 } => UnbatchableBinnedEntityIndices {
1225 instance_index: instance_range.start + entity_index as u32,
1226 extra_index: match first_indirect_parameters_index {
1227 None => PhaseItemExtraIndex::None,
1228 Some(first_indirect_parameters_index) => {
1229 let first_indirect_parameters_index_for_entity =
1230 u32::from(*first_indirect_parameters_index)
1231 + entity_index as u32;
1232 PhaseItemExtraIndex::IndirectParametersIndex {
1233 range: first_indirect_parameters_index_for_entity
1234 ..(first_indirect_parameters_index_for_entity + 1),
1235 batch_set_index: None,
1236 }
1237 }
1238 },
1239 },
1240 UnbatchableBinnedEntityIndexSet::Dense(dynamic_offsets) => {
1241 dynamic_offsets[entity_index].clone()
1242 }
1243 };
1244
1245 let binned_phase_item = BPI::new(
1246 batch_set_key.clone(),
1247 bin_key.clone(),
1248 (*entity.1, *entity.0),
1249 unbatchable_dynamic_offset.instance_index
1250 ..(unbatchable_dynamic_offset.instance_index + 1),
1251 unbatchable_dynamic_offset.extra_index,
1252 );
1253
1254 // Fetch the draw function.
1255 let Some(draw_function) = draw_functions.get_mut(binned_phase_item.draw_function())
1256 else {
1257 continue;
1258 };
1259
1260 draw_function.draw(world, render_pass, view, &binned_phase_item)?;
1261 }
1262 }
1263 Ok(())
1264 }
1265
1266 /// Renders all objects of type [`BinnedRenderPhaseType::NonMesh`].
1267 ///
1268 /// These will have been added by plugins or the application.
1269 fn render_non_meshes<'w>(
1270 &self,
1271 render_pass: &mut TrackedRenderPass<'w>,
1272 world: &'w World,
1273 view: Entity,
1274 ) -> Result<(), DrawError> {
1275 let draw_functions = world.resource::<DrawFunctions<BPI>>();
1276 let mut draw_functions = draw_functions.write();
1277
1278 for ((batch_set_key, bin_key), non_mesh_entities) in &self.non_mesh_items {
1279 for (main_entity, entity) in non_mesh_entities.entities.iter() {
1280 // Come up with a fake batch range and extra index. The draw
1281 // function is expected to manage any sort of batching logic itself.
1282 let binned_phase_item = BPI::new(
1283 batch_set_key.clone(),
1284 bin_key.clone(),
1285 (*entity, *main_entity),
1286 0..1,
1287 PhaseItemExtraIndex::None,
1288 );
1289
1290 let Some(draw_function) = draw_functions.get_mut(binned_phase_item.draw_function())
1291 else {
1292 continue;
1293 };
1294
1295 draw_function.draw(world, render_pass, view, &binned_phase_item)?;
1296 }
1297 }
1298
1299 Ok(())
1300 }
1301
1302 pub fn is_empty(&self) -> bool {
1303 self.multidrawable_meshes.is_empty()
1304 && self.batchable_meshes.is_empty()
1305 && self.unbatchable_meshes.is_empty()
1306 && self.non_mesh_items.is_empty()
1307 }
1308
1309 pub fn prepare_for_new_frame(&mut self) {
1310 self.batch_sets.clear();
1311
1312 for unbatchable_bin in self.unbatchable_meshes.values_mut() {
1313 unbatchable_bin.buffer_indices.clear();
1314 }
1315 }
1316
1317 /// Removes a single entity from its bin.
1318 ///
1319 /// If doing so makes the bin empty, this method removes the bin as well.
1320 pub fn remove(&mut self, main_entity: MainEntity) {
1321 let Some(cached_binned_entity) = self.cached_entity_bin_keys.remove(&main_entity) else {
1322 return;
1323 };
1324
1325 if let Some(ref cached_bin_key) = cached_binned_entity.cached_bin_key {
1326 remove_entity_from_bin(
1327 main_entity,
1328 cached_bin_key,
1329 &mut self.multidrawable_meshes,
1330 &mut self.batchable_meshes,
1331 &mut self.unbatchable_meshes,
1332 &mut self.non_mesh_items,
1333 );
1334 }
1335 }
1336}
1337
1338/// Removes an entity from a bin.
1339///
1340/// If this makes the bin empty, this function removes the bin as well.
1341///
1342/// This is a standalone function instead of a method on [`BinnedRenderPhase`]
1343/// for borrow check reasons.
1344fn remove_entity_from_bin<BPI>(
1345 entity: MainEntity,
1346 entity_bin_key: &CachedBinKey<BPI>,
1347 multidrawable_meshes: &mut IndexMap<BPI::BatchSetKey, RenderMultidrawableBatchSet<BPI>>,
1348 batchable_meshes: &mut IndexMap<(BPI::BatchSetKey, BPI::BinKey), RenderBin>,
1349 unbatchable_meshes: &mut IndexMap<(BPI::BatchSetKey, BPI::BinKey), UnbatchableBinnedEntities>,
1350 non_mesh_items: &mut IndexMap<(BPI::BatchSetKey, BPI::BinKey), NonMeshEntities>,
1351) where
1352 BPI: BinnedPhaseItem,
1353{
1354 match entity_bin_key.phase_type {
1355 BinnedRenderPhaseType::MultidrawableMesh => {
1356 if let indexmap::map::Entry::Occupied(mut batch_set_entry) =
1357 multidrawable_meshes.entry(entity_bin_key.batch_set_key.clone())
1358 {
1359 batch_set_entry
1360 .get_mut()
1361 .remove(entity, &entity_bin_key.bin_key);
1362
1363 // If the batch set is now empty, remove it. This will perturb
1364 // the order, but that's OK because we're going to sort the bin
1365 // afterwards.
1366 if batch_set_entry.get_mut().is_empty() {
1367 batch_set_entry.swap_remove();
1368 }
1369 }
1370 }
1371
1372 BinnedRenderPhaseType::BatchableMesh => {
1373 if let indexmap::map::Entry::Occupied(mut bin_entry) = batchable_meshes.entry((
1374 entity_bin_key.batch_set_key.clone(),
1375 entity_bin_key.bin_key.clone(),
1376 )) {
1377 bin_entry.get_mut().remove(entity);
1378
1379 // If the bin is now empty, remove the bin.
1380 if bin_entry.get_mut().is_empty() {
1381 bin_entry.swap_remove();
1382 }
1383 }
1384 }
1385
1386 BinnedRenderPhaseType::UnbatchableMesh => {
1387 if let indexmap::map::Entry::Occupied(mut bin_entry) = unbatchable_meshes.entry((
1388 entity_bin_key.batch_set_key.clone(),
1389 entity_bin_key.bin_key.clone(),
1390 )) {
1391 bin_entry.get_mut().entities.remove(&entity);
1392
1393 // If the bin is now empty, remove the bin.
1394 if bin_entry.get_mut().entities.is_empty() {
1395 bin_entry.swap_remove();
1396 }
1397 }
1398 }
1399
1400 BinnedRenderPhaseType::NonMesh => {
1401 if let indexmap::map::Entry::Occupied(mut bin_entry) = non_mesh_items.entry((
1402 entity_bin_key.batch_set_key.clone(),
1403 entity_bin_key.bin_key.clone(),
1404 )) {
1405 bin_entry.get_mut().entities.remove(&entity);
1406
1407 // If the bin is now empty, remove the bin.
1408 if bin_entry.get_mut().entities.is_empty() {
1409 bin_entry.swap_remove();
1410 }
1411 }
1412 }
1413 }
1414}
1415
1416impl<BPI> BinnedRenderPhase<BPI>
1417where
1418 BPI: BinnedPhaseItem,
1419{
1420 fn new(gpu_preprocessing: GpuPreprocessingMode) -> Self {
1421 Self {
1422 multidrawable_meshes: IndexMap::default(),
1423 batchable_meshes: IndexMap::default(),
1424 unbatchable_meshes: IndexMap::default(),
1425 non_mesh_items: IndexMap::default(),
1426 batch_sets: match gpu_preprocessing {
1427 GpuPreprocessingMode::Culling => {
1428 BinnedRenderPhaseBatchSets::MultidrawIndirect(vec![])
1429 }
1430 GpuPreprocessingMode::PreprocessingOnly => {
1431 BinnedRenderPhaseBatchSets::Direct(vec![])
1432 }
1433 GpuPreprocessingMode::None => BinnedRenderPhaseBatchSets::DynamicUniforms(vec![]),
1434 },
1435 cached_entity_bin_keys: MainEntityHashMap::default(),
1436 gpu_preprocessing_mode: gpu_preprocessing,
1437 }
1438 }
1439}
1440
1441impl UnbatchableBinnedEntityIndexSet {
1442 /// Returns the [`UnbatchableBinnedEntityIndices`] for the given entity.
1443 fn indices_for_entity_index(
1444 &self,
1445 entity_index: u32,
1446 ) -> Option<UnbatchableBinnedEntityIndices> {
1447 match self {
1448 UnbatchableBinnedEntityIndexSet::NoEntities => None,
1449 UnbatchableBinnedEntityIndexSet::Sparse { instance_range, .. }
1450 if entity_index >= instance_range.len() as u32 =>
1451 {
1452 None
1453 }
1454 UnbatchableBinnedEntityIndexSet::Sparse {
1455 instance_range,
1456 first_indirect_parameters_index: None,
1457 } => Some(UnbatchableBinnedEntityIndices {
1458 instance_index: instance_range.start + entity_index,
1459 extra_index: PhaseItemExtraIndex::None,
1460 }),
1461 UnbatchableBinnedEntityIndexSet::Sparse {
1462 instance_range,
1463 first_indirect_parameters_index: Some(first_indirect_parameters_index),
1464 } => {
1465 let first_indirect_parameters_index_for_this_batch =
1466 u32::from(*first_indirect_parameters_index) + entity_index;
1467 Some(UnbatchableBinnedEntityIndices {
1468 instance_index: instance_range.start + entity_index,
1469 extra_index: PhaseItemExtraIndex::IndirectParametersIndex {
1470 range: first_indirect_parameters_index_for_this_batch
1471 ..(first_indirect_parameters_index_for_this_batch + 1),
1472 batch_set_index: None,
1473 },
1474 })
1475 }
1476 UnbatchableBinnedEntityIndexSet::Dense(indices) => {
1477 indices.get(entity_index as usize).cloned()
1478 }
1479 }
1480 }
1481}
1482
1483/// A convenient abstraction for adding all the systems necessary for a binned
1484/// render phase to the render app.
1485///
1486/// This is the version used when the pipeline supports GPU preprocessing: e.g.
1487/// 3D PBR meshes.
1488pub struct BinnedRenderPhasePlugin<BPI, GFBD>
1489where
1490 BPI: BinnedPhaseItem,
1491 GFBD: GetFullBatchData,
1492{
1493 /// Debugging flags that can optionally be set when constructing the renderer.
1494 pub debug_flags: RenderDebugFlags,
1495 phantom: PhantomData<(BPI, GFBD)>,
1496}
1497
1498impl<BPI, GFBD> BinnedRenderPhasePlugin<BPI, GFBD>
1499where
1500 BPI: BinnedPhaseItem,
1501 GFBD: GetFullBatchData,
1502{
1503 pub fn new(debug_flags: RenderDebugFlags) -> Self {
1504 Self {
1505 debug_flags,
1506 phantom: PhantomData,
1507 }
1508 }
1509}
1510
1511impl<BPI, GFBD> Plugin for BinnedRenderPhasePlugin<BPI, GFBD>
1512where
1513 BPI: BinnedPhaseItem,
1514 GFBD: GetFullBatchData + Sync + Send + 'static,
1515{
1516 fn build(&self, app: &mut App) {
1517 let Some(render_app) = app.get_sub_app_mut(RenderApp) else {
1518 return;
1519 };
1520
1521 render_app
1522 .init_gpu_resource::<ViewBinnedRenderPhases<BPI>>()
1523 .allow_ambiguous_resource::<ViewBinnedRenderPhases<BPI>>()
1524 .init_gpu_resource::<PhaseBatchedInstanceBuffers<BPI, GFBD::BufferData>>()
1525 .init_gpu_resource::<PhaseIndirectParametersBuffers<BPI>>()
1526 .add_systems(
1527 Render,
1528 (
1529 batching::sort_binned_render_phase::<BPI>.in_set(RenderSystems::PhaseSort),
1530 (
1531 no_gpu_preprocessing::batch_and_prepare_binned_render_phase::<BPI, GFBD>
1532 .run_if(resource_exists::<BatchedInstanceBuffer<GFBD::BufferData>>),
1533 gpu_preprocessing::batch_and_prepare_binned_render_phase::<BPI, GFBD>
1534 .run_if(
1535 resource_exists::<
1536 BatchedInstanceBuffers<GFBD::BufferData, GFBD::BufferInputData>,
1537 >,
1538 ),
1539 )
1540 .in_set(RenderSystems::PrepareResourcesBatchPhases)
1541 .ambiguous_with(RenderSystems::PrepareResourcesBatchPhases),
1542 gpu_preprocessing::write_binned_instance_buffers::<BPI, GFBD>
1543 .run_if(
1544 resource_exists::<
1545 BatchedInstanceBuffers<GFBD::BufferData, GFBD::BufferInputData>,
1546 >,
1547 )
1548 .in_set(RenderSystems::PrepareResourcesWritePhaseBuffers)
1549 .ambiguous_with(RenderSystems::PrepareResourcesWritePhaseBuffers),
1550 gpu_preprocessing::collect_buffers_for_phase::<BPI, GFBD>
1551 .run_if(
1552 resource_exists::<
1553 BatchedInstanceBuffers<GFBD::BufferData, GFBD::BufferInputData>,
1554 >,
1555 )
1556 .in_set(RenderSystems::PrepareResourcesCollectPhaseBuffers),
1557 ),
1558 );
1559 }
1560}
1561
1562/// Stores the rendering instructions for a single phase that sorts items in all
1563/// views.
1564///
1565/// They're cleared out every frame, but storing them in a resource like this
1566/// allows us to reuse allocations.
1567#[derive(Resource, Deref, DerefMut)]
1568pub struct ViewSortedRenderPhases<SPI>(pub HashMap<RetainedViewEntity, SortedRenderPhase<SPI>>)
1569where
1570 SPI: SortedPhaseItem;
1571
1572impl<SPI> Default for ViewSortedRenderPhases<SPI>
1573where
1574 SPI: SortedPhaseItem,
1575{
1576 fn default() -> Self {
1577 Self(default())
1578 }
1579}
1580
1581impl<SPI> ViewSortedRenderPhases<SPI>
1582where
1583 SPI: SortedPhaseItem,
1584{
1585 /// Ensures that a set of phases are present for the given
1586 /// [`RetainedViewEntity`].
1587 pub fn prepare_for_new_frame(&mut self, retained_view_entity: RetainedViewEntity) {
1588 match self.entry(retained_view_entity) {
1589 Entry::Occupied(mut entry) => {
1590 let render_phase = entry.get_mut();
1591 for (render_entity, main_entity) in render_phase.transient_items.drain(..) {
1592 render_phase
1593 .items
1594 .swap_remove(&(render_entity, main_entity));
1595 }
1596 }
1597 Entry::Vacant(entry) => {
1598 entry.insert(default());
1599 }
1600 }
1601 }
1602}
1603
1604/// A convenient abstraction for adding all the systems necessary for a sorted
1605/// render phase to the render app.
1606///
1607/// This is the version used when the pipeline supports GPU preprocessing: e.g.
1608/// 3D PBR meshes.
1609pub struct SortedRenderPhasePlugin<SPI, GFBD>
1610where
1611 SPI: SortedPhaseItem,
1612 GFBD: GetFullBatchData,
1613{
1614 /// Debugging flags that can optionally be set when constructing the renderer.
1615 pub debug_flags: RenderDebugFlags,
1616 phantom: PhantomData<(SPI, GFBD)>,
1617}
1618
1619impl<SPI, GFBD> SortedRenderPhasePlugin<SPI, GFBD>
1620where
1621 SPI: SortedPhaseItem,
1622 GFBD: GetFullBatchData,
1623{
1624 pub fn new(debug_flags: RenderDebugFlags) -> Self {
1625 Self {
1626 debug_flags,
1627 phantom: PhantomData,
1628 }
1629 }
1630}
1631
1632impl<SPI, GFBD> Plugin for SortedRenderPhasePlugin<SPI, GFBD>
1633where
1634 SPI: SortedPhaseItem + CachedRenderPipelinePhaseItem,
1635 GFBD: GetFullBatchData + Sync + Send + 'static,
1636{
1637 fn build(&self, app: &mut App) {
1638 let Some(render_app) = app.get_sub_app_mut(RenderApp) else {
1639 return;
1640 };
1641
1642 render_app
1643 .init_gpu_resource::<ViewSortedRenderPhases<SPI>>()
1644 .allow_ambiguous_resource::<ViewSortedRenderPhases<SPI>>()
1645 .init_gpu_resource::<PhaseBatchedInstanceBuffers<SPI, GFBD::BufferData>>()
1646 .init_gpu_resource::<PhaseIndirectParametersBuffers<SPI>>()
1647 .add_systems(
1648 Render,
1649 (
1650 (
1651 no_gpu_preprocessing::batch_and_prepare_sorted_render_phase::<SPI, GFBD>
1652 .run_if(resource_exists::<BatchedInstanceBuffer<GFBD::BufferData>>),
1653 gpu_preprocessing::batch_and_prepare_sorted_render_phase::<SPI, GFBD>
1654 .run_if(
1655 resource_exists::<
1656 BatchedInstanceBuffers<GFBD::BufferData, GFBD::BufferInputData>,
1657 >,
1658 ),
1659 )
1660 .in_set(RenderSystems::PrepareResourcesBatchPhases),
1661 gpu_preprocessing::collect_buffers_for_phase::<SPI, GFBD>
1662 .run_if(
1663 resource_exists::<
1664 BatchedInstanceBuffers<GFBD::BufferData, GFBD::BufferInputData>,
1665 >,
1666 )
1667 .in_set(RenderSystems::PrepareResourcesCollectPhaseBuffers),
1668 ),
1669 );
1670 }
1671}
1672
1673impl UnbatchableBinnedEntityIndexSet {
1674 /// Adds a new entity to the list of unbatchable binned entities.
1675 pub fn add(&mut self, indices: UnbatchableBinnedEntityIndices) {
1676 match self {
1677 UnbatchableBinnedEntityIndexSet::NoEntities => {
1678 match indices.extra_index {
1679 PhaseItemExtraIndex::DynamicOffset(_) => {
1680 // This is the first entity we've seen, and we don't have
1681 // compute shaders. Initialize an array.
1682 *self = UnbatchableBinnedEntityIndexSet::Dense(vec![indices]);
1683 }
1684 PhaseItemExtraIndex::None => {
1685 // This is the first entity we've seen, and we have compute
1686 // shaders. Initialize the fast path.
1687 *self = UnbatchableBinnedEntityIndexSet::Sparse {
1688 instance_range: indices.instance_index..indices.instance_index + 1,
1689 first_indirect_parameters_index: None,
1690 }
1691 }
1692 PhaseItemExtraIndex::IndirectParametersIndex {
1693 range: ref indirect_parameters_index,
1694 ..
1695 } => {
1696 // This is the first entity we've seen, and we have compute
1697 // shaders. Initialize the fast path.
1698 *self = UnbatchableBinnedEntityIndexSet::Sparse {
1699 instance_range: indices.instance_index..indices.instance_index + 1,
1700 first_indirect_parameters_index: NonMaxU32::new(
1701 indirect_parameters_index.start,
1702 ),
1703 }
1704 }
1705 }
1706 }
1707
1708 UnbatchableBinnedEntityIndexSet::Sparse {
1709 instance_range,
1710 first_indirect_parameters_index,
1711 } if instance_range.end == indices.instance_index
1712 && ((first_indirect_parameters_index.is_none()
1713 && indices.extra_index == PhaseItemExtraIndex::None)
1714 || first_indirect_parameters_index.is_some_and(
1715 |first_indirect_parameters_index| match indices.extra_index {
1716 PhaseItemExtraIndex::IndirectParametersIndex {
1717 range: ref this_range,
1718 ..
1719 } => {
1720 u32::from(first_indirect_parameters_index) + instance_range.end
1721 - instance_range.start
1722 == this_range.start
1723 }
1724 PhaseItemExtraIndex::DynamicOffset(_) | PhaseItemExtraIndex::None => {
1725 false
1726 }
1727 },
1728 )) =>
1729 {
1730 // This is the normal case on non-WebGL 2.
1731 instance_range.end += 1;
1732 }
1733
1734 UnbatchableBinnedEntityIndexSet::Sparse { instance_range, .. } => {
1735 // We thought we were in non-WebGL 2 mode, but we got a dynamic
1736 // offset or non-contiguous index anyway. This shouldn't happen,
1737 // but let's go ahead and do the sensible thing anyhow: demote
1738 // the compressed `NoDynamicOffsets` field to the full
1739 // `DynamicOffsets` array.
1740 warn!(
1741 "Unbatchable binned entity index set was demoted from sparse to dense. \
1742 This is a bug in the renderer. Please report it.",
1743 );
1744 let new_dynamic_offsets = (0..instance_range.len() as u32)
1745 .flat_map(|entity_index| self.indices_for_entity_index(entity_index))
1746 .chain(iter::once(indices))
1747 .collect();
1748 *self = UnbatchableBinnedEntityIndexSet::Dense(new_dynamic_offsets);
1749 }
1750
1751 UnbatchableBinnedEntityIndexSet::Dense(dense_indices) => {
1752 dense_indices.push(indices);
1753 }
1754 }
1755 }
1756
1757 /// Clears the unbatchable binned entity index set.
1758 fn clear(&mut self) {
1759 match self {
1760 UnbatchableBinnedEntityIndexSet::Dense(dense_indices) => dense_indices.clear(),
1761 UnbatchableBinnedEntityIndexSet::Sparse { .. } => {
1762 *self = UnbatchableBinnedEntityIndexSet::NoEntities;
1763 }
1764 _ => {}
1765 }
1766 }
1767}
1768
1769/// A collection of all items to be rendered that will be encoded to GPU
1770/// commands for a single render phase for a single view.
1771///
1772/// Each view (camera, or shadow-casting light, etc.) can have one or multiple render phases.
1773/// They are used to queue entities for rendering.
1774/// Multiple phases might be required due to different sorting/batching behaviors
1775/// (e.g. opaque: front to back, transparent: back to front) or because one phase depends on
1776/// the rendered texture of the previous phase (e.g. for screen-space reflections).
1777/// All [`PhaseItem`]s are then rendered using a single [`TrackedRenderPass`].
1778/// The render pass might be reused for multiple phases to reduce GPU overhead.
1779///
1780/// This flavor of render phase is used only for meshes that need to be sorted
1781/// back-to-front, such as transparent meshes. For items that don't need strict
1782/// sorting, [`BinnedRenderPhase`] is preferred, for performance.
1783pub struct SortedRenderPhase<I>
1784where
1785 I: SortedPhaseItem,
1786{
1787 /// The items within this [`SortedRenderPhase`].
1788 pub items: IndexMap<(Entity, MainEntity), I, EntityHash>,
1789 /// Items within this render phase that will be automatically removed after
1790 /// this frame.
1791 pub transient_items: Vec<(Entity, MainEntity)>,
1792}
1793
1794impl<I> Default for SortedRenderPhase<I>
1795where
1796 I: SortedPhaseItem,
1797{
1798 fn default() -> Self {
1799 Self {
1800 items: IndexMap::default(),
1801 transient_items: vec![],
1802 }
1803 }
1804}
1805
1806impl<I> SortedRenderPhase<I>
1807where
1808 I: SortedPhaseItem,
1809{
1810 /// Adds a [`PhaseItem`] to this render phase, which will persist between
1811 /// frames until removed.
1812 #[inline]
1813 pub fn add_retained(&mut self, item: I) {
1814 self.items.insert((item.entity(), item.main_entity()), item);
1815 }
1816
1817 /// Adds a [`PhaseItem`] to this render phase, which will be automatically
1818 /// removed after this frame.
1819 #[inline]
1820 pub fn add_transient(&mut self, item: I) {
1821 let key = (item.entity(), item.main_entity());
1822 self.items.insert(key, item);
1823 self.transient_items.push(key);
1824 }
1825
1826 /// Removes the [`PhaseItem`] corresponding to the given main-world entity
1827 /// from this render phase.
1828 #[inline]
1829 pub fn remove(&mut self, render_entity: Entity, main_entity: MainEntity) {
1830 self.items.swap_remove(&(render_entity, main_entity));
1831 }
1832
1833 /// Removes all [`PhaseItem`]s from this render phase.
1834 #[inline]
1835 pub fn clear(&mut self) {
1836 self.items.clear();
1837 }
1838
1839 /// Populates whatever internal fields are necessary in order to perform the
1840 /// sort.
1841 ///
1842 /// For example, for transparent 3D phases, this calculates the distance
1843 /// from each object to the view.
1844 pub fn recalculate_sort_keys(&mut self, view: &ExtractedView) {
1845 I::recalculate_sort_keys(&mut self.items, view);
1846 }
1847
1848 /// Sorts all of its [`PhaseItem`]s.
1849 pub fn sort(&mut self) {
1850 I::sort(&mut self.items);
1851 }
1852
1853 /// An [`Iterator`] through the associated [`Entity`] for each [`PhaseItem`] in order.
1854 #[inline]
1855 pub fn iter_entities(&'_ self) -> impl Iterator<Item = Entity> + '_ {
1856 self.items.values().map(PhaseItem::entity)
1857 }
1858
1859 /// Renders all of its [`PhaseItem`]s using their corresponding draw functions.
1860 pub fn render<'w>(
1861 &self,
1862 render_pass: &mut TrackedRenderPass<'w>,
1863 world: &'w World,
1864 view: Entity,
1865 ) -> Result<(), DrawError> {
1866 self.render_range(render_pass, world, view, ..)
1867 }
1868
1869 /// Renders all [`PhaseItem`]s in the provided `range` (based on their index in `self.items`) using their corresponding draw functions.
1870 pub fn render_range<'w>(
1871 &self,
1872 render_pass: &mut TrackedRenderPass<'w>,
1873 world: &'w World,
1874 view: Entity,
1875 range: impl RangeBounds<usize>,
1876 ) -> Result<(), DrawError> {
1877 let items = self
1878 .items
1879 .get_range(range)
1880 .expect("`Range` provided to `render_range()` is out of bounds");
1881
1882 let draw_functions = world.resource::<DrawFunctions<I>>();
1883 let mut draw_functions = draw_functions.write();
1884 draw_functions.prepare(world);
1885
1886 let mut index = 0;
1887 while index < items.len() {
1888 let item = &items[index];
1889 let batch_range = item.batch_range();
1890 if batch_range.is_empty() {
1891 index += 1;
1892 } else {
1893 let draw_function = draw_functions.get_mut(item.draw_function()).unwrap();
1894 draw_function.draw(world, render_pass, view, item)?;
1895 index += batch_range.len();
1896 }
1897 }
1898 Ok(())
1899 }
1900}
1901
1902/// An item (entity of the render world) which will be drawn to a texture or the screen,
1903/// as part of a render phase.
1904///
1905/// The data required for rendering an entity is extracted from the main world in the
1906/// [`ExtractSchedule`](crate::ExtractSchedule).
1907/// Then it has to be queued up for rendering during the [`RenderSystems::Queue`],
1908/// by adding a corresponding phase item to a render phase.
1909/// Afterwards it will be possibly sorted and rendered automatically in the
1910/// [`RenderSystems::PhaseSort`] and [`RenderSystems::Render`], respectively.
1911///
1912/// `PhaseItem`s come in two flavors: [`BinnedPhaseItem`]s and
1913/// [`SortedPhaseItem`]s.
1914///
1915/// * Binned phase items have a `BinKey` which specifies what bin they're to be
1916/// placed in. All items in the same bin are eligible to be batched together.
1917/// The `BinKey`s are sorted, but the individual bin items aren't. Binned phase
1918/// items are good for opaque meshes, in which the order of rendering isn't
1919/// important. Generally, binned phase items are faster than sorted phase items.
1920///
1921/// * Sorted phase items, on the other hand, are placed into one large buffer
1922/// and then sorted all at once. This is needed for transparent meshes, which
1923/// have to be sorted back-to-front to render with the painter's algorithm.
1924/// These types of phase items are generally slower than binned phase items.
1925pub trait PhaseItem: Sized + Send + Sync + 'static {
1926 /// Whether or not this `PhaseItem` should be subjected to automatic batching. (Default: `true`)
1927 const AUTOMATIC_BATCHING: bool = true;
1928
1929 /// The corresponding entity that will be drawn.
1930 ///
1931 /// This is used to fetch the render data of the entity, required by the draw function,
1932 /// from the render world .
1933 fn entity(&self) -> Entity;
1934
1935 /// The main world entity represented by this `PhaseItem`.
1936 fn main_entity(&self) -> MainEntity;
1937
1938 /// Specifies the [`Draw`] function used to render the item.
1939 fn draw_function(&self) -> DrawFunctionId;
1940
1941 /// The range of instances that the batch covers. After doing a batched draw, batch range
1942 /// length phase items will be skipped. This design is to avoid having to restructure the
1943 /// render phase unnecessarily.
1944 fn batch_range(&self) -> &Range<u32>;
1945 fn batch_range_mut(&mut self) -> &mut Range<u32>;
1946
1947 /// Returns the [`PhaseItemExtraIndex`].
1948 ///
1949 /// If present, this is either a dynamic offset or an indirect parameters
1950 /// index.
1951 fn extra_index(&self) -> PhaseItemExtraIndex;
1952
1953 /// Returns a pair of mutable references to both the batch range and extra
1954 /// index.
1955 fn batch_range_and_extra_index_mut(&mut self) -> (&mut Range<u32>, &mut PhaseItemExtraIndex);
1956}
1957
1958/// The "extra index" associated with some [`PhaseItem`]s, alongside the
1959/// indirect instance index.
1960///
1961/// Sometimes phase items require another index in addition to the range of
1962/// instances they already have. These can be:
1963///
1964/// * The *dynamic offset*: a `wgpu` dynamic offset into the uniform buffer of
1965/// instance data. This is used on platforms that don't support storage
1966/// buffers, to work around uniform buffer size limitations.
1967///
1968/// * The *indirect parameters index*: an index into the buffer that specifies
1969/// the indirect parameters for this [`PhaseItem`]'s drawcall. This is used when
1970/// indirect mode is on (as used for GPU culling).
1971///
1972/// Note that our indirect draw functionality requires storage buffers, so it's
1973/// impossible to have both a dynamic offset and an indirect parameters index.
1974/// This convenient fact allows us to pack both indices into a single `u32`.
1975#[derive(Clone, PartialEq, Eq, Hash, Debug)]
1976pub enum PhaseItemExtraIndex {
1977 /// No extra index is present.
1978 None,
1979 /// A `wgpu` dynamic offset into the uniform buffer of instance data. This
1980 /// is used on platforms that don't support storage buffers, to work around
1981 /// uniform buffer size limitations.
1982 DynamicOffset(u32),
1983 /// An index into the buffer that specifies the indirect parameters for this
1984 /// [`PhaseItem`]'s drawcall. This is used when indirect mode is on (as used
1985 /// for GPU culling).
1986 IndirectParametersIndex {
1987 /// The range of indirect parameters within the indirect parameters array.
1988 ///
1989 /// If we're using `multi_draw_indirect_count`, this specifies the
1990 /// maximum range of indirect parameters within that array. If batches
1991 /// are ultimately culled out on the GPU, the actual number of draw
1992 /// commands might be lower than the length of this range.
1993 range: Range<u32>,
1994 /// If `multi_draw_indirect_count` is in use, and this phase item is
1995 /// part of a batch set, specifies the index of the batch set that this
1996 /// phase item is a part of.
1997 ///
1998 /// If `multi_draw_indirect_count` isn't in use, or this phase item
1999 /// isn't part of a batch set, this is `None`.
2000 batch_set_index: Option<NonMaxU32>,
2001 },
2002}
2003
2004impl PhaseItemExtraIndex {
2005 /// Returns either an indirect parameters index or
2006 /// [`PhaseItemExtraIndex::None`], as appropriate.
2007 pub fn maybe_indirect_parameters_index(
2008 indirect_parameters_index: Option<NonMaxU32>,
2009 ) -> PhaseItemExtraIndex {
2010 match indirect_parameters_index {
2011 Some(indirect_parameters_index) => PhaseItemExtraIndex::IndirectParametersIndex {
2012 range: u32::from(indirect_parameters_index)
2013 ..(u32::from(indirect_parameters_index) + 1),
2014 batch_set_index: None,
2015 },
2016 None => PhaseItemExtraIndex::None,
2017 }
2018 }
2019
2020 /// Returns either a dynamic offset index or [`PhaseItemExtraIndex::None`],
2021 /// as appropriate.
2022 pub fn maybe_dynamic_offset(dynamic_offset: Option<NonMaxU32>) -> PhaseItemExtraIndex {
2023 match dynamic_offset {
2024 Some(dynamic_offset) => PhaseItemExtraIndex::DynamicOffset(dynamic_offset.into()),
2025 None => PhaseItemExtraIndex::None,
2026 }
2027 }
2028}
2029
2030/// Represents phase items that are placed into bins. The `BinKey` specifies
2031/// which bin they're to be placed in. Bin keys are sorted, and items within the
2032/// same bin are eligible to be batched together. The elements within the bins
2033/// aren't themselves sorted.
2034///
2035/// An example of a binned phase item is `Opaque3d`, for which the rendering
2036/// order isn't critical.
2037pub trait BinnedPhaseItem: PhaseItem {
2038 /// The key used for binning [`PhaseItem`]s into bins. Order the members of
2039 /// [`BinnedPhaseItem::BinKey`] by the order of binding for best
2040 /// performance. For example, pipeline id, draw function id, mesh asset id,
2041 /// lowest variable bind group id such as the material bind group id, and
2042 /// its dynamic offsets if any, next bind group and offsets, etc. This
2043 /// reduces the need for rebinding between bins and improves performance.
2044 type BinKey: Clone + Send + Sync + PartialEq + Eq + Ord + Hash;
2045
2046 /// The key used to combine batches into batch sets.
2047 ///
2048 /// A *batch set* is a set of meshes that can potentially be multi-drawn
2049 /// together.
2050 type BatchSetKey: PhaseItemBatchSetKey;
2051
2052 /// Creates a new binned phase item from the key and per-entity data.
2053 ///
2054 /// Unlike [`SortedPhaseItem`]s, this is generally called "just in time"
2055 /// before rendering. The resulting phase item isn't stored in any data
2056 /// structures, resulting in significant memory savings.
2057 fn new(
2058 batch_set_key: Self::BatchSetKey,
2059 bin_key: Self::BinKey,
2060 representative_entity: (Entity, MainEntity),
2061 batch_range: Range<u32>,
2062 extra_index: PhaseItemExtraIndex,
2063 ) -> Self;
2064}
2065
2066/// A key used to combine batches into batch sets.
2067///
2068/// A *batch set* is a set of meshes that can potentially be multi-drawn
2069/// together.
2070pub trait PhaseItemBatchSetKey: Clone + Send + Sync + PartialEq + Eq + Ord + Hash {
2071 /// Returns true if this batch set key describes indexed meshes or false if
2072 /// it describes non-indexed meshes.
2073 ///
2074 /// Bevy uses this in order to determine which kind of indirect draw
2075 /// parameters to use, if indirect drawing is enabled.
2076 fn indexed(&self) -> bool;
2077}
2078
2079/// Represents phase items that must be sorted. The `SortKey` specifies the
2080/// order that these items are drawn in. These are placed into a single array,
2081/// and the array as a whole is then sorted.
2082///
2083/// An example of a sorted phase item is `Transparent3d`, which must be sorted
2084/// back to front in order to correctly render with the painter's algorithm.
2085pub trait SortedPhaseItem: PhaseItem {
2086 /// The type used for ordering the items. The smallest values are drawn first.
2087 /// This order can be calculated using the [`ViewRangefinder3d`],
2088 /// based on the view-space `Z` value of the corresponding view matrix.
2089 type SortKey: Ord;
2090
2091 /// Determines the order in which the items are drawn.
2092 fn sort_key(&self) -> Self::SortKey;
2093
2094 /// Sorts a slice of phase items into render order. Generally if the same type
2095 /// is batched this should use a stable sort like [`slice::sort_by_key`].
2096 /// In almost all other cases, this should not be altered from the default,
2097 /// which uses an unstable sort, as this provides the best balance of CPU and GPU
2098 /// performance.
2099 ///
2100 /// Implementers can optionally not sort the list at all. This is generally advisable if and
2101 /// only if the renderer supports a depth prepass, which is by default not supported by
2102 /// the rest of Bevy's first party rendering crates. Even then, this may have a negative
2103 /// impact on GPU-side performance due to overdraw.
2104 ///
2105 /// It's advised to always profile for performance changes when changing this implementation.
2106 #[inline]
2107 fn sort(items: &mut IndexMap<(Entity, MainEntity), Self, EntityHash>) {
2108 items.sort_unstable_by_key(|_, value| Self::sort_key(value));
2109 }
2110
2111 /// Populates whatever internal fields are necessary in order to perform the
2112 /// sort.
2113 ///
2114 /// The renderer calls this method right before calling [`Self::sort`]. For
2115 /// 3D transparent phases that need to be depth sorted, it populates the
2116 /// `distance` field with the actual distance from the view. For other
2117 /// phases, this method is generally a no-op.
2118 fn recalculate_sort_keys(
2119 items: &mut IndexMap<(Entity, MainEntity), Self, EntityHash>,
2120 view: &ExtractedView,
2121 );
2122
2123 /// Whether this phase item targets indexed meshes (those with both vertex
2124 /// and index buffers as opposed to just vertex buffers).
2125 ///
2126 /// Bevy needs this information in order to properly group phase items
2127 /// together for multi-draw indirect, because the GPU layout of indirect
2128 /// commands differs between indexed and non-indexed meshes.
2129 ///
2130 /// If you're implementing a custom phase item that doesn't describe a mesh,
2131 /// you can safely return false here.
2132 fn indexed(&self) -> bool;
2133}
2134
2135/// A [`PhaseItem`] item, that automatically sets the appropriate render pipeline,
2136/// cached in the [`PipelineCache`].
2137///
2138/// You can use the [`SetItemPipeline`] render command to set the pipeline for this item.
2139pub trait CachedRenderPipelinePhaseItem: PhaseItem {
2140 /// The id of the render pipeline, cached in the [`PipelineCache`], that will be used to draw
2141 /// this phase item.
2142 fn cached_pipeline(&self) -> CachedRenderPipelineId;
2143}
2144
2145/// A [`RenderCommand`] that sets the pipeline for the [`CachedRenderPipelinePhaseItem`].
2146pub struct SetItemPipeline;
2147
2148impl<P: CachedRenderPipelinePhaseItem> RenderCommand<P> for SetItemPipeline {
2149 type Param = SRes<PipelineCache>;
2150 type ViewQuery = ();
2151 type ItemQuery = ();
2152 #[inline]
2153 fn render<'w>(
2154 item: &P,
2155 _view: (),
2156 _entity: Option<()>,
2157 pipeline_cache: SystemParamItem<'w, '_, Self::Param>,
2158 pass: &mut TrackedRenderPass<'w>,
2159 ) -> RenderCommandResult {
2160 if let Some(pipeline) = pipeline_cache
2161 .into_inner()
2162 .get_render_pipeline(item.cached_pipeline())
2163 {
2164 pass.set_render_pipeline(pipeline);
2165 RenderCommandResult::Success
2166 } else {
2167 RenderCommandResult::Skip
2168 }
2169 }
2170}
2171
2172/// This system sorts the [`PhaseItem`]s of all [`SortedRenderPhase`]s of this
2173/// type.
2174pub fn sort_phase_system<I>(
2175 views: Query<&ExtractedView>,
2176 mut render_phases: ResMut<ViewSortedRenderPhases<I>>,
2177) where
2178 I: SortedPhaseItem,
2179{
2180 for view in &views {
2181 let Some(phase) = render_phases.get_mut(&view.retained_view_entity) else {
2182 continue;
2183 };
2184 phase.recalculate_sort_keys(view);
2185 phase.sort();
2186 }
2187}
2188
2189impl BinnedRenderPhaseType {
2190 pub fn mesh(
2191 batchable: bool,
2192 gpu_preprocessing_support: &GpuPreprocessingSupport,
2193 ) -> BinnedRenderPhaseType {
2194 match (batchable, gpu_preprocessing_support.max_supported_mode) {
2195 (true, GpuPreprocessingMode::Culling) => BinnedRenderPhaseType::MultidrawableMesh,
2196 (true, _) => BinnedRenderPhaseType::BatchableMesh,
2197 (false, _) => BinnedRenderPhaseType::UnbatchableMesh,
2198 }
2199 }
2200}
2201
2202impl RenderBin {
2203 /// Creates a [`RenderBin`] containing a single entity.
2204 fn from_entity(entity: MainEntity, uniform_index: InputUniformIndex) -> RenderBin {
2205 let mut entities = IndexMap::default();
2206 entities.insert(entity, uniform_index);
2207 RenderBin { entities }
2208 }
2209
2210 /// Inserts an entity into the bin.
2211 fn insert(&mut self, entity: MainEntity, uniform_index: InputUniformIndex) {
2212 self.entities.insert(entity, uniform_index);
2213 }
2214
2215 /// Removes an entity from the bin.
2216 fn remove(&mut self, entity_to_remove: MainEntity) {
2217 self.entities.swap_remove(&entity_to_remove);
2218 }
2219
2220 /// Returns true if the bin contains no entities.
2221 fn is_empty(&self) -> bool {
2222 self.entities.is_empty()
2223 }
2224
2225 /// Returns the [`IndexMap`] containing all the entities in the bin, along
2226 /// with the cached [`InputUniformIndex`] of each.
2227 #[inline]
2228 pub fn entities(&self) -> &IndexMap<MainEntity, InputUniformIndex, EntityHash> {
2229 &self.entities
2230 }
2231}
2232
2233#[cfg(test)]
2234mod tests {
2235 use proptest_derive::Arbitrary;
2236
2237 use crate::render_phase::GpuRenderBinnedMeshInstance;
2238
2239 /// A `proptest`-based randomized test for `RenderMultidrawableBatchSet`.
2240 ///
2241 /// `proptest` works by generating random test cases and performing checks.
2242 /// We use it to generate random sets of entity bin insertion and removal
2243 /// operations, then verify that the data structure is consistent and that
2244 /// all invariants are upheld.
2245 #[test]
2246 #[expect(
2247 non_local_definitions,
2248 reason = "`derive(Arbitrary)` generates an impl here"
2249 )]
2250 fn render_multidrawable_batch_set() {
2251 use super::RenderMultidrawableBatchSet;
2252
2253 use core::ops::Range;
2254
2255 use bevy_ecs::entity::{Entity, EntityIndex};
2256 use bevy_material::labels::DrawFunctionId;
2257 use proptest::{bool, collection, test_runner::TestRunner};
2258
2259 use crate::{
2260 render_phase::{
2261 BinnedPhaseItem, InputUniformIndex, PhaseItem, PhaseItemBatchSetKey, RenderBinIndex,
2262 },
2263 sync_world::{MainEntity, MainEntityHashMap, MainEntityHashSet},
2264 };
2265
2266 /// A fake `BinnedPhaseItem` that we use for testing.
2267 #[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
2268 struct MockBinnedPhaseItem;
2269
2270 /// A fake `BinnedPhaseItem::BinKey` that we use for testing.
2271 ///
2272 /// The bin key should match the bin index.
2273 #[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
2274 struct MockBinnedPhaseItemBinKey(u32);
2275
2276 /// A fake `BinnedPhaseItem::BatchKey` that we use for testing.
2277 #[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
2278 struct MockBinnedPhaseItemBatchSetKey;
2279
2280 impl BinnedPhaseItem for MockBinnedPhaseItem {
2281 type BinKey = MockBinnedPhaseItemBinKey;
2282
2283 type BatchSetKey = MockBinnedPhaseItemBatchSetKey;
2284
2285 fn new(
2286 _: Self::BatchSetKey,
2287 _: Self::BinKey,
2288 _: (Entity, MainEntity),
2289 _: Range<u32>,
2290 _: super::PhaseItemExtraIndex,
2291 ) -> Self {
2292 Self
2293 }
2294 }
2295
2296 impl PhaseItem for MockBinnedPhaseItem {
2297 fn entity(&self) -> Entity {
2298 unimplemented!()
2299 }
2300
2301 fn main_entity(&self) -> MainEntity {
2302 unimplemented!()
2303 }
2304
2305 fn draw_function(&self) -> DrawFunctionId {
2306 unimplemented!()
2307 }
2308
2309 fn batch_range(&self) -> &Range<u32> {
2310 unimplemented!()
2311 }
2312
2313 fn batch_range_mut(&mut self) -> &mut Range<u32> {
2314 unimplemented!()
2315 }
2316
2317 fn extra_index(&self) -> super::PhaseItemExtraIndex {
2318 unimplemented!()
2319 }
2320
2321 fn batch_range_and_extra_index_mut(
2322 &mut self,
2323 ) -> (&mut Range<u32>, &mut super::PhaseItemExtraIndex) {
2324 unimplemented!()
2325 }
2326 }
2327
2328 impl PhaseItemBatchSetKey for MockBinnedPhaseItemBatchSetKey {
2329 fn indexed(&self) -> bool {
2330 // Doesn't matter. We arbitrarily return true.
2331 true
2332 }
2333 }
2334
2335 /// A single operation that we perform on the multidrawable batch set.
2336 #[derive(Arbitrary, Debug)]
2337 enum RenderMultidrawableBatchSetOperation {
2338 /// Add an entity (mock mesh instance) to the batch set.
2339 Add {
2340 /// The ID of the entity.
2341 #[proptest(strategy = "0..32u32")]
2342 entity_id: u32,
2343 /// The index of the bin that we place the entity into.
2344 #[proptest(strategy = "0..8u32")]
2345 bin_index: u32,
2346 /// The input uniform index associated with the entity.
2347 #[proptest(strategy = "0..1024u32")]
2348 input_uniform_index: u32,
2349 },
2350 /// Remove an entity (mock mesh instance) from the batch set.
2351 Remove {
2352 /// The ID of the entity.
2353 #[proptest(strategy = "0..32u32")]
2354 entity_id: u32,
2355 },
2356 }
2357
2358 /// A "control" structure that stores the expected contents of the
2359 /// multidrawable batch set.
2360 ///
2361 /// This is essentially a simpler, but inefficient, version of
2362 /// `RenderMultidrawableBatchSet` that we use to check that the
2363 /// invariants of `RenderMultidrawableBatchSet` are being upheld.
2364 struct ExpectedMultidrawableBatchSet {
2365 /// A mapping from each bin index to the entities within it.
2366 bin_index_to_entities: Vec<MainEntityHashSet>,
2367 /// A mapping from each entity ID to the binned mesh instance data.
2368 entity_to_binned_mesh_instance: MainEntityHashMap<GpuRenderBinnedMeshInstance>,
2369 }
2370
2371 impl ExpectedMultidrawableBatchSet {
2372 /// Inserts an entity into the given bin of the control structure.
2373 fn insert(
2374 &mut self,
2375 entity: MainEntity,
2376 bin_index: RenderBinIndex,
2377 input_uniform_index: InputUniformIndex,
2378 ) {
2379 self.entity_to_binned_mesh_instance.insert(
2380 entity,
2381 GpuRenderBinnedMeshInstance {
2382 bin_index: bin_index.0,
2383 input_uniform_index: input_uniform_index.0,
2384 },
2385 );
2386 self.bin_index_to_entities[bin_index.0 as usize].insert(entity);
2387 }
2388
2389 /// Removes an entity from the control structure and returns its instance.
2390 fn remove(&mut self, entity: MainEntity) -> GpuRenderBinnedMeshInstance {
2391 let render_binned_mesh_instance =
2392 self.entity_to_binned_mesh_instance.remove(&entity).unwrap();
2393 self.bin_index_to_entities[render_binned_mesh_instance.bin_index as usize]
2394 .remove(&entity);
2395 render_binned_mesh_instance
2396 }
2397 }
2398
2399 let mut runner = TestRunner::default();
2400 runner
2401 .run(
2402 // Generate up to 1024 random operations.
2403 //
2404 // Invalid operations (attempting to bin an entity that's
2405 // already binned or attempting to unbin an entity that wasn't
2406 // binned) will be skipped.
2407 &collection::vec(
2408 proptest::prelude::any::<RenderMultidrawableBatchSetOperation>(),
2409 0..1024,
2410 ),
2411 |ops| {
2412 // Create the data structure to test.
2413 let mut batch_set = RenderMultidrawableBatchSet::<MockBinnedPhaseItem>::new();
2414
2415 // Create the control data structure.
2416 let mut expected = ExpectedMultidrawableBatchSet {
2417 bin_index_to_entities: vec![MainEntityHashSet::default(); 1024],
2418 entity_to_binned_mesh_instance: MainEntityHashMap::default(),
2419 };
2420
2421 // Process each operation, skipping invalid ones.
2422 for op in ops.iter() {
2423 match *op {
2424 RenderMultidrawableBatchSetOperation::Add {
2425 entity_id,
2426 bin_index,
2427 input_uniform_index,
2428 } => {
2429 let entity = MainEntity::from(Entity::from_index(
2430 EntityIndex::from_raw_u32(entity_id).unwrap(),
2431 ));
2432 let input_uniform_index = InputUniformIndex(input_uniform_index);
2433
2434 // Skip this operation if it's trying to add an entity that's already binned.
2435 if expected
2436 .entity_to_binned_mesh_instance
2437 .contains_key(&entity)
2438 {
2439 continue;
2440 }
2441
2442 // Insert into the expected and actual data
2443 // structures.
2444 expected.insert(
2445 entity,
2446 RenderBinIndex(bin_index),
2447 input_uniform_index,
2448 );
2449 batch_set.insert(
2450 MockBinnedPhaseItemBinKey(bin_index),
2451 entity,
2452 input_uniform_index,
2453 );
2454 }
2455
2456 RenderMultidrawableBatchSetOperation::Remove { entity_id } => {
2457 let entity = MainEntity::from(Entity::from_index(
2458 EntityIndex::from_raw_u32(entity_id).unwrap(),
2459 ));
2460
2461 // Skip this operation if it's trying to remove
2462 // an entity that wasn't already binned.
2463 if !expected
2464 .entity_to_binned_mesh_instance
2465 .contains_key(&entity)
2466 {
2467 continue;
2468 }
2469
2470 // Insert into the expected and actual data
2471 // structures.
2472 let render_binned_mesh_instance = expected.remove(entity);
2473 batch_set.remove(
2474 entity,
2475 &MockBinnedPhaseItemBinKey(
2476 render_binned_mesh_instance.bin_index,
2477 ),
2478 );
2479 }
2480 }
2481 }
2482
2483 // Verify that the batch set invariants are upheld.
2484 verify(&batch_set, &expected);
2485
2486 Ok(())
2487 },
2488 )
2489 .unwrap();
2490
2491 // Verifies that the given `batch_set` matches the expected batch set
2492 // and ensures that the invariants of that batch set are upheld.
2493 fn verify(
2494 batch_set: &RenderMultidrawableBatchSet<MockBinnedPhaseItem>,
2495 expected: &ExpectedMultidrawableBatchSet,
2496 ) {
2497 // Verify every entity is present.
2498 verify_entity_presence(
2499 batch_set,
2500 &expected.bin_index_to_entities,
2501 &expected.entity_to_binned_mesh_instance,
2502 );
2503
2504 // Verify that the binned mesh instance GPU buffer is correct.
2505 verify_render_binned_mesh_instance_buffer(batch_set, &expected.bin_index_to_entities);
2506
2507 // Verify that no indirect parameter offsets overlap.
2508 verify_indirect_parameters_offsets(batch_set);
2509 }
2510
2511 /// Verifies that every entity is present in the multidrawable batch
2512 /// set after modifications.
2513 fn verify_entity_presence(
2514 batch_set: &RenderMultidrawableBatchSet<MockBinnedPhaseItem>,
2515 expected: &[MainEntityHashSet],
2516 entity_to_bin_index_and_input_uniform_index: &MainEntityHashMap<
2517 GpuRenderBinnedMeshInstance,
2518 >,
2519 ) {
2520 for (bin_key_index, expected_entities) in expected.iter().enumerate() {
2521 let bin_key = MockBinnedPhaseItemBinKey(bin_key_index as u32);
2522 if expected_entities.is_empty() {
2523 assert!(!batch_set.bin_key_to_bin_index.contains_key(&bin_key));
2524 continue;
2525 }
2526
2527 let Some(render_bin_index) = batch_set.bin_key_to_bin_index.get(&bin_key) else {
2528 panic!("Bin not present: key {:?}", bin_key);
2529 };
2530 let Some(render_bin) = batch_set.bin(*render_bin_index) else {
2531 panic!("Bin not present: index {:?}", render_bin_index);
2532 };
2533 for expected_entity in expected_entities {
2534 let Some(GpuRenderBinnedMeshInstance {
2535 bin_index,
2536 input_uniform_index,
2537 }) = entity_to_bin_index_and_input_uniform_index.get(expected_entity)
2538 else {
2539 panic!(
2540 "Test harness bug: entity-to-bin-index-and-input-uniform-index \
2541 table and expected table don't agree"
2542 );
2543 };
2544 assert_eq!(MockBinnedPhaseItemBinKey(*bin_index), bin_key);
2545
2546 let Some(render_bin_buffer_index) = render_bin
2547 .entity_to_binned_mesh_instance_index
2548 .get(expected_entity)
2549 else {
2550 panic!("Buffer index not present");
2551 };
2552 let render_bin_entry = batch_set
2553 .gpu_buffers
2554 .render_binned_mesh_instance_buffer
2555 .values()[render_bin_buffer_index.0 as usize];
2556 assert_eq!(render_bin_entry.bin_index, **render_bin_index);
2557 assert_eq!(render_bin_entry.input_uniform_index, *input_uniform_index);
2558 }
2559 }
2560 }
2561
2562 /// Verifies that the
2563 /// `RenderMultidrawableBatchSet::render_binned_mesh_instances_cpu`
2564 /// contains the correct entity and bin index.
2565 fn verify_render_binned_mesh_instance_buffer(
2566 batch_set: &RenderMultidrawableBatchSet<MockBinnedPhaseItem>,
2567 expected: &[MainEntityHashSet],
2568 ) {
2569 for (render_bin_buffer_index, gpu_render_binned_mesh_instance) in batch_set
2570 .gpu_buffers
2571 .render_binned_mesh_instance_buffer
2572 .values()
2573 .iter()
2574 .enumerate()
2575 {
2576 let binned_mesh_instance_cpu =
2577 &batch_set.render_binned_mesh_instances_cpu[render_bin_buffer_index];
2578
2579 // Make sure that the `GpuRenderBinnedMeshInstance::bin_index`
2580 // matches the `CpuRenderBinnedMeshInstance::bin_index`.
2581 let gpu_render_bin_index = gpu_render_binned_mesh_instance.bin_index;
2582 assert_eq!(gpu_render_bin_index, *binned_mesh_instance_cpu.bin_index);
2583
2584 let render_bin = batch_set.bins[gpu_render_bin_index as usize]
2585 .as_ref()
2586 .unwrap();
2587
2588 // Make sure that the entity in the
2589 // `RenderMultidrawableBin::entity_to_binned_mesh_instance_index`
2590 // table matches the entity in the
2591 // `CpuRenderBinnedMeshInstance`.
2592 let Some(entity) = render_bin
2593 .entity_to_binned_mesh_instance_index
2594 .iter()
2595 .find_map(|(entity, buffer_index)| {
2596 if render_bin_buffer_index as u32 == buffer_index.0 {
2597 Some(entity)
2598 } else {
2599 None
2600 }
2601 })
2602 else {
2603 panic!(
2604 "Entity at buffer index {:?} not found in bin {:?}",
2605 render_bin_buffer_index, gpu_render_bin_index
2606 );
2607 };
2608 assert_eq!(binned_mesh_instance_cpu.main_entity, *entity);
2609
2610 // Make sure that the bin with the appropriate bin key should
2611 // actually contain the entity.
2612 let Some(bin_key) =
2613 batch_set
2614 .bin_key_to_bin_index
2615 .iter()
2616 .find_map(|(bin_key, bin_index)| {
2617 if bin_index.0 == gpu_render_bin_index {
2618 Some(*bin_key)
2619 } else {
2620 None
2621 }
2622 })
2623 else {
2624 panic!(
2625 "Couldn't find a bin key for bin index {:?}",
2626 gpu_render_bin_index
2627 );
2628 };
2629 assert!(expected[bin_key.0 as usize].contains(entity));
2630 }
2631 }
2632
2633 fn verify_indirect_parameters_offsets(
2634 batch_set: &RenderMultidrawableBatchSet<MockBinnedPhaseItem>,
2635 ) {
2636 for (render_bin_index, indirect_parameters_offset) in batch_set
2637 .gpu_buffers
2638 .bin_index_to_indirect_parameters_offset_buffer
2639 .values()
2640 .iter()
2641 .enumerate()
2642 {
2643 if *indirect_parameters_offset == u32::MAX {
2644 continue;
2645 }
2646 assert_eq!(
2647 batch_set.indirect_parameters_offset_to_bin_index
2648 [*indirect_parameters_offset as usize],
2649 RenderBinIndex(render_bin_index as u32)
2650 );
2651 }
2652 for (indirect_parameters_offset, render_bin_index) in batch_set
2653 .indirect_parameters_offset_to_bin_index
2654 .iter()
2655 .enumerate()
2656 {
2657 assert!(batch_set.bins[render_bin_index.0 as usize].is_some());
2658 assert_eq!(
2659 *batch_set
2660 .gpu_buffers
2661 .bin_index_to_indirect_parameters_offset_buffer
2662 .get(render_bin_index.0)
2663 .unwrap(),
2664 indirect_parameters_offset as u32
2665 );
2666 }
2667 }
2668 }
2669}