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

djeedai / bevy_hanabi / 29355083385

14 Jul 2026 05:45PM UTC coverage: 59.24% (-0.06%) from 59.296%
29355083385

Pull #549

github

web-flow
Merge e19362ba4 into e2f65dd39
Pull Request #549: Fix stable bind group in GPU ops

8 of 29 new or added lines in 3 files covered. (27.59%)

5424 of 9156 relevant lines covered (59.24%)

684.71 hits per line

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

44.55
/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 {
37✔
61
        let item_size = std::mem::size_of::<T>();
74✔
62
        let aligned_size = <T as ShaderSize>::SHADER_SIZE.get() as usize;
74✔
63
        assert!(aligned_size >= item_size);
74✔
64
        Self {
65
            values: Vec::new(),
74✔
66
            buffer: None,
67
            capacity: 0,
68
            buffer_usage: BufferUsages::all(),
74✔
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(
37✔
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;
74✔
99
        // Extra manual alignment for device constraints
100
        let aligned_size = if let Some(item_align) = item_align {
108✔
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));
136✔
105
            aligned_size
34✔
106
        } else {
107
            item_size
3✔
108
        };
109
        trace!(
×
110
            "AlignedBufferVec['{}']: item_size={} aligned_size={}",
×
111
            label.as_ref().map(|s| &s[..]).unwrap_or(""),
54✔
112
            item_size,
×
113
            aligned_size
×
114
        );
115
        if buffer_usage.contains(BufferUsages::UNIFORM) {
5✔
116
            <T as ShaderType>::assert_uniform_compat();
5✔
117
        }
118
        Self {
119
            buffer_usage,
120
            aligned_size,
121
            label,
122
            ..Default::default()
123
        }
124
    }
125

126
    fn safe_label(&self) -> &str {
2,216✔
127
        self.label.as_ref().map(|s| &s[..]).unwrap_or("")
13,296✔
128
    }
129

130
    #[inline]
