#[repr(C)]
#[derive(Debug, Clone, Copy, bytemuck::Pod, bytemuck::Zeroable)]
pub struct InstanceData {
pub model: [f32; 16],
pub color: [f32; 4],
}
const IDENTITY_MAT4: [f32; 16] = [
1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, ];
impl Default for InstanceData {
fn default() -> Self {
Self {
model: IDENTITY_MAT4,
color: [1.0, 1.0, 1.0, 1.0],
}
}
}
impl InstanceData {
#[must_use]
pub fn from_translation(x: f32, y: f32, z: f32) -> Self {
let mut model = IDENTITY_MAT4;
model[12] = x;
model[13] = y;
model[14] = z;
Self {
model,
..Default::default()
}
}
#[must_use]
pub fn layout() -> wgpu::VertexBufferLayout<'static> {
wgpu::VertexBufferLayout {
array_stride: std::mem::size_of::<Self>() as wgpu::BufferAddress,
step_mode: wgpu::VertexStepMode::Instance,
attributes: &[
wgpu::VertexAttribute {
offset: 0,
shader_location: 7,
format: wgpu::VertexFormat::Float32x4,
},
wgpu::VertexAttribute {
offset: 16,
shader_location: 8,
format: wgpu::VertexFormat::Float32x4,
},
wgpu::VertexAttribute {
offset: 32,
shader_location: 9,
format: wgpu::VertexFormat::Float32x4,
},
wgpu::VertexAttribute {
offset: 48,
shader_location: 10,
format: wgpu::VertexFormat::Float32x4,
},
wgpu::VertexAttribute {
offset: 64,
shader_location: 11,
format: wgpu::VertexFormat::Float32x4,
},
],
}
}
}
pub struct InstanceBuffer {
pub buffer: wgpu::Buffer,
pub count: u32,
capacity: usize,
}
impl InstanceBuffer {
#[must_use = "GPU buffer allocated but not used"]
pub fn new(device: &wgpu::Device, instances: &[InstanceData]) -> Self {
use wgpu::util::DeviceExt;
tracing::debug!(count = instances.len(), "creating instance buffer");
let buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
label: Some("instance_buffer"),
contents: bytemuck::cast_slice(instances),
usage: wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST,
});
Self {
buffer,
count: instances.len() as u32,
capacity: instances.len(),
}
}
#[must_use = "GPU buffer allocated but not used"]
pub fn with_capacity(device: &wgpu::Device, capacity: usize) -> Self {
let capacity = capacity.max(16);
let buffer = device.create_buffer(&wgpu::BufferDescriptor {
label: Some("instance_buffer"),
size: capacity.saturating_mul(std::mem::size_of::<InstanceData>()) as u64,
usage: wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST,
mapped_at_creation: false,
});
Self {
buffer,
count: 0,
capacity,
}
}
pub fn update(
&mut self,
device: &wgpu::Device,
queue: &wgpu::Queue,
instances: &[InstanceData],
) {
if instances.is_empty() {
self.count = 0;
return;
}
if instances.len() > self.capacity {
self.capacity = instances.len().saturating_mul(3).saturating_div(2).max(16);
tracing::debug!(
old_count = self.count,
new_capacity = self.capacity,
"instance buffer regrow"
);
self.buffer = device.create_buffer(&wgpu::BufferDescriptor {
label: Some("instance_buffer"),
size: self
.capacity
.saturating_mul(std::mem::size_of::<InstanceData>())
as u64,
usage: wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST,
mapped_at_creation: false,
});
}
queue.write_buffer(&self.buffer, 0, bytemuck::cast_slice(instances));
self.count = instances.len() as u32;
}
#[must_use]
#[inline]
pub fn count(&self) -> u32 {
self.count
}
#[must_use]
#[inline]
pub fn capacity(&self) -> usize {
self.capacity
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn instance_data_size() {
assert_eq!(std::mem::size_of::<InstanceData>(), 80);
}
#[test]
fn instance_data_default() {
let d = InstanceData::default();
assert_eq!(d.model[0], 1.0);
assert_eq!(d.model[5], 1.0);
assert_eq!(d.model[10], 1.0);
assert_eq!(d.model[15], 1.0);
assert_eq!(d.color, [1.0, 1.0, 1.0, 1.0]);
}
#[test]
fn instance_data_translation() {
let d = InstanceData::from_translation(5.0, 3.0, 1.0);
assert_eq!(d.model[12], 5.0);
assert_eq!(d.model[13], 3.0);
assert_eq!(d.model[14], 1.0);
}
#[test]
fn instance_data_layout() {
let layout = InstanceData::layout();
assert_eq!(layout.array_stride, 80);
assert_eq!(layout.attributes.len(), 5);
assert_eq!(layout.step_mode, wgpu::VertexStepMode::Instance);
}
#[test]
fn instance_data_layout_offsets() {
let layout = InstanceData::layout();
assert_eq!(layout.attributes[0].offset, 0);
assert_eq!(layout.attributes[1].offset, 16);
assert_eq!(layout.attributes[2].offset, 32);
assert_eq!(layout.attributes[3].offset, 48);
assert_eq!(layout.attributes[4].offset, 64);
}
#[test]
fn instance_data_layout_locations() {
let layout = InstanceData::layout();
assert_eq!(layout.attributes[0].shader_location, 7);
assert_eq!(layout.attributes[4].shader_location, 11);
}
#[test]
fn instance_data_bytemuck() {
let d = InstanceData::default();
let bytes = bytemuck::bytes_of(&d);
assert_eq!(bytes.len(), 80);
}
#[test]
fn instance_data_batch_cast() {
let instances = vec![
InstanceData::from_translation(0.0, 0.0, 0.0),
InstanceData::from_translation(1.0, 0.0, 0.0),
InstanceData::from_translation(2.0, 0.0, 0.0),
];
let bytes: &[u8] = bytemuck::cast_slice(&instances);
assert_eq!(bytes.len(), 80 * 3);
}
#[test]
fn instance_buffer_types() {
let _size = std::mem::size_of::<InstanceBuffer>();
}
#[test]
fn identity_matrix_is_correct() {
let m = IDENTITY_MAT4;
for i in 0..4 {
for j in 0..4 {
let expected = if i == j { 1.0 } else { 0.0 };
assert_eq!(m[j * 4 + i], expected, "mat[{i}][{j}] wrong");
}
}
}
fn try_gpu() -> Option<(wgpu::Device, wgpu::Queue)> {
let ctx = pollster::block_on(crate::context::GpuContext::new()).ok()?;
Some((ctx.device, ctx.queue))
}
#[test]
fn gpu_instance_buffer_create() {
let Some((device, _queue)) = try_gpu() else {
return;
};
let instances = vec![InstanceData::default(); 10];
let buf = InstanceBuffer::new(&device, &instances);
assert_eq!(buf.count, 10);
}
#[test]
fn gpu_instance_buffer_update() {
let Some((device, queue)) = try_gpu() else {
return;
};
let initial = vec![InstanceData::default(); 4];
let mut buf = InstanceBuffer::new(&device, &initial);
assert_eq!(buf.count, 4);
let larger = vec![InstanceData::from_translation(1.0, 0.0, 0.0); 100];
buf.update(&device, &queue, &larger);
assert_eq!(buf.count, 100);
}
#[test]
fn gpu_instance_buffer_empty() {
let Some((device, queue)) = try_gpu() else {
return;
};
let initial = vec![InstanceData::default(); 4];
let mut buf = InstanceBuffer::new(&device, &initial);
buf.update(&device, &queue, &[]);
assert_eq!(buf.count, 0);
}
}