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