use bytemuck::Pod;
use rustc_hash::FxHasher;
use std::hash::{Hash, Hasher};
const MINIMUM_CAPACITY: u64 = 4096;
pub struct InstanceBuffer {
buffer: wgpu::Buffer,
bind_group: wgpu::BindGroup,
capacity: u64,
label: &'static str,
last_hash: Option<(usize, u64)>,
}
fn hash_slice(bytes: &[u8]) -> (usize, u64) {
let mut hasher = FxHasher::default();
bytes.hash(&mut hasher);
(bytes.len(), hasher.finish())
}
impl InstanceBuffer {
pub fn new(device: &wgpu::Device, layout: &wgpu::BindGroupLayout, label: &'static str) -> Self {
Self::with_capacity(device, layout, label, MINIMUM_CAPACITY)
}
fn with_capacity(
device: &wgpu::Device,
layout: &wgpu::BindGroupLayout,
label: &'static str,
capacity: u64,
) -> Self {
let buffer = device.create_buffer(&wgpu::BufferDescriptor {
label: Some(label),
size: capacity,
usage: wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_DST,
mapped_at_creation: false,
});
let bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
label: Some(label),
layout,
entries: &[wgpu::BindGroupEntry { binding: 0, resource: buffer.as_entire_binding() }],
});
Self { buffer, bind_group, capacity, label, last_hash: None }
}
pub fn bind_group(&self) -> &wgpu::BindGroup {
&self.bind_group
}
pub fn upload<T: Pod>(
&mut self,
device: &wgpu::Device,
queue: &wgpu::Queue,
layout: &wgpu::BindGroupLayout,
items: &[T],
) {
let bytes: &[u8] = bytemuck::cast_slice(items);
if bytes.is_empty() {
return;
}
let signature = hash_slice(bytes);
let required = bytes.len() as u64;
if required > self.capacity {
*self = Self::with_capacity(device, layout, self.label, required.next_power_of_two());
} else if self.last_hash == Some(signature) {
return;
}
self.last_hash = Some(signature);
queue.write_buffer(&self.buffer, 0, bytes);
}
pub fn shrink(&mut self, device: &wgpu::Device, layout: &wgpu::BindGroupLayout) {
if self.capacity > MINIMUM_CAPACITY {
*self = Self::new(device, layout, self.label);
} else {
self.last_hash = None;
}
}
}