Skip to content

Commit 3c7a6d0

Browse files
authored
Add EffectAsset::prng_seed (#427)
Add a new field to the effect asset, which determines the seed for the GPU side PRNG used in shader expressions. This value defaults to zero, making consecutive runs deterministic. This both gives better artistic control (what executes at runtime is what was authored), and helps with debugging by making repros consistent. To restore the old behavior, assign a random value to the seed when creating the effect asset, _.e.g_ `prng_seed = rand::random::<u32>()`.
1 parent 06b160f commit 3c7a6d0

File tree

5 files changed

+46
-17
lines changed

5 files changed

+46
-17
lines changed

CHANGELOG.md

+3
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
3535
- Added new `InheritAttributeModifier` to set the value of a particle attribute by copying the value of its parent.
3636
This modifier is only valid for the `ModifierContext::Init` pass,
3737
when an effect has a parent (uses `EffectParent`).
38+
- Added `EffectAsset::prng_seed` used as the PRNG seed for random expressions on GPU.
39+
Previously the PRNG seed was implicitly set to a random value.
40+
To restore the former behavior, just set `prng_seed = rand::random::<u32>()`.
3841

3942
### Changed
4043

docs/migration-v0.14-to-v0.15.md

+18
Original file line numberDiff line numberDiff line change
@@ -155,3 +155,21 @@ and use multiple ribbons in the same effect.
155155
Following Bevy's own deprecation of the bundle mechanism, `ParticleEffectBundle` has been removed.
156156
Use `ParticleEffect` directly instead, which now supports the ECS `#[require()]` mechanism,
157157
and will automatically add the mandatory components `CompiledParticleEffect`, `Visibility`, and `Transform`.
158+
159+
## Deterministic randomness
160+
161+
Effects using GPU expressions with randomness, like the built-in expression obtained from `ExprWriter::rand()`,
162+
now use an explicit PRNG seed set stored in `EffectAsset::prng_seed`.
163+
This value is `0` by default, meaning the effect will produce the same result each application run.
164+
This ensures the effect authored is played back deterministically, which gives artistic control,
165+
and is also useful to generate deterministic repro examples of bugs for debugging.
166+
167+
If you want true randomness (old behavior), simply assign this new field to a random value yourself,
168+
for example:
169+
170+
```rust
171+
EffectAsset {
172+
prng_seed: rand::random::<u32>(),
173+
// [...]
174+
}
175+
```

src/asset.rs

+5
Original file line numberDiff line numberDiff line change
@@ -295,6 +295,10 @@ pub struct EffectAsset {
295295
pub simulation_space: SimulationSpace,
296296
/// Condition under which the effect is simulated.
297297
pub simulation_condition: SimulationCondition,
298+
/// Seed for the pseudo-random number generator.
299+
///
300+
/// This is uploaded to GPU and used for the various random expressions and quantities computed in shaders.
301+
pub prng_seed: u32,
298302
/// Init modifier defining the effect.
299303
#[reflect(ignore)]
300304
// TODO - Can't manage to implement FromReflect for BoxedModifier in a nice way yet
@@ -870,6 +874,7 @@ mod tests {
870874
z_layer_2d: 0.0,
871875
simulation_space: Global,
872876
simulation_condition: WhenVisible,
877+
prng_seed: 0,
873878
init_modifiers: [
874879
{
875880
"SetAttributeModifier": (

src/lib.rs

+6-2
Original file line numberDiff line numberDiff line change
@@ -593,8 +593,7 @@ impl From<&PropertyInstance> for PropertyValue {
593593
/// make use of the Bevy visibility system and optimize rendering of the
594594
/// effects. This influences simulation when using
595595
/// [`SimulationCondition::WhenVisible`].
596-
/// - A [`Transform`] component to define the position of the particle
597-
/// emitter.
596+
/// - A [`Transform`] component to define the position of the particle emitter.
598597
///
599598
/// ## Optional components
600599
///
@@ -1331,7 +1330,10 @@ pub struct CompiledParticleEffect {
13311330
layout_flags: LayoutFlags,
13321331
/// Alpha mode.
13331332
alpha_mode: AlphaMode,
1333+
/// Particle layout of the parent effect, if any.
13341334
parent_particle_layout: Option<ParticleLayout>,
1335+
/// PRNG seed.
1336+
prng_seed: u32,
13351337
}
13361338

13371339
impl Default for CompiledParticleEffect {
@@ -1349,6 +1351,7 @@ impl Default for CompiledParticleEffect {
13491351
layout_flags: LayoutFlags::NONE,
13501352
alpha_mode: default(),
13511353
parent_particle_layout: None,
1354+
prng_seed: 0,
13521355
}
13531356
}
13541357
}
@@ -1395,6 +1398,7 @@ impl CompiledParticleEffect {
13951398
// diff what may or may not have changed.
13961399
self.asset = instance.handle.clone();
13971400
self.simulation_condition = asset.simulation_condition;
1401+
self.prng_seed = asset.prng_seed;
13981402

13991403
// Check if the instance changed. If so, rebuild some data from this compiled
14001404
// effect based on the new data of the effect instance.

src/render/mod.rs

+14-15
Original file line numberDiff line numberDiff line change
@@ -2139,7 +2139,7 @@ impl SpecializedRenderPipeline for ParticlesRenderPipeline {
21392139
/// render world item.
21402140
///
21412141
/// [`ParticleEffect`]: crate::ParticleEffect
2142-
#[derive(Debug, Component)]
2142+
#[derive(Debug)]
21432143
pub(crate) struct ExtractedEffect {
21442144
/// Main world entity owning the [`CompiledParticleEffect`] this effect was
21452145
/// extracted from. Mainly used for visibility.
@@ -2166,14 +2166,13 @@ pub(crate) struct ExtractedEffect {
21662166
///
21672167
/// [`property_layout`]: crate::render::ExtractedEffect::property_layout
21682168
pub property_data: Option<Vec<u8>>,
2169-
/// Effect spawner.
2170-
///
2171-
/// Obtained from calling [`EffectSpawner::tick()`] on the source effect
2172-
/// instance.
2169+
/// Number of particles to spawn this frame.
21732170
///
2174-
/// [`EffectSpawner::tick()`]: crate::EffectSpawner::tick
2175-
pub effect_spawner: EffectSpawner, /* TODO - move to separate component instead of
2176-
* extracting into this struct? */
2171+
/// This is ignored if the effect is a child effect consuming GPU spawn
2172+
/// events.
2173+
pub spawn_count: u32,
2174+
/// PRNG seed.
2175+
pub prng_seed: u32,
21772176
/// Global transform of the effect origin.
21782177
pub transform: GlobalTransform,
21792178
/// Layout flags.
@@ -2564,7 +2563,8 @@ pub(crate) fn extract_effects(
25642563
particle_layout: asset.particle_layout().clone(),
25652564
property_layout,
25662565
property_data,
2567-
effect_spawner: *effect_spawner,
2566+
spawn_count: effect_spawner.spawn_count,
2567+
prng_seed: compiled_effect.prng_seed,
25682568
transform: *transform,
25692569
layout_flags,
25702570
texture_layout,
@@ -2970,6 +2970,7 @@ impl EffectsMeta {
29702970
&mut self,
29712971
global_transform: &GlobalTransform,
29722972
spawn_count: u32,
2973+
prng_seed: u32,
29732974
effect_metadata_buffer_table_id: BufferTableId,
29742975
) -> u32 {
29752976
let spawner_base = self.spawner_buffer.len() as u32;
@@ -2980,14 +2981,11 @@ impl EffectsMeta {
29802981
global_transform.affine().inverse(),
29812982
)
29822983
.into();
2983-
// FIXME - Probably bad to re-seed each time there's a change (actually, each
2984-
// frame even!)
2985-
let seed = 0; //rand::random::<u32>();
29862984
let spawner_params = GpuSpawnerParams {
29872985
transform,
29882986
inverse_transform,
29892987
spawn: spawn_count as i32,
2990-
seed,
2988+
seed: prng_seed,
29912989
effect_metadata_index: effect_metadata_buffer_table_id.0,
29922990
..default()
29932991
};
@@ -3935,7 +3933,8 @@ pub(crate) fn prepare_effects(
39353933

39363934
let spawner_index = effects_meta.allocate_spawner(
39373935
&extracted_effect.transform,
3938-
extracted_effect.effect_spawner.spawn_count,
3936+
extracted_effect.spawn_count,
3937+
extracted_effect.prng_seed,
39393938
dispatch_buffer_indices.effect_metadata_buffer_table_id,
39403939
);
39413940

@@ -3962,7 +3961,7 @@ pub(crate) fn prepare_effects(
39623961
particle_layout: extracted_effect.particle_layout.clone(),
39633962
shaders: extracted_effect.effect_shaders,
39643963
spawner_base: spawner_index,
3965-
spawn_count: extracted_effect.effect_spawner.spawn_count,
3964+
spawn_count: extracted_effect.spawn_count,
39663965
#[cfg(feature = "3d")]
39673966
position: extracted_effect.transform.translation(),
39683967
init_indirect_dispatch_index: cached_child_info

0 commit comments

Comments
 (0)