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

djeedai / bevy_hanabi / 14602170426

22 Apr 2025 06:39PM UTC coverage: 39.892% (+0.5%) from 39.373%
14602170426

push

github

web-flow
Fix `InitFillDispatchArgs` source offset (#458)

Fix the source buffer offset for the init fill dispatch pass reading from the
`GpuChildInfo` array the event count, and writing into the corresponding
`GpuDispatchIndirect` struct the number of workgroups to dispatch to handle
that number of events.

Remove the incorrect dependency to event buffers; the event buffers only store
the events themselves, which are unused here. Instead, the init fill dispatch
reads the event count, which is stored in the `GpuChildInfo` in a different
buffer.

Remove the dedicated `init_fill_dispatch` compute kernel; instead we can use
the generic `fill_dispatch` one since we're directly reading from and writing
to uniform arrays.

Create a separate batching queue type `InitFillDispatchQueue` for the indirect
init pass, which batches entries before they're submitted into the generic
queue of buffer operations. Remove all sorting from that generic collection,
now renamed `GpuBufferOperations`. Add support for separate queues of
operations submitted as transactional units and dispatched as a single compute
workload.

Fix `AlignedBufferVec::range_binding()` using the wrong (unaligned) item size,
leading to an incorrect binding.

62 of 162 new or added lines in 3 files covered. (38.27%)

5 existing lines in 2 files now uncovered.

3041 of 7623 relevant lines covered (39.89%)

17.6 hits per line

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

42.65
/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
use copyless::VecHelper;
15

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

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

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

127
    #[inline]
128
    pub fn buffer(&self) -> Option<&Buffer> {
1✔
129
        self.buffer.as_ref()
1✔
130
    }
131

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

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

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

177
    #[inline]
178
    pub fn len(&self) -> usize {
27✔
179
        self.values.len()
27✔
180
    }
181

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

189
    /// Calculate a dynamic byte offset for a bind group from an array element
190
    /// index.
191
    ///
192
    /// This returns the product of `index` by the internal [`aligned_size()`].
193
    ///
194
    /// # Panic
195
    ///
196
    /// Panics if the `index` is too large, producing a byte offset larger than
197
    /// `u32::MAX`.
198
    ///
199
    /// [`aligned_size()`]: crate::AlignedBufferVec::aligned_size
200
    #[inline]
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
    #[allow(dead_code)]
209
    pub fn is_empty(&self) -> bool {
42✔
210
        self.values.is_empty()
42✔
211
    }
212

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

225
    /// Reserve some capacity into the buffer.
226
    ///
227
    /// If the buffer is reallocated, the old content (on the GPU) is lost, and
228
    /// needs to be re-uploaded to the newly-created buffer. This is done with
229
    /// [`write_buffer()`].
230
    ///
231
    /// # Returns
232
    ///
233
    /// `true` if the buffer was (re)allocated, or `false` if an existing buffer
234
    /// was reused which already had enough capacity.
235
    ///
236
    /// [`write_buffer()`]: crate::AlignedBufferVec::write_buffer
237
    pub fn reserve(&mut self, capacity: usize, device: &RenderDevice) -> bool {
5✔
238
        if capacity > self.capacity {
5✔
239
            let size = self.aligned_size * capacity;
3✔
240
            trace!(
3✔
241
                "reserve: increase capacity from {} to {} elements, new size {} bytes",
×
242
                self.capacity,
×
243
                capacity,
×
244
                size
×
245
            );
246
            self.capacity = capacity;
3✔
247
            if let Some(buffer) = self.buffer.take() {
4✔
248
                buffer.destroy();
×
249
            }
250
            self.buffer = Some(device.create_buffer(&BufferDescriptor {
3✔
251
                label: self.label.as_ref().map(|s| &s[..]),
8✔
252
                size: size as BufferAddress,
3✔
253
                usage: BufferUsages::COPY_DST | self.buffer_usage,
3✔
254
                mapped_at_creation: false,
3✔
255
            }));
256
            // FIXME - this discards the old content if any!!!
257
            true
3✔
258
        } else {
259
            false
2✔
260
        }
261
    }
262

263
    /// Schedule the buffer write to GPU.
264
    ///
265
    /// # Returns
266
    ///
267
    /// `true` if the buffer was (re)allocated, `false` otherwise.
268
    pub fn write_buffer(&mut self, device: &RenderDevice, queue: &RenderQueue) -> bool {
4✔
269
        if self.values.is_empty() {
4✔
270
            return false;
×
271
        }
272
        trace!(
4✔
273
            "write_buffer: values.len={} item_size={} aligned_size={}",
×
274
            self.values.len(),
×
275
            self.item_size,
×
276
            self.aligned_size
×
277
        );
278
        let buffer_changed = self.reserve(self.values.len(), device);
×
279
        if let Some(buffer) = &self.buffer {
4✔
280
            let aligned_size = self.aligned_size * self.values.len();
×
281
            trace!("aligned_buffer: size={}", aligned_size);
×
282
            let mut aligned_buffer: Vec<u8> = vec![0; aligned_size];
×
283
            for i in 0..self.values.len() {
7✔
284
                let src: &[u8] = cast_slice(std::slice::from_ref(&self.values[i]));
×
285
                let dst_offset = i * self.aligned_size;
×
286
                let dst_range = dst_offset..dst_offset + self.item_size;
×
287
                trace!("+ copy: src={:?} dst={:?}", src.as_ptr(), dst_range);
×
288
                let dst = &mut aligned_buffer[dst_range];
×
289
                dst.copy_from_slice(src);
×
290
            }
291
            let bytes: &[u8] = cast_slice(&aligned_buffer);
×
292
            queue.write_buffer(buffer, 0, bytes);
×
293
        }
294
        buffer_changed
×
295
    }
296

297
    pub fn clear(&mut self) {
3✔
298
        self.values.clear();
3✔
299
    }
300
}
301

