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

djeedai / bevy_hanabi / 28973967326

08 Jul 2026 08:36PM UTC coverage: 59.296% (+0.7%) from 58.625%
28973967326

Pull #547

github

web-flow
Merge 5f5c70ea4 into cf35d53d5
Pull Request #547: Batch compatible effect instances in init/update passes

291 of 500 new or added lines in 7 files covered. (58.2%)

11 existing lines in 3 files now uncovered.

5422 of 9144 relevant lines covered (59.3%)

685.76 hits per line

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

29.41
/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, ShaderSize, 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
    // Bound at binding 4 with a per-effect dynamic offset; reallocates as
59
    // effects grow, so it must be keyed or a stale buffer overruns the offset.
60
    spawner: BufferId,
61
}
62

63
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
64
struct SortCopyBindGroupKey {
65
    indirect_index: BufferId,
66
    sort: BufferId,
67
    effect_metadata: BufferId,
68
    // Bound at binding 3 with a per-effect dynamic offset; reallocates as
69
    // effects grow, so it must be keyed or a stale buffer overruns the offset.
70
    spawner: BufferId,
71
}
72

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

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

117
impl SortBindGroups {
118
    pub fn new(
4✔
119
        world: &mut World,
120
        sort_fill_shader: Handle<Shader>,
121
        sort_shader: Handle<Shader>,
122
        sort_copy_shader: Handle<Shader>,
123
    ) -> Self {
124
        let render_device = world.resource::<RenderDevice>();
12✔
125
        let pipeline_cache = world.resource::<PipelineCache>();
12✔
126

127
        let sort_buffer = render_device.create_buffer(&BufferDescriptor {
16✔
128
            label: Some("hanabi:buffer:sort:pairs"),
8✔
129
            size: 3 * 1024 * 1024,
4✔
130
            usage: BufferUsages::COPY_DST | BufferUsages::STORAGE,
4✔
131
            mapped_at_creation: false,
4✔
132
        });
133

134
        // Initial room for 256 ribbon sort dispatches; the GpuBuffer grows on demand.
135
        let indirect_item_size = GpuDispatchIndirectArgs::SHADER_SIZE.get();
8✔
136
        let indirect_buffer = render_device.create_buffer(&BufferDescriptor {
16✔
137
            label: Some("hanabi:buffer:sort:indirect"),
8✔
138
            size: 256 * indirect_item_size,
4✔
139
            usage: BufferUsages::COPY_SRC
4✔
140
                | BufferUsages::COPY_DST
4✔
141
                | BufferUsages::STORAGE
4✔
142
                | BufferUsages::INDIRECT,
4✔
143
            mapped_at_creation: false,
4✔
144
        });
145
        let indirect_buffer = GpuBuffer::new_allocated(
146
            indirect_buffer,
4✔
147
            Some("hanabi:buffer:sort:indirect".to_string()),
4✔
148
        );
149

150
        let sort_bind_group_layout_desc = BindGroupLayoutDescriptor::new(
151
            "hanabi:bgl:sort",
152
            &BindGroupLayoutEntries::single(
8✔
153
                ShaderStages::COMPUTE,
4✔
154
                storage_buffer_sized(false, Some(NonZeroU64::new(16).unwrap())), /* count + dual
8✔
155
                                                                                  * kv pair */
4✔
156
            ),
157
        );
158

159
        let sort_pipeline_id = pipeline_cache.queue_compute_pipeline(ComputePipelineDescriptor {
16✔
160
            label: Some("hanabi:pipeline:sort".into()),
8✔
161
            layout: vec![sort_bind_group_layout_desc.clone()],
16✔
162
            shader: sort_shader,
8✔
163
            shader_defs: vec!["HAS_DUAL_KEY".into()],
16✔
164
            entry_point: Some("main".into()),
4✔
165
            immediate_size: 0,
4✔
166
            zero_initialize_workgroup_memory: false,
4✔
167
        });
168

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

192
        let sort_copy_pipeline_id =
4✔
193
            pipeline_cache.queue_compute_pipeline(ComputePipelineDescriptor {
12✔
194
                label: Some("hanabi:pipeline:sort_copy".into()),
8✔
195
                layout: vec![sort_copy_bind_group_layout_desc.clone()],
16✔
196
                shader: sort_copy_shader,
8✔
197
                shader_defs: vec![],
8✔
198
                entry_point: Some("main".into()),
4✔
199
                immediate_size: 0,
4✔
200
                zero_initialize_workgroup_memory: false,
4✔
201
            });
202

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

224
    #[inline]
225
    pub fn clear_indirect_dispatch_buffer(&mut self) {
570✔
226
        self.indirect_buffer.clear();
1,140✔
227
    }
228

229
    #[inline]
230
    pub fn allocate_indirect_dispatch(&mut self) -> u32 {
×
231
        self.indirect_buffer.allocate()
×
232
    }
233

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

239
    #[inline]
240
    #[allow(dead_code)]
241
    pub fn sort_buffer(&self) -> &Buffer {
×
242
        &self.sort_buffer
×
243
    }
244

245
    #[inline]
246
    pub fn indirect_buffer(&self) -> Option<&Buffer> {
×
247
        self.indirect_buffer.buffer()
×
248
    }
249

250
    #[inline]
251
    pub fn sort_pipeline_id(&self) -> CachedComputePipelineId {
×
252
        self.sort_pipeline_id
×
253
    }
254

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

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

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

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

298
        true
×
299
    }
300

301
    #[inline]
302
    pub fn prepare_buffers(&mut self, render_device: &RenderDevice) {
570✔
303
        self.indirect_buffer.prepare_buffers(render_device);
1,710✔
304
    }
305

306
    #[inline]
307
    pub fn write_buffers(&self, command_encoder: &mut CommandEncoder) {
570✔
308
        self.indirect_buffer.write_buffers(command_encoder);
1,710✔
309
    }
310

311
    #[inline]
312
    pub fn clear_previous_frame_resizes(&mut self) {
570✔
313
        self.indirect_buffer.clear_previous_frame_resizes();
1,140✔
314
    }
315

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

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

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

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

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

465
    pub fn sort_fill_bind_group(
×
466
        &self,
467
        particle: BufferId,
468
        indirect_index: BufferId,
469
        effect_metadata: BufferId,
470
        spawner: BufferId,
471
    ) -> Option<&BindGroup> {
472
        let key = SortFillBindGroupKey {
473
            particle,
474
            indirect_index,
475
            effect_metadata,
476
            spawner,
477
        };
478
        self.sort_fill_bind_groups.get(&key)
×
479
    }
480

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

499
    #[inline]
500
    pub fn sort_bind_group(&self) -> Option<&BindGroup> {
×
501
        self.sort_bind_group.as_ref()
×
502
    }
503

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

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