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

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

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

64
    /// Compile the effect instances, updating the [`CompiledParticleEffect`]
65
    /// components.
66
    ///
67
    /// This system runs during the [`PostUpdate`] schedule. This is largely an
68
    /// internal task which can be ignored by most users.
69
    ///
70
    /// [`CompiledParticleEffect`]: crate::CompiledParticleEffect
71
    CompileEffects,
72

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

83
    /// Prepare effect assets for the extracted effects.
84
    ///
85
    /// Part of Bevy's own [`RenderSystems::PrepareAssets`].
86
    PrepareEffectAssets,
87

88
    /// Queue the GPU commands for the extracted effects.
89
    ///
90
    /// Part of Bevy's own [`RenderSystems::Queue`].
91
    QueueEffects,
92

93
    /// Prepare GPU data for the queued effects.
94
    ///
95
    /// Part of Bevy's own [`RenderSystems::PrepareResources`].
96
    PrepareEffectGpuResources,
97

98
    /// Prepare the GPU bind groups once all buffers have been (re-)allocated
99
    /// and won't change this frame.
100
    ///
101
    /// Part of Bevy's own [`RenderSystems::PrepareBindGroups`].
102
    PrepareBindGroups,
103
}
104

105
pub mod main_graph {
106
    pub mod node {
107
        use bevy::render::render_graph::RenderLabel;
108

109
        /// Label for the simulation driver node running the simulation graph.
110
        #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, RenderLabel)]
111
        pub struct HanabiDriverNode;
112
    }
113
}
114

115
pub mod simulate_graph {
116
    use bevy::render::render_graph::RenderSubGraph;
117

118
    /// Name of the simulation sub-graph.
119
    #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, RenderSubGraph)]
120
    pub struct HanabiSimulateGraph;
121

122
    pub mod node {
123
        use bevy::render::render_graph::RenderLabel;
124

125
        /// Label for the simulation node (init and update compute passes;
126
        /// view-independent).
127
        #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, RenderLabel)]
128
        pub struct HanabiSimulateNode;
129
    }
130
}
131

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

135
/// Plugin to add systems related to Hanabi.
136
#[derive(Debug, Clone, Copy)]
137
pub struct HanabiPlugin;
138

139
impl HanabiPlugin {
140
    /// Create the `vfx_common.wgsl` shader with proper alignment.
141
    ///
142
    /// This creates a new [`Shader`] from the `vfx_common.wgsl` template file,
143
    /// by applying the given alignment for storage buffers. This produces a
144
    /// shader ready for the specific GPU device associated with that
145
    /// alignment.
146
    pub(crate) fn make_common_shader(min_storage_buffer_offset_alignment: u32) -> Shader {
6✔
147
        let spawner_padding_code =
6✔
148
            GpuSpawnerParams::padding_code(min_storage_buffer_offset_alignment);
12✔
149
        let effect_metadata_padding_code =
6✔
150
            GpuEffectMetadata::padding_code(min_storage_buffer_offset_alignment);
12✔
151
        let render_effect_indirect_size =
6✔
152
            GpuEffectMetadata::aligned_size(min_storage_buffer_offset_alignment);
12✔
153
        let effect_metadata_stride_code =
6✔
154
            (render_effect_indirect_size.get() as u32).to_wgsl_string();
12✔
155
        let common_code = include_str!("render/vfx_common.wgsl")
30✔
156
            .replace("{{SPAWNER_PADDING}}", &spawner_padding_code)
18✔
157
            .replace("{{EFFECT_METADATA_PADDING}}", &effect_metadata_padding_code)
12✔
158
            .replace("{{EFFECT_METADATA_STRIDE}}", &effect_metadata_stride_code);
6✔
159
        Shader::from_wgsl(
160
            common_code,
6✔
161
            std::path::Path::new(file!())
18✔
162
                .parent()
12✔
163
                .unwrap()
12✔
164
                .join(format!(
6✔
165
                    "render/vfx_common_{}.wgsl",
6✔
166
                    min_storage_buffer_offset_alignment
6✔
167
                ))
168
                .to_string_lossy(),
6✔
169
        )
170
    }
171

172
    /// Create the `vfx_indirect.wgsl` shader with proper alignment.
173
    ///
174
    /// This creates a new [`Shader`] from the `vfx_indirect.wgsl` template
175
    /// file, by applying the given alignment for storage buffers. This
176
    /// produces a shader ready for the specific GPU device associated with
177
    /// that alignment.
178
    pub(crate) fn make_indirect_shader(
6✔
179
        min_storage_buffer_offset_alignment: u32,
180
        has_events: bool,
181
    ) -> Shader {
182
        let render_effect_indirect_size =
6✔
183
            GpuEffectMetadata::aligned_size(min_storage_buffer_offset_alignment);
12✔
184
        let render_effect_indirect_stride_code =
6✔
185
            (render_effect_indirect_size.get() as u32).to_wgsl_string();
12✔
186
        let indirect_code = include_str!("render/vfx_indirect.wgsl").replace(
18✔
187
            "{{EFFECT_METADATA_STRIDE}}",
188
            &render_effect_indirect_stride_code,
6✔
189
        );
190
        Shader::from_wgsl(
191
            indirect_code,
6✔
192
            std::path::Path::new(file!())
12✔
193
                .parent()
6✔
194
                .unwrap()
6✔
195
                .join(format!(
12✔
196
                    "render/vfx_indirect_{}_{}.wgsl",
197
                    min_storage_buffer_offset_alignment,
198
                    if has_events { "events" } else { "noevent" },
12✔
199
                ))
200
                .to_string_lossy(),
6✔
201
        )
202
    }
203
}
204

