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}