302
impl<T: Pod + ShaderSize> std::ops::Index<usize> for AlignedBufferVec<T> {
303
    type Output = T;
304

305
    fn index(&self, index: usize) -> &Self::Output {
×
306
        &self.values[index]
×
307
    }
308
}
309

310
impl<T: Pod + ShaderSize> std::ops::IndexMut<usize> for AlignedBufferVec<T> {
311
    fn index_mut(&mut self, index: usize) -> &mut Self::Output {
×
312
        &mut self.values[index]
×
313
    }
314
}
315

316
#[derive(Debug, Clone, PartialEq, Eq)]
317
struct FreeRow(pub Range<u32>);
318

319
impl PartialOrd for FreeRow {
320
    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
×
321
        Some(self.cmp(other))
×
322
    }
323
}
324

325
impl Ord for FreeRow {
326
    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
6✔
327
        self.0.start.cmp(&other.0.start)
6✔
328
    }
329
}
330

331
/// Like [`AlignedBufferVec`], but for heterogenous data.
332
#[derive(Debug)]
333
pub struct HybridAlignedBufferVec {
334
    /// Pending values accumulated on CPU and not yet written to GPU.
335
    values: Vec<u8>,
336
    /// GPU buffer if already allocated, or `None` otherwise.
337
    buffer: Option<Buffer>,
338
    /// Capacity of the buffer, in bytes.
339
    capacity: usize,
340
    /// Alignment of each element, in bytes.
341
    item_align: usize,
342
    /// GPU buffer usages.
343
    buffer_usage: BufferUsages,
344
    /// Optional GPU buffer name, for debugging.
345
    label: Option<String>,
346
    /// Free ranges available for re-allocation. Those are row ranges; byte
347
    /// ranges are obtained by multiplying these by `item_align`.
348
    free_rows: Vec<FreeRow>,
349
    /// Is the GPU buffer stale and the CPU one need to be re-uploaded?
350
    is_stale: bool,
351
}
352

