Skip to main content

pebble/wgpu/
buffers.rs

1//! Buffer and bind-group construction helpers.
2//!
3//! Naming convention used throughout this file:
4//! - `build_*` — always allocates something new.
5//! - `resolve_*` — allocates from raw bytes (`BufferSource::Data`), or
6//!   passes an already-built buffer through unchanged (`BufferSource::Buffer`)
7//!   — "give me *a* buffer for this, new or not" rather than "make me one."
8//! - `update_*` — writes into a buffer that already exists; never allocates.
9//! - `dynamic_*` — the per-element-offset counterpart of a plain helper,
10//!   for packing many elements into one buffer and selecting between them
11//!   with `set_bind_group`'s dynamic offset instead of a bind group each.
12//!
13//! Sections below, roughly simple-to-complex: fresh buffers, resolving a
14//! [`BufferSource`], writing to an existing buffer, single-resource bind
15//! groups, dynamic-offset helpers, then the general multi-resource
16//! [`build_bind_group`] everything above is built from.
17
18/// Either raw bytes to upload into a fresh buffer, or an already-built
19/// buffer to use as-is. Accepted via `impl Into<BufferSource>` by the
20/// `resolve_*`/`build_*_bind_group` functions below, so callers can pass a
21/// `&[u8]` directly (the common case) without constructing this by hand.
22pub enum BufferSource<'a> {
23    /// Bytes to upload into a newly-created buffer.
24    Data(&'a [u8]),
25    /// A buffer that already exists — used as-is, no upload.
26    Buffer(wgpu::Buffer),
27}
28
29impl<'a> From<&'a [u8]> for BufferSource<'a> {
30    fn from(data: &'a [u8]) -> Self {
31        BufferSource::Data(data)
32    }
33}
34
35impl<'a> From<wgpu::Buffer> for BufferSource<'a> {
36    fn from(buffer: wgpu::Buffer) -> Self {
37        BufferSource::Buffer(buffer)
38    }
39}
40
41/// One resource to bind into a bind group, passed to [`build_bind_group`].
42/// Covers every binding kind [`BindingKind`](super::binding::BindingKind)
43/// can describe.
44pub enum BindingResource<'a> {
45    /// A uniform buffer, built from or passed through via [`BufferSource`].
46    UniformBuffer(BufferSource<'a>),
47    /// A storage buffer, built from or passed through via [`BufferSource`].
48    StorageBuffer(BufferSource<'a>),
49    /// A uniform buffer bound with a dynamic offset. The bind group entry is scoped to a
50    /// single `element_size`-sized element (see [`dynamic_buffer_binding`]) rather than the
51    /// whole buffer, so it works with the dynamic offset passed to `set_bind_group`.
52    DynamicUniformBuffer { buffer: wgpu::Buffer, element_size: u64 },
53    /// Same as `DynamicUniformBuffer` but for a storage buffer.
54    DynamicStorageBuffer { buffer: wgpu::Buffer, element_size: u64 },
55    /// A texture view (e.g. for a `texture_2d` shader binding).
56    TextureView(&'a wgpu::TextureView),
57    /// A sampler.
58    Sampler(&'a wgpu::Sampler),
59}
60
61// ---------------------------------------------------------------------
62// Fresh buffers
63// ---------------------------------------------------------------------
64
65/// Creates a buffer pre-populated with `contents`.
66pub fn build_buffer(device: &wgpu::Device, contents: &[u8], usage: wgpu::BufferUsages) -> wgpu::Buffer {
67    use wgpu::util::DeviceExt;
68    device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
69        label: None,
70        contents,
71        usage,
72    })
73}
74
75/// Creates an empty buffer of `size` bytes, to be written into later (e.g.
76/// via [`update_buffer`]).
77pub fn build_buffer_sized(device: &wgpu::Device, size: u64, usage: wgpu::BufferUsages) -> wgpu::Buffer {
78    device.create_buffer(&wgpu::BufferDescriptor {
79        label: None,
80        size,
81        usage,
82        mapped_at_creation: false,
83    })
84}
85
86// ---------------------------------------------------------------------
87// Resolving a BufferSource — build fresh from bytes, or pass an existing
88// buffer through unchanged.
89// ---------------------------------------------------------------------
90
91/// Builds a fresh buffer from `Data`, or passes an already-built `Buffer`
92/// straight through unchanged.
93pub fn resolve_buffer(device: &wgpu::Device, source: BufferSource<'_>, usage: wgpu::BufferUsages) -> wgpu::Buffer {
94    match source {
95        BufferSource::Data(data) => build_buffer(device, data, usage),
96        BufferSource::Buffer(buffer) => buffer,
97    }
98}
99
100/// [`resolve_buffer`] with `UNIFORM | COPY_DST` usage.
101pub fn resolve_uniform_buffer(device: &wgpu::Device, source: BufferSource<'_>) -> wgpu::Buffer {
102    resolve_buffer(device, source, wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST)
103}
104
105/// [`resolve_buffer`] with `STORAGE | COPY_DST` usage.
106pub fn resolve_storage_buffer(device: &wgpu::Device, source: BufferSource<'_>) -> wgpu::Buffer {
107    resolve_buffer(device, source, wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_DST)
108}
109
110// ---------------------------------------------------------------------
111// Writing to an existing buffer
112// ---------------------------------------------------------------------
113
114/// Overwrites `buffer`'s contents with `data`, starting at offset 0. Plain
115/// `queue.write_buffer` underneath — works for any buffer usage, not just
116/// uniform buffers, despite the neighboring `update_buffer_at`'s
117/// dynamic-offset framing.
118pub fn update_buffer(queue: &wgpu::Queue, buffer: &wgpu::Buffer, data: &[u8]) {
119    queue.write_buffer(buffer, 0, data);
120}
121
122/// Writes `data` into `buffer` at a byte offset, for updating one element of a
123/// dynamically-offset buffer without touching the others. `offset` should be a
124/// multiple of the stride returned by [`dynamic_uniform_offset_stride`]
125/// (or [`dynamic_storage_offset_stride`], for a storage buffer).
126pub fn update_buffer_at(queue: &wgpu::Queue, buffer: &wgpu::Buffer, offset: u64, data: &[u8]) {
127    queue.write_buffer(buffer, offset, data);
128}
129
130// ---------------------------------------------------------------------
131// Single-resource bind groups
132// ---------------------------------------------------------------------
133
134/// Resolves `source` into a uniform buffer and builds a single-entry bind
135/// group for it against `layout`, returning both.
136pub fn build_uniform_bind_group<'a>(
137    device: &wgpu::Device,
138    layout: &wgpu::BindGroupLayout,
139    source: impl Into<BufferSource<'a>>,
140) -> (wgpu::Buffer, wgpu::BindGroup) {
141    let (mut buffers, bind_group) = build_bind_group(device, layout, vec![BindingResource::UniformBuffer(source.into())]);
142    (buffers.remove(0), bind_group)
143}
144
145/// Same as [`build_uniform_bind_group`] but for a storage buffer.
146pub fn build_storage_bind_group<'a>(
147    device: &wgpu::Device,
148    layout: &wgpu::BindGroupLayout,
149    source: impl Into<BufferSource<'a>>,
150) -> (wgpu::Buffer, wgpu::BindGroup) {
151    let (mut buffers, bind_group) = build_bind_group(device, layout, vec![BindingResource::StorageBuffer(source.into())]);
152    (buffers.remove(0), bind_group)
153}
154
155/// Allocates a dynamically-offset uniform buffer sized for `count` elements and builds a
156/// bind group for it in one step. Returns the buffer, the per-element stride to use as the
157/// dynamic offset in `set_bind_group`, and the bind group. Use with a layout built from
158/// [`BindingKind::dynamic_uniform_buffer`](super::binding::BindingKind::dynamic_uniform_buffer).
159pub fn build_dynamic_uniform_bind_group(
160    device: &wgpu::Device,
161    layout: &wgpu::BindGroupLayout,
162    element_size: u64,
163    count: u64,
164) -> (wgpu::Buffer, u64, wgpu::BindGroup) {
165    let (buffer, stride) = build_dynamic_uniform_buffer(device, element_size, count);
166    let (mut buffers, bind_group) =
167        build_bind_group(device, layout, vec![BindingResource::DynamicUniformBuffer { buffer, element_size }]);
168    (buffers.remove(0), stride, bind_group)
169}
170
171/// Same as [`build_dynamic_uniform_bind_group`] but for a storage buffer.
172pub fn build_dynamic_storage_bind_group(
173    device: &wgpu::Device,
174    layout: &wgpu::BindGroupLayout,
175    element_size: u64,
176    count: u64,
177) -> (wgpu::Buffer, u64, wgpu::BindGroup) {
178    let (buffer, stride) = build_dynamic_storage_buffer(device, element_size, count);
179    let (mut buffers, bind_group) =
180        build_bind_group(device, layout, vec![BindingResource::DynamicStorageBuffer { buffer, element_size }]);
181    (buffers.remove(0), stride, bind_group)
182}
183
184// ---------------------------------------------------------------------
185// Dynamic-offset helpers
186// ---------------------------------------------------------------------
187
188/// Rounds `element_size` up to the device's required alignment for dynamic offsets on
189/// uniform buffers, giving the stride to use when packing multiple elements into one
190/// buffer for use with [`BindingKind::dynamic_uniform_buffer`](super::binding::BindingKind::dynamic_uniform_buffer).
191pub fn dynamic_uniform_offset_stride(device: &wgpu::Device, element_size: u64) -> u64 {
192    align_to(element_size, device.limits().min_uniform_buffer_offset_alignment as u64)
193}
194
195/// Same as [`dynamic_uniform_offset_stride`] but for storage buffers.
196pub fn dynamic_storage_offset_stride(device: &wgpu::Device, element_size: u64) -> u64 {
197    align_to(element_size, device.limits().min_storage_buffer_offset_alignment as u64)
198}
199
200fn align_to(size: u64, alignment: u64) -> u64 {
201    size.div_ceil(alignment) * alignment
202}
203
204/// Builds the bind group entry resource for a dynamically-offset binding. Unlike
205/// `buffer.as_entire_binding()`, this scopes the entry to a single `element_size`-sized
206/// element starting at offset 0 in the buffer — required because the dynamic offset passed
207/// to `set_bind_group` at draw/dispatch time is added on top of this base range, and wgpu
208/// validates `offset + size <= buffer size`. Binding the whole buffer here would make any
209/// nonzero dynamic offset fail validation.
210pub fn dynamic_buffer_binding(buffer: &wgpu::Buffer, element_size: u64) -> wgpu::BindingResource<'_> {
211    wgpu::BindingResource::Buffer(wgpu::BufferBinding {
212        buffer,
213        offset: 0,
214        size: wgpu::BufferSize::new(element_size),
215    })
216}
217
218/// Builds an empty buffer sized to hold `count` elements of a dynamically-offset uniform
219/// buffer, and returns the buffer along with the per-element stride to use as dynamic
220/// offsets in `RenderPass::set_bind_group`.
221pub fn build_dynamic_uniform_buffer(device: &wgpu::Device, element_size: u64, count: u64) -> (wgpu::Buffer, u64) {
222    let stride = dynamic_uniform_offset_stride(device, element_size);
223    let buffer = build_buffer_sized(device, stride * count, wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST);
224    (buffer, stride)
225}
226
227/// Builds an empty buffer sized to hold `count` elements of a dynamically-offset storage
228/// buffer, and returns the buffer along with the per-element stride.
229pub fn build_dynamic_storage_buffer(device: &wgpu::Device, element_size: u64, count: u64) -> (wgpu::Buffer, u64) {
230    let stride = dynamic_storage_offset_stride(device, element_size);
231    let buffer = build_buffer_sized(device, stride * count, wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_DST);
232    (buffer, stride)
233}
234
235// ---------------------------------------------------------------------
236// General multi-resource bind group — the primitive every build_*_bind_group
237// helper above is built from. Reach for it directly when you need more than
238// one binding (a texture + sampler + uniform buffer, say) in a single group.
239// ---------------------------------------------------------------------
240
241/// Builds a bind group from multiple resources. Buffers created from `Data` are returned
242/// in order (texture views and samplers are not returned). Pre-built buffers passed via
243/// `Buffer` are consumed and also returned.
244pub fn build_bind_group<'a>(
245    device: &wgpu::Device,
246    layout: &wgpu::BindGroupLayout,
247    resources: Vec<BindingResource<'a>>,
248) -> (Vec<wgpu::Buffer>, wgpu::BindGroup) {
249    enum Resolved<'a> {
250        Buffer(wgpu::Buffer),
251        DynamicBuffer(wgpu::Buffer, u64),
252        TextureView(&'a wgpu::TextureView),
253        Sampler(&'a wgpu::Sampler),
254    }
255
256    let resolved: Vec<Resolved> = resources
257        .into_iter()
258        .map(|r| match r {
259            BindingResource::UniformBuffer(src) => Resolved::Buffer(resolve_uniform_buffer(device, src)),
260            BindingResource::StorageBuffer(src) => Resolved::Buffer(resolve_storage_buffer(device, src)),
261            BindingResource::DynamicUniformBuffer { buffer, element_size } => Resolved::DynamicBuffer(buffer, element_size),
262            BindingResource::DynamicStorageBuffer { buffer, element_size } => Resolved::DynamicBuffer(buffer, element_size),
263            BindingResource::TextureView(view) => Resolved::TextureView(view),
264            BindingResource::Sampler(sampler) => Resolved::Sampler(sampler),
265        })
266        .collect();
267
268    let bind_group = {
269        let entries: Vec<wgpu::BindGroupEntry> = resolved
270            .iter()
271            .enumerate()
272            .map(|(i, r)| wgpu::BindGroupEntry {
273                binding: i as u32,
274                resource: match r {
275                    Resolved::Buffer(buf) => buf.as_entire_binding(),
276                    Resolved::DynamicBuffer(buf, element_size) => dynamic_buffer_binding(buf, *element_size),
277                    Resolved::TextureView(view) => wgpu::BindingResource::TextureView(view),
278                    Resolved::Sampler(sampler) => wgpu::BindingResource::Sampler(sampler),
279                },
280            })
281            .collect();
282
283        device.create_bind_group(&wgpu::BindGroupDescriptor {
284            label: None,
285            layout,
286            entries: &entries,
287        })
288    };
289
290    let buffers = resolved
291        .into_iter()
292        .filter_map(|r| match r {
293            Resolved::Buffer(buf) => Some(buf),
294            Resolved::DynamicBuffer(buf, _) => Some(buf),
295            _ => None,
296        })
297        .collect();
298
299    (buffers, bind_group)
300}