Skip to content

Commit

Permalink
Disable broken effect batching
Browse files Browse the repository at this point in the history
Batching of compatible effects (and in particular, multiple instances of
the same effect) is currently broken due to the inability for a particle
to tell which effect it's associated with, and therefore which
`GpuSpawnerParams` struct it should index.

To prevent batching to trigger, set the minimum buffer capacity to 1
particle, which effectively means each effect will get a dedicated GPU
buffer that just fits it, preventing another effect from using the same
buffer, and effectively disabling batching.o

Bug: #73
  • Loading branch information
djeedai committed Oct 29, 2022
1 parent e05af2b commit 982e015
Show file tree
Hide file tree
Showing 5 changed files with 107 additions and 4 deletions.
4 changes: 4 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,10 @@ required-features = [ "bevy/bevy_winit", "bevy/bevy_sprite", "2d" ]
name = "lifetime"
required-features = [ "bevy/bevy_winit", "bevy/bevy_pbr", "3d" ]

[[example]]
name = "instancing"
required-features = [ "bevy/bevy_winit", "bevy/bevy_pbr", "3d" ]

[workspace]
resolver = "2"
members = ["."]
91 changes: 91 additions & 0 deletions examples/instancing.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
use bevy::{prelude::*, render::mesh::shape::Cube};
use bevy_inspector_egui::WorldInspectorPlugin;

use bevy_hanabi::prelude::*;

fn main() {
App::default()
.insert_resource(bevy::log::LogSettings {
level: bevy::log::Level::WARN,
filter: "bevy_hanabi=trace,instancing=trace".to_string(),
})
.add_plugins(DefaultPlugins)
.add_system(bevy::window::close_on_esc)
.add_plugin(HanabiPlugin)
.add_plugin(WorldInspectorPlugin::new())
.add_startup_system(setup)
.run();
}

fn setup(
mut commands: Commands,
mut effects: ResMut<Assets<EffectAsset>>,
mut meshes: ResMut<Assets<Mesh>>,
mut materials: ResMut<Assets<StandardMaterial>>,
) {
let mut camera = Camera3dBundle::default();
camera.transform.translation = Vec3::new(0.0, 0.0, 180.0);
commands.spawn_bundle(camera);

commands.spawn_bundle(DirectionalLightBundle {
directional_light: DirectionalLight {
color: Color::WHITE,
// Crank the illuminance way (too) high to make the reference cube clearly visible
illuminance: 100000.,
shadows_enabled: false,
..Default::default()
},
..Default::default()
});

let cube = meshes.add(Mesh::from(Cube { size: 1.0 }));
let mat = materials.add(Color::PURPLE.into());

let mut gradient = Gradient::new();
gradient.add_key(0.0, Vec4::new(0.0, 0.0, 1.0, 1.0));
gradient.add_key(1.0, Vec4::splat(0.0));

let effect = effects.add(
EffectAsset {
name: "effect".to_string(),
capacity: 512,
spawner: Spawner::rate(50.0.into()),
..Default::default()
}
.init(PositionSphereModifier {
center: Vec3::ZERO,
radius: 1.,
dimension: ShapeDimension::Volume,
speed: 2.0.into(),
})
.init(ParticleLifetimeModifier { lifetime: 12.0 })
.render(ColorOverLifetimeModifier { gradient }),
);

for j in -4..=4 {
for i in -5..=5 {
commands
.spawn()
.insert(Name::new(format!("({},{})", i, j)))
.insert_bundle(ParticleEffectBundle {
effect: ParticleEffect::new(effect.clone()),
transform: Transform::from_translation(Vec3::new(
i as f32 * 10.,
j as f32 * 10.,
0.,
)),
..Default::default()
})
.with_children(|p| {
// Reference cube to visualize the emit origin
p.spawn()
.insert_bundle(PbrBundle {
mesh: cube.clone(),
material: mat.clone(),
..Default::default()
})
.insert(Name::new("source"));
});
}
}
}
3 changes: 2 additions & 1 deletion run_examples.bat
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,5 @@ cargo r --example spawn_on_command --no-default-features --features="bevy/bevy_w
cargo r --example activate --no-default-features --features="bevy/bevy_winit bevy/bevy_pbr 3d"
cargo r --example force_field --no-default-features --features="bevy/bevy_winit bevy/bevy_pbr 3d"
cargo r --example 2d --no-default-features --features="bevy/bevy_winit bevy/bevy_sprite 2d"
cargo r --example lifetime --no-default-features --features="bevy/bevy_winit bevy/bevy_pbr 3d"
cargo r --example lifetime --no-default-features --features="bevy/bevy_winit bevy/bevy_pbr 3d"
cargo r --example instancing --no-default-features --features="bevy/bevy_winit bevy/bevy_pbr 3d"
6 changes: 5 additions & 1 deletion src/render/effect_cache.rs
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,11 @@ pub enum BufferState {

impl EffectBuffer {
/// Minimum buffer capacity to allocate, in number of particles.
pub const MIN_CAPACITY: u32 = 65536; // at least 64k particles
// FIXME - Batching is broken due to binding a single GpuSpawnerParam instead of N,
// and inability for a particle index to tell which Spawner it should use. Setting
// this to 1 effectively ensures that all new buffers just fit the effect, so batching
// never occurs.
pub const MIN_CAPACITY: u32 = 1; //65536; // at least 64k particles

/// Create a new group and a GPU buffer to back it up.
///
Expand Down
7 changes: 5 additions & 2 deletions src/render/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1396,6 +1396,7 @@ pub(crate) fn queue_effects(
resource: BindingResource::Buffer(BufferBinding {
buffer: effects_meta.spawner_buffer.buffer().unwrap(),
offset: 0,
// TODO - should bind N consecutive structs for batching
size: Some(GpuSpawnerParams::min_size()),
}),
}],
Expand Down Expand Up @@ -1980,14 +1981,16 @@ impl Node for ParticleUpdateNode {
assert!(
spawner_buffer_aligned >= GpuSpawnerParams::min_size().get() as usize
);
let spawner_offset = spawner_base * spawner_buffer_aligned as u32;

trace!(
"record commands for pipeline of effect {:?} ({} items / {}B/item = {} workgroups) spawner_base={} buffer_offset={}...",
"record commands for pipeline of effect {:?} ({} items / {}B/item = {} workgroups) spawner_base={} spawner_offset={} buffer_offset={}...",
batch.handle,
item_count,
item_size,
workgroup_count,
spawner_base,
spawner_offset,
buffer_offset,
);

Expand All @@ -2007,7 +2010,7 @@ impl Node for ParticleUpdateNode {
compute_pass.set_bind_group(
2,
effects_meta.spawner_bind_group.as_ref().unwrap(),
&[spawner_base * spawner_buffer_aligned as u32],
&[spawner_offset],
);
compute_pass.dispatch_workgroups(workgroup_count, 1, 1);
trace!("compute dispatched");
Expand Down

0 comments on commit 982e015

Please sign in to comment.