1#define_import_path bevy_hanabi::vfx_common3struct SimParams {4 /// Delta time in seconds since last simulation tick.5 delta_time: f32,6 /// Time in seconds since the start of simulation.7 time: f32,8 /// Virtual delta time in seconds since last simulation tick.9 virtual_delta_time: f32,10 /// Virtual time in seconds since the start of simulation.11 virtual_time: f32,12 /// Real delta time in seconds since last simulation tick.13 real_delta_time: f32,14 /// Real time in seconds since the start of simulation.15 real_time: f32,16 /// Total number of effects to update this frame. Used by the indirect17 /// compute pipeline to cap the compute thread to the actual number of18 /// effects to process.19 num_effects: u32,20}22struct Spawner {23 /// Compressed transform of the emitter.24 transform: mat3x4<f32>, // transposed (row-major)25 /// Inverse compressed transform of the emitter.26 inverse_transform: mat3x4<f32>, // transposed (row-major)27 /// Number of particles to spawn this frame, as calculated by the CPU Spawner.28 ///29 /// This is only used if the effect is not a child effect (driven by GPU events).30 spawn: i32,31 /// PRNG seed for this effect instance. Currently this can change each time the32 /// effect is recompiled, and cannot be set deterministically (TODO).33 seed: u32,34 /// Index of the ping-pong buffer of particle indices to read particles from35 /// during rendering. Cached from EffectMetadata::ping after it's swapped36 /// in the indirect dispatch, because the EffectMetadata struct is used by GPU37 /// as an indirect draw source so cannot also be bound as regular storage38 /// buffer for reading.39 render_indirect_read_index: u32,40 /// Index of the [`EffectMetadata`] for this effect.41 effect_metadata_index: u32,42 /// Index of the [`DrawIndirectArgs`] or [`DrawIndexedIndirectArgs`] for this effect.43 draw_indirect_index: u32,44#ifdef SPAWNER_PADDING45 {{SPAWNER_PADDING}}46#endif47}49const SPAWNER_OFFSET_PONG: u32 = 27u;51/// Single row entry into an IndirectBuffer.52///53/// Each row corresponds to one row in the ParticleBuffer. The total number of rows in the54/// IndirectBuffer is the capacity of the ParticleBuffer itself (that is, they have the same55/// number of rows).56struct IndirectEntry {57 /// Ping-pong index into ParticleBuffer.58 particle_index: array<u32, 2>,59 /// Index into ParticleBuffer of a dead particle slot which can be recycled.60 dead_index: u32,61}63/// Indirection buffer storing the indices of alive and dead particles as a contiguous64/// range, to ensure we can dispatch a tight number of workgroups only for alive particles,65/// and can find a dead one to recycle quickly.66struct IndirectBuffer {67 rows: array<IndirectEntry>,68}70/// A event emitted by another effect during its update pass, to trigger the spawning71/// of one or more particle in this effect.72struct SpawnEvent {73 /// The particle index in the parent effect buffer of the source particle which74 /// triggered the event. This is used to inherit attributes like position or velocity.75 particle_index: u32,76}78/// Append buffer populated during the Update pass of the previous frame by a parent effect,79/// and read back by its child effect(s) during the Init pass of the next frame.80struct EventBuffer {81 /// The spawn events themselves.82 spawn_events: array<SpawnEvent>,83}85/// Info about a single child of a parent effect.86struct ChildInfo {87 /// Index of the effect's DispatchIndirectArgs entry in the global init indirect dispatch array.88 init_indirect_dispatch_index: u32,89 /// Number of events in the associated event buffer.90#ifdef CHILD_INFO_EVENT_COUNT_IS_ATOMIC91 event_count: atomic<i32>,92#else93 event_count: i32,94#endif95}97/// Buffer storing all the ChildInfo structs for several effects.98struct ChildInfoBuffer {99 /// The child info structs themselves.100 rows: array<ChildInfo>,101}103/// Indirect compute dispatch struct for GPU-driven passes. The layout of this struct is dictated by WGSL.104struct DispatchIndirectArgs {105 /// Number of workgroups. Each workgroup has exactly 64 threads.106 x: u32,107 /// Unused; always 1.108 y: u32,109 /// Unused; always 1.110 z: u32,111}113/// Stride in u32 count (4 bytes) of the DispatchIndirectArgs struct.114const DISPATCH_INDIRECT_STRIDE: u32 = 3u;116/// Indirect draw (non-indexed) dispatch struct for GPU-driven passes. The layout of this struct is dictated by WGSL.117/// See https://docs.rs/wgpu/latest/wgpu/util/struct.DrawIndirectArgs.html.118struct DrawIndirectArgs {119 vertex_count: u32,120 instance_count: atomic<u32>,121 first_vertex: u32,122 first_instance: u32,123}125/// Stride in u32 count (4 bytes) of the DrawIndirectArgs struct.126const DRAW_INDIRECT_STRIDE: u32 = 4u;128/// Indirect draw (non-indexed) dispatch struct for GPU-driven passes. The layout of this struct is dictated by WGSL.129/// See https://docs.rs/wgpu/latest/wgpu/util/struct.DrawIndexedIndirectArgs.html.130struct DrawIndexedIndirectArgs {131 index_count: u32,132 instance_count: atomic<u32>,133 first_index: u32,134 base_vertex: i32,135 first_instance: u32,136}138/// Stride in u32 count (4 bytes) of the DrawIndexedIndirectArgs struct.139const DRAW_INDEXED_INDIRECT_STRIDE: u32 = 5u;141// Effect metadata offsets. Used when accessing a tightly packed array of EffectMetadata142// as a raw array<u32>, so that we can avoid WGSL struct padding and keep data more compact143// in the GPU buffer. Each offset corresponds to a field in the EffectMetadata struct.144// Note that all fields are 4 bytes, so we can index by "number of 4-byte field".145const EM_OFFSET_CAPACITY: u32 = 0u;146const EM_OFFSET_ALIVE_COUNT: u32 = 1u;147const EM_OFFSET_MAX_UPDATE: u32 = 2u;148const EM_OFFSET_MAX_SPAWN: u32 = 3u;149const EM_OFFSET_INDIRECT_WRITE_INDEX: u32 = 4u;150const EM_OFFSET_INDIRECT_DISPATCH_INDEX: u32 = 5u;152/// Draw indirect parameters for GPU-driven rendering, and additional effect data.153struct EffectMetadata {154 /// Total number of particles for this effect. This is the capacity of the effect,155 /// in number of particles, which is used to sub-allocate various storages for this156 /// effect, notably in the ParticleBuffer and the IndirectBuffer. This is constant157 /// for the duration of the effect instance life.158 capacity: u32,160 /// Number of particles alive. Note that because particles can be simulated even161 /// when off-screen, in theory this could be greater than instance_count. Currently162 /// we don't have GPU culling, so in practice this remains strictly equal. But we163 /// store it separately 1) because this could change in the future, and 2) because164 /// the indirect render fields above should really be in their own buffer, not here.165 alive_count: atomic<u32>,166 /// Maximum number of update threads to run. This is cached from `alive_count`167 /// during the indirect dispatch, so that the update compute pass can cap its168 /// thread count while also modifying the actual `alive_count` if some particle169 /// dies during the update pass.170 max_update: u32,171 /// Maxmimum number of init threads to run on next frame. This is cached from172 /// `capacity - alive_count` during the indirect dispatch of the previous frame,173 /// so that the init compute pass can cap its thread count while also decrementing174 /// the actual dead count (increment the `alive_count`) as particles are spawned.175 max_spawn: atomic<u32>,177 /// Write index into the ping-pong buffer for particle indices. The buffers178 /// are swapped during the indirect dispatch (although the render pass still uses179 /// the complement of this to write, so technically ignores that swap).180 indirect_write_index: u32,181 /// Index of the [`GpuDispatchIndirect`] struct inside the global182 /// [`EffectsMeta::dispatch_indirect_buffer`].183 indirect_dispatch_index: u32,184 /// Index of the [`GpuRenderIndirect`] struct inside the global185 /// [`EffectsMeta::render_group_dispatch_buffer`].186 indirect_render_index: u32,187 /// Offset (in u32 count) of the init indirect dispatch struct inside its188 /// buffer. This avoids having to align those 16-byte structs to the GPU189 /// alignment (at least 32 bytes, even 256 bytes on some).190 init_indirect_dispatch_index: u32,191 /// Index of this effect into its parent's ChildInfo array192 /// ([`EffectChildren::effect_cache_ids`] and its associated GPU193 /// array). This starts at zero for the first child of each effect, and is194 /// only unique per parent, not globally. Only available if this effect is a195 /// child of another effect (i.e. if it has a parent).196 local_child_index: u32,197 /// For children, global index of the ChildInfo into the shared array.198 global_child_index: u32,199 /// For parents, base index of the their first ChildInfo into the shared array.200 base_child_index: u32,202 /// Particle stride, in number of u32.203 particle_stride: u32,204 /// Offset from the particle start to the first sort key, in number of u32.205 sort_key_offset: u32,206 /// Offset from the particle start to the second sort key, in number of u32.207 sort_key2_offset: u32,209 /// Atomic counter incremented each time a particle spawns. Useful for210 /// things like RIBBON_ID or any other use where a unique value is needed.211 /// The value loops back after some time, but unless some particle lives212 /// forever there's little chance of repetition.213 particle_counter: atomic<u32>,215 /// Padding for storage buffer alignment. This struct is sometimes bound as part216 /// of an array, or sometimes individually as a single unit. In the later case,217 /// we need it to be aligned to the GPU limits of the device. That limit is only218 /// known at runtime when initializing the WebGPU device.219 // FIXME - not anymore, but would be again with proper batching, so keep for now220 {{EFFECT_METADATA_PADDING}}221}223/// Stride, in u32 count, between elements of an array<EffectMetadata>.224const EFFECT_METADATA_STRIDE: u32 = {{EFFECT_METADATA_STRIDE}} / 4u;226var<private> seed : u32 = 0u;228const tau: f32 = 6.283185307179586476925286766559;230// Rand: PCG231// https://www.reedbeta.com/blog/hash-functions-for-gpu-rendering/232fn pcg_hash(input: u32) -> u32 {233 var state: u32 = input * 747796405u + 2891336453u;234 var word: u32 = ((state >> ((state >> 28u) + 4u)) ^ state) * 277803737u;235 return (word >> 22u) ^ word;236}238fn to_float01(u: u32) -> f32 {239 // Note: could generate only 24 bits of randomness240 return bitcast<f32>((u & 0x007fffffu) | 0x3f800000u) - 1.;241}243// Random floating-point number in [0:1]244fn frand() -> f32 {245 seed = pcg_hash(seed);246 return to_float01(pcg_hash(seed));247}249// Random floating-point number in [0:1]^2250fn frand2() -> vec2<f32> {251 seed = pcg_hash(seed);252 var x = to_float01(seed);253 seed = pcg_hash(seed);254 var y = to_float01(seed);255 return vec2<f32>(x, y);256}258// Random floating-point number in [0:1]^3259fn frand3() -> vec3<f32> {260 seed = pcg_hash(seed);261 var x = to_float01(seed);262 seed = pcg_hash(seed);263 var y = to_float01(seed);264 seed = pcg_hash(seed);265 var z = to_float01(seed);266 return vec3<f32>(x, y, z);267}269// Random floating-point number in [0:1]^4270fn frand4() -> vec4<f32> {271 // Each rand() produces 32 bits, and we need 24 bits per component,272 // so can get away with only 3 calls.273 var r0 = pcg_hash(seed);274 var r1 = pcg_hash(r0);275 var r2 = pcg_hash(r1);276 seed = r2;277 var x = to_float01(r0);278 var r01 = (r0 & 0xff000000u) >> 8u | (r1 & 0x0000ffffu);279 var y = to_float01(r01);280 var r12 = (r1 & 0xffff0000u) >> 8u | (r2 & 0x000000ffu);281 var z = to_float01(r12);282 var r22 = r2 >> 8u;283 var w = to_float01(r22);284 return vec4<f32>(x, y, z, w);285}287fn rand_uniform_f(a: f32, b: f32) -> f32 {288 return a + frand() * (b - a);289}291fn rand_uniform_vec2(a: vec2<f32>, b: vec2<f32>) -> vec2<f32> {292 return a + frand2() * (b - a);293}295fn rand_uniform_vec3(a: vec3<f32>, b: vec3<f32>) -> vec3<f32> {296 return a + frand3() * (b - a);297}299fn rand_uniform_vec4(a: vec4<f32>, b: vec4<f32>) -> vec4<f32> {300 return a + frand4() * (b - a);301}303// Normal distribution computed using Box-Muller transform304fn rand_normal_f(mean: f32, std_dev: f32) -> f32 {305 var u = frand();306 var v = frand();307 var r = sqrt(-2.0 * log(u));308 return mean + std_dev * r * cos(tau * v);309}311fn rand_normal_vec2(mean: vec2f, std_dev: vec2f) -> vec2f {312 var u = frand();313 var v = frand2();314 var r = sqrt(-2.0 * log(u));315 return mean + std_dev * r * cos(tau * v);316}318fn rand_normal_vec3(mean: vec3f, std_dev: vec3f) -> vec3f {319 var u = frand();320 var v = frand3();321 var r = sqrt(-2.0 * log(u));322 return mean + std_dev * r * cos(tau * v);323}325fn rand_normal_vec4(mean: vec4f, std_dev: vec4f) -> vec4f {326 var u = frand();327 var v = frand4();328 var r = sqrt(-2.0 * log(u));329 return mean + std_dev * r * cos(tau * v);330}332fn proj(u: vec3<f32>, v: vec3<f32>) -> vec3<f32> {333 return dot(v, u) / dot(u,u) * u;334}