use alloc::sync::{Arc, Weak};
use core::{
slice,
sync::atomic::{AtomicU64, Ordering},
};
use bevy_app::{App, Plugin};
use bevy_asset::{embedded_asset, load_embedded_asset, AssetServer, Handle};
use bevy_derive::{Deref, DerefMut};
use bevy_ecs::{
resource::Resource,
schedule::IntoScheduleConfigs as _,
system::{Commands, Res, ResMut},
};
use bevy_log::{debug, error, info};
use bevy_material::{
bind_group_layout_entries::{
binding_types::{storage_buffer, storage_buffer_read_only, uniform_buffer},
BindGroupLayoutEntries,
},
descriptor::{BindGroupLayoutDescriptor, CachedComputePipelineId, ComputePipelineDescriptor},
};
use bevy_shader::Shader;
use bytemuck::{Pod, Zeroable};
use encase::ShaderType;
use weak_table::WeakKeyHashMap;
use wgpu::{BufferDescriptor, BufferUsages, ComputePassDescriptor, ShaderStages};
use crate::{
diagnostic::RecordDiagnostics as _,
init_gpu_resource,
render_resource::{
AtomicPod, BindGroup, BindGroupEntries, Buffer, PipelineCache, RawBufferVec,
SpecializedComputePipeline, SpecializedComputePipelines, UniformBuffer,
},
renderer::{RenderContext, RenderDevice, RenderGraph, RenderGraphSystems, RenderQueue},
ExtractSchedule, GpuResourceAppExt, RenderApp, RenderStartup,
};
pub struct SparseBufferPlugin;
impl Plugin for SparseBufferPlugin {
fn build(&self, app: &mut App) {
embedded_asset!(app, "sparse_buffer_update.wesl");
}
fn finish(&self, app: &mut App) {
let Some(render_app) = app.get_sub_app_mut(RenderApp) else {
return;
};
render_app
.init_gpu_resource::<SparseBufferUpdateJobs>()
.init_gpu_resource::<SpecializedComputePipelines<SparseBufferUpdatePipelines>>()
.add_systems(ExtractSchedule, clear_sparse_buffer_jobs)
.add_systems(
RenderStartup,
(
init_sparse_buffer_update_pipelines,
init_sparse_buffer_update_bind_groups.after(init_gpu_resource::<SpecializedComputePipelines<SparseBufferUpdatePipelines>>),
)
.chain(),
)
.add_systems(
RenderGraph,
update_sparse_buffers.in_set(RenderGraphSystems::Begin),
);
}
}
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug, Deref, DerefMut)]
pub struct SparseBufferId(pub u64);
pub type SparseBufferHandle = Arc<SparseBufferId>;
static NEXT_SPARSE_BUFFER_ID: AtomicU64 = AtomicU64::new(0);
const SPARSE_BUFFER_UPDATE_WORKGROUP_SIZE: u32 = 256;
const SPARSE_UPLOAD_THRESHOLD: f64 = 0.15;
const MAX_WORKGROUPS: u32 = 65535;
const REALLOCATION_FACTOR: f64 = 1.5;
const REALLOCATION_SIZE_MULTIPLE: usize = 256;
const BITS_PER_WORD: u32 = 64;
#[derive(Resource)]
pub struct SparseBufferUpdatePipelines {
bind_group_layout: Option<BindGroupLayoutDescriptor>,
shader: Option<Handle<Shader>>,
}
#[derive(Resource)]
pub struct SparseBufferUpdateBindGroups {
bind_groups: WeakKeyHashMap<Weak<SparseBufferId>, SparseBufferUpdateBindGroup>,
pipeline_id: Option<CachedComputePipelineId>,
}
pub struct SparseBufferUpdateBindGroup {
bind_group: BindGroup,
}
#[derive(Resource, Default, Deref, DerefMut)]
pub struct SparseBufferUpdateJobs(pub Vec<SparseBufferUpdateJob>);
pub struct SparseBufferUpdateJob {
sparse_buffer_handle: SparseBufferHandle,
updated_element_count: u32,
element_word_size: u32,
}
impl SparseBufferUpdateJob {
fn words_to_update(&self) -> u32 {
self.updated_element_count * self.element_word_size
}
fn workgroup_count(&self) -> u32 {
self.words_to_update()
.div_ceil(SPARSE_BUFFER_UPDATE_WORKGROUP_SIZE)
}
}
#[derive(Clone, Copy, Default, ShaderType, Pod, Zeroable)]
#[repr(C)]
struct GpuSparseBufferUpdateMetadata {
element_size: u32,
updated_element_count: u32,
}
pub fn update_sparse_buffers(
sparse_buffer_update_jobs: Res<SparseBufferUpdateJobs>,
sparse_buffer_update_bind_groups: Res<SparseBufferUpdateBindGroups>,
pipeline_cache: Res<PipelineCache>,
mut render_context: RenderContext,
) {
if sparse_buffer_update_jobs.is_empty() {
return;
}
let Some(pipeline_id) = sparse_buffer_update_bind_groups.pipeline_id else {
return;
};
let Some(compute_pipeline) = pipeline_cache.get_compute_pipeline(pipeline_id) else {
return;
};
let diagnostics = render_context.diagnostic_recorder();
let diagnostics = diagnostics.as_deref();
let command_encoder = render_context.command_encoder();
let mut sparse_buffer_update_pass =
command_encoder.begin_compute_pass(&ComputePassDescriptor {
label: Some("sparse buffer updates"),
timestamp_writes: None,
});
sparse_buffer_update_pass.set_pipeline(compute_pipeline);
let time_span = diagnostics.time_span(&mut sparse_buffer_update_pass, "sparse buffer updates");
for sparse_buffer_update_job in sparse_buffer_update_jobs.iter() {
let Some(sparse_buffer_update_bind_group) = sparse_buffer_update_bind_groups
.bind_groups
.get(&sparse_buffer_update_job.sparse_buffer_handle)
else {
continue;
};
sparse_buffer_update_pass.set_bind_group(
0,
&sparse_buffer_update_bind_group.bind_group,
&[],
);
sparse_buffer_update_pass.dispatch_workgroups(
sparse_buffer_update_job.workgroup_count(),
1,
1,
);
}
time_span.end(&mut sparse_buffer_update_pass);
}
fn clear_sparse_buffer_jobs(mut sparse_buffer_update_jobs: ResMut<SparseBufferUpdateJobs>) {
sparse_buffer_update_jobs.clear();
}
pub fn init_sparse_buffer_update_pipelines(
mut commands: Commands,
asset_server: Res<AssetServer>,
render_device: Res<RenderDevice>,
) {
let limit = render_device.limits().max_storage_buffers_per_shader_stage;
if limit < 3 {
info!(
"Sparse buffer updates disabled. RenderDevice lacks support: max_storage_buffers_per_shader_stage ({}) < 3.",
limit
);
commands.insert_resource(SparseBufferUpdatePipelines {
bind_group_layout: None,
shader: None,
});
return;
}
let bind_group_layout = BindGroupLayoutDescriptor::new(
"sparse buffer update bind group layout",
&BindGroupLayoutEntries::sequential(
ShaderStages::COMPUTE,
(
storage_buffer::<u32>(false),
storage_buffer_read_only::<u32>(false),
storage_buffer_read_only::<u32>(false),
uniform_buffer::<GpuSparseBufferUpdateMetadata>(false),
),
),
);
commands.insert_resource(SparseBufferUpdatePipelines {
bind_group_layout: Some(bind_group_layout),
shader: Some(load_embedded_asset!(
asset_server.as_ref(),
"sparse_buffer_update.wesl"
)),
});
}
impl SpecializedComputePipeline for SparseBufferUpdatePipelines {
type Key = Handle<Shader>;
fn specialize(&self, key: Self::Key) -> ComputePipelineDescriptor {
ComputePipelineDescriptor {
label: Some("sparse buffer update pipeline".into()),
layout: self.bind_group_layout.clone().into_iter().collect(),
shader: key,
shader_defs: vec![],
..ComputePipelineDescriptor::default()
}
}
}
struct SparseBufferStagingBuffers {
source_data: RawBufferVec<u32>,
indices: RawBufferVec<u32>,
element_word_size: u32,
}
impl SparseBufferStagingBuffers {
fn new(label: &str, element_word_size: u32) -> SparseBufferStagingBuffers {
let mut source_data_buffer =
RawBufferVec::new(BufferUsages::COPY_DST | BufferUsages::STORAGE);
source_data_buffer.set_label(Some(&*format!("{} staging buffer", label)));
let mut indices_buffer = RawBufferVec::new(BufferUsages::COPY_DST | BufferUsages::STORAGE);
indices_buffer.set_label(Some(&*format!("{} index buffer", label)));
SparseBufferStagingBuffers {
source_data: source_data_buffer,
indices: indices_buffer,
element_word_size,
}
}
fn updated_element_count(&self) -> u32 {
(self.source_data.len() / self.element_word_size as usize) as u32
}
fn write_buffers(
&mut self,
metadata_uniform: &mut UniformBuffer<GpuSparseBufferUpdateMetadata>,
render_device: &RenderDevice,
render_queue: &RenderQueue,
) {
metadata_uniform.get_mut().updated_element_count = self.updated_element_count();
metadata_uniform.write_buffer(render_device, render_queue);
self.source_data.write_buffer(render_device, render_queue);
self.indices.write_buffer(render_device, render_queue);
}
fn should_perform_full_reupload(
&self,
changed_element_count: u32,
buffer_length: usize,
) -> bool {
let total_changed_word_count = changed_element_count * self.element_word_size;
if total_changed_word_count > MAX_WORKGROUPS * SPARSE_BUFFER_UPDATE_WORKGROUP_SIZE {
return true;
}
let sparse_upload_fraction = changed_element_count as f64 / buffer_length as f64;
let should_reupload = sparse_upload_fraction > SPARSE_UPLOAD_THRESHOLD;
debug!(
"Sparse buffer changed {}/{} elements ({:.3}, threshold {:.3}): performing {} upload",
changed_element_count,
buffer_length,
sparse_upload_fraction,
SPARSE_UPLOAD_THRESHOLD,
if should_reupload { "full" } else { "sparse" }
);
should_reupload
}
}
pub struct AtomicSparseBufferVec<T>
where
T: AtomicPod,
{
handle: SparseBufferHandle,
values: Vec<T::Blob>,
data_buffer: Option<Buffer>,
staging_buffers: SparseBufferStagingBuffers,
metadata_uniform: UniformBuffer<GpuSparseBufferUpdateMetadata>,
capacity: usize,
buffer_usages: BufferUsages,
label: Arc<str>,
summary: Vec<AtomicU64>,
dirty_bits: Vec<AtomicU64>,
needs_full_reupload: bool,
sparse_update_scheduled: bool,
}
impl<T> AtomicSparseBufferVec<T>
where
T: AtomicPod,
{
pub fn new(buffer_usages: BufferUsages, label: Arc<str>) -> Self {
debug_assert_eq!(size_of::<T>() % 4, 0);
let element_word_size = size_of::<T>() / 4;
let id = Arc::new(SparseBufferId(
NEXT_SPARSE_BUFFER_ID.fetch_add(1, Ordering::Relaxed),
));
Self {
handle: id,
values: vec![],
data_buffer: None,
staging_buffers: SparseBufferStagingBuffers::new(&label, element_word_size as u32),
metadata_uniform: UniformBuffer::from(GpuSparseBufferUpdateMetadata::new::<T>()),
capacity: 0,
buffer_usages: buffer_usages | BufferUsages::COPY_DST,
label,
summary: vec![],
dirty_bits: vec![],
needs_full_reupload: false,
sparse_update_scheduled: false,
}
}
pub fn len(&self) -> u32 {
self.values.len() as u32
}
pub fn is_empty(&self) -> bool {
self.values.is_empty()
}
pub fn buffer(&self) -> Option<&Buffer> {
self.data_buffer.as_ref()
}
pub fn clear(&mut self) {
self.values.clear();
self.summary.clear();
self.dirty_bits.clear();
}
pub fn get(&self, index: u32) -> T {
T::read_from_blob(&self.values[index as usize])
}
pub fn set(&self, index: u32, value: T) {
value.write_to_blob(&self.values[index as usize]);
self.note_changed_index(index);
}
pub fn push(&mut self, value: T) -> u32 {
let index = self.values.len() as u32;
self.values.push(T::Blob::default());
value.write_to_blob(&self.values[index as usize]);
let dirty_word_index = (index / BITS_PER_WORD) as usize;
let summary_word_index = dirty_word_index / BITS_PER_WORD as usize;
while self.summary.len() < summary_word_index + 1 {
self.summary.push(AtomicU64::default());
}
while self.dirty_bits.len() < dirty_word_index + 1 {
self.dirty_bits.push(AtomicU64::default());
}
self.note_changed_index(index);
index
}
fn note_changed_index(&self, index: u32) {
note_changed_index(index, &self.summary, &self.dirty_bits);
}
pub fn reserve(&mut self, new_capacity: usize, render_device: &RenderDevice) {
reserve(
new_capacity,
&mut self.capacity,
&self.label,
&mut self.data_buffer,
self.buffer_usages,
&mut self.needs_full_reupload,
size_of::<T::Blob>(),
render_device,
);
}
pub fn grow(&mut self, new_len: u32) {
let old_len = self.values.len() as u32;
if old_len >= new_len {
return;
}
self.values.reserve(new_len as usize - old_len as usize);
self.values.resize_with(new_len as usize, T::Blob::default);
set_dirty_bits_for_vector_growth(old_len, new_len, &mut self.summary, &mut self.dirty_bits);
}
pub fn write_buffers(&mut self, render_device: &RenderDevice, render_queue: &RenderQueue) {
if self.values.is_empty() {
return;
}
let good_size = calculate_allocation_size(self.values.len());
self.reserve(good_size, render_device);
if self.should_perform_full_reupload(render_device) {
self.write_entire_buffer(render_queue);
} else {
self.prepare_sparse_upload(render_device, render_queue);
}
}
fn should_perform_full_reupload(&self, render_device: &RenderDevice) -> bool {
if self.needs_full_reupload
|| render_device.limits().max_storage_buffers_per_shader_stage < 3
{
return true;
}
let changed_element_count = count_dirty_elements(&self.summary, &self.dirty_bits);
self.staging_buffers
.should_perform_full_reupload(changed_element_count, self.values.len())
}
fn write_entire_buffer(&mut self, render_queue: &RenderQueue) {
let Some(ref mut data_buffer) = self.data_buffer else {
error!("Dirty sparse buffer should have created a data buffer by now");
return;
};
unsafe {
render_queue.write_buffer(
data_buffer,
0,
slice::from_raw_parts(
self.values.as_ptr().cast::<u8>(),
self.values.len() * size_of::<T::Blob>(),
),
);
}
for atomic_summary_word in self.summary.iter() {
atomic_summary_word.store(0, Ordering::Relaxed);
}
for atomic_dirty_word in self.dirty_bits.iter() {
atomic_dirty_word.store(0, Ordering::Relaxed);
}
self.sparse_update_scheduled = false;
self.needs_full_reupload = false;
}
fn prepare_sparse_upload(&mut self, render_device: &RenderDevice, render_queue: &RenderQueue) {
for (summary_word_index, atomic_summary_word) in self.summary.iter().enumerate() {
let summary_word = atomic_summary_word.load(Ordering::Relaxed);
for summary_bit_offset in BitIter::new(summary_word) {
let dirty_word_index =
summary_word_index * BITS_PER_WORD as usize + summary_bit_offset as usize;
let atomic_dirty_word = &self.dirty_bits[dirty_word_index];
let dirty_word = atomic_dirty_word.load(Ordering::Relaxed);
for dirty_bit_offset in BitIter::new(dirty_word) {
let element_index =
dirty_word_index * BITS_PER_WORD as usize + dirty_bit_offset as usize;
let Some(blob) = self.values.get(element_index) else {
continue;
};
self.staging_buffers.indices.push(element_index as u32);
let value = T::read_from_blob(blob);
self.staging_buffers
.source_data
.extend(bytemuck::cast_slice(&[value]).iter().copied());
debug_assert_eq!(
self.staging_buffers.source_data.len()
% self.staging_buffers.element_word_size as usize,
0
);
}
atomic_dirty_word.store(0, Ordering::Relaxed);
}
atomic_summary_word.store(0, Ordering::Relaxed);
}
self.sparse_update_scheduled = !self.staging_buffers.source_data.is_empty();
if self.sparse_update_scheduled {
self.staging_buffers.write_buffers(
&mut self.metadata_uniform,
render_device,
render_queue,
);
}
}
pub fn prepare_to_populate_buffers(
&mut self,
render_device: &RenderDevice,
pipeline_cache: &PipelineCache,
sparse_buffer_update_jobs: &mut SparseBufferUpdateJobs,
sparse_buffer_update_bind_groups: &mut SparseBufferUpdateBindGroups,
sparse_buffer_update_pipelines: &SparseBufferUpdatePipelines,
) {
let mut retry_with_full_reupload = false;
if self.sparse_update_scheduled {
let scheduled = match (&self.data_buffer, self.metadata_uniform.buffer()) {
(Some(data_buffer), Some(metadata_buffer)) => prepare_to_populate_buffers(
self.handle.clone(),
&self.label,
data_buffer,
&mut self.staging_buffers,
metadata_buffer,
render_device,
pipeline_cache,
sparse_buffer_update_jobs,
sparse_buffer_update_bind_groups,
sparse_buffer_update_pipelines,
),
_ => {
error!("Buffers should have been created by now");
false
}
};
retry_with_full_reupload = !scheduled;
}
self.staging_buffers.source_data.clear();
self.staging_buffers.indices.clear();
self.needs_full_reupload |= retry_with_full_reupload;
self.sparse_update_scheduled = false;
}
}
impl<T> AtomicSparseBufferVec<T>
where
T: AtomicPod + PartialEq,
{
pub fn grow_and_set(&mut self, index: u32, value: T) {
if self.len() > index {
if self.get(index) == value {
return;
}
} else {
self.grow(index + 1);
}
self.set(index, value);
}
pub fn set_if_changed(&self, index: u32, value: T) {
debug_assert!(
index < self.len(),
"buffer was not grown past index {index}"
);
if self.get(index) != value {
self.set(index, value);
}
}
}
pub fn init_sparse_buffer_update_bind_groups(
mut commands: Commands,
mut specialized_sparse_buffer_update_pipelines: ResMut<
SpecializedComputePipelines<SparseBufferUpdatePipelines>,
>,
pipeline_cache: Res<PipelineCache>,
sparse_buffer_update_pipelines: Res<SparseBufferUpdatePipelines>,
) {
let pipeline_id = sparse_buffer_update_pipelines.shader.clone().map(|shader| {
specialized_sparse_buffer_update_pipelines.specialize(
&pipeline_cache,
&sparse_buffer_update_pipelines,
shader,
)
});
commands.insert_resource(SparseBufferUpdateBindGroups {
bind_groups: WeakKeyHashMap::default(),
pipeline_id,
});
}
fn set_dirty_bits_for_vector_growth(
old_len: u32,
new_len: u32,
summary: &mut Vec<AtomicU64>,
dirty_bits: &mut Vec<AtomicU64>,
) {
debug_assert!(new_len >= old_len);
if new_len == old_len {
return;
}
if old_len > 0 {
let old_final_dirty_word_index = (old_len - 1) / BITS_PER_WORD;
let old_final_dirty_bit_offset = (old_len - 1) % BITS_PER_WORD;
if old_final_dirty_bit_offset < BITS_PER_WORD - 1
&& let Some(ref mut old_final_atomic_dirty_word) =
dirty_bits.get_mut(old_final_dirty_word_index as usize)
{
*old_final_atomic_dirty_word.get_mut() |=
!((1u64 << (old_final_dirty_bit_offset + 1)).wrapping_sub(1));
}
let old_final_summary_word_index = old_final_dirty_word_index / BITS_PER_WORD;
let mut old_final_summary_bit_offset = old_final_dirty_word_index % BITS_PER_WORD;
if old_final_dirty_bit_offset == BITS_PER_WORD - 1 {
old_final_summary_bit_offset += 1;
}
if old_final_summary_bit_offset < BITS_PER_WORD
&& let Some(ref mut old_final_atomic_summary_word) =
summary.get_mut(old_final_summary_word_index as usize)
{
*old_final_atomic_summary_word.get_mut() |=
!((1u64 << old_final_summary_bit_offset).wrapping_sub(1));
}
}
let new_dirty_word_count = (new_len as usize).div_ceil(BITS_PER_WORD as usize);
let new_summary_word_count = new_dirty_word_count.div_ceil(BITS_PER_WORD as usize);
summary.resize_with(new_summary_word_count, || AtomicU64::new(u64::MAX));
dirty_bits.resize_with(new_dirty_word_count, || AtomicU64::new(u64::MAX));
let last_dirty_bit_offset = new_len % BITS_PER_WORD;
if last_dirty_bit_offset != 0 {
let mut final_dirty_word = dirty_bits[new_dirty_word_count - 1].load(Ordering::Relaxed);
final_dirty_word &= (1u64 << last_dirty_bit_offset) - 1;
dirty_bits[new_dirty_word_count - 1].store(final_dirty_word, Ordering::Relaxed);
}
let last_summary_bit_offset = new_dirty_word_count % BITS_PER_WORD as usize;
if last_summary_bit_offset != 0 {
let mut final_summary_word = summary[new_summary_word_count - 1].load(Ordering::Relaxed);
final_summary_word &= (1u64 << last_summary_bit_offset) - 1;
summary[new_summary_word_count - 1].store(final_summary_word, Ordering::Relaxed);
}
}
fn note_changed_index(index: u32, summary: &[AtomicU64], dirty_bits: &[AtomicU64]) {
let dirty_word_index = index / BITS_PER_WORD;
let (summary_word_index, summary_bit_offset) = (
dirty_word_index / BITS_PER_WORD,
dirty_word_index % BITS_PER_WORD,
);
summary[summary_word_index as usize].fetch_or(1 << summary_bit_offset, Ordering::Relaxed);
let (element_word, element_in_word) = (index / BITS_PER_WORD, index % BITS_PER_WORD);
dirty_bits[element_word as usize].fetch_or(1 << element_in_word, Ordering::Relaxed);
}
fn count_dirty_elements(summary: &[AtomicU64], dirty_bits: &[AtomicU64]) -> u32 {
let mut changed_element_count = 0u32;
for (summary_word_index, summary_word) in summary.iter().enumerate() {
for summary_bit_offset in BitIter::new(summary_word.load(Ordering::Relaxed)) {
let dirty_word_index =
summary_word_index * BITS_PER_WORD as usize + summary_bit_offset as usize;
let dirty_word = dirty_bits[dirty_word_index].load(Ordering::Relaxed);
changed_element_count += dirty_word.count_ones();
}
}
changed_element_count
}
fn prepare_to_populate_buffers(
sparse_buffer_handle: SparseBufferHandle,
label: &Arc<str>,
data_buffer: &Buffer,
staging_buffers: &mut SparseBufferStagingBuffers,
metadata_buffer: &Buffer,
render_device: &RenderDevice,
pipeline_cache: &PipelineCache,
sparse_buffer_update_jobs: &mut SparseBufferUpdateJobs,
sparse_buffer_update_bind_groups: &mut SparseBufferUpdateBindGroups,
sparse_buffer_update_pipelines: &SparseBufferUpdatePipelines,
) -> bool {
let (Some(source_data_staging_buffer), Some(indices_staging_buffer)) = (
staging_buffers.source_data.buffer(),
staging_buffers.indices.buffer(),
) else {
error!("Staging buffers should have been created by now");
return false;
};
let Some(bind_group_layout) = &sparse_buffer_update_pipelines.bind_group_layout else {
return false;
};
let Some(pipeline_id) = sparse_buffer_update_bind_groups.pipeline_id else {
return false;
};
if pipeline_cache.get_compute_pipeline(pipeline_id).is_none() {
return false;
}
sparse_buffer_update_jobs.push(SparseBufferUpdateJob {
sparse_buffer_handle: sparse_buffer_handle.clone(),
updated_element_count: staging_buffers.updated_element_count(),
element_word_size: staging_buffers.element_word_size,
});
let bind_group = render_device.create_bind_group(
Some(&*format!("{} bind group", label)),
&pipeline_cache.get_bind_group_layout(bind_group_layout),
&BindGroupEntries::sequential((
data_buffer.as_entire_binding(),
source_data_staging_buffer.as_entire_binding(),
indices_staging_buffer.as_entire_binding(),
metadata_buffer.as_entire_binding(),
)),
);
sparse_buffer_update_bind_groups.bind_groups.insert(
sparse_buffer_handle,
SparseBufferUpdateBindGroup { bind_group },
);
true
}
fn reserve(
new_capacity: usize,
capacity: &mut usize,
label: &str,
data_buffer: &mut Option<Buffer>,
buffer_usages: BufferUsages,
needs_full_reupload: &mut bool,
element_size: usize,
render_device: &RenderDevice,
) {
if new_capacity == 0 || new_capacity <= *capacity {
return;
}
*capacity = new_capacity;
*data_buffer = Some(render_device.create_buffer(&BufferDescriptor {
label: Some(label),
size: element_size as u64 * new_capacity as u64,
usage: buffer_usages,
mapped_at_creation: false,
}));
*needs_full_reupload = true;
}
impl GpuSparseBufferUpdateMetadata {
fn new<T>() -> GpuSparseBufferUpdateMetadata {
assert_eq!(size_of::<T>() % 4, 0);
GpuSparseBufferUpdateMetadata {
element_size: (size_of::<T>() / 4) as u32,
updated_element_count: 0,
}
}
}
struct BitIter(u64);
impl BitIter {
fn new(bits: u64) -> BitIter {
BitIter(bits)
}
}
impl Iterator for BitIter {
type Item = u32;
fn next(&mut self) -> Option<Self::Item> {
let trailing_zeros = self.0.trailing_zeros();
if trailing_zeros == 64 {
return None;
}
self.0 &= !(1 << trailing_zeros);
Some(trailing_zeros)
}
}
fn calculate_allocation_size(length: usize) -> usize {
let exponent = (length as f64).log(REALLOCATION_FACTOR).ceil();
let size = REALLOCATION_FACTOR.powf(exponent) as usize;
size.next_multiple_of(REALLOCATION_SIZE_MULTIPLE)
}
#[cfg(test)]
mod tests {
use super::{BitIter, BITS_PER_WORD};
use core::{
iter,
sync::atomic::{AtomicU64, Ordering},
};
use proptest::prelude::proptest;
proptest! {
#[test]
fn bit_iter(bits: u64) {
let bits_reference: Vec<_> = (0u32..64u32).filter(|bit_pos| {
(bits & (1 << bit_pos)) != 0
}).collect();
let bits_iter_results: Vec<_> = BitIter::new(bits).collect();
assert_eq!(bits_iter_results, bits_reference);
}
#[test]
fn set_dirty_bits_for_vector_growth(
old_len in 0u32..16384u32,
new_element_count in 0u32..16384u32,
start_dirty: bool
) {
let new_len = old_len + new_element_count;
let mut dirty_bits: Vec<_> = iter::repeat_with(|| {
AtomicU64::new(0)
}).take(old_len.div_ceil(BITS_PER_WORD) as usize).collect();
if start_dirty {
for bit_index in 0..old_len {
let word_index = bit_index as usize / 64;
dirty_bits[word_index].fetch_or(1 << (bit_index % 64), Ordering::Relaxed);
}
}
let mut summary: Vec<_> = iter::repeat_with(|| {
AtomicU64::new(0)
}).take(dirty_bits.len().div_ceil(BITS_PER_WORD as usize)).collect();
for (word_index, word) in dirty_bits.iter().enumerate() {
if word.load(Ordering::Relaxed) != 0 {
summary[word_index / 64].fetch_or(1 << (word_index % 64), Ordering::Relaxed);
}
}
super::set_dirty_bits_for_vector_growth(
old_len,
new_len,
&mut summary,
&mut dirty_bits
);
for element_index in 0..old_len {
check_element_dirty(element_index, &dirty_bits, start_dirty);
}
for element_index in old_len..new_len {
check_element_dirty(element_index, &dirty_bits, true);
}
for element_index in (new_len..).take_while(|element_index| {
element_index % BITS_PER_WORD != 0
}) {
check_element_dirty(element_index, &dirty_bits, false);
}
for (dirty_word_index, atomic_dirty_word) in dirty_bits.iter().enumerate() {
let element_start = dirty_word_index * BITS_PER_WORD as usize;
let element_end =
((dirty_word_index + 1) * BITS_PER_WORD as usize).min(new_len as usize);
assert!(element_start <= element_end);
let dirty_word = atomic_dirty_word.load(Ordering::Relaxed);
let block_is_dirty = (element_start..element_end).any(|element_index| {
(dirty_word & (1 << (element_index % (BITS_PER_WORD as usize)))) != 0
});
check_block_dirty(dirty_word_index as u32, &summary, block_is_dirty);
}
if !summary.is_empty() {
let last_summary_word_index = summary.len();
let last_padding_block_index = last_summary_word_index * BITS_PER_WORD as usize;
let last_dirty_word_index = (new_len as usize - 1) / BITS_PER_WORD as usize;
for padding_block_index in (last_dirty_word_index + 1)..last_padding_block_index {
check_block_dirty(padding_block_index as u32, &summary, false);
}
}
fn check_element_dirty(
element_index: u32,
dirty_bits: &[AtomicU64],
expect_dirty: bool
) {
let expected = if expect_dirty { 1 } else { 0 };
let dirty_word_index = element_index / BITS_PER_WORD;
let dirty_bit_offset = element_index % BITS_PER_WORD;
let dirty_word = dirty_bits[dirty_word_index as usize].load(Ordering::Relaxed);
assert_eq!((dirty_word >> dirty_bit_offset) & 1, expected);
}
fn check_block_dirty(block_index: u32, summary: &[AtomicU64], expect_dirty: bool) {
let expected = if expect_dirty { 1 } else { 0 };
let summary_word_index = block_index / BITS_PER_WORD;
let summary_bit_offset = block_index % BITS_PER_WORD;
let summary_word = summary[summary_word_index as usize].load(Ordering::Relaxed);
assert_eq!((summary_word >> summary_bit_offset) & 1, expected);
}
}
#[test]
fn dirty_element_count(dirty_flags: Vec<bool>) {
let dirty_word_count = dirty_flags.len().div_ceil(BITS_PER_WORD as usize);
let summary_word_count = dirty_word_count.div_ceil(BITS_PER_WORD as usize);
let dirty_bits: Vec<_> = (0..dirty_word_count).map(|_| AtomicU64::new(0)).collect();
let summary: Vec<_> = (0..summary_word_count).map(|_| AtomicU64::new(0)).collect();
let mut true_dirty_element_count = 0;
for (element_index, _) in dirty_flags.iter().enumerate().filter(|(_, element)| **element) {
super::note_changed_index(element_index as u32, &summary, &dirty_bits);
true_dirty_element_count += 1;
}
let calculated_dirty_element_count = super::count_dirty_elements(
&summary,
&dirty_bits
);
assert_eq!(calculated_dirty_element_count, true_dirty_element_count);
}
}
}