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

djeedai / bevy_hanabi / 12128238298

02 Dec 2024 09:24PM UTC coverage: 48.661% (-7.6%) from 56.217%
12128238298

Pull #401

github

web-flow
Merge 30c486d1a into 19aee8dbc
Pull Request #401: Upgrade to Bevy v0.15.0

39 of 284 new or added lines in 11 files covered. (13.73%)

435 existing lines in 8 files now uncovered.

3106 of 6383 relevant lines covered (48.66%)

21.61 hits per line

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

32.89
/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
    prelude::*,
7
    render::{
8
        mesh::allocator::allocate_and_free_meshes,
9
        render_asset::prepare_assets,
10
        render_graph::RenderGraph,
11
        render_phase::DrawFunctions,
12
        render_resource::{SpecializedComputePipelines, SpecializedRenderPipelines},
13
        renderer::{RenderAdapterInfo, RenderDevice},
14
        texture::GpuImage,
15
        view::{check_visibility, prepare_view_uniforms, visibility::VisibilitySystems},
16
        Render, RenderApp, RenderSet,
17
    },
18
    time::{time_system, TimeSystem},
19
};
20

21
#[cfg(feature = "serde")]
22
use crate::asset::EffectAssetLoader;
23
use crate::{
24
    asset::EffectAsset,
25
    compile_effects, gather_removed_effects,
26
    properties::EffectProperties,
27
    render::{
28
        extract_effect_events, extract_effects, prepare_bind_groups, prepare_effects,
29
        prepare_gpu_resources, queue_effects, DispatchIndirectPipeline, DrawEffects,
30
        EffectAssetEvents, EffectBindGroups, EffectCache, EffectsMeta, ExtractedEffects,
31
        GpuDispatchIndirect, GpuParticleGroup, GpuRenderEffectMetadata, GpuRenderGroupIndirect,
32
        GpuSpawnerParams, ParticlesInitPipeline, ParticlesRenderPipeline, ParticlesUpdatePipeline,
33
        ShaderCache, SimParams, StorageType as _, VfxSimulateDriverNode, VfxSimulateNode,
34
    },
35
    spawn::{self, Random},
36
    tick_initializers,
37
    time::effect_simulation_time_system,
38
    update_properties_from_asset, CompiledParticleEffect, EffectSimulation, ParticleEffect,
39
    RemovedEffectsEvent, Spawner,
40
};
41

