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

djeedai / bevy_hanabi / 13640457354

03 Mar 2025 09:09PM UTC coverage: 40.055% (-6.7%) from 46.757%
13640457354

push

github

web-flow
Hierarchical effects and GPU spawn event (#424)

This change introduces hierarchical effects, the ability of an effect to
be parented to another effect through the `EffectParent` component.
Child effects can inherit attributes from their parent when spawned
during the init pass, but are otherwise independent effects. They
replace the old group system, which is entirely removed. The parent
effect can emit GPU spawn events, which are consumed by the child effect
to spawn particles instead of the traditional CPU spawn count. Those GPU
spawn events currently are just the ID of the parent particles, to allow
read-only access to its attribute in _e.g._ the new
`InheritAttributeModifier`.

The ribbon/trail system is also reworked. The atomic linked list based
on `Attribute::PREV` and `Attribute::NEXT` is abandoned, and replaced
with an explicit sort compute pass which orders particles by
`Attribute::RIBBON_ID` first, and `Attribute::AGE` next. The ribbon ID
is any `u32` value unique to each ribbon/trail. Sorting particles by age
inside a given ribbon/trail allows avoiding the edge case where a
particle in the middle of a trail dies, leaving a gap in the list.

A migration guide is provided from v0.14 to the upcoming v0.15 which
will include this change, due to the large change of behavior and APIs.

409 of 2997 new or added lines in 17 files covered. (13.65%)

53 existing lines in 11 files now uncovered.

3208 of 8009 relevant lines covered (40.05%)

18.67 hits per line

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

45.42
/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!(
×
NEW
111
            "AlignedBufferVec['{}']: item_size={} aligned_size={}",
×
NEW
112
            label.as_ref().map(|s| &s[..]).unwrap_or(""),
×
113
            item_size,
×
114
            aligned_size
×
115
        );
116
        if buffer_usage.contains(BufferUsages::UNIFORM) {
24✔
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)]
NEW
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.
NEW
142
        let buffer = self.buffer()?;
×
NEW
143
        Some(BindingResource::Buffer(BufferBinding {
×
NEW
144
            buffer,
×
NEW
145
            offset: 0,
×
NEW
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]
NEW
156
    pub fn lead_binding(&self, count: u32) -> Option<BindingResource> {
×
NEW
157
        assert!(count > 0);
×
NEW
158
        let buffer = self.buffer()?;
×
NEW
159
        let size = NonZeroU64::new(T::SHADER_SIZE.get() * count as u64).unwrap();
×
NEW
160
        Some(BindingResource::Buffer(BufferBinding {
×
NEW
161
            buffer,
×
NEW
162
            offset: 0,
×
NEW
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)]
NEW
176
    pub fn range_binding(&self, offset: u32, count: u32) -> Option<BindingResource> {
×
NEW
177
        assert!(count > 0);
×
NEW
178
        let buffer = self.buffer()?;
×
NEW
179
        let offset = T::SHADER_SIZE.get() * offset as u64;
×
NEW
180
        let size = NonZeroU64::new(T::SHADER_SIZE.get() * count as u64).unwrap();
×
NEW
181
        Some(BindingResource::Buffer(BufferBinding {
×
NEW
182
            buffer,
×
NEW
183
            offset,
×
NEW
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]
NEW
218
    pub fn dynamic_offset(&self, index: usize) -> u32 {
×
NEW
219
        let offset = self.aligned_size * index;
×
NEW
220
        assert!(offset <= u32::MAX as usize);
×
NEW
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() {
1✔
284
                buffer.destroy();
×
285
            }
UNCOV
286
            self.buffer = Some(device.create_buffer(&BufferDescriptor {
×
287
                label: self.label.as_ref().map(|s| &s[..]),
2✔
288
                size: size as BufferAddress,
×
289
                usage: BufferUsages::COPY_DST | self.buffer_usage,
×
290
                mapped_at_creation: false,
×
291
            }));
292
            // FIXME - this discards the old content if any!!!
293
            true
×
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
        );
314
        let buffer_changed = self.reserve(self.values.len(), device);
3✔
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);
×
318
            let mut aligned_buffer: Vec<u8> = vec![0; aligned_size];
3✔
319
            for i in 0..self.values.len() {
6✔
320
                let src: &[u8] = cast_slice(std::slice::from_ref(&self.values[i]));
6✔
321
                let dst_offset = i * self.aligned_size;
6✔
322
                let dst_range = dst_offset..dst_offset + self.item_size;
6✔
323
                trace!("+ copy: src={:?} dst={:?}", src.as_ptr(), dst_range);
6✔
324
                let dst = &mut aligned_buffer[dst_range];
6✔
325
                dst.copy_from_slice(src);
6✔
326
            }
327
            let bytes: &[u8] = cast_slice(&aligned_buffer);
3✔
328
            queue.write_buffer(buffer, 0, bytes);
3✔
329
        }
330
        buffer_changed
3✔
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
NEW
791
    pub fn update(&mut self, offset: u32, data: &[u8]) {
×
792
        // Can only update entire blocks starting at an aligned size
NEW
793
        let align = self.item_align as u32;
×
NEW
794
        if offset % align != 0 {
×
NEW
795
            return;
×
796
        }
797

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

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

NEW
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
            }
834
            self.buffer = Some(device.create_buffer(&BufferDescriptor {
835
                label: self.label.as_ref().map(|s| &s[..]),
×
836
                size: capacity as BufferAddress,
837
                usage: BufferUsages::COPY_DST | self.buffer_usage,
838
                mapped_at_creation: false,
839
            }));
840
            self.is_stale = !self.values.is_empty();
841
            // FIXME - this discards the old content if any!!!
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
        }
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