205
impl Plugin for HanabiPlugin {
206
    fn build(&self, app: &mut App) {
3✔
207
        // Register asset
208
        app.init_asset::<EffectAsset>()
18✔
209
            .insert_resource(Random(spawn::new_rng()))
15✔
210
            .add_plugins(ExtractComponentPlugin::<EffectVisibilityClass>::default())
12✔
211
            .init_resource::<DefaultMesh>()
212
            .init_resource::<ShaderCache>()
213
            .init_resource::<DebugSettings>()
214
            .init_resource::<Time<EffectSimulation>>()
215
            .configure_sets(
216
                PostUpdate,
12✔
217
                (
218
                    EffectSystems::TickSpawners
12✔
219
                        // This checks the visibility to skip work, so needs to run after
220
                        // ComputedVisibility was updated.
221
                        .after(VisibilitySystems::VisibilityPropagate),
12✔
222
                    EffectSystems::CompileEffects,
9✔
223
                ),
224
            )
225
            .configure_sets(
226
                PreUpdate,
9✔
227
                EffectSystems::UpdatePropertiesFromAsset.after(bevy::asset::AssetTrackingSystems),
9✔
228
            )
229
            .add_systems(
230
                First,
6✔
231
                effect_simulation_time_system
3✔
232
                    .after(time_system)
6✔
233
                    .in_set(TimeSystems),
3✔
234
            )
235
            .add_systems(
236
                PostUpdate,
3✔
237
                (
238
                    tick_spawners.in_set(EffectSystems::TickSpawners),
9✔
239
                    compile_effects.in_set(EffectSystems::CompileEffects),
9✔
240
                    update_properties_from_asset.in_set(EffectSystems::UpdatePropertiesFromAsset),
3✔
241
                ),
242
            );
243

244
        #[cfg(feature = "serde")]
245
        app.init_asset_loader::<EffectAssetLoader>();
6✔
246

247
        // Register types with reflection
248
        app.register_type::<EffectAsset>()
3✔
249
            .register_type::<ParticleEffect>()
250
            .register_type::<EffectProperties>()
251
            .register_type::<SpawnerSettings>()
252
            .register_type::<Time<EffectSimulation>>();
253
    }
254

255
    fn finish(&self, app: &mut App) {
3✔
256
        let render_device = app
9✔
257
            .sub_app(RenderApp)
3✔
258
            .world()
259
            .resource::<RenderDevice>()
260
            .clone();
261

262
        let adapter_name = app
6✔
263
            .world()
264
            .get_resource::<RenderAdapterInfo>()
265
            .map(|ai| &ai.name[..])
6✔
266
            .unwrap_or("<unknown>");
267

268
        // Check device limits
269
        let limits = render_device.limits();
9✔
270
        if limits.max_bind_groups < 4 {
3✔
271
            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);
×
272
            return;
×
273
        } else {
274
            info!("Initializing Hanabi for GPU adapter {}", adapter_name);
3✔
275
        }
276

277
        // Insert the properly aligned `vfx_common.wgsl` shader into Assets<Shader>, so
278
        // that the automated Bevy shader processing finds it as an import. This is used
279
        // for init/update/render shaders (but not the indirect one).
280
        {
281
            let common_shader = HanabiPlugin::make_common_shader(
UNCOV
282
                render_device.limits().min_storage_buffer_offset_alignment,
×
283
            );
UNCOV
284
            let mut assets = app.world_mut().resource_mut::<Assets<Shader>>();
×
UNCOV
285
            assets
×
UNCOV
286
                .insert(&HANABI_COMMON_TEMPLATE_HANDLE, common_shader)
×
287
                .unwrap();
288
        }
289

290
        // Insert the two variants of the properly aligned `vfx_indirect.wgsl` shaders
291
        // into Assets<Shader>.
292
        let (
UNCOV
293
            indirect_shader_noevent,
×
UNCOV
294
            indirect_shader_events,
×
UNCOV
295
            sort_fill_shader,
×
UNCOV
296
            sort_shader,
×
UNCOV
297
            sort_copy_shader,
×
UNCOV
298
        ) = {
×
UNCOV
299
            let align = render_device.limits().min_storage_buffer_offset_alignment;
×
UNCOV
300
            let indirect_shader_noevent = HanabiPlugin::make_indirect_shader(align, false);
×
UNCOV
301
            let indirect_shader_events = HanabiPlugin::make_indirect_shader(align, true);
×
302
            let sort_fill_shader = Shader::from_wgsl(
UNCOV
303
                include_str!("render/vfx_sort_fill.wgsl"),
×
UNCOV
304
                std::path::Path::new(file!())
×
UNCOV
305
                    .parent()
×
UNCOV
306
                    .unwrap()
×
UNCOV
307
                    .join("render/vfx_sort_fill.wgsl")
×
UNCOV
308
                    .to_string_lossy(),
×
309
            );
310
            let sort_shader = Shader::from_wgsl(
UNCOV
311
                include_str!("render/vfx_sort.wgsl"),
×
UNCOV
312
                std::path::Path::new(file!())
×
UNCOV
313
                    .parent()
×
UNCOV
314
                    .unwrap()
×
UNCOV
315
                    .join("render/vfx_sort.wgsl")
×
UNCOV
316
                    .to_string_lossy(),
×
317
            );
318
            let sort_copy_shader = Shader::from_wgsl(
UNCOV
319
                include_str!("render/vfx_sort_copy.wgsl"),
×
UNCOV
320
                std::path::Path::new(file!())
×
UNCOV
321
                    .parent()
×
UNCOV
322
                    .unwrap()
×
UNCOV
323
                    .join("render/vfx_sort_copy.wgsl")
×
UNCOV
324
                    .to_string_lossy(),
×
325
            );
326

UNCOV
327
            let mut assets = app.world_mut().resource_mut::<Assets<Shader>>();
×
UNCOV
328
            let indirect_shader_noevent = assets.add(indirect_shader_noevent);
×
UNCOV
329
            let indirect_shader_events = assets.add(indirect_shader_events);
×
UNCOV
330
            let sort_fill_shader = assets.add(sort_fill_shader);
×
UNCOV
331
            let sort_shader = assets.add(sort_shader);
×
UNCOV
332
            let sort_copy_shader = assets.add(sort_copy_shader);
×
333

334
            (
UNCOV
335
                indirect_shader_noevent,
×
UNCOV
336
                indirect_shader_events,
×
UNCOV
337
                sort_fill_shader,
×
UNCOV
338
                sort_shader,
×
UNCOV
339
                sort_copy_shader,
×
340
            )
341
        };
342

343
        let effects_meta = EffectsMeta::new(
UNCOV
344
            render_device.clone(),
×
UNCOV
345
            indirect_shader_noevent,
×
UNCOV
346
            indirect_shader_events,
×
347
        );
348

UNCOV
349
        let effect_cache = EffectCache::new(render_device.clone());
×
UNCOV
350
        let property_cache = PropertyCache::new(render_device.clone());
×
UNCOV
351
        let event_cache = EventCache::new(render_device);
×
352

UNCOV
353
        let render_app = app.sub_app_mut(RenderApp);
×
354
        let sort_bind_groups = SortBindGroups::new(
UNCOV
355
            render_app.world_mut(),
×
UNCOV
356
            sort_fill_shader,
×
UNCOV
357
            sort_shader,
×
UNCOV
358
            sort_copy_shader,
×
359
        );
360

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

519
        // Register observers to deallocate GPU resources
520
        {
UNCOV
521
            let world = render_app.world_mut();
×
UNCOV
522
            world.add_observer(on_remove_cached_effect);
×
UNCOV
523
            world.add_observer(on_remove_cached_metadata);
×
UNCOV
524
            world.add_observer(on_remove_cached_draw_indirect_args);
×
UNCOV
525
            world.add_observer(on_remove_cached_effect_events);
×
UNCOV
526
            world.add_observer(on_remove_cached_properties);
×
527
        }
528

529
        // Register the draw function for drawing the particles. This will be called
530
        // during the main 2D/3D pass, at the Transparent2d/3d phase, after the
531
        // opaque objects have been rendered (or, rather, commands for those
532
        // have been recorded).
533
        #[cfg(feature = "2d")]
534
        {
UNCOV
535
            let draw_particles = DrawEffects::new(render_app.world_mut());
×
UNCOV
536
            render_app
×
537
                .world()
538
                .get_resource::<DrawFunctions<Transparent2d>>()
539
                .unwrap()
540
                .write()
UNCOV
541
                .add(draw_particles);
×
542
        }
543
        #[cfg(feature = "3d")]
544
        {
UNCOV
545
            let draw_particles = DrawEffects::new(render_app.world_mut());
×
UNCOV
546
            render_app
×
547
                .world()
548
                .get_resource::<DrawFunctions<Transparent3d>>()
549
                .unwrap()
550
                .write()
UNCOV
551
                .add(draw_particles);
×
552

UNCOV
553
            let draw_particles = DrawEffects::new(render_app.world_mut());
×
UNCOV
554
            render_app
×
555
                .world()
556
                .get_resource::<DrawFunctions<AlphaMask3d>>()
557
                .unwrap()
558
                .write()
UNCOV
559
                .add(draw_particles);
×
560

UNCOV
561
            let draw_particles = DrawEffects::new(render_app.world_mut());
×
UNCOV
562
            render_app
×
563
                .world()
564
                .get_resource::<DrawFunctions<Opaque3d>>()
565
                .unwrap()
566
                .write()
UNCOV
567
                .add(draw_particles);
×
568
        }
569

570
        // Add the simulation sub-graph. This render graph runs once per frame no matter
571
        // how many cameras/views are active (view-independent).
UNCOV
572
        let mut simulate_graph = RenderGraph::default();
×
UNCOV
573
        let simulate_node = VfxSimulateNode::new(render_app.world_mut());
×
UNCOV
574
        simulate_graph.add_node(simulate_graph::node::HanabiSimulateNode, simulate_node);
×
UNCOV
575
        let mut graph = render_app
×
576
            .world_mut()
577
            .get_resource_mut::<RenderGraph>()
578
            .unwrap();
UNCOV
579
        graph.add_sub_graph(simulate_graph::HanabiSimulateGraph, simulate_graph);
×
580

581
        // Add the simulation driver node which executes the simulation sub-graph. It
582
        // runs before the camera driver, since rendering needs to access simulated
583
        // particles.
UNCOV
584
        graph.add_node(main_graph::node::HanabiDriverNode, VfxSimulateDriverNode {});
×
UNCOV
585
        graph.add_node_edge(
×
UNCOV
586
            main_graph::node::HanabiDriverNode,
×
UNCOV
587
            bevy::render::graph::CameraDriverLabel,
×
588
        );
589
    }
590
}
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