1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
use std::marker::PhantomData;
use bevy::{
log::trace,
render::{
render_resource::{
BindingResource, Buffer, BufferAddress, BufferDescriptor, BufferUsages, ShaderSize,
ShaderType,
},
renderer::RenderDevice,
},
};
use bytemuck::Pod;
use wgpu::CommandEncoder;
struct BufferAndSize {
/// Allocate GPU buffer.
pub buffer: Buffer,
/// Size of the buffer, in number of elements.
pub size: u32,
}
/// GPU-only buffer without CPU-side storage.
///
/// This is a rather specialized helper to allocate an array on the GPU and
/// manage its buffer, depending on the device constraints and the WGSL rules
/// for data alignment, and allowing to resize the buffer without losing its
/// content (so, scheduling a buffer-to-buffer copy on GPU after reallocatin).
///
/// The element type `T` needs to implement the following traits:
/// - [`Pod`] to prevent user error. This is not strictly necessary, as there's
/// no copy from or to CPU, but if the placeholder type is not POD this might
/// indicate some user error.
/// - [`ShaderSize`] to ensure a fixed footprint, to allow packing multiple
/// instances inside a single buffer. This therefore excludes any
/// runtime-sized array (T being the element type here; it will itself be part
/// of an array).
pub struct GpuBuffer<T: Pod + ShaderSize> {
/// GPU buffer if already allocated, or `None` otherwise.
buffer: Option<BufferAndSize>,
/// Previous GPU buffer, pending copy.
old_buffer: Option<BufferAndSize>,
/// GPU buffer usages.
buffer_usage: BufferUsages,
/// Optional GPU buffer name, for debugging.
label: Option<String>,
/// Used size, in element count. Elements past this are all free. Elements
/// with a lower index are either allocated or in the free list.
used_size: u32,
/// Free list.
free_list: Vec<u32>,
_phantom: PhantomData<T>,
}
impl<T: Pod + ShaderType + ShaderSize> Default for GpuBuffer<T> {
fn default() -> Self {
Self {
buffer: None,
old_buffer: None,
buffer_usage: BufferUsages::all(),
label: None,
used_size: 0,
free_list: vec![],
_phantom: PhantomData,
}
}
}
impl<T: Pod + ShaderType + ShaderSize> GpuBuffer<T> {
/// Create a new collection.
///
/// The buffer usage is always augmented by [`BufferUsages::COPY_SRC`] and
/// [`BufferUsages::COPY_DST`] in order to allow buffer-to-buffer copy when
/// reallocating, to preserve old content.
///
/// # Panics
///
/// Panics if `buffer_usage` contains [`BufferUsages::UNIFORM`] and the
/// layout of the element type `T` does not meet the requirements of the
/// uniform address space, as tested by
/// [`ShaderType::assert_uniform_compat()`].
///
/// [`BufferUsages::UNIFORM`]: bevy::render::render_resource::BufferUsages::UNIFORM
#[allow(dead_code)]
pub fn new(buffer_usage: BufferUsages, label: Option<String>) -> Self {
// GPU-aligned item size, compatible with WGSL rules
let item_size = <T as ShaderSize>::SHADER_SIZE.get() as usize;
trace!("GpuBuffer: item_size={}", item_size);
if buffer_usage.contains(BufferUsages::UNIFORM) {
<T as ShaderType>::assert_uniform_compat();
}
Self {
// We need both COPY_SRC and COPY_DST for copy_buffer_to_buffer() on realloc
buffer_usage: buffer_usage | BufferUsages::COPY_SRC | BufferUsages::COPY_DST,
label,
..Default::default()
}
}
/// Create a new collection from an allocated buffer.
///
/// The buffer usage must contain [`BufferUsages::COPY_SRC`] and
/// [`BufferUsages::COPY_DST`] in order to allow buffer-to-buffer copy when
/// reallocating, to preserve old content.
///
/// # Panics
///
/// Panics if `buffer_usage` doesn't contain [`BufferUsages::COPY_SRC`] or
/// [`BufferUsages::COPY_DST`].
///
/// Panics if `buffer_usage` contains [`BufferUsages::UNIFORM`] and the
/// layout of the element type `T` does not meet the requirements of the
/// uniform address space, as tested by
/// [`ShaderType::assert_uniform_compat()`].
///
/// [`BufferUsages::UNIFORM`]: bevy::render::render_resource::BufferUsages::UNIFORM
pub fn new_allocated(buffer: Buffer, label: Option<String>) -> Self {
// GPU-aligned item size, compatible with WGSL rules.
let item_size = <T as ShaderSize>::SHADER_SIZE.get();
let buffer_usage = buffer.usage();
assert!(
buffer_usage.contains(BufferUsages::COPY_SRC | BufferUsages::COPY_DST),
"GpuBuffer requires COPY_SRC and COPY_DST buffer usages to allow copy on reallocation."
);
if buffer_usage.contains(BufferUsages::UNIFORM) {
<T as ShaderType>::assert_uniform_compat();
}
// Capacity is derived from the physical buffer so the two can't disagree.
debug_assert_eq!(
buffer.size() % item_size,
0,
"GpuBuffer physical size ({} bytes) is not a multiple of the element size ({} bytes)",
buffer.size(),
item_size,
);
let size = (buffer.size() / item_size) as u32;
trace!("GpuBuffer: item_size={item_size} capacity={size}");
Self {
buffer: Some(BufferAndSize { buffer, size }),
buffer_usage,
label,
..Default::default()
}
}
/// Clear the buffer.
///
/// This doesn't de-allocate any GPU buffer.
pub fn clear(&mut self) {
self.free_list.clear();
self.used_size = 0;
}
/// Allocate a new entry in the buffer.
///
/// If the GPU buffer has not enough storage, or is not allocated yet, this
/// schedules a (re-)allocation, which must be applied by calling
/// [`allocate_gpu()`] once a frame after all [`allocate()`] calls were made
/// for that frame.
///
/// # Returns
///
/// The index of the allocated entry.
///
/// [`allocate_gpu()`]: Self::allocate_gpu
/// [`allocate()`]: Self::allocate
pub fn allocate(&mut self) -> u32 {
if let Some(index) = self.free_list.pop() {
index
} else {
// Note: we may return an index past the buffer capacity. This will instruct
// allocate_gpu() to re-allocate the buffer.
let index = self.used_size;
self.used_size += 1;
index
}
}
/// Free an existing entry.
///
/// # Panics
///
/// In debug only, panics if the entry is not allocated (double-free). In
/// non-debug, the behavior is undefined and will generally lead to bugs.
// Currently we use GpuBuffer in sorting, and re-allocate everything each frame.
#[allow(dead_code)]
pub fn free(&mut self, index: u32) {
if index < self.used_size {
debug_assert!(
!self.free_list.contains(&index),
"Double-free in GpuBuffer at index #{}",
index
);
self.free_list.push(index);
}
}
/// Get the current GPU buffer, if allocated.
#[inline]
pub fn buffer(&self) -> Option<&Buffer> {
self.buffer.as_ref().map(|b| &b.buffer)
}
/// Get a binding for the entire GPU buffer, if allocated.
#[inline]
#[allow(dead_code)]
pub fn as_entire_binding(&self) -> Option<BindingResource<'_>> {
let buffer = self.buffer()?;
Some(buffer.as_entire_binding())
}
/// Get the current buffer capacity, in element count.
///
/// This is the CPU view of allocations, which counts the number of
/// [`allocate()`] and [`free()`] calls.
///
/// [`allocate()`]: Self::allocate
/// [`free()`]: Self::allocate_gpu
#[inline]
#[allow(dead_code)]
pub fn capacity(&self) -> u32 {
debug_assert!(self.used_size >= self.free_list.len() as u32);
self.used_size - self.free_list.len() as u32
}
/// Get the current GPU buffer capacity, in element count.
///
/// Note that it is possible for [`allocate()`] to return an index greater
/// than or equal to the value returned by [`capacity()`], at least
/// temporarily until [`allocate_gpu()`] is called.
///
/// [`allocate()`]: Self::allocate
/// [`gpu_capacity()`]: Self::gpu_capacity
/// [`allocate_gpu()`]: Self::allocate_gpu
#[inline]
pub fn gpu_capacity(&self) -> u32 {
self.buffer.as_ref().map(|b| b.size).unwrap_or(0)
}
/// Size in bytes of a single item in the buffer.
///
/// This is equal to [`ShaderSize::SHADER_SIZE`] for the buffer element `T`.
#[inline]
pub fn item_size(&self) -> usize {
<T as ShaderSize>::SHADER_SIZE.get() as usize
}
/// Check if the buffer is empty.
///
/// The check is based on the CPU representation of the buffer, that is the
/// number of calls to [`allocate()`]. The buffer is considered empty if no
/// [`allocate()`] call was made, or they all have been followed by a
/// corresponding [`free()`] call. This makes no assumption about the GPU
/// buffer.
///
/// [`allocate()`]: Self::allocate
/// [`free()`]: Self::free
#[inline]
#[allow(dead_code)]
pub fn is_empty(&self) -> bool {
self.used_size == 0
}
/// Allocate or reallocate the GPU buffer if needed.
///
/// This allocates or reallocates a GPU buffer to ensure storage for all
/// previous calls to [`allocate()`]. This is a no-op if a GPU buffer is
/// already allocated and has sufficient storage.
///
/// This should be called once a frame after any new [`allocate()`] in that
/// frame. After this call, [`buffer()`] is guaranteed to return `Some(..)`.
///
/// # Returns
///
/// `true` if the buffer was (re)allocated, or `false` if an existing buffer
/// was reused which already had enough capacity.
///
/// [`reserve()`]: Self::reserve
/// [`allocate()`]: Self::allocate
/// [`buffer()`]: Self::buffer
pub fn prepare_buffers(&mut self, render_device: &RenderDevice) -> bool {
// Don't do anything if we still have some storage.
let old_capacity = self.gpu_capacity();
if self.used_size <= old_capacity {
return false;
}
// Ensure we allocate at least 256 more entries than what we need this frame,
// and round that to make it nicer for the GPU.
let new_capacity = (self.used_size + 256).next_multiple_of(1024);
if new_capacity <= old_capacity {
return false;
}
// Save the old buffer, we will need to copy it to the new one later.
assert!(self.old_buffer.is_none(), "Multiple calls to GpuTable::prepare_buffers() before write_buffers() was called to copy old content.");
self.old_buffer = self.buffer.take();
// Allocate a new buffer of the appropriate size.
let byte_size = self.item_size() * new_capacity as usize;
trace!(
"prepare_buffers(): increase capacity from {} to {} elements, new size {} bytes",
old_capacity,
new_capacity,
byte_size
);
let buffer = render_device.create_buffer(&BufferDescriptor {
label: self.label.as_ref().map(|s| &s[..]),
size: byte_size as BufferAddress,
usage: BufferUsages::COPY_DST | self.buffer_usage,
mapped_at_creation: false,
});
self.buffer = Some(BufferAndSize {
buffer,
size: new_capacity,
});
true
}
/// Schedule any pending buffer copy.
///
/// If a new buffer was (re-)allocated this frame, this schedules a
/// buffer-to-buffer copy from the old buffer to the new one, then releases
/// the old buffer.
///
/// This should be called once a frame after [`prepare_buffers()`]. This is
/// a no-op if there's no need for a buffer copy.
///
/// [`prepare_buffers()`]: Self::prepare_buffers
pub fn write_buffers(&self, command_encoder: &mut CommandEncoder) {
if let Some(old_buffer) = self.old_buffer.as_ref() {
let new_buffer = self.buffer.as_ref().unwrap();
assert!(
new_buffer.size >= old_buffer.size,
"Old buffer is smaller than the new one. This is unexpected."
);
command_encoder.copy_buffer_to_buffer(
&old_buffer.buffer,
0,
&new_buffer.buffer,
0,
old_buffer.size as u64,
);
}
}
/// Clear any stale buffer used for resize in the previous frame during
/// rendering while the data structure was immutable.
///
/// This must be called before any new [`allocate()`].
///
/// [`allocate()`]: Self::allocate
pub fn clear_previous_frame_resizes(&mut self) {
if let Some(old_buffer) = self.old_buffer.take() {
old_buffer.buffer.destroy();
}
}
}