• 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

0.0
/src/render/sort.rs
1
use std::num::{NonZeroU32, NonZeroU64};
2

3
use bevy::{
4
    asset::Handle,
5
    ecs::{system::Resource, world::World},
6
    render::{
7
        render_resource::{
8
            BindGroup, BindGroupLayout, Buffer, BufferId, CachedComputePipelineId,
9
            ComputePipelineDescriptor, PipelineCache, Shader,
10
        },
11
        renderer::RenderDevice,
12
    },
13
    utils::{
14
        default,
15
        hashbrown::{hash_map::Entry, HashMap},
16
    },
17
};
18
use wgpu::{
19
    BindGroupEntry, BindGroupLayoutEntry, BindingResource, BindingType, BufferBinding,
20
    BufferBindingType, BufferDescriptor, BufferUsages, CommandEncoder, ShaderStages,
21
};
22

23
use super::{gpu_buffer::GpuBuffer, GpuDispatchIndirect, GpuEffectMetadata, StorageType};
24
use crate::{Attribute, ParticleLayout};
25

26
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
27
struct SortFillBindGroupLayoutKey {
28
    particle_min_binding_size: NonZeroU32,
29
    particle_ribbon_id_offset: u32,
30
    particle_age_offset: u32,
31
}
32

33
impl SortFillBindGroupLayoutKey {
NEW
34
    pub fn from_particle_layout(particle_layout: &ParticleLayout) -> Result<Self, ()> {
×
NEW
35
        let particle_ribbon_id_offset = particle_layout.offset(Attribute::RIBBON_ID).ok_or(())?;
×
NEW
36
        let particle_age_offset = particle_layout.offset(Attribute::AGE).ok_or(())?;
×
37
        let key = SortFillBindGroupLayoutKey {
38
            particle_min_binding_size: particle_layout.min_binding_size32(),
39
            particle_ribbon_id_offset,
40
            particle_age_offset,
41
        };
42
        Ok(key)
43
    }
44
}
45

46
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
47
struct SortFillBindGroupKey {
48
    particle: BufferId,
49
    indirect_index: BufferId,
50
    effect_metadata: BufferId,
51
}
52

53
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
54
struct SortCopyBindGroupKey {
55
    indirect_index: BufferId,
56
    sort: BufferId,
57
    effect_metadata: BufferId,
58
}
59

60
#[derive(Resource)]
61
pub struct SortBindGroups {
62
    /// Render device.
63
    render_device: RenderDevice,
64
    /// Sort-fill pass compute shader.
65
    sort_fill_shader: Handle<Shader>,
66
    /// GPU buffer of key-value pairs to sort.
67
    sort_buffer: Buffer,
68
    /// GPU buffer containing the [`GpuDispatchIndirect`] structs for the
69
    /// sort-fill and sort passes.
70
    indirect_buffer: GpuBuffer<GpuDispatchIndirect>,
71
    /// Bind group layouts for group #0 of the sort-fill compute pass.
72
    sort_fill_bind_group_layouts:
73
        HashMap<SortFillBindGroupLayoutKey, (BindGroupLayout, CachedComputePipelineId)>,
74
    /// Bind groups for group #0 of the sort-fill compute pass.
75
    sort_fill_bind_groups: HashMap<SortFillBindGroupKey, BindGroup>,
76
    /// Bind group for group #0 of the sort compute pass.
77
    sort_bind_group: BindGroup,
78
    sort_copy_bind_group_layout: BindGroupLayout,
79
    /// Pipeline for sort pass.
80
    sort_pipeline_id: CachedComputePipelineId,
81
    /// Pipeline for sort-copy pass.
82
    sort_copy_pipeline_id: CachedComputePipelineId,
83
    /// Bind groups for group #0 of the sort-copy compute pass.
84
    sort_copy_bind_groups: HashMap<SortCopyBindGroupKey, BindGroup>,
85
}
86

