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