Skip to main content

pebble/wgpu/
buffers.rs

1//! Buffer and bind-group construction — three builders, one per thing being
2//! built:
3//! - [`BufferBuilder`] — a plain, uniform, or storage buffer, empty or
4//!   pre-populated with data.
5//! - [`DynamicBufferBuilder`] — a buffer sized and aligned to hold many
6//!   dynamically-offset elements; returns the buffer *and* the per-element
7//!   stride you'll need later, so the two can't drift apart.
8//! - [`BindGroupBuilder`] — assembles a `wgpu::BindGroup` from already-built
9//!   buffers/texture views/samplers, one binding at a time.
10//!
11//! Prefer these over hand-writing `wgpu::BufferDescriptor`/`BindGroupDescriptor`
12//! against `backend.device` directly: correct usage flags are one method
13//! call away instead of memorized flag combinations, and the dynamic-offset
14//! path gets alignment right in a way that's easy to miss by hand. Re-exported,
15//! along with [`binding`](super::binding), from [`wgpu::prelude`](super::prelude).
16
17// ---------------------------------------------------------------------
18// Plain buffers
19// ---------------------------------------------------------------------
20
21enum BufferContents<'a> {
22    Empty(u64),
23    Data(&'a [u8]),
24}
25
26/// Builds a `wgpu::Buffer` — empty (via [`size`](Self::size)) or
27/// pre-populated (via [`data`](Self::data)).
28///
29/// ```ignore
30/// let camera_buffer = BufferBuilder::new()
31///     .label("camera")
32///     .uniform()
33///     .size(64)
34///     .build(&device);
35///
36/// let vertex_buffer = BufferBuilder::new()
37///     .label("mesh vertices")
38///     .usage(wgpu::BufferUsages::VERTEX)
39///     .data(bytemuck::cast_slice(&vertices))
40///     .build(&device);
41/// ```
42///
43/// For a dynamically-offset buffer (many elements, selected via
44/// `set_bind_group`'s dynamic offset), use [`DynamicBufferBuilder`] instead
45/// — it returns the per-element stride alongside the buffer, which plain
46/// `BufferBuilder` has no way to compute.
47pub struct BufferBuilder<'a> {
48    label: Option<&'a str>,
49    usage: wgpu::BufferUsages,
50    contents: BufferContents<'a>,
51}
52
53impl<'a> Default for BufferBuilder<'a> {
54    fn default() -> Self {
55        Self { label: None, usage: wgpu::BufferUsages::empty(), contents: BufferContents::Empty(0) }
56    }
57}
58
59impl<'a> BufferBuilder<'a> {
60    pub fn new() -> Self {
61        Self::default()
62    }
63
64    pub fn label(mut self, label: impl Into<Option<&'a str>>) -> Self {
65        self.label = label.into();
66        self
67    }
68
69    /// Sets the buffer's usage flags outright — use this for anything not
70    /// covered by [`uniform`](Self::uniform)/[`storage`](Self::storage)
71    /// (a vertex/index buffer, a `MAP_READ` staging buffer, ...).
72    pub fn usage(mut self, usage: wgpu::BufferUsages) -> Self {
73        self.usage = usage;
74        self
75    }
76
77    /// Shorthand for `.usage(wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST)`.
78    pub fn uniform(self) -> Self {
79        self.usage(wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST)
80    }
81
82    /// Shorthand for `.usage(wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_DST)`.
83    pub fn storage(self) -> Self {
84        self.usage(wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_DST)
85    }
86
87    /// Pre-populates the buffer with `data` (its size is taken from `data`'s
88    /// length). Mutually exclusive with [`size`](Self::size) — whichever is
89    /// called last wins.
90    pub fn data(mut self, data: &'a [u8]) -> Self {
91        self.contents = BufferContents::Data(data);
92        self
93    }
94
95    /// Allocates an empty buffer of `size` bytes, to be written into later
96    /// (e.g. via [`update_buffer`]). Mutually exclusive with
97    /// [`data`](Self::data) — whichever is called last wins.
98    pub fn size(mut self, size: u64) -> Self {
99        self.contents = BufferContents::Empty(size);
100        self
101    }
102
103    pub fn build(self, device: &wgpu::Device) -> wgpu::Buffer {
104        match self.contents {
105            BufferContents::Data(data) => {
106                use wgpu::util::DeviceExt;
107                device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
108                    label: self.label,
109                    contents: data,
110                    usage: self.usage,
111                })
112            }
113            BufferContents::Empty(size) => device.create_buffer(&wgpu::BufferDescriptor {
114                label: self.label,
115                size,
116                usage: self.usage,
117                mapped_at_creation: false,
118            }),
119        }
120    }
121}
122
123// ---------------------------------------------------------------------
124// Writing to an existing buffer
125// ---------------------------------------------------------------------
126
127/// Overwrites `buffer`'s contents with `data`, starting at offset 0. Plain
128/// `queue.write_buffer` underneath — works for any buffer usage, not just
129/// uniform buffers, despite the neighboring [`update_buffer_at`]'s
130/// dynamic-offset framing. Not a builder: there's nothing optional here to
131/// configure, just an existing buffer to write into.
132pub fn update_buffer(queue: &wgpu::Queue, buffer: &wgpu::Buffer, data: &[u8]) {
133    queue.write_buffer(buffer, 0, data);
134}
135
136/// Writes `data` into `buffer` at a byte offset, for updating one element of a
137/// dynamically-offset buffer without touching the others. `offset` should be a
138/// multiple of the stride returned by [`DynamicBufferBuilder::build`].
139pub fn update_buffer_at(queue: &wgpu::Queue, buffer: &wgpu::Buffer, offset: u64, data: &[u8]) {
140    queue.write_buffer(buffer, offset, data);
141}
142
143// ---------------------------------------------------------------------
144// Dynamically-offset buffers
145// ---------------------------------------------------------------------
146
147enum DynamicKind {
148    Uniform,
149    Storage,
150}
151
152/// Builds an empty buffer sized and aligned to hold `count` dynamically-offset
153/// elements of `element_size` bytes each — for one large buffer holding many
154/// objects'/elements' data, rebound at a different offset via
155/// `set_bind_group`'s dynamic offsets slice instead of a bind group per
156/// object/dispatch. Pair with a layout entry from
157/// [`BindingKind::dynamic_uniform_buffer`](super::binding::BindingKind::dynamic_uniform_buffer)/
158/// [`dynamic_storage_buffer`](super::binding::BindingKind::dynamic_storage_buffer).
159///
160/// ```ignore
161/// let (buffer, stride) = DynamicBufferBuilder::uniform(element_size, count).build(&device);
162/// // ... later, per element:
163/// update_buffer_at(&queue, &buffer, index as u64 * stride, &element_bytes);
164/// // ... at draw time:
165/// pass.set_bind_group(0, Some(&bind_group), &[index as u32 * stride as u32]);
166/// ```
167///
168/// [`build`](Self::build) returns `(wgpu::Buffer, u64)` — the buffer and the
169/// per-element stride to use for both `update_buffer_at` and
170/// `set_bind_group`'s dynamic offset — rather than [`BufferBuilder`]'s plain
171/// `wgpu::Buffer`, since there'd otherwise be no way to recover the
172/// alignment-padded stride after the fact.
173pub struct DynamicBufferBuilder<'a> {
174    label: Option<&'a str>,
175    kind: DynamicKind,
176    element_size: u64,
177    count: u64,
178}
179
180impl<'a> DynamicBufferBuilder<'a> {
181    pub fn uniform(element_size: u64, count: u64) -> Self {
182        Self { label: None, kind: DynamicKind::Uniform, element_size, count }
183    }
184
185    pub fn storage(element_size: u64, count: u64) -> Self {
186        Self { label: None, kind: DynamicKind::Storage, element_size, count }
187    }
188
189    pub fn label(mut self, label: impl Into<Option<&'a str>>) -> Self {
190        self.label = label.into();
191        self
192    }
193
194    pub fn build(self, device: &wgpu::Device) -> (wgpu::Buffer, u64) {
195        let (usage, stride) = match self.kind {
196            DynamicKind::Uniform => (
197                wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
198                dynamic_uniform_offset_stride(device, self.element_size),
199            ),
200            DynamicKind::Storage => (
201                wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_DST,
202                dynamic_storage_offset_stride(device, self.element_size),
203            ),
204        };
205        let buffer = BufferBuilder::new()
206            .label(self.label)
207            .usage(usage)
208            .size(stride * self.count)
209            .build(device);
210        (buffer, stride)
211    }
212}
213
214/// Rounds `element_size` up to the device's required alignment for dynamic offsets on
215/// uniform buffers, giving the stride to use when packing multiple elements into one
216/// buffer for use with [`BindingKind::dynamic_uniform_buffer`](super::binding::BindingKind::dynamic_uniform_buffer).
217/// [`DynamicBufferBuilder`] calls this for you — use it directly only if you're sizing
218/// a dynamic buffer some other way.
219pub fn dynamic_uniform_offset_stride(device: &wgpu::Device, element_size: u64) -> u64 {
220    align_to(element_size, device.limits().min_uniform_buffer_offset_alignment as u64)
221}
222
223/// Same as [`dynamic_uniform_offset_stride`] but for storage buffers.
224pub fn dynamic_storage_offset_stride(device: &wgpu::Device, element_size: u64) -> u64 {
225    align_to(element_size, device.limits().min_storage_buffer_offset_alignment as u64)
226}
227
228fn align_to(size: u64, alignment: u64) -> u64 {
229    size.div_ceil(alignment) * alignment
230}
231
232/// Builds the bind group entry resource for a dynamically-offset binding. Unlike
233/// `buffer.as_entire_binding()`, this scopes the entry to a single `element_size`-sized
234/// element starting at offset 0 in the buffer — required because the dynamic offset passed
235/// to `set_bind_group` at draw/dispatch time is added on top of this base range, and wgpu
236/// validates `offset + size <= buffer size`. Binding the whole buffer here would make any
237/// nonzero dynamic offset fail validation. [`BindGroupBuilder::dynamic_buffer`] calls this
238/// for you.
239fn dynamic_buffer_binding(buffer: &wgpu::Buffer, element_size: u64) -> wgpu::BindingResource<'_> {
240    wgpu::BindingResource::Buffer(wgpu::BufferBinding {
241        buffer,
242        offset: 0,
243        size: wgpu::BufferSize::new(element_size),
244    })
245}
246
247// ---------------------------------------------------------------------
248// Bind groups
249// ---------------------------------------------------------------------
250
251/// Builds a `wgpu::BindGroup` against `layout` one binding at a time.
252///
253/// The plain methods ([`buffer`](Self::buffer), [`texture`](Self::texture),
254/// [`sampler`](Self::sampler), [`dynamic_buffer`](Self::dynamic_buffer))
255/// assign `@binding(N)` in call order, starting at 0 — the common case,
256/// matching a layout whose entries are numbered the same way. If your
257/// target's bindings aren't contiguous from 0 (e.g. looked up by name
258/// against a [`BindGroupTarget`](super::binding::BindGroupTarget), as
259/// [`GPUBindingInstance`](super::instance::GPUBindingInstance) does), use
260/// the `_at` variants to assign an explicit `@binding(N)` instead.
261///
262/// ```ignore
263/// let bind_group = BindGroupBuilder::new(&layout)
264///     .label("camera_bind_group")
265///     .buffer(&camera_buffer)
266///     .build(&device);
267/// ```
268pub struct BindGroupBuilder<'a> {
269    label: Option<&'a str>,
270    layout: &'a wgpu::BindGroupLayout,
271    entries: Vec<wgpu::BindGroupEntry<'a>>,
272    next_binding: u32,
273}
274
275impl<'a> BindGroupBuilder<'a> {
276    pub fn new(layout: &'a wgpu::BindGroupLayout) -> Self {
277        Self { label: None, layout, entries: Vec::new(), next_binding: 0 }
278    }
279
280    pub fn label(mut self, label: impl Into<Option<&'a str>>) -> Self {
281        self.label = label.into();
282        self
283    }
284
285    /// Binds `buffer` in its entirety at the next `@binding(N)` (call order,
286    /// starting at 0).
287    pub fn buffer(self, buffer: &'a wgpu::Buffer) -> Self {
288        let binding = self.next_binding;
289        self.buffer_at(binding, buffer)
290    }
291
292    /// Same as [`buffer`](Self::buffer) but at an explicit `@binding(N)`.
293    pub fn buffer_at(mut self, binding: u32, buffer: &'a wgpu::Buffer) -> Self {
294        self.entries.push(wgpu::BindGroupEntry { binding, resource: buffer.as_entire_binding() });
295        self.next_binding = self.next_binding.max(binding + 1);
296        self
297    }
298
299    /// Binds `buffer` scoped to one `element_size`-sized element — not the
300    /// whole buffer, since the dynamic offset passed to `set_bind_group` at
301    /// draw/dispatch time is added on top of this base range, and wgpu
302    /// validates `offset + size <= buffer size` — at the next `@binding(N)`,
303    /// for a buffer built by [`DynamicBufferBuilder`].
304    pub fn dynamic_buffer(self, buffer: &'a wgpu::Buffer, element_size: u64) -> Self {
305        let binding = self.next_binding;
306        self.dynamic_buffer_at(binding, buffer, element_size)
307    }
308
309    /// Same as [`dynamic_buffer`](Self::dynamic_buffer) but at an explicit `@binding(N)`.
310    pub fn dynamic_buffer_at(mut self, binding: u32, buffer: &'a wgpu::Buffer, element_size: u64) -> Self {
311        self.entries.push(wgpu::BindGroupEntry { binding, resource: dynamic_buffer_binding(buffer, element_size) });
312        self.next_binding = self.next_binding.max(binding + 1);
313        self
314    }
315
316    /// Binds `view` at the next `@binding(N)`.
317    pub fn texture(self, view: &'a wgpu::TextureView) -> Self {
318        let binding = self.next_binding;
319        self.texture_at(binding, view)
320    }
321
322    /// Same as [`texture`](Self::texture) but at an explicit `@binding(N)`.
323    pub fn texture_at(mut self, binding: u32, view: &'a wgpu::TextureView) -> Self {
324        self.entries.push(wgpu::BindGroupEntry { binding, resource: wgpu::BindingResource::TextureView(view) });
325        self.next_binding = self.next_binding.max(binding + 1);
326        self
327    }
328
329    /// Binds `sampler` at the next `@binding(N)`.
330    pub fn sampler(self, sampler: &'a wgpu::Sampler) -> Self {
331        let binding = self.next_binding;
332        self.sampler_at(binding, sampler)
333    }
334
335    /// Same as [`sampler`](Self::sampler) but at an explicit `@binding(N)`.
336    pub fn sampler_at(mut self, binding: u32, sampler: &'a wgpu::Sampler) -> Self {
337        self.entries.push(wgpu::BindGroupEntry { binding, resource: wgpu::BindingResource::Sampler(sampler) });
338        self.next_binding = self.next_binding.max(binding + 1);
339        self
340    }
341
342    pub fn build(self, device: &wgpu::Device) -> wgpu::BindGroup {
343        device.create_bind_group(&wgpu::BindGroupDescriptor {
344            label: self.label,
345            layout: self.layout,
346            entries: &self.entries,
347        })
348    }
349}