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

djeedai / bevy_hanabi / 14389198533

10 Apr 2025 07:59PM UTC coverage: 39.38% (-0.7%) from 40.116%
14389198533

push

github

web-flow
Make the number of particles to emit in a GPU event an expression instead of a constant. (#444)

This makes Hanabi match Unity, which supports this feature [as explained
here]. It allows for a variety of effects. For example, one could emit a
random number of particles on every frame instead of a fixed number. Or
one could emit a "burst" of particles after traveling some distance, by
conditionally setting the number of particles to spawn to 0.

[as explained here]: https://discussions.unity.com/t/gpuevent-trigger-once-at-birth/779939/2

0 of 3 new or added lines in 1 file covered. (0.0%)

139 existing lines in 8 files now uncovered.

3022 of 7674 relevant lines covered (39.38%)

17.34 hits per line

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

42.27
/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 the first `count` elements of the buffer.
151
    ///
152
    /// # Panics
153
    ///
154
    /// Panics if `count` is zero.
155
    #[inline]
156
    pub fn lead_binding(&self, count: u32) -> Option<BindingResource> {
×
157
        assert!(count > 0);
×
158
        let buffer = self.buffer()?;
×
159
        let size = NonZeroU64::new(T::SHADER_SIZE.get() * count as u64).unwrap();
×
160
        Some(BindingResource::Buffer(BufferBinding {
×
161
            buffer,
×
162
            offset: 0,
×
163
            size: Some(size),
×
164
        }))
165
    }
166

167
    /// Get a binding for a subset of the elements of the buffer.
168
    ///
169
    /// Returns a binding for the elements in the range `offset..offset+count`.
170
    ///
171
    /// # Panics
172
    ///
173
    /// Panics if `count` is zero.
174
    #[inline]
175
    #[allow(dead_code)]
176
    pub fn range_binding(&self, offset: u32, count: u32) -> Option<BindingResource> {
×
177
        assert!(count > 0);
×
178
        let buffer = self.buffer()?;
×
179
        let offset = T::SHADER_SIZE.get() * offset as u64;
×
180
        let size = NonZeroU64::new(T::SHADER_SIZE.get() * count as u64).unwrap();
×
181
        Some(BindingResource::Buffer(BufferBinding {
×
182
            buffer,
×
183
            offset,
×
184
            size: Some(size),
×
185
        }))
186
    }
187

188
    #[inline]
189
    #[allow(dead_code)]
190
    pub fn capacity(&self) -> usize {
×
191
        self.capacity
×
192
    }
193

194
    #[inline]
195
    pub fn len(&self) -> usize {
21✔
196
        self.values.len()
21✔
197
    }
198

199
    /// Size in bytes of a single item in the buffer, aligned to the item
200
    /// alignment.
201
    #[inline]
202
    pub fn aligned_size(&self) -> usize {
22✔
203
        self.aligned_size
22✔
204
    }
205

206
    /// Calculate a dynamic byte offset for a bind group from an array element
207
    /// index.
208
    ///
209
    /// This returns the product of `index` by the internal [`aligned_size()`].
210
    ///
211
    /// # Panic
212
    ///
213
    /// Panics if the `index` is too large, producing a byte offset larger than
214
    /// `u32::MAX`.
215
    ///
216
    /// [`aligned_size()`]: crate::AlignedBufferVec::aligned_size
217
    #[inline]
218
    pub fn dynamic_offset(&self, index: usize) -> u32 {
×
219
        let offset = self.aligned_size * index;
×
220
        assert!(offset <= u32::MAX as usize);
×
221
        u32::try_from(offset).expect("AlignedBufferVec index out of bounds")
×
222
    }
223

224
    #[inline]
225
    #[allow(dead_code)]
226
    pub fn is_empty(&self) -> bool {
44✔
227
        self.values.is_empty()
44✔
228
    }
229

230
    /// Append a value to the buffer.
231
    ///
232
    /// As with [`set_content()`], the content is stored on the CPU and uploaded
233
    /// on the GPU once [`write_buffer()`] is called.
234
    ///
235
    /// [`write_buffer()`]: crate::AlignedBufferVec::write_buffer
236
    pub fn push(&mut self, value: T) -> usize {
24✔
237
        let index = self.values.len();
24✔
238
        self.values.alloc().init(value);
24✔
239
        index
24✔
240
    }
241

242
    /// Set the content of the CPU buffer, overwritting any previous data.
243
    ///
244
    /// As with [`push()`], the content is stored on the CPU and uploaded on the
245
    /// GPU once [`write_buffer()`] is called.
246
    ///
247
    /// [`write_buffer()`]: crate::AlignedBufferVec::write_buffer
248
    pub fn set_content(&mut self, data: Vec<T>) {
2✔
249
        self.values = data;
2✔
250
    }
251

252
    /// Get the content of the CPU buffer.
253
    ///
254
    /// The data may or may not be representative of the GPU content, depending
255
    /// on whether the buffer was already uploaded and/or has been modified by
256
    /// the GPU itself.
257
    pub fn content(&self) -> &[T] {
2✔
258
        &self.values
2✔
259
    }
260

261
    /// Reserve some capacity into the buffer.
262
    ///
263
    /// If the buffer is reallocated, the old content (on the GPU) is lost, and
264
    /// needs to be re-uploaded to the newly-created buffer. This is done with
265
    /// [`write_buffer()`].
266
    ///
267
    /// # Returns
268
    ///
269
    /// `true` if the buffer was (re)allocated, or `false` if an existing buffer
270
    /// was reused which already had enough capacity.
271
    ///
272
    /// [`write_buffer()`]: crate::AlignedBufferVec::write_buffer
273
    pub fn reserve(&mut self, capacity: usize, device: &RenderDevice) -> bool {
4✔
274
        if capacity > self.capacity {
4✔
275
            let size = self.aligned_size * capacity;
3✔
276
            trace!(
3✔
277
                "reserve: increase capacity from {} to {} elements, new size {} bytes",
×
278
                self.capacity,
×
279
                capacity,
×
280
                size
×
281
            );
282
            self.capacity = capacity;
3✔
283
            if let Some(buffer) = self.buffer.take() {
4✔
284
                buffer.destroy();
×
285
            }
286
            self.buffer = Some(device.create_buffer(&BufferDescriptor {
3✔
287
                label: self.label.as_ref().map(|s| &s[..]),
8✔
288
                size: size as BufferAddress,
3✔
289
                usage: BufferUsages::COPY_DST | self.buffer_usage,
3✔
290
                mapped_at_creation: false,
3✔
291
            }));
292
            // FIXME - this discards the old content if any!!!
293
            true
3✔
294
        } else {
295
            false
1✔
296
        }
297
    }
298

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

333
    pub fn clear(&mut self) {
2✔
334
        self.values.clear();
2✔
335
    }
336
}
337

