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

djeedai / bevy_hanabi / 30177227450

25 Jul 2026 10:15PM UTC coverage: 58.445% (-0.7%) from 59.154%
30177227450

push

github

web-flow
Pack `EffectMetadata` and bind as array (#553)

Bind the `EffectMetadata` struct describing the per-effect-instance data
as an array. This allows compacting the struct and avoiding
device-mandated padding. This also greatly simplifies the `vfx_indirect`
shader which know can use the typed struct instead of manipulating
type-erased `u32`/`f32` values.

Fix missing GPU thread capping in CPU-based init and in update passes,
to early out when the thread has no particle to process, instead of
doing a useless binary search. GPU-based init continues to do the binary
search, which is anyway currently operating on a single-element array
(no GPU event batching).

Update the `instancing` example to use properties per effect, to prove
property batching works.

139 of 320 new or added lines in 7 files covered. (43.44%)

41 existing lines in 2 files now uncovered.

5426 of 9284 relevant lines covered (58.44%)

678.76 hits per line

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

31.56
/src/plugin.rs
1
use std::borrow::Cow;
2

3
#[cfg(feature = "2d")]
4
use bevy::core_pipeline::core_2d::Transparent2d;
5
#[cfg(feature = "3d")]
6
use bevy::core_pipeline::core_3d::{AlphaMask3d, Opaque3d, Transparent3d};
7
use bevy::{
8
    asset::uuid_handle,
9
    camera::visibility::VisibilitySystems,
10
    prelude::*,
11
    render::{
12
        extract_component::ExtractComponentPlugin,
13
        render_asset::prepare_assets,
14
        render_phase::DrawFunctions,
15
        render_resource::{SpecializedComputePipelines, SpecializedRenderPipelines},
16
        renderer::{RenderAdapterInfo, RenderDevice},
17
        texture::GpuImage,
18
        view::prepare_view_uniforms,
19
        Render, RenderApp, RenderSystems,
20
    },
21
    time::{time_system, TimeSystems},
22
};
23

24
use crate::asset::EffectAssetLoader;
25
use crate::{
26
    asset::{DefaultMesh, EffectAsset},
27
    compile_effects,
28
    properties::EffectProperties,
29
    register_modifiers,
30
    render::{
31
        allocate_effects, allocate_events, allocate_metadata, allocate_parent_child_infos,
32
        allocate_properties, batch_effects, clear_previous_frame_resizes,
33
        clear_transient_batch_inputs, extract_effect_events, extract_effects, extract_sim_params,
34
        fixup_parents, on_remove_cached_draw_indirect_args, on_remove_cached_effect,
35
        on_remove_cached_effect_events, on_remove_cached_metadata, on_remove_cached_properties,
36
        prepare_batch_inputs, prepare_bind_groups, prepare_effect_metadata, prepare_gpu_resources,
37
        prepare_indirect_pipeline, prepare_init_update_pipelines, prepare_property_buffers,
38
        propagate_ready_state, queue_effects, queue_init_fill_dispatch_ops,
39
        queue_init_indirect_workgroup_update, queue_sort_fill_dispatch_ops, report_ready_state,
40
        start_stop_gpu_debug_capture, update_mesh_locations, Batcher, DebugSettings,
41
        DispatchIndirectPipeline, DrawEffects, EffectAssetEvents, EffectBindGroups, EffectCache,
42
        EffectsMeta, EventCache, GpuBatchInfo, GpuBufferOperations, GpuSpawnerParams,
43
        HanabiRenderPlugin, InitFillDispatchQueue, ParticlesInitPipeline, ParticlesRenderPipeline,
44
        ParticlesUpdatePipeline, PrefixSumPipeline, PropertyBindGroups, PropertyCache,
45
        RenderDebugSettings, ShaderCache, SimParams, SortBindGroups, SortFillDispatchQueue,
46
        StorageType as _, UtilsPipeline,
47
    },
48
    spawn::{self, Random},
49
    tick_spawners,
50
    time::effect_simulation_time_system,
51
    update_properties_from_asset, EffectSimulation, EffectVisibilityClass, ParticleEffect,
52
    SpawnerSettings,
53
};
54

55
/// Source code for the `vfx_sort` compute shader.
56
pub(crate) const VFX_SORT_WGSL: Cow<'static, str> =
57
    Cow::Borrowed(include_str!("render/vfx_sort.wgsl"));
58

59
/// Labels for the Hanabi systems.
60
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, SystemSet)]
61
pub enum EffectSystems {
62
    /// Tick all effect instances to generate particle spawn counts.
63
    ///
64
    /// This system runs during the [`PostUpdate`] schedule. Any system which
65
    /// modifies an effect spawner should run before this set to ensure the
66
    /// spawner takes into account the newly set values during its ticking.
67
    TickSpawners,
68

69
    /// Compile the effect instances, updating the [`CompiledParticleEffect`]
70
    /// components.
71
    ///
72
    /// This system runs during the [`PostUpdate`] schedule. This is largely an
73
    /// internal task which can be ignored by most users.
74
    ///
75
    /// [`CompiledParticleEffect`]: crate::CompiledParticleEffect
76
    CompileEffects,
77

78
    /// Update the properties of the effect instance based on the declared
79
    /// properties in the [`EffectAsset`], updating the associated
80
    /// [`EffectProperties`] component.
81
    ///
82
    /// This system runs during the [`PostUpdate`] schedule, after the assets
83
    /// have been updated. Any system which modifies an [`EffectAsset`]'s
84
    /// declared properties should run before this set in order for changes to
85
    /// be taken into account in the same frame.
86
    UpdatePropertiesFromAsset,
87

88
    /// Prepare effect assets for the extracted effects.
89
    ///
90
    /// Part of Bevy's own [`RenderSystems::PrepareAssets`].
91
    PrepareEffectAssets,
92

93
    /// Queue the GPU commands for the extracted effects.
94
    ///
95
    /// Part of Bevy's own [`RenderSystems::Queue`].
96
    QueueEffects,
97

98
    /// Prepare GPU data for the queued effects.
99
    ///
100
    /// Part of Bevy's own [`RenderSystems::PrepareResources`].
101
    PrepareEffectGpuResources,
102

103
    /// Prepare the GPU bind groups once all buffers have been (re-)allocated
104
    /// and won't change this frame.
105
    ///
106
    /// Part of Bevy's own [`RenderSystems::PrepareBindGroups`].
107
    PrepareBindGroups,
108
}
109

