Skip to main content

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}