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

djeedai / bevy_hanabi / 22263909674

21 Feb 2026 08:36PM UTC coverage: 57.75% (-0.6%) from 58.351%
22263909674

push

github

web-flow
Clean-up bind groups with Bevy utils (#524)

Use the Bevy utils to shorten the bind group layout and bind group
creation and make the code more readable.

Rename all bind group GPU resources to `bg` and the layout ones to `bgl`
to make them shorter in debugger too.

108 of 181 new or added lines in 6 files covered. (59.67%)

4728 of 8187 relevant lines covered (57.75%)

197.44 hits per line

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

29.09
/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
            binding_types::{
10
                storage_buffer, storage_buffer_read_only, storage_buffer_read_only_sized,
11
                storage_buffer_sized,
12
            },
13
            BindGroup, BindGroupEntries, BindGroupLayoutDescriptor, BindGroupLayoutEntries, Buffer,
14
            BufferId, CachedComputePipelineId, CachedPipelineState, ComputePipelineDescriptor,
15
            PipelineCache, ShaderType,
16
        },
17
        renderer::RenderDevice,
18
    },
19
    shader::Shader,
20
    utils::default,
21
};
22
use bytemuck::{Pod, Zeroable};
23
use wgpu::{BufferBinding, BufferDescriptor, BufferUsages, CommandEncoder, ShaderStages};
24

25
use super::{gpu_buffer::GpuBuffer, GpuDispatchIndirectArgs, GpuEffectMetadata, StorageType};
26
use crate::{
27
    render::{GpuIndirectIndex, GpuSpawnerParams},
28
    Attribute, ParticleLayout,
29
};
30

31
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
32
struct SortFillBindGroupLayoutKey {
33
    particle_min_binding_size: NonZeroU32,
34
    particle_ribbon_id_offset: u32,
35
    particle_age_offset: u32,
36
}
37

38
impl SortFillBindGroupLayoutKey {
39
    pub fn from_particle_layout(particle_layout: &ParticleLayout) -> Result<Self, ()> {
×
40
        let particle_ribbon_id_offset = particle_layout
×
41
            .byte_offset(Attribute::RIBBON_ID)
×
42
            .ok_or(())?;
×
43
        let particle_age_offset = particle_layout.byte_offset(Attribute::AGE).ok_or(())?;
×
44
        let key = SortFillBindGroupLayoutKey {
45
            particle_min_binding_size: particle_layout.min_binding_size32(),
×
46
            particle_ribbon_id_offset,
47
            particle_age_offset,
48
        };
49
        Ok(key)
×
50
    }
51
}
52

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

60
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
61
struct SortCopyBindGroupKey {
62
    indirect_index: BufferId,
63
    sort: BufferId,
64
    effect_metadata: BufferId,
65
}
66

67
/// GPU representation of a single dual-key value pair, with the added buffer
68
/// count as prefix. This is mainly used for shorcuts in bindings, not directly
69
/// as a type.
70
#[repr(C)]
71
#[derive(Debug, Copy, Clone, Pod, Zeroable, ShaderType)]
72
struct GpuSortBufferSingleEntry {
73
    /// Number of key-value pairs to sort. This is the first element of the
74
    /// entire buffer.
75
    pub count: u32,
76
    /// Key for the first entry.
77
    pub key: u32,
78
    /// Secondary key for the first entry.
79
    pub key2: u32,
80
    /// Value for the first entry.
81
    pub value: u32,
82
}
83