110
const HANABI_COMMON_TEMPLATE_HANDLE: Handle<Shader> =
111
    uuid_handle!("626E7AD3-4E54-487E-B796-9A90E34CC1EC");
112

113
/// Plugin to add systems related to Hanabi.
114
#[derive(Debug, Clone, Copy)]
115
pub struct HanabiPlugin;
116

117
impl HanabiPlugin {
118
    /// Create the `vfx_common.wgsl` shader with proper alignment.
119
    ///
120
    /// This creates a new [`Shader`] from the `vfx_common.wgsl` template file,
121
    /// by applying the given alignment for storage buffers. This produces a
122
    /// shader ready for the specific GPU device associated with that
123
    /// alignment.
124
    pub(crate) fn make_common_shader(min_storage_buffer_offset_alignment: u32) -> Shader {
7✔
125
        let spawner_padding_code =
7✔
126
            GpuSpawnerParams::padding_code(min_storage_buffer_offset_alignment);
14✔
127
        let batch_info_padding_code =
7✔
128
            GpuBatchInfo::padding_code(min_storage_buffer_offset_alignment);
14✔
129
        let common_code = include_str!("render/vfx_common.wgsl")
28✔
130
            .replace("{{SPAWNER_PADDING}}", &spawner_padding_code)
14✔
131
            .replace("{{BATCH_INFO_PADDING}}", &batch_info_padding_code);
7✔
132
        Shader::from_wgsl(
133
            common_code,
7✔
134
            std::path::Path::new(file!())
21✔
135
                .parent()
14✔
136
                .unwrap()
14✔
137
                .join(format!(
7✔
138
                    "render/vfx_common_{}.wgsl",
7✔
139
                    min_storage_buffer_offset_alignment
7✔
140
                ))
141
                .to_string_lossy(),
7✔
142
        )
143
    }
144

145
    /// Create the `vfx_indirect.wgsl` shader with proper alignment.
146
    ///
147
    /// This creates a new [`Shader`] from the `vfx_indirect.wgsl` template
148
    /// file, by applying the given alignment for storage buffers. This
149
    /// produces a shader ready for the specific GPU device associated with
150
    /// that alignment.
151
    pub(crate) fn make_indirect_shader(
8✔
152
        min_storage_buffer_offset_alignment: u32,
153
        has_events: bool,
154
    ) -> Shader {
155
        let indirect_code = include_str!("render/vfx_indirect.wgsl");
16✔
156
        Shader::from_wgsl(
157
            indirect_code,
8✔
158
            std::path::Path::new(file!())
16✔
159
                .parent()
8✔
160
                .unwrap()
8✔
161
                .join(format!(
16✔
162
                    "render/vfx_indirect_{}_{}.wgsl",
163
                    min_storage_buffer_offset_alignment,
164
                    if has_events { "events" } else { "noevent" },
16✔
165
                ))
166
                .to_string_lossy(),
8✔
167
        )
168
    }
169

170
    /// Create the `vfx_prefix_sum.wgsl` shader.
171
    ///
172
    /// This creates a new [`Shader`] from the `vfx_prefix_sum.wgsl` template
173
    /// file.
174
    pub(crate) fn make_prefix_sum_shader() -> Shader {
4✔
175
        let prefix_sum_code = include_str!("render/vfx_prefix_sum.wgsl");
8✔
176
        Shader::from_wgsl(
177
            prefix_sum_code,
4✔
178
            std::path::Path::new(file!())
8✔
179
                .parent()
4✔
180
                .unwrap()
4✔
181
                .join("render/vfx_prefix_sum.wgsl")
4✔
182
                .to_string_lossy(),
4✔
183
        )
184
    }
185
}
186

