Skip to content

Commit

Permalink
Disable broken effect batching (#74)
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 authored Oct 29, 2022
1 parent e05af2b commit 0fa016d
Show file tree
Hide file tree
Showing 6 changed files with 115 additions and 6 deletions.
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,12 @@
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [Unreleased]

### Changed

- Disabled broken effect batching until #73 is fixed, to prevent triggering batching which breaks rendering.

## [0.4.1] 2022-10-28

### Fixed
Expand Down
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=warn,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"
10 changes: 7 additions & 3 deletions 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 Expand Up @@ -526,7 +530,7 @@ mod gpu_tests {
Some("my_buffer"),
);

assert_eq!(EffectBuffer::MIN_CAPACITY, buffer.capacity);
assert_eq!(buffer.capacity, capacity.max(EffectBuffer::MIN_CAPACITY));
assert_eq!(64, buffer.item_size);
assert_eq!(0, buffer.used_size);
assert!(buffer.free_slices.is_empty());
Expand Down Expand Up @@ -580,7 +584,7 @@ mod gpu_tests {
//let render_queue = renderer.queue();

let asset = Handle::weak(HandleId::random::<EffectAsset>());
let capacity = EffectBuffer::MIN_CAPACITY;
let capacity = 2048; //EffectBuffer::MIN_CAPACITY;
assert!(capacity >= 2048); // otherwise the logic below breaks
let item_size = 64;
let mut buffer = EffectBuffer::new(
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 0fa016d

Please sign in to comment.