87
impl SortBindGroups {
NEW
88
    pub fn new(
×
89
        world: &mut World,
90
        sort_fill_shader: Handle<Shader>,
91
        sort_shader: Handle<Shader>,
92
        sort_copy_shader: Handle<Shader>,
93
    ) -> Self {
NEW
94
        let render_device = world.resource::<RenderDevice>();
×
NEW
95
        let pipeline_cache = world.resource::<PipelineCache>();
×
96

NEW
97
        let sort_buffer = render_device.create_buffer(&BufferDescriptor {
×
NEW
98
            label: Some("hanabi:buffer:sort:pairs"),
×
NEW
99
            size: 3 * 1024 * 1024,
×
NEW
100
            usage: BufferUsages::COPY_DST | BufferUsages::STORAGE,
×
NEW
101
            mapped_at_creation: false,
×
102
        });
103

NEW
104
        let indirect_buffer_size = 3 * 1024;
×
NEW
105
        let indirect_buffer = render_device.create_buffer(&BufferDescriptor {
×
NEW
106
            label: Some("hanabi:buffer:sort:indirect"),
×
NEW
107
            size: indirect_buffer_size,
×
NEW
108
            usage: BufferUsages::COPY_SRC
×
NEW
109
                | BufferUsages::COPY_DST
×
NEW
110
                | BufferUsages::STORAGE
×
NEW
111
                | BufferUsages::INDIRECT,
×
NEW
112
            mapped_at_creation: false,
×
113
        });
114
        let mut indirect_buffer = GpuBuffer::new_allocated(
NEW
115
            indirect_buffer,
×
NEW
116
            indirect_buffer_size as u32,
×
NEW
117
            Some("hanabi:buffer:sort:indirect".to_string()),
×
118
        );
119

120
        // Always allocate entry #0 for the sort pass itself. We don't store the index,
121
        // it's always zero.
NEW
122
        indirect_buffer.allocate();
×
123

NEW
124
        let sort_bind_group_layout = render_device.create_bind_group_layout(
×
125
            "hanabi:bind_group_layout:sort",
NEW
126
            &[BindGroupLayoutEntry {
×
NEW
127
                binding: 0,
×
NEW
128
                visibility: ShaderStages::COMPUTE,
×
NEW
129
                ty: BindingType::Buffer {
×
NEW
130
                    ty: BufferBindingType::Storage { read_only: false },
×
NEW
131
                    has_dynamic_offset: false,
×
NEW
132
                    min_binding_size: Some(NonZeroU64::new(16).unwrap()), // count + dual kv pair
×
133
                },
NEW
134
                count: None,
×
135
            }],
136
        );
137

NEW
138
        let sort_bind_group = render_device.create_bind_group(
×
139
            "hanabi:bind_group:sort",
NEW
140
            &sort_bind_group_layout,
×
NEW
141
            &[
×
142
                // @group(0) @binding(0) var<storage, read_write> pairs : array<KeyValuePair>;
NEW
143
                BindGroupEntry {
×
NEW
144
                    binding: 0,
×
NEW
145
                    resource: BindingResource::Buffer(BufferBinding {
×
NEW
146
                        buffer: &sort_buffer,
×
NEW
147
                        offset: 0,
×
NEW
148
                        size: None,
×
149
                    }),
150
                },
151
            ],
152
        );
153

NEW
154
        let sort_pipeline_id = pipeline_cache.queue_compute_pipeline(ComputePipelineDescriptor {
×
NEW
155
            label: Some("hanabi:pipeline:sort".into()),
×
NEW
156
            layout: vec![sort_bind_group_layout],
×
NEW
157
            shader: sort_shader,
×
NEW
158
            shader_defs: vec!["HAS_DUAL_KEY".into()],
×
NEW
159
            entry_point: "main".into(),
×
NEW
160
            push_constant_ranges: vec![],
×
NEW
161
            zero_initialize_workgroup_memory: false,
×
162
        });
163

164
        let effect_metadata_min_binding_size = GpuEffectMetadata::aligned_size(
NEW
165
            render_device.limits().min_storage_buffer_offset_alignment,
×
166
        );
NEW
167
        let sort_copy_bind_group_layout = render_device.create_bind_group_layout(
×
168
            "hanabi:bind_group_layout:sort_copy",
NEW
169
            &[
×
170
                // @group(0) @binding(0) var<storage, read_write> indirect_index_buffer :
171
                // IndirectIndexBuffer;
NEW
172
                BindGroupLayoutEntry {
×
NEW
173
                    binding: 0,
×
NEW
174
                    visibility: ShaderStages::COMPUTE,
×
NEW
175
                    ty: BindingType::Buffer {
×
NEW
176
                        ty: BufferBindingType::Storage { read_only: false },
×
NEW
177
                        has_dynamic_offset: true,
×
NEW
178
                        min_binding_size: Some(NonZeroU64::new(12).unwrap()), // ping/pong+dead
×
179
                    },
NEW
180
                    count: None,
×
181
                },
182
                // @group(0) @binding(1) var<storage, read> sort_buffer : SortBuffer;
NEW
183
                BindGroupLayoutEntry {
×
NEW
184
                    binding: 1,
×
NEW
185
                    visibility: ShaderStages::COMPUTE,
×
NEW
186
                    ty: BindingType::Buffer {
×
NEW
187
                        ty: BufferBindingType::Storage { read_only: true },
×
NEW
188
                        has_dynamic_offset: false,
×
NEW
189
                        min_binding_size: Some(NonZeroU64::new(16).unwrap()), /* count + dual kv
×
NEW
190
                                                                               * pair */
×
191
                    },
NEW
192
                    count: None,
×
193
                },
194
                // @group(0) @binding(2) var<storage, read> effect_metadata : EffectMetadata;
NEW
195
                BindGroupLayoutEntry {
×
NEW
196
                    binding: 2,
×
NEW
197
                    visibility: ShaderStages::COMPUTE,
×
NEW
198
                    ty: BindingType::Buffer {
×
NEW
199
                        ty: BufferBindingType::Storage { read_only: true },
×
NEW
200
                        has_dynamic_offset: true,
×
NEW
201
                        min_binding_size: Some(effect_metadata_min_binding_size),
×
202
                    },
NEW
203
                    count: None,
×
204
                },
205
            ],
206
        );
207

NEW
208
        let sort_copy_pipeline_id =
×
NEW
209
            pipeline_cache.queue_compute_pipeline(ComputePipelineDescriptor {
×
NEW
210
                label: Some("hanabi:pipeline:sort_copy".into()),
×
NEW
211
                layout: vec![sort_copy_bind_group_layout.clone()],
×
NEW
212
                shader: sort_copy_shader,
×
NEW
213
                shader_defs: vec![],
×
NEW
214
                entry_point: "main".into(),
×
NEW
215
                push_constant_ranges: vec![],
×
NEW
216
                zero_initialize_workgroup_memory: false,
×
217
            });
218

219
        Self {
NEW
220
            render_device: render_device.clone(),
×
221
            sort_fill_shader,
222
            sort_buffer,
223
            indirect_buffer,
NEW
224
            sort_fill_bind_group_layouts: default(),
×
NEW
225
            sort_fill_bind_groups: default(),
×
226
            sort_bind_group,
227
            sort_copy_bind_group_layout,
228
            sort_pipeline_id,
229
            sort_copy_pipeline_id,
NEW
230
            sort_copy_bind_groups: default(),
×
231
        }
232
    }
233

234
    #[inline]
NEW
235
    pub fn clear_indirect_dispatch_buffer(&mut self) {
×
NEW
236
        self.indirect_buffer.clear();
×
237
    }
238

239
    #[inline]
NEW
240
    pub fn allocate_indirect_dispatch(&mut self) -> u32 {
×
NEW
241
        self.indirect_buffer.allocate()
×
242
    }
243

244
    #[inline]
NEW
245
    pub fn get_indirect_dispatch_byte_offset(&self, index: u32) -> u32 {
×
NEW
246
        self.indirect_buffer.item_size() as u32 * index
×
247
    }
248

249
    #[inline]
NEW
250
    pub fn get_sort_indirect_dispatch_byte_offset(&self) -> u32 {
×
251
        // See new(); we always allocate entry #0 in the indirect buffer for the sort
252
        // pass itself
NEW
253
        0
×
254
    }
255

256
    #[inline]
257
    #[allow(dead_code)]
NEW
258
    pub fn sort_buffer(&self) -> &Buffer {
×
NEW
259
        &self.sort_buffer
×
260
    }
261

262
    #[inline]
NEW
263
    pub fn indirect_buffer(&self) -> Option<&Buffer> {
×
NEW
264
        self.indirect_buffer.buffer()
×
265
    }
266

267
    #[inline]
NEW
268
    pub fn sort_bind_group(&self) -> &BindGroup {
×
NEW
269
        &self.sort_bind_group
×
270
    }
271

272
    #[inline]
NEW
273
    pub fn sort_pipeline_id(&self) -> CachedComputePipelineId {
×
NEW
274
        self.sort_pipeline_id
×
275
    }
276

277
    #[inline]
NEW
278
    pub fn prepare_buffers(&mut self, render_device: &RenderDevice) {
×
NEW
279
        self.indirect_buffer.prepare_buffers(render_device);
×
280
    }
281

282
    #[inline]
NEW
283
    pub fn write_buffers(&self, command_encoder: &mut CommandEncoder) {
×
NEW
284
        self.indirect_buffer.write_buffers(command_encoder);
×
285
    }
286

287
    #[inline]
NEW
288
    pub fn clear_previous_frame_resizes(&mut self) {
×
NEW
289
        self.indirect_buffer.clear_previous_frame_resizes();
×
290
    }
291

NEW
292
    pub fn ensure_sort_fill_bind_group_layout(
×
293
        &mut self,
294
        pipeline_cache: &PipelineCache,
295
        particle_layout: &ParticleLayout,
296
    ) -> Result<&BindGroupLayout, ()> {
NEW
297
        let key = SortFillBindGroupLayoutKey::from_particle_layout(particle_layout)?;
×
298
        let (layout, _) = self
299
            .sort_fill_bind_group_layouts
300
            .entry(key)
NEW
301
            .or_insert_with(|| {
×
NEW
302
                let alignment = self
×
NEW
303
                    .render_device
×
NEW
304
                    .limits()
×
NEW
305
                    .min_storage_buffer_offset_alignment;
×
NEW
306
                let bind_group_layout = self.render_device.create_bind_group_layout(
×
NEW
307
                    "hanabi:bind_group_layout:sort_fill",
×
NEW
308
                    &[
×
309
                        // @group(0) @binding(0) var<storage, read_write> pairs: array<KeyValuePair>;
NEW
310
                        BindGroupLayoutEntry {
×
NEW
311
                            binding: 0,
×
NEW
312
                            visibility: ShaderStages::COMPUTE,
×
NEW
313
                            ty: BindingType::Buffer {
×
NEW
314
                                ty: BufferBindingType::Storage { read_only: false },
×
NEW
315
                                has_dynamic_offset: false,
×
NEW
316
                                min_binding_size: Some(NonZeroU64::new(16).unwrap()), // count + dual kv pair
×
317
                            },
NEW
318
                            count: None,
×
319
                        },
320
                        // @group(0) @binding(1) var<storage, read> particle_buffer: ParticleBuffer;
NEW
321
                        BindGroupLayoutEntry {
×
NEW
322
                            binding: 1,
×
NEW
323
                            visibility: ShaderStages::COMPUTE,
×
NEW
324
                            ty: BindingType::Buffer {
×
NEW
325
                                ty: BufferBindingType::Storage { read_only: true },
×
NEW
326
                                has_dynamic_offset: true,
×
NEW
327
                                min_binding_size: Some(key.particle_min_binding_size.into()),
×
328
                            },
NEW
329
                            count: None,
×
330
                        },
331
                        // @group(0) @binding(2) var<storage, read> indirect_index_buffer : array<u32>;
NEW
332
                        BindGroupLayoutEntry {
×
NEW
333
                            binding: 2,
×
NEW
334
                            visibility: ShaderStages::COMPUTE,
×
NEW
335
                            ty: BindingType::Buffer {
×
NEW
336
                                ty: BufferBindingType::Storage { read_only: true },
×
NEW
337
                                has_dynamic_offset: true,
×
NEW
338
                                min_binding_size: Some(NonZeroU64::new(12).unwrap()), // ping/pong+dead
×
339
                            },
NEW
340
                            count: None,
×
341
                        },
342
                        // @group(0) @binding(3) var<storage, read> effect_metadata : EffectMetadata;
NEW
343
                        BindGroupLayoutEntry {
×
NEW
344
                            binding: 3,
×
NEW
345
                            visibility: ShaderStages::COMPUTE,
×
NEW
346
                            ty: BindingType::Buffer {
×
NEW
347
                                ty: BufferBindingType::Storage { read_only: true },
×
NEW
348
                                has_dynamic_offset: true,
×
NEW
349
                                min_binding_size: Some(GpuEffectMetadata::aligned_size(alignment)),
×
350
                            },
NEW
351
                            count: None,
×
352
                        },
353
                    ],
354
                );
NEW
355
                let pipeline_id =
×
NEW
356
                    pipeline_cache.queue_compute_pipeline(ComputePipelineDescriptor {
×
NEW
357
                        label: Some("hanabi:pipeline:sort_fill".into()),
×
NEW
358
                        layout: vec![bind_group_layout.clone()],
×
NEW
359
                        shader: self.sort_fill_shader.clone(),
×
NEW
360
                        shader_defs: vec!["HAS_DUAL_KEY".into()],
×
NEW
361
                        entry_point: "main".into(),
×
NEW
362
                        push_constant_ranges: vec![],
×
NEW
363
                        zero_initialize_workgroup_memory: false,
×
364
                    });
NEW
365
                (bind_group_layout, pipeline_id)
×
366
            });
367
        Ok(layout)
368
    }
369

370
    // We currently only use the bind group layout internally in
371
    // ensure_sort_fill_bind_group()
372
    #[allow(dead_code)]
NEW
373
    pub fn get_sort_fill_bind_group_layout(
×
374
        &self,
375
        particle_layout: &ParticleLayout,
376
    ) -> Option<&BindGroupLayout> {
NEW
377
        let key = SortFillBindGroupLayoutKey::from_particle_layout(particle_layout).ok()?;
×
378
        self.sort_fill_bind_group_layouts
379
            .get(&key)
NEW
380
            .map(|(layout, _)| layout)
×
381
    }
382

NEW
383
    pub fn get_sort_fill_pipeline_id(
×
384
        &self,
385
        particle_layout: &ParticleLayout,
386
    ) -> Option<CachedComputePipelineId> {
NEW
387
        let key = SortFillBindGroupLayoutKey::from_particle_layout(particle_layout).ok()?;
×
388
        self.sort_fill_bind_group_layouts
389
            .get(&key)
NEW
390
            .map(|(_, pipeline_id)| *pipeline_id)
×
391
    }
392

NEW
393
    pub fn get_sort_copy_pipeline_id(&self) -> CachedComputePipelineId {
×
NEW
394
        self.sort_copy_pipeline_id
×
395
    }
396

NEW
397
    pub fn ensure_sort_fill_bind_group(
×
398
        &mut self,
399
        particle_layout: &ParticleLayout,
400
        particle: &Buffer,
401
        indirect_index: &Buffer,
402
        effect_metadata: &Buffer,
403
    ) -> Result<&BindGroup, ()> {
404
        let key = SortFillBindGroupKey {
NEW
405
            particle: particle.id(),
×
NEW
406
            indirect_index: indirect_index.id(),
×
NEW
407
            effect_metadata: effect_metadata.id(),
×
408
        };
NEW
409
        let entry = self.sort_fill_bind_groups.entry(key);
×
NEW
410
        let bind_group = match entry {
×
NEW
411
            Entry::Occupied(entry) => entry.into_mut(),
×
NEW
412
            Entry::Vacant(entry) => {
×
413
                // Note: can't use get_bind_group_layout() because the function call mixes the
414
                // lifetimes of the two hash maps and complains the bind group one is already
415
                // borrowed. Doing a manual access to the layout one instead makes the compiler
416
                // happy.
NEW
417
                let key = SortFillBindGroupLayoutKey::from_particle_layout(particle_layout)?;
×
NEW
418
                let layout = &self.sort_fill_bind_group_layouts.get(&key).ok_or(())?.0;
×
419
                entry.insert(
420
                    self.render_device.create_bind_group(
421
                        "hanabi:bind_group:sort_fill",
422
                        layout,
423
                        &[
424
                            // @group(0) @binding(0) var<storage, read_write> pairs:
425
                            // array<KeyValuePair>;
426
                            BindGroupEntry {
427
                                binding: 0,
428
                                resource: BindingResource::Buffer(BufferBinding {
429
                                    buffer: &self.sort_buffer,
430
                                    offset: 0,
431
                                    size: None,
432
                                }),
433
                            },
434
                            // @group(0) @binding(1) var<storage, read> particle_buffer:
435
                            // ParticleBuffer;
436
                            BindGroupEntry {
437
                                binding: 1,
438
                                resource: BindingResource::Buffer(BufferBinding {
439
                                    buffer: particle,
440
                                    offset: 0,
441
                                    size: None,
442
                                }),
443
                            },
444
                            // @group(0) @binding(2) var<storage, read> indirect_index_buffer :
445
                            // array<u32>;
446
                            BindGroupEntry {
447
                                binding: 2,
448
                                resource: BindingResource::Buffer(BufferBinding {
449
                                    buffer: indirect_index,
450
                                    offset: 0,
451
                                    size: None,
452
                                }),
453
                            },
454
                            // @group(0) @binding(3) var<storage, read> effect_metadata :
455
                            // EffectMetadata;
456
                            BindGroupEntry {
457
                                binding: 3,
458
                                resource: BindingResource::Buffer(BufferBinding {
459
                                    buffer: effect_metadata,
460
                                    offset: 0,
461
                                    size: Some(GpuEffectMetadata::aligned_size(
462
                                        self.render_device
463
                                            .limits()
464
                                            .min_storage_buffer_offset_alignment,
465
                                    )),
466
                                }),
467
                            },
468
                        ],
469
                    ),
470
                )
471
            }
472
        };
473
        Ok(bind_group)
474
    }
475

NEW
476
    pub fn sort_fill_bind_group(
×
477
        &self,
478
        particle: BufferId,
479
        indirect_index: BufferId,
480
        effect_metadata: BufferId,
481
    ) -> Option<&BindGroup> {
482
        let key = SortFillBindGroupKey {
483
            particle,
484
            indirect_index,
485
            effect_metadata,
486
        };
NEW
487
        self.sort_fill_bind_groups.get(&key)
×
488
    }
489

NEW
490
    pub fn ensure_sort_copy_bind_group(
×
491
        &mut self,
492
        indirect_index_buffer: &Buffer,
493
        effect_metadata_buffer: &Buffer,
494
    ) -> Result<&BindGroup, ()> {
495
        let key = SortCopyBindGroupKey {
NEW
496
            indirect_index: indirect_index_buffer.id(),
×
NEW
497
            sort: self.sort_buffer.id(),
×
NEW
498
            effect_metadata: effect_metadata_buffer.id(),
×
499
        };
NEW
500
        let entry = self.sort_copy_bind_groups.entry(key);
×
NEW
501
        let bind_group = match entry {
×
NEW
502
            Entry::Occupied(entry) => entry.into_mut(),
×
NEW
503
            Entry::Vacant(entry) => {
×
NEW
504
                entry.insert(
×
NEW
505
                    self.render_device.create_bind_group(
×
NEW
506
                        "hanabi:bind_group:sort_copy",
×
NEW
507
                        &self.sort_copy_bind_group_layout,
×
NEW
508
                        &[
×
509
                            // @group(0) @binding(0) var<storage, read_write> indirect_index_buffer
510
                            // : IndirectIndexBuffer;
NEW
511
                            BindGroupEntry {
×
NEW
512
                                binding: 0,
×
NEW
513
                                resource: BindingResource::Buffer(BufferBinding {
×
NEW
514
                                    buffer: indirect_index_buffer,
×
NEW
515
                                    offset: 0,
×
NEW
516
                                    size: None,
×
517
                                }),
518
                            },
519
                            // @group(0) @binding(1) var<storage, read> sort_buffer : SortBuffer;
NEW
520
                            BindGroupEntry {
×
NEW
521
                                binding: 1,
×
NEW
522
                                resource: BindingResource::Buffer(BufferBinding {
×
NEW
523
                                    buffer: &self.sort_buffer,
×
NEW
524
                                    offset: 0,
×
NEW
525
                                    size: None,
×
526
                                }),
527
                            },
528
                            // @group(0) @binding(2) var<storage, read> effect_metadata :
529
                            // EffectMetadata;
NEW
530
                            BindGroupEntry {
×
NEW
531
                                binding: 2,
×
NEW
532
                                resource: BindingResource::Buffer(BufferBinding {
×
NEW
533
                                    buffer: effect_metadata_buffer,
×
NEW
534
                                    offset: 0,
×
NEW
535
                                    size: Some(GpuEffectMetadata::aligned_size(
×
NEW
536
                                        self.render_device
×
NEW
537
                                            .limits()
×
NEW
538
                                            .min_storage_buffer_offset_alignment,
×
539
                                    )),
540
                                }),
541
                            },
542
                        ],
543
                    ),
544
                )
545
            }
546
        };
NEW
547
        Ok(bind_group)
×
548
    }
549

NEW
550
    pub fn sort_copy_bind_group(
×
551
        &self,
552
        indirect_index: BufferId,
553
        effect_metadata: BufferId,
554
    ) -> Option<&BindGroup> {
555
        let key = SortCopyBindGroupKey {
556
            indirect_index,
NEW
557
            sort: self.sort_buffer.id(),
×
558
            effect_metadata,
559
        };
NEW
560
        self.sort_copy_bind_groups.get(&key)
×
561
    }
562
}
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