Skip to main content

bevy_render/render_resource/
sparse_buffer_vec.rs

1//! GPU buffers that support sparse updates if only a small number of elements
2//! have changed.
3
4use alloc::sync::{Arc, Weak};
5use core::{
6    slice,
7    sync::atomic::{AtomicU64, Ordering},
8};
9
10use bevy_app::{App, Plugin};
11use bevy_asset::{embedded_asset, load_embedded_asset, AssetServer, Handle};
12use bevy_derive::{Deref, DerefMut};
13use bevy_ecs::{
14    resource::Resource,
15    schedule::IntoScheduleConfigs as _,
16    system::{Commands, Res, ResMut},
17};
18use bevy_log::{debug, error, info};
19use bevy_material::{
20    bind_group_layout_entries::{
21        binding_types::{storage_buffer, storage_buffer_read_only, uniform_buffer},
22        BindGroupLayoutEntries,
23    },
24    descriptor::{BindGroupLayoutDescriptor, CachedComputePipelineId, ComputePipelineDescriptor},
25};
26use bevy_shader::Shader;
27use bytemuck::{Pod, Zeroable};
28use encase::ShaderType;
29use weak_table::WeakKeyHashMap;
30use wgpu::{BufferDescriptor, BufferUsages, ComputePassDescriptor, ShaderStages};
31
32use crate::{
33    diagnostic::RecordDiagnostics as _,
34    init_gpu_resource,
35    render_resource::{
36        AtomicPod, BindGroup, BindGroupEntries, Buffer, PipelineCache, RawBufferVec,
37        SpecializedComputePipeline, SpecializedComputePipelines, UniformBuffer,
38    },
39    renderer::{RenderContext, RenderDevice, RenderGraph, RenderGraphSystems, RenderQueue},
40    ExtractSchedule, GpuResourceAppExt, RenderApp, RenderStartup,
41};
42
43/// A plugin that allows sparse updates of GPU buffers if only a small number of
44/// elements have changed.
45pub struct SparseBufferPlugin;
46
47impl Plugin for SparseBufferPlugin {
48    fn build(&self, app: &mut App) {
49        embedded_asset!(app, "sparse_buffer_update.wesl");
50    }
51
52    fn finish(&self, app: &mut App) {
53        let Some(render_app) = app.get_sub_app_mut(RenderApp) else {
54            return;
55        };
56
57        render_app
58            .init_gpu_resource::<SparseBufferUpdateJobs>()
59            .init_gpu_resource::<SpecializedComputePipelines<SparseBufferUpdatePipelines>>()
60            .add_systems(ExtractSchedule, clear_sparse_buffer_jobs)
61            .add_systems(
62                RenderStartup,
63                (
64                    init_sparse_buffer_update_pipelines,
65                    init_sparse_buffer_update_bind_groups.after(init_gpu_resource::<SpecializedComputePipelines<SparseBufferUpdatePipelines>>),
66                )
67                    .chain(),
68            )
69            .add_systems(
70                RenderGraph,
71                // We perform sparse buffer updates very early so that sparse
72                // buffers can be used in any render pass.
73                update_sparse_buffers.in_set(RenderGraphSystems::Begin),
74            );
75    }
76}
77
78/// A globally-unique ID that identifies this sparse buffer.
79#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug, Deref, DerefMut)]
80pub struct SparseBufferId(pub u64);
81
82/// An object that allows the sparse buffer ID to be query and holds the bind
83/// group for that sparse buffer alive.
84///
85/// Each sparse buffer holds a strong reference to this handle, and the
86/// [`SparseBufferUpdateBindGroups`] resource contains a weak map from this
87/// handle to the bind group. This setup ensures that, when the sparse buffer is
88/// freed, the bind groups for that sparse buffer are freed as well.
89pub type SparseBufferHandle = Arc<SparseBufferId>;
90
91/// The next sparse buffer ID to be assigned.
92static NEXT_SPARSE_BUFFER_ID: AtomicU64 = AtomicU64::new(0);
93
94/// The size of a single workgroup in the sparse buffer shader.
95const SPARSE_BUFFER_UPDATE_WORKGROUP_SIZE: u32 = 256;
96
97/// The fraction of the buffer that may be changed before we fall back to full
98/// reupload.
99///
100/// This is set to 15% by default. This was obtained experimentally by testing
101/// very large scenes and roughly matches the values used by other engines that
102/// perform sparse buffer updates.
103const SPARSE_UPLOAD_THRESHOLD: f64 = 0.15;
104
105/// The WebGPU limit on the number of workgroups that can be dispatched.
106const MAX_WORKGROUPS: u32 = 65535;
107
108/// We round all allocations up to the nearest power of this.
109const REALLOCATION_FACTOR: f64 = 1.5;
110/// We round all allocations up to the nearest multiple of this.
111const REALLOCATION_SIZE_MULTIPLE: usize = 256;
112
113/// The number of bits packed into each [`AtomicU64`] word.
114const BITS_PER_WORD: u32 = 64;
115
116/// Pipelines for the sparse buffer update shader.
117///
118/// This shader is shared among all sparse buffer vectors.
119#[derive(Resource)]
120pub struct SparseBufferUpdatePipelines {
121    /// The bind group layout.
122    ///
123    /// We only have one bind group layout shared among all sparse buffer
124    /// vectors.
125    bind_group_layout: Option<BindGroupLayoutDescriptor>,
126    /// The shader that performs the scatter operation.
127    shader: Option<Handle<Shader>>,
128}
129
130/// A resource, part of the render world, that stores the bind groups for each
131/// sparse buffer.
132#[derive(Resource)]
133pub struct SparseBufferUpdateBindGroups {
134    /// The bind groups for each sparse buffer.
135    ///
136    /// These are stored in a weak map so that when the sparse buffer goes away,
137    /// the bind group for that buffer goes away as well.
138    bind_groups: WeakKeyHashMap<Weak<SparseBufferId>, SparseBufferUpdateBindGroup>,
139    /// The ID of the update shader pipeline shared among all sparse buffers.
140    pipeline_id: Option<CachedComputePipelineId>,
141}
142
143/// A single bind group for the sparse buffer update shader.
144pub struct SparseBufferUpdateBindGroup {
145    /// The actual bind group.
146    bind_group: BindGroup,
147}
148
149/// A resource, part of the render world, that stores all pending sparse updates
150/// to buffers.
151#[derive(Resource, Default, Deref, DerefMut)]
152pub struct SparseBufferUpdateJobs(pub Vec<SparseBufferUpdateJob>);
153
154/// Describes a sparse update operation for a buffer.
155pub struct SparseBufferUpdateJob {
156    /// A handle to the buffer to be updated.
157    sparse_buffer_handle: SparseBufferHandle,
158    /// The number of elements to update.
159    updated_element_count: u32,
160    /// The size of each element in 32-bit words.
161    element_word_size: u32,
162}
163
164impl SparseBufferUpdateJob {
165    /// Calculates the number of words that need to be updated.
166    fn words_to_update(&self) -> u32 {
167        self.updated_element_count * self.element_word_size
168    }
169
170    /// Calculates the number of workgroups that need to be dispatched.
171    fn workgroup_count(&self) -> u32 {
172        self.words_to_update()
173            .div_ceil(SPARSE_BUFFER_UPDATE_WORKGROUP_SIZE)
174    }
175}
176
177/// A GPU type that describes a sparse update that is to be performed.
178#[derive(Clone, Copy, Default, ShaderType, Pod, Zeroable)]
179#[repr(C)]
180struct GpuSparseBufferUpdateMetadata {
181    /// The size of a single element in 32-bit words.
182    element_size: u32,
183    /// The number of elements that need to be updated.
184    updated_element_count: u32,
185}
186
187/// A system, part of the render graph, that performs sparse buffer updates to
188/// buffers for which only a small number of elements have changed.
189///
190/// This runs as early in the pipeline as possible so that sparse buffers can be
191/// used for any subsequent pass.
192///
193/// Runs in [`RenderGraphSystems::Begin`].
194pub fn update_sparse_buffers(
195    sparse_buffer_update_jobs: Res<SparseBufferUpdateJobs>,
196    sparse_buffer_update_bind_groups: Res<SparseBufferUpdateBindGroups>,
197    pipeline_cache: Res<PipelineCache>,
198    mut render_context: RenderContext,
199) {
200    // Bail if we have nothing to do.
201    if sparse_buffer_update_jobs.is_empty() {
202        return;
203    }
204
205    let Some(pipeline_id) = sparse_buffer_update_bind_groups.pipeline_id else {
206        return;
207    };
208    let Some(compute_pipeline) = pipeline_cache.get_compute_pipeline(pipeline_id) else {
209        return;
210    };
211
212    let diagnostics = render_context.diagnostic_recorder();
213    let diagnostics = diagnostics.as_deref();
214    let command_encoder = render_context.command_encoder();
215
216    let mut sparse_buffer_update_pass =
217        command_encoder.begin_compute_pass(&ComputePassDescriptor {
218            label: Some("sparse buffer updates"),
219            timestamp_writes: None,
220        });
221    sparse_buffer_update_pass.set_pipeline(compute_pipeline);
222    let time_span = diagnostics.time_span(&mut sparse_buffer_update_pass, "sparse buffer updates");
223
224    // Process each sparse buffer update job.
225    for sparse_buffer_update_job in sparse_buffer_update_jobs.iter() {
226        let Some(sparse_buffer_update_bind_group) = sparse_buffer_update_bind_groups
227            .bind_groups
228            .get(&sparse_buffer_update_job.sparse_buffer_handle)
229        else {
230            continue;
231        };
232
233        sparse_buffer_update_pass.set_bind_group(
234            0,
235            &sparse_buffer_update_bind_group.bind_group,
236            &[],
237        );
238        sparse_buffer_update_pass.dispatch_workgroups(
239            sparse_buffer_update_job.workgroup_count(),
240            1,
241            1,
242        );
243    }
244
245    time_span.end(&mut sparse_buffer_update_pass);
246}
247
248/// A system that clears out the sparse buffer update jobs in preparation for a
249/// new frame.
250fn clear_sparse_buffer_jobs(mut sparse_buffer_update_jobs: ResMut<SparseBufferUpdateJobs>) {
251    sparse_buffer_update_jobs.clear();
252}
253
254pub fn init_sparse_buffer_update_pipelines(
255    mut commands: Commands,
256    asset_server: Res<AssetServer>,
257    render_device: Res<RenderDevice>,
258) {
259    let limit = render_device.limits().max_storage_buffers_per_shader_stage;
260    if limit < 3 {
261        info!(
262            "Sparse buffer updates disabled. RenderDevice lacks support: max_storage_buffers_per_shader_stage ({}) < 3.",
263            limit
264        );
265        commands.insert_resource(SparseBufferUpdatePipelines {
266            bind_group_layout: None,
267            shader: None,
268        });
269        return;
270    }
271
272    let bind_group_layout = BindGroupLayoutDescriptor::new(
273        "sparse buffer update bind group layout",
274        &BindGroupLayoutEntries::sequential(
275            ShaderStages::COMPUTE,
276            (
277                // @group(0) @binding(0) var<storage, read_write> dest_buffer: array<u32>;
278                storage_buffer::<u32>(false),
279                // @group(0) @binding(1) var<storage> src_buffer: array<u32>;
280                storage_buffer_read_only::<u32>(false),
281                // @group(0) @binding(2) var<storage> indices: array<u32>;
282                storage_buffer_read_only::<u32>(false),
283                // @group(0) @binding(3) var<uniform> metadata:
284                // SparseBufferUpdateMetadata;
285                uniform_buffer::<GpuSparseBufferUpdateMetadata>(false),
286            ),
287        ),
288    );
289
290    commands.insert_resource(SparseBufferUpdatePipelines {
291        bind_group_layout: Some(bind_group_layout),
292        shader: Some(load_embedded_asset!(
293            asset_server.as_ref(),
294            "sparse_buffer_update.wesl"
295        )),
296    });
297}
298
299impl SpecializedComputePipeline for SparseBufferUpdatePipelines {
300    type Key = Handle<Shader>;
301
302    fn specialize(&self, key: Self::Key) -> ComputePipelineDescriptor {
303        ComputePipelineDescriptor {
304            label: Some("sparse buffer update pipeline".into()),
305            layout: self.bind_group_layout.clone().into_iter().collect(),
306            shader: key,
307            shader_defs: vec![],
308            ..ComputePipelineDescriptor::default()
309        }
310    }
311}
312
313/// The buffers that we use to sparsely scatter new data to the GPU.
314///
315/// There's one such set of buffers per sparse buffer vector.
316struct SparseBufferStagingBuffers {
317    /// All elements that have changed and need to be updated.
318    source_data: RawBufferVec<u32>,
319
320    /// The index at which we write each element in [`Self::source_data`].
321    ///
322    /// The length of this buffer is equal to [`Self::source_data`] divided by
323    /// [`Self::element_word_size`].
324    indices: RawBufferVec<u32>,
325
326    /// The size of each element in 32-bit words.
327    element_word_size: u32,
328}
329
330impl SparseBufferStagingBuffers {
331    /// Creates a new set of staging buffers for a sparse buffer vector.
332    fn new(label: &str, element_word_size: u32) -> SparseBufferStagingBuffers {
333        let mut source_data_buffer =
334            RawBufferVec::new(BufferUsages::COPY_DST | BufferUsages::STORAGE);
335        source_data_buffer.set_label(Some(&*format!("{} staging buffer", label)));
336
337        let mut indices_buffer = RawBufferVec::new(BufferUsages::COPY_DST | BufferUsages::STORAGE);
338        indices_buffer.set_label(Some(&*format!("{} index buffer", label)));
339
340        SparseBufferStagingBuffers {
341            source_data: source_data_buffer,
342            indices: indices_buffer,
343            element_word_size,
344        }
345    }
346
347    /// Returns the number of updated elements.
348    fn updated_element_count(&self) -> u32 {
349        (self.source_data.len() / self.element_word_size as usize) as u32
350    }
351
352    /// Writes the buffers that contain all the data necessary to perform a
353    /// sparse upload to the GPU.
354    ///
355    /// This includes the buffer associated with the supplied
356    /// `metadata_uniform`.
357    fn write_buffers(
358        &mut self,
359        metadata_uniform: &mut UniformBuffer<GpuSparseBufferUpdateMetadata>,
360        render_device: &RenderDevice,
361        render_queue: &RenderQueue,
362    ) {
363        metadata_uniform.get_mut().updated_element_count = self.updated_element_count();
364        metadata_uniform.write_buffer(render_device, render_queue);
365
366        self.source_data.write_buffer(render_device, render_queue);
367        self.indices.write_buffer(render_device, render_queue);
368    }
369
370    /// Returns true if a sparse buffer update should *not* be performed because
371    /// too many words changed.
372    fn should_perform_full_reupload(
373        &self,
374        changed_element_count: u32,
375        buffer_length: usize,
376    ) -> bool {
377        // Calculate the number of changed words. If it's greater than the
378        // maximum number of workgroups as defined by `wgpu`, we must perform a
379        // full reupload.
380        //
381        // FIXME: This degrades performance in the exact scenarios we need it
382        // the most. We should fall back to doing multiple rounds of uploads in
383        // this case.
384        let total_changed_word_count = changed_element_count * self.element_word_size;
385        if total_changed_word_count > MAX_WORKGROUPS * SPARSE_BUFFER_UPDATE_WORKGROUP_SIZE {
386            return true;
387        }
388
389        // Don't perform a sparse upload if too many words changed, as it'll end
390        // up being slower than just uploading the whole buffer afresh.
391        let sparse_upload_fraction = changed_element_count as f64 / buffer_length as f64;
392        let should_reupload = sparse_upload_fraction > SPARSE_UPLOAD_THRESHOLD;
393
394        debug!(
395            "Sparse buffer changed {}/{} elements ({:.3}, threshold {:.3}): performing {} upload",
396            changed_element_count,
397            buffer_length,
398            sparse_upload_fraction,
399            SPARSE_UPLOAD_THRESHOLD,
400            if should_reupload { "full" } else { "sparse" }
401        );
402
403        should_reupload
404    }
405}
406
407/// A GPU buffer that can grow, can be updated atomically from multiple threads
408/// on the CPU, and is sparsely updated on the GPU if only a small number of
409/// elements have changed.
410///
411/// This type is similar to
412/// [`crate::render_resource::buffer_vec::AtomicRawBufferVec`], but instead of
413/// reuploading the entire buffer to the GPU when it's changed, it tracks
414/// changes on a per-element level and uploads only the elements that changed if
415/// the number of such elements is small. It uses a compute shader to scatter
416/// those changed elements.
417///
418/// As the stored data is [`AtomicPod`], multiple threads may update the buffer
419/// simultaneously. Note that, like
420/// [`crate::render_resource::buffer_vec::AtomicRawBufferVec`], only existing
421/// elements may be updated from multiple threads; new data still requires
422/// exclusive access.
423///
424/// `T` must have a size that's a multiple of 4.
425pub struct AtomicSparseBufferVec<T>
426where
427    T: AtomicPod,
428{
429    /// An ID that uniquely identifies this [`AtomicSparseBufferVec`].
430    handle: SparseBufferHandle,
431    /// The underlying values.
432    ///
433    /// These are stored as their blob representation to allow for thread-safe
434    /// update.
435    values: Vec<T::Blob>,
436    /// The GPU buffer, if allocated.
437    data_buffer: Option<Buffer>,
438    /// The GPU buffers that data is copied into in preparation to be scattered
439    /// to the [`Self::data_buffer`].
440    staging_buffers: SparseBufferStagingBuffers,
441    /// A GPU buffer that stores information such as the element size and stride
442    /// that's needed to perform sparse updates.
443    metadata_uniform: UniformBuffer<GpuSparseBufferUpdateMetadata>,
444    /// The capacity of the GPU buffer in elements.
445    capacity: usize,
446    /// The allowed `wgpu` buffer usages for the GPU buffer.
447    buffer_usages: BufferUsages,
448    /// An optional debug label to identify this buffer.
449    label: Arc<str>,
450    /// A bit set of dirty blocks.
451    ///
452    /// The size of this vector in bits is the number of elements divided
453    /// (rounded up) by 64: in other words, the size of this vector in *bits* is
454    /// the size of the [`Self::dirty_bits`] vector in *words*. A 1 in a bit
455    /// indicates that the block has changed since the last upload, while a 0
456    /// indicates that the block hasn't changed.
457    summary: Vec<AtomicU64>,
458    /// A bit set of dirty elements.
459    ///
460    /// The size of this vector in bits is the number of elements, rounded up to
461    /// the nearest 64. A 1 in a bit indicates that the element has changed since
462    /// the last upload, while a 0 indicates that the element hasn't changed.
463    ///
464    /// Each group of 64 elements, corresponding to a single word in this array,
465    /// is known as a *block*.
466    dirty_bits: Vec<AtomicU64>,
467    /// True if the entire buffer needs to be reuploaded, either because it
468    /// resized or because a sparse update couldn't be recorded and its staged
469    /// elements would otherwise be lost.
470    needs_full_reupload: bool,
471    /// True if a sparse update is to be performed.
472    sparse_update_scheduled: bool,
473}
474
475impl<T> AtomicSparseBufferVec<T>
476where
477    T: AtomicPod,
478{
479    /// Creates a new [`AtomicSparseBufferVec`] with the given set of buffer
480    /// usages and label.
481    ///
482    /// `buffer_usages` specifies the set of allowed `wgpu` buffer usages for
483    /// the buffer that [`AtomicSparseBufferVec`] manages.
484    /// `BufferUsages::COPY_DST` is automatically added to this set.
485    pub fn new(buffer_usages: BufferUsages, label: Arc<str>) -> Self {
486        // Make sure the value is word-aligned.
487        debug_assert_eq!(size_of::<T>() % 4, 0);
488        let element_word_size = size_of::<T>() / 4;
489
490        // Create a unique ID.
491        let id = Arc::new(SparseBufferId(
492            NEXT_SPARSE_BUFFER_ID.fetch_add(1, Ordering::Relaxed),
493        ));
494
495        Self {
496            handle: id,
497            values: vec![],
498            data_buffer: None,
499            staging_buffers: SparseBufferStagingBuffers::new(&label, element_word_size as u32),
500            metadata_uniform: UniformBuffer::from(GpuSparseBufferUpdateMetadata::new::<T>()),
501            capacity: 0,
502            buffer_usages: buffer_usages | BufferUsages::COPY_DST,
503            label,
504            summary: vec![],
505            dirty_bits: vec![],
506            needs_full_reupload: false,
507            sparse_update_scheduled: false,
508        }
509    }
510
511    /// Returns the number of elements in the CPU side copy of the buffer.
512    pub fn len(&self) -> u32 {
513        self.values.len() as u32
514    }
515
516    /// Returns true if there are no elements in the CPU side copy of the buffer.
517    pub fn is_empty(&self) -> bool {
518        self.values.is_empty()
519    }
520
521    /// Returns a handle to the buffer, if the data has been uploaded.
522    pub fn buffer(&self) -> Option<&Buffer> {
523        self.data_buffer.as_ref()
524    }
525
526    /// Removes all elements from the buffer.
527    pub fn clear(&mut self) {
528        self.values.clear();
529        self.summary.clear();
530        self.dirty_bits.clear();
531    }
532
533    /// Copies a value out of the buffer.
534    pub fn get(&self, index: u32) -> T {
535        T::read_from_blob(&self.values[index as usize])
536    }
537
538    /// Sets the value at the given index.
539    ///
540    /// If the index isn't in range of the buffer, this method panics.
541    ///
542    /// Internally, the value is converted to its blob representation.
543    ///
544    /// Note that this method is thread-safe and doesn't require `&mut self`.
545    /// It's your responsibility, however, to ensure synchronization; though
546    /// this method is memory-safe, it's possible for other threads to observe
547    /// partially-overwritten values if [`Self::get`] or similar methods are
548    /// called while the write operation is occurring.
549    pub fn set(&self, index: u32, value: T) {
550        value.write_to_blob(&self.values[index as usize]);
551        self.note_changed_index(index);
552    }
553
554    /// Adds a new value and returns its index.
555    pub fn push(&mut self, value: T) -> u32 {
556        let index = self.values.len() as u32;
557        self.values.push(T::Blob::default());
558        value.write_to_blob(&self.values[index as usize]);
559
560        let dirty_word_index = (index / BITS_PER_WORD) as usize;
561        let summary_word_index = dirty_word_index / BITS_PER_WORD as usize;
562        while self.summary.len() < summary_word_index + 1 {
563            self.summary.push(AtomicU64::default());
564        }
565        while self.dirty_bits.len() < dirty_word_index + 1 {
566            self.dirty_bits.push(AtomicU64::default());
567        }
568
569        self.note_changed_index(index);
570        index
571    }
572
573    /// Marks the given element index as dirty so that we know that we need to
574    /// upload it.
575    fn note_changed_index(&self, index: u32) {
576        note_changed_index(index, &self.summary, &self.dirty_bits);
577    }
578
579    /// Ensures that the backing buffer for this buffer vector is present and
580    /// appropriately sized on the GPU.
581    pub fn reserve(&mut self, new_capacity: usize, render_device: &RenderDevice) {
582        reserve(
583            new_capacity,
584            &mut self.capacity,
585            &self.label,
586            &mut self.data_buffer,
587            self.buffer_usages,
588            &mut self.needs_full_reupload,
589            size_of::<T::Blob>(),
590            render_device,
591        );
592    }
593
594    /// Grows the buffer by adding default values so that it's at least the
595    /// given size.
596    ///
597    /// This method sets all the newly-added values to dirty.
598    ///
599    /// If the buffer is already large enough, this method does nothing.
600    pub fn grow(&mut self, new_len: u32) {
601        let old_len = self.values.len() as u32;
602        if old_len >= new_len {
603            return;
604        }
605
606        self.values.reserve(new_len as usize - old_len as usize);
607        self.values.resize_with(new_len as usize, T::Blob::default);
608
609        set_dirty_bits_for_vector_growth(old_len, new_len, &mut self.summary, &mut self.dirty_bits);
610    }
611
612    /// Writes the data to the GPU, either via a sparse upload or a bulk data
613    /// upload.
614    pub fn write_buffers(&mut self, render_device: &RenderDevice, render_queue: &RenderQueue) {
615        if self.values.is_empty() {
616            return;
617        }
618
619        // Round up the size to a good value to balance reallocation frequency
620        // against memory waste.
621        let good_size = calculate_allocation_size(self.values.len());
622        self.reserve(good_size, render_device);
623
624        if self.should_perform_full_reupload(render_device) {
625            self.write_entire_buffer(render_queue);
626        } else {
627            self.prepare_sparse_upload(render_device, render_queue);
628        }
629    }
630
631    /// Returns true if the sparse buffer should perform a full reupload, either
632    /// because it was resized or because too much data changed for a sparse
633    /// update to be worthwhile.
634    fn should_perform_full_reupload(&self, render_device: &RenderDevice) -> bool {
635        if self.needs_full_reupload
636            || render_device.limits().max_storage_buffers_per_shader_stage < 3
637        {
638            return true;
639        }
640
641        let changed_element_count = count_dirty_elements(&self.summary, &self.dirty_bits);
642        self.staging_buffers
643            .should_perform_full_reupload(changed_element_count, self.values.len())
644    }
645
646    /// Writes the entire buffer in bulk.
647    ///
648    /// This is the method used when a sparse update is not used, either because
649    /// the buffer resized or because too much data changed for a sparse update
650    /// to be worthwhile.
651    fn write_entire_buffer(&mut self, render_queue: &RenderQueue) {
652        let Some(ref mut data_buffer) = self.data_buffer else {
653            error!("Dirty sparse buffer should have created a data buffer by now");
654            return;
655        };
656
657        // SAFETY: We're just writing atomic data to the GPU. The worst that
658        // can happen is that we race with somebody, which is unfortunate
659        // but not memory-unsafe.
660        unsafe {
661            render_queue.write_buffer(
662                data_buffer,
663                0,
664                slice::from_raw_parts(
665                    self.values.as_ptr().cast::<u8>(),
666                    self.values.len() * size_of::<T::Blob>(),
667                ),
668            );
669        }
670
671        // Mark all pages as clean.
672        for atomic_summary_word in self.summary.iter() {
673            atomic_summary_word.store(0, Ordering::Relaxed);
674        }
675        for atomic_dirty_word in self.dirty_bits.iter() {
676            atomic_dirty_word.store(0, Ordering::Relaxed);
677        }
678        self.sparse_update_scheduled = false;
679
680        // The GPU now matches `values` in full, which is the only thing this flag was asking for.
681        self.needs_full_reupload = false;
682    }
683
684    /// Schedules a sparse upload of only the elements that changed.
685    fn prepare_sparse_upload(&mut self, render_device: &RenderDevice, render_queue: &RenderQueue) {
686        // Iterate over all dirty elements, using the summary to accelerate the
687        // search.
688        for (summary_word_index, atomic_summary_word) in self.summary.iter().enumerate() {
689            let summary_word = atomic_summary_word.load(Ordering::Relaxed);
690            for summary_bit_offset in BitIter::new(summary_word) {
691                let dirty_word_index =
692                    summary_word_index * BITS_PER_WORD as usize + summary_bit_offset as usize;
693
694                // Iterate over all dirty elements in each dirty page.
695                let atomic_dirty_word = &self.dirty_bits[dirty_word_index];
696                let dirty_word = atomic_dirty_word.load(Ordering::Relaxed);
697                for dirty_bit_offset in BitIter::new(dirty_word) {
698                    let element_index =
699                        dirty_word_index * BITS_PER_WORD as usize + dirty_bit_offset as usize;
700
701                    let Some(blob) = self.values.get(element_index) else {
702                        continue;
703                    };
704
705                    // Write the index of the element so the shader will know where to
706                    // scatter the data to.
707                    self.staging_buffers.indices.push(element_index as u32);
708
709                    // Copy the element to the GPU staging buffer.
710                    let value = T::read_from_blob(blob);
711                    self.staging_buffers
712                        .source_data
713                        .extend(bytemuck::cast_slice(&[value]).iter().copied());
714
715                    // Make sure we're aligned up to a full element.
716                    debug_assert_eq!(
717                        self.staging_buffers.source_data.len()
718                            % self.staging_buffers.element_word_size as usize,
719                        0
720                    );
721                }
722
723                // Mark the element as clean.
724                atomic_dirty_word.store(0, Ordering::Relaxed);
725            }
726
727            // Mark the block as clean.
728            atomic_summary_word.store(0, Ordering::Relaxed);
729        }
730
731        // Schedule a sparse update if there was something to do.
732        self.sparse_update_scheduled = !self.staging_buffers.source_data.is_empty();
733        if self.sparse_update_scheduled {
734            self.staging_buffers.write_buffers(
735                &mut self.metadata_uniform,
736                render_device,
737                render_queue,
738            );
739        }
740    }
741
742    /// If a sparse update has been scheduled, prepares all GPU resources
743    /// necessary to perform a sparse buffer update, other than updating the
744    /// metadata uniform.
745    pub fn prepare_to_populate_buffers(
746        &mut self,
747        render_device: &RenderDevice,
748        pipeline_cache: &PipelineCache,
749        sparse_buffer_update_jobs: &mut SparseBufferUpdateJobs,
750        sparse_buffer_update_bind_groups: &mut SparseBufferUpdateBindGroups,
751        sparse_buffer_update_pipelines: &SparseBufferUpdatePipelines,
752    ) {
753        // Staging the elements cleared their dirty bits, so a scatter that never gets recorded
754        // would drop those writes for good. Reupload the whole buffer next frame instead, which
755        // rebuilds it from `values` and needs no compute pipeline.
756        let mut retry_with_full_reupload = false;
757
758        if self.sparse_update_scheduled {
759            let scheduled = match (&self.data_buffer, self.metadata_uniform.buffer()) {
760                (Some(data_buffer), Some(metadata_buffer)) => prepare_to_populate_buffers(
761                    self.handle.clone(),
762                    &self.label,
763                    data_buffer,
764                    &mut self.staging_buffers,
765                    metadata_buffer,
766                    render_device,
767                    pipeline_cache,
768                    sparse_buffer_update_jobs,
769                    sparse_buffer_update_bind_groups,
770                    sparse_buffer_update_pipelines,
771                ),
772                _ => {
773                    error!("Buffers should have been created by now");
774                    false
775                }
776            };
777
778            retry_with_full_reupload = !scheduled;
779        }
780
781        // Clear out the staging buffers, now that we know the data is already
782        // on the GPU.
783        self.staging_buffers.source_data.clear();
784        self.staging_buffers.indices.clear();
785
786        // Accumulate rather than assign, so a reupload that `reserve` asked for survives a frame
787        // where `write_buffers` bailed out early on an empty vector.
788        self.needs_full_reupload |= retry_with_full_reupload;
789        self.sparse_update_scheduled = false;
790    }
791}
792
793impl<T> AtomicSparseBufferVec<T>
794where
795    T: AtomicPod + PartialEq,
796{
797    /// Sets the value at the given index, growing the buffer if the index isn't in range.
798    ///
799    /// If the buffer already holds `value` there, this does nothing, leaving the element's dirty
800    /// bit clear so that a sparse update skips it.
801    ///
802    /// Requires `&mut self` because growing reallocates.
803    ///
804    /// Use [`Self::set_if_changed`] when the index is known to be in range and
805    /// only a shared reference is available.
806    pub fn grow_and_set(&mut self, index: u32, value: T) {
807        if self.len() > index {
808            if self.get(index) == value {
809                return;
810            }
811        } else {
812            self.grow(index + 1);
813        }
814        self.set(index, value);
815    }
816
817    /// Sets the value at an index that's already in range, without growing the buffer.
818    ///
819    /// If the buffer already holds `value` there, this does nothing, leaving the element's dirty
820    /// bit clear so that a sparse update skips it.
821    ///
822    /// Like [`Self::set`], this is thread-safe, doesn't require `&mut self`, and panics if the
823    /// index isn't in range.
824    pub fn set_if_changed(&self, index: u32, value: T) {
825        debug_assert!(
826            index < self.len(),
827            "buffer was not grown past index {index}"
828        );
829        if self.get(index) != value {
830            self.set(index, value);
831        }
832    }
833}
834
835pub fn init_sparse_buffer_update_bind_groups(
836    mut commands: Commands,
837    mut specialized_sparse_buffer_update_pipelines: ResMut<
838        SpecializedComputePipelines<SparseBufferUpdatePipelines>,
839    >,
840    pipeline_cache: Res<PipelineCache>,
841    sparse_buffer_update_pipelines: Res<SparseBufferUpdatePipelines>,
842) {
843    let pipeline_id = sparse_buffer_update_pipelines.shader.clone().map(|shader| {
844        specialized_sparse_buffer_update_pipelines.specialize(
845            &pipeline_cache,
846            &sparse_buffer_update_pipelines,
847            shader,
848        )
849    });
850
851    commands.insert_resource(SparseBufferUpdateBindGroups {
852        bind_groups: WeakKeyHashMap::default(),
853        pipeline_id,
854    });
855}
856
857/// Marks elements within the range `old_len..new_len` as dirty, under the
858/// assumption that the vector is being resized from a length of `old_len` to a
859/// length of `new_len`.
860///
861/// This is more efficient than individually marking elements one-by-one. It
862/// also resizes the `summary` and `dirty_bits` bitfields as necessary.
863///
864/// `new_len` must be greater than or equal to `old_len`.
865fn set_dirty_bits_for_vector_growth(
866    old_len: u32,
867    new_len: u32,
868    summary: &mut Vec<AtomicU64>,
869    dirty_bits: &mut Vec<AtomicU64>,
870) {
871    debug_assert!(new_len >= old_len);
872    if new_len == old_len {
873        return;
874    }
875
876    if old_len > 0 {
877        // Compute the index of the bit corresponding to the final existing
878        // element. We're going to set every bit *after* that bit.
879        let old_final_dirty_word_index = (old_len - 1) / BITS_PER_WORD;
880        let old_final_dirty_bit_offset = (old_len - 1) % BITS_PER_WORD;
881        if old_final_dirty_bit_offset < BITS_PER_WORD - 1
882            && let Some(ref mut old_final_atomic_dirty_word) =
883                dirty_bits.get_mut(old_final_dirty_word_index as usize)
884        {
885            // We add one here because we want to set every bit *after*, but not
886            // including, the index we computed above.
887            *old_final_atomic_dirty_word.get_mut() |=
888                !((1u64 << (old_final_dirty_bit_offset + 1)).wrapping_sub(1));
889        }
890
891        // Now set all the blocks from the block corresponding to `old_len - 1`
892        // onward to dirty. Note that this is an inclusive range, because we
893        // want to include the page that `old_len - 1` is on.
894        let old_final_summary_word_index = old_final_dirty_word_index / BITS_PER_WORD;
895        let mut old_final_summary_bit_offset = old_final_dirty_word_index % BITS_PER_WORD;
896        // This is a tricky exception. If `old_len` was precisely aligned on a
897        // block boundary, then we *don't* include the block that `old_len - 1`
898        // is on.
899        if old_final_dirty_bit_offset == BITS_PER_WORD - 1 {
900            old_final_summary_bit_offset += 1;
901        }
902        if old_final_summary_bit_offset < BITS_PER_WORD
903            && let Some(ref mut old_final_atomic_summary_word) =
904                summary.get_mut(old_final_summary_word_index as usize)
905        {
906            // We don't add one to `old_final_summary_bit_offset` here because
907            // we want to include the block that `old_len - 1` is on.
908            *old_final_atomic_summary_word.get_mut() |=
909                !((1u64 << old_final_summary_bit_offset).wrapping_sub(1));
910        }
911    }
912
913    // Add any new summary and dirty words, with all bits set.
914    let new_dirty_word_count = (new_len as usize).div_ceil(BITS_PER_WORD as usize);
915    let new_summary_word_count = new_dirty_word_count.div_ceil(BITS_PER_WORD as usize);
916    summary.resize_with(new_summary_word_count, || AtomicU64::new(u64::MAX));
917    dirty_bits.resize_with(new_dirty_word_count, || AtomicU64::new(u64::MAX));
918
919    // Clear all bits past the last valid element index in `dirty_bits`.
920    let last_dirty_bit_offset = new_len % BITS_PER_WORD;
921    if last_dirty_bit_offset != 0 {
922        let mut final_dirty_word = dirty_bits[new_dirty_word_count - 1].load(Ordering::Relaxed);
923        final_dirty_word &= (1u64 << last_dirty_bit_offset) - 1;
924        dirty_bits[new_dirty_word_count - 1].store(final_dirty_word, Ordering::Relaxed);
925    }
926
927    // Clear all bits past the last valid summary bit in `summary`.
928    let last_summary_bit_offset = new_dirty_word_count % BITS_PER_WORD as usize;
929    if last_summary_bit_offset != 0 {
930        let mut final_summary_word = summary[new_summary_word_count - 1].load(Ordering::Relaxed);
931        final_summary_word &= (1u64 << last_summary_bit_offset) - 1;
932        summary[new_summary_word_count - 1].store(final_summary_word, Ordering::Relaxed);
933    }
934}
935
936/// Marks the given element index as dirty so that we know that we need to
937/// upload it.
938///
939/// This is a separate function so we can unit test it easily (i.e. without the
940/// need of a `RenderDevice`).
941fn note_changed_index(index: u32, summary: &[AtomicU64], dirty_bits: &[AtomicU64]) {
942    let dirty_word_index = index / BITS_PER_WORD;
943    let (summary_word_index, summary_bit_offset) = (
944        dirty_word_index / BITS_PER_WORD,
945        dirty_word_index % BITS_PER_WORD,
946    );
947    summary[summary_word_index as usize].fetch_or(1 << summary_bit_offset, Ordering::Relaxed);
948    let (element_word, element_in_word) = (index / BITS_PER_WORD, index % BITS_PER_WORD);
949    dirty_bits[element_word as usize].fetch_or(1 << element_in_word, Ordering::Relaxed);
950}
951
952/// Returns the total number of bits set in `dirty_bits`, using the given
953/// `summary` to accelerate the count.
954fn count_dirty_elements(summary: &[AtomicU64], dirty_bits: &[AtomicU64]) -> u32 {
955    let mut changed_element_count = 0u32;
956    for (summary_word_index, summary_word) in summary.iter().enumerate() {
957        for summary_bit_offset in BitIter::new(summary_word.load(Ordering::Relaxed)) {
958            let dirty_word_index =
959                summary_word_index * BITS_PER_WORD as usize + summary_bit_offset as usize;
960            let dirty_word = dirty_bits[dirty_word_index].load(Ordering::Relaxed);
961            changed_element_count += dirty_word.count_ones();
962        }
963    }
964
965    changed_element_count
966}
967
968/// Prepares all GPU resources necessary to perform a sparse buffer update,
969/// other than updating the metadata uniform.
970///
971/// This function creates the [`SparseBufferUpdateJob`] and ensures the bind
972/// group and pipeline are up to date.
973///
974/// Returns whether the update was recorded. A caller that has already cleared the dirty bits for
975/// the staged elements has to reupload the whole buffer when this is false, as nothing will scatter
976/// them.
977fn prepare_to_populate_buffers(
978    sparse_buffer_handle: SparseBufferHandle,
979    label: &Arc<str>,
980    data_buffer: &Buffer,
981    staging_buffers: &mut SparseBufferStagingBuffers,
982    metadata_buffer: &Buffer,
983    render_device: &RenderDevice,
984    pipeline_cache: &PipelineCache,
985    sparse_buffer_update_jobs: &mut SparseBufferUpdateJobs,
986    sparse_buffer_update_bind_groups: &mut SparseBufferUpdateBindGroups,
987    sparse_buffer_update_pipelines: &SparseBufferUpdatePipelines,
988) -> bool {
989    let (Some(source_data_staging_buffer), Some(indices_staging_buffer)) = (
990        staging_buffers.source_data.buffer(),
991        staging_buffers.indices.buffer(),
992    ) else {
993        error!("Staging buffers should have been created by now");
994        return false;
995    };
996
997    let Some(bind_group_layout) = &sparse_buffer_update_pipelines.bind_group_layout else {
998        return false;
999    };
1000
1001    // `update_sparse_buffers` can't dispatch anything until the shared scatter pipeline has
1002    // compiled, which takes a few frames from startup
1003    let Some(pipeline_id) = sparse_buffer_update_bind_groups.pipeline_id else {
1004        return false;
1005    };
1006    if pipeline_cache.get_compute_pipeline(pipeline_id).is_none() {
1007        return false;
1008    }
1009
1010    // Record the update job.
1011    sparse_buffer_update_jobs.push(SparseBufferUpdateJob {
1012        sparse_buffer_handle: sparse_buffer_handle.clone(),
1013        updated_element_count: staging_buffers.updated_element_count(),
1014        element_word_size: staging_buffers.element_word_size,
1015    });
1016
1017    // Create the bind group.
1018    let bind_group = render_device.create_bind_group(
1019        Some(&*format!("{} bind group", label)),
1020        &pipeline_cache.get_bind_group_layout(bind_group_layout),
1021        &BindGroupEntries::sequential((
1022            // @group(0) @binding(0) var<storage, read_write> dest_buffer: array<u32>;
1023            data_buffer.as_entire_binding(),
1024            // @group(0) @binding(1) var<storage> src_buffer: array<u32>;
1025            source_data_staging_buffer.as_entire_binding(),
1026            // @group(0) @binding(2) var<storage> indices: array<u32>;
1027            indices_staging_buffer.as_entire_binding(),
1028            // @group(0) @binding(3) var<uniform> metadata:
1029            // SparseBufferUpdateMetadata;
1030            metadata_buffer.as_entire_binding(),
1031        )),
1032    );
1033    sparse_buffer_update_bind_groups.bind_groups.insert(
1034        sparse_buffer_handle,
1035        SparseBufferUpdateBindGroup { bind_group },
1036    );
1037
1038    true
1039}
1040
1041/// Ensures that the backing buffer for an [`AtomicSparseBufferVec`] is present
1042/// on the GPU.
1043///
1044/// The `capacity`, `data_buffer`, and `needs_full_reupload` fields are updated
1045/// to reflect the new buffer.
1046fn reserve(
1047    new_capacity: usize,
1048    capacity: &mut usize,
1049    label: &str,
1050    data_buffer: &mut Option<Buffer>,
1051    buffer_usages: BufferUsages,
1052    needs_full_reupload: &mut bool,
1053    element_size: usize,
1054    render_device: &RenderDevice,
1055) {
1056    // If the buffer is already big enough, do nothing.
1057    if new_capacity == 0 || new_capacity <= *capacity {
1058        return;
1059    }
1060
1061    *capacity = new_capacity;
1062    *data_buffer = Some(render_device.create_buffer(&BufferDescriptor {
1063        label: Some(label),
1064        size: element_size as u64 * new_capacity as u64,
1065        usage: buffer_usages,
1066        mapped_at_creation: false,
1067    }));
1068
1069    // Since we resized the buffer, we need to reupload it.
1070    *needs_full_reupload = true;
1071}
1072
1073impl GpuSparseBufferUpdateMetadata {
1074    /// Returns a new [`GpuSparseBufferUpdateMetadata`] for the given type.
1075    fn new<T>() -> GpuSparseBufferUpdateMetadata {
1076        assert_eq!(size_of::<T>() % 4, 0);
1077        GpuSparseBufferUpdateMetadata {
1078            element_size: (size_of::<T>() / 4) as u32,
1079            updated_element_count: 0,
1080        }
1081    }
1082}
1083
1084/// Iterates over the bits in a single `u64`, from the least significant bit to
1085/// the most significant bit.
1086struct BitIter(u64);
1087
1088impl BitIter {
1089    fn new(bits: u64) -> BitIter {
1090        BitIter(bits)
1091    }
1092}
1093
1094impl Iterator for BitIter {
1095    type Item = u32;
1096
1097    fn next(&mut self) -> Option<Self::Item> {
1098        let trailing_zeros = self.0.trailing_zeros();
1099        if trailing_zeros == 64 {
1100            return None;
1101        }
1102        self.0 &= !(1 << trailing_zeros);
1103        Some(trailing_zeros)
1104    }
1105}
1106
1107/// Calculates the size that a buffer should be in order to balance reallocation
1108/// frequency against memory waste.
1109fn calculate_allocation_size(length: usize) -> usize {
1110    let exponent = (length as f64).log(REALLOCATION_FACTOR).ceil();
1111    let size = REALLOCATION_FACTOR.powf(exponent) as usize;
1112    size.next_multiple_of(REALLOCATION_SIZE_MULTIPLE)
1113}
1114
1115#[cfg(test)]
1116mod tests {
1117    use super::{BitIter, BITS_PER_WORD};
1118    use core::{
1119        iter,
1120        sync::atomic::{AtomicU64, Ordering},
1121    };
1122    use proptest::prelude::proptest;
1123
1124    proptest! {
1125        // Ensures that the `BitIter` is correct.
1126        #[test]
1127        fn bit_iter(bits: u64) {
1128            let bits_reference: Vec<_> = (0u32..64u32).filter(|bit_pos| {
1129                (bits & (1 << bit_pos)) != 0
1130            }).collect();
1131            let bits_iter_results: Vec<_> = BitIter::new(bits).collect();
1132            assert_eq!(bits_iter_results, bits_reference);
1133        }
1134
1135        // Ensures that `set_dirty_bits_for_vector_growth` is correct.
1136        #[test]
1137        fn set_dirty_bits_for_vector_growth(
1138            old_len in 0u32..16384u32,
1139            new_element_count in 0u32..16384u32,
1140            start_dirty: bool
1141        ) {
1142            // Initialize the dirty bits.
1143            let new_len = old_len + new_element_count;
1144            let mut dirty_bits: Vec<_> = iter::repeat_with(|| {
1145                AtomicU64::new(0)
1146            }).take(old_len.div_ceil(BITS_PER_WORD) as usize).collect();
1147            if start_dirty {
1148                for bit_index in 0..old_len {
1149                    let word_index = bit_index as usize / 64;
1150                    dirty_bits[word_index].fetch_or(1 << (bit_index % 64), Ordering::Relaxed);
1151                }
1152            }
1153
1154            // Initialize the summary.
1155            let mut summary: Vec<_> = iter::repeat_with(|| {
1156                AtomicU64::new(0)
1157            }).take(dirty_bits.len().div_ceil(BITS_PER_WORD as usize)).collect();
1158            for (word_index, word) in dirty_bits.iter().enumerate() {
1159                if word.load(Ordering::Relaxed) != 0 {
1160                    summary[word_index / 64].fetch_or(1 << (word_index % 64), Ordering::Relaxed);
1161                }
1162            }
1163
1164            super::set_dirty_bits_for_vector_growth(
1165                old_len,
1166                new_len,
1167                &mut summary,
1168                &mut dirty_bits
1169            );
1170
1171            // Check dirty flags for elements.
1172            // Bits in the range [0, old_len) should be unchanged.
1173            for element_index in 0..old_len {
1174                check_element_dirty(element_index, &dirty_bits, start_dirty);
1175            }
1176            // Bits in the range [old_len, new_len) should be dirty.
1177            for element_index in old_len..new_len {
1178                check_element_dirty(element_index, &dirty_bits, true);
1179            }
1180            // Bits in the range [new_len, end) should be clean.
1181            for element_index in (new_len..).take_while(|element_index| {
1182                element_index % BITS_PER_WORD != 0
1183            }) {
1184                check_element_dirty(element_index, &dirty_bits, false);
1185            }
1186
1187            // Check the dirty flag for each block to ensure that it precisely
1188            // corresponds to the logical *or* of the dirty flags for all
1189            // elements in that block.
1190            for (dirty_word_index, atomic_dirty_word) in dirty_bits.iter().enumerate() {
1191                // Determine the range of elements that this block encompasses.
1192                let element_start = dirty_word_index * BITS_PER_WORD as usize;
1193                let element_end =
1194                    ((dirty_word_index + 1) * BITS_PER_WORD as usize).min(new_len as usize);
1195                assert!(element_start <= element_end);
1196
1197                // Determine whether the block should be dirty.
1198                let dirty_word = atomic_dirty_word.load(Ordering::Relaxed);
1199                let block_is_dirty = (element_start..element_end).any(|element_index| {
1200                    (dirty_word & (1 << (element_index % (BITS_PER_WORD as usize)))) != 0
1201                });
1202
1203                // Check to make sure that the block has the correct dirty state.
1204                check_block_dirty(dirty_word_index as u32, &summary, block_is_dirty);
1205            }
1206
1207            // Make sure that all dirty block bits past the last valid dirty
1208            // block bit are clear.
1209            if !summary.is_empty() {
1210                let last_summary_word_index = summary.len();
1211                let last_padding_block_index = last_summary_word_index * BITS_PER_WORD as usize;
1212                let last_dirty_word_index = (new_len as usize - 1) / BITS_PER_WORD as usize;
1213                for padding_block_index in (last_dirty_word_index + 1)..last_padding_block_index {
1214                    check_block_dirty(padding_block_index as u32, &summary, false);
1215                }
1216            }
1217
1218            // Asserts that the dirty status of the element at `element_index`
1219            // matches the expected dirty status.
1220            fn check_element_dirty(
1221                element_index: u32,
1222                dirty_bits: &[AtomicU64],
1223                expect_dirty: bool
1224            ) {
1225                let expected = if expect_dirty { 1 } else { 0 };
1226
1227                let dirty_word_index = element_index / BITS_PER_WORD;
1228                let dirty_bit_offset = element_index % BITS_PER_WORD;
1229                let dirty_word = dirty_bits[dirty_word_index as usize].load(Ordering::Relaxed);
1230                assert_eq!((dirty_word >> dirty_bit_offset) & 1, expected);
1231            }
1232
1233            // Asserts that the dirty status of the block at `block_index`
1234            // matches the expected dirty status in the summary.
1235            //
1236            // This is actually the same code as `ensure_elements_dirty`, but is
1237            // duplicated for clarity.
1238            fn check_block_dirty(block_index: u32, summary: &[AtomicU64], expect_dirty: bool) {
1239                let expected = if expect_dirty { 1 } else { 0 };
1240
1241                let summary_word_index = block_index / BITS_PER_WORD;
1242                let summary_bit_offset = block_index % BITS_PER_WORD;
1243                let summary_word = summary[summary_word_index as usize].load(Ordering::Relaxed);
1244                assert_eq!((summary_word >> summary_bit_offset) & 1, expected);
1245            }
1246        }
1247
1248        // Ensures that the population-count-based `count_dirty_elements` code
1249        // correctly calculates the number of changed elements.
1250        //
1251        // The input `dirty_flags` is an array of booleans, one for each
1252        // element, in which `false` represents "not changed" and `true`
1253        // represents "changed".
1254        #[test]
1255        fn dirty_element_count(dirty_flags: Vec<bool>) {
1256            let dirty_word_count = dirty_flags.len().div_ceil(BITS_PER_WORD as usize);
1257            let summary_word_count = dirty_word_count.div_ceil(BITS_PER_WORD as usize);
1258
1259            let dirty_bits: Vec<_> = (0..dirty_word_count).map(|_| AtomicU64::new(0)).collect();
1260            let summary: Vec<_> = (0..summary_word_count).map(|_| AtomicU64::new(0)).collect();
1261
1262            let mut true_dirty_element_count = 0;
1263            for (element_index, _) in dirty_flags.iter().enumerate().filter(|(_, element)| **element) {
1264                super::note_changed_index(element_index as u32, &summary, &dirty_bits);
1265                true_dirty_element_count += 1;
1266            }
1267
1268            let calculated_dirty_element_count = super::count_dirty_elements(
1269                &summary,
1270                &dirty_bits
1271            );
1272            assert_eq!(calculated_dirty_element_count, true_dirty_element_count);
1273        }
1274    }
1275}