84
#[derive(Resource)]
85
pub struct SortBindGroups {
86
    /// Render device.
87
    render_device: RenderDevice,
88
    /// Sort-fill pass compute shader.
89
    sort_fill_shader: Handle<Shader>,
90
    /// GPU buffer of key-value pairs to sort.
91
    sort_buffer: Buffer,
92
    /// GPU buffer containing the [`GpuDispatchIndirect`] structs for the
93
    /// sort-fill and sort passes.
94
    indirect_buffer: GpuBuffer<GpuDispatchIndirectArgs>,
95
    /// Bind group layouts for group #0 of the sort-fill compute pass.
96
    sort_fill_bind_group_layout_descs:
97
        HashMap<SortFillBindGroupLayoutKey, (BindGroupLayoutDescriptor, CachedComputePipelineId)>,
98
    /// Bind groups for group #0 of the sort-fill compute pass.
99
    sort_fill_bind_groups: HashMap<SortFillBindGroupKey, BindGroup>,
100
    /// Bind group layout descriptor for group #0 of the sort compute pass.
101
    sort_bind_group_layout_desc: BindGroupLayoutDescriptor,
102
    /// Bind group for group #0 of the sort compute pass.
103
    sort_bind_group: Option<BindGroup>,
104
    sort_copy_bind_group_layout_desc: BindGroupLayoutDescriptor,
105
    /// Pipeline for sort pass.
106
    sort_pipeline_id: CachedComputePipelineId,
107
    /// Pipeline for sort-copy pass.
108
    sort_copy_pipeline_id: CachedComputePipelineId,
109
    /// Bind groups for group #0 of the sort-copy compute pass.
110
    sort_copy_bind_groups: HashMap<SortCopyBindGroupKey, BindGroup>,
111
}
112