338
impl<T: Pod + ShaderSize> std::ops::Index<usize> for AlignedBufferVec<T> {
339
    type Output = T;
340

341
    fn index(&self, index: usize) -> &Self::Output {
×
342
        &self.values[index]
×
343
    }
344
}
345

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

352
#[derive(Debug, Clone, PartialEq, Eq)]
353
struct FreeRow(pub Range<u32>);
354

355
impl PartialOrd for FreeRow {
356
    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
×
357
        Some(self.cmp(other))
×
358
    }
359
}
360

361
impl Ord for FreeRow {
362
    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
6✔
363
        self.0.start.cmp(&other.0.start)
6✔
364
    }
365
}
366

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

389
impl HybridAlignedBufferVec {
390
    /// Create a new collection.
391
    ///
392
    /// `item_align` is an optional additional alignment for items in the
393
    /// collection. If greater than the natural alignment dictated by WGSL
394
    /// rules, this extra alignment is enforced. Otherwise it's ignored (so you
395
    /// can pass `None` to ignore, which defaults to no alignment).
396
    pub fn new(
1✔
397
        buffer_usage: BufferUsages,
398
        item_align: Option<NonZeroU64>,
399
        label: Option<String>,
400
    ) -> Self {
401
        let item_align = item_align.map(|nz| nz.get()).unwrap_or(1) as usize;
3✔
402
        trace!(
1✔
403
            "HybridAlignedBufferVec['{}']: item_align={} byte",
×
404
            label.as_ref().map(|s| &s[..]).unwrap_or(""),
×
405
            item_align,
406
        );
407
        Self {
408
            values: vec![],
1✔
409
            buffer: None,
410
            capacity: 0,
411
            item_align,
412
            buffer_usage,
413
            label,
414
            free_rows: vec![],
1✔
415
            is_stale: true,
416
        }
417
    }
418

419
    #[inline]
420
    pub fn buffer(&self) -> Option<&Buffer> {
×
421
        self.buffer.as_ref()
×
422
    }
423

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

442
    /// Get a binding for the first `size` bytes of the buffer.
443
    ///
444
    /// # Panics
445
    ///
446
    /// Panics if `size` is zero.
447
    #[allow(dead_code)]
448
    #[inline]
449
    pub fn lead_binding(&self, size: u32) -> Option<BindingResource> {
×
450
        let buffer = self.buffer()?;
×
451
        let size = NonZeroU64::new(size as u64).unwrap();
×
452
        Some(BindingResource::Buffer(BufferBinding {
×
453
            buffer,
×
454
            offset: 0,
×
455
            size: Some(size),
×
456
        }))
457
    }
458

459
    /// Get a binding for a subset of the elements of the buffer.
460
    ///
461
    /// Returns a binding for the elements in the range `offset..offset+count`.
462
    ///
463
    /// # Panics
464
    ///
465
    /// Panics if `offset` is not a multiple of the alignment specified on
466
    /// construction.
467
    ///
468
    /// Panics if `size` is zero.
469
    #[allow(dead_code)]
470
    #[inline]
471
    pub fn range_binding(&self, offset: u32, size: u32) -> Option<BindingResource> {
×
472
        assert!(offset as usize % self.item_align == 0);
×
473
        let buffer = self.buffer()?;
×
474
        let size = NonZeroU64::new(size as u64).unwrap();
×
475
        Some(BindingResource::Buffer(BufferBinding {
×
476
            buffer,
×
477
            offset: offset as u64,
×
478
            size: Some(size),
×
479
        }))
480
    }
481

482
    /// Capacity of the allocated GPU buffer, in bytes.
483
    ///
484
    /// This may be zero if the buffer was not allocated yet. In general, this
485
    /// can differ from the actual data size cached on CPU and waiting to be
486
    /// uploaded to GPU.
487
    #[inline]
488
    #[allow(dead_code)]
489
    pub fn capacity(&self) -> usize {
×
490
        self.capacity
×
491
    }
492

493
    /// Current buffer size, in bytes.
494
    ///
495
    /// This represents the size of the CPU data uploaded to GPU. Pending a GPU
496
    /// buffer re-allocation or re-upload, this size might differ from the
497
    /// actual GPU buffer size. But they're eventually consistent.
498
    #[inline]
499
    pub fn len(&self) -> usize {
×
500
        self.values.len()
×
501
    }
502

503
    /// Alignment, in bytes, of all the elements.
504
    #[allow(dead_code)]
505
    #[inline]
506
    pub fn item_align(&self) -> usize {
×
507
        self.item_align
×
508
    }
509

510
    /// Calculate a dynamic byte offset for a bind group from an array element
511
    /// index.
512
    ///
513
    /// This returns the product of `index` by the internal [`item_align()`].
514
    ///
515
    /// # Panic
516
    ///
517
    /// Panics if the `index` is too large, producing a byte offset larger than
518
    /// `u32::MAX`.
519
    ///
520
    /// [`item_align()`]: crate::HybridAlignedBufferVec::item_align
521
    #[allow(dead_code)]
522
    #[inline]
523
    pub fn dynamic_offset(&self, index: usize) -> u32 {
×
524
        let offset = self.item_align * index;
×
525
        assert!(offset <= u32::MAX as usize);
×
526
        u32::try_from(offset).expect("HybridAlignedBufferVec index out of bounds")
×
527
    }
528

529
    #[inline]
530
    #[allow(dead_code)]
531
    pub fn is_empty(&self) -> bool {
21✔
532
        self.values.is_empty()
21✔
533
    }
534

535
    /// Append a value to the buffer.
536
    ///
537
    /// As with [`set_content()`], the content is stored on the CPU and uploaded
538
    /// on the GPU once [`write_buffers()`] is called.
539
    ///
540
    /// # Returns
541
    ///
542
    /// Returns a range starting at the byte offset at which the new element was
543
    /// inserted, which is guaranteed to be a multiple of [`item_align()`].
544
    /// The range span is the item byte size.
545
    ///
546
    /// [`item_align()`]: self::HybridAlignedBufferVec::item_align
547
    #[allow(dead_code)]
548
    pub fn push<T: Pod + ShaderSize>(&mut self, value: &T) -> Range<u32> {
15✔
549
        let src: &[u8] = cast_slice(std::slice::from_ref(value));
15✔
550
        assert_eq!(value.size().get() as usize, src.len());
15✔
551
        self.push_raw(src)
15✔
552
    }
553

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

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

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

592
        // Try to find a block of free rows which can accomodate it, and pick the
593
        // smallest one in order to limit wasted space.
594
        let mut best_slot: Option<(u32, usize)> = None;
15✔
595
        for (index, range) in self.free_rows.iter().enumerate() {
15✔
596
            let free_rows = range.0.end - range.0.start;
×
597
            if free_rows >= num_rows {
×
598
                let wasted_rows = free_rows - num_rows;
×
599
                // If we found a slot with the exact size, just use it already
600
                if wasted_rows == 0 {
×
601
                    best_slot = Some((0, index));
×
602
                    break;
×
603
                }
604
                // Otherwise try to find the smallest oversized slot to reduce wasted space
605
                if let Some(best_slot) = best_slot.as_mut() {
×
606
                    if wasted_rows < best_slot.0 {
×
607
                        *best_slot = (wasted_rows, index);
×
608
                    }
609
                } else {
610
                    best_slot = Some((wasted_rows, index));
×
611
                }
612
            }
613
        }
614

615
        // Insert into existing space
616
        if let Some((_, index)) = best_slot {
15✔
617
            let row_range = self.free_rows.remove(index);
618
            let offset = row_range.0.start as usize * self.item_align;
619
            let free_size = (row_range.0.end - row_range.0.start) as usize * self.item_align;
620
            let size = src.len();
621
            assert!(size <= free_size);
622

623
            let dst = self.values.as_mut_ptr();
×
624
            // SAFETY: dst is guaranteed to point to allocated bytes, which are already
625
            // initialized from a previous call, and are initialized by overwriting the
626
            // bytes with those of a POD type.
627
            #[allow(unsafe_code)]
×
628
            unsafe {
629
                let dst = dst.add(offset);
×
630
                dst.copy_from_nonoverlapping(src.as_ptr(), size);
×
631
            }
632

633
            let start = offset as u32;
×
634
            let end = start + size as u32;
×
635
            start..end
×
636
        }
637
        // Insert at end of vector, after resizing it
638
        else {
639
            // Calculate new aligned insertion offset and new capacity
640
            let offset = self.values.len().next_multiple_of(self.item_align);
15✔
641
            let size = src.len();
15✔
642
            let new_capacity = offset + size;
15✔
643
            if new_capacity > self.values.capacity() {
15✔
644
                let additional = new_capacity - self.values.len();
4✔
645
                self.values.reserve(additional)
4✔
646
            }
647

648
            // Insert padding if needed
649
            if offset > self.values.len() {
25✔
650
                self.values.resize(offset, 0);
10✔
651
            }
652

653
            // Insert serialized value
654
            // Dealing with safe code via Vec::spare_capacity_mut() is quite difficult
655
            // without the upcoming (unstable) additions to MaybeUninit to deal with arrays.
656
            // To prevent having to loop over individual u8, we use direct pointers instead.
657
            assert!(self.values.capacity() >= offset + size);
15✔
658
            assert_eq!(self.values.len(), offset);
15✔
659
            let dst = self.values.as_mut_ptr();
15✔
660
            // SAFETY: dst is guaranteed to point to allocated (offset+size) bytes, which
661
            // are written by copying a Pod type, so ensures those values are initialized,
662
            // and the final size is set to exactly (offset+size).
663
            #[allow(unsafe_code)]
15✔
664
            unsafe {
665
                let dst = dst.add(offset);
15✔
666
                dst.copy_from_nonoverlapping(src.as_ptr(), size);
15✔
667
                self.values.set_len(offset + size);
15✔
668
            }
669

670
            debug_assert_eq!(offset % self.item_align, 0);
30✔
671
            let start = offset as u32;
15✔
672
            let end = start + size as u32;
15✔
673
            start..end
15✔
674
        }
675
    }