131
    pub fn buffer(&self) -> Option<&Buffer> {
2,226✔
132
        self.buffer.as_ref()
4,452✔
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 {
6,147✔
178
        self.values.len()
12,294✔
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 {
2,215✔
185
        self.aligned_size
2,215✔
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,720✔
211
        self.values.is_empty()
3,440✔
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 {
5,031✔
221
        let index = self.values.len();
15,093✔
222
        self.values.push(value);
15,093✔
223
        index
5,031✔
224
    }
225

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

233
    #[inline]
234
    #[must_use]
235
    pub fn last_mut(&mut self) -> Option<&mut T> {
2,775✔
236
        self.values.last_mut()
2,775✔
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
    #[must_use]
252
    pub fn reserve(&mut self, capacity: usize, device: &RenderDevice) -> bool {
1,109✔
253
        if capacity > self.capacity {
1,109✔
254
            let size = self.aligned_size * capacity;
22✔
255
            trace!(
11✔
256
                "reserve['{}']: increase capacity from {} to {} elements, new size {} bytes",
×
257
                self.safe_label(),
12✔
258
                self.capacity,
×
259
                capacity,
×
260
                size
×
261
            );
262
            self.capacity = capacity;
11✔
263
            if let Some(old_buffer) = self.buffer.take() {
12✔
264
                trace!(
×
265
                    "reserve['{}']: forgetting old buffer #{:?}",
×
266
                    self.safe_label(),
×
267
                    old_buffer.id()
×
268
                );
269
                // Do not explicitly destroy the old buffer here; let the
270
                // backend drop it safely.
271
            }
272
            let new_buffer = device.create_buffer(&BufferDescriptor {
33✔
273
                label: self.label.as_ref().map(|s| &s[..]),
41✔
274
                size: size as BufferAddress,
11✔
275
                usage: BufferUsages::COPY_DST | self.buffer_usage,
11✔
276
                mapped_at_creation: false,
×
277
            });
278
            trace!(
11✔
279
                "reserve['{}']: created new buffer #{:?}",
×
280
                self.safe_label(),
12✔
281
                new_buffer.id(),
12✔
282
            );
283
            self.buffer = Some(new_buffer);
22✔
284
            // FIXME - this discards the old content if any!!!
285
            true
11✔
286
        } else {
287
            false
1,098✔
288
        }
289
    }
290

291
    /// Schedule the buffer write to GPU.
292
    ///
293
    /// # Returns
294
    ///
295
    /// `true` if the buffer was (re)allocated, `false` otherwise. This
296
    /// indicates whether bind groups need to be re-created.
297
    #[must_use]
298
    pub fn write_buffer(&mut self, device: &RenderDevice, queue: &RenderQueue) -> bool {
1,716✔
299
        if self.values.is_empty() {
3,432✔
300
            return false;
608✔
301
        }
302
        trace!(
×
303
            "write_buffer['{}']: values.len={} item_size={} aligned_size={}",
×
304
            self.safe_label(),
2,204✔
305
            self.values.len(),
2,204✔
306
            self.item_size,
×
307
            self.aligned_size
×
308
        );
309
        let buffer_changed = self.reserve(self.values.len(), device);
×
310
        if let Some(buffer) = &self.buffer {
1,108✔
311
            let aligned_size = self.aligned_size * self.values.len();
×
NEW
312
            if self.aligned_size == self.item_size {
×
313
                // The CPU storage already contains aligned items; directly write to GPU buffer.
NEW
314
                trace!(
×
NEW
315
                    "+ direct-write['{}']: size={}B buffer={:?}",
×
NEW
316
                    self.safe_label(),
×
NEW
317
                    aligned_size,
×
NEW
318
                    buffer.id(),
×
319
                );
NEW
320
                let src: &[u8] = cast_slice(&self.values[..]);
×
NEW
321
                queue.write_buffer(buffer, 0, src);
×
322
            } else {
323
                // The CPU storage contains items smaller than the aligned size; copy with
324
                // padding into a temporary storage to align items, then write from that
325
                // temporary to the GPU buffer.
326
                trace!(
1,108✔
NEW
327
                    "+ aligned_buffer['{}']: size={}B buffer={:?}",
×
328
                    self.safe_label(),
2,204✔
NEW
329
                    aligned_size,
×
330
                    buffer.id(),
2,204✔
331
                );
NEW
332
                let mut aligned_buffer: Vec<u8> = vec![0; aligned_size];
×
333
                for i in 0..self.values.len() {
5,010✔
NEW
334
                    let src: &[u8] = cast_slice(std::slice::from_ref(&self.values[i]));
×
NEW
335
                    let dst_offset = i * self.aligned_size;
×
NEW
336
                    let dst_range = dst_offset..dst_offset + self.item_size;
×
NEW
337
                    trace!(
×
NEW
338
                        "+ copy: src={:?} ({}B) dst={:?}",
×
339
                        src.as_ptr(),
9,998✔
NEW
340
                        self.item_size,
×
NEW
341
                        dst_range
×
342
                    );
NEW
343
                    let dst = &mut aligned_buffer[dst_range];
×
NEW
344
                    dst.copy_from_slice(src);
×
345
                }
NEW
346
                queue.write_buffer(buffer, 0, &aligned_buffer[..]);
×
347
            }
348
        }
349
        buffer_changed
×
350
    }
351

352
    pub fn clear(&mut self) {
1,713✔
353
        self.values.clear();
3,426✔
354
    }
355
}
356

357
impl<T: Pod + ShaderSize> std::ops::Index<usize> for AlignedBufferVec<T> {
358
    type Output = T;
359

360
    fn index(&self, index: usize) -> &Self::Output {
×
361
        &self.values[index]
×
362
    }
363
}
364

365
impl<T: Pod + ShaderSize> std::ops::IndexMut<usize> for AlignedBufferVec<T> {
366
    fn index_mut(&mut self, index: usize) -> &mut Self::Output {
×
367
        &mut self.values[index]
×
368
    }
369
}
370

371
#[derive(Debug, Clone, PartialEq, Eq)]
372
struct FreeRow(pub Range<u32>);
373

374
impl PartialOrd for FreeRow {
375
    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
×
376
        Some(self.cmp(other))
×
377
    }
378
}
379

380
impl Ord for FreeRow {
381
    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
6✔
382
        self.0.start.cmp(&other.0.start)
18✔
383
    }
384
}
385

386
/// Like [`AlignedBufferVec`], but for heterogenous data.
387
#[derive(Debug)]
388
pub struct HybridAlignedBufferVec {
389
    /// Pending values accumulated on CPU and not yet written to GPU.
390
    values: Vec<u8>,
391
    /// GPU buffer if already allocated, or `None` otherwise.
392
    buffer: Option<Buffer>,
393
    /// Capacity of the buffer, in bytes.
394
    capacity: usize,
395
    /// Alignment of each element, in bytes.
396
    item_align: usize,
397
    /// GPU buffer usages.
398
    buffer_usage: BufferUsages,
399
    /// Optional GPU buffer name, for debugging.
400
    label: Option<String>,
401
    /// Free ranges available for re-allocation. Those are row ranges; byte
402
    /// ranges are obtained by multiplying these by `item_align`.
403
    free_rows: Vec<FreeRow>,
404
    /// Is the GPU buffer stale and the CPU one need to be re-uploaded?
405
    is_stale: bool,
406
}
407

408
impl HybridAlignedBufferVec {
409
    /// Create a new collection.
410
    ///
411
    /// `item_align` is the alignment for items in the collection.
412
    pub fn new(buffer_usage: BufferUsages, item_align: NonZeroU64, label: Option<String>) -> Self {
5✔
413
        let item_align = item_align.get() as usize;
10✔
414
        trace!(
5✔
415
            "HybridAlignedBufferVec['{}']: item_align={} byte",
416
            label.as_ref().map(|s| &s[..]).unwrap_or(""),
18✔
417
            item_align,
418
        );
419
        Self {
420
            values: vec![],
10✔
421
            buffer: None,
422
            capacity: 0,
423
            item_align,
424
            buffer_usage,
425
            label,
426
            free_rows: vec![],
5✔
427
            is_stale: true,
428
        }
429
    }
430

431
    #[inline]
432
    pub fn buffer(&self) -> Option<&Buffer> {
1,102✔
433
        self.buffer.as_ref()
2,204✔
434
    }
435

436
    /// Get a binding for the entire buffer.
437
    #[allow(dead_code)]
438
    #[inline]
439
    pub fn max_binding(&self) -> Option<BindingResource<'_>> {
×
440
        // FIXME - Return a Buffer wrapper first, which can be unwrapped, then from that
441
        // wrapper implement all the xxx_binding() helpers. That avoids a bunch of "if
442
        // let Some()" everywhere when we know the buffer is valid. The only reason the
443
        // buffer might not be valid is if it was not created, and in that case
444
        // we wouldn't be calling the xxx_bindings() helpers, we'd have earlied out
445
        // before.
446
        let buffer = self.buffer()?;
×
447
        Some(BindingResource::Buffer(BufferBinding {
×
448
            buffer,
×
449
            offset: 0,
×
450
            size: None, // entire buffer
×
451
        }))
452
    }
453

454
    /// Get a binding for the first `size` bytes of the buffer.
455
    ///
456
    /// # Panics
457
    ///
458
    /// Panics if `size` is zero.
459
    #[allow(dead_code)]
460
    #[inline]
461
    pub fn lead_binding(&self, size: u32) -> Option<BindingResource<'_>> {
×
462
        let buffer = self.buffer()?;
×
463
        let size = NonZeroU64::new(size as u64).unwrap();
×
464
        Some(BindingResource::Buffer(BufferBinding {
×
465
            buffer,
×
466
            offset: 0,
×
467
            size: Some(size),
×
468
        }))
469
    }