113
impl SortBindGroups {
114
    pub fn new(
3✔
115
        world: &mut World,
116
        sort_fill_shader: Handle<Shader>,
117
        sort_shader: Handle<Shader>,
118
        sort_copy_shader: Handle<Shader>,
119
    ) -> Self {
120
        let render_device = world.resource::<RenderDevice>();
9✔
121
        let pipeline_cache = world.resource::<PipelineCache>();
9✔
122

123
        let sort_buffer = render_device.create_buffer(&BufferDescriptor {
12✔
124
            label: Some("hanabi:buffer:sort:pairs"),
6✔
125
            size: 3 * 1024 * 1024,
3✔
126
            usage: BufferUsages::COPY_DST | BufferUsages::STORAGE,
3✔
127
            mapped_at_creation: false,
3✔
128
        });
129

130
        let indirect_buffer_size = 3 * 1024;
6✔
131
        let indirect_buffer = render_device.create_buffer(&BufferDescriptor {
12✔
132
            label: Some("hanabi:buffer:sort:indirect"),
6✔
133
            size: indirect_buffer_size,
3✔
134
            usage: BufferUsages::COPY_SRC
3✔
135
                | BufferUsages::COPY_DST
3✔
136
                | BufferUsages::STORAGE
3✔
137
                | BufferUsages::INDIRECT,
3✔
138
            mapped_at_creation: false,
3✔
139
        });
140
        let indirect_buffer = GpuBuffer::new_allocated(
141
            indirect_buffer,
3✔
142
            indirect_buffer_size as u32,
3✔
143
            Some("hanabi:buffer:sort:indirect".to_string()),
3✔
144
        );
145

146
        let sort_bind_group_layout_desc = BindGroupLayoutDescriptor::new(
147
            "hanabi:bgl:sort",
148
            &BindGroupLayoutEntries::single(
6✔
149
                ShaderStages::COMPUTE,
3✔
150
                storage_buffer_sized(false, Some(NonZeroU64::new(16).unwrap())), /* count + dual
6✔
151
                                                                                  * kv pair */
3✔
152
            ),
153
        );
154

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

165
        let alignment = render_device.limits().min_storage_buffer_offset_alignment;
6✔
166
        let sort_copy_bind_group_layout_desc = BindGroupLayoutDescriptor::new(
167
            "hanabi:bgl:sort_copy",
168
            &BindGroupLayoutEntries::sequential(
6✔
169
                ShaderStages::COMPUTE,
3✔
170
                (
171
                    // @group(0) @binding(0) var<storage, read_write> indirect_index_buffer :
172
                    // IndirectIndexBuffer;
173
                    storage_buffer::<GpuIndirectIndex>(false),
6✔
174
                    // @group(0) @binding(1) var<storage, read> sort_buffer : SortBuffer;
175
                    storage_buffer_read_only::<GpuSortBufferSingleEntry>(false),
6✔
176
                    // @group(0) @binding(2) var<storage, read_write> effect_metadata :
177
                    // EffectMetadata;
178
                    storage_buffer_sized(true, Some(GpuEffectMetadata::aligned_size(alignment))),
9✔
179
                    // @group(0) @binding(3) var<storage, read> spawner : Spawner;
180
                    storage_buffer_read_only_sized(
6✔
181
                        true,
3✔
182
                        Some(GpuSpawnerParams::aligned_size(alignment)),
3✔
183
                    ),
184
                ),
185
            ),
186
        );
187

188
        let sort_copy_pipeline_id =
3✔
189
            pipeline_cache.queue_compute_pipeline(ComputePipelineDescriptor {
9✔
190
                label: Some("hanabi:pipeline:sort_copy".into()),
6✔
191
                layout: vec![sort_copy_bind_group_layout_desc.clone()],
12✔
192
                shader: sort_copy_shader,
6✔
193
                shader_defs: vec![],
6✔
194
                entry_point: Some("main".into()),
3✔
195
                push_constant_ranges: vec![],
3✔
196
                zero_initialize_workgroup_memory: false,
3✔
197
            });
198

199
        Self {
200
            render_device: render_device.clone(),
9✔
201
            sort_fill_shader,
202
            sort_buffer,
203
            indirect_buffer,
204
            sort_fill_bind_group_layout_descs: default(),
6✔
205
            sort_fill_bind_groups: default(),
6✔
206
            sort_bind_group_layout_desc,
207
            // This bind group is created later, once the pipeline and its bind group layouts are
208
            // created by the pipeline cache. Technically we could create the bind group layout
209
            // immediately because the PipelineCache pretends to but actually creates them
210
            // on-the-fly in get_bind_group_layout(), but this is brittle as any behavior change
211
            // would break Hanabi. Instead we create this bind group alongside all others, which is
212
            // more consistent too.
213
            sort_bind_group: None,
214
            sort_copy_bind_group_layout_desc,
215
            sort_pipeline_id,
216
            sort_copy_pipeline_id,
217
            sort_copy_bind_groups: default(),
3✔
218
        }
219
    }
220

221
    #[inline]
222
    pub fn clear_indirect_dispatch_buffer(&mut self) {
330✔
223
        self.indirect_buffer.clear();
660✔
224
    }
225

226
    #[inline]
227
    pub fn allocate_indirect_dispatch(&mut self) -> u32 {
×
228
        self.indirect_buffer.allocate()
×
229
    }
230

231
    #[inline]
232
    pub fn get_indirect_dispatch_byte_offset(&self, index: u32) -> u32 {
×
233
        self.indirect_buffer.item_size() as u32 * index
×
234
    }
235

236
    #[inline]
237
    #[allow(dead_code)]
238
    pub fn sort_buffer(&self) -> &Buffer {
×
239
        &self.sort_buffer
×
240
    }
241

242
    #[inline]
243
    pub fn indirect_buffer(&self) -> Option<&Buffer> {
312✔
244
        self.indirect_buffer.buffer()
624✔
245
    }
246

247
    #[inline]
248
    pub fn sort_pipeline_id(&self) -> CachedComputePipelineId {
×
249
        self.sort_pipeline_id
×
250
    }
251

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

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

279
        // Validate the sort pipeline
280
        if !matches!(
×
281
            pipeline_cache.get_compute_pipeline_state(self.sort_pipeline_id()),
×
282
            CachedPipelineState::Ok(_)
283
        ) {
284
            return false;
×
285
        }
286

287
        // Validate the sort-copy pipeline
288
        if !matches!(
×
289
            pipeline_cache.get_compute_pipeline_state(self.get_sort_copy_pipeline_id()),
×
290
            CachedPipelineState::Ok(_)
291
        ) {
292
            return false;
×
293
        }
294

295
        true
×
296
    }
297

298
    #[inline]
299
    pub fn prepare_buffers(&mut self, render_device: &RenderDevice) {
330✔
300
        self.indirect_buffer.prepare_buffers(render_device);
990✔
301
    }
302

303
    #[inline]
304
    pub fn write_buffers(&self, command_encoder: &mut CommandEncoder) {
330✔
305
        self.indirect_buffer.write_buffers(command_encoder);
990✔
306
    }