187
impl Plugin for HanabiPlugin {
188
    fn build(&self, app: &mut App) {
4✔
189
        // Register modifiers
190
        {
191
            let type_registry = app.world().resource::<AppTypeRegistry>();
16✔
192
            register_modifiers(type_registry);
4✔
193
        }
194

195
        // Register asset
196
        app.init_asset::<EffectAsset>()
28✔
197
            .insert_resource(Random(spawn::new_rng()))
24✔
198
            .add_plugins(HanabiRenderPlugin)
20✔
199
            .add_plugins(ExtractComponentPlugin::<EffectVisibilityClass>::default())
16✔
200
            .init_resource::<DefaultMesh>()
201
            .init_resource::<ShaderCache>()
202
            .init_resource::<DebugSettings>()
203
            .init_resource::<Time<EffectSimulation>>()
204
            .configure_sets(
205
                PostUpdate,
16✔
206
                (
207
                    EffectSystems::TickSpawners
16✔
208
                        // This checks the visibility to skip work, so needs to run after
209
                        // ComputedVisibility was updated.
210
                        .after(VisibilitySystems::VisibilityPropagate),
16✔
211
                    EffectSystems::CompileEffects,
12✔
212
                ),
213
            )
214
            .configure_sets(
215
                PreUpdate,
12✔
216
                EffectSystems::UpdatePropertiesFromAsset.after(bevy::asset::AssetTrackingSystems),
12✔
217
            )
218
            .add_systems(
219
                First,
8✔
220
                effect_simulation_time_system
4✔
221
                    .after(time_system)
8✔
222
                    .in_set(TimeSystems),
4✔
223
            )
224
            .add_systems(
225
                PostUpdate,
4✔
226
                (
227
                    tick_spawners.in_set(EffectSystems::TickSpawners),
12✔
228
                    compile_effects.in_set(EffectSystems::CompileEffects),
12✔
229
                    update_properties_from_asset.in_set(EffectSystems::UpdatePropertiesFromAsset),
4✔
230
                ),
231
            );
232

233
        app.init_asset_loader::<EffectAssetLoader>();
8✔
234

235
        // Register types with reflection
236
        app.register_type::<EffectAsset>()
4✔
237
            .register_type::<ParticleEffect>()
238
            .register_type::<EffectProperties>()
239
            .register_type::<SpawnerSettings>()
240
            .register_type::<Time<EffectSimulation>>();
241
    }
242

243
    fn finish(&self, app: &mut App) {
4✔
244
        let render_device = app
12✔
245
            .sub_app(RenderApp)
4✔
246
            .world()
247
            .resource::<RenderDevice>()
248
            .clone();
249

250
        let adapter_name = app
8✔
251
            .world()
252
            .get_resource::<RenderAdapterInfo>()
253
            .map(|ai| &ai.name[..])
8✔
254
            .unwrap_or("<unknown>");
255

256
        // Check device limits
257
        let limits = render_device.limits();
12✔
258
        if limits.max_bind_groups < 4 {
4✔
259
            error!("Hanabi requires a GPU device supporting at least 4 bind groups (Limits::max_bind_groups).\n  Current adapter: {}\n  Supported bind groups: {}", adapter_name, limits.max_bind_groups);
×
260
            return;
×
261
        } else {
262
            info!("Initializing Hanabi for GPU adapter {}", adapter_name);
4✔
263
        }
264

265
        // Insert the properly aligned `vfx_common.wgsl` shader into Assets<Shader>, so
266
        // that the automated Bevy shader processing finds it as an import. This is used
267
        // for init/update/render shaders (but not the indirect one).
268
        {
269
            let common_shader = HanabiPlugin::make_common_shader(
270
                render_device.limits().min_storage_buffer_offset_alignment,
×
271
            );
272
            let mut assets = app.world_mut().resource_mut::<Assets<Shader>>();
×
273
            assets
×
274
                .insert(&HANABI_COMMON_TEMPLATE_HANDLE, common_shader)
×
275
                .unwrap();
276
        }
277

278
        // Insert the two variants of the properly aligned `vfx_indirect.wgsl` shaders
279
        // into Assets<Shader>.
280
        let (
281
            indirect_shader_noevent,
×
282
            indirect_shader_events,
×
283
            prefix_sum_shader,
×
284
            sort_fill_shader,
×
285
            sort_shader,
×
286
            sort_copy_shader,
×
287
        ) = {
×
288
            let align = render_device.limits().min_storage_buffer_offset_alignment;
×
289
            let indirect_shader_noevent = HanabiPlugin::make_indirect_shader(align, false);
×
290
            let indirect_shader_events = HanabiPlugin::make_indirect_shader(align, true);
×
291
            let prefix_sum_shader = HanabiPlugin::make_prefix_sum_shader();
×
292
            let sort_fill_shader = Shader::from_wgsl(
293
                include_str!("render/vfx_sort_fill.wgsl"),
×
294
                std::path::Path::new(file!())
×
295
                    .parent()
×
296
                    .unwrap()
×
297
                    .join("render/vfx_sort_fill.wgsl")
×
298
                    .to_string_lossy(),
×
299
            );
300
            let sort_shader = Shader::from_wgsl(
301
                VFX_SORT_WGSL,
×
302
                std::path::Path::new(file!())
×
303
                    .parent()
×
304
                    .unwrap()
×
305
                    .join("render/vfx_sort.wgsl")
×
306
                    .to_string_lossy(),
×
307
            );
308
            let sort_copy_shader = Shader::from_wgsl(
309
                include_str!("render/vfx_sort_copy.wgsl"),
×
310
                std::path::Path::new(file!())
×
311
                    .parent()
×
312
                    .unwrap()
×
313
                    .join("render/vfx_sort_copy.wgsl")
×
314
                    .to_string_lossy(),
×
315
            );
316

317
            let mut assets = app.world_mut().resource_mut::<Assets<Shader>>();
×
318
            let indirect_shader_noevent = assets.add(indirect_shader_noevent);
×
319
            let indirect_shader_events = assets.add(indirect_shader_events);
×
320
            let prefix_sum_shader = assets.add(prefix_sum_shader);
×
321
            let sort_fill_shader = assets.add(sort_fill_shader);
×
322
            let sort_shader = assets.add(sort_shader);
×
323
            let sort_copy_shader = assets.add(sort_copy_shader);
×
324

325
            (
326
                indirect_shader_noevent,
×
327
                indirect_shader_events,
×
328
                prefix_sum_shader,
×
329
                sort_fill_shader,
×
330
                sort_shader,
×
331
                sort_copy_shader,
×
332
            )
333
        };
334

335
        let effects_meta = EffectsMeta::new(
336
            render_device.clone(),
×
337
            indirect_shader_noevent,
×
338
            indirect_shader_events,
×
339
            prefix_sum_shader,
×
340
        );
341

342
        let effect_cache = EffectCache::new(render_device.clone());
×
343
        let property_cache = PropertyCache::new(&render_device);
×
344
        let event_cache = EventCache::new(render_device);
×
345

346
        let render_app = app.sub_app_mut(RenderApp);
×
347
        let sort_bind_groups = SortBindGroups::new(
348
            render_app.world_mut(),
×
349
            sort_fill_shader,
×
350
            sort_shader,
×
351
            sort_copy_shader,
×
352
        );
353

354
        // Register the custom render pipeline
355
        render_app
×
356
            .insert_resource(effects_meta)
×
357
            .insert_resource(effect_cache)
×
358
            .insert_resource(property_cache)
×
359
            .insert_resource(event_cache)
×
360
            .init_resource::<RenderDebugSettings>()
361
            .init_resource::<EffectBindGroups>()
362
            .init_resource::<PropertyBindGroups>()
363
            .init_resource::<InitFillDispatchQueue>()
364
            .init_resource::<SortFillDispatchQueue>()
365
            .insert_resource(sort_bind_groups)
×
366
            .init_resource::<UtilsPipeline>()
367
            .init_resource::<GpuBufferOperations>()
368
            .init_resource::<PrefixSumPipeline>()
369
            .init_resource::<DispatchIndirectPipeline>()
370
            .init_resource::<SpecializedComputePipelines<DispatchIndirectPipeline>>()
371
            .init_resource::<ParticlesInitPipeline>()
372
            .init_resource::<SpecializedComputePipelines<ParticlesInitPipeline>>()
373
            .init_resource::<ParticlesInitPipeline>()
374
            .init_resource::<SpecializedComputePipelines<ParticlesInitPipeline>>()
375
            .init_resource::<ParticlesUpdatePipeline>()
376
            .init_resource::<SpecializedComputePipelines<ParticlesUpdatePipeline>>()
377
            .init_resource::<ParticlesRenderPipeline>()
378
            .init_resource::<SpecializedRenderPipelines<ParticlesRenderPipeline>>()
379
            .init_resource::<EffectAssetEvents>()
380
            .init_resource::<SimParams>()
381
            .init_resource::<Batcher>()
382
            .configure_sets(
383
                Render,
×
384
                (
385
                    EffectSystems::PrepareEffectAssets.in_set(RenderSystems::PrepareAssets),
×
386
                    EffectSystems::QueueEffects.in_set(RenderSystems::Queue),
×
387
                    EffectSystems::PrepareEffectGpuResources
×
388
                        .in_set(RenderSystems::PrepareResources),
×
389
                    EffectSystems::PrepareBindGroups.in_set(RenderSystems::PrepareBindGroups),
×
390
                ),
391
            )
392
            .edit_schedule(ExtractSchedule, |schedule| {
4✔
393
                schedule.add_systems((
12✔
394
                    start_stop_gpu_debug_capture,
4✔
395
                    report_ready_state.before(extract_effects),
4✔
396
                    extract_effects,
4✔
397
                    extract_sim_params,
4✔
398
                    extract_effect_events,
4✔
399
                ));
400
            })
401
            .add_systems(
402
                Render,
×
403
                (
404
                    (
405
                        // Do all clears from previous frame; they can run in parallel as they
406
                        // clear different resources.
407
                        (clear_transient_batch_inputs, clear_previous_frame_resizes),
×
408
                        // Allocate GPU resources depending only on the extracted data; they can
409
                        // run in parallel as they touch different components.
410
                        (
411
                            // Allocate GPU storage for the effect particles
412
                            allocate_effects,
×
413
                            // Allocate GPU storage for GPU events (for child effects)
414
                            allocate_events,
×
415
                            // Allocate GPU storage for properties
416
                            allocate_properties,
×
417
                            // Update draw indirect args if Bevy relocated a render mesh
418
                            update_mesh_locations
×
419
                                // Need Bevy to have allocated the mesh in the MeshAllocator
420
                                .after(bevy::render::mesh::allocator::allocate_and_free_meshes)
×
421
                                // Need Bevy to have prepared the RenderMesh to read it
422
                                .after(prepare_assets::<bevy::render::mesh::RenderMesh>),
×
423
                            // Allocate GPU effect metadata
424
                            allocate_metadata,
×
425
                        ),
426
                        // Allocate parent and child infos. Those need all effects allocated and
427
                        // all parents resolved first, as well as event buffers allocated.
428
                        allocate_parent_child_infos
×
429
                            // Need the effects allocated to fetch the parent's slab ID
430
                            .after(allocate_effects)
×
431
                            // Need the events allocated to fetch the event buffer of children
432
                            .after(allocate_events),
×
433
                        fixup_parents
×
434
                            // Second pass fixup after allocate_parent_child_infos()
435
                            .after(allocate_parent_child_infos),
×
436
                        // Prepare pipelines; they can run in parallel as they touch different
437
                        // resources.
438
                        (
439
                            // Resolve the init and update pipelines, queue them if needed, and
440
                            // check their state to determine if the
441
                            // effect can be used this frame.
442
                            prepare_init_update_pipelines
×
443
                                // Need the bind group layout for the effect itself, which depends
444
                                // on the particle layout.
445
                                .after(allocate_effects)
×
446
                                // Need the bind group layout for properties, which depends on the
447
                                // property layout.
448
                                .after(allocate_properties)
×
449
                                // Need the number of event buffers to bind
450
                                .after(fixup_parents),
×
451
                            // Prepare the indirect pipeline depending on whether there's any child
452
                            // info.
453
                            prepare_indirect_pipeline
×
454
                                // Need to know if any GPU event using effect is active or not
455
                                .after(allocate_events),
×
456
                        ),
457
                        propagate_ready_state
×
458
                            // Need the ready state of parents, which depends on the init/update
459
                            // pipeline states
460
                            .after(prepare_init_update_pipelines),
×
461
                        prepare_batch_inputs,
×
462
                        batch_effects,
×
463
                    )
464
                        // TODO: remove this chain() once all system dependencies are setup
465
                        // correctly above.
466
                        .chain()
×
467
                        .in_set(EffectSystems::PrepareEffectAssets),
×
468
                    // Once batched, queue the effects/batches which are ready to be
469
                    // updated/rendered this frame.
470
                    queue_effects
×
471
                        .in_set(EffectSystems::QueueEffects)
×
472
                        .after(batch_effects),
×
473
                    // Queue the dispatch ops to fill the indirect dispatch args of the init pass
474
                    // of child effects.
475
                    queue_init_indirect_workgroup_update
×
476
                        .in_set(EffectSystems::QueueEffects)
×
477
                        .after(batch_effects)
×
478
                        .after(fixup_parents),
×
479
                    prepare_gpu_resources
×
480
                        .in_set(EffectSystems::PrepareEffectGpuResources)
×
481
                        // This creates the bind group for the view
482
                        .after(prepare_view_uniforms)
×
483
                        // Upload and optionally resize the draw indirect args buffer
484
                        .after(update_mesh_locations)
×
485
                        // Bind groups depend on buffers being re-/allocated
486
                        .before(prepare_bind_groups),
×
487
                    prepare_property_buffers
×
488
                        .in_set(EffectSystems::PrepareEffectGpuResources)
×
489
                        .before(prepare_bind_groups),
×
490
                    prepare_effect_metadata
×
491
                        .in_set(EffectSystems::PrepareEffectGpuResources)
×
492
                        //
493
                        .after(allocate_effects)
×
494
                        // Need the draw indirect args to be allocated
495
                        .after(update_mesh_locations)
×
496
                        // Need the local/global/base child index
497
                        .after(fixup_parents)
×
498
                        // Need the indirect dispatch args index for GPU event based init pass
499
                        .after(allocate_events)
×
500
                        // Need the properties block offset in GPU slab
501
                        .after(allocate_properties)
×
502
                        // This may invalidate some bind groups when resizing the metadata buffer
503
                        .before(prepare_bind_groups),
×
504
                    // Queue the dispatch ops to fill the indirect dispatch args of the ribbon
505
                    // particle sort pass. Deferred from batch_effects() so it runs after the
506
                    // effect metadata buffer has been (re-)allocated to this frame's size.
507
                    queue_sort_fill_dispatch_ops
×
508
                        .in_set(EffectSystems::PrepareEffectGpuResources)
×
509
                        // The prefix sum buffer is allocated and uploaded while batching.
NEW
510
                        .after(batch_effects)
×
511
                        // Need the metadata buffer (re-)allocated so the captured handle and
512
                        // dynamic offsets are correct and in-bounds
513
                        .after(prepare_effect_metadata)
×
514
                        // Keep this aligned with other queue submissions that capture GPU buffers.
NEW
515
                        .after(prepare_gpu_resources)
×
516
                        // Must submit into the shared GpuBufferOperations before
517
                        // queue_init_fill_dispatch_ops uploads its args buffer (end_frame)
518
                        .before(queue_init_fill_dispatch_ops)
×
519
                        .before(prepare_bind_groups),
×
520
                    queue_init_fill_dispatch_ops
×
521
                        .in_set(EffectSystems::PrepareEffectGpuResources)
×
522
                        .after(prepare_gpu_resources)
×
523
                        .before(prepare_bind_groups),
×
524
                    // Prepare the bind groups
525
                    prepare_bind_groups
×
526
                        .in_set(EffectSystems::PrepareBindGroups)
×
527
                        .after(queue_effects)
×
528
                        .after(prepare_assets::<GpuImage>),
×
529
                ),
530
            );
531

532
        // Register observers to deallocate GPU resources
533
        {
534
            let world = render_app.world_mut();
×
535
            world.add_observer(on_remove_cached_effect);
×
536
            world.add_observer(on_remove_cached_metadata);
×
537
            world.add_observer(on_remove_cached_draw_indirect_args);
×
538
            world.add_observer(on_remove_cached_effect_events);
×
539
            world.add_observer(on_remove_cached_properties);
×
540
        }
541

542
        // Register the draw function for drawing the particles. This will be called
543
        // during the main 2D/3D pass, at the Transparent2d/3d phase, after the
544
        // opaque objects have been rendered (or, rather, commands for those
545
        // have been recorded).
546
        #[cfg(feature = "2d")]
547
        {
548
            let draw_particles = DrawEffects::new(render_app.world_mut());
×
549
            render_app
×
550
                .world()
551
                .get_resource::<DrawFunctions<Transparent2d>>()
552
                .unwrap()
553
                .write()
554
                .add(draw_particles);
×
555
        }
556
        #[cfg(feature = "3d")]
557
        {
558
            let draw_particles = DrawEffects::new(render_app.world_mut());
×
559
            render_app
×
560
                .world()
561
                .get_resource::<DrawFunctions<Transparent3d>>()
562
                .unwrap()
563
                .write()
564
                .add(draw_particles);
×
565

566
            let draw_particles = DrawEffects::new(render_app.world_mut());
×
567
            render_app
×
568
                .world()
569
                .get_resource::<DrawFunctions<AlphaMask3d>>()
570
                .unwrap()
571
                .write()
572
                .add(draw_particles);
×
573

574
            let draw_particles = DrawEffects::new(render_app.world_mut());
×
575
            render_app
×
576
                .world()
577
                .get_resource::<DrawFunctions<Opaque3d>>()
578
                .unwrap()
579
                .write()
580
                .add(draw_particles);
×
581
        }
582
    }
583
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE TRIAL · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc