Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
bevyengine
GitHub Repository: bevyengine/bevy
Path: blob/main/crates/bevy_render/src/render_phase/mod.rs
9358 views
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
27
mod draw;
28
mod draw_state;
29
mod rangefinder;
30
31
use bevy_app::{App, Plugin};
32
use bevy_derive::{Deref, DerefMut};
33
use bevy_ecs::change_detection::Tick;
34
use bevy_ecs::entity::EntityHash;
35
use bevy_platform::collections::{hash_map::Entry, HashMap};
36
use bevy_utils::default;
37
pub use draw::*;
38
pub use draw_state::*;
39
use encase::{internal::WriteInto, ShaderSize};
40
use fixedbitset::{Block, FixedBitSet};
41
use indexmap::IndexMap;
42
use nonmax::NonMaxU32;
43
pub use rangefinder::*;
44
use wgpu::Features;
45
46
use crate::batching::gpu_preprocessing::{
47
GpuPreprocessingMode, GpuPreprocessingSupport, PhaseBatchedInstanceBuffers,
48
PhaseIndirectParametersBuffers,
49
};
50
use crate::renderer::RenderDevice;
51
use crate::sync_world::{MainEntity, MainEntityHashMap};
52
use crate::view::RetainedViewEntity;
53
use crate::RenderDebugFlags;
54
use bevy_material::descriptor::CachedRenderPipelineId;
55
56
use crate::{
57
batching::{
58
self,
59
gpu_preprocessing::{self, BatchedInstanceBuffers},
60
no_gpu_preprocessing::{self, BatchedInstanceBuffer},
61
GetFullBatchData,
62
},
63
render_resource::{GpuArrayBufferIndex, PipelineCache},
64
Render, RenderApp, RenderSystems,
65
};
66
use bevy_ecs::{
67
prelude::*,
68
system::{lifetimeless::SRes, SystemParamItem},
69
};
70
use bevy_log::warn;
71
pub use bevy_material::labels::DrawFunctionId;
72
pub use bevy_material_macros::DrawFunctionLabel;
73
pub use bevy_material_macros::ShaderLabel;
74
use bevy_render::renderer::RenderAdapterInfo;
75
use core::{fmt::Debug, hash::Hash, iter, marker::PhantomData, ops::Range, slice::SliceIndex};
76
use smallvec::SmallVec;
77
78
/// Stores the rendering instructions for a single phase that uses bins in all
79
/// views.
80
///
81
/// They're cleared out every frame, but storing them in a resource like this
82
/// allows us to reuse allocations.
83
#[derive(Resource, Deref, DerefMut)]
84
pub struct ViewBinnedRenderPhases<BPI>(pub HashMap<RetainedViewEntity, BinnedRenderPhase<BPI>>)
85
where
86
BPI: BinnedPhaseItem;
87
88
/// A collection of all rendering instructions, that will be executed by the GPU, for a
89
/// single render phase for a single view.
90
///
91
/// Each view (camera, or shadow-casting light, etc.) can have one or multiple render phases.
92
/// They are used to queue entities for rendering.
93
/// Multiple phases might be required due to different sorting/batching behaviors
94
/// (e.g. opaque: front to back, transparent: back to front) or because one phase depends on
95
/// the rendered texture of the previous phase (e.g. for screen-space reflections).
96
/// All [`PhaseItem`]s are then rendered using a single [`TrackedRenderPass`].
97
/// The render pass might be reused for multiple phases to reduce GPU overhead.
98
///
99
/// This flavor of render phase is used for phases in which the ordering is less
100
/// critical: for example, `Opaque3d`. It's generally faster than the
101
/// alternative [`SortedRenderPhase`].
102
pub struct BinnedRenderPhase<BPI>
103
where
104
BPI: BinnedPhaseItem,
105
{
106
/// The multidrawable bins.
107
///
108
/// Each batch set key maps to a *batch set*, which in this case is a set of
109
/// meshes that can be drawn together in one multidraw call. Each batch set
110
/// is subdivided into *bins*, each of which represents a particular mesh.
111
/// Each bin contains the entity IDs of instances of that mesh.
112
///
113
/// So, for example, if there are two cubes and a sphere present in the
114
/// scene, we would generally have one batch set containing two bins,
115
/// assuming that the cubes and sphere meshes are allocated together and use
116
/// the same pipeline. The first bin, corresponding to the cubes, will have
117
/// two entities in it. The second bin, corresponding to the sphere, will
118
/// have one entity in it.
119
pub multidrawable_meshes: IndexMap<BPI::BatchSetKey, IndexMap<BPI::BinKey, RenderBin>>,
120
121
/// The bins corresponding to batchable items that aren't multidrawable.
122
///
123
/// For multidrawable entities, use `multidrawable_meshes`; for
124
/// unbatchable entities, use `unbatchable_values`.
125
pub batchable_meshes: IndexMap<(BPI::BatchSetKey, BPI::BinKey), RenderBin>,
126
127
/// The unbatchable bins.
128
///
129
/// Each entity here is rendered in a separate drawcall.
130
pub unbatchable_meshes: IndexMap<(BPI::BatchSetKey, BPI::BinKey), UnbatchableBinnedEntities>,
131
132
/// Items in the bin that aren't meshes at all.
133
///
134
/// Bevy itself doesn't place anything in this list, but plugins or your app
135
/// can in order to execute custom drawing commands. Draw functions for each
136
/// entity are simply called in order at rendering time.
137
///
138
/// See the `custom_phase_item` example for an example of how to use this.
139
pub non_mesh_items: IndexMap<(BPI::BatchSetKey, BPI::BinKey), NonMeshEntities>,
140
141
/// Information on each batch set.
142
///
143
/// A *batch set* is a set of entities that will be batched together unless
144
/// we're on a platform that doesn't support storage buffers (e.g. WebGL 2)
145
/// and differing dynamic uniform indices force us to break batches. On
146
/// platforms that support storage buffers, a batch set always consists of
147
/// at most one batch.
148
///
149
/// Multidrawable entities come first, then batchable entities, then
150
/// unbatchable entities.
151
pub(crate) batch_sets: BinnedRenderPhaseBatchSets<BPI::BinKey>,
152
153
/// The batch and bin key for each entity.
154
///
155
/// We retain these so that, when the entity changes,
156
/// [`Self::sweep_old_entities`] can quickly find the bin it was located in
157
/// and remove it.
158
cached_entity_bin_keys: IndexMap<MainEntity, CachedBinnedEntity<BPI>, EntityHash>,
159
160
/// The set of indices in [`Self::cached_entity_bin_keys`] that are
161
/// confirmed to be up to date.
162
///
163
/// Note that each bit in this bit set refers to an *index* in the
164
/// [`IndexMap`] (i.e. a bucket in the hash table). They aren't entity IDs.
165
valid_cached_entity_bin_keys: FixedBitSet,
166
167
/// The set of entities that changed bins this frame.
168
///
169
/// An entity will only be present in this list if it was in one bin on the
170
/// previous frame and is in a new bin on this frame. Each list entry
171
/// specifies the bin the entity used to be in. We use this in order to
172
/// remove the entity from the old bin during
173
/// [`BinnedRenderPhase::sweep_old_entities`].
174
entities_that_changed_bins: Vec<EntityThatChangedBins<BPI>>,
175
/// The gpu preprocessing mode configured for the view this phase is associated
176
/// with.
177
gpu_preprocessing_mode: GpuPreprocessingMode,
178
}
179
180
/// All entities that share a mesh and a material and can be batched as part of
181
/// a [`BinnedRenderPhase`].
182
#[derive(Default)]
183
pub struct RenderBin {
184
/// A list of the entities in each bin, along with their cached
185
/// [`InputUniformIndex`].
186
entities: IndexMap<MainEntity, InputUniformIndex, EntityHash>,
187
}
188
189
/// Information that we track about an entity that was in one bin on the
190
/// previous frame and is in a different bin this frame.
191
struct EntityThatChangedBins<BPI>
192
where
193
BPI: BinnedPhaseItem,
194
{
195
/// The entity.
196
main_entity: MainEntity,
197
/// The key that identifies the bin that this entity used to be in.
198
old_cached_binned_entity: CachedBinnedEntity<BPI>,
199
}
200
201
/// Information that we keep about an entity currently within a bin.
202
pub struct CachedBinnedEntity<BPI>
203
where
204
BPI: BinnedPhaseItem,
205
{
206
/// Information that we use to identify a cached entity in a bin.
207
pub cached_bin_key: Option<CachedBinKey<BPI>>,
208
/// The last modified tick of the entity.
209
///
210
/// We use this to detect when the entity needs to be invalidated.
211
pub change_tick: Tick,
212
}
213
214
/// Information that we use to identify a cached entity in a bin.
215
pub struct CachedBinKey<BPI>
216
where
217
BPI: BinnedPhaseItem,
218
{
219
/// The key of the batch set containing the entity.
220
pub batch_set_key: BPI::BatchSetKey,
221
/// The key of the bin containing the entity.
222
pub bin_key: BPI::BinKey,
223
/// The type of render phase that we use to render the entity: multidraw,
224
/// plain batch, etc.
225
pub phase_type: BinnedRenderPhaseType,
226
}
227
228
impl<BPI> Clone for CachedBinnedEntity<BPI>
229
where
230
BPI: BinnedPhaseItem,
231
{
232
fn clone(&self) -> Self {
233
CachedBinnedEntity {
234
cached_bin_key: self.cached_bin_key.clone(),
235
change_tick: self.change_tick,
236
}
237
}
238
}
239
240
impl<BPI> Clone for CachedBinKey<BPI>
241
where
242
BPI: BinnedPhaseItem,
243
{
244
fn clone(&self) -> Self {
245
CachedBinKey {
246
batch_set_key: self.batch_set_key.clone(),
247
bin_key: self.bin_key.clone(),
248
phase_type: self.phase_type,
249
}
250
}
251
}
252
253
impl<BPI> PartialEq for CachedBinKey<BPI>
254
where
255
BPI: BinnedPhaseItem,
256
{
257
fn eq(&self, other: &Self) -> bool {
258
self.batch_set_key == other.batch_set_key
259
&& self.bin_key == other.bin_key
260
&& self.phase_type == other.phase_type
261
}
262
}
263
264
/// How we store and render the batch sets.
265
///
266
/// Each one of these corresponds to a [`GpuPreprocessingMode`].
267
pub enum BinnedRenderPhaseBatchSets<BK> {
268
/// Batches are grouped into batch sets based on dynamic uniforms.
269
///
270
/// This corresponds to [`GpuPreprocessingMode::None`].
271
DynamicUniforms(Vec<SmallVec<[BinnedRenderPhaseBatch; 1]>>),
272
273
/// Batches are never grouped into batch sets.
274
///
275
/// This corresponds to [`GpuPreprocessingMode::PreprocessingOnly`].
276
Direct(Vec<BinnedRenderPhaseBatch>),
277
278
/// Batches are grouped together into batch sets based on their ability to
279
/// be multi-drawn together.
280
///
281
/// This corresponds to [`GpuPreprocessingMode::Culling`].
282
MultidrawIndirect(Vec<BinnedRenderPhaseBatchSet<BK>>),
283
}
284
285
/// A group of entities that will be batched together into a single multi-draw
286
/// call.
287
pub struct BinnedRenderPhaseBatchSet<BK> {
288
/// The first batch in this batch set.
289
pub(crate) first_batch: BinnedRenderPhaseBatch,
290
/// The key of the bin that the first batch corresponds to.
291
pub(crate) bin_key: BK,
292
/// The number of batches.
293
pub(crate) batch_count: u32,
294
/// The index of the batch set in the GPU buffer.
295
pub(crate) index: u32,
296
}
297
298
impl<BK> BinnedRenderPhaseBatchSets<BK> {
299
fn clear(&mut self) {
300
match *self {
301
BinnedRenderPhaseBatchSets::DynamicUniforms(ref mut vec) => vec.clear(),
302
BinnedRenderPhaseBatchSets::Direct(ref mut vec) => vec.clear(),
303
BinnedRenderPhaseBatchSets::MultidrawIndirect(ref mut vec) => vec.clear(),
304
}
305
}
306
}
307
308
/// Information about a single batch of entities rendered using binned phase
309
/// items.
310
#[derive(Debug)]
311
pub struct BinnedRenderPhaseBatch {
312
/// An entity that's *representative* of this batch.
313
///
314
/// Bevy uses this to fetch the mesh. It can be any entity in the batch.
315
pub representative_entity: (Entity, MainEntity),
316
/// The range of instance indices in this batch.
317
pub instance_range: Range<u32>,
318
319
/// The dynamic offset of the batch.
320
///
321
/// Note that dynamic offsets are only used on platforms that don't support
322
/// storage buffers.
323
pub extra_index: PhaseItemExtraIndex,
324
}
325
326
/// Information about the unbatchable entities in a bin.
327
pub struct UnbatchableBinnedEntities {
328
/// The entities.
329
pub entities: MainEntityHashMap<Entity>,
330
331
/// The GPU array buffer indices of each unbatchable binned entity.
332
pub(crate) buffer_indices: UnbatchableBinnedEntityIndexSet,
333
}
334
335
/// Information about [`BinnedRenderPhaseType::NonMesh`] entities.
336
pub struct NonMeshEntities {
337
/// The entities.
338
pub entities: MainEntityHashMap<Entity>,
339
}
340
341
/// Stores instance indices and dynamic offsets for unbatchable entities in a
342
/// binned render phase.
343
///
344
/// This is conceptually `Vec<UnbatchableBinnedEntityDynamicOffset>`, but it
345
/// avoids the overhead of storing dynamic offsets on platforms that support
346
/// them. In other words, this allows a fast path that avoids allocation on
347
/// platforms that aren't WebGL 2.
348
#[derive(Default)]
349
350
pub(crate) enum UnbatchableBinnedEntityIndexSet {
351
/// There are no unbatchable entities in this bin (yet).
352
#[default]
353
NoEntities,
354
355
/// The instances for all unbatchable entities in this bin are contiguous,
356
/// and there are no dynamic uniforms.
357
///
358
/// This is the typical case on platforms other than WebGL 2. We special
359
/// case this to avoid allocation on those platforms.
360
Sparse {
361
/// The range of indices.
362
instance_range: Range<u32>,
363
/// The index of the first indirect instance parameters.
364
///
365
/// The other indices immediately follow these.
366
first_indirect_parameters_index: Option<NonMaxU32>,
367
},
368
369
/// Dynamic uniforms are present for unbatchable entities in this bin.
370
///
371
/// We fall back to this on WebGL 2.
372
Dense(Vec<UnbatchableBinnedEntityIndices>),
373
}
374
375
/// The instance index and dynamic offset (if present) for an unbatchable entity.
376
///
377
/// This is only useful on platforms that don't support storage buffers.
378
#[derive(Clone)]
379
pub(crate) struct UnbatchableBinnedEntityIndices {
380
/// The instance index.
381
pub(crate) instance_index: u32,
382
/// The [`PhaseItemExtraIndex`], if present.
383
pub(crate) extra_index: PhaseItemExtraIndex,
384
}
385
386
/// Identifies the list within [`BinnedRenderPhase`] that a phase item is to be
387
/// placed in.
388
#[derive(Clone, Copy, PartialEq, Debug)]
389
pub enum BinnedRenderPhaseType {
390
/// The item is a mesh that's eligible for multi-draw indirect rendering and
391
/// can be batched with other meshes of the same type.
392
MultidrawableMesh,
393
394
/// The item is a mesh that can be batched with other meshes of the same type and
395
/// drawn in a single draw call.
396
BatchableMesh,
397
398
/// The item is a mesh that's eligible for indirect rendering, but can't be
399
/// batched with other meshes of the same type.
400
UnbatchableMesh,
401
402
/// The item isn't a mesh at all.
403
///
404
/// Bevy will simply invoke the drawing commands for such items one after
405
/// another, with no further processing.
406
///
407
/// The engine itself doesn't enqueue any items of this type, but it's
408
/// available for use in your application and/or plugins.
409
NonMesh,
410
}
411
412
impl<T> From<GpuArrayBufferIndex<T>> for UnbatchableBinnedEntityIndices
413
where
414
T: Clone + ShaderSize + WriteInto,
415
{
416
fn from(value: GpuArrayBufferIndex<T>) -> Self {
417
UnbatchableBinnedEntityIndices {
418
instance_index: value.index,
419
extra_index: PhaseItemExtraIndex::maybe_dynamic_offset(value.dynamic_offset),
420
}
421
}
422
}
423
424
impl<BPI> Default for ViewBinnedRenderPhases<BPI>
425
where
426
BPI: BinnedPhaseItem,
427
{
428
fn default() -> Self {
429
Self(default())
430
}
431
}
432
433
impl<BPI> ViewBinnedRenderPhases<BPI>
434
where
435
BPI: BinnedPhaseItem,
436
{
437
pub fn prepare_for_new_frame(
438
&mut self,
439
retained_view_entity: RetainedViewEntity,
440
gpu_preprocessing: GpuPreprocessingMode,
441
) {
442
match self.entry(retained_view_entity) {
443
Entry::Occupied(mut entry) => entry.get_mut().prepare_for_new_frame(),
444
Entry::Vacant(entry) => {
445
entry.insert(BinnedRenderPhase::<BPI>::new(gpu_preprocessing));
446
}
447
}
448
}
449
}
450
451
/// The index of the uniform describing this object in the GPU buffer, when GPU
452
/// preprocessing is enabled.
453
///
454
/// For example, for 3D meshes, this is the index of the `MeshInputUniform` in
455
/// the buffer.
456
///
457
/// This field is ignored if GPU preprocessing isn't in use, such as (currently)
458
/// in the case of 2D meshes. In that case, it can be safely set to
459
/// [`core::default::Default::default`].
460
#[derive(Clone, Copy, PartialEq, Default, Deref, DerefMut)]
461
#[repr(transparent)]
462
pub struct InputUniformIndex(pub u32);
463
464
impl<BPI> BinnedRenderPhase<BPI>
465
where
466
BPI: BinnedPhaseItem,
467
{
468
/// Bins a new entity.
469
///
470
/// The `phase_type` parameter specifies whether the entity is a
471
/// preprocessable mesh and whether it can be binned with meshes of the same
472
/// type.
473
pub fn add(
474
&mut self,
475
batch_set_key: BPI::BatchSetKey,
476
bin_key: BPI::BinKey,
477
(entity, main_entity): (Entity, MainEntity),
478
input_uniform_index: InputUniformIndex,
479
mut phase_type: BinnedRenderPhaseType,
480
change_tick: Tick,
481
) {
482
// If the user has overridden indirect drawing for this view, we need to
483
// force the phase type to be batchable instead.
484
if self.gpu_preprocessing_mode == GpuPreprocessingMode::PreprocessingOnly
485
&& phase_type == BinnedRenderPhaseType::MultidrawableMesh
486
{
487
phase_type = BinnedRenderPhaseType::BatchableMesh;
488
}
489
490
match phase_type {
491
BinnedRenderPhaseType::MultidrawableMesh => {
492
match self.multidrawable_meshes.entry(batch_set_key.clone()) {
493
indexmap::map::Entry::Occupied(mut entry) => {
494
entry
495
.get_mut()
496
.entry(bin_key.clone())
497
.or_default()
498
.insert(main_entity, input_uniform_index);
499
}
500
indexmap::map::Entry::Vacant(entry) => {
501
let mut new_batch_set = IndexMap::default();
502
new_batch_set.insert(
503
bin_key.clone(),
504
RenderBin::from_entity(main_entity, input_uniform_index),
505
);
506
entry.insert(new_batch_set);
507
}
508
}
509
}
510
511
BinnedRenderPhaseType::BatchableMesh => {
512
match self
513
.batchable_meshes
514
.entry((batch_set_key.clone(), bin_key.clone()).clone())
515
{
516
indexmap::map::Entry::Occupied(mut entry) => {
517
entry.get_mut().insert(main_entity, input_uniform_index);
518
}
519
indexmap::map::Entry::Vacant(entry) => {
520
entry.insert(RenderBin::from_entity(main_entity, input_uniform_index));
521
}
522
}
523
}
524
525
BinnedRenderPhaseType::UnbatchableMesh => {
526
match self
527
.unbatchable_meshes
528
.entry((batch_set_key.clone(), bin_key.clone()))
529
{
530
indexmap::map::Entry::Occupied(mut entry) => {
531
entry.get_mut().entities.insert(main_entity, entity);
532
}
533
indexmap::map::Entry::Vacant(entry) => {
534
let mut entities = MainEntityHashMap::default();
535
entities.insert(main_entity, entity);
536
entry.insert(UnbatchableBinnedEntities {
537
entities,
538
buffer_indices: default(),
539
});
540
}
541
}
542
}
543
544
BinnedRenderPhaseType::NonMesh => {
545
// We don't process these items further.
546
match self
547
.non_mesh_items
548
.entry((batch_set_key.clone(), bin_key.clone()).clone())
549
{
550
indexmap::map::Entry::Occupied(mut entry) => {
551
entry.get_mut().entities.insert(main_entity, entity);
552
}
553
indexmap::map::Entry::Vacant(entry) => {
554
let mut entities = MainEntityHashMap::default();
555
entities.insert(main_entity, entity);
556
entry.insert(NonMeshEntities { entities });
557
}
558
}
559
}
560
}
561
562
// Update the cache.
563
self.update_cache(
564
main_entity,
565
Some(CachedBinKey {
566
batch_set_key,
567
bin_key,
568
phase_type,
569
}),
570
change_tick,
571
);
572
}
573
574
/// Inserts an entity into the cache with the given change tick.
575
pub fn update_cache(
576
&mut self,
577
main_entity: MainEntity,
578
cached_bin_key: Option<CachedBinKey<BPI>>,
579
change_tick: Tick,
580
) {
581
let new_cached_binned_entity = CachedBinnedEntity {
582
cached_bin_key,
583
change_tick,
584
};
585
586
let (index, old_cached_binned_entity) = self
587
.cached_entity_bin_keys
588
.insert_full(main_entity, new_cached_binned_entity.clone());
589
590
// If the entity changed bins, record its old bin so that we can remove
591
// the entity from it.
592
if let Some(old_cached_binned_entity) = old_cached_binned_entity
593
&& old_cached_binned_entity.cached_bin_key != new_cached_binned_entity.cached_bin_key
594
{
595
self.entities_that_changed_bins.push(EntityThatChangedBins {
596
main_entity,
597
old_cached_binned_entity,
598
});
599
}
600
601
// Mark the entity as valid.
602
self.valid_cached_entity_bin_keys.grow_and_insert(index);
603
}
604
605
/// Encodes the GPU commands needed to render all entities in this phase.
606
pub fn render<'w>(
607
&self,
608
render_pass: &mut TrackedRenderPass<'w>,
609
world: &'w World,
610
view: Entity,
611
) -> Result<(), DrawError> {
612
{
613
let draw_functions = world.resource::<DrawFunctions<BPI>>();
614
let mut draw_functions = draw_functions.write();
615
draw_functions.prepare(world);
616
// Make sure to drop the reader-writer lock here to avoid recursive
617
// locks.
618
}
619
620
self.render_batchable_meshes(render_pass, world, view)?;
621
self.render_unbatchable_meshes(render_pass, world, view)?;
622
self.render_non_meshes(render_pass, world, view)?;
623
624
Ok(())
625
}
626
627
/// Renders all batchable meshes queued in this phase.
628
fn render_batchable_meshes<'w>(
629
&self,
630
render_pass: &mut TrackedRenderPass<'w>,
631
world: &'w World,
632
view: Entity,
633
) -> Result<(), DrawError> {
634
let draw_functions = world.resource::<DrawFunctions<BPI>>();
635
let mut draw_functions = draw_functions.write();
636
637
let render_device = world.resource::<RenderDevice>();
638
let render_adapter_info = world.resource::<RenderAdapterInfo>();
639
let multi_draw_indirect_count_supported = render_device
640
.features()
641
.contains(Features::MULTI_DRAW_INDIRECT_COUNT)
642
// TODO: https://github.com/gfx-rs/wgpu/issues/7974
643
&& !matches!(render_adapter_info.backend, wgpu::Backend::Dx12);
644
645
match self.batch_sets {
646
BinnedRenderPhaseBatchSets::DynamicUniforms(ref batch_sets) => {
647
debug_assert_eq!(self.batchable_meshes.len(), batch_sets.len());
648
649
for ((batch_set_key, bin_key), batch_set) in
650
self.batchable_meshes.keys().zip(batch_sets.iter())
651
{
652
for batch in batch_set {
653
let binned_phase_item = BPI::new(
654
batch_set_key.clone(),
655
bin_key.clone(),
656
batch.representative_entity,
657
batch.instance_range.clone(),
658
batch.extra_index.clone(),
659
);
660
661
// Fetch the draw function.
662
let Some(draw_function) =
663
draw_functions.get_mut(binned_phase_item.draw_function())
664
else {
665
continue;
666
};
667
668
draw_function.draw(world, render_pass, view, &binned_phase_item)?;
669
}
670
}
671
}
672
673
BinnedRenderPhaseBatchSets::Direct(ref batch_set) => {
674
for (batch, (batch_set_key, bin_key)) in
675
batch_set.iter().zip(self.batchable_meshes.keys())
676
{
677
let binned_phase_item = BPI::new(
678
batch_set_key.clone(),
679
bin_key.clone(),
680
batch.representative_entity,
681
batch.instance_range.clone(),
682
batch.extra_index.clone(),
683
);
684
685
// Fetch the draw function.
686
let Some(draw_function) =
687
draw_functions.get_mut(binned_phase_item.draw_function())
688
else {
689
continue;
690
};
691
692
draw_function.draw(world, render_pass, view, &binned_phase_item)?;
693
}
694
}
695
696
BinnedRenderPhaseBatchSets::MultidrawIndirect(ref batch_sets) => {
697
for (batch_set_key, batch_set) in self
698
.multidrawable_meshes
699
.keys()
700
.chain(
701
self.batchable_meshes
702
.keys()
703
.map(|(batch_set_key, _)| batch_set_key),
704
)
705
.zip(batch_sets.iter())
706
{
707
let batch = &batch_set.first_batch;
708
709
let batch_set_index = if multi_draw_indirect_count_supported {
710
NonMaxU32::new(batch_set.index)
711
} else {
712
None
713
};
714
715
let binned_phase_item = BPI::new(
716
batch_set_key.clone(),
717
batch_set.bin_key.clone(),
718
batch.representative_entity,
719
batch.instance_range.clone(),
720
match batch.extra_index {
721
PhaseItemExtraIndex::None => PhaseItemExtraIndex::None,
722
PhaseItemExtraIndex::DynamicOffset(ref dynamic_offset) => {
723
PhaseItemExtraIndex::DynamicOffset(*dynamic_offset)
724
}
725
PhaseItemExtraIndex::IndirectParametersIndex { ref range, .. } => {
726
PhaseItemExtraIndex::IndirectParametersIndex {
727
range: range.start..(range.start + batch_set.batch_count),
728
batch_set_index,
729
}
730
}
731
},
732
);
733
734
// Fetch the draw function.
735
let Some(draw_function) =
736
draw_functions.get_mut(binned_phase_item.draw_function())
737
else {
738
continue;
739
};
740
741
draw_function.draw(world, render_pass, view, &binned_phase_item)?;
742
}
743
}
744
}
745
746
Ok(())
747
}
748
749
/// Renders all unbatchable meshes queued in this phase.
750
fn render_unbatchable_meshes<'w>(
751
&self,
752
render_pass: &mut TrackedRenderPass<'w>,
753
world: &'w World,
754
view: Entity,
755
) -> Result<(), DrawError> {
756
let draw_functions = world.resource::<DrawFunctions<BPI>>();
757
let mut draw_functions = draw_functions.write();
758
759
for (batch_set_key, bin_key) in self.unbatchable_meshes.keys() {
760
let unbatchable_entities =
761
&self.unbatchable_meshes[&(batch_set_key.clone(), bin_key.clone())];
762
for (entity_index, entity) in unbatchable_entities.entities.iter().enumerate() {
763
let unbatchable_dynamic_offset = match &unbatchable_entities.buffer_indices {
764
UnbatchableBinnedEntityIndexSet::NoEntities => {
765
// Shouldn't happen…
766
continue;
767
}
768
UnbatchableBinnedEntityIndexSet::Sparse {
769
instance_range,
770
first_indirect_parameters_index,
771
} => UnbatchableBinnedEntityIndices {
772
instance_index: instance_range.start + entity_index as u32,
773
extra_index: match first_indirect_parameters_index {
774
None => PhaseItemExtraIndex::None,
775
Some(first_indirect_parameters_index) => {
776
let first_indirect_parameters_index_for_entity =
777
u32::from(*first_indirect_parameters_index)
778
+ entity_index as u32;
779
PhaseItemExtraIndex::IndirectParametersIndex {
780
range: first_indirect_parameters_index_for_entity
781
..(first_indirect_parameters_index_for_entity + 1),
782
batch_set_index: None,
783
}
784
}
785
},
786
},
787
UnbatchableBinnedEntityIndexSet::Dense(dynamic_offsets) => {
788
dynamic_offsets[entity_index].clone()
789
}
790
};
791
792
let binned_phase_item = BPI::new(
793
batch_set_key.clone(),
794
bin_key.clone(),
795
(*entity.1, *entity.0),
796
unbatchable_dynamic_offset.instance_index
797
..(unbatchable_dynamic_offset.instance_index + 1),
798
unbatchable_dynamic_offset.extra_index,
799
);
800
801
// Fetch the draw function.
802
let Some(draw_function) = draw_functions.get_mut(binned_phase_item.draw_function())
803
else {
804
continue;
805
};
806
807
draw_function.draw(world, render_pass, view, &binned_phase_item)?;
808
}
809
}
810
Ok(())
811
}
812
813
/// Renders all objects of type [`BinnedRenderPhaseType::NonMesh`].
814
///
815
/// These will have been added by plugins or the application.
816
fn render_non_meshes<'w>(
817
&self,
818
render_pass: &mut TrackedRenderPass<'w>,
819
world: &'w World,
820
view: Entity,
821
) -> Result<(), DrawError> {
822
let draw_functions = world.resource::<DrawFunctions<BPI>>();
823
let mut draw_functions = draw_functions.write();
824
825
for ((batch_set_key, bin_key), non_mesh_entities) in &self.non_mesh_items {
826
for (main_entity, entity) in non_mesh_entities.entities.iter() {
827
// Come up with a fake batch range and extra index. The draw
828
// function is expected to manage any sort of batching logic itself.
829
let binned_phase_item = BPI::new(
830
batch_set_key.clone(),
831
bin_key.clone(),
832
(*entity, *main_entity),
833
0..1,
834
PhaseItemExtraIndex::None,
835
);
836
837
let Some(draw_function) = draw_functions.get_mut(binned_phase_item.draw_function())
838
else {
839
continue;
840
};
841
842
draw_function.draw(world, render_pass, view, &binned_phase_item)?;
843
}
844
}
845
846
Ok(())
847
}
848
849
pub fn is_empty(&self) -> bool {
850
self.multidrawable_meshes.is_empty()
851
&& self.batchable_meshes.is_empty()
852
&& self.unbatchable_meshes.is_empty()
853
&& self.non_mesh_items.is_empty()
854
}
855
856
pub fn prepare_for_new_frame(&mut self) {
857
self.batch_sets.clear();
858
859
self.valid_cached_entity_bin_keys.clear();
860
self.valid_cached_entity_bin_keys
861
.grow(self.cached_entity_bin_keys.len());
862
self.valid_cached_entity_bin_keys
863
.set_range(self.cached_entity_bin_keys.len().., true);
864
865
self.entities_that_changed_bins.clear();
866
867
for unbatchable_bin in self.unbatchable_meshes.values_mut() {
868
unbatchable_bin.buffer_indices.clear();
869
}
870
}
871
872
/// Checks to see whether the entity is in a bin and returns true if it's
873
/// both in a bin and up to date.
874
///
875
/// If this function returns true, we also add the entry to the
876
/// `valid_cached_entity_bin_keys` list.
877
pub fn validate_cached_entity(
878
&mut self,
879
visible_entity: MainEntity,
880
current_change_tick: Tick,
881
) -> bool {
882
if let indexmap::map::Entry::Occupied(entry) =
883
self.cached_entity_bin_keys.entry(visible_entity)
884
&& entry.get().change_tick == current_change_tick
885
{
886
self.valid_cached_entity_bin_keys.insert(entry.index());
887
return true;
888
}
889
890
false
891
}
892
893
/// Removes all entities not marked as clean from the bins.
894
///
895
/// During `queue_material_meshes`, we process all visible entities and mark
896
/// each as clean as we come to it. Then, in [`sweep_old_entities`], we call
897
/// this method, which removes entities that aren't marked as clean from the
898
/// bins.
899
pub fn sweep_old_entities(&mut self) {
900
// Search for entities not marked as valid. We have to do this in
901
// reverse order because `swap_remove_index` will potentially invalidate
902
// all indices after the one we remove.
903
for index in ReverseFixedBitSetZeroesIterator::new(&self.valid_cached_entity_bin_keys) {
904
let Some((entity, cached_binned_entity)) =
905
self.cached_entity_bin_keys.swap_remove_index(index)
906
else {
907
continue;
908
};
909
910
if let Some(ref cached_bin_key) = cached_binned_entity.cached_bin_key {
911
remove_entity_from_bin(
912
entity,
913
cached_bin_key,
914
&mut self.multidrawable_meshes,
915
&mut self.batchable_meshes,
916
&mut self.unbatchable_meshes,
917
&mut self.non_mesh_items,
918
);
919
}
920
}
921
922
// If an entity changed bins, we need to remove it from its old bin.
923
for entity_that_changed_bins in self.entities_that_changed_bins.drain(..) {
924
let Some(ref old_cached_bin_key) = entity_that_changed_bins
925
.old_cached_binned_entity
926
.cached_bin_key
927
else {
928
continue;
929
};
930
remove_entity_from_bin(
931
entity_that_changed_bins.main_entity,
932
old_cached_bin_key,
933
&mut self.multidrawable_meshes,
934
&mut self.batchable_meshes,
935
&mut self.unbatchable_meshes,
936
&mut self.non_mesh_items,
937
);
938
}
939
}
940
}
941
942
/// Removes an entity from a bin.
943
///
944
/// If this makes the bin empty, this function removes the bin as well.
945
///
946
/// This is a standalone function instead of a method on [`BinnedRenderPhase`]
947
/// for borrow check reasons.
948
fn remove_entity_from_bin<BPI>(
949
entity: MainEntity,
950
entity_bin_key: &CachedBinKey<BPI>,
951
multidrawable_meshes: &mut IndexMap<BPI::BatchSetKey, IndexMap<BPI::BinKey, RenderBin>>,
952
batchable_meshes: &mut IndexMap<(BPI::BatchSetKey, BPI::BinKey), RenderBin>,
953
unbatchable_meshes: &mut IndexMap<(BPI::BatchSetKey, BPI::BinKey), UnbatchableBinnedEntities>,
954
non_mesh_items: &mut IndexMap<(BPI::BatchSetKey, BPI::BinKey), NonMeshEntities>,
955
) where
956
BPI: BinnedPhaseItem,
957
{
958
match entity_bin_key.phase_type {
959
BinnedRenderPhaseType::MultidrawableMesh => {
960
if let indexmap::map::Entry::Occupied(mut batch_set_entry) =
961
multidrawable_meshes.entry(entity_bin_key.batch_set_key.clone())
962
{
963
if let indexmap::map::Entry::Occupied(mut bin_entry) = batch_set_entry
964
.get_mut()
965
.entry(entity_bin_key.bin_key.clone())
966
{
967
bin_entry.get_mut().remove(entity);
968
969
// If the bin is now empty, remove the bin.
970
if bin_entry.get_mut().is_empty() {
971
bin_entry.swap_remove();
972
}
973
}
974
975
// If the batch set is now empty, remove it. This will perturb
976
// the order, but that's OK because we're going to sort the bin
977
// afterwards.
978
if batch_set_entry.get_mut().is_empty() {
979
batch_set_entry.swap_remove();
980
}
981
}
982
}
983
984
BinnedRenderPhaseType::BatchableMesh => {
985
if let indexmap::map::Entry::Occupied(mut bin_entry) = batchable_meshes.entry((
986
entity_bin_key.batch_set_key.clone(),
987
entity_bin_key.bin_key.clone(),
988
)) {
989
bin_entry.get_mut().remove(entity);
990
991
// If the bin is now empty, remove the bin.
992
if bin_entry.get_mut().is_empty() {
993
bin_entry.swap_remove();
994
}
995
}
996
}
997
998
BinnedRenderPhaseType::UnbatchableMesh => {
999
if let indexmap::map::Entry::Occupied(mut bin_entry) = unbatchable_meshes.entry((
1000
entity_bin_key.batch_set_key.clone(),
1001
entity_bin_key.bin_key.clone(),
1002
)) {
1003
bin_entry.get_mut().entities.remove(&entity);
1004
1005
// If the bin is now empty, remove the bin.
1006
if bin_entry.get_mut().entities.is_empty() {
1007
bin_entry.swap_remove();
1008
}
1009
}
1010
}
1011
1012
BinnedRenderPhaseType::NonMesh => {
1013
if let indexmap::map::Entry::Occupied(mut bin_entry) = non_mesh_items.entry((
1014
entity_bin_key.batch_set_key.clone(),
1015
entity_bin_key.bin_key.clone(),
1016
)) {
1017
bin_entry.get_mut().entities.remove(&entity);
1018
1019
// If the bin is now empty, remove the bin.
1020
if bin_entry.get_mut().entities.is_empty() {
1021
bin_entry.swap_remove();
1022
}
1023
}
1024
}
1025
}
1026
}
1027
1028
impl<BPI> BinnedRenderPhase<BPI>
1029
where
1030
BPI: BinnedPhaseItem,
1031
{
1032
fn new(gpu_preprocessing: GpuPreprocessingMode) -> Self {
1033
Self {
1034
multidrawable_meshes: IndexMap::default(),
1035
batchable_meshes: IndexMap::default(),
1036
unbatchable_meshes: IndexMap::default(),
1037
non_mesh_items: IndexMap::default(),
1038
batch_sets: match gpu_preprocessing {
1039
GpuPreprocessingMode::Culling => {
1040
BinnedRenderPhaseBatchSets::MultidrawIndirect(vec![])
1041
}
1042
GpuPreprocessingMode::PreprocessingOnly => {
1043
BinnedRenderPhaseBatchSets::Direct(vec![])
1044
}
1045
GpuPreprocessingMode::None => BinnedRenderPhaseBatchSets::DynamicUniforms(vec![]),
1046
},
1047
cached_entity_bin_keys: IndexMap::default(),
1048
valid_cached_entity_bin_keys: FixedBitSet::new(),
1049
entities_that_changed_bins: vec![],
1050
gpu_preprocessing_mode: gpu_preprocessing,
1051
}
1052
}
1053
}
1054
1055
impl UnbatchableBinnedEntityIndexSet {
1056
/// Returns the [`UnbatchableBinnedEntityIndices`] for the given entity.
1057
fn indices_for_entity_index(
1058
&self,
1059
entity_index: u32,
1060
) -> Option<UnbatchableBinnedEntityIndices> {
1061
match self {
1062
UnbatchableBinnedEntityIndexSet::NoEntities => None,
1063
UnbatchableBinnedEntityIndexSet::Sparse { instance_range, .. }
1064
if entity_index >= instance_range.len() as u32 =>
1065
{
1066
None
1067
}
1068
UnbatchableBinnedEntityIndexSet::Sparse {
1069
instance_range,
1070
first_indirect_parameters_index: None,
1071
} => Some(UnbatchableBinnedEntityIndices {
1072
instance_index: instance_range.start + entity_index,
1073
extra_index: PhaseItemExtraIndex::None,
1074
}),
1075
UnbatchableBinnedEntityIndexSet::Sparse {
1076
instance_range,
1077
first_indirect_parameters_index: Some(first_indirect_parameters_index),
1078
} => {
1079
let first_indirect_parameters_index_for_this_batch =
1080
u32::from(*first_indirect_parameters_index) + entity_index;
1081
Some(UnbatchableBinnedEntityIndices {
1082
instance_index: instance_range.start + entity_index,
1083
extra_index: PhaseItemExtraIndex::IndirectParametersIndex {
1084
range: first_indirect_parameters_index_for_this_batch
1085
..(first_indirect_parameters_index_for_this_batch + 1),
1086
batch_set_index: None,
1087
},
1088
})
1089
}
1090
UnbatchableBinnedEntityIndexSet::Dense(indices) => {
1091
indices.get(entity_index as usize).cloned()
1092
}
1093
}
1094
}
1095
}
1096
1097
/// A convenient abstraction for adding all the systems necessary for a binned
1098
/// render phase to the render app.
1099
///
1100
/// This is the version used when the pipeline supports GPU preprocessing: e.g.
1101
/// 3D PBR meshes.
1102
pub struct BinnedRenderPhasePlugin<BPI, GFBD>
1103
where
1104
BPI: BinnedPhaseItem,
1105
GFBD: GetFullBatchData,
1106
{
1107
/// Debugging flags that can optionally be set when constructing the renderer.
1108
pub debug_flags: RenderDebugFlags,
1109
phantom: PhantomData<(BPI, GFBD)>,
1110
}
1111
1112
impl<BPI, GFBD> BinnedRenderPhasePlugin<BPI, GFBD>
1113
where
1114
BPI: BinnedPhaseItem,
1115
GFBD: GetFullBatchData,
1116
{
1117
pub fn new(debug_flags: RenderDebugFlags) -> Self {
1118
Self {
1119
debug_flags,
1120
phantom: PhantomData,
1121
}
1122
}
1123
}
1124
1125
impl<BPI, GFBD> Plugin for BinnedRenderPhasePlugin<BPI, GFBD>
1126
where
1127
BPI: BinnedPhaseItem,
1128
GFBD: GetFullBatchData + Sync + Send + 'static,
1129
{
1130
fn build(&self, app: &mut App) {
1131
let Some(render_app) = app.get_sub_app_mut(RenderApp) else {
1132
return;
1133
};
1134
1135
render_app
1136
.init_resource::<ViewBinnedRenderPhases<BPI>>()
1137
.init_resource::<PhaseBatchedInstanceBuffers<BPI, GFBD::BufferData>>()
1138
.insert_resource(PhaseIndirectParametersBuffers::<BPI>::new(
1139
self.debug_flags
1140
.contains(RenderDebugFlags::ALLOW_COPIES_FROM_INDIRECT_PARAMETERS),
1141
))
1142
.add_systems(
1143
Render,
1144
(
1145
batching::sort_binned_render_phase::<BPI>.in_set(RenderSystems::PhaseSort),
1146
(
1147
no_gpu_preprocessing::batch_and_prepare_binned_render_phase::<BPI, GFBD>
1148
.run_if(resource_exists::<BatchedInstanceBuffer<GFBD::BufferData>>),
1149
gpu_preprocessing::batch_and_prepare_binned_render_phase::<BPI, GFBD>
1150
.run_if(
1151
resource_exists::<
1152
BatchedInstanceBuffers<GFBD::BufferData, GFBD::BufferInputData>,
1153
>,
1154
),
1155
)
1156
.in_set(RenderSystems::PrepareResources),
1157
sweep_old_entities::<BPI>.in_set(RenderSystems::QueueSweep),
1158
gpu_preprocessing::collect_buffers_for_phase::<BPI, GFBD>
1159
.run_if(
1160
resource_exists::<
1161
BatchedInstanceBuffers<GFBD::BufferData, GFBD::BufferInputData>,
1162
>,
1163
)
1164
.in_set(RenderSystems::PrepareResourcesCollectPhaseBuffers),
1165
),
1166
);
1167
}
1168
}
1169
1170
/// Stores the rendering instructions for a single phase that sorts items in all
1171
/// views.
1172
///
1173
/// They're cleared out every frame, but storing them in a resource like this
1174
/// allows us to reuse allocations.
1175
#[derive(Resource, Deref, DerefMut)]
1176
pub struct ViewSortedRenderPhases<SPI>(pub HashMap<RetainedViewEntity, SortedRenderPhase<SPI>>)
1177
where
1178
SPI: SortedPhaseItem;
1179
1180
impl<SPI> Default for ViewSortedRenderPhases<SPI>
1181
where
1182
SPI: SortedPhaseItem,
1183
{
1184
fn default() -> Self {
1185
Self(default())
1186
}
1187
}
1188
1189
impl<SPI> ViewSortedRenderPhases<SPI>
1190
where
1191
SPI: SortedPhaseItem,
1192
{
1193
pub fn insert_or_clear(&mut self, retained_view_entity: RetainedViewEntity) {
1194
match self.entry(retained_view_entity) {
1195
Entry::Occupied(mut entry) => entry.get_mut().clear(),
1196
Entry::Vacant(entry) => {
1197
entry.insert(default());
1198
}
1199
}
1200
}
1201
}
1202
1203
/// A convenient abstraction for adding all the systems necessary for a sorted
1204
/// render phase to the render app.
1205
///
1206
/// This is the version used when the pipeline supports GPU preprocessing: e.g.
1207
/// 3D PBR meshes.
1208
pub struct SortedRenderPhasePlugin<SPI, GFBD>
1209
where
1210
SPI: SortedPhaseItem,
1211
GFBD: GetFullBatchData,
1212
{
1213
/// Debugging flags that can optionally be set when constructing the renderer.
1214
pub debug_flags: RenderDebugFlags,
1215
phantom: PhantomData<(SPI, GFBD)>,
1216
}
1217
1218
impl<SPI, GFBD> SortedRenderPhasePlugin<SPI, GFBD>
1219
where
1220
SPI: SortedPhaseItem,
1221
GFBD: GetFullBatchData,
1222
{
1223
pub fn new(debug_flags: RenderDebugFlags) -> Self {
1224
Self {
1225
debug_flags,
1226
phantom: PhantomData,
1227
}
1228
}
1229
}
1230
1231
impl<SPI, GFBD> Plugin for SortedRenderPhasePlugin<SPI, GFBD>
1232
where
1233
SPI: SortedPhaseItem + CachedRenderPipelinePhaseItem,
1234
GFBD: GetFullBatchData + Sync + Send + 'static,
1235
{
1236
fn build(&self, app: &mut App) {
1237
let Some(render_app) = app.get_sub_app_mut(RenderApp) else {
1238
return;
1239
};
1240
1241
render_app
1242
.init_resource::<ViewSortedRenderPhases<SPI>>()
1243
.init_resource::<PhaseBatchedInstanceBuffers<SPI, GFBD::BufferData>>()
1244
.insert_resource(PhaseIndirectParametersBuffers::<SPI>::new(
1245
self.debug_flags
1246
.contains(RenderDebugFlags::ALLOW_COPIES_FROM_INDIRECT_PARAMETERS),
1247
))
1248
.add_systems(
1249
Render,
1250
(
1251
(
1252
no_gpu_preprocessing::batch_and_prepare_sorted_render_phase::<SPI, GFBD>
1253
.run_if(resource_exists::<BatchedInstanceBuffer<GFBD::BufferData>>),
1254
gpu_preprocessing::batch_and_prepare_sorted_render_phase::<SPI, GFBD>
1255
.run_if(
1256
resource_exists::<
1257
BatchedInstanceBuffers<GFBD::BufferData, GFBD::BufferInputData>,
1258
>,
1259
),
1260
)
1261
.in_set(RenderSystems::PrepareResources),
1262
gpu_preprocessing::collect_buffers_for_phase::<SPI, GFBD>
1263
.run_if(
1264
resource_exists::<
1265
BatchedInstanceBuffers<GFBD::BufferData, GFBD::BufferInputData>,
1266
>,
1267
)
1268
.in_set(RenderSystems::PrepareResourcesCollectPhaseBuffers),
1269
),
1270
);
1271
}
1272
}
1273
1274
impl UnbatchableBinnedEntityIndexSet {
1275
/// Adds a new entity to the list of unbatchable binned entities.
1276
pub fn add(&mut self, indices: UnbatchableBinnedEntityIndices) {
1277
match self {
1278
UnbatchableBinnedEntityIndexSet::NoEntities => {
1279
match indices.extra_index {
1280
PhaseItemExtraIndex::DynamicOffset(_) => {
1281
// This is the first entity we've seen, and we don't have
1282
// compute shaders. Initialize an array.
1283
*self = UnbatchableBinnedEntityIndexSet::Dense(vec![indices]);
1284
}
1285
PhaseItemExtraIndex::None => {
1286
// This is the first entity we've seen, and we have compute
1287
// shaders. Initialize the fast path.
1288
*self = UnbatchableBinnedEntityIndexSet::Sparse {
1289
instance_range: indices.instance_index..indices.instance_index + 1,
1290
first_indirect_parameters_index: None,
1291
}
1292
}
1293
PhaseItemExtraIndex::IndirectParametersIndex {
1294
range: ref indirect_parameters_index,
1295
..
1296
} => {
1297
// This is the first entity we've seen, and we have compute
1298
// shaders. Initialize the fast path.
1299
*self = UnbatchableBinnedEntityIndexSet::Sparse {
1300
instance_range: indices.instance_index..indices.instance_index + 1,
1301
first_indirect_parameters_index: NonMaxU32::new(
1302
indirect_parameters_index.start,
1303
),
1304
}
1305
}
1306
}
1307
}
1308
1309
UnbatchableBinnedEntityIndexSet::Sparse {
1310
instance_range,
1311
first_indirect_parameters_index,
1312
} if instance_range.end == indices.instance_index
1313
&& ((first_indirect_parameters_index.is_none()
1314
&& indices.extra_index == PhaseItemExtraIndex::None)
1315
|| first_indirect_parameters_index.is_some_and(
1316
|first_indirect_parameters_index| match indices.extra_index {
1317
PhaseItemExtraIndex::IndirectParametersIndex {
1318
range: ref this_range,
1319
..
1320
} => {
1321
u32::from(first_indirect_parameters_index) + instance_range.end
1322
- instance_range.start
1323
== this_range.start
1324
}
1325
PhaseItemExtraIndex::DynamicOffset(_) | PhaseItemExtraIndex::None => {
1326
false
1327
}
1328
},
1329
)) =>
1330
{
1331
// This is the normal case on non-WebGL 2.
1332
instance_range.end += 1;
1333
}
1334
1335
UnbatchableBinnedEntityIndexSet::Sparse { instance_range, .. } => {
1336
// We thought we were in non-WebGL 2 mode, but we got a dynamic
1337
// offset or non-contiguous index anyway. This shouldn't happen,
1338
// but let's go ahead and do the sensible thing anyhow: demote
1339
// the compressed `NoDynamicOffsets` field to the full
1340
// `DynamicOffsets` array.
1341
warn!(
1342
"Unbatchable binned entity index set was demoted from sparse to dense. \
1343
This is a bug in the renderer. Please report it.",
1344
);
1345
let new_dynamic_offsets = (0..instance_range.len() as u32)
1346
.flat_map(|entity_index| self.indices_for_entity_index(entity_index))
1347
.chain(iter::once(indices))
1348
.collect();
1349
*self = UnbatchableBinnedEntityIndexSet::Dense(new_dynamic_offsets);
1350
}
1351
1352
UnbatchableBinnedEntityIndexSet::Dense(dense_indices) => {
1353
dense_indices.push(indices);
1354
}
1355
}
1356
}
1357
1358
/// Clears the unbatchable binned entity index set.
1359
fn clear(&mut self) {
1360
match self {
1361
UnbatchableBinnedEntityIndexSet::Dense(dense_indices) => dense_indices.clear(),
1362
UnbatchableBinnedEntityIndexSet::Sparse { .. } => {
1363
*self = UnbatchableBinnedEntityIndexSet::NoEntities;
1364
}
1365
_ => {}
1366
}
1367
}
1368
}
1369
1370
/// A collection of all items to be rendered that will be encoded to GPU
1371
/// commands for a single render phase for a single view.
1372
///
1373
/// Each view (camera, or shadow-casting light, etc.) can have one or multiple render phases.
1374
/// They are used to queue entities for rendering.
1375
/// Multiple phases might be required due to different sorting/batching behaviors
1376
/// (e.g. opaque: front to back, transparent: back to front) or because one phase depends on
1377
/// the rendered texture of the previous phase (e.g. for screen-space reflections).
1378
/// All [`PhaseItem`]s are then rendered using a single [`TrackedRenderPass`].
1379
/// The render pass might be reused for multiple phases to reduce GPU overhead.
1380
///
1381
/// This flavor of render phase is used only for meshes that need to be sorted
1382
/// back-to-front, such as transparent meshes. For items that don't need strict
1383
/// sorting, [`BinnedRenderPhase`] is preferred, for performance.
1384
pub struct SortedRenderPhase<I>
1385
where
1386
I: SortedPhaseItem,
1387
{
1388
/// The items within this [`SortedRenderPhase`].
1389
pub items: Vec<I>,
1390
}
1391
1392
impl<I> Default for SortedRenderPhase<I>
1393
where
1394
I: SortedPhaseItem,
1395
{
1396
fn default() -> Self {
1397
Self { items: Vec::new() }
1398
}
1399
}
1400
1401
impl<I> SortedRenderPhase<I>
1402
where
1403
I: SortedPhaseItem,
1404
{
1405
/// Adds a [`PhaseItem`] to this render phase.
1406
#[inline]
1407
pub fn add(&mut self, item: I) {
1408
self.items.push(item);
1409
}
1410
1411
/// Removes all [`PhaseItem`]s from this render phase.
1412
#[inline]
1413
pub fn clear(&mut self) {
1414
self.items.clear();
1415
}
1416
1417
/// Sorts all of its [`PhaseItem`]s.
1418
pub fn sort(&mut self) {
1419
I::sort(&mut self.items);
1420
}
1421
1422
/// An [`Iterator`] through the associated [`Entity`] for each [`PhaseItem`] in order.
1423
#[inline]
1424
pub fn iter_entities(&'_ self) -> impl Iterator<Item = Entity> + '_ {
1425
self.items.iter().map(PhaseItem::entity)
1426
}
1427
1428
/// Renders all of its [`PhaseItem`]s using their corresponding draw functions.
1429
pub fn render<'w>(
1430
&self,
1431
render_pass: &mut TrackedRenderPass<'w>,
1432
world: &'w World,
1433
view: Entity,
1434
) -> Result<(), DrawError> {
1435
self.render_range(render_pass, world, view, ..)
1436
}
1437
1438
/// Renders all [`PhaseItem`]s in the provided `range` (based on their index in `self.items`) using their corresponding draw functions.
1439
pub fn render_range<'w>(
1440
&self,
1441
render_pass: &mut TrackedRenderPass<'w>,
1442
world: &'w World,
1443
view: Entity,
1444
range: impl SliceIndex<[I], Output = [I]>,
1445
) -> Result<(), DrawError> {
1446
let items = self
1447
.items
1448
.get(range)
1449
.expect("`Range` provided to `render_range()` is out of bounds");
1450
1451
let draw_functions = world.resource::<DrawFunctions<I>>();
1452
let mut draw_functions = draw_functions.write();
1453
draw_functions.prepare(world);
1454
1455
let mut index = 0;
1456
while index < items.len() {
1457
let item = &items[index];
1458
let batch_range = item.batch_range();
1459
if batch_range.is_empty() {
1460
index += 1;
1461
} else {
1462
let draw_function = draw_functions.get_mut(item.draw_function()).unwrap();
1463
draw_function.draw(world, render_pass, view, item)?;
1464
index += batch_range.len();
1465
}
1466
}
1467
Ok(())
1468
}
1469
}
1470
1471
/// An item (entity of the render world) which will be drawn to a texture or the screen,
1472
/// as part of a render phase.
1473
///
1474
/// The data required for rendering an entity is extracted from the main world in the
1475
/// [`ExtractSchedule`](crate::ExtractSchedule).
1476
/// Then it has to be queued up for rendering during the [`RenderSystems::Queue`],
1477
/// by adding a corresponding phase item to a render phase.
1478
/// Afterwards it will be possibly sorted and rendered automatically in the
1479
/// [`RenderSystems::PhaseSort`] and [`RenderSystems::Render`], respectively.
1480
///
1481
/// `PhaseItem`s come in two flavors: [`BinnedPhaseItem`]s and
1482
/// [`SortedPhaseItem`]s.
1483
///
1484
/// * Binned phase items have a `BinKey` which specifies what bin they're to be
1485
/// placed in. All items in the same bin are eligible to be batched together.
1486
/// The `BinKey`s are sorted, but the individual bin items aren't. Binned phase
1487
/// items are good for opaque meshes, in which the order of rendering isn't
1488
/// important. Generally, binned phase items are faster than sorted phase items.
1489
///
1490
/// * Sorted phase items, on the other hand, are placed into one large buffer
1491
/// and then sorted all at once. This is needed for transparent meshes, which
1492
/// have to be sorted back-to-front to render with the painter's algorithm.
1493
/// These types of phase items are generally slower than binned phase items.
1494
pub trait PhaseItem: Sized + Send + Sync + 'static {
1495
/// Whether or not this `PhaseItem` should be subjected to automatic batching. (Default: `true`)
1496
const AUTOMATIC_BATCHING: bool = true;
1497
1498
/// The corresponding entity that will be drawn.
1499
///
1500
/// This is used to fetch the render data of the entity, required by the draw function,
1501
/// from the render world .
1502
fn entity(&self) -> Entity;
1503
1504
/// The main world entity represented by this `PhaseItem`.
1505
fn main_entity(&self) -> MainEntity;
1506
1507
/// Specifies the [`Draw`] function used to render the item.
1508
fn draw_function(&self) -> DrawFunctionId;
1509
1510
/// The range of instances that the batch covers. After doing a batched draw, batch range
1511
/// length phase items will be skipped. This design is to avoid having to restructure the
1512
/// render phase unnecessarily.
1513
fn batch_range(&self) -> &Range<u32>;
1514
fn batch_range_mut(&mut self) -> &mut Range<u32>;
1515
1516
/// Returns the [`PhaseItemExtraIndex`].
1517
///
1518
/// If present, this is either a dynamic offset or an indirect parameters
1519
/// index.
1520
fn extra_index(&self) -> PhaseItemExtraIndex;
1521
1522
/// Returns a pair of mutable references to both the batch range and extra
1523
/// index.
1524
fn batch_range_and_extra_index_mut(&mut self) -> (&mut Range<u32>, &mut PhaseItemExtraIndex);
1525
}
1526
1527
/// The "extra index" associated with some [`PhaseItem`]s, alongside the
1528
/// indirect instance index.
1529
///
1530
/// Sometimes phase items require another index in addition to the range of
1531
/// instances they already have. These can be:
1532
///
1533
/// * The *dynamic offset*: a `wgpu` dynamic offset into the uniform buffer of
1534
/// instance data. This is used on platforms that don't support storage
1535
/// buffers, to work around uniform buffer size limitations.
1536
///
1537
/// * The *indirect parameters index*: an index into the buffer that specifies
1538
/// the indirect parameters for this [`PhaseItem`]'s drawcall. This is used when
1539
/// indirect mode is on (as used for GPU culling).
1540
///
1541
/// Note that our indirect draw functionality requires storage buffers, so it's
1542
/// impossible to have both a dynamic offset and an indirect parameters index.
1543
/// This convenient fact allows us to pack both indices into a single `u32`.
1544
#[derive(Clone, PartialEq, Eq, Hash, Debug)]
1545
pub enum PhaseItemExtraIndex {
1546
/// No extra index is present.
1547
None,
1548
/// A `wgpu` dynamic offset into the uniform buffer of instance data. This
1549
/// is used on platforms that don't support storage buffers, to work around
1550
/// uniform buffer size limitations.
1551
DynamicOffset(u32),
1552
/// An index into the buffer that specifies the indirect parameters for this
1553
/// [`PhaseItem`]'s drawcall. This is used when indirect mode is on (as used
1554
/// for GPU culling).
1555
IndirectParametersIndex {
1556
/// The range of indirect parameters within the indirect parameters array.
1557
///
1558
/// If we're using `multi_draw_indirect_count`, this specifies the
1559
/// maximum range of indirect parameters within that array. If batches
1560
/// are ultimately culled out on the GPU, the actual number of draw
1561
/// commands might be lower than the length of this range.
1562
range: Range<u32>,
1563
/// If `multi_draw_indirect_count` is in use, and this phase item is
1564
/// part of a batch set, specifies the index of the batch set that this
1565
/// phase item is a part of.
1566
///
1567
/// If `multi_draw_indirect_count` isn't in use, or this phase item
1568
/// isn't part of a batch set, this is `None`.
1569
batch_set_index: Option<NonMaxU32>,
1570
},
1571
}
1572
1573
impl PhaseItemExtraIndex {
1574
/// Returns either an indirect parameters index or
1575
/// [`PhaseItemExtraIndex::None`], as appropriate.
1576
pub fn maybe_indirect_parameters_index(
1577
indirect_parameters_index: Option<NonMaxU32>,
1578
) -> PhaseItemExtraIndex {
1579
match indirect_parameters_index {
1580
Some(indirect_parameters_index) => PhaseItemExtraIndex::IndirectParametersIndex {
1581
range: u32::from(indirect_parameters_index)
1582
..(u32::from(indirect_parameters_index) + 1),
1583
batch_set_index: None,
1584
},
1585
None => PhaseItemExtraIndex::None,
1586
}
1587
}
1588
1589
/// Returns either a dynamic offset index or [`PhaseItemExtraIndex::None`],
1590
/// as appropriate.
1591
pub fn maybe_dynamic_offset(dynamic_offset: Option<NonMaxU32>) -> PhaseItemExtraIndex {
1592
match dynamic_offset {
1593
Some(dynamic_offset) => PhaseItemExtraIndex::DynamicOffset(dynamic_offset.into()),
1594
None => PhaseItemExtraIndex::None,
1595
}
1596
}
1597
}
1598
1599
/// Represents phase items that are placed into bins. The `BinKey` specifies
1600
/// which bin they're to be placed in. Bin keys are sorted, and items within the
1601
/// same bin are eligible to be batched together. The elements within the bins
1602
/// aren't themselves sorted.
1603
///
1604
/// An example of a binned phase item is `Opaque3d`, for which the rendering
1605
/// order isn't critical.
1606
pub trait BinnedPhaseItem: PhaseItem {
1607
/// The key used for binning [`PhaseItem`]s into bins. Order the members of
1608
/// [`BinnedPhaseItem::BinKey`] by the order of binding for best
1609
/// performance. For example, pipeline id, draw function id, mesh asset id,
1610
/// lowest variable bind group id such as the material bind group id, and
1611
/// its dynamic offsets if any, next bind group and offsets, etc. This
1612
/// reduces the need for rebinding between bins and improves performance.
1613
type BinKey: Clone + Send + Sync + PartialEq + Eq + Ord + Hash;
1614
1615
/// The key used to combine batches into batch sets.
1616
///
1617
/// A *batch set* is a set of meshes that can potentially be multi-drawn
1618
/// together.
1619
type BatchSetKey: PhaseItemBatchSetKey;
1620
1621
/// Creates a new binned phase item from the key and per-entity data.
1622
///
1623
/// Unlike [`SortedPhaseItem`]s, this is generally called "just in time"
1624
/// before rendering. The resulting phase item isn't stored in any data
1625
/// structures, resulting in significant memory savings.
1626
fn new(
1627
batch_set_key: Self::BatchSetKey,
1628
bin_key: Self::BinKey,
1629
representative_entity: (Entity, MainEntity),
1630
batch_range: Range<u32>,
1631
extra_index: PhaseItemExtraIndex,
1632
) -> Self;
1633
}
1634
1635
/// A key used to combine batches into batch sets.
1636
///
1637
/// A *batch set* is a set of meshes that can potentially be multi-drawn
1638
/// together.
1639
pub trait PhaseItemBatchSetKey: Clone + Send + Sync + PartialEq + Eq + Ord + Hash {
1640
/// Returns true if this batch set key describes indexed meshes or false if
1641
/// it describes non-indexed meshes.
1642
///
1643
/// Bevy uses this in order to determine which kind of indirect draw
1644
/// parameters to use, if indirect drawing is enabled.
1645
fn indexed(&self) -> bool;
1646
}
1647
1648
/// Represents phase items that must be sorted. The `SortKey` specifies the
1649
/// order that these items are drawn in. These are placed into a single array,
1650
/// and the array as a whole is then sorted.
1651
///
1652
/// An example of a sorted phase item is `Transparent3d`, which must be sorted
1653
/// back to front in order to correctly render with the painter's algorithm.
1654
pub trait SortedPhaseItem: PhaseItem {
1655
/// The type used for ordering the items. The smallest values are drawn first.
1656
/// This order can be calculated using the [`ViewRangefinder3d`],
1657
/// based on the view-space `Z` value of the corresponding view matrix.
1658
type SortKey: Ord;
1659
1660
/// Determines the order in which the items are drawn.
1661
fn sort_key(&self) -> Self::SortKey;
1662
1663
/// Sorts a slice of phase items into render order. Generally if the same type
1664
/// is batched this should use a stable sort like [`slice::sort_by_key`].
1665
/// In almost all other cases, this should not be altered from the default,
1666
/// which uses an unstable sort, as this provides the best balance of CPU and GPU
1667
/// performance.
1668
///
1669
/// Implementers can optionally not sort the list at all. This is generally advisable if and
1670
/// only if the renderer supports a depth prepass, which is by default not supported by
1671
/// the rest of Bevy's first party rendering crates. Even then, this may have a negative
1672
/// impact on GPU-side performance due to overdraw.
1673
///
1674
/// It's advised to always profile for performance changes when changing this implementation.
1675
#[inline]
1676
fn sort(items: &mut [Self]) {
1677
items.sort_unstable_by_key(Self::sort_key);
1678
}
1679
1680
/// Whether this phase item targets indexed meshes (those with both vertex
1681
/// and index buffers as opposed to just vertex buffers).
1682
///
1683
/// Bevy needs this information in order to properly group phase items
1684
/// together for multi-draw indirect, because the GPU layout of indirect
1685
/// commands differs between indexed and non-indexed meshes.
1686
///
1687
/// If you're implementing a custom phase item that doesn't describe a mesh,
1688
/// you can safely return false here.
1689
fn indexed(&self) -> bool;
1690
}
1691
1692
/// A [`PhaseItem`] item, that automatically sets the appropriate render pipeline,
1693
/// cached in the [`PipelineCache`].
1694
///
1695
/// You can use the [`SetItemPipeline`] render command to set the pipeline for this item.
1696
pub trait CachedRenderPipelinePhaseItem: PhaseItem {
1697
/// The id of the render pipeline, cached in the [`PipelineCache`], that will be used to draw
1698
/// this phase item.
1699
fn cached_pipeline(&self) -> CachedRenderPipelineId;
1700
}
1701
1702
/// A [`RenderCommand`] that sets the pipeline for the [`CachedRenderPipelinePhaseItem`].
1703
pub struct SetItemPipeline;
1704
1705
impl<P: CachedRenderPipelinePhaseItem> RenderCommand<P> for SetItemPipeline {
1706
type Param = SRes<PipelineCache>;
1707
type ViewQuery = ();
1708
type ItemQuery = ();
1709
#[inline]
1710
fn render<'w>(
1711
item: &P,
1712
_view: (),
1713
_entity: Option<()>,
1714
pipeline_cache: SystemParamItem<'w, '_, Self::Param>,
1715
pass: &mut TrackedRenderPass<'w>,
1716
) -> RenderCommandResult {
1717
if let Some(pipeline) = pipeline_cache
1718
.into_inner()
1719
.get_render_pipeline(item.cached_pipeline())
1720
{
1721
pass.set_render_pipeline(pipeline);
1722
RenderCommandResult::Success
1723
} else {
1724
RenderCommandResult::Skip
1725
}
1726
}
1727
}
1728
1729
/// This system sorts the [`PhaseItem`]s of all [`SortedRenderPhase`]s of this
1730
/// type.
1731
pub fn sort_phase_system<I>(mut render_phases: ResMut<ViewSortedRenderPhases<I>>)
1732
where
1733
I: SortedPhaseItem,
1734
{
1735
for phase in render_phases.values_mut() {
1736
phase.sort();
1737
}
1738
}
1739
1740
/// Removes entities that became invisible or changed phases from the bins.
1741
///
1742
/// This must run after queuing.
1743
pub fn sweep_old_entities<BPI>(mut render_phases: ResMut<ViewBinnedRenderPhases<BPI>>)
1744
where
1745
BPI: BinnedPhaseItem,
1746
{
1747
for phase in render_phases.0.values_mut() {
1748
phase.sweep_old_entities();
1749
}
1750
}
1751
1752
impl BinnedRenderPhaseType {
1753
pub fn mesh(
1754
batchable: bool,
1755
gpu_preprocessing_support: &GpuPreprocessingSupport,
1756
) -> BinnedRenderPhaseType {
1757
match (batchable, gpu_preprocessing_support.max_supported_mode) {
1758
(true, GpuPreprocessingMode::Culling) => BinnedRenderPhaseType::MultidrawableMesh,
1759
(true, _) => BinnedRenderPhaseType::BatchableMesh,
1760
(false, _) => BinnedRenderPhaseType::UnbatchableMesh,
1761
}
1762
}
1763
}
1764
1765
impl RenderBin {
1766
/// Creates a [`RenderBin`] containing a single entity.
1767
fn from_entity(entity: MainEntity, uniform_index: InputUniformIndex) -> RenderBin {
1768
let mut entities = IndexMap::default();
1769
entities.insert(entity, uniform_index);
1770
RenderBin { entities }
1771
}
1772
1773
/// Inserts an entity into the bin.
1774
fn insert(&mut self, entity: MainEntity, uniform_index: InputUniformIndex) {
1775
self.entities.insert(entity, uniform_index);
1776
}
1777
1778
/// Removes an entity from the bin.
1779
fn remove(&mut self, entity_to_remove: MainEntity) {
1780
self.entities.swap_remove(&entity_to_remove);
1781
}
1782
1783
/// Returns true if the bin contains no entities.
1784
fn is_empty(&self) -> bool {
1785
self.entities.is_empty()
1786
}
1787
1788
/// Returns the [`IndexMap`] containing all the entities in the bin, along
1789
/// with the cached [`InputUniformIndex`] of each.
1790
#[inline]
1791
pub fn entities(&self) -> &IndexMap<MainEntity, InputUniformIndex, EntityHash> {
1792
&self.entities
1793
}
1794
}
1795
1796
/// An iterator that efficiently finds the indices of all zero bits in a
1797
/// [`FixedBitSet`] and returns them in reverse order.
1798
///
1799
/// [`FixedBitSet`] doesn't natively offer this functionality, so we have to
1800
/// implement it ourselves.
1801
#[derive(Debug)]
1802
struct ReverseFixedBitSetZeroesIterator<'a> {
1803
/// The bit set.
1804
bitset: &'a FixedBitSet,
1805
/// The next bit index we're going to scan when [`Iterator::next`] is
1806
/// called.
1807
bit_index: isize,
1808
}
1809
1810
impl<'a> ReverseFixedBitSetZeroesIterator<'a> {
1811
fn new(bitset: &'a FixedBitSet) -> ReverseFixedBitSetZeroesIterator<'a> {
1812
ReverseFixedBitSetZeroesIterator {
1813
bitset,
1814
bit_index: (bitset.len() as isize) - 1,
1815
}
1816
}
1817
}
1818
1819
impl<'a> Iterator for ReverseFixedBitSetZeroesIterator<'a> {
1820
type Item = usize;
1821
1822
fn next(&mut self) -> Option<usize> {
1823
while self.bit_index >= 0 {
1824
// Unpack the bit index into block and bit.
1825
let block_index = self.bit_index / (Block::BITS as isize);
1826
let bit_pos = self.bit_index % (Block::BITS as isize);
1827
1828
// Grab the block. Mask off all bits above the one we're scanning
1829
// from by setting them all to 1.
1830
let mut block = self.bitset.as_slice()[block_index as usize];
1831
if bit_pos + 1 < (Block::BITS as isize) {
1832
block |= (!0) << (bit_pos + 1);
1833
}
1834
1835
// Search for the next unset bit. Note that the `leading_ones`
1836
// function counts from the MSB to the LSB, so we need to flip it to
1837
// get the bit number.
1838
let pos = (Block::BITS as isize) - (block.leading_ones() as isize) - 1;
1839
1840
// If we found an unset bit, return it.
1841
if pos != -1 {
1842
let result = block_index * (Block::BITS as isize) + pos;
1843
self.bit_index = result - 1;
1844
return Some(result as usize);
1845
}
1846
1847
// Otherwise, go to the previous block.
1848
self.bit_index = block_index * (Block::BITS as isize) - 1;
1849
}
1850
1851
None
1852
}
1853
}
1854
1855
#[cfg(test)]
1856
mod test {
1857
use super::ReverseFixedBitSetZeroesIterator;
1858
use fixedbitset::FixedBitSet;
1859
use proptest::{collection::vec, prop_assert_eq, proptest};
1860
1861
proptest! {
1862
#[test]
1863
fn reverse_fixed_bit_set_zeroes_iterator(
1864
bits in vec(0usize..1024usize, 0usize..1024usize),
1865
size in 0usize..1024usize,
1866
) {
1867
// Build a random bit set.
1868
let mut bitset = FixedBitSet::new();
1869
bitset.grow(size);
1870
for bit in bits {
1871
if bit < size {
1872
bitset.set(bit, true);
1873
}
1874
}
1875
1876
// Iterate over the bit set backwards in a naive way, and check that
1877
// that iteration sequence corresponds to the optimized one.
1878
let mut iter = ReverseFixedBitSetZeroesIterator::new(&bitset);
1879
for bit_index in (0..size).rev() {
1880
if !bitset.contains(bit_index) {
1881
prop_assert_eq!(iter.next(), Some(bit_index));
1882
}
1883
}
1884
1885
prop_assert_eq!(iter.next(), None);
1886
}
1887
}
1888
}
1889
1890