307

308
    #[inline]
309
    pub fn clear_previous_frame_resizes(&mut self) {
330✔
310
        self.indirect_buffer.clear_previous_frame_resizes();
660✔
311
    }
312

313
    pub fn ensure_sort_fill_bind_group_layout_desc(
×
314
        &mut self,
315
        pipeline_cache: &PipelineCache,
316
        particle_layout: &ParticleLayout,
317
    ) -> Result<&BindGroupLayoutDescriptor, ()> {
318
        let key = SortFillBindGroupLayoutKey::from_particle_layout(particle_layout)?;
×
319
        let (layout, _) = self
×
320
            .sort_fill_bind_group_layout_descs
×
321
            .entry(key)
×
322
            .or_insert_with(|| {
×
323
                let alignment = self
×
324
                    .render_device
×
325
                    .limits()
×
326
                    .min_storage_buffer_offset_alignment;
×
327
                let bind_group_layout_desc = BindGroupLayoutDescriptor::new(
×
328
                    "hanabi:bgl:sort_fill",
NEW
329
                    &BindGroupLayoutEntries::sequential(
×
NEW
330
                        ShaderStages::COMPUTE,
×
331
                        (
332
                            // @group(0) @binding(0) var<storage, read_write> sort_buffer :
333
                            // SortBuffer;
NEW
334
                            storage_buffer::<GpuSortBufferSingleEntry>(false),
×
335
                            // @group(0) @binding(1) var<storage, read> particle_buffer :
336
                            // RawParticleBuffer;
NEW
337
                            storage_buffer_read_only_sized(
×
NEW
338
                                false,
×
NEW
339
                                Some(key.particle_min_binding_size.into()),
×
340
                            ),
341
                            // @group(0) @binding(2) var<storage, read> indirect_index_buffer :
342
                            // array<u32>;
NEW
343
                            storage_buffer_read_only::<GpuIndirectIndex>(false),
×
344
                            // @group(0) @binding(3) var<storage, read_write> effect_metadata :
345
                            // EffectMetadata;
NEW
346
                            storage_buffer_sized(
×
NEW
347
                                true,
×
NEW
348
                                Some(GpuEffectMetadata::aligned_size(alignment)),
×
349
                            ),
350
                            // @group(0) @binding(4) var<storage, read> spawner : Spawner;
NEW
351
                            storage_buffer_read_only_sized(
×
NEW
352
                                true,
×
NEW
353
                                Some(GpuSpawnerParams::aligned_size(alignment)),
×
354
                            ),
355
                        ),
356
                    ),
357
                );
358
                let pipeline_id =
×
359
                    pipeline_cache.queue_compute_pipeline(ComputePipelineDescriptor {
×
360
                        label: Some("hanabi:pipeline:sort_fill".into()),
×
361
                        layout: vec![bind_group_layout_desc.clone()],
×
362
                        shader: self.sort_fill_shader.clone(),
×
363
                        shader_defs: vec!["HAS_DUAL_KEY".into()],
×
364
                        entry_point: Some("main".into()),
×
365
                        push_constant_ranges: vec![],
×
366
                        zero_initialize_workgroup_memory: false,
×
367
                    });
368
                (bind_group_layout_desc, pipeline_id)
×
369
            });
370
        Ok(layout)
×
371
    }
372

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

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

396
    pub fn get_sort_copy_pipeline_id(&self) -> CachedComputePipelineId {
×
397
        self.sort_copy_pipeline_id
×
398
    }
399

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

471
    pub fn sort_fill_bind_group(
×
472
        &self,
473
        particle: BufferId,
474
        indirect_index: BufferId,
475
        effect_metadata: BufferId,
476
    ) -> Option<&BindGroup> {
477
        let key = SortFillBindGroupKey {
478
            particle,
479
            indirect_index,
480
            effect_metadata,
481
        };
482
        self.sort_fill_bind_groups.get(&key)
×
483
    }
484

485
    /// Ensure the bind group for the sort pass is created.
486
    pub fn ensure_sort_bind_group(
×
487
        &mut self,
488
        pipeline_cache: &PipelineCache,
489
    ) -> Result<&BindGroup, ()> {
490
        if self.sort_bind_group.is_none() {
×
491
            let sort_bind_group = self.render_device.create_bind_group(
×
492
                "hanabi:bg:sort",
493
                &pipeline_cache.get_bind_group_layout(&self.sort_bind_group_layout_desc),
×
494
                // @group(0) @binding(0) var<storage, read_write> pairs : array<KeyValuePair>;
NEW
495
                &BindGroupEntries::single(self.sort_buffer.as_entire_binding()),
×
496
            );
497
            self.sort_bind_group = Some(sort_bind_group);
×
498
        }
499
        Ok(self.sort_bind_group.as_ref().unwrap())
×
500
    }
501

502
    #[inline]
503
    pub fn sort_bind_group(&self) -> Option<&BindGroup> {
×
504
        self.sort_bind_group.as_ref()
×
505
    }
506

507
    pub fn ensure_sort_copy_bind_group(
×
508
        &mut self,
509
        indirect_index_buffer: &Buffer,
510
        effect_metadata_buffer: &Buffer,
511
        spawner_buffer: &Buffer,
512
        pipeline_cache: &PipelineCache,
513
    ) -> Result<&BindGroup, ()> {
514
        let key = SortCopyBindGroupKey {
515
            indirect_index: indirect_index_buffer.id(),
×
516
            sort: self.sort_buffer.id(),
×
517
            effect_metadata: effect_metadata_buffer.id(),
×
518
        };
519
        let entry = self.sort_copy_bind_groups.entry(key);
×
520
        let bind_group = match entry {
×
521
            Entry::Occupied(entry) => entry.into_mut(),
×
522
            Entry::Vacant(entry) => {
×
523
                entry.insert(
×
524
                    self.render_device.create_bind_group(
×
NEW
525
                        "hanabi:bg:sort_copy",
×
526
                        &pipeline_cache
×
527
                            .get_bind_group_layout(&self.sort_copy_bind_group_layout_desc),
×
NEW
528
                        &BindGroupEntries::sequential((
×
529
                            // @group(0) @binding(0) var<storage, read_write> indirect_index_buffer
530
                            // : IndirectIndexBuffer;
NEW
531
                            indirect_index_buffer.as_entire_binding(),
×
532
                            // @group(0) @binding(1) var<storage, read> sort_buffer : SortBuffer;
NEW
533
                            self.sort_buffer.as_entire_binding(),
×
534
                            // @group(0) @binding(2) var<storage, read> effect_metadata :
535
                            // EffectMetadata;
NEW
536
                            BufferBinding {
×
NEW
537
                                buffer: effect_metadata_buffer,
×
NEW
538
                                offset: 0,
×
NEW
539
                                size: Some(GpuEffectMetadata::aligned_size(
×
NEW
540
                                    self.render_device
×
NEW
541
                                        .limits()
×
NEW
542
                                        .min_storage_buffer_offset_alignment,
×
543
                                )),
544
                            },
545
                            // @group(0) @binding(3) var<storage, read> spawner : Spawner;
NEW
546
                            BufferBinding {
×
NEW
547
                                buffer: spawner_buffer,
×
NEW
548
                                offset: 0,
×
NEW
549
                                size: Some(GpuSpawnerParams::aligned_size(
×
NEW
550
                                    self.render_device
×
NEW
551
                                        .limits()
×
NEW
552
                                        .min_storage_buffer_offset_alignment,
×
553
                                )),
554
                            },
555
                        )),
556
                    ),
557
                )
558
            }
559
        };
560
        Ok(bind_group)
×
561
    }
562

563
    pub fn sort_copy_bind_group(
×
564
        &self,
565
        indirect_index: BufferId,
566
        effect_metadata: BufferId,
567
    ) -> Option<&BindGroup> {
568
        let key = SortCopyBindGroupKey {
569
            indirect_index,
570
            sort: self.sort_buffer.id(),
×
571
            effect_metadata,
572
        };
573
        self.sort_copy_bind_groups.get(&key)
×
574
    }
575
}
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