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 [`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::flags::BufferUsages;
22use crate::wgpu::gpu_context::GpuContext;
23use crate::wgpu::samplers::Sampler;
24use crate::wgpu::texture_array::GPUTextureArray;
25use crate::wgpu::texture_view::TextureView;
26use crate::wgpu::textures::GPUTexture;
27
28/// A `wgpu::BindGroup`, opaque — built only via [`BindGroupBuilder::build`].
29/// Bind it against a [`RenderPass`](super::render_pass::RenderPass)/
30/// [`ComputePass`](super::compute_pass::ComputePass) via their
31/// `set_bind_group`; there's no way to reach the underlying `wgpu::BindGroup`
32/// from outside this crate.
33pub struct BindGroup(wgpu::BindGroup);
34
35impl BindGroup {
36    pub(crate) fn raw(&self) -> &wgpu::BindGroup {
37        &self.0
38    }
39}
40
41// ---------------------------------------------------------------------
42// Plain buffers
43// ---------------------------------------------------------------------
44
45enum BufferContents<'a> {
46    Empty(u64),
47    Data(&'a [u8]),
48}
49
50impl<'a> BufferContents<'a> {
51    fn size(&self) -> u64 {
52        match self {
53            BufferContents::Empty(size) => *size,
54            BufferContents::Data(data) => data.len() as u64,
55        }
56    }
57}
58
59/// Builds a [`Buffer`] — empty (via [`empty`](Self::empty)) or pre-populated
60/// (via [`with_data`](Self::with_data)). Two constructors rather than one
61/// `new()` plus a `.data()`/`.size()` setter pair, so there's no way to call
62/// both and have the second one silently win — the same shape as
63/// [`Texture`](super::textures::Texture)'s `from_file`/`from_data`/`empty`.
64///
65/// ```ignore
66/// let camera_buffer = BufferBuilder::empty(64)
67///     .label("camera")
68///     .uniform()
69///     .build(&backend);
70///
71/// let vertex_buffer = BufferBuilder::with_data(bytemuck::cast_slice(&vertices))
72///     .label("mesh vertices")
73///     .usage(BufferUsages::VERTEX)
74///     .build(&backend);
75/// ```
76///
77/// For a dynamically-offset buffer (many elements, selected via
78/// `set_bind_group`'s dynamic offset), use [`DynamicBufferBuilder`] instead
79/// — it returns the per-element stride alongside the buffer, which plain
80/// `BufferBuilder` has no way to compute.
81pub struct BufferBuilder<'a> {
82    label: Option<&'a str>,
83    usage: BufferUsages,
84    contents: BufferContents<'a>,
85}
86
87impl<'a> BufferBuilder<'a> {
88    /// Allocates an empty buffer of `size` bytes, to be written into later
89    /// via [`Buffer::write`].
90    pub fn empty(size: u64) -> Self {
91        Self { label: None, usage: BufferUsages::empty(), contents: BufferContents::Empty(size) }
92    }
93
94    /// Pre-populates the buffer with `data` (its size is taken from `data`'s length).
95    pub fn with_data(data: &'a [u8]) -> Self {
96        Self { label: None, usage: BufferUsages::empty(), contents: BufferContents::Data(data) }
97    }
98
99    pub fn label(mut self, label: impl Into<Option<&'a str>>) -> Self {
100        self.label = label.into();
101        self
102    }
103
104    /// Sets the buffer's usage flags outright — use this for anything not
105    /// covered by [`uniform`](Self::uniform)/[`storage`](Self::storage)
106    /// (a vertex/index buffer, a `MAP_READ` staging buffer, ...).
107    pub fn usage(mut self, usage: BufferUsages) -> Self {
108        self.usage = usage;
109        self
110    }
111
112    /// Shorthand for `.usage(BufferUsages::UNIFORM | BufferUsages::COPY_DST)`.
113    pub fn uniform(self) -> Self {
114        self.usage(BufferUsages::UNIFORM | BufferUsages::COPY_DST)
115    }
116
117    /// Shorthand for `.usage(BufferUsages::STORAGE | BufferUsages::COPY_DST)`.
118    pub fn storage(self) -> Self {
119        self.usage(BufferUsages::STORAGE | BufferUsages::COPY_DST)
120    }
121
122    pub fn build(self, backend: &WGPUBackend) -> Buffer {
123        let raw = self.build_raw(&backend.device);
124        Buffer::new(raw, GpuContext::from_backend(backend))
125    }
126
127    /// Internal primitive behind [`build`](Self::build) — used directly only
128    /// where a [`WGPUBackend`] isn't available yet (bootstrapping a staging
129    /// buffer for [`Buffer::read`](crate::wgpu::buffer::Buffer::read), which
130    /// only has `device`/`queue` separately).
131    pub(crate) fn build_raw(self, device: &wgpu::Device) -> wgpu::Buffer {
132        check_buffer_size(device, self.label, self.usage, self.contents.size());
133        match self.contents {
134            BufferContents::Data(data) => {
135                use wgpu::util::DeviceExt;
136                device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
137                    label: self.label,
138                    contents: data,
139                    usage: self.usage.into(),
140                })
141            }
142            BufferContents::Empty(size) => device.create_buffer(&wgpu::BufferDescriptor {
143                label: self.label,
144                size,
145                usage: self.usage.into(),
146                mapped_at_creation: false,
147            }),
148        }
149    }
150}
151
152/// Panics if `size` exceeds this device's `max_buffer_size`, or — when `usage` includes
153/// `UNIFORM`/`STORAGE` — the tighter `max_uniform_buffer_binding_size`/
154/// `max_storage_buffer_binding_size` those binding types are further capped to. The
155/// difference between a clear message here (the actual size and the device's real limit) and
156/// an opaque wgpu validation panic deep inside `create_buffer`/`create_buffer_init`.
157fn check_buffer_size(device: &wgpu::Device, label: Option<&str>, usage: BufferUsages, size: u64) {
158    let limits = device.limits();
159    let labeled = || label.map(|l| format!(" '{l}'")).unwrap_or_default();
160    if size > limits.max_buffer_size {
161        panic!("buffer{}: {size} bytes exceeds this device's max_buffer_size ({})", labeled(), limits.max_buffer_size);
162    }
163    if usage.contains(BufferUsages::UNIFORM) && size > limits.max_uniform_buffer_binding_size {
164        panic!(
165            "buffer{}: {size} bytes exceeds this device's max_uniform_buffer_binding_size ({})",
166            labeled(),
167            limits.max_uniform_buffer_binding_size
168        );
169    }
170    if usage.contains(BufferUsages::STORAGE) && size > limits.max_storage_buffer_binding_size {
171        panic!(
172            "buffer{}: {size} bytes exceeds this device's max_storage_buffer_binding_size ({})",
173            labeled(),
174            limits.max_storage_buffer_binding_size
175        );
176    }
177}
178
179// ---------------------------------------------------------------------
180// Dynamically-offset buffers
181// ---------------------------------------------------------------------
182
183enum DynamicKind {
184    Uniform,
185    Storage,
186}
187
188/// Builds a [`DynamicBuffer`] — empty, sized and aligned to hold `count`
189/// dynamically-offset elements of `element_size` bytes each — for one large
190/// buffer holding many objects'/elements' data, rebound at a different
191/// offset via `set_bind_group`'s dynamic offsets slice instead of a bind
192/// group per object/dispatch. Pair with a layout entry from
193/// [`BindingKind::dynamic_uniform_buffer`](super::binding::BindingKind::dynamic_uniform_buffer)/
194/// [`dynamic_storage_buffer`](super::binding::BindingKind::dynamic_storage_buffer).
195///
196/// ```ignore
197/// let dynamic = DynamicBufferBuilder::uniform(element_size, count).build(&backend);
198/// // ... later, per element:
199/// dynamic.write_element(index, &element_bytes);
200/// // ... at draw time:
201/// pass.set_bind_group(0, Some(&bind_group), &[index as u32 * dynamic.stride() as u32]);
202/// ```
203pub struct DynamicBufferBuilder<'a> {
204    label: Option<&'a str>,
205    kind: DynamicKind,
206    element_size: u64,
207    count: u64,
208}
209
210impl<'a> DynamicBufferBuilder<'a> {
211    pub fn uniform(element_size: u64, count: u64) -> Self {
212        Self { label: None, kind: DynamicKind::Uniform, element_size, count }
213    }
214
215    pub fn storage(element_size: u64, count: u64) -> Self {
216        Self { label: None, kind: DynamicKind::Storage, element_size, count }
217    }
218
219    pub fn label(mut self, label: impl Into<Option<&'a str>>) -> Self {
220        self.label = label.into();
221        self
222    }
223
224    pub fn build(self, backend: &WGPUBackend) -> DynamicBuffer {
225        let (usage, stride) = match self.kind {
226            DynamicKind::Uniform => (
227                BufferUsages::UNIFORM | BufferUsages::COPY_DST,
228                dynamic_uniform_offset_stride(backend, self.element_size),
229            ),
230            DynamicKind::Storage => (
231                BufferUsages::STORAGE | BufferUsages::COPY_DST,
232                dynamic_storage_offset_stride(backend, self.element_size),
233            ),
234        };
235        let buffer = BufferBuilder::empty(stride * self.count).label(self.label).usage(usage).build(backend);
236        DynamicBuffer::new(buffer, stride, self.element_size)
237    }
238}
239
240/// Rounds `element_size` up to the device's required alignment for dynamic offsets on
241/// uniform buffers, giving the stride to use when packing multiple elements into one
242/// buffer for use with [`BindingKind::dynamic_uniform_buffer`](super::binding::BindingKind::dynamic_uniform_buffer).
243/// [`DynamicBufferBuilder`] calls this for you — use it directly only if you're sizing
244/// a dynamic buffer some other way.
245pub fn dynamic_uniform_offset_stride(backend: &WGPUBackend, element_size: u64) -> u64 {
246    dynamic_uniform_offset_stride_raw(&backend.device, element_size)
247}
248
249/// Internal primitive behind [`dynamic_uniform_offset_stride`] — used
250/// directly only by tests, which have a raw `wgpu::Device` but no full
251/// [`WGPUBackend`].
252pub(crate) fn dynamic_uniform_offset_stride_raw(device: &wgpu::Device, element_size: u64) -> u64 {
253    align_to(element_size, device.limits().min_uniform_buffer_offset_alignment as u64)
254}
255
256/// Same as [`dynamic_uniform_offset_stride`] but for storage buffers.
257pub fn dynamic_storage_offset_stride(backend: &WGPUBackend, element_size: u64) -> u64 {
258    dynamic_storage_offset_stride_raw(&backend.device, element_size)
259}
260
261/// Internal primitive behind [`dynamic_storage_offset_stride`] — used
262/// directly only by tests, which have a raw `wgpu::Device` but no full
263/// [`WGPUBackend`].
264pub(crate) fn dynamic_storage_offset_stride_raw(device: &wgpu::Device, element_size: u64) -> u64 {
265    align_to(element_size, device.limits().min_storage_buffer_offset_alignment as u64)
266}
267
268fn align_to(size: u64, alignment: u64) -> u64 {
269    size.div_ceil(alignment) * alignment
270}
271
272/// Builds the bind group entry resource for a dynamically-offset binding. Unlike
273/// `buffer.as_entire_binding()`, this scopes the entry to a single `element_size`-sized
274/// element starting at offset 0 in the buffer — required because the dynamic offset passed
275/// to `set_bind_group` at draw/dispatch time is added on top of this base range, and wgpu
276/// validates `offset + size <= buffer size`. Binding the whole buffer here would make any
277/// nonzero dynamic offset fail validation. [`BindGroupBuilder::dynamic_buffer`] calls this
278/// for you.
279fn dynamic_buffer_binding(buffer: &wgpu::Buffer, element_size: u64) -> wgpu::BindingResource<'_> {
280    wgpu::BindingResource::Buffer(wgpu::BufferBinding {
281        buffer,
282        offset: 0,
283        size: wgpu::BufferSize::new(element_size),
284    })
285}
286
287// ---------------------------------------------------------------------
288// Bind groups
289// ---------------------------------------------------------------------
290
291/// Builds a `wgpu::BindGroup` against `layout` one binding at a time.
292///
293/// The plain methods ([`buffer`](Self::buffer), [`texture_2d`](Self::texture_2d),
294/// [`sampler`](Self::sampler), [`dynamic_buffer`](Self::dynamic_buffer), ...)
295/// assign `@binding(N)` in call order, starting at 0 — the common case,
296/// matching a layout whose entries are numbered the same way. If your
297/// target's bindings aren't contiguous from 0 (e.g. looked up by name
298/// against a [`BindGroupTarget`](super::binding::BindGroupTarget), as
299/// [`GPUBindingInstance`](super::instance::GPUBindingInstance) does), use
300/// the `_at` variants to assign an explicit `@binding(N)` instead.
301///
302/// ```ignore
303/// let bind_group = BindGroupBuilder::new(&layout)
304///     .label("camera_bind_group")
305///     .buffer(&camera_buffer)
306///     .build(&backend);
307/// ```
308pub struct BindGroupBuilder<'a> {
309    label: Option<&'a str>,
310    layout: &'a wgpu::BindGroupLayout,
311    entries: Vec<wgpu::BindGroupEntry<'a>>,
312    next_binding: u32,
313}
314
315impl<'a> BindGroupBuilder<'a> {
316    pub fn new(layout: &'a BindGroupLayout) -> Self {
317        Self::new_raw(layout.raw())
318    }
319
320    /// Internal primitive behind [`new`](Self::new) — used directly only by
321    /// code with its own raw `wgpu::BindGroupLayout` that never goes through
322    /// [`BindGroupLayoutBuilder`](super::binding::BindGroupLayoutBuilder)
323    /// (mipmap generation's fixed-shape blit layout).
324    pub(crate) fn new_raw(layout: &'a wgpu::BindGroupLayout) -> Self {
325        Self { label: None, layout, entries: Vec::new(), next_binding: 0 }
326    }
327
328    pub fn label(mut self, label: impl Into<Option<&'a str>>) -> Self {
329        self.label = label.into();
330        self
331    }
332
333    /// Binds `buffer` in its entirety at the next `@binding(N)` (call order,
334    /// starting at 0).
335    pub fn buffer(self, buffer: &'a Buffer) -> Self {
336        let binding = self.next_binding;
337        self.buffer_at(binding, buffer)
338    }
339
340    /// Same as [`buffer`](Self::buffer) but at an explicit `@binding(N)`.
341    pub fn buffer_at(mut self, binding: u32, buffer: &'a Buffer) -> Self {
342        self.entries.push(wgpu::BindGroupEntry { binding, resource: buffer.raw().as_entire_binding() });
343        self.next_binding = self.next_binding.max(binding + 1);
344        self
345    }
346
347    /// Binds `buffer` scoped to one element (see [`DynamicBuffer::element_size`])
348    /// at the next `@binding(N)`.
349    pub fn dynamic_buffer(self, buffer: &'a DynamicBuffer) -> Self {
350        let binding = self.next_binding;
351        self.dynamic_buffer_at(binding, buffer)
352    }
353
354    /// Same as [`dynamic_buffer`](Self::dynamic_buffer) but at an explicit `@binding(N)`.
355    pub fn dynamic_buffer_at(mut self, binding: u32, buffer: &'a DynamicBuffer) -> Self {
356        let resource = dynamic_buffer_binding(buffer.buffer.raw(), buffer.element_size);
357        self.entries.push(wgpu::BindGroupEntry { binding, resource });
358        self.next_binding = self.next_binding.max(binding + 1);
359        self
360    }
361
362    /// Binds a 2D texture's view at the next `@binding(N)`.
363    pub fn texture_2d(self, texture: &'a GPUTexture) -> Self {
364        let binding = self.next_binding;
365        self.texture_2d_at(binding, texture)
366    }
367
368    /// Same as [`texture_2d`](Self::texture_2d) but at an explicit `@binding(N)`.
369    pub fn texture_2d_at(self, binding: u32, texture: &'a GPUTexture) -> Self {
370        self.texture_view_raw_at(binding, texture.view())
371    }
372
373    /// Binds a texture array's view at the next `@binding(N)`.
374    pub fn texture_array(self, texture: &'a GPUTextureArray) -> Self {
375        let binding = self.next_binding;
376        self.texture_array_at(binding, texture)
377    }
378
379    /// Same as [`texture_array`](Self::texture_array) but at an explicit `@binding(N)`.
380    pub fn texture_array_at(self, binding: u32, texture: &'a GPUTextureArray) -> Self {
381        self.texture_view_raw_at(binding, texture.view())
382    }
383
384    /// Binds a cubemap's view at the next `@binding(N)`.
385    pub fn texture_cubemap(self, texture: &'a GPUCubemap) -> Self {
386        let binding = self.next_binding;
387        self.texture_cubemap_at(binding, texture)
388    }
389
390    /// Same as [`texture_cubemap`](Self::texture_cubemap) but at an explicit `@binding(N)`.
391    pub fn texture_cubemap_at(self, binding: u32, texture: &'a GPUCubemap) -> Self {
392        self.texture_view_raw_at(binding, texture.view())
393    }
394
395    /// Binds an opaque [`TextureView`] — a render target built via
396    /// [`TextureBuilder`](super::texture_view::TextureBuilder)/
397    /// [`GPUCubemap::face_attachment`](GPUCubemap::face_attachment) — at the
398    /// next `@binding(N)`, for sampling it back in a later pass (a shadow
399    /// map, a post-process input, ...).
400    pub fn texture_view(self, view: &'a TextureView) -> Self {
401        let binding = self.next_binding;
402        self.texture_view_at(binding, view)
403    }
404
405    /// Same as [`texture_view`](Self::texture_view) but at an explicit `@binding(N)`.
406    pub fn texture_view_at(self, binding: u32, view: &'a TextureView) -> Self {
407        self.texture_view_raw_at(binding, view.raw())
408    }
409
410    /// Low-level primitive behind every `texture_*` method above — kept
411    /// `pub(crate)` for internal code (mipmap generation's blit pass) that
412    /// binds an ad-hoc single-mip-level view rather than a whole
413    /// [`GPUTexture`]/[`GPUTextureArray`]/[`GPUCubemap`]/[`TextureView`].
414    pub(crate) fn texture_view_raw_at(mut self, binding: u32, view: &'a wgpu::TextureView) -> Self {
415        self.entries.push(wgpu::BindGroupEntry { binding, resource: wgpu::BindingResource::TextureView(view) });
416        self.next_binding = self.next_binding.max(binding + 1);
417        self
418    }
419
420    /// Binds `sampler` at the next `@binding(N)`.
421    pub fn sampler(self, sampler: &'a Sampler) -> Self {
422        let binding = self.next_binding;
423        self.sampler_at(binding, sampler)
424    }
425
426    /// Same as [`sampler`](Self::sampler) but at an explicit `@binding(N)`.
427    pub fn sampler_at(self, binding: u32, sampler: &'a Sampler) -> Self {
428        self.sampler_raw_at(binding, sampler.raw())
429    }
430
431    /// Low-level primitive behind [`sampler`](Self::sampler) — kept
432    /// `pub(crate)` for the same internal reason as
433    /// [`texture_view_raw_at`](Self::texture_view_raw_at).
434    pub(crate) fn sampler_raw_at(mut self, binding: u32, sampler: &'a wgpu::Sampler) -> Self {
435        self.entries.push(wgpu::BindGroupEntry { binding, resource: wgpu::BindingResource::Sampler(sampler) });
436        self.next_binding = self.next_binding.max(binding + 1);
437        self
438    }
439
440    pub fn build(self, backend: &WGPUBackend) -> BindGroup {
441        BindGroup(self.build_raw(&backend.device))
442    }
443
444    /// Internal primitive behind [`build`](Self::build) — used directly only
445    /// by code that needs a raw `wgpu::BindGroup` to feed into a raw
446    /// `wgpu::RenderPass` it built itself (mipmap generation's blit pass).
447    pub(crate) fn build_raw(self, device: &wgpu::Device) -> wgpu::BindGroup {
448        device.create_bind_group(&wgpu::BindGroupDescriptor {
449            label: self.label,
450            layout: self.layout,
451            entries: &self.entries,
452        })
453    }
454}
455
456#[cfg(test)]
457mod tests {
458    use super::*;
459    use crate::wgpu::test_util::with_device;
460
461    #[test]
462    fn a_size_within_every_limit_does_not_panic() {
463        with_device!(device, _queue, {
464            BufferBuilder::empty(64).uniform().build_raw(&device);
465        });
466    }
467
468    #[test]
469    fn exceeding_max_buffer_size_panics() {
470        with_device!(device, _queue, {
471            let too_big = device.limits().max_buffer_size + 1;
472            let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
473                BufferBuilder::empty(too_big).usage(BufferUsages::COPY_DST).build_raw(&device);
474            }));
475            assert!(result.is_err(), "expected a panic for a size exceeding max_buffer_size");
476        });
477    }
478
479    #[test]
480    fn exceeding_max_uniform_buffer_binding_size_panics() {
481        with_device!(device, _queue, {
482            let too_big = device.limits().max_uniform_buffer_binding_size + 1;
483            let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
484                BufferBuilder::empty(too_big).uniform().build_raw(&device);
485            }));
486            assert!(
487                result.is_err(),
488                "expected a panic for a size exceeding max_uniform_buffer_binding_size"
489            );
490        });
491    }
492
493    #[test]
494    fn exceeding_max_uniform_buffer_binding_size_is_fine_for_a_non_uniform_buffer() {
495        // Same size that panics as a uniform buffer above must NOT panic here — the tighter
496        // check only applies when `usage` actually includes UNIFORM (not STORAGE either, so
497        // this can't accidentally trip the sibling max_storage_buffer_binding_size check).
498        with_device!(device, _queue, {
499            let big = (device.limits().max_uniform_buffer_binding_size + 1).min(device.limits().max_buffer_size);
500            BufferBuilder::empty(big).usage(BufferUsages::COPY_DST).build_raw(&device);
501        });
502    }
503}