• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

djeedai / bevy_hanabi / 22800469877

07 Mar 2026 02:04PM UTC coverage: 57.546% (-0.2%) from 57.75%
22800469877

push

github

web-flow
Batch spawners and properties (#525)

Bind the entire spawner and property arrays in all passes, instead of a
single entry. This removes the need to pad those structures. Access them
via an offset in the effect's own metadata.

Add a new `BatchInfo` struct holding the per-batch data. This clarifies
the responsibilites between this and `EffectMetadata`, the latter
holding per-effect (not per-batch) data.

Remove the `DispatchBufferIndices` component, which was used to track
the allocated entry for indirect compute dispatch. Instead, align those
allocations 1:1 with the GPU batch info allocations, which are
re-computed each frame.

Add a prefix sum pass before the update pass, which computes the prefix
sum of alive particles after the init pass. This is used to enable
batched update compute dispatch, where the number of compute threads
maps to the total number of alive particles in the batch. In that case,
we need to find which thread updates which particle of which batch,
using that prefix sum. Note that in this change, due to other
limitations still present, each effect instance is still in its own
batch (there's effectively no batching). Enabling full batching requires
more work, notably on the sort pass for ribbons, and the GPU-based init
pass with GPU events.

Change the allocation of spawners to occur after sorting. This ensures
all effects in a same batch have sequential allocations, which enables
accessing those spawners with a simple {offset + index} strategy.

Change the render pass to use the same bind group "spawner@2" than other
passes. This binds the property buffer, although in this change the
metadata buffer is still not available, so properties can't be used yet
in the render pass. The bind groups should be reviewed anyway because,
with batching approaching, and with the current change, assumptions
about frequency of changes is now wrong, and individual bindings should
be re-grouped in more suitable frequency-based groups.

Finally, the... (continued)

193 of 404 new or added lines in 7 files covered. (47.77%)

26 existing lines in 3 files now uncovered.

4793 of 8329 relevant lines covered (57.55%)

198.51 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

51.88
/src/render/aligned_buffer_vec.rs
1
use std::{num::NonZeroU64, ops::Range};
2

3
use bevy::{
4
    log::trace,
5
    render::{
6
        render_resource::{
7
            BindingResource, Buffer, BufferAddress, BufferBinding, BufferDescriptor, BufferUsages,
8
            ShaderSize, ShaderType,
9
        },
10
        renderer::{RenderDevice, RenderQueue},
11
    },
12
};
13
use bytemuck::{cast_slice, Pod};
14

15
/// Like Bevy's [`BufferVec`], but with extra per-item alignment.
16
///
17
/// This helper ensures the individual array elements are properly aligned,
18
/// depending on the device constraints and the WGSL rules. In general using
19
/// [`BufferVec`] is enough to ensure alignment; however when some array items
20
/// also need to be bound individually, then each item (not only the array
21
/// itself) needs to be aligned to the device requirements. This is admittedly a
22
/// very specific case, because the device alignment might be very large (256
23
/// bytes) and this causes a lot of wasted space (padding per-element, instead
24
/// of padding for the entire array).
25
///
26
/// For this buffer to work correctly and items be bindable individually, the
27
/// alignment must come from one of the [`WgpuLimits`]. For example for a
28
/// storage buffer, to be able to bind the entire buffer but also any subset of
29
/// it (including individual elements), the extra alignment must
30
/// be [`WgpuLimits::min_storage_buffer_offset_alignment`].
31
///
32
/// The element type `T` needs to implement the following traits:
33
/// - [`Pod`] to allow copy.
34
/// - [`ShaderType`] because it needs to be mapped for a shader.
35
/// - [`ShaderSize`] to ensure a fixed footprint, to allow packing multiple
36
///   instances inside a single buffer. This therefore excludes any
37
///   runtime-sized array.
38
///
39
/// [`BufferVec`]: bevy::render::render_resource::BufferVec
40
/// [`WgpuLimits`]: bevy::render::settings::WgpuLimits
41
pub struct AlignedBufferVec<T: Pod + ShaderSize> {
42
    /// Pending values accumulated on CPU and not yet written to GPU.
43
    values: Vec<T>,
44
    /// GPU buffer if already allocated, or `None` otherwise.
45
    buffer: Option<Buffer>,
46
    /// Capacity of the buffer, in number of elements.
47
    capacity: usize,
48
    /// Size of a single buffer element, in bytes, in CPU memory (Rust layout).
49
    item_size: usize,
50
    /// Size of a single buffer element, in bytes, aligned to GPU memory
51
    /// constraints.
52
    aligned_size: usize,
53
    /// GPU buffer usages.
54
    buffer_usage: BufferUsages,
55
    /// Optional GPU buffer name, for debugging.
56
    label: Option<String>,
57
}
58

59
impl<T: Pod + ShaderSize> Default for AlignedBufferVec<T> {
60
    fn default() -> Self {
32✔
61
        let item_size = std::mem::size_of::<T>();
64✔
62
        let aligned_size = <T as ShaderSize>::SHADER_SIZE.get() as usize;
64✔
63
        assert!(aligned_size >= item_size);
64✔
64
        Self {
65
            values: Vec::new(),
64✔
66
            buffer: None,
67
            capacity: 0,
68
            buffer_usage: BufferUsages::all(),
64✔
69
            item_size,
70
            aligned_size,
71
            label: None,
72
        }
73
    }
74
}
75

76
impl<T: Pod + ShaderSize> AlignedBufferVec<T> {
77
    /// Create a new collection.
78
    ///
79
    /// `item_align` is an optional additional alignment for items in the
80
    /// collection. If greater than the natural alignment dictated by WGSL
81
    /// rules, this extra alignment is enforced. Otherwise it's ignored (so you
82
    /// can pass `0` to ignore).
83
    ///
84
    /// # Panics
85
    ///
86
    /// Panics if `buffer_usage` contains [`BufferUsages::UNIFORM`] and the
87
    /// layout of the element type `T` does not meet the requirements of the
88
    /// uniform address space, as tested by
89
    /// [`ShaderType::assert_uniform_compat()`].
90
    ///
91
    /// [`BufferUsages::UNIFORM`]: bevy::render::render_resource::BufferUsages::UNIFORM
92
    pub fn new(
32✔
93
        buffer_usage: BufferUsages,
94
        item_align: Option<NonZeroU64>,
95
        label: Option<String>,
96
    ) -> Self {
97
        // GPU-aligned item size, compatible with WGSL rules
98
        let item_size = <T as ShaderSize>::SHADER_SIZE.get() as usize;
64✔
99
        // Extra manual alignment for device constraints
100
        let aligned_size = if let Some(item_align) = item_align {
93✔
101
            let item_align = item_align.get() as usize;
×
102
            let aligned_size = item_size.next_multiple_of(item_align);
×
103
            assert!(aligned_size >= item_size);
×
104
            assert!(aligned_size.is_multiple_of(item_align));
116✔
105
            aligned_size
29✔
106
        } else {
107
            item_size
3✔
108
        };
109
        trace!(
×
110
            "AlignedBufferVec['{}']: item_size={} aligned_size={}",
×
111
            label.as_ref().map(|s| &s[..]).unwrap_or(""),
36✔
112
            item_size,
×
113
            aligned_size
×
114
        );
115
        if buffer_usage.contains(BufferUsages::UNIFORM) {
4✔
116
            <T as ShaderType>::assert_uniform_compat();
4✔
117
        }
118
        Self {
119
            buffer_usage,
120
            aligned_size,
121
            label,
122
            ..Default::default()
123
        }
124
    }
125

126
    fn safe_label(&self) -> &str {
1,256✔
127
        self.label.as_ref().map(|s| &s[..]).unwrap_or("")
7,536✔
128
    }
129

130
    #[inline]
131
    pub fn buffer(&self) -> Option<&Buffer> {
1,267✔
132
        self.buffer.as_ref()
2,534✔
133
    }
134

135
    /// Get a binding for the entire buffer.
136
    #[inline]
137
    #[allow(dead_code)]
138
    pub fn binding(&self) -> Option<BindingResource<'_>> {
×
139
        // FIXME - Return a Buffer wrapper first, which can be unwrapped, then from that
140
        // wrapper implement all the xxx_binding() helpers. That avoids a bunch of "if
141
        // let Some()" everywhere when we know the buffer is valid. The only reason the
142
        // buffer might not be valid is if it was not created, and in that case
143
        // we wouldn't be calling the xxx_bindings() helpers, we'd have earlied out
144
        // before.
145
        let buffer = self.buffer()?;
×
146
        Some(buffer.as_entire_binding())
×
147
    }
148

149
    /// Get a binding for a subset of the elements of the buffer.
150
    ///
151
    /// Returns a binding for the elements in the range `offset..offset+count`.
152
    ///
153
    /// # Panics
154
    ///
155
    /// Panics if `count` is zero.
156
    #[inline]
157
    #[allow(dead_code)]
158
    pub fn range_binding(&self, offset: u32, count: u32) -> Option<BindingResource<'_>> {
×
159
        assert!(count > 0);
×
160
        let buffer = self.buffer()?;
×
161
        let offset = self.aligned_size as u64 * offset as u64;
×
162
        let size = NonZeroU64::new(self.aligned_size as u64 * count as u64).unwrap();
×
163
        Some(BindingResource::Buffer(BufferBinding {
×
164
            buffer,
×
165
            offset,
×
166
            size: Some(size),
×
167
        }))
168
    }
169

170
    #[inline]
171
    #[allow(dead_code)]
172
    pub fn capacity(&self) -> usize {
×
173
        self.capacity
×
174
    }
175

176
    #[inline]
177
    pub fn len(&self) -> usize {
1,605✔
178
        self.values.len()
3,210✔
179
    }
180

181
    /// Size in bytes of a single item in the buffer, aligned to the item
182
    /// alignment.
183
    #[inline]
184
    pub fn aligned_size(&self) -> usize {
1,239✔
185
        self.aligned_size
1,239✔
186
    }
187

188
    /// Calculate a dynamic byte offset for a bind group from an array element
189
    /// index.
190
    ///
191
    /// This returns the product of `index` by the internal [`aligned_size()`].
192
    ///
193
    /// # Panic
194
    ///
195
    /// Panics if the `index` is too large, producing a byte offset larger than
196
    /// `u32::MAX`.
197
    ///
198
    /// [`aligned_size()`]: crate::AlignedBufferVec::aligned_size
199
    #[inline]
200
    #[must_use]
201
    pub fn dynamic_offset(&self, index: usize) -> u32 {
×
202
        let offset = self.aligned_size * index;
×
203
        assert!(offset <= u32::MAX as usize);
×
204
        u32::try_from(offset).expect("AlignedBufferVec index out of bounds")
×
205
    }
206

207
    #[inline]
208
    #[must_use]
209
    #[allow(dead_code)]
210
    pub fn is_empty(&self) -> bool {
1,002✔
211
        self.values.is_empty()
2,004✔
212
    }
213

214
    /// Append a value to the buffer.
215
    ///
216
    /// The content is stored on the CPU and uploaded on the GPU once
217
    /// [`write_buffer()`] is called.
218
    ///
219
    /// [`write_buffer()`]: crate::AlignedBufferVec::write_buffer
220
    pub fn push(&mut self, value: T) -> usize {
652✔
221
        let index = self.values.len();
1,956✔
222
        self.values.push(value);
1,956✔
223
        index
652✔
224
    }
225

226
    #[inline]
227
    #[must_use]
228
    #[allow(dead_code)]
NEW
229
    pub fn last(&self) -> Option<&T> {
×
NEW
230
        self.values.last()
×
231
    }
232

233
    #[inline]
234
    #[must_use]
235
    pub fn last_mut(&mut self) -> Option<&mut T> {
312✔
236
        self.values.last_mut()
312✔
237
    }
238

239
    /// Reserve some capacity into the buffer.
240
    ///
241
    /// If the buffer is reallocated, the old content (on the GPU) is lost, and
242
    /// needs to be re-uploaded to the newly-created buffer. This is done with
243
    /// [`write_buffer()`].
244
    ///
245
    /// # Returns
246
    ///
247
    /// `true` if the buffer was (re)allocated, or `false` if an existing buffer
248
    /// was reused which already had enough capacity.
249
    ///
250
    /// [`write_buffer()`]: crate::AlignedBufferVec::write_buffer
251
    pub fn reserve(&mut self, capacity: usize, device: &RenderDevice) -> bool {
629✔
252
        if capacity > self.capacity {
629✔
253
            let size = self.aligned_size * capacity;
14✔
254
            trace!(
7✔
255
                "reserve['{}']: increase capacity from {} to {} elements, new size {} bytes",
×
256
                self.safe_label(),
8✔
257
                self.capacity,
×
258
                capacity,
×
259
                size
×
260
            );
261
            self.capacity = capacity;
7✔
262
            if let Some(old_buffer) = self.buffer.take() {
8✔
263
                trace!(
×
264
                    "reserve['{}']: destroying old buffer #{:?}",
×
265
                    self.safe_label(),
×
266
                    old_buffer.id()
×
267
                );
268
                old_buffer.destroy();
×
269
            }
270
            let new_buffer = device.create_buffer(&BufferDescriptor {
21✔
271
                label: self.label.as_ref().map(|s| &s[..]),
27✔
272
                size: size as BufferAddress,
7✔
273
                usage: BufferUsages::COPY_DST | self.buffer_usage,
7✔
274
                mapped_at_creation: false,
×
275
            });
276
            trace!(
7✔
277
                "reserve['{}']: created new buffer #{:?}",
×
278
                self.safe_label(),
8✔
279
                new_buffer.id(),
8✔
280
            );
281
            self.buffer = Some(new_buffer);
14✔
282
            // FIXME - this discards the old content if any!!!
283
            true
7✔
284
        } else {
285
            false
622✔
286
        }
287
    }
288

289
    /// Schedule the buffer write to GPU.
290
    ///
291
    /// # Returns
292
    ///
293
    /// `true` if the buffer was (re)allocated, `false` otherwise.
294
    pub fn write_buffer(&mut self, device: &RenderDevice, queue: &RenderQueue) -> bool {
994✔
295
        if self.values.is_empty() {
1,988✔
296
            return false;
366✔
297
        }
298
        trace!(
×
299
            "write_buffer['{}']: values.len={} item_size={} aligned_size={}",
×
300
            self.safe_label(),
1,248✔
301
            self.values.len(),
1,248✔
302
            self.item_size,
×
303
            self.aligned_size
×
304
        );
305
        let buffer_changed = self.reserve(self.values.len(), device);
×
306
        if let Some(buffer) = &self.buffer {
628✔
307
            let aligned_size = self.aligned_size * self.values.len();
×
308
            trace!(
×
309
                "aligned_buffer['{}']: size={} buffer={:?}",
×
310
                self.safe_label(),
1,248✔
311
                aligned_size,
×
312
                buffer.id(),
1,248✔
313
            );
314
            let mut aligned_buffer: Vec<u8> = vec![0; aligned_size];
×
315
            for i in 0..self.values.len() {
631✔
316
                let src: &[u8] = cast_slice(std::slice::from_ref(&self.values[i]));
×
317
                let dst_offset = i * self.aligned_size;
×
318
                let dst_range = dst_offset..dst_offset + self.item_size;
×
319
                trace!("+ copy: src={:?} dst={:?}", src.as_ptr(), dst_range);
1,248✔
320
                let dst = &mut aligned_buffer[dst_range];
×
321
                dst.copy_from_slice(src);
×
322
            }
323
            let bytes: &[u8] = cast_slice(&aligned_buffer);
×
324
            queue.write_buffer(buffer, 0, bytes);
×
325
        }
326
        buffer_changed
×
327
    }
328

329
    pub fn clear(&mut self) {
993✔
330
        self.values.clear();
1,986✔
331
    }
332
}
333