470

471
    /// Get a binding for a subset of the elements of the buffer.
472
    ///
473
    /// Returns a binding for the elements in the range `offset..offset+count`.
474
    ///
475
    /// # Panics
476
    ///
477
    /// Panics if `offset` is not a multiple of the alignment specified on
478
    /// construction.
479
    ///
480
    /// Panics if `size` is zero.
481
    #[allow(dead_code)]
482
    #[inline]
483
    pub fn range_binding(&self, offset: u32, size: u32) -> Option<BindingResource<'_>> {
×
484
        assert!((offset as usize).is_multiple_of(self.item_align));
×
485
        let buffer = self.buffer()?;
×
486
        let size = NonZeroU64::new(size as u64).unwrap();
×
487
        Some(BindingResource::Buffer(BufferBinding {
×
488
            buffer,
×
489
            offset: offset as u64,
×
490
            size: Some(size),
×
491
        }))
492
    }
493

494
    /// Capacity of the allocated GPU buffer, in bytes.
495
    ///
496
    /// This may be zero if the buffer was not allocated yet. In general, this
497
    /// can differ from the actual data size cached on CPU and waiting to be
498
    /// uploaded to GPU.
499
    #[inline]
500
    #[allow(dead_code)]
501
    pub fn capacity(&self) -> usize {
×
502
        self.capacity
×
503
    }
504

505
    /// Current buffer size, in bytes.
506
    ///
507
    /// This represents the size of the CPU data uploaded to GPU. Pending a GPU
508
    /// buffer re-allocation or re-upload, this size might differ from the
509
    /// actual GPU buffer size. But they're eventually consistent.
510
    #[allow(dead_code)]
511
    #[inline]
512
    pub fn len(&self) -> usize {
×
513
        self.values.len()
×
514
    }
515

516
    /// Alignment, in bytes, of all the elements.
517
    #[allow(dead_code)]
518
    #[inline]
519
    pub fn item_align(&self) -> usize {
×
520
        self.item_align
×
521
    }
522

523
    /// Calculate a dynamic byte offset for a bind group from an array element
524
    /// index.
525
    ///
526
    /// This returns the product of `index` by the internal [`item_align()`].
527
    ///
528
    /// # Panic
529
    ///
530
    /// Panics if the `index` is too large, producing a byte offset larger than
531
    /// `u32::MAX`.
532
    ///
533
    /// [`item_align()`]: crate::HybridAlignedBufferVec::item_align
534
    #[allow(dead_code)]
535
    #[inline]
536
    pub fn dynamic_offset(&self, index: usize) -> u32 {
×
537
        let offset = self.item_align * index;
×
538
        assert!(offset <= u32::MAX as usize);
×
539
        u32::try_from(offset).expect("HybridAlignedBufferVec index out of bounds")
×
540
    }
541

542
    #[inline]
543
    #[allow(dead_code)]
544
    pub fn is_empty(&self) -> bool {
1,142✔
545
        self.values.is_empty()
2,284✔
546
    }
547

548
    /// Append a value to the buffer.
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 was
556
    /// inserted, which is guaranteed to be a multiple of [`item_align()`].
557
    /// The range span is the item byte size.
558
    ///
559
    /// [`item_align()`]: self::HybridAlignedBufferVec::item_align
560
    #[allow(dead_code)]
561
    pub fn push<T: Pod + ShaderSize>(&mut self, value: &T) -> Range<u32> {
15✔
562
        let src: &[u8] = cast_slice(std::slice::from_ref(value));
60✔
563
        assert_eq!(value.size().get() as usize, src.len());
75✔
564
        self.push_raw(src)
45✔
565
    }
566

567
    /// Append a slice of values to the buffer.
568
    ///
569
    /// The values are assumed to be tightly packed, and will be copied
570
    /// back-to-back into the buffer, without any padding between them. This
571
    /// means that the individul slice items must be properly aligned relative
572
    /// to the beginning of the slice.
573
    ///
574
    /// As with [`set_content()`], the content is stored on the CPU and uploaded
575
    /// on the GPU once [`write_buffers()`] is called.
576
    ///
577
    /// # Returns
578
    ///
579
    /// Returns a range starting at the byte offset at which the new element
580
    /// (the slice) was inserted, which is guaranteed to be a multiple of
581
    /// [`item_align()`]. The range span is the item byte size.
582
    ///
583
    /// # Panics
584
    ///
585
    /// Panics if the byte size of the element `T` is not at least a multiple of
