use std::{collections::BTreeMap, marker::PhantomData};
use bevy::{
prelude::*,
render::{
ExtractSchedule, MainWorld, Render, RenderApp, RenderStartup,
render_resource::{Buffer, BufferUsages},
renderer::{RenderDevice, RenderQueue},
},
};
use encase::ShaderSize;
use encase::internal::WriteInto;
use wgpu::util::BufferInitDescriptor;
use crate::{
ShaderSystems,
bindings::{AutoBufferKind, BindingTable, register_binding},
};
#[derive(Resource)]
pub struct ArrayBufferChanges<Tag> {
pub(crate) changes: Vec<(usize, Vec<u8>)>,
pub(crate) len: usize,
pub(crate) _marker: PhantomData<Tag>,
}
impl<Tag> Default for ArrayBufferChanges<Tag> {
fn default() -> Self {
Self {
changes: Vec::new(),
len: 0,
_marker: PhantomData,
}
}
}
fn serialize_element<T: ShaderSize + WriteInto>(value: &T) -> Vec<u8> {
let el_size = T::SHADER_SIZE.get() as usize;
let mut bytes = vec![0u8; el_size];
encase::StorageBuffer::new(&mut bytes[..])
.write(value)
.unwrap();
bytes
}
pub(crate) fn array_stride<T: ShaderSize>() -> usize {
<[T; 1] as ShaderSize>::SHADER_SIZE.get() as usize
}
pub(crate) fn default_filled_bytes<T: ShaderSize + WriteInto + Default>(len: usize) -> Vec<u8> {
let stride = array_stride::<T>();
let el_bytes = serialize_element(&T::default());
let mut bytes = vec![0u8; stride * len];
for i in 0..len {
bytes[i * stride..i * stride + el_bytes.len()].copy_from_slice(&el_bytes);
}
bytes
}
pub(crate) fn register<Tag, T, const N: usize>(
app: &mut App,
group_index: u32,
binding_index: u32,
read_write: bool,
) where
Tag: Send + Sync + 'static,
T: ShaderSize + WriteInto + Default + Send + Sync + 'static,
{
app.insert_resource(ArrayBufferChanges::<Tag> {
changes: Vec::new(),
len: N,
_marker: PhantomData,
});
let render_app = app.sub_app_mut(RenderApp);
register_binding(
render_app,
group_index,
binding_index,
AutoBufferKind::Storage {
read_only: !read_write,
},
);
render_app.add_systems(
RenderStartup,
|mut commands: Commands, render_device: Res<RenderDevice>| {
let buffer = render_device.create_buffer_with_data(&BufferInitDescriptor {
label: Some("array_buffer"),
contents: &default_filled_bytes::<T>(N),
usage: BufferUsages::STORAGE | BufferUsages::COPY_DST | BufferUsages::COPY_SRC,
});
commands.insert_resource(ArrayBufferState::<Tag> {
buffer,
stride: array_stride::<T>(),
_marker: PhantomData,
});
},
);
render_app.add_systems(ExtractSchedule, extract_array_changes::<Tag>);
render_app.add_systems(
Render,
(move |state: Option<Res<ArrayBufferState<Tag>>>,
changes: Option<Res<ArrayBufferChanges<Tag>>>,
render_queue: Res<RenderQueue>,
mut table: ResMut<BindingTable>| {
let (Some(state), Some(changes)) = (state, changes) else {
return;
};
apply_array_buffer_changes(
&state,
&changes,
&render_queue,
&mut table,
group_index,
binding_index,
);
})
.in_set(ShaderSystems::PrepareBindings),
);
}
impl<Tag> ArrayBufferChanges<Tag> {
pub fn set<T: ShaderSize + WriteInto>(&mut self, index: usize, value: T) {
if index >= self.len {
panic!(
"ArrayBufferChanges::set index out of bounds: {} >= {}",
index, self.len
);
}
let bytes = serialize_element(&value);
self.changes.push((index, bytes));
}
pub fn set_many<T, I>(&mut self, pairs: I)
where
T: ShaderSize + WriteInto,
I: IntoIterator<Item = (usize, T)>,
{
for (index, value) in pairs {
self.set(index, value);
}
}
pub fn set_all<T: ShaderSize + WriteInto>(&mut self, value: T) {
let bytes = serialize_element(&value);
self.changes.reserve(self.len);
for i in 0..self.len {
self.changes.push((i, bytes.clone()));
}
}
}
#[derive(Resource)]
pub struct ArrayBufferState<Tag> {
pub(crate) buffer: Buffer,
pub(crate) stride: usize,
pub(crate) _marker: PhantomData<Tag>,
}
pub(crate) fn extract_array_changes<Tag: Send + Sync + 'static>(
mut commands: Commands,
mut main_world: ResMut<MainWorld>,
) {
let extracted = {
let Some(mut changes) = main_world.get_resource_mut::<ArrayBufferChanges<Tag>>() else {
return;
};
(std::mem::take(&mut changes.changes), changes.len)
};
commands.insert_resource(ArrayBufferChanges::<Tag> {
changes: extracted.0,
len: extracted.1,
_marker: PhantomData,
});
}
fn apply_array_buffer_changes<Tag: Send + Sync + 'static>(
state: &ArrayBufferState<Tag>,
changes: &ArrayBufferChanges<Tag>,
render_queue: &RenderQueue,
table: &mut BindingTable,
group_index: u32,
binding_index: u32,
) {
let stride = state.stride;
let mut by_index: BTreeMap<usize, &[u8]> = BTreeMap::new();
for (i, bytes) in &changes.changes {
by_index.insert(*i, bytes);
}
let mut runs: Vec<(u64, Vec<u8>)> = Vec::new();
let mut run_start: Option<usize> = None;
let mut run_bytes: Vec<u8> = Vec::new();
let mut prev: Option<usize> = None;
for (&index, &el_bytes) in &by_index {
let mut padded = vec![0u8; stride];
padded[..el_bytes.len()].copy_from_slice(el_bytes);
match prev {
Some(p) if index == p + 1 => {
run_bytes.extend_from_slice(&padded);
}
_ => {
if let Some(start) = run_start {
runs.push((start as u64 * stride as u64, std::mem::take(&mut run_bytes)));
}
run_start = Some(index);
run_bytes = padded;
}
}
prev = Some(index);
}
if let Some(start) = run_start {
runs.push((start as u64 * stride as u64, run_bytes));
}
for (offset, bytes) in runs {
render_queue.write_buffer(&state.buffer, offset, &bytes);
}
table.set_buffer(group_index, binding_index, &state.buffer);
}