334
impl<T: Pod + ShaderSize> std::ops::Index<usize> for AlignedBufferVec<T> {
335
    type Output = T;
336

337
    fn index(&self, index: usize) -> &Self::Output {
×
338
        &self.values[index]
×
339
    }
340
}
341

342
impl<T: Pod + ShaderSize> std::ops::IndexMut<usize> for AlignedBufferVec<T> {
343
    fn index_mut(&mut self, index: usize) -> &mut Self::Output {
×
344
        &mut self.values[index]
×
345
    }
346
}
347

348
#[derive(Debug, Clone, PartialEq, Eq)]
349
struct FreeRow(pub Range<u32>);
350

351
impl PartialOrd for FreeRow {
352
    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
×
353
        Some(self.cmp(other))
×
354
    }
355
}
356

357
impl Ord for FreeRow {
358
    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
6✔
359
        self.0.start.cmp(&other.0.start)
18✔
360
    }
361
}
362

363
/// Like [`AlignedBufferVec`], but for heterogenous data.
364
#[derive(Debug)]
365
pub struct HybridAlignedBufferVec {
366
    /// Pending values accumulated on CPU and not yet written to GPU.
367
    values: Vec<u8>,
368
    /// GPU buffer if already allocated, or `None` otherwise.
369
    buffer: Option<Buffer>,
370
    /// Capacity of the buffer, in bytes.
371
    capacity: usize,
372
    /// Alignment of each element, in bytes.
373
    item_align: usize,
374
    /// GPU buffer usages.
375
    buffer_usage: BufferUsages,
376
    /// Optional GPU buffer name, for debugging.
377
    label: Option<String>,
378
    /// Free ranges available for re-allocation. Those are row ranges; byte
379
    /// ranges are obtained by multiplying these by `item_align`.
380
    free_rows: Vec<FreeRow>,
381
    /// Is the GPU buffer stale and the CPU one need to be re-uploaded?
382
    is_stale: bool,
383
}
384