42
/// Labels for the Hanabi systems.
43
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, SystemSet)]
44
pub enum EffectSystems {
45
    /// Tick all effect instances to generate particle spawn counts.
46
    ///
47
    /// This system runs during the [`PostUpdate`] schedule. Any system which
48
    /// modifies an effect spawner should run before this set to ensure the
49
    /// spawner takes into account the newly set values during its ticking.
50
    TickSpawners,
51

52
    /// Compile the effect instances, updating the [`CompiledParticleEffect`]
53
    /// components.
54
    ///
55
    /// This system runs during the [`PostUpdate`] schedule. This is largely an
56
    /// internal task which can be ignored by most users.
57
    ///
58
    /// [`CompiledParticleEffect`]: crate::CompiledParticleEffect
59
    CompileEffects,
60

61
    /// Update the properties of the effect instance based on the declared
62
    /// properties in the [`EffectAsset`], updating the associated
63
    /// [`EffectProperties`] component.
64
    ///
65
    /// This system runs during the [`PostUpdate`] schedule, after the assets
66
    /// have been updated. Any system which modifies an [`EffectAsset`]'s
67
    /// declared properties should run before this set in order for changes to
68
    /// be taken into account in the same frame.
69
    UpdatePropertiesFromAsset,
70

71
    /// Gather all removed [`ParticleEffect`] components during the
72
    /// [`PostUpdate`] set, to clean-up unused GPU resources.
73
    ///
74
    /// Systems deleting entities with a [`ParticleEffect`] component should run
75
    /// before this set if they want the particle effect is cleaned-up during
76
    /// the same frame.
77
    ///
78
    /// [`ParticleEffect`]: crate::ParticleEffect
79
    GatherRemovedEffects,
80

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

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

91
    /// Prepare GPU data for the queued effects.
92
    ///
93
    /// Part of Bevy's own [`RenderSet::Prepare`].
94
    PrepareEffectGpuResources,
95

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

103
pub mod main_graph {
104
    pub mod node {
105
        use bevy::render::render_graph::RenderLabel;
106

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

113
pub mod simulate_graph {
114
    use bevy::render::render_graph::RenderSubGraph;
115

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

120
    pub mod node {
121
        use bevy::render::render_graph::RenderLabel;
122

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

130
// {626E7AD3-4E54-487E-B796-9A90E34CC1EC}
131
const HANABI_COMMON_TEMPLATE_HANDLE: Handle<Shader> =
132
    Handle::weak_from_u128(0x626E7AD34E54487EB7969A90E34CC1ECu128);
133

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

138
impl HanabiPlugin {
139
    /// Create the `vfx_common.wgsl` shader with proper alignment.
140
    ///
141
    /// This creates a new [`Shader`] from the `vfx_common.wgsl` template file,
142
    /// by applying the given alignment for storage buffers. This produces a
143
    /// shader ready for the specific GPU device associated with that
144
    /// alignment.
145
    pub(crate) fn make_common_shader(min_storage_buffer_offset_alignment: u32) -> Shader {
3✔
146
        let spawner_padding_code =
3✔
147
            GpuSpawnerParams::padding_code(min_storage_buffer_offset_alignment);
3✔
148
        let dispatch_indirect_padding_code =
3✔
149
            GpuDispatchIndirect::padding_code(min_storage_buffer_offset_alignment);
3✔
150
        let render_effect_indirect_padding_code =
3✔
151
            GpuRenderEffectMetadata::padding_code(min_storage_buffer_offset_alignment);
3✔
152
        let render_group_indirect_padding_code =
3✔
153
            GpuRenderGroupIndirect::padding_code(min_storage_buffer_offset_alignment);
3✔
154
        let particle_group_padding_code =
3✔
155
            GpuParticleGroup::padding_code(min_storage_buffer_offset_alignment);
3✔
156
        let common_code = include_str!("render/vfx_common.wgsl")
3✔
157
            .replace("{{SPAWNER_PADDING}}", &spawner_padding_code)
3✔
158
            .replace(
159
                "{{DISPATCH_INDIRECT_PADDING}}",
160
                &dispatch_indirect_padding_code,
3✔
161
            )
162
            .replace(
163
                "{{RENDER_EFFECT_INDIRECT_PADDING}}",
164
                &render_effect_indirect_padding_code,
3✔
165
            )
166
            .replace(
167
                "{{RENDER_GROUP_INDIRECT_PADDING}}",
168
                &render_group_indirect_padding_code,
3✔
169
            )
170
            .replace("{{PARTICLE_GROUP_PADDING}}", &particle_group_padding_code);
3✔
171
        Shader::from_wgsl(
172
            common_code,
3✔
173
            std::path::Path::new(file!())
3✔
174
                .parent()
3✔
175
                .unwrap()
3✔
176
                .join(format!(
3✔
177
                    "render/vfx_common_{}.wgsl",
3✔
178
                    min_storage_buffer_offset_alignment
3✔
179
                ))
180
                .to_string_lossy(),
3✔
181
        )
182
    }
183
}
184

185
/// A convenient alias for `With<CompiledParticleEffect>`, for use with
186
/// [`bevy_render::view::VisibleEntities`].
187
pub type WithCompiledParticleEffect = With<CompiledParticleEffect>;
188

189
impl Plugin for HanabiPlugin {
UNCOV
190
    fn build(&self, app: &mut App) {
×
191
        // Register asset
UNCOV
192
        app.init_asset::<EffectAsset>()
×
193
            .add_event::<RemovedEffectsEvent>()
UNCOV
194
            .insert_resource(Random(spawn::new_rng()))
×
195
            .init_resource::<ShaderCache>()
196
            .init_resource::<Time<EffectSimulation>>()
197
            .configure_sets(
UNCOV
198
                PostUpdate,
×
199
                (
UNCOV
200
                    EffectSystems::TickSpawners
×
201
                        // This checks the visibility to skip work, so needs to run after
202
                        // ComputedVisibility was updated.
UNCOV
203
                        .after(VisibilitySystems::VisibilityPropagate),
×
UNCOV
204
                    EffectSystems::CompileEffects,
×
UNCOV
205
                    EffectSystems::GatherRemovedEffects,
×
206
                ),
207
            )
208
            .configure_sets(
UNCOV
209
                PreUpdate,
×
UNCOV
210
                EffectSystems::UpdatePropertiesFromAsset.after(bevy::asset::TrackAssets),
×
211
            )
212
            .add_systems(
UNCOV
213
                First,
×
UNCOV
214
                effect_simulation_time_system
×
UNCOV
215
                    .after(time_system)
×
UNCOV
216
                    .in_set(TimeSystem),
×
217
            )
218
            .add_systems(
UNCOV
219
                PostUpdate,
×
220
                (
UNCOV
221
                    tick_initializers.in_set(EffectSystems::TickSpawners),
×
UNCOV
222
                    compile_effects.in_set(EffectSystems::CompileEffects),
×
UNCOV
223
                    update_properties_from_asset.in_set(EffectSystems::UpdatePropertiesFromAsset),
×
UNCOV
224
                    gather_removed_effects.in_set(EffectSystems::GatherRemovedEffects),
×
UNCOV
225
                    check_visibility::<WithCompiledParticleEffect>
×
UNCOV
226
                        .in_set(VisibilitySystems::CheckVisibility),
×
227
                ),
228
            );
229

230
        #[cfg(feature = "serde")]
UNCOV
231
        app.init_asset_loader::<EffectAssetLoader>();
×
232

233
        // Register types with reflection
UNCOV
234
        app.register_type::<EffectAsset>()
×
235
            .register_type::<ParticleEffect>()
236
            .register_type::<EffectProperties>()
237
            .register_type::<Spawner>()
238
            .register_type::<Time<EffectSimulation>>();
239
    }
240

UNCOV
241
    fn finish(&self, app: &mut App) {
×
UNCOV
242
        let render_device = app
×
UNCOV
243
            .sub_app(RenderApp)
×
244
            .world()
245
            .resource::<RenderDevice>()
246
            .clone();
247

UNCOV
248
        let adapter_name = app
×
249
            .world()
250
            .get_resource::<RenderAdapterInfo>()
UNCOV
251
            .map(|ai| &ai.name[..])
×
252
            .unwrap_or("<unknown>");
253

254
        // Check device limits
UNCOV
255
        let limits = render_device.limits();
×
UNCOV
256
        if limits.max_bind_groups < 4 {
×
257
            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);
×
258
            return;
×
259
        } else {
UNCOV
260
            info!("Initializing Hanabi for GPU adapter {}", adapter_name);
×
261
        }
262

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

UNCOV
274
        let effects_meta = {
×
UNCOV
275
            let mut assets = app.world_mut().resource_mut::<Assets<Mesh>>();
×
UNCOV
276
            EffectsMeta::new(render_device.clone(), &mut assets)
×
277
        };
278

UNCOV
279
        let effect_cache = EffectCache::new(render_device);
×
280

281
        // Register the custom render pipeline
UNCOV
282
        let render_app = app.sub_app_mut(RenderApp);
×
UNCOV
283
        render_app
×
UNCOV
284
            .insert_resource(effects_meta)
×
UNCOV
285
            .insert_resource(effect_cache)
×
286
            .init_resource::<EffectBindGroups>()
287
            .init_resource::<DispatchIndirectPipeline>()
288
            .init_resource::<ParticlesInitPipeline>()
289
            .init_resource::<SpecializedComputePipelines<ParticlesInitPipeline>>()
290
            .init_resource::<ParticlesInitPipeline>()
291
            .init_resource::<SpecializedComputePipelines<ParticlesInitPipeline>>()
292
            .init_resource::<ParticlesUpdatePipeline>()
293
            .init_resource::<SpecializedComputePipelines<ParticlesUpdatePipeline>>()
294
            .init_resource::<ParticlesRenderPipeline>()
295
            .init_resource::<SpecializedRenderPipelines<ParticlesRenderPipeline>>()
296
            .init_resource::<ExtractedEffects>()
297
            .init_resource::<EffectAssetEvents>()
298
            .init_resource::<SimParams>()
299
            .configure_sets(
UNCOV
300
                Render,
×
301
                (
UNCOV
302
                    EffectSystems::PrepareEffectAssets.in_set(RenderSet::PrepareAssets),
×
UNCOV
303
                    EffectSystems::QueueEffects.in_set(RenderSet::Queue),
×
UNCOV
304
                    EffectSystems::PrepareEffectGpuResources.in_set(RenderSet::Prepare),
×
UNCOV
305
                    EffectSystems::PrepareBindGroups.in_set(RenderSet::PrepareBindGroups),
×
306
                ),
307
            )
UNCOV
308
            .edit_schedule(ExtractSchedule, |schedule| {
×
UNCOV
309
                schedule.add_systems((extract_effects, extract_effect_events));
×
310
            })
311
            .add_systems(
312
                Render,
313
                (
314
                    prepare_effects
315
                        .in_set(EffectSystems::PrepareEffectAssets)
316
                        // Ensure we run after Bevy prepared the render Mesh
317
                        .after(allocate_and_free_meshes),
318
                    queue_effects
319
                        .in_set(EffectSystems::QueueEffects)
320
                        .after(prepare_effects),
321
                    prepare_gpu_resources
322
                        .in_set(EffectSystems::PrepareEffectGpuResources)
323
                        .after(prepare_view_uniforms),
324
                    prepare_bind_groups
325
                        .in_set(EffectSystems::PrepareBindGroups)
326
                        .after(queue_effects)
327
                        .after(prepare_assets::<GpuImage>),
328
                ),
329
            );
330

331
        // Register the draw function for drawing the particles. This will be called
332
        // during the main 2D/3D pass, at the Transparent2d/3d phase, after the
333
        // opaque objects have been rendered (or, rather, commands for those
334
        // have been recorded).
335
        #[cfg(feature = "2d")]
336
        {
337
            let draw_particles = DrawEffects::new(render_app.world_mut());
338
            render_app
339
                .world()
340
                .get_resource::<DrawFunctions<Transparent2d>>()
341
                .unwrap()
342
                .write()
343
                .add(draw_particles);
344
        }
345
        #[cfg(feature = "3d")]
346
        {
347
            let draw_particles = DrawEffects::new(render_app.world_mut());
348
            render_app
349
                .world()
350
                .get_resource::<DrawFunctions<Transparent3d>>()
351
                .unwrap()
352
                .write()
353
                .add(draw_particles);
354

355
            let draw_particles = DrawEffects::new(render_app.world_mut());
356
            render_app
357
                .world()
358
                .get_resource::<DrawFunctions<AlphaMask3d>>()
359
                .unwrap()
360
                .write()
361
                .add(draw_particles);
362

363
            let draw_particles = DrawEffects::new(render_app.world_mut());
364
            render_app
365
                .world()
366
                .get_resource::<DrawFunctions<Opaque3d>>()
367
                .unwrap()
368
                .write()
369
                .add(draw_particles);
370
        }
371

372
        // Add the simulation sub-graph. This render graph runs once per frame no matter
373
        // how many cameras/views are active (view-independent).
374
        let mut simulate_graph = RenderGraph::default();
375
        let simulate_node = VfxSimulateNode::new(render_app.world_mut());
376
        simulate_graph.add_node(simulate_graph::node::HanabiSimulateNode, simulate_node);
377
        let mut graph = render_app
378
            .world_mut()
379
            .get_resource_mut::<RenderGraph>()
380
            .unwrap();
381
        graph.add_sub_graph(simulate_graph::HanabiSimulateGraph, simulate_graph);
382

383
        // Add the simulation driver node which executes the simulation sub-graph. It
384
        // runs before the camera driver, since rendering needs to access simulated
385
        // particles.
386
        graph.add_node(main_graph::node::HanabiDriverNode, VfxSimulateDriverNode {});
387
        graph.add_node_edge(
388
            main_graph::node::HanabiDriverNode,
389
            bevy::render::graph::CameraDriverLabel,
390
        );
391
    }
392
}
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