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