385
impl HybridAlignedBufferVec {
386
    /// Create a new collection.
387
    ///
388
    /// `item_align` is the alignment for items in the collection.
389
    pub fn new(buffer_usage: BufferUsages, item_align: NonZeroU64, label: Option<String>) -> Self {
5✔
390
        let item_align = item_align.get() as usize;
10✔
391
        trace!(
5✔
392
            "HybridAlignedBufferVec['{}']: item_align={} byte",
393
            label.as_ref().map(|s| &s[..]).unwrap_or(""),
18✔
394
            item_align,
395
        );
396
        Self {
397
            values: vec![],
10✔
398
            buffer: None,
399
            capacity: 0,
400
            item_align,
401
            buffer_usage,
402
            label,
403
            free_rows: vec![],
5✔
404
            is_stale: true,
405
        }
406
    }
407

408
    #[inline]
409
    pub fn buffer(&self) -> Option<&Buffer> {
638✔
410
        self.buffer.as_ref()
1,276✔
411
    }
412

413
    /// Get a binding for the entire buffer.
414
    #[allow(dead_code)]
415
    #[inline]
416
    pub fn max_binding(&self) -> Option<BindingResource<'_>> {
×
417
        // FIXME - Return a Buffer wrapper first, which can be unwrapped, then from that
418
        // wrapper implement all the xxx_binding() helpers. That avoids a bunch of "if
419
        // let Some()" everywhere when we know the buffer is valid. The only reason the
420
        // buffer might not be valid is if it was not created, and in that case
421
        // we wouldn't be calling the xxx_bindings() helpers, we'd have earlied out
422
        // before.
423
        let buffer = self.buffer()?;
×
424
        Some(BindingResource::Buffer(BufferBinding {
×
425
            buffer,
×
426
            offset: 0,
×
427
            size: None, // entire buffer
×
428
        }))
429
    }
430

431
    /// Get a binding for the first `size` bytes of the buffer.
432
    ///
433
    /// # Panics
434
    ///
435
    /// Panics if `size` is zero.
436
    #[allow(dead_code)]
437
    #[inline]
438
    pub fn lead_binding(&self, size: u32) -> Option<BindingResource<'_>> {
×
439
        let buffer = self.buffer()?;
×
440
        let size = NonZeroU64::new(size as u64).unwrap();