586
    /// the minimum GPU alignment, which is 4 bytes. Note that this doesn't
587
    /// guarantee that the written data is well-formed for use on GPU, as array
588
    /// elements on GPU have other alignment requirements according to WGSL, but
589
    /// at least this catches obvious errors.
590
    ///
591
    /// [`item_align()`]: self::HybridAlignedBufferVec::item_align
592
    #[allow(dead_code)]
593
    pub fn push_many<T: Pod + ShaderSize>(&mut self, value: &[T]) -> Range<u32> {
×
594
        assert_eq!(size_of::<T>() % 4, 0);
×
595
        let src: &[u8] = cast_slice(value);
×
596
        self.push_raw(src)
×
597
    }
598

599
    pub fn push_raw(&mut self, src: &[u8]) -> Range<u32> {
15✔
600
        self.is_stale = true;
15✔
601

602
        // Calculate the number of (aligned) rows to allocate
603
        let num_rows = src.len().div_ceil(self.item_align) as u32;
60✔
604

605
        // Try to find a block of free rows which can accomodate it, and pick the
606
        // smallest one in order to limit wasted space.
607
        let mut best_slot: Option<(u32, usize)> = None;
45✔
608
        for (index, range) in self.free_rows.iter().enumerate() {
30✔
609
            let free_rows = range.0.end - range.0.start;
×
610
            if free_rows >= num_rows {
×
611
                let wasted_rows = free_rows - num_rows;
×
612
                // If we found a slot with the exact size, just use it already
613
                if wasted_rows == 0 {
×
614
                    best_slot = Some((0, index));
×
615
                    break;
616
                }
617
                // Otherwise try to find the smallest oversized slot to reduce wasted space
618
                if let Some(best_slot) = best_slot.as_mut() {
×
619
                    if wasted_rows < best_slot.0 {
×
620
                        *best_slot = (wasted_rows, index);
×
621
                    }
622
                } else {
623
                    best_slot = Some((wasted_rows, index));
×
624
                }
625
            }
626
        }
627

628
        // Insert into existing space
629
        if let Some((_, index)) = best_slot {
15✔
630
            let row_range = self.free_rows.remove(index);
631
            let offset = row_range.0.start as usize * self.item_align;
632
            let free_size = (row_range.0.end - row_range.0.start) as usize * self.item_align;
633
            let size = src.len();
634
            assert!(size <= free_size);
635

636
            let dst = self.values.as_mut_ptr();
×
637
            // SAFETY: dst is guaranteed to point to allocated bytes, which are already
638
            // initialized from a previous call, and are initialized by overwriting the
639
            // bytes with those of a POD type.
640
            #[allow(unsafe_code)]
641
            unsafe {
642
                let dst = dst.add(offset);
×
643
                dst.copy_from_nonoverlapping(src.as_ptr(), size);
×
644
            }
645

646
            let start = offset as u32;
×
647
            let end = start + size as u32;
×
648
            start..end
×
649
        }
650
        // Insert at end of vector, after resizing it
651
        else {
652
            // Calculate new aligned insertion offset and new capacity
653
            let offset = self.values.len().next_multiple_of(self.item_align);
75✔
654
            let size = src.len();
45✔
655
            let new_capacity = offset + size;
30✔
656
            if new_capacity > self.values.capacity() {
30✔
657
                let additional = new_capacity - self.values.len();
12✔
658
                self.values.reserve(additional)
12✔
659
            }
660

661
            // Insert padding if needed
662
            if offset > self.values.len() {
40✔
663
                self.values.resize(offset, 0);
20✔
664
            }
665

666
            // Insert serialized value
667
            // Dealing with safe code via Vec::spare_capacity_mut() is quite difficult
668
            // without the upcoming (unstable) additions to MaybeUninit to deal with arrays.
669
            // To prevent having to loop over individual u8, we use direct pointers instead.
670
            assert!(self.values.capacity() >= offset + size);
60✔
671
            assert_eq!(self.values.len(), offset);
45✔
672
            let dst = self.values.as_mut_ptr();
45✔
673
            // SAFETY: dst is guaranteed to point to allocated (offset+size) bytes, which
674
            // are written by copying a Pod type, so ensures those values are initialized,
675
            // and the final size is set to exactly (offset+size).
676
            #[allow(unsafe_code)]
677
            unsafe {
678
                let dst = dst.add(offset);
75✔
679
                dst.copy_from_nonoverlapping(src.as_ptr(), size);
90✔
680
                self.values.set_len(offset + size);
45✔
681
            }
682

683
            debug_assert_eq!(offset % self.item_align, 0);
30✔
684
            let start = offset as u32;
30✔
685
            let end = start + size as u32;
30✔
686
            start..end
15✔
687
        }
688
    }
689

690
    /// Remove a range of bytes previously added.
691
    ///
692
    /// Remove a range of bytes previously returned by adding one or more
693
    /// elements with [`push()`] or [`push_many()`].
694
    ///
695
    /// # Returns
696
    ///
697
    /// Returns `true` if the range was valid and the corresponding data was
698
    /// removed, or `false` otherwise. In that case, the buffer is not modified.
699
    ///
700
    /// [`push()`]: Self::push
701
    /// [`push_many()`]: Self::push_many
702
    pub fn remove(&mut self, range: Range<u32>) -> bool {
16✔
703
        // Can only remove entire blocks starting at an aligned size
704
        let align = self.item_align as u32;
32✔
705
        if !range.start.is_multiple_of(align) {
32✔
706
            return false;
×
707
        }
708

709
        // Check for out of bounds argument
710
        let end = self.values.len() as u32;
32✔
711
        if range.start >= end || range.end > end {
32✔
712
            return false;
×
713
        }
714

715
        // Note: See below, sometimes self.values() has some padding left we couldn't
716
        // recover earlier beause we didn't know the size of this allocation, but we
717
        // need to still deallocate the row here.
718
        if range.end == end || range.end.next_multiple_of(align) == end {
11✔
719
            // If the allocation is at the end of the buffer, shorten the CPU values. This
720
            // ensures is_empty() eventually returns true.
721
            let mut new_row_end = range.start.div_ceil(align);
7✔
722

723
            // Walk the (sorted) free list to also dequeue any range which is now at the end
724
            // of the buffer
725
            while let Some(free_row) = self.free_rows.pop() {
15✔
726
                if free_row.0.end == new_row_end {
4✔
727
                    new_row_end = free_row.0.start;
4✔
728
                } else {
729
                    self.free_rows.push(free_row);
×
730
                    break;
731
                }
732
            }
733

734
            // Note: we can't really recover any padding here because we don't know the
735
            // exact size of that allocation, only its row-aligned size.
736
            self.values.truncate((new_row_end * align) as usize);
737
        } else {
738
            // Otherwise, save the row into the free list.
739
            let start = range.start / align;
9✔
740
            let end = range.end.div_ceil(align);
741
            let free_row = FreeRow(start..end);
742

743
            // Insert as sorted
744
            if self.free_rows.is_empty() {
4✔
745
                // Special case to simplify below, and to avoid binary_search()
746
                self.free_rows.push(free_row);
8✔
747
            } else if let Err(index) = self.free_rows.binary_search(&free_row) {
13✔
748
                if index >= self.free_rows.len() {
749
                    // insert at end
750
                    let prev = self.free_rows.last_mut().unwrap(); // known
3✔
751
                    if prev.0.end == free_row.0.start {
2✔
752
                        // merge with last value
753
                        prev.0.end = free_row.0.end;
1✔
754
                    } else {
755
                        // insert last, with gap
756
                        self.free_rows.push(free_row);
×
757
                    }
758
                } else if index == 0 {
3✔
759
                    // insert at start
760
                    let next = &mut self.free_rows[0];
4✔
761
                    if free_row.0.end == next.0.start {
3✔
762
                        // merge with next
763
                        next.0.start = free_row.0.start;
1✔
764
                    } else {
765
                        // insert first, with gap
766
                        self.free_rows.insert(0, free_row);
1✔
767
                    }
768
                } else {
769
                    // insert between 2 existing elements
770
                    let prev = &mut self.free_rows[index - 1];
1✔
771
                    if prev.0.end == free_row.0.start {
772
                        // merge with previous value
773
                        prev.0.end = free_row.0.end;
1✔
774

775
                        let prev = self.free_rows[index - 1].clone();
3✔
776
                        let next = &mut self.free_rows[index];
2✔
777
                        if prev.0.end == next.0.start {
2✔
778
                            // also merge prev with next, and remove prev
779
                            next.0.start = prev.0.start;
2✔
780
                            self.free_rows.remove(index - 1);
2✔
781
                        }
782
                    } else {
783
                        let next = &mut self.free_rows[index];
×
784
                        if free_row.0.end == next.0.start {
×
785
                            // merge with next value
786
                            next.0.start = free_row.0.start;
×
787
                        } else {
788
                            // insert between 2 values, with gaps on both sides
789
                            self.free_rows.insert(0, free_row);
×
790
                        }
791
                    }
792
                }
793
            } else {
794
                // The range exists in the free list, this means it's already removed. This is a
795
                // duplicate; ignore it.
796
                return false;
1✔
797
            }
798
        }
799
        self.is_stale = true;
15✔
800
        true
801
    }
802

803
    /// Update an allocated entry with a new value.
804
    #[allow(dead_code)]
805
    #[inline]
806
    pub fn update<T: Pod + ShaderSize>(&mut self, offset: u32, value: &T) {
×
807
        let data: &[u8] = cast_slice(std::slice::from_ref(value));
×
808
        assert_eq!(value.size().get() as usize, data.len());
×
809
        self.update_raw(offset, data);
×
810
    }
811

812
    /// Update an allocated entry with new data.
813
    pub fn update_raw(&mut self, offset: u32, data: &[u8]) {
×
814
        // Can only update entire blocks starting at an aligned size
815
        let align = self.item_align as u32;
×
816
        if !offset.is_multiple_of(align) {
×
817
            return;
×
818
        }
819

820
        // Check for out of bounds argument
821
        let end = self.values.len() as u32;
×
822
        let data_end = offset + data.len() as u32;
×
823
        if offset >= end || data_end > end {
×
824
            return;
×
825
        }
826

827
        let dst: &mut [u8] = &mut self.values[offset as usize..data_end as usize];
×
828
        dst.copy_from_slice(data);
×
829

830
        self.is_stale = true;
×
831
    }
832

833
    /// Reserve some capacity into the buffer.
834
    ///
835
    /// If the buffer is reallocated, the old content (on the GPU) is lost, and
836
    /// needs to be re-uploaded to the newly-created buffer. This is done with
837
    /// [`write_buffer()`].
838
    ///