676

677
    /// Remove a range of bytes previously added.
678
    ///
679
    /// Remove a range of bytes previously returned by adding one or more
680
    /// elements with [`push()`] or [`push_many()`].
681
    ///
682
    /// # Returns
683
    ///
684
    /// Returns `true` if the range was valid and the corresponding data was
685
    /// removed, or `false` otherwise. In that case, the buffer is not modified.
686
    ///
687
    /// [`push()`]: Self::push
688
    /// [`push_many()`]: Self::push_many
689
    pub fn remove(&mut self, range: Range<u32>) -> bool {
16✔
690
        // Can only remove entire blocks starting at an aligned size
691
        let align = self.item_align as u32;
16✔
692
        if range.start % align != 0 {
16✔
693
            return false;
×
694
        }
695

696
        // Check for out of bounds argument
697
        let end = self.values.len() as u32;
16✔
698
        if range.start >= end || range.end > end {
32✔
699
            return false;
×
700
        }
701

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

710
            // Walk the (sorted) free list to also dequeue any range which is now at the end
711
            // of the buffer
712
            while let Some(free_row) = self.free_rows.pop() {
15✔
713
                if free_row.0.end == new_row_end {
4✔
714
                    new_row_end = free_row.0.start;
4✔
715
                } else {
716
                    self.free_rows.push(free_row);
×
717
                    break;
×
718
                }
719
            }
720

721
            // Note: we can't really recover any padding here because we don't know the
722
            // exact size of that allocation, only its row-aligned size.
723
            self.values.truncate((new_row_end * align) as usize);
724
        } else {
725
            // Otherwise, save the row into the free list.
726
            let start = range.start / align;
9✔
727
            let end = range.end.div_ceil(align);
9✔
728
            let free_row = FreeRow(start..end);
9✔
729

730
            // Insert as sorted
731
            if self.free_rows.is_empty() {
13✔
732
                // Special case to simplify below, and to avoid binary_search()
733
                self.free_rows.push(free_row);
4✔
734
            } else if let Err(index) = self.free_rows.binary_search(&free_row) {
13✔
735
                if index >= self.free_rows.len() {
736
                    // insert at end
737
                    let prev = self.free_rows.last_mut().unwrap(); // known
1✔
738
                    if prev.0.end == free_row.0.start {
2✔
739
                        // merge with last value
740
                        prev.0.end = free_row.0.end;
1✔
741
                    } else {
742
                        // insert last, with gap
743
                        self.free_rows.push(free_row);
×
744
                    }
745
                } else if index == 0 {
3✔
746
                    // insert at start
747
                    let next = &mut self.free_rows[0];
2✔
748
                    if free_row.0.end == next.0.start {
3✔
749
                        // merge with next
750
                        next.0.start = free_row.0.start;
1✔
751
                    } else {
752
                        // insert first, with gap
753
                        self.free_rows.insert(0, free_row);
1✔
754
                    }
755
                } else {
756
                    // insert between 2 existing elements
757
                    let prev = &mut self.free_rows[index - 1];
1✔
758
                    if prev.0.end == free_row.0.start {
1✔
759
                        // merge with previous value
760
                        prev.0.end = free_row.0.end;
1✔
761

762
                        let prev = self.free_rows[index - 1].clone();
1✔
763
                        let next = &mut self.free_rows[index];
1✔
764
                        if prev.0.end == next.0.start {
2✔
765
                            // also merge prev with next, and remove prev
766
                            next.0.start = prev.0.start;
1✔
767
                            self.free_rows.remove(index - 1);
1✔
768
                        }
769
                    } else {
770
                        let next = &mut self.free_rows[index];
×
771
                        if free_row.0.end == next.0.start {
×
772
                            // merge with next value
773
                            next.0.start = free_row.0.start;
×
774
                        } else {
775
                            // insert between 2 values, with gaps on both sides
776
                            self.free_rows.insert(0, free_row);
×
777
                        }
778
                    }
779
                }
780
            } else {
781
                // The range exists in the free list, this means it's already removed. This is a
782
                // duplicate; ignore it.
783
                return false;
1✔
784
            }
785
        }