×
441
        Some(BindingResource::Buffer(BufferBinding {
×
442
            buffer,
×
443
            offset: 0,
×
444
            size: Some(size),
×
445
        }))
446
    }
447

448
    /// Get a binding for a subset of the elements of the buffer.
449
    ///
450
    /// Returns a binding for the elements in the range `offset..offset+count`.
451
    ///
452
    /// # Panics
453
    ///
454
    /// Panics if `offset` is not a multiple of the alignment specified on
455
    /// construction.
456
    ///
457
    /// Panics if `size` is zero.
458
    #[allow(dead_code)]
459
    #[inline]
460
    pub fn range_binding(&self, offset: u32, size: u32) -> Option<BindingResource<'_>> {
×
461
        assert!((offset as usize).is_multiple_of(self.item_align));
×
462
        let buffer = self.buffer()?;
×
463
        let size = NonZeroU64::new(size as u64).unwrap();
×
464
        Some(BindingResource::Buffer(BufferBinding {
×
465
            buffer,
×
466
            offset: offset as u64,
×
467
            size: Some(size),
×
468
        }))
469
    }
470

471
    /// Capacity of the allocated GPU buffer, in bytes.
472
    ///
473
    /// This may be zero if the buffer was not allocated yet. In general, this
474
    /// can differ from the actual data size cached on CPU and waiting to be
475
    /// uploaded to GPU.
476
    #[inline]
477
    #[allow(dead_code)]
478
    pub fn capacity(&self) -> usize {
×
479
        self.capacity
×
480
    }
481

482
    /// Current buffer size, in bytes.
483
    ///
484
    /// This represents the size of the CPU data uploaded to GPU. Pending a GPU
485
    /// buffer re-allocation or re-upload, this size might differ from the
486
    /// actual GPU buffer size. But they're eventually consistent.
487
    #[inline]
488
    pub fn len(&self) -> usize {
1✔
489
        self.values.len()
2✔
490
    }
491

492
    /// Alignment, in bytes, of all the elements.
493
    #[allow(dead_code)]
494
    #[inline]
495
    pub fn item_align(&self) -> usize {
×
496
        self.item_align
×
497
    }
498

499
    /// Calculate a dynamic byte offset for a bind group from an array element
500
    /// index.
501
    ///
502
    /// This returns the product of `index` by the internal [`item_align()`].
503
    ///
504
    /// # Panic
505
    ///
506
    /// Panics if the `index` is too large, producing a byte offset larger than
507
    /// `u32::MAX`.
508
    ///
509
    /// [`item_align()`]: crate::HybridAlignedBufferVec::item_align
510
    #[allow(dead_code)]
511
    #[inline]
512
    pub fn dynamic_offset(&self, index: usize) -> u32 {
×
513
        let offset = self.item_align * index;
×
514
        assert!(offset <= u32::MAX as usize);
×
515
        u32::try_from(offset).expect("HybridAlignedBufferVec index out of bounds")
×
516
    }
517

518
    #[inline]
519
    #[allow(dead_code)]
520
    pub fn is_empty(&self) -> bool {
664✔
521
        self.values.is_empty()
1,328✔
522
    }
523

524
    /// Append a value to the buffer.
525
    ///
526
    /// As with [`set_content()`], the content is stored on the CPU and uploaded
527
    /// on the GPU once [`write_buffers()`] is called.
528
    ///
529
    /// # Returns
530
    ///
531
    /// Returns a range starting at the byte offset at which the new element was
532
    /// inserted, which is guaranteed to be a multiple of [`item_align()`].
533
    /// The range span is the item byte size.
534
    ///
535
    /// [`item_align()`]: self::HybridAlignedBufferVec::item_align
536
    #[allow(dead_code)]
537
    pub fn push<T: Pod + ShaderSize>(&mut self, value: &T) -> Range<u32> {
15✔
538
        let src: &[u8] = cast_slice(std::slice::from_ref(value));
60✔
539
        assert_eq!(value.size().get() as usize, src.len());
75✔
540
        self.push_raw(src)
45✔
541
    }
542

543
    /// Append a slice of values to the buffer.
544
    ///
545
    /// The values are assumed to be tightly packed, and will be copied
546
    /// back-to-back into the buffer, without any padding between them. This
547
    /// means that the individul slice items must be properly aligned relative
548
    /// to the beginning of the slice.
549
    ///
550
    /// As with [`set_content()`], the content is stored on the CPU and uploaded
551
    /// on the GPU once [`write_buffers()`] is called.
552
    ///
553
    /// # Returns
554
    ///
555
    /// Returns a range starting at the byte offset at which the new element
556
    /// (the slice) was inserted, which is guaranteed to be a multiple of
557
    /// [`item_align()`]. The range span is the item byte size.
558
    ///
559
    /// # Panics
560
    ///
561
    /// Panics if the byte size of the element `T` is not at least a multiple of
562
    /// the minimum GPU alignment, which is 4 bytes. Note that this doesn't
563
    /// guarantee that the written data is well-formed for use on GPU, as array
564
    /// elements on GPU have other alignment requirements according to WGSL, but
565
    /// at least this catches obvious errors.
566
    ///
567
    /// [`item_align()`]: self::HybridAlignedBufferVec::item_align
568
    #[allow(dead_code)]
569
    pub fn push_many<T: Pod + ShaderSize>(&mut self, value: &[T]) -> Range<u32> {
×
570
        assert_eq!(size_of::<T>() % 4, 0);
×
571
        let src: &[u8] = cast_slice(value);
×
572
        self.push_raw(src)
×
573
    }
574

575
    pub fn push_raw(&mut self, src: &[u8]) -> Range<u32> {
16✔
576
        self.is_stale = true;
16✔
577

578
        // Calculate the number of (aligned) rows to allocate
579
        let num_rows = src.len().div_ceil(self.item_align) as u32;
64✔
580

581
        // Try to find a block of free rows which can accomodate it, and pick the
582
        // smallest one in order to limit wasted space.
583
        let mut best_slot: Option<(u32, usize)> = None;
48✔
584
        for (index, range) in self.free_rows.iter().enumerate() {
32✔
585
            let free_rows = range.0.end - range.0.start;
×
586
            if free_rows >= num_rows {
×
587
                let wasted_rows = free_rows - num_rows;
×
588
                // If we found a slot with the exact size, just use it already
589
                if wasted_rows == 0 {
×
590
                    best_slot = Some((0, index));
×
591
                    break;
592
                }
593
                // Otherwise try to find the smallest oversized slot to reduce wasted space
594
                if let Some(best_slot) = best_slot.as_mut() {
×
595
                    if wasted_rows < best_slot.0 {
×
596
                        *best_slot = (wasted_rows, index);
×
597
                    }
598
                } else {
599
                    best_slot = Some((wasted_rows, index));
×
600
                }
601
            }
602
        }
603

604
        // Insert into existing space
605
        if let Some((_, index)) = best_slot {
16✔
606
            let row_range = self.free_rows.remove(index);
607
            let offset = row_range.0.start as usize * self.item_align;
608
            let free_size = (row_range.0.end - row_range.0.start) as usize * self.item_align;
609
            let size = src.len();
610
            assert!(size <= free_size);
611

612
            let dst = self.values.as_mut_ptr();
×
613
            // SAFETY: dst is guaranteed to point to allocated bytes, which are already
614
            // initialized from a previous call, and are initialized by overwriting the
615
            // bytes with those of a POD type.
616
            #[allow(unsafe_code)]
617
            unsafe {
618
                let dst = dst.add(offset);
×
619
                dst.copy_from_nonoverlapping(src.as_ptr(), size);
×
620
            }
621

622
            let start = offset as u32;
×
623
            let end = start + size as u32;
×
624
            start..end
×
625
        }
626
        // Insert at end of vector, after resizing it
627
        else {
628
            // Calculate new aligned insertion offset and new capacity
629
            let offset = self.values.len().next_multiple_of(self.item_align);
80✔
630
            let size = src.len();
48✔
631
            let new_capacity = offset + size;
32✔
632
            if new_capacity > self.values.capacity() {
32✔
633
                let additional = new_capacity - self.values.len();
15✔
634
                self.values.reserve(additional)
15✔
635
            }
636

637
            // Insert padding if needed
638
            if offset > self.values.len() {
42✔
639
                self.values.resize(offset, 0);
20✔
640
            }
641

642
            // Insert serialized value
643
            // Dealing with safe code via Vec::spare_capacity_mut() is quite difficult
644
            // without the upcoming (unstable) additions to MaybeUninit to deal with arrays.
645
            // To prevent having to loop over individual u8, we use direct pointers instead.
646
            assert!(self.values.capacity() >= offset + size);
64✔
647
            assert_eq!(self.values.len(), offset);
48✔
648
            let dst = self.values.as_mut_ptr();
48✔
649
            // SAFETY: dst is guaranteed to point to allocated (offset+size) bytes, which
650
            // are written by copying a Pod type, so ensures those values are initialized,
651
            // and the final size is set to exactly (offset+size).
652
            #[allow(unsafe_code)]
653
            unsafe {
654
                let dst = dst.add(offset);
80✔
655
                dst.copy_from_nonoverlapping(src.as_ptr(), size);
96✔
656
                self.values.set_len(offset + size);
48✔
657
            }
658

659
            debug_assert_eq!(offset % self.item_align, 0);
32✔
660
            let start = offset as u32;
32✔
661
            let end = start + size as u32;
32✔
662
            start..end
16✔
663
        }
664
    }
665

666
    /// Remove a range of bytes previously added.
667
    ///
668
    /// Remove a range of bytes previously returned by adding one or more
669
    /// elements with [`push()`] or [`push_many()`].
670
    ///
671
    /// # Returns
672
    ///
673
    /// Returns `true` if the range was valid and the corresponding data was
674
    /// removed, or `false` otherwise. In that case, the buffer is not modified.
675
    ///
676
    /// [`push()`]: Self::push
677
    /// [`push_many()`]: Self::push_many
678
    pub fn remove(&mut self, range: Range<u32>) -> bool {
17✔
679
        // Can only remove entire blocks starting at an aligned size
680
        let align = self.item_align as u32;
34✔
681
        if !range.start.is_multiple_of(align) {
34✔
682
            return false;
×
683
        }
684

685
        // Check for out of bounds argument
686
        let end = self.values.len() as u32;
34✔
687
        if range.start >= end || range.end > end {
34✔
688
            return false;
×
689
        }
690

691
        // Note: See below, sometimes self.values() has some padding left we couldn't
692
        // recover earlier beause we didn't know the size of this allocation, but we
693
        // need to still deallocate the row here.
694
        if range.end == end || range.end.next_multiple_of(align) == end {
11✔
695
            // If the allocation is at the end of the buffer, shorten the CPU values. This
696
            // ensures is_empty() eventually returns true.
697
            let mut new_row_end = range.start.div_ceil(align);
8✔
698

699
            // Walk the (sorted) free list to also dequeue any range which is now at the end
700
            // of the buffer
701
            while let Some(free_row) = self.free_rows.pop() {
16✔
702
                if free_row.0.end == new_row_end {
4✔
703
                    new_row_end = free_row.0.start;
4✔
704
                } else {
705
                    self.free_rows.push(free_row);
×
706
                    break;
707
                }
708
            }
709

710
            // Note: we can't really recover any padding here because we don't know the
711
            // exact size of that allocation, only its row-aligned size.
712
            self.values.truncate((new_row_end * align) as usize);
713
        } else {
714
            // Otherwise, save the row into the free list.
715
            let start = range.start / align;
9✔
716
            let end = range.end.div_ceil(align);
717
            let free_row = FreeRow(start..end);
718

719
            // Insert as sorted
720
            if self.free_rows.is_empty() {
4✔
721
                // Special case to simplify below, and to avoid binary_search()
722
                self.free_rows.push(free_row);
8✔
723
            } else if let Err(index) = self.free_rows.binary_search(&free_row) {
13✔
724
                if index >= self.free_rows.len() {
725
                    // insert at end
726
                    let prev = self.free_rows.last_mut().unwrap(); // known
3✔
727
                    if prev.0.end == free_row.0.start {
2✔
728
                        // merge with last value
729
                        prev.0.end = free_row.0.end;
1✔
730
                    } else {
731
                        // insert last, with gap
732
                        self.free_rows.push(free_row);
×
733
                    }
734
                } else if index == 0 {
3✔
735
                    // insert at start
736
                    let next = &mut self.free_rows[0];
4✔
737
                    if free_row.0.end == next.0.start {
3✔
738
                        // merge with next
739
                        next.0.start = free_row.0.start;
1✔
740
                    } else {
741
                        // insert first, with gap
742
                        self.free_rows.insert(0, free_row);
1✔
743
                    }
744
                } else {
745
                    // insert between 2 existing elements
746
                    let prev = &mut self.free_rows[index - 1];
1✔
747
                    if prev.0.end == free_row.0.start {
748
                        // merge with previous value
749
                        prev.0.end = free_row.0.end;
1✔
750

751
                        let prev = self.free_rows[index - 1].clone();
3✔
752
                        let next = &mut self.free_rows[index];
2✔
753
                        if prev.0.end == next.0.start {
2✔
754
                            // also merge prev with next, and remove prev
755
                            next.0.start = prev.0.start;
2✔
756
                            self.free_rows.remove(index - 1);
2✔
757
                        }
758
                    } else {
759
                        let next = &mut self.free_rows[index];
×
760
                        if free_row.0.end == next.0.start {
×
761
                            // merge with next value
762
                            next.0.start = free_row.0.start;
×
763
                        } else {
764
                            // insert between 2 values, with gaps on both sides
765
                            self.free_rows.insert(0, free_row);
×
766
                        }
767
                    }
768
                }
769
            } else {
770
                // The range exists in the free list, this means it's already removed. This is a
771
                // duplicate; ignore it.
772
                return false;
1✔
773
            }
774
        }
775
        self.is_stale = true;
16✔
776
        true
777
    }
778

779
    /// Update an allocated entry with a new value.
780
    #[allow(dead_code)]
781
    #[inline]
782
    pub fn update<T: Pod + ShaderSize>(&mut self, offset: u32, value: &T) {
×
783
        let data: &[u8] = cast_slice(std::slice::from_ref(value));
×
784
        assert_eq!(value.size().get() as usize, data.len());
×
785
        self.update_raw(offset, data);
×
786
    }
787

788
    /// Update an allocated entry with new data.
789
    pub fn update_raw(&mut self, offset: u32, data: &[u8]) {
×
790
        // Can only update entire blocks starting at an aligned size
791
        let align = self.item_align as u32;
×
792
        if !offset.is_multiple_of(align) {
×
793
            return;
×
794
        }
795

796
        // Check for out of bounds argument
797
        let end = self.values.len() as u32;
×
798
        let data_end = offset + data.len() as u32;
×
799
        if offset >= end || data_end > end {
×
800
            return;
×
801
        }
802

803
        let dst: &mut [u8] = &mut self.values[offset as usize..data_end as usize];
×
804
        dst.copy_from_slice(data);
×
805

806
        self.is_stale = true;
×
807
    }
808

809
    /// Reserve some capacity into the buffer.
810
    ///
811
    /// If the buffer is reallocated, the old content (on the GPU) is lost, and
812
    /// needs to be re-uploaded to the newly-created buffer. This is done with
813
    /// [`write_buffer()`].
814
    ///
815
    /// # Returns
816
    ///
817
    /// `true` if the buffer was (re)allocated, or `false` if an existing buffer
818
    /// was reused which already had enough capacity.
819
    ///
820
    /// [`write_buffer()`]: crate::AlignedBufferVec::write_buffer
821
    pub fn reserve(&mut self, capacity: usize, device: &RenderDevice) -> bool {
1✔
822
        if capacity > self.capacity {
1✔
823
            trace!(
1✔
824
                "reserve: increase capacity from {} to {} bytes",
825
                self.capacity,
826
                capacity,
827
            );
828
            self.capacity = capacity;
1✔
829
            if let Some(buffer) = self.buffer.take() {
1✔
830
                buffer.destroy();
×
831
            }
832
            self.buffer = Some(device.create_buffer(&BufferDescriptor {
3✔
833
                label: self.label.as_ref().map(|s| &s[..]),
4✔
834
                size: capacity as BufferAddress,
1✔
835
                usage: BufferUsages::COPY_DST | self.buffer_usage,
1✔
836
                mapped_at_creation: false,
837
            }));
838
            self.is_stale = !self.values.is_empty();
1✔
839
            // FIXME - this discards the old content if any!!!
840
            true
1✔
841
        } else {
842
            false
×
843
        }
844
    }
845

846
    /// Schedule the buffer write to GPU.
847
    ///
848
    /// # Returns
849
    ///
850
    /// `true` if the buffer was (re)allocated, `false` otherwise. If the buffer
851
    /// was reallocated, all bind groups referencing the old buffer should be
852
    /// destroyed.
853
    pub fn write_buffer(&mut self, device: &RenderDevice, queue: &RenderQueue) -> bool {
350✔
854
        if self.values.is_empty() || !self.is_stale {
714✔
855
            return false;
349✔
856
        }
857
        let size = self.values.len();
3✔
858
        trace!(
1✔
859
            "hybrid abv: write_buffer: size={}B item_align={}B",
860
            size,
861
            self.item_align,
862
        );
863
        let buffer_changed = self.reserve(size, device);
5✔
864
        if let Some(buffer) = &self.buffer {
2✔
865
            queue.write_buffer(buffer, 0, self.values.as_slice());
×
866
            self.is_stale = false;
×
867
        }
868
        buffer_changed
1✔
869
    }
870

871
    #[allow(dead_code)]
872
    pub fn clear(&mut self) {
×
873
        if !self.values.is_empty() {
×
874
            self.is_stale = true;
×
875
        }
876
        self.values.clear();
×
877
    }
878
}
879

880
#[cfg(test)]
881
mod tests {
882
    use std::num::NonZeroU64;
883

884
    use bevy::math::Vec3;
885
    use bytemuck::{Pod, Zeroable};
886

887
    use super::*;
888

889
    #[repr(C)]
890
    #[derive(Debug, Default, Clone, Copy, Pod, Zeroable, ShaderType)]
891
    pub(crate) struct GpuDummy {
892
        pub v: Vec3,
893
    }
894

895
    #[repr(C)]
896
    #[derive(Debug, Default, Clone, Copy, Pod, Zeroable, ShaderType)]
897
    pub(crate) struct GpuDummyComposed {
898
        pub simple: GpuDummy,
899
        pub tag: u32,
900
        // GPU padding to 16 bytes due to GpuDummy forcing align to 16 bytes
901
    }
902

903
    #[repr(C)]
904
    #[derive(Debug, Clone, Copy, Pod, Zeroable, ShaderType)]
905
    pub(crate) struct GpuDummyLarge {
906
        pub simple: GpuDummy,
907
        pub tag: u32,
908
        pub large: [f32; 128],
909
    }
910

911
    #[test]
912
    fn abv_sizes() {
913
        // Rust
914
        assert_eq!(std::mem::size_of::<GpuDummy>(), 12);
915
        assert_eq!(std::mem::align_of::<GpuDummy>(), 4);
916
        assert_eq!(std::mem::size_of::<GpuDummyComposed>(), 16); // tight packing
917
        assert_eq!(std::mem::align_of::<GpuDummyComposed>(), 4);
918
        assert_eq!(std::mem::size_of::<GpuDummyLarge>(), 132 * 4); // tight packing
919
        assert_eq!(std::mem::align_of::<GpuDummyLarge>(), 4);
920

921
        // GPU
922
        assert_eq!(<GpuDummy as ShaderType>::min_size().get(), 16); // Vec3 gets padded to 16 bytes
923
        assert_eq!(<GpuDummy as ShaderSize>::SHADER_SIZE.get(), 16);
924
        assert_eq!(<GpuDummyComposed as ShaderType>::min_size().get(), 32); // align is 16 bytes, forces padding
925
        assert_eq!(<GpuDummyComposed as ShaderSize>::SHADER_SIZE.get(), 32);
926
        assert_eq!(<GpuDummyLarge as ShaderType>::min_size().get(), 544); // align is 16 bytes, forces padding
927
        assert_eq!(<GpuDummyLarge as ShaderSize>::SHADER_SIZE.get(), 544);
928

929
        for (item_align, expected_aligned_size) in [
930
            (0, 16),
931
            (4, 16),
932
            (8, 16),
933
            (16, 16),
934
            (32, 32),
935
            (256, 256),
936
            (512, 512),
937
        ] {
938
            let mut abv = AlignedBufferVec::<GpuDummy>::new(
939
                BufferUsages::STORAGE,
940
                NonZeroU64::new(item_align),
941
                None,
942
            );
943
            assert_eq!(abv.aligned_size(), expected_aligned_size);
944
            assert!(abv.is_empty());
945
            abv.push(GpuDummy::default());
946
            assert!(!abv.is_empty());
947
            assert_eq!(abv.len(), 1);
948
        }
949

950
        for (item_align, expected_aligned_size) in [
951
            (0, 32),
952
            (4, 32),
953
            (8, 32),
954
            (16, 32),
955
            (32, 32),
956
            (256, 256),
957
            (512, 512),
958
        ] {
959
            let mut abv = AlignedBufferVec::<GpuDummyComposed>::new(
960
                BufferUsages::STORAGE,
961
                NonZeroU64::new(item_align),
962
                None,
963
            );
964
            assert_eq!(abv.aligned_size(), expected_aligned_size);
965
            assert!(abv.is_empty());
966
            abv.push(GpuDummyComposed::default());
967
            assert!(!abv.is_empty());
968
            assert_eq!(abv.len(), 1);
969
        }
970

971
        for (item_align, expected_aligned_size) in [
972
            (0, 544),
973
            (4, 544),
974
            (8, 544),
975
            (16, 544),
976
            (32, 544),
977
            (256, 768),
978
            (512, 1024),
979
        ] {
980
            let mut abv = AlignedBufferVec::<GpuDummyLarge>::new(
981
                BufferUsages::STORAGE,
982
                NonZeroU64::new(item_align),
983
                None,
984
            );
985
            assert_eq!(abv.aligned_size(), expected_aligned_size);
986
            assert!(abv.is_empty());
987
            abv.push(GpuDummyLarge {
988
                simple: Default::default(),
989
                tag: 0,
990
                large: [0.; 128],
991
            });
992
            assert!(!abv.is_empty());
993
            assert_eq!(abv.len(), 1);
994
        }
995
    }
996

997
    #[test]
998
    fn habv_remove() {
999
        let mut habv =
1000
            HybridAlignedBufferVec::new(BufferUsages::STORAGE, NonZeroU64::new(32).unwrap(), None);
1001
        assert!(habv.is_empty());
1002
        assert_eq!(habv.item_align, 32);
1003

1004
        // +r -r
1005
        {
1006
            let r = habv.push(&42u32);
1007
            assert_eq!(r, 0..4);
1008
            assert!(!habv.is_empty());
1009
            assert_eq!(habv.values.len(), 4);
1010
            assert!(habv.free_rows.is_empty());
1011

1012
            assert!(habv.remove(r));
1013
            assert!(habv.is_empty());
1014
            assert!(habv.values.is_empty());
1015
            assert!(habv.free_rows.is_empty());
1016
        }
1017

1018
        // +r0 +r1 +r2 -r0 -r0 -r1 -r2
1019
        {
1020
            let r0 = habv.push(&42u32);
1021
            let r1 = habv.push(&84u32);
1022
            let r2 = habv.push(&84u32);
1023
            assert_eq!(r0, 0..4);
1024
            assert_eq!(r1, 32..36);
1025
            assert_eq!(r2, 64..68);
1026
            assert!(!habv.is_empty());
1027
            assert_eq!(habv.values.len(), 68);
1028
            assert!(habv.free_rows.is_empty());
1029

1030
            assert!(habv.remove(r0.clone()));
1031
            assert!(!habv.is_empty());
1032
            assert_eq!(habv.values.len(), 68);
1033
            assert_eq!(habv.free_rows.len(), 1);
1034
            assert_eq!(habv.free_rows[0], FreeRow(0..1));
1035

1036
            // dupe; no-op
1037
            assert!(!habv.remove(r0));
1038

1039
            assert!(habv.remove(r1.clone()));
1040
            assert!(!habv.is_empty());
1041
            assert_eq!(habv.values.len(), 68);
1042
            assert_eq!(habv.free_rows.len(), 1); // merged!
1043
            assert_eq!(habv.free_rows[0], FreeRow(0..2));
1044

1045
            assert!(habv.remove(r2));
1046
            assert!(habv.is_empty());
1047
            assert_eq!(habv.values.len(), 0);
1048
            assert!(habv.free_rows.is_empty());
1049
        }
1050

1051
        // +r0 +r1 +r2 -r1 -r0 -r2
1052
        {
1053
            let r0 = habv.push(&42u32);
1054
            let r1 = habv.push(&84u32);
1055
            let r2 = habv.push(&84u32);
1056
            assert_eq!(r0, 0..4);
1057
            assert_eq!(r1, 32..36);
1058
            assert_eq!(r2, 64..68);
1059
            assert!(!habv.is_empty());
1060
            assert_eq!(habv.values.len(), 68);
1061
            assert!(habv.free_rows.is_empty());
1062

1063
            assert!(habv.remove(r1.clone()));
1064
            assert!(!habv.is_empty());
1065
            assert_eq!(habv.values.len(), 68);
1066
            assert_eq!(habv.free_rows.len(), 1);
1067
            assert_eq!(habv.free_rows[0], FreeRow(1..2));
1068

1069
            assert!(habv.remove(r0.clone()));
1070
            assert!(!habv.is_empty());
1071
            assert_eq!(habv.values.len(), 68);
1072
            assert_eq!(habv.free_rows.len(), 1); // merged!
1073
            assert_eq!(habv.free_rows[0], FreeRow(0..2));
1074

1075
            assert!(habv.remove(r2));
1076
            assert!(habv.is_empty());
1077
            assert_eq!(habv.values.len(), 0);
1078
            assert!(habv.free_rows.is_empty());
1079
        }
1080

1081
        // +r0 +r1 +r2 -r1 -r2 -r0
1082
        {
1083
            let r0 = habv.push(&42u32);
1084
            let r1 = habv.push(&84u32);
1085
            let r2 = habv.push(&84u32);
1086
            assert_eq!(r0, 0..4);
1087
            assert_eq!(r1, 32..36);
1088
            assert_eq!(r2, 64..68);
1089
            assert!(!habv.is_empty());
1090
            assert_eq!(habv.values.len(), 68);
1091
            assert!(habv.free_rows.is_empty());
1092

1093
            assert!(habv.remove(r1.clone()));
1094
            assert!(!habv.is_empty());
1095
            assert_eq!(habv.values.len(), 68);
1096
            assert_eq!(habv.free_rows.len(), 1);
1097
            assert_eq!(habv.free_rows[0], FreeRow(1..2));
1098

1099
            assert!(habv.remove(r2.clone()));
1100
            assert!(!habv.is_empty());
1101
            assert_eq!(habv.values.len(), 32); // can't recover exact alloc (4), only row-aligned size (32)
1102
            assert!(habv.free_rows.is_empty()); // merged!
1103

1104
            assert!(habv.remove(r0));
1105
            assert!(habv.is_empty());
1106
            assert_eq!(habv.values.len(), 0);
1107
            assert!(habv.free_rows.is_empty());
1108
        }
1109

1110
        // +r0 +r1 +r2 +r3 +r4 -r3 -r1 -r2 -r4 r0
1111
        {
1112
            let r0 = habv.push(&42u32);
1113
            let r1 = habv.push(&84u32);
1114
            let r2 = habv.push(&84u32);
1115
            let r3 = habv.push(&84u32);
1116
            let r4 = habv.push(&84u32);
1117
            assert_eq!(r0, 0..4);
1118
            assert_eq!(r1, 32..36);
1119
            assert_eq!(r2, 64..68);
1120
            assert_eq!(r3, 96..100);
1121
            assert_eq!(r4, 128..132);
1122
            assert!(!habv.is_empty());
1123
            assert_eq!(habv.values.len(), 132);
1124
            assert!(habv.free_rows.is_empty());
1125

1126
            assert!(habv.remove(r3.clone()));
1127
            assert!(!habv.is_empty());
1128
            assert_eq!(habv.values.len(), 132);
1129
            assert_eq!(habv.free_rows.len(), 1);
1130
            assert_eq!(habv.free_rows[0], FreeRow(3..4));
1131

1132
            assert!(habv.remove(r1.clone()));
1133
            assert!(!habv.is_empty());
1134
            assert_eq!(habv.values.len(), 132);
1135
            assert_eq!(habv.free_rows.len(), 2);
1136
            assert_eq!(habv.free_rows[0], FreeRow(1..2)); // sorted!
1137
            assert_eq!(habv.free_rows[1], FreeRow(3..4));
1138

1139
            assert!(habv.remove(r2.clone()));
1140
            assert!(!habv.is_empty());
1141
            assert_eq!(habv.values.len(), 132);
1142
            assert_eq!(habv.free_rows.len(), 1); // merged!
1143
            assert_eq!(habv.free_rows[0], FreeRow(1..4)); // merged!
1144

1145
            assert!(habv.remove(r4.clone()));
1146
            assert!(!habv.is_empty());
1147
            assert_eq!(habv.values.len(), 32); // can't recover exact alloc (4), only row-aligned size (32)
1148
            assert!(habv.free_rows.is_empty());
1149

1150
            assert!(habv.remove(r0));
1151
            assert!(habv.is_empty());
1152
            assert_eq!(habv.values.len(), 0);
1153
            assert!(habv.free_rows.is_empty());
1154
        }
1155
    }
1156
}
1157