353
impl HybridAlignedBufferVec {
354
    /// Create a new collection.
355
    ///
356
    /// `item_align` is an optional additional alignment for items in the
357
    /// collection. If greater than the natural alignment dictated by WGSL
358
    /// rules, this extra alignment is enforced. Otherwise it's ignored (so you
359
    /// can pass `None` to ignore, which defaults to no alignment).
360
    pub fn new(
1✔
361
        buffer_usage: BufferUsages,
362
        item_align: Option<NonZeroU64>,
363
        label: Option<String>,
364
    ) -> Self {
365
        let item_align = item_align.map(|nz| nz.get()).unwrap_or(1) as usize;
3✔
366
        trace!(
1✔
367
            "HybridAlignedBufferVec['{}']: item_align={} byte",
×
368
            label.as_ref().map(|s| &s[..]).unwrap_or(""),
×
369
            item_align,
370
        );
371
        Self {
372
            values: vec![],
1✔
373
            buffer: None,
374
            capacity: 0,
375
            item_align,
376
            buffer_usage,
377
            label,
378
            free_rows: vec![],
1✔
379
            is_stale: true,
380
        }
381
    }
382

383
    #[inline]
384
    pub fn buffer(&self) -> Option<&Buffer> {
×
385
        self.buffer.as_ref()
×
386
    }
387

388
    /// Get a binding for the entire buffer.
389
    #[allow(dead_code)]
390
    #[inline]
391
    pub fn max_binding(&self) -> Option<BindingResource> {
×
392
        // FIXME - Return a Buffer wrapper first, which can be unwrapped, then from that
393
        // wrapper implement all the xxx_binding() helpers. That avoids a bunch of "if
394
        // let Some()" everywhere when we know the buffer is valid. The only reason the
395
        // buffer might not be valid is if it was not created, and in that case
396
        // we wouldn't be calling the xxx_bindings() helpers, we'd have earlied out
397
        // before.
398
        let buffer = self.buffer()?;
×
399
        Some(BindingResource::Buffer(BufferBinding {
×
400
            buffer,
×
401
            offset: 0,
×
402
            size: None, // entire buffer
403
        }))
404
    }
405

406
    /// Get a binding for the first `size` bytes of the buffer.
407
    ///
408
    /// # Panics
409
    ///
410
    /// Panics if `size` is zero.
411
    #[allow(dead_code)]
412
    #[inline]
413
    pub fn lead_binding(&self, size: u32) -> Option<BindingResource> {
×
414
        let buffer = self.buffer()?;
×
415
        let size = NonZeroU64::new(size as u64).unwrap();
×
416
        Some(BindingResource::Buffer(BufferBinding {
×
417
            buffer,
×
418
            offset: 0,
×
419
            size: Some(size),
×
420
        }))
421
    }
422

423
    /// Get a binding for a subset of the elements of the buffer.
424
    ///
425
    /// Returns a binding for the elements in the range `offset..offset+count`.
426
    ///
427
    /// # Panics
428
    ///
429
    /// Panics if `offset` is not a multiple of the alignment specified on
430
    /// construction.
431
    ///
432
    /// Panics if `size` is zero.
433
    #[allow(dead_code)]
434
    #[inline]
435
    pub fn range_binding(&self, offset: u32, size: u32) -> Option<BindingResource> {
×
436
        assert!(offset as usize % self.item_align == 0);
×
437
        let buffer = self.buffer()?;
×
438
        let size = NonZeroU64::new(size as u64).unwrap();
×
439
        Some(BindingResource::Buffer(BufferBinding {
×
440
            buffer,
×
441
            offset: offset as u64,
×
442
            size: Some(size),
×
443
        }))
444
    }
445

446
    /// Capacity of the allocated GPU buffer, in bytes.
447
    ///
448
    /// This may be zero if the buffer was not allocated yet. In general, this
449
    /// can differ from the actual data size cached on CPU and waiting to be
450
    /// uploaded to GPU.
451
    #[inline]
452
    #[allow(dead_code)]
453
    pub fn capacity(&self) -> usize {
×
454
        self.capacity
×
455
    }
456

457
    /// Current buffer size, in bytes.
458
    ///
459
    /// This represents the size of the CPU data uploaded to GPU. Pending a GPU
460
    /// buffer re-allocation or re-upload, this size might differ from the
461
    /// actual GPU buffer size. But they're eventually consistent.
462
    #[inline]
463
    pub fn len(&self) -> usize {
×
464
        self.values.len()
×
465
    }
466

467
    /// Alignment, in bytes, of all the elements.
468
    #[allow(dead_code)]
469
    #[inline]
470
    pub fn item_align(&self) -> usize {
×
471
        self.item_align
×
472
    }
473

474
    /// Calculate a dynamic byte offset for a bind group from an array element
475
    /// index.
476
    ///
477
    /// This returns the product of `index` by the internal [`item_align()`].
478
    ///
479
    /// # Panic
480
    ///
481
    /// Panics if the `index` is too large, producing a byte offset larger than
482
    /// `u32::MAX`.
483
    ///
484
    /// [`item_align()`]: crate::HybridAlignedBufferVec::item_align
485
    #[allow(dead_code)]
486
    #[inline]
487
    pub fn dynamic_offset(&self, index: usize) -> u32 {
×
488
        let offset = self.item_align * index;
×
489
        assert!(offset <= u32::MAX as usize);
×
490
        u32::try_from(offset).expect("HybridAlignedBufferVec index out of bounds")
×
491
    }
492

493
    #[inline]
494
    #[allow(dead_code)]
495
    pub fn is_empty(&self) -> bool {
21✔
496
        self.values.is_empty()
21✔
497
    }
498

499
    /// Append a value to the buffer.
500
    ///
501
    /// As with [`set_content()`], the content is stored on the CPU and uploaded
502
    /// on the GPU once [`write_buffers()`] is called.
503
    ///
504
    /// # Returns
505
    ///
506
    /// Returns a range starting at the byte offset at which the new element was
507
    /// inserted, which is guaranteed to be a multiple of [`item_align()`].
508
    /// The range span is the item byte size.
509
    ///
510
    /// [`item_align()`]: self::HybridAlignedBufferVec::item_align
511
    #[allow(dead_code)]
512
    pub fn push<T: Pod + ShaderSize>(&mut self, value: &T) -> Range<u32> {
15✔
513
        let src: &[u8] = cast_slice(std::slice::from_ref(value));
15✔
514
        assert_eq!(value.size().get() as usize, src.len());
15✔
515
        self.push_raw(src)
15✔
516
    }
517

518
    /// Append a slice of values to the buffer.
519
    ///
520
    /// The values are assumed to be tightly packed, and will be copied
521
    /// back-to-back into the buffer, without any padding between them. This
522
    /// means that the individul slice items must be properly aligned relative
523
    /// to the beginning of the slice.
524
    ///
525
    /// As with [`set_content()`], the content is stored on the CPU and uploaded
526
    /// on the GPU once [`write_buffers()`] is called.
527
    ///
528
    /// # Returns
529
    ///
530
    /// Returns a range starting at the byte offset at which the new element
531
    /// (the slice) was inserted, which is guaranteed to be a multiple of
532
    /// [`item_align()`]. The range span is the item byte size.
533
    ///
534
    /// # Panics
535
    ///
536
    /// Panics if the byte size of the element `T` is not at least a multiple of
537
    /// the minimum GPU alignment, which is 4 bytes. Note that this doesn't
538
    /// guarantee that the written data is well-formed for use on GPU, as array
539
    /// elements on GPU have other alignment requirements according to WGSL, but
540
    /// at least this catches obvious errors.
541
    ///
542
    /// [`item_align()`]: self::HybridAlignedBufferVec::item_align
543
    #[allow(dead_code)]
544
    pub fn push_many<T: Pod + ShaderSize>(&mut self, value: &[T]) -> Range<u32> {
×
545
        assert_eq!(size_of::<T>() % 4, 0);
×
546
        let src: &[u8] = cast_slice(value);
×
547
        self.push_raw(src)
×
548
    }
549

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

553
        // Calculate the number of (aligned) rows to allocate
554
        let num_rows = src.len().div_ceil(self.item_align) as u32;
15✔
555

556
        // Try to find a block of free rows which can accomodate it, and pick the
557
        // smallest one in order to limit wasted space.
558
        let mut best_slot: Option<(u32, usize)> = None;
15✔
559
        for (index, range) in self.free_rows.iter().enumerate() {
15✔
560
            let free_rows = range.0.end - range.0.start;
×
561
            if free_rows >= num_rows {
×
562
                let wasted_rows = free_rows - num_rows;
×
563
                // If we found a slot with the exact size, just use it already
564
                if wasted_rows == 0 {
×
565
                    best_slot = Some((0, index));
×
566
                    break;
×
567
                }
568
                // Otherwise try to find the smallest oversized slot to reduce wasted space
569
                if let Some(best_slot) = best_slot.as_mut() {
×
570
                    if wasted_rows < best_slot.0 {
×
571
                        *best_slot = (wasted_rows, index);
×
572
                    }
573
                } else {
574
                    best_slot = Some((wasted_rows, index));
×
575
                }
576
            }
577
        }
578

579
        // Insert into existing space
580
        if let Some((_, index)) = best_slot {
15✔
581
            let row_range = self.free_rows.remove(index);
582
            let offset = row_range.0.start as usize * self.item_align;
583
            let free_size = (row_range.0.end - row_range.0.start) as usize * self.item_align;
584
            let size = src.len();
585
            assert!(size <= free_size);
586

587
            let dst = self.values.as_mut_ptr();
×
588
            // SAFETY: dst is guaranteed to point to allocated bytes, which are already
589
            // initialized from a previous call, and are initialized by overwriting the
590
            // bytes with those of a POD type.
591
            #[allow(unsafe_code)]
×
592
            unsafe {
593
                let dst = dst.add(offset);
×
594
                dst.copy_from_nonoverlapping(src.as_ptr(), size);
×
595
            }
596

597
            let start = offset as u32;
×
598
            let end = start + size as u32;
×
599
            start..end
×
600
        }
601
        // Insert at end of vector, after resizing it
602
        else {
603
            // Calculate new aligned insertion offset and new capacity
604
            let offset = self.values.len().next_multiple_of(self.item_align);
15✔
605
            let size = src.len();
15✔
606
            let new_capacity = offset + size;
15✔
607
            if new_capacity > self.values.capacity() {
15✔
608
                let additional = new_capacity - self.values.len();
4✔
609
                self.values.reserve(additional)
4✔
610
            }
611

612
            // Insert padding if needed
613
            if offset > self.values.len() {
25✔
614
                self.values.resize(offset, 0);
10✔
615
            }
616

617
            // Insert serialized value
618
            // Dealing with safe code via Vec::spare_capacity_mut() is quite difficult
619
            // without the upcoming (unstable) additions to MaybeUninit to deal with arrays.
620
            // To prevent having to loop over individual u8, we use direct pointers instead.
621
            assert!(self.values.capacity() >= offset + size);
15✔
622
            assert_eq!(self.values.len(), offset);
15✔
623
            let dst = self.values.as_mut_ptr();
15✔
624
            // SAFETY: dst is guaranteed to point to allocated (offset+size) bytes, which
625
            // are written by copying a Pod type, so ensures those values are initialized,
626
            // and the final size is set to exactly (offset+size).
627
            #[allow(unsafe_code)]
15✔
628
            unsafe {
629
                let dst = dst.add(offset);
15✔
630
                dst.copy_from_nonoverlapping(src.as_ptr(), size);
15✔
631
                self.values.set_len(offset + size);
15✔
632
            }
633

634
            debug_assert_eq!(offset % self.item_align, 0);
30✔
635
            let start = offset as u32;
15✔
636
            let end = start + size as u32;
15✔
637
            start..end
15✔
638
        }
639
    }
