Skip to main content

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}