786
        self.is_stale = true;
15✔
787
        true
15✔
788
    }
789

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

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

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

808
        self.is_stale = true;
×
809
    }
810

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

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

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

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

886
    use bevy::math::Vec3;
887
    use bytemuck::{Pod, Zeroable};
888

889
    use super::*;
890

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

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

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

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

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

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

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

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

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

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

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

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

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

1038
            // dupe; no-op
1039
            assert!(!habv.remove(r0));
1040

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

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

1053
        // +r0 +r1 +r2 -r1 -r0 -r2
1054
        {
1055
            let r0 = habv.push(&42u32);
1056
            let r1 = habv.push(&84u32);
1057
            let r2 = habv.push(&84u32);
1058
            assert_eq!(r0, 0..4);
1059
            assert_eq!(r1, 32..36);
1060
            assert_eq!(r2, 64..68);
1061
            assert!(!habv.is_empty());
1062
            assert_eq!(habv.values.len(), 68);
1063
            assert!(habv.free_rows.is_empty());
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);
1069
            assert_eq!(habv.free_rows[0], FreeRow(1..2));
1070

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

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

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

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

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

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

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

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

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

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

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

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

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

1164
    use super::*;
1165
    use crate::test_utils::MockRenderer;
1166

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

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

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

1189
        const CAPACITY: usize = 42;
1190

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

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

1228
        // Validate content
1229
        assert_eq!(view.len(), final_align as usize * CAPACITY);
1230
        for i in 0..3 {
1231
            let offset = i * final_align as usize;
1232
            let dummy_composed: &[GpuDummyComposed] =
1233
                cast_slice(&view[offset..offset + std::mem::size_of::<GpuDummyComposed>()]);
1234
            assert_eq!(dummy_composed[0].tag, (i + 1) as u32);
1235
        }
1236
    }
1237
}
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