640

641
    /// Remove a range of bytes previously added.
642
    ///
643
    /// Remove a range of bytes previously returned by adding one or more
644
    /// elements with [`push()`] or [`push_many()`].
645
    ///
646
    /// # Returns
647
    ///
648
    /// Returns `true` if the range was valid and the corresponding data was
649
    /// removed, or `false` otherwise. In that case, the buffer is not modified.
650
    ///
651
    /// [`push()`]: Self::push
652
    /// [`push_many()`]: Self::push_many
653
    pub fn remove(&mut self, range: Range<u32>) -> bool {
16✔
654
        // Can only remove entire blocks starting at an aligned size
655
        let align = self.item_align as u32;
16✔
656
        if range.start % align != 0 {
16✔
657
            return false;
×
658
        }
659

660
        // Check for out of bounds argument
661
        let end = self.values.len() as u32;
16✔
662
        if range.start >= end || range.end > end {
32✔
663
            return false;
×
664
        }
665

666
        // Note: See below, sometimes self.values() has some padding left we couldn't
667
        // recover earlier beause we didn't know the size of this allocation, but we
668
        // need to still deallocate the row here.
669
        if range.end == end || range.end.next_multiple_of(align) == end {
27✔
670
            // If the allocation is at the end of the buffer, shorten the CPU values. This
671
            // ensures is_empty() eventually returns true.
672
            let mut new_row_end = range.start.div_ceil(align);
7✔
673

674
            // Walk the (sorted) free list to also dequeue any range which is now at the end
675
            // of the buffer
676
            while let Some(free_row) = self.free_rows.pop() {
15✔
677
                if free_row.0.end == new_row_end {
4✔
678
                    new_row_end = free_row.0.start;
4✔
679
                } else {
680
                    self.free_rows.push(free_row);
×
681
                    break;
×
682
                }
683
            }
684

685
            // Note: we can't really recover any padding here because we don't know the
686
            // exact size of that allocation, only its row-aligned size.
687
            self.values.truncate((new_row_end * align) as usize);
688
        } else {
689
            // Otherwise, save the row into the free list.
690
            let start = range.start / align;
9✔
691
            let end = range.end.div_ceil(align);
9✔
692
            let free_row = FreeRow(start..end);
9✔
693

694
            // Insert as sorted
695
            if self.free_rows.is_empty() {
13✔
696
                // Special case to simplify below, and to avoid binary_search()
697
                self.free_rows.push(free_row);
4✔
698
            } else if let Err(index) = self.free_rows.binary_search(&free_row) {
13✔
699
                if index >= self.free_rows.len() {
700
                    // insert at end
701
                    let prev = self.free_rows.last_mut().unwrap(); // known
1✔
702
                    if prev.0.end == free_row.0.start {
2✔
703
                        // merge with last value
704
                        prev.0.end = free_row.0.end;
1✔
705
                    } else {
706
                        // insert last, with gap
707
                        self.free_rows.push(free_row);
×
708
                    }
709
                } else if index == 0 {
3✔
710
                    // insert at start
711
                    let next = &mut self.free_rows[0];
2✔
712
                    if free_row.0.end == next.0.start {
3✔
713
                        // merge with next
714
                        next.0.start = free_row.0.start;
1✔
715
                    } else {
716
                        // insert first, with gap
717
                        self.free_rows.insert(0, free_row);
1✔
718
                    }
719
                } else {
720
                    // insert between 2 existing elements
721
                    let prev = &mut self.free_rows[index - 1];
1✔
722
                    if prev.0.end == free_row.0.start {
1✔
723
                        // merge with previous value
724
                        prev.0.end = free_row.0.end;
1✔
725

726
                        let prev = self.free_rows[index - 1].clone();
1✔
727
                        let next = &mut self.free_rows[index];
1✔
728
                        if prev.0.end == next.0.start {
2✔
729
                            // also merge prev with next, and remove prev
730
                            next.0.start = prev.0.start;
1✔
731
                            self.free_rows.remove(index - 1);
1✔
732
                        }
733
                    } else {
734
                        let next = &mut self.free_rows[index];
×
735
                        if free_row.0.end == next.0.start {
×
736
                            // merge with next value
737
                            next.0.start = free_row.0.start;
×
738
                        } else {
739
                            // insert between 2 values, with gaps on both sides
740
                            self.free_rows.insert(0, free_row);
×
741
                        }
742
                    }
743
                }
744
            } else {
745
                // The range exists in the free list, this means it's already removed. This is a
746
                // duplicate; ignore it.
747
                return false;
1✔
748
            }
749
        }
750
        self.is_stale = true;
15✔
751
        true
15✔
752
    }
753

754
    /// Update an allocated entry with new data
755
    pub fn update(&mut self, offset: u32, data: &[u8]) {
×
756
        // Can only update entire blocks starting at an aligned size
757
        let align = self.item_align as u32;
×
758
        if offset % align != 0 {
×
759
            return;
×
760
        }
761

762
        // Check for out of bounds argument
763
        let end = self.values.len() as u32;
×
764
        let data_end = offset + data.len() as u32;
×
765
        if offset >= end || data_end > end {
×
766
            return;
×
767
        }
768

769
        let dst: &mut [u8] = &mut self.values[offset as usize..data_end as usize];
×
770
        dst.copy_from_slice(data);
×
771

772
        self.is_stale = true;
×
773
    }
774

775
    /// Reserve some capacity into the buffer.
776
    ///
777
    /// If the buffer is reallocated, the old content (on the GPU) is lost, and
778
    /// needs to be re-uploaded to the newly-created buffer. This is done with
779
    /// [`write_buffer()`].
780
    ///
781
    /// # Returns
782
    ///
783
    /// `true` if the buffer was (re)allocated, or `false` if an existing buffer
784
    /// was reused which already had enough capacity.
785
    ///
786
    /// [`write_buffer()`]: crate::AlignedBufferVec::write_buffer
787
    pub fn reserve(&mut self, capacity: usize, device: &RenderDevice) -> bool {
×
788
        if capacity > self.capacity {
×
789
            trace!(
×
790
                "reserve: increase capacity from {} to {} bytes",
×
791
                self.capacity,
792
                capacity,
793
            );
794
            self.capacity = capacity;
×
795
            if let Some(buffer) = self.buffer.take() {
×
796
                buffer.destroy();
797
            }
798
            self.buffer = Some(device.create_buffer(&BufferDescriptor {
×
799
                label: self.label.as_ref().map(|s| &s[..]),
×
800
                size: capacity as BufferAddress,
×
801
                usage: BufferUsages::COPY_DST | self.buffer_usage,
×
802
                mapped_at_creation: false,
×
803
            }));
804
            self.is_stale = !self.values.is_empty();
×
805
            // FIXME - this discards the old content if any!!!
806
            true
×
807
        } else {
808
            false
×
809
        }
810
    }