1158
#[cfg(all(test, feature = "gpu_tests"))]
1159
mod gpu_tests {
1160
    use tests::*;
1161

1162
    use super::*;
1163
    use crate::test_utils::MockRenderer;
1164

1165
    #[test]
1166
    fn abv_write() {
1167
        let renderer = MockRenderer::new();
1168
        let device = renderer.device();
1169
        let queue = renderer.queue();
1170

1171
        // Create a dummy CommandBuffer to force the write_buffer() call to have any
1172
        // effect.
1173
        let encoder = device.create_command_encoder(&wgpu::CommandEncoderDescriptor {
1174
            label: Some("test"),
1175
        });
1176
        let command_buffer = encoder.finish();
1177

1178
        let item_align = device.limits().min_storage_buffer_offset_alignment as u64;
1179
        let mut abv = AlignedBufferVec::<GpuDummyComposed>::new(
1180
            BufferUsages::STORAGE | BufferUsages::MAP_READ,
1181
            NonZeroU64::new(item_align),
1182
            None,
1183
        );
1184
        let final_align = item_align.max(<GpuDummyComposed as ShaderSize>::SHADER_SIZE.get());
1185
        assert_eq!(abv.aligned_size(), final_align as usize);
1186

1187
        const CAPACITY: usize = 42;
1188

1189
        // Write buffer (CPU -> GPU)
1190
        abv.push(GpuDummyComposed {
1191
            tag: 1,
1192
            ..Default::default()
1193
        });
1194
        abv.push(GpuDummyComposed {
1195
            tag: 2,
1196
            ..Default::default()
1197
        });
1198
        abv.push(GpuDummyComposed {
1199
            tag: 3,
1200
            ..Default::default()
1201
        });
1202
        abv.reserve(CAPACITY, &device);
1203
        abv.write_buffer(&device, &queue);
1204
        // need a submit() for write_buffer() to be processed
1205
        queue.submit([command_buffer]);
1206
        let (tx, rx) = futures::channel::oneshot::channel();
1207
        queue.on_submitted_work_done(move || {
1208
            tx.send(()).unwrap();
1209
        });
1210
        let _ = device.poll(wgpu::PollType::Wait {
1211
            submission_index: None,
1212
            timeout: None,
1213
        });
1214
        let _ = futures::executor::block_on(rx);
1215
        println!("Buffer written");
1216

1217
        // Read back (GPU -> CPU)
1218
        let buffer = abv.buffer();
1219
        let buffer = buffer.as_ref().expect("Buffer was not allocated");
1220
        let buffer = buffer.slice(..);
1221
        let (tx, rx) = futures::channel::oneshot::channel();
1222
        buffer.map_async(wgpu::MapMode::Read, move |result| {
1223
            tx.send(result).unwrap();
1224
        });
1225
        let _ = device.poll(wgpu::PollType::Wait {
1226
            submission_index: None,
1227
            timeout: None,
1228
        });
1229
        let _result = futures::executor::block_on(rx);
1230
        let view = buffer.get_mapped_range();
1231

1232
        // Validate content
1233
        assert_eq!(view.len(), final_align as usize * CAPACITY);
1234
        for i in 0..3 {
1235
            let offset = i * final_align as usize;
1236
            let dummy_composed: &[GpuDummyComposed] =
1237
                cast_slice(&view[offset..offset + std::mem::size_of::<GpuDummyComposed>()]);
1238
            assert_eq!(dummy_composed[0].tag, (i + 1) as u32);
1239
        }
1240
    }
1241
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc