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