811

812
    /// Schedule the buffer write to GPU.
813
    ///
814
    /// # Returns
815
    ///
816
    /// `true` if the buffer was (re)allocated, `false` otherwise. If the buffer
817
    /// was reallocated, all bind groups referencing the old buffer should be
818
    /// destroyed.
819
    pub fn write_buffer(&mut self, device: &RenderDevice, queue: &RenderQueue) -> bool {
×
820
        if self.values.is_empty() || !self.is_stale {
×
821
            return false;
×
822
        }
823
        let size = self.values.len();
×
824
        trace!(
×
825
            "hybrid abv: write_buffer: size={}B item_align={}B",
×
826
            size,
827
            self.item_align,
828
        );
829
        let buffer_changed = self.reserve(size, device);
×
830
        if let Some(buffer) = &self.buffer {
×
831
            queue.write_buffer(buffer, 0, self.values.as_slice());
832
            self.is_stale = false;
833
        }
834
        buffer_changed
×
835
    }
836

837
    #[allow(dead_code)]
838
    pub fn clear(&mut self) {
×
839
        if !self.values.is_empty() {
×
840
            self.is_stale = true;
×
841
        }
842
        self.values.clear();
×
843
    }
844
}
845

846
#[cfg(test)]
847
mod tests {
848
    use std::num::NonZeroU64;
849

850
    use bevy::math::Vec3;
851
    use bytemuck::{Pod, Zeroable};
852

853
    use super::*;
854

855
    #[repr(C)]
856
    #[derive(Debug, Default, Clone, Copy, Pod, Zeroable, ShaderType)]
857
    pub(crate) struct GpuDummy {
858
        pub v: Vec3,
859
    }
860

861
    #[repr(C)]
862
    #[derive(Debug, Default, Clone, Copy, Pod, Zeroable, ShaderType)]
863
    pub(crate) struct GpuDummyComposed {
864
        pub simple: GpuDummy,
865
        pub tag: u32,
866
        // GPU padding to 16 bytes due to GpuDummy forcing align to 16 bytes
867
    }
868

869
    #[repr(C)]
870
    #[derive(Debug, Clone, Copy, Pod, Zeroable, ShaderType)]
871
    pub(crate) struct GpuDummyLarge {
872
        pub simple: GpuDummy,
873
        pub tag: u32,
874
        pub large: [f32; 128],
875
    }
876

877
    #[test]
878
    fn abv_sizes() {
879
        // Rust
880
        assert_eq!(std::mem::size_of::<GpuDummy>(), 12);
881
        assert_eq!(std::mem::align_of::<GpuDummy>(), 4);
882
        assert_eq!(std::mem::size_of::<GpuDummyComposed>(), 16); // tight packing
883
        assert_eq!(std::mem::align_of::<GpuDummyComposed>(), 4);
884
        assert_eq!(std::mem::size_of::<GpuDummyLarge>(), 132 * 4); // tight packing
885
        assert_eq!(std::mem::align_of::<GpuDummyLarge>(), 4);
886

887
        // GPU
888
        assert_eq!(<GpuDummy as ShaderType>::min_size().get(), 16); // Vec3 gets padded to 16 bytes
889
        assert_eq!(<GpuDummy as ShaderSize>::SHADER_SIZE.get(), 16);
890
        assert_eq!(<GpuDummyComposed as ShaderType>::min_size().get(), 32); // align is 16 bytes, forces padding
891
        assert_eq!(<GpuDummyComposed as ShaderSize>::SHADER_SIZE.get(), 32);
892
        assert_eq!(<GpuDummyLarge as ShaderType>::min_size().get(), 544); // align is 16 bytes, forces padding
893
        assert_eq!(<GpuDummyLarge as ShaderSize>::SHADER_SIZE.get(), 544);
894

895
        for (item_align, expected_aligned_size) in [
896
            (0, 16),
897
            (4, 16),
898
            (8, 16),
899
            (16, 16),
900
            (32, 32),
901
            (256, 256),
902
            (512, 512),
903
        ] {
904
            let mut abv = AlignedBufferVec::<GpuDummy>::new(
905
                BufferUsages::STORAGE,
906
                NonZeroU64::new(item_align),
907
                None,
908
            );
909
            assert_eq!(abv.aligned_size(), expected_aligned_size);
910
            assert!(abv.is_empty());
911
            abv.push(GpuDummy::default());
912
            assert!(!abv.is_empty());
913
            assert_eq!(abv.len(), 1);
914
        }
915

916
        for (item_align, expected_aligned_size) in [
917
            (0, 32),
918
            (4, 32),
919
            (8, 32),
920
            (16, 32),
921
            (32, 32),
922
            (256, 256),
923
            (512, 512),
924
        ] {
925
            let mut abv = AlignedBufferVec::<GpuDummyComposed>::new(
926
                BufferUsages::STORAGE,
927
                NonZeroU64::new(item_align),
928
                None,
929
            );
930
            assert_eq!(abv.aligned_size(), expected_aligned_size);
931
            assert!(abv.is_empty());
932
            abv.push(GpuDummyComposed::default());
933
            assert!(!abv.is_empty());
934
            assert_eq!(abv.len(), 1);
935
        }
936

937
        for (item_align, expected_aligned_size) in [
938
            (0, 544),
939
            (4, 544),
940
            (8, 544),
941
            (16, 544),
942
            (32, 544),
943
            (256, 768),
944
            (512, 1024),
945
        ] {
946
            let mut abv = AlignedBufferVec::<GpuDummyLarge>::new(
947
                BufferUsages::STORAGE,
948
                NonZeroU64::new(item_align),
949
                None,
950
            );
951
            assert_eq!(abv.aligned_size(), expected_aligned_size);
952
            assert!(abv.is_empty());
953
            abv.push(GpuDummyLarge {
954
                simple: Default::default(),
955
                tag: 0,
956
                large: [0.; 128],
957
            });
958
            assert!(!abv.is_empty());
959
            assert_eq!(abv.len(), 1);
960
        }
961
    }
962

963
    #[test]
964
    fn habv_remove() {
965
        let mut habv =
966
            HybridAlignedBufferVec::new(BufferUsages::STORAGE, NonZeroU64::new(32), None);
967
        assert!(habv.is_empty());
968
        assert_eq!(habv.item_align, 32);
969

970
        // +r -r
971
        {
972
            let r = habv.push(&42u32);
973
            assert_eq!(r, 0..4);
974
            assert!(!habv.is_empty());
975
            assert_eq!(habv.values.len(), 4);
976
            assert!(habv.free_rows.is_empty());
977

978
            assert!(habv.remove(r));
979
            assert!(habv.is_empty());
980
            assert!(habv.values.is_empty());
981
            assert!(habv.free_rows.is_empty());
982
        }
983

984
        // +r0 +r1 +r2 -r0 -r0 -r1 -r2
985
        {
986
            let r0 = habv.push(&42u32);
987
            let r1 = habv.push(&84u32);
988
            let r2 = habv.push(&84u32);
989
            assert_eq!(r0, 0..4);
990
            assert_eq!(r1, 32..36);
991
            assert_eq!(r2, 64..68);
992
            assert!(!habv.is_empty());
993
            assert_eq!(habv.values.len(), 68);
994
            assert!(habv.free_rows.is_empty());
995

996
            assert!(habv.remove(r0.clone()));
997
            assert!(!habv.is_empty());
998
            assert_eq!(habv.values.len(), 68);
999
            assert_eq!(habv.free_rows.len(), 1);
1000
            assert_eq!(habv.free_rows[0], FreeRow(0..1));
1001

1002
            // dupe; no-op
1003
            assert!(!habv.remove(r0));
1004

1005
            assert!(habv.remove(r1.clone()));
1006
            assert!(!habv.is_empty());
1007
            assert_eq!(habv.values.len(), 68);
1008
            assert_eq!(habv.free_rows.len(), 1); // merged!
1009
            assert_eq!(habv.free_rows[0], FreeRow(0..2));
1010

1011
            assert!(habv.remove(r2));
1012
            assert!(habv.is_empty());
1013
            assert_eq!(habv.values.len(), 0);
1014
            assert!(habv.free_rows.is_empty());
1015
        }
1016

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

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

1035
            assert!(habv.remove(r0.clone()));
1036
            assert!(!habv.is_empty());
1037
            assert_eq!(habv.values.len(), 68);
1038
            assert_eq!(habv.free_rows.len(), 1); // merged!
1039
            assert_eq!(habv.free_rows[0], FreeRow(0..2));
1040

1041
            assert!(habv.remove(r2));
1042
            assert!(habv.is_empty());
1043
            assert_eq!(habv.values.len(), 0);
1044
            assert!(habv.free_rows.is_empty());
1045
        }
1046

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

1059
            assert!(habv.remove(r1.clone()));
1060
            assert!(!habv.is_empty());
1061
            assert_eq!(habv.values.len(), 68);
1062
            assert_eq!(habv.free_rows.len(), 1);
1063
            assert_eq!(habv.free_rows[0], FreeRow(1..2));
1064

1065
            assert!(habv.remove(r2.clone()));
1066
            assert!(!habv.is_empty());
1067
            assert_eq!(habv.values.len(), 32); // can't recover exact alloc (4), only row-aligned size (32)
1068
            assert!(habv.free_rows.is_empty()); // merged!
1069

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

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

1092
            assert!(habv.remove(r3.clone()));
1093
            assert!(!habv.is_empty());
1094
            assert_eq!(habv.values.len(), 132);
1095
            assert_eq!(habv.free_rows.len(), 1);
1096
            assert_eq!(habv.free_rows[0], FreeRow(3..4));
1097

1098
            assert!(habv.remove(r1.clone()));
1099
            assert!(!habv.is_empty());
1100
            assert_eq!(habv.values.len(), 132);
1101
            assert_eq!(habv.free_rows.len(), 2);
1102
            assert_eq!(habv.free_rows[0], FreeRow(1..2)); // sorted!
1103
            assert_eq!(habv.free_rows[1], FreeRow(3..4));
1104

1105
            assert!(habv.remove(r2.clone()));
1106
            assert!(!habv.is_empty());
1107
            assert_eq!(habv.values.len(), 132);
1108
            assert_eq!(habv.free_rows.len(), 1); // merged!
1109
            assert_eq!(habv.free_rows[0], FreeRow(1..4)); // merged!
1110

1111
            assert!(habv.remove(r4.clone()));
1112
            assert!(!habv.is_empty());
1113
            assert_eq!(habv.values.len(), 32); // can't recover exact alloc (4), only row-aligned size (32)
1114
            assert!(habv.free_rows.is_empty());
1115

1116
            assert!(habv.remove(r0));
1117
            assert!(habv.is_empty());
1118
            assert_eq!(habv.values.len(), 0);
1119
            assert!(habv.free_rows.is_empty());
1120
        }
1121
    }
1122
}
1123

1124
#[cfg(all(test, feature = "gpu_tests"))]
1125
mod gpu_tests {
1126
    use tests::*;
1127

1128
    use super::*;
1129
    use crate::test_utils::MockRenderer;
1130

1131
    #[test]
1132
    fn abv_write() {
1133
        let renderer = MockRenderer::new();
1134
        let device = renderer.device();
1135
        let queue = renderer.queue();
1136

1137
        // Create a dummy CommandBuffer to force the write_buffer() call to have any
1138
        // effect.
1139
        let encoder = device.create_command_encoder(&wgpu::CommandEncoderDescriptor {
1140
            label: Some("test"),
1141
        });
1142
        let command_buffer = encoder.finish();
1143

1144
        let item_align = device.limits().min_storage_buffer_offset_alignment as u64;
1145
        let mut abv = AlignedBufferVec::<GpuDummyComposed>::new(
1146
            BufferUsages::STORAGE | BufferUsages::MAP_READ,
1147
            NonZeroU64::new(item_align),
1148
            None,
1149
        );
1150
        let final_align = item_align.max(<GpuDummyComposed as ShaderSize>::SHADER_SIZE.get());
1151
        assert_eq!(abv.aligned_size(), final_align as usize);
1152

1153
        const CAPACITY: usize = 42;
1154

1155
        // Write buffer (CPU -> GPU)
1156
        abv.push(GpuDummyComposed {
1157
            tag: 1,
1158
            ..Default::default()
1159
        });
1160
        abv.push(GpuDummyComposed {
1161
            tag: 2,
1162
            ..Default::default()
1163
        });
1164
        abv.push(GpuDummyComposed {
1165
            tag: 3,
1166
            ..Default::default()
1167
        });
1168
        abv.reserve(CAPACITY, &device);
1169
        abv.write_buffer(&device, &queue);
1170
        // need a submit() for write_buffer() to be processed
1171
        queue.submit([command_buffer]);
1172
        let (tx, rx) = futures::channel::oneshot::channel();
1173
        queue.on_submitted_work_done(move || {
1174
            tx.send(()).unwrap();
1175
        });
1176
        device.poll(wgpu::Maintain::Wait);
1177
        let _ = futures::executor::block_on(rx);
1178
        println!("Buffer written");
1179

1180
        // Read back (GPU -> CPU)
1181
        let buffer = abv.buffer();
1182
        let buffer = buffer.as_ref().expect("Buffer was not allocated");
1183
        let buffer = buffer.slice(..);
1184
        let (tx, rx) = futures::channel::oneshot::channel();
1185
        buffer.map_async(wgpu::MapMode::Read, move |result| {
1186
            tx.send(result).unwrap();
1187
        });
1188
        device.poll(wgpu::Maintain::Wait);
1189
        let _result = futures::executor::block_on(rx);
1190
        let view = buffer.get_mapped_range();
1191

1192
        // Validate content
1193
        assert_eq!(view.len(), final_align as usize * CAPACITY);
1194
        for i in 0..3 {
1195
            let offset = i * final_align as usize;
1196
            let dummy_composed: &[GpuDummyComposed] =
1197
                cast_slice(&view[offset..offset + std::mem::size_of::<GpuDummyComposed>()]);
1198
            assert_eq!(dummy_composed[0].tag, (i + 1) as u32);
1199
        }
1200
    }
1201
}
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