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

djeedai / bevy_hanabi / 21565578469

01 Feb 2026 03:38PM UTC coverage: 58.351% (-8.1%) from 66.442%
21565578469

push

github

web-flow
Update to Bevy v0.18 (#521)

Thanks to @morgenthum for the original work.

93 of 170 new or added lines in 6 files covered. (54.71%)

968 existing lines in 17 files now uncovered.

4954 of 8490 relevant lines covered (58.35%)

190.51 hits per line

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

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

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

22
use super::{gpu_buffer::GpuBuffer, GpuDispatchIndirectArgs, GpuEffectMetadata, StorageType};
23
use crate::{render::GpuSpawnerParams, Attribute, ParticleLayout};
24

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

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

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

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

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

90
impl SortBindGroups {
91
    pub fn new(
3✔
92
        world: &mut World,
93
        sort_fill_shader: Handle<Shader>,
94
        sort_shader: Handle<Shader>,
95
        sort_copy_shader: Handle<Shader>,
96
    ) -> Self {
97
        let render_device = world.resource::<RenderDevice>();
9✔
98
        let pipeline_cache = world.resource::<PipelineCache>();
9✔
99

100
        let sort_buffer = render_device.create_buffer(&BufferDescriptor {
12✔
101
            label: Some("hanabi:buffer:sort:pairs"),
6✔
102
            size: 3 * 1024 * 1024,
3✔
103
            usage: BufferUsages::COPY_DST | BufferUsages::STORAGE,
3✔
104
            mapped_at_creation: false,
3✔
105
        });
106

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

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

137
        let sort_pipeline_id = pipeline_cache.queue_compute_pipeline(ComputePipelineDescriptor {
12✔
138
            label: Some("hanabi:pipeline:sort".into()),
6✔
139
            layout: vec![sort_bind_group_layout_desc.clone()],
12✔
140
            shader: sort_shader,
6✔
141
            shader_defs: vec!["HAS_DUAL_KEY".into()],
12✔
142
            entry_point: Some("main".into()),
3✔
143
            push_constant_ranges: vec![],
3✔
144
            zero_initialize_workgroup_memory: false,
3✔
145
        });
146

147
        let alignment = render_device.limits().min_storage_buffer_offset_alignment;
6✔
148
        let sort_copy_bind_group_layout_desc = BindGroupLayoutDescriptor::new(
149
            "hanabi:bind_group_layout:sort_copy",
150
            &[
3✔
151
                // @group(0) @binding(0) var<storage, read_write> indirect_index_buffer :
152
                // IndirectIndexBuffer;
153
                BindGroupLayoutEntry {
6✔
154
                    binding: 0,
6✔
155
                    visibility: ShaderStages::COMPUTE,
6✔
156
                    ty: BindingType::Buffer {
6✔
157
                        ty: BufferBindingType::Storage { read_only: false },
9✔
158
                        has_dynamic_offset: false,
6✔
159
                        min_binding_size: Some(NonZeroU64::new(12).unwrap()), // ping/pong+dead
6✔
160
                    },
161
                    count: None,
6✔
162
                },
163
                // @group(0) @binding(1) var<storage, read> sort_buffer : SortBuffer;
164
                BindGroupLayoutEntry {
6✔
165
                    binding: 1,
6✔
166
                    visibility: ShaderStages::COMPUTE,
6✔
167
                    ty: BindingType::Buffer {
6✔
168
                        ty: BufferBindingType::Storage { read_only: true },
9✔
169
                        has_dynamic_offset: false,
6✔
170
                        min_binding_size: Some(NonZeroU64::new(16).unwrap()), /* count + dual kv
6✔
171
                                                                               * pair */
6✔
172
                    },
173
                    count: None,
6✔
174
                },
175
                // @group(0) @binding(2) var<storage, read_write> effect_metadata : EffectMetadata;
176
                BindGroupLayoutEntry {
6✔
177
                    binding: 2,
6✔
178
                    visibility: ShaderStages::COMPUTE,
6✔
179
                    ty: BindingType::Buffer {
6✔
180
                        ty: BufferBindingType::Storage { read_only: false },
9✔
181
                        has_dynamic_offset: true,
6✔
182
                        min_binding_size: Some(GpuEffectMetadata::aligned_size(alignment)),
6✔
183
                    },
184
                    count: None,
6✔
185
                },
186
                // @group(0) @binding(3) var<storage, read> spawner : Spawner;
187
                BindGroupLayoutEntry {
3✔
188
                    binding: 3,
3✔
189
                    visibility: ShaderStages::COMPUTE,
3✔
190
                    ty: BindingType::Buffer {
3✔
191
                        ty: BufferBindingType::Storage { read_only: true },
6✔
192
                        has_dynamic_offset: true,
3✔
193
                        min_binding_size: Some(GpuSpawnerParams::aligned_size(alignment)),
3✔
194
                    },
195
                    count: None,
3✔
196
                },
197
            ],
198
        );
199

200
        let sort_copy_pipeline_id =
3✔
201
            pipeline_cache.queue_compute_pipeline(ComputePipelineDescriptor {
9✔
202
                label: Some("hanabi:pipeline:sort_copy".into()),
6✔
203
                layout: vec![sort_copy_bind_group_layout_desc.clone()],
12✔
204
                shader: sort_copy_shader,
6✔
205
                shader_defs: vec![],
6✔
206
                entry_point: Some("main".into()),
3✔
207
                push_constant_ranges: vec![],
3✔
208
                zero_initialize_workgroup_memory: false,
3✔
209
            });
210

211
        Self {
212
            render_device: render_device.clone(),
9✔
213
            sort_fill_shader,
214
            sort_buffer,
215
            indirect_buffer,
216
            sort_fill_bind_group_layout_descs: default(),
6✔
217
            sort_fill_bind_groups: default(),
6✔
218
            sort_bind_group_layout_desc,
219
            sort_bind_group: None, // created once the pipeline and its bind group layouts are created by the pipeline cache
220
            sort_copy_bind_group_layout_desc,
221
            sort_pipeline_id,
222
            sort_copy_pipeline_id,
223
            sort_copy_bind_groups: default(),
3✔
224
        }
225
    }
226

227
    #[inline]
228
    pub fn clear_indirect_dispatch_buffer(&mut self) {
330✔
229
        self.indirect_buffer.clear();
660✔
230
    }
231

232
    #[inline]
233
    pub fn allocate_indirect_dispatch(&mut self) -> u32 {
×
234
        self.indirect_buffer.allocate()
×
235
    }
236

237
    #[inline]
238
    pub fn get_indirect_dispatch_byte_offset(&self, index: u32) -> u32 {
×
239
        self.indirect_buffer.item_size() as u32 * index
×
240
    }
241

242
    #[inline]
243
    #[allow(dead_code)]
244
    pub fn sort_buffer(&self) -> &Buffer {
×
245
        &self.sort_buffer
×
246
    }
247

248
    #[inline]
249
    pub fn indirect_buffer(&self) -> Option<&Buffer> {
312✔
250
        self.indirect_buffer.buffer()
624✔
251
    }
252

253
    #[inline]
254
    pub fn sort_pipeline_id(&self) -> CachedComputePipelineId {
×
255
        self.sort_pipeline_id
×
256
    }
257

258
    /// Check if the sort pipeline is ready to run for the given effect
259
    /// instance.
260
    ///
261
    /// This ensures all compute pipelines are compiled and ready to be used
262
    /// this frame.
263
    pub fn is_pipeline_ready(
×
264
        &self,
265
        particle_layout: &ParticleLayout,
266
        pipeline_cache: &PipelineCache,
267
    ) -> bool {
268
        // Validate the sort-fill pipeline. It was created and queued for compile by
269
        // ensure_sort_fill_bind_group_layout(), which normally is called just before
270
        // is_pipeline_ready().
271
        let Some(pipeline_id) = self.get_sort_fill_pipeline_id(particle_layout) else {
×
272
            return false;
×
273
        };
274
        if !matches!(
×
UNCOV
275
            pipeline_cache.get_compute_pipeline_state(pipeline_id),
×
276
            CachedPipelineState::Ok(_)
277
        ) {
278
            return false;
×
279
        }
280

281
        // The 2 pipelines below are created and queued for compile in new(), so are
282
        // almost always ready.
283
        // FIXME - they could be checked once a frame only, not once per effect...
284

285
        // Validate the sort pipeline
286
        if !matches!(
×
287
            pipeline_cache.get_compute_pipeline_state(self.sort_pipeline_id()),
×
288
            CachedPipelineState::Ok(_)
289
        ) {
290
            return false;
×
291
        }
292

293
        // Validate the sort-copy pipeline
294
        if !matches!(
×
295
            pipeline_cache.get_compute_pipeline_state(self.get_sort_copy_pipeline_id()),
×
296
            CachedPipelineState::Ok(_)
297
        ) {
298
            return false;
×
299
        }
300

301
        true
×
302
    }
303

304
    #[inline]
305
    pub fn prepare_buffers(&mut self, render_device: &RenderDevice) {
330✔
306
        self.indirect_buffer.prepare_buffers(render_device);
990✔
307
    }
308

309
    #[inline]
310
    pub fn write_buffers(&self, command_encoder: &mut CommandEncoder) {
330✔
311
        self.indirect_buffer.write_buffers(command_encoder);
990✔
312
    }
313

314
    #[inline]
315
    pub fn clear_previous_frame_resizes(&mut self) {
330✔
316
        self.indirect_buffer.clear_previous_frame_resizes();
660✔
317
    }
318

NEW
319
    pub fn ensure_sort_fill_bind_group_layout_desc(
×
320
        &mut self,
321
        pipeline_cache: &PipelineCache,
322
        particle_layout: &ParticleLayout,
323
    ) -> Result<&BindGroupLayoutDescriptor, ()> {
324
        let key = SortFillBindGroupLayoutKey::from_particle_layout(particle_layout)?;
×
UNCOV
325
        let (layout, _) = self
×
NEW
326
            .sort_fill_bind_group_layout_descs
×
UNCOV
327
            .entry(key)
×
328
            .or_insert_with(|| {
×
329
                let alignment = self
×
330
                    .render_device
×
331
                    .limits()
×
332
                    .min_storage_buffer_offset_alignment;
×
NEW
333
                let bind_group_layout_desc = BindGroupLayoutDescriptor::new(
×
334
                    "hanabi:bind_group_layout:sort_fill",
335
                    &[
×
336
                        // @group(0) @binding(0) var<storage, read_write> sort_buffer : SortBuffer;
337
                        BindGroupLayoutEntry {
×
338
                            binding: 0,
×
339
                            visibility: ShaderStages::COMPUTE,
×
340
                            ty: BindingType::Buffer {
×
341
                                ty: BufferBindingType::Storage { read_only: false },
×
342
                                has_dynamic_offset: false,
×
343
                                min_binding_size: Some(NonZeroU64::new(16).unwrap()), // count + dual kv pair
×
344
                            },
345
                            count: None,
×
346
                        },
347
                        // @group(0) @binding(1) var<storage, read> particle_buffer : RawParticleBuffer;
348
                        BindGroupLayoutEntry {
×
349
                            binding: 1,
×
350
                            visibility: ShaderStages::COMPUTE,
×
351
                            ty: BindingType::Buffer {
×
352
                                ty: BufferBindingType::Storage { read_only: true },
×
353
                                has_dynamic_offset: false,
×
354
                                min_binding_size: Some(key.particle_min_binding_size.into()),
×
355
                            },
356
                            count: None,
×
357
                        },
358
                        // @group(0) @binding(2) var<storage, read> indirect_index_buffer : array<u32>;
359
                        BindGroupLayoutEntry {
×
360
                            binding: 2,
×
361
                            visibility: ShaderStages::COMPUTE,
×
362
                            ty: BindingType::Buffer {
×
363
                                ty: BufferBindingType::Storage { read_only: true },
×
364
                                has_dynamic_offset: false,
×
365
                                min_binding_size: Some(NonZeroU64::new(12).unwrap()), // ping/pong+dead
×
366
                            },
367
                            count: None,
×
368
                        },
369
                        // @group(0) @binding(3) var<storage, read_write> effect_metadata : EffectMetadata;
370
                        BindGroupLayoutEntry {
×
371
                            binding: 3,
×
372
                            visibility: ShaderStages::COMPUTE,
×
373
                            ty: BindingType::Buffer {
×
374
                                ty: BufferBindingType::Storage { read_only: false },
×
375
                                has_dynamic_offset: true,
×
376
                                min_binding_size: Some(GpuEffectMetadata::aligned_size(alignment)),
×
377
                            },
378
                            count: None,
×
379
                        },
380
                        // @group(0) @binding(4) var<storage, read> spawner : Spawner;
381
                        BindGroupLayoutEntry {
×
382
                            binding: 4,
×
383
                            visibility: ShaderStages::COMPUTE,
×
384
                            ty: BindingType::Buffer {
×
385
                                ty: BufferBindingType::Storage { read_only: true },
×
386
                                has_dynamic_offset: true,
×
387
                                min_binding_size: Some(GpuSpawnerParams::aligned_size(alignment)),
×
388
                            },
389
                            count: None,
×
390
                        },
391
                    ],
392
                );
393
                let pipeline_id =
×
394
                    pipeline_cache.queue_compute_pipeline(ComputePipelineDescriptor {
×
395
                        label: Some("hanabi:pipeline:sort_fill".into()),
×
NEW
396
                        layout: vec![bind_group_layout_desc.clone()],
×
397
                        shader: self.sort_fill_shader.clone(),
×
398
                        shader_defs: vec!["HAS_DUAL_KEY".into()],
×
399
                        entry_point: Some("main".into()),
×
400
                        push_constant_ranges: vec![],
×
401
                        zero_initialize_workgroup_memory: false,
×
402
                    });
NEW
403
                (bind_group_layout_desc, pipeline_id)
×
404
            });
UNCOV
405
        Ok(layout)
×
406
    }
407

408
    // We currently only use the bind group layout internally in
409
    // ensure_sort_fill_bind_group()
410
    #[allow(dead_code)]
NEW
411
    pub fn get_sort_fill_bind_group_layout_desc(
×
412
        &self,
413
        particle_layout: &ParticleLayout,
414
    ) -> Option<&BindGroupLayoutDescriptor> {
415
        let key = SortFillBindGroupLayoutKey::from_particle_layout(particle_layout).ok()?;
×
NEW
416
        self.sort_fill_bind_group_layout_descs
×
UNCOV
417
            .get(&key)
×
UNCOV
418
            .map(|(layout, _)| layout)
×
419
    }
420

421
    pub fn get_sort_fill_pipeline_id(
×
422
        &self,
423
        particle_layout: &ParticleLayout,
424
    ) -> Option<CachedComputePipelineId> {
425
        let key = SortFillBindGroupLayoutKey::from_particle_layout(particle_layout).ok()?;
×
NEW
426
        self.sort_fill_bind_group_layout_descs
×
UNCOV
427
            .get(&key)
×
UNCOV
428
            .map(|(_, pipeline_id)| *pipeline_id)
×
429
    }
430

431
    pub fn get_sort_copy_pipeline_id(&self) -> CachedComputePipelineId {
×
432
        self.sort_copy_pipeline_id
×
433
    }
434

435
    pub fn ensure_sort_fill_bind_group(
×
436
        &mut self,
437
        particle_layout: &ParticleLayout,
438
        particle: &Buffer,
439
        indirect_index: &Buffer,
440
        effect_metadata: &Buffer,
441
        spawner_buffer: &Buffer,
442
        pipeline_cache: &PipelineCache,
443
    ) -> Result<&BindGroup, ()> {
444
        let key = SortFillBindGroupKey {
445
            particle: particle.id(),
×
446
            indirect_index: indirect_index.id(),
×
447
            effect_metadata: effect_metadata.id(),
×
448
        };
449
        let entry = self.sort_fill_bind_groups.entry(key);
×
450
        let bind_group = match entry {
×
451
            Entry::Occupied(entry) => entry.into_mut(),
×
452
            Entry::Vacant(entry) => {
×
453
                // Note: can't use get_bind_group_layout() because the function call mixes the
454
                // lifetimes of the two hash maps and complains the bind group one is already
455
                // borrowed. Doing a manual access to the layout one instead makes the compiler
456
                // happy.
457
                let key = SortFillBindGroupLayoutKey::from_particle_layout(particle_layout)?;
×
NEW
458
                let layout_desc = &self
×
NEW
459
                    .sort_fill_bind_group_layout_descs
×
NEW
460
                    .get(&key)
×
NEW
461
                    .ok_or(())?
×
462
                    .0;
UNCOV
463
                entry.insert(
×
UNCOV
464
                    self.render_device.create_bind_group(
×
UNCOV
465
                        "hanabi:bind_group:sort_fill",
×
NEW
466
                        &pipeline_cache.get_bind_group_layout(layout_desc),
×
UNCOV
467
                        &[
×
468
                            // @group(0) @binding(0) var<storage, read_write> pairs:
469
                            // array<KeyValuePair>;
UNCOV
470
                            BindGroupEntry {
×
UNCOV
471
                                binding: 0,
×
UNCOV
472
                                resource: self.sort_buffer.as_entire_binding(),
×
473
                            },
474
                            // @group(0) @binding(1) var<storage, read> particle_buffer:
475
                            // ParticleBuffer;
UNCOV
476
                            BindGroupEntry {
×
UNCOV
477
                                binding: 1,
×
UNCOV
478
                                resource: particle.as_entire_binding(),
×
479
                            },
480
                            // @group(0) @binding(2) var<storage, read> indirect_index_buffer :
481
                            // array<u32>;
UNCOV
482
                            BindGroupEntry {
×
UNCOV
483
                                binding: 2,
×
UNCOV
484
                                resource: indirect_index.as_entire_binding(),
×
485
                            },
486
                            // @group(0) @binding(3) var<storage, read> effect_metadata :
487
                            // EffectMetadata;
UNCOV
488
                            BindGroupEntry {
×
UNCOV
489
                                binding: 3,
×
UNCOV
490
                                resource: BindingResource::Buffer(BufferBinding {
×
UNCOV
491
                                    buffer: effect_metadata,
×
UNCOV
492
                                    offset: 0,
×
UNCOV
493
                                    size: Some(GpuEffectMetadata::aligned_size(
×
UNCOV
494
                                        self.render_device
×
UNCOV
495
                                            .limits()
×
UNCOV
496
                                            .min_storage_buffer_offset_alignment,
×
497
                                    )),
498
                                }),
499
                            },
500
                            // @group(0) @binding(4) var<storage, read> spawner : Spawner;
UNCOV
501
                            BindGroupEntry {
×
UNCOV
502
                                binding: 4,
×
UNCOV
503
                                resource: BindingResource::Buffer(BufferBinding {
×
UNCOV
504
                                    buffer: spawner_buffer,
×
UNCOV
505
                                    offset: 0,
×
UNCOV
506
                                    size: Some(GpuSpawnerParams::aligned_size(
×
UNCOV
507
                                        self.render_device
×
UNCOV
508
                                            .limits()
×
UNCOV
509
                                            .min_storage_buffer_offset_alignment,
×
510
                                    )),
511
                                }),
512
                            },
513
                        ],
514
                    ),
515
                )
516
            }
517
        };
UNCOV
518
        Ok(bind_group)
×
519
    }
520

521
    pub fn sort_fill_bind_group(
×
522
        &self,
523
        particle: BufferId,
524
        indirect_index: BufferId,
525
        effect_metadata: BufferId,
526
    ) -> Option<&BindGroup> {
527
        let key = SortFillBindGroupKey {
528
            particle,
529
            indirect_index,
530
            effect_metadata,
531
        };
532
        self.sort_fill_bind_groups.get(&key)
×
533
    }
534

535
    /// Ensure the bind group for the sort pass is created.
NEW
536
    pub fn ensure_sort_bind_group(
×
537
        &mut self,
538
        pipeline_cache: &PipelineCache,
539
    ) -> Result<&BindGroup, ()> {
NEW
540
        if self.sort_bind_group.is_none() {
×
NEW
541
            let sort_bind_group = self.render_device.create_bind_group(
×
542
                "hanabi:bind_group:sort",
NEW
543
                &pipeline_cache.get_bind_group_layout(&self.sort_bind_group_layout_desc),
×
NEW
544
                &[
×
545
                    // @group(0) @binding(0) var<storage, read_write> pairs : array<KeyValuePair>;
NEW
546
                    BindGroupEntry {
×
NEW
547
                        binding: 0,
×
NEW
548
                        resource: self.sort_buffer.as_entire_binding(),
×
549
                    },
550
                ],
551
            );
NEW
552
            self.sort_bind_group = Some(sort_bind_group);
×
553
        }
NEW
554
        Ok(self.sort_bind_group.as_ref().unwrap())
×
555
    }
556

557
    #[inline]
NEW
558
    pub fn sort_bind_group(&self) -> Option<&BindGroup> {
×
NEW
559
        self.sort_bind_group.as_ref()
×
560
    }
561

UNCOV
562
    pub fn ensure_sort_copy_bind_group(
×
563
        &mut self,
564
        indirect_index_buffer: &Buffer,
565
        effect_metadata_buffer: &Buffer,
566
        spawner_buffer: &Buffer,
567
        pipeline_cache: &PipelineCache,
568
    ) -> Result<&BindGroup, ()> {
569
        let key = SortCopyBindGroupKey {
570
            indirect_index: indirect_index_buffer.id(),
×
571
            sort: self.sort_buffer.id(),
×
572
            effect_metadata: effect_metadata_buffer.id(),
×
573
        };
574
        let entry = self.sort_copy_bind_groups.entry(key);
×
575
        let bind_group = match entry {
×
576
            Entry::Occupied(entry) => entry.into_mut(),
×
577
            Entry::Vacant(entry) => {
×
578
                entry.insert(
×
579
                    self.render_device.create_bind_group(
×
580
                        "hanabi:bind_group:sort_copy",
×
NEW
581
                        &pipeline_cache
×
NEW
582
                            .get_bind_group_layout(&self.sort_copy_bind_group_layout_desc),
×
UNCOV
583
                        &[
×
584
                            // @group(0) @binding(0) var<storage, read_write> indirect_index_buffer
585
                            // : IndirectIndexBuffer;
586
                            BindGroupEntry {
×
587
                                binding: 0,
×
588
                                resource: indirect_index_buffer.as_entire_binding(),
×
589
                            },
590
                            // @group(0) @binding(1) var<storage, read> sort_buffer : SortBuffer;
591
                            BindGroupEntry {
×
592
                                binding: 1,
×
593
                                resource: self.sort_buffer.as_entire_binding(),
×
594
                            },
595
                            // @group(0) @binding(2) var<storage, read> effect_metadata :
596
                            // EffectMetadata;
597
                            BindGroupEntry {
×
598
                                binding: 2,
×
599
                                resource: BindingResource::Buffer(BufferBinding {
×
600
                                    buffer: effect_metadata_buffer,
×
601
                                    offset: 0,
×
602
                                    size: Some(GpuEffectMetadata::aligned_size(
×
603
                                        self.render_device
×
604
                                            .limits()
×
605
                                            .min_storage_buffer_offset_alignment,
×
606
                                    )),
607
                                }),
608
                            },
609
                            // @group(0) @binding(3) var<storage, read> spawner : Spawner;
610
                            BindGroupEntry {
×
611
                                binding: 3,
×
612
                                resource: BindingResource::Buffer(BufferBinding {
×
613
                                    buffer: spawner_buffer,
×
614
                                    offset: 0,
×
615
                                    size: Some(GpuSpawnerParams::aligned_size(
×
616
                                        self.render_device
×
617
                                            .limits()
×
618
                                            .min_storage_buffer_offset_alignment,
×
619
                                    )),
620
                                }),
621
                            },
622
                        ],
623
                    ),
624
                )
625
            }
626
        };
627
        Ok(bind_group)
×
628
    }
629

630
    pub fn sort_copy_bind_group(
×
631
        &self,
632
        indirect_index: BufferId,
633
        effect_metadata: BufferId,
634
    ) -> Option<&BindGroup> {
635
        let key = SortCopyBindGroupKey {
636
            indirect_index,
637
            sort: self.sort_buffer.id(),
×
638
            effect_metadata,
639
        };
640
        self.sort_copy_bind_groups.get(&key)
×
641
    }
642
}
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