unlit_wgpu/instance_stream.rs
1//! A reused vertex buffer holding one family's per-frame instance records.
2//!
3//! A pipeline family that draws with instance-stepped attributes owns one
4//! [`InstanceBuffer`]: every frame it packs one record per visible instance and
5//! uploads them through the frame's encoder. Keeping the stream per family is
6//! what lets a caller's family carry its own per-instance data without sharing
7//! a record layout or a buffer with the built-in lit family.
8
9use crate::staging::StagingBuffer;
10
11/// Where a family's instance stream binds and how wide one record is.
12#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
13pub struct InstanceStreamDesc {
14 /// The vertex-buffer slot the stream binds at.
15 pub slot: u32,
16 /// Bytes per instance record. Zero means the family binds no instance
17 /// stream at all.
18 pub array_stride: u32,
19 /// The label the stream's buffer is created with, as it appears in a
20 /// backend's resource list. `None` takes [InstanceBuffer]'s own.
21 pub label: Option<&'static str>,
22}
23
24impl InstanceStreamDesc {
25 /// The description of a family that binds no instance stream.
26 pub const NONE: Self = Self {
27 slot: 0,
28 array_stride: 0,
29 label: None,
30 };
31}
32
33/// A reused vertex buffer holding one frame's packed instance records.
34///
35/// The buffer grows geometrically and is reused between frames, so a steady
36/// scene allocates nothing. Bytes reach it through a [`StagingBuffer`], the
37/// same path the camera and frame globals take.
38pub struct InstanceBuffer {
39 /// Where the stream binds and how wide one record is.
40 desc: InstanceStreamDesc,
41 /// The buffer holding the last upload, allocated on the first one.
42 buffer: Option<wgpu::Buffer>,
43 /// How many records the buffer can hold.
44 capacity: u32,
45 /// The staging buffers the uploads pass through.
46 staging: StagingBuffer,
47}
48
49impl InstanceBuffer {
50 /// Create an empty stream for `desc`, allocating no GPU buffer until the
51 /// first upload.
52 ///
53 /// # Panics
54 ///
55 /// If `desc` names a stride that is not a multiple of
56 /// [`wgpu::VERTEX_ALIGNMENT`], which a vertex-buffer layout cannot declare.
57 pub fn new(desc: InstanceStreamDesc) -> Self {
58 assert!(
59 desc.array_stride == 0
60 || desc
61 .array_stride
62 .is_multiple_of(wgpu::VERTEX_ALIGNMENT as u32),
63 "an instance record's stride must be a multiple of `VERTEX_ALIGNMENT`"
64 );
65 Self {
66 desc,
67 buffer: None,
68 capacity: 0,
69 staging: StagingBuffer::new(),
70 }
71 }
72
73 /// The buffer holding the last upload, or `None` before the first one.
74 pub fn buffer(&self) -> Option<&wgpu::Buffer> {
75 self.buffer.as_ref()
76 }
77
78 /// Upload `records` as this frame's instance stream.
79 ///
80 /// A stream with no stride uploads nothing, and so does an empty one: a
81 /// family with no visible instances binds no buffer, exactly as one that
82 /// declares no stream does.
83 ///
84 /// # Panics
85 ///
86 /// If `records.len()` is not a whole number of records.
87 pub fn upload(
88 &mut self,
89 device: &wgpu::Device,
90 encoder: &mut wgpu::CommandEncoder,
91 records: &[u8],
92 ) {
93 let stride = self.desc.array_stride;
94 if stride == 0 || records.is_empty() {
95 return;
96 }
97 assert_eq!(
98 records.len() % stride as usize,
99 0,
100 "instance records are whole strides"
101 );
102 let count = (records.len() / stride as usize) as u32;
103 self.reserve(device, count);
104 let buffer = self.buffer.as_ref().expect("the buffer was just reserved");
105 self.staging.write(device, encoder, buffer, 0, records);
106 }
107
108 /// Grow the buffer geometrically so it can hold `count` records.
109 fn reserve(&mut self, device: &wgpu::Device, count: u32) {
110 if count <= self.capacity {
111 return;
112 }
113 // Grow by a factor rather than exactly, so a scene that adds one
114 // instance per frame does not reallocate every frame.
115 let new_cap = count.max(self.capacity + self.capacity / 2).max(1);
116 let buffer = device.create_buffer(&wgpu::BufferDescriptor {
117 label: Some(self.desc.label.unwrap_or("unlit_wgpu::instance_stream")),
118 size: u64::from(new_cap) * u64::from(self.desc.array_stride),
119 usage: wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST,
120 mapped_at_creation: false,
121 });
122 self.buffer = Some(buffer);
123 self.capacity = new_cap;
124 }
125}