839
    /// # Returns
840
    ///
841
    /// `true` if the buffer was (re)allocated, or `false` if an existing buffer
842
    /// was reused which already had enough capacity.
843
    ///
844
    /// [`write_buffer()`]: crate::AlignedBufferVec::write_buffer
845
    pub fn reserve(&mut self, capacity: usize, device: &RenderDevice) -> bool {
×
846
        if capacity > self.capacity {
×
847
            trace!(
×
848
                "reserve: increase capacity from {} to {} bytes",
849
                self.capacity,
850
                capacity,
851
            );
852
            self.capacity = capacity;
×
853
            if let Some(old_buffer) = self.buffer.take() {
×
854
                trace!("reserve: forgetting old buffer #{:?}", old_buffer.id());
×
855
                // Do not explicitly destroy the old buffer here; let the
856
                // backend drop it safely.
857
            }
858
            self.buffer = Some(device.create_buffer(&BufferDescriptor {
×
859
                label: self.label.as_ref().map(|s| &s[..]),
×
860
                size: capacity as BufferAddress,
×
861
                usage: BufferUsages::COPY_DST | self.buffer_usage,
×
862
                mapped_at_creation: false,
863
            }));
864
            self.is_stale = !self.values.is_empty();
×
865
            // FIXME - this discards the old content if any!!!
866
            true
×
867
        } else {
868
            false
×
869
        }
870
    }
871

872
    /// Schedule the buffer write to GPU.
873
    ///
874
    /// # Returns
875
    ///
876
    /// `true` if the buffer was (re)allocated, `false` otherwise. If the buffer
877
    /// was reallocated, all bind groups referencing the old buffer should be
878
    /// destroyed.
879
    pub fn write_buffer(&mut self, device: &RenderDevice, queue: &RenderQueue) -> bool {
570✔
880
        if self.values.is_empty() || !self.is_stale {
1,140✔
881
            return false;
570✔
882
        }
883
        let size = self.values.len();
×
884
        trace!(
×
885
            "hybrid abv: write_buffer: size={}B item_align={}B",
886
            size,
887
            self.item_align,
888
        );
889
        let buffer_changed = self.reserve(size, device);
×
890
        if let Some(buffer) = &self.buffer {
×
891
            queue.write_buffer(buffer, 0, self.values.as_slice());
×
892
            self.is_stale = false;
×
893
        }
894
        buffer_changed
×
895
    }
896

897
    #[allow(dead_code)]
898
    pub fn clear(&mut self) {
×
899
        if !self.values.is_empty() {
×
900
            self.is_stale = true;
×
901
        }
902
        self.values.clear();
×
903
    }
904
}
905

906
#[cfg(test)]
907
mod tests {
908
    use std::num::NonZeroU64;
909

910
    use bevy::math::Vec3;
911
    use bytemuck::{Pod, Zeroable};
912

913
    use super::*;
914

915
    #[repr(C)]
916
    #[derive(Debug, Default, Clone, Copy, Pod, Zeroable, ShaderType)]
917
    pub(crate) struct GpuDummy {
918
        pub v: Vec3,
919
    }
920

921
    #[repr(C)]
922
    #[derive(Debug, Default, Clone, Copy, Pod, Zeroable, ShaderType)]
923
    pub(crate) struct GpuDummyComposed {
924
        pub simple: GpuDummy,
925
        pub tag: u32,
926
        // GPU padding to 16 bytes due to GpuDummy forcing align to 16 bytes
927
    }
928

929
    #[repr(C)]
930
    #[derive(Debug, Clone, Copy, Pod, Zeroable, ShaderType)]
931
    pub(crate) struct GpuDummyLarge {
932
        pub simple: GpuDummy,
933
        pub tag: u32,
934
        pub large: [f32; 128],
935
    }
936

937
    #[test]
938
    fn abv_sizes() {
939
        // Rust
940
        assert_eq!(std::mem::size_of::<GpuDummy>(), 12);
941
        assert_eq!(std::mem::align_of::<GpuDummy>(), 4);
942
        assert_eq!(std::mem::size_of::<GpuDummyComposed>(), 16); // tight packing
943
        assert_eq!(std::mem::align_of::<GpuDummyComposed>(), 4);
944
        assert_eq!(std::mem::size_of::<GpuDummyLarge>(), 132 * 4); // tight packing
945
        assert_eq!(std::mem::align_of::<GpuDummyLarge>(), 4);
946

947
        // GPU
948
        assert_eq!(<GpuDummy as ShaderType>::min_size().get(), 16); // Vec3 gets padded to 16 bytes
949
        assert_eq!(<GpuDummy as ShaderSize>::SHADER_SIZE.get(), 16);
950
        assert_eq!(<GpuDummyComposed as ShaderType>::min_size().get(), 32); // align is 16 bytes, forces padding
951
        assert_eq!(<GpuDummyComposed as ShaderSize>::SHADER_SIZE.get(), 32);
952
        assert_eq!(<GpuDummyLarge as ShaderType>::min_size().get(), 544); // align is 16 bytes, forces padding
953
        assert_eq!(<GpuDummyLarge as ShaderSize>::SHADER_SIZE.get(), 544);
954

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

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

997
        for (item_align, expected_aligned_size) in [
998
            (0, 544),
999
            (4, 544),
1000
            (8, 544),
1001
            (16, 544),
1002
            (32, 544),
1003
            (256, 768),
1004
            (512, 1024),
1005
        ] {
1006
            let mut abv = AlignedBufferVec::<GpuDummyLarge>::new(
1007
                BufferUsages::STORAGE,
1008
                NonZeroU64::new(item_align),
1009
                None,
1010
            );
1011
            assert_eq!(abv.aligned_size(), expected_aligned_size);
1012
            assert!(abv.is_empty());
1013
            abv.push(GpuDummyLarge {
1014
                simple: Default::default(),
1015
                tag: 0,
1016
                large: [0.; 128],
1017
            });
1018
            assert!(!abv.is_empty());
1019
            assert_eq!(abv.len(), 1);
1020
        }
1021
    }
1022

1023
    #[test]
1024
    fn habv_remove() {
1025
        let mut habv =
1026
            HybridAlignedBufferVec::new(BufferUsages::STORAGE, NonZeroU64::new(32).unwrap(), None);
1027
        assert!(habv.is_empty());
1028
        assert_eq!(habv.item_align, 32);
1029

1030
        // +r -r
1031
        {
1032
            let r = habv.push(&42u32);
1033
            assert_eq!(r, 0..4);
1034
            assert!(!habv.is_empty());
1035
            assert_eq!(habv.values.len(), 4);
1036
            assert!(habv.free_rows.is_empty());
1037

1038
            assert!(habv.remove(r));
1039
            assert!(habv.is_empty());
1040
            assert!(habv.values.is_empty());
1041
            assert!(habv.free_rows.is_empty());
1042
        }
1043

1044
        // +r0 +r1 +r2 -r0 -r0 -r1 -r2
1045
        {
1046
            let r0 = habv.push(&42u32);
1047
            let r1 = habv.push(&84u32);
1048
            let r2 = habv.push(&84u32);
1049
            assert_eq!(r0, 0..4);
1050
            assert_eq!(r1, 32..36);
1051
            assert_eq!(r2, 64..68);
1052
            assert!(!habv.is_empty());
1053
            assert_eq!(habv.values.len(), 68);
1054
            assert!(habv.free_rows.is_empty());
1055

1056
            assert!(habv.remove(r0.clone()));
1057
            assert!(!habv.is_empty());
1058
            assert_eq!(habv.values.len(), 68);
1059
            assert_eq!(habv.free_rows.len(), 1);
1060
            assert_eq!(habv.free_rows[0], FreeRow(0..1));
1061

1062
            // dupe; no-op
1063
            assert!(!habv.remove(r0));
1064

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

1071
            assert!(habv.remove(r2));
1072
            assert!(habv.is_empty());
1073
            assert_eq!(habv.values.len(), 0);
1074
            assert!(habv.free_rows.is_empty());
1075
        }
1076

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

1089
            assert!(habv.remove(r1.clone()));
1090
            assert!(!habv.is_empty());
1091
            assert_eq!(habv.values.len(), 68);
1092
            assert_eq!(habv.free_rows.len(), 1);
1093
            assert_eq!(habv.free_rows[0], FreeRow(1..2));
1094

1095
            assert!(habv.remove(r0.clone()));
1096
            assert!(!habv.is_empty());
1097
            assert_eq!(habv.values.len(), 68);
1098
            assert_eq!(habv.free_rows.len(), 1); // merged!
1099
            assert_eq!(habv.free_rows[0], FreeRow(0..2));
1100

1101
            assert!(habv.remove(r2));
1102
            assert!(habv.is_empty());
1103
            assert_eq!(habv.values.len(), 0);
1104
            assert!(habv.free_rows.is_empty());
1105
        }
1106

1107
        // +r0 +r1 +r2 -r1 -r2 -r0
1108
        {
1109
            let r0 = habv.push(&42u32);
1110
            let r1 = habv.push(&84u32);
1111
            let r2 = habv.push(&84u32);
1112
            assert_eq!(r0, 0..4);
1113
            assert_eq!(r1, 32..36);
1114
            assert_eq!(r2, 64..68);
1115
            assert!(!habv.is_empty());
1116
            assert_eq!(habv.values.len(), 68);
1117
            assert!(habv.free_rows.is_empty());
1118

1119
            assert!(habv.remove(r1.clone()));
1120
            assert!(!habv.is_empty());
1121
            assert_eq!(habv.values.len(), 68);
1122
            assert_eq!(habv.free_rows.len(), 1);
1123
            assert_eq!(habv.free_rows[0], FreeRow(1..2));
1124

1125
            assert!(habv.remove(r2.clone()));
1126
            assert!(!habv.is_empty());
1127
            assert_eq!(habv.values.len(), 32); // can't recover exact alloc (4), only row-aligned size (32)
1128
            assert!(habv.free_rows.is_empty()); // merged!
1129

1130
            assert!(habv.remove(r0));
1131
            assert!(habv.is_empty());
1132
            assert_eq!(habv.values.len(), 0);
1133
            assert!(habv.free_rows.is_empty());
1134
        }
1135

1136
        // +r0 +r1 +r2 +r3 +r4 -r3 -r1 -r2 -r4 r0
1137
        {
1138
            let r0 = habv.push(&42u32);
1139
            let r1 = habv.push(&84u32);
1140
            let r2 = habv.push(&84u32);
1141
            let r3 = habv.push(&84u32);
1142
            let r4 = habv.push(&84u32);
1143
            assert_eq!(r0, 0..4);
1144
            assert_eq!(r1, 32..36);
1145
            assert_eq!(r2, 64..68);
1146
            assert_eq!(r3, 96..100);
1147
            assert_eq!(r4, 128..132);
1148
            assert!(!habv.is_empty());
1149
            assert_eq!(habv.values.len(), 132);
1150
            assert!(habv.free_rows.is_empty());
1151

1152
            assert!(habv.remove(r3.clone()));
1153
            assert!(!habv.is_empty());
1154
            assert_eq!(habv.values.len(), 132);
1155
            assert_eq!(habv.free_rows.len(), 1);
1156
            assert_eq!(habv.free_rows[0], FreeRow(3..4));
1157

1158
            assert!(habv.remove(r1.clone()));
1159
            assert!(!habv.is_empty());
1160
            assert_eq!(habv.values.len(), 132);
1161
            assert_eq!(habv.free_rows.len(), 2);
1162
            assert_eq!(habv.free_rows[0], FreeRow(1..2)); // sorted!
1163
            assert_eq!(habv.free_rows[1], FreeRow(3..4));
1164

1165
            assert!(habv.remove(r2.clone()));
1166
            assert!(!habv.is_empty());
1167
            assert_eq!(habv.values.len(), 132);
1168
            assert_eq!(habv.free_rows.len(), 1); // merged!
1169
            assert_eq!(habv.free_rows[0], FreeRow(1..4)); // merged!
1170

1171
            assert!(habv.remove(r4.clone()));
1172
            assert!(!habv.is_empty());
1173
            assert_eq!(habv.values.len(), 32); // can't recover exact alloc (4), only row-aligned size (32)
1174
            assert!(habv.free_rows.is_empty());
1175

1176
            assert!(habv.remove(r0));
1177
            assert!(habv.is_empty());
1178
            assert_eq!(habv.values.len(), 0);
1179
            assert!(habv.free_rows.is_empty());
1180
        }
1181
    }
1182
}
1183

1184
#[cfg(all(test, feature = "gpu_tests"))]
1185
mod gpu_tests {
1186
    use tests::*;
1187

1188
    use super::*;
1189
    use crate::test_utils::MockRenderer;
1190

1191
    #[test]
1192
    fn abv_write() {
1193
        let renderer = MockRenderer::new();
1194
        let device = renderer.device();
1195
        let queue = renderer.queue();
1196

1197
        // Create a dummy CommandBuffer to force the write_buffer() call to have any
1198
        // effect.
1199
        let encoder = device.create_command_encoder(&wgpu::CommandEncoderDescriptor {
1200
            label: Some("test"),
1201
        });
1202
        let command_buffer = encoder.finish();
1203

1204
        let item_align = device.limits().min_storage_buffer_offset_alignment as u64;
1205
        let mut abv = AlignedBufferVec::<GpuDummyComposed>::new(
1206
            BufferUsages::STORAGE | BufferUsages::MAP_READ,
1207
            NonZeroU64::new(item_align),
1208
            None,
1209
        );
1210
        let final_align = item_align.max(<GpuDummyComposed as ShaderSize>::SHADER_SIZE.get());
1211
        assert_eq!(abv.aligned_size(), final_align as usize);
1212

1213
        const CAPACITY: usize = 42;
1214

1215
        // Write buffer (CPU -> GPU)
1216
        abv.push(GpuDummyComposed {
1217
            tag: 1,
1218
            ..Default::default()
1219
        });
1220
        abv.push(GpuDummyComposed {
1221
            tag: 2,
1222
            ..Default::default()
1223
        });
1224
        abv.push(GpuDummyComposed {
1225
            tag: 3,
1226
            ..Default::default()
1227
        });
1228
        assert!(abv.reserve(CAPACITY, &device));
1229
        assert!(!abv.write_buffer(&device, &queue));
1230
        // need a submit() for write_buffer() to be processed
1231
        queue.submit([command_buffer]);
1232
        let (tx, rx) = futures::channel::oneshot::channel();
1233
        queue.on_submitted_work_done(move || {
1234
            tx.send(()).unwrap();
1235
        });
1236
        let _ = device.poll(wgpu::PollType::Wait {
1237
            submission_index: None,
1238
            timeout: None,
1239
        });
1240
        let _ = futures::executor::block_on(rx);
1241
        println!("Buffer written");
1242

1243
        // Read back (GPU -> CPU)
1244
        let buffer = abv.buffer();
1245
        let buffer = buffer.as_ref().expect("Buffer was not allocated");
1246
        let buffer = buffer.slice(..);
1247
        let (tx, rx) = futures::channel::oneshot::channel();
1248
        buffer.map_async(wgpu::MapMode::Read, move |result| {
1249
            tx.send(result).unwrap();
1250
        });
1251
        let _ = device.poll(wgpu::PollType::Wait {
1252
            submission_index: None,
1253
            timeout: None,
1254
        });
1255
        let _result = futures::executor::block_on(rx);
1256
        let view = buffer.get_mapped_range();
1257

1258
        // Validate content
1259
        assert_eq!(view.len(), final_align as usize * CAPACITY);
1260
        for i in 0..3 {
1261
            let offset = i * final_align as usize;
1262
            let dummy_composed: &[GpuDummyComposed] =
1263
                cast_slice(&view[offset..offset + std::mem::size_of::<GpuDummyComposed>()]);
1264
            assert_eq!(dummy_composed[0].tag, (i + 1) as u32);
1265
        }
1266
    }
1267
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE TRIAL · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc