Search across packages, versions, files, declarations, and comments. Use multiple terms to narrow results (AND), and add @vertex, @fragment, or @compute to filter by shader stage.
1#define_import_path bevy_hanabi::vfx_common
3struct 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 indirect
17 /// compute pipeline to cap the compute thread to the actual number of
18 /// effects to process.
19 num_effects: u32,
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 the
32 /// 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 from
35 /// during rendering. Cached from EffectMetadata::ping after it's swapped
36 /// in the indirect dispatch, because the EffectMetadata struct is used by GPU
37 /// as an indirect draw source so cannot also be bound as regular storage
38 /// 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 /// Start offset of the particles and indirect indices into the effect's
45 /// slab, in number of particles (row index).
46 slab_offset: u32,
47 /// Start offset of the particles and indirect indices into the parent effect's
48 /// slab (if the effect has a parent effect), in number of particles (row index).
49 /// This is ignored if the effect has no parent.
50 parent_slab_offset: u32,
51#ifdef SPAWNER_PADDING
52 {{SPAWNER_PADDING}}
53#endif
56const SPAWNER_OFFSET_PONG: u32 = 27u;
58/// Single row entry into an IndirectBuffer.
59///
60/// Each row corresponds to one row in the ParticleBuffer. The total number of rows in the
61/// IndirectBuffer is the capacity of the ParticleBuffer itself (that is, they have the same
62/// number of rows).
63struct IndirectEntry {
64 /// Ping-pong index into ParticleBuffer.
65 particle_index: array<u32, 2>,
66 /// Index into ParticleBuffer of a dead particle slot which can be recycled.
67 dead_index: u32,
70/// Indirection buffer storing the indices of alive and dead particles as a contiguous
71/// range, to ensure we can dispatch a tight number of workgroups only for alive particles,
72/// and can find a dead one to recycle quickly.
73struct IndirectBuffer {
74 rows: array<IndirectEntry>,
77/// A event emitted by another effect during its update pass, to trigger the spawning
78/// of one or more particle in this effect.
79struct SpawnEvent {
80 /// The particle index in the parent effect buffer of the source particle which
81 /// triggered the event. This is used to inherit attributes like position or velocity.
82 particle_index: u32,
85/// Append buffer populated during the Update pass of the previous frame by a parent effect,
86/// and read back by its child effect(s) during the Init pass of the next frame.
87struct EventBuffer {
88 /// The spawn events themselves.
89 spawn_events: array<SpawnEvent>,
92/// Info about a single child of a parent effect.
93struct ChildInfo {
94 /// Index of the effect's DispatchIndirectArgs entry in the global init indirect dispatch array.
95 init_indirect_dispatch_index: u32,
96 /// Number of events in the associated event buffer.
97#ifdef CHILD_INFO_EVENT_COUNT_IS_ATOMIC
98 event_count: atomic<i32>,
99#else
100 event_count: i32,
101#endif
104/// Buffer storing all the ChildInfo structs for several effects.
105struct ChildInfoBuffer {
106 /// The child info structs themselves.
107 rows: array<ChildInfo>,
110/// Indirect compute dispatch struct for GPU-driven passes. The layout of this struct is dictated by WGSL.
111struct DispatchIndirectArgs {
112 /// Number of workgroups. Each workgroup has exactly 64 threads.
113 x: u32,
114 /// Unused; always 1.
115 y: u32,
116 /// Unused; always 1.
117 z: u32,
120/// Indirect draw (non-indexed) dispatch struct for GPU-driven passes. The layout of this struct is dictated by WGSL.
121/// See https://docs.rs/wgpu/latest/wgpu/util/struct.DrawIndirectArgs.html.
122struct DrawIndirectArgs {
123 vertex_count: u32,
124 instance_count: atomic<u32>,
125 first_vertex: u32,
126 first_instance: u32,
129/// Stride in u32 count (4 bytes) of the DrawIndirectArgs struct.
130const DRAW_INDIRECT_STRIDE: u32 = 4u;
132/// Indirect draw (non-indexed) dispatch struct for GPU-driven passes. The layout of this struct is dictated by WGSL.
133/// See https://docs.rs/wgpu/latest/wgpu/util/struct.DrawIndexedIndirectArgs.html.
134struct DrawIndexedIndirectArgs {
135 index_count: u32,
136 instance_count: atomic<u32>,
137 first_index: u32,
138 base_vertex: i32,
139 first_instance: u32,
142/// Stride in u32 count (4 bytes) of the DrawIndexedIndirectArgs struct.
143const DRAW_INDEXED_INDIRECT_STRIDE: u32 = 5u;
145// Effect metadata offsets. Used when accessing a tightly packed array of EffectMetadata
146// as a raw array<u32>, so that we can avoid WGSL struct padding and keep data more compact
147// in the GPU buffer. Each offset corresponds to a field in the EffectMetadata struct.
148// Note that all fields are 4 bytes, so we can index by "number of 4-byte field".
149const EM_OFFSET_CAPACITY: u32 = 0u;
150const EM_OFFSET_ALIVE_COUNT: u32 = 1u;
151const EM_OFFSET_MAX_UPDATE: u32 = 2u;
152const EM_OFFSET_MAX_SPAWN: u32 = 3u;
153const EM_OFFSET_INDIRECT_WRITE_INDEX: u32 = 4u;
154const EM_OFFSET_INDIRECT_DISPATCH_INDEX: u32 = 5u;
156/// Draw indirect parameters for GPU-driven rendering, and additional effect data.
157struct EffectMetadata {
158 /// Total number of particles for this effect. This is the capacity of the effect,
159 /// in number of particles, which is used to sub-allocate various storages for this
160 /// effect, notably in the ParticleBuffer and the IndirectBuffer. This is constant
161 /// for the duration of the effect instance life.
162 capacity: u32,
164 /// Number of particles alive. Note that because particles can be simulated even
165 /// when off-screen, in theory this could be greater than instance_count. Currently
166 /// we don't have GPU culling, so in practice this remains strictly equal. But we
167 /// store it separately 1) because this could change in the future, and 2) because
168 /// the indirect render fields above should really be in their own buffer, not here.
169 alive_count: atomic<u32>,
170 /// Maximum number of update threads to run. This is cached from `alive_count`
171 /// during the indirect dispatch, so that the update compute pass can cap its
172 /// thread count while also modifying the actual `alive_count` if some particle
173 /// dies during the update pass.
174 max_update: u32,
175 /// Maxmimum number of init threads to run on next frame. This is cached from
176 /// `capacity - alive_count` during the indirect dispatch of the previous frame,
177 /// so that the init compute pass can cap its thread count while also decrementing
178 /// the actual dead count (increment the `alive_count`) as particles are spawned.
179 max_spawn: atomic<u32>,
181 /// Write index into the ping-pong buffer for particle indices. The buffers
182 /// are swapped during the indirect dispatch (although the render pass still uses
183 /// the complement of this to write, so technically ignores that swap).
184 indirect_write_index: u32,
185 /// Index of the [`GpuDispatchIndirect`] struct inside the global
186 /// [`EffectsMeta::dispatch_indirect_buffer`].
187 indirect_dispatch_index: u32,
188 /// Index of the [`GpuRenderIndirect`] struct inside the global
189 /// [`EffectsMeta::render_group_dispatch_buffer`].
190 indirect_render_index: u32,
191 /// Offset (in u32 count) of the init indirect dispatch struct inside its
192 /// buffer. This avoids having to align those 16-byte structs to the GPU
193 /// alignment (at least 32 bytes, even 256 bytes on some).
194 init_indirect_dispatch_index: u32,
195 /// Index of this effect into its parent's ChildInfo array
196 /// ([`EffectChildren::effect_cache_ids`] and its associated GPU
197 /// array). This starts at zero for the first child of each effect, and is
198 /// only unique per parent, not globally. Only available if this effect is a
199 /// child of another effect (i.e. if it has a parent).
200 local_child_index: u32,
201 /// For children, global index of the ChildInfo into the shared array.
202 global_child_index: u32,
203 /// For parents, base index of the their first ChildInfo into the shared array.
204 base_child_index: u32,
206 /// Particle stride, in number of u32.
207 particle_stride: u32,
208 /// Offset from the particle start to the first sort key, in number of u32.
209 sort_key_offset: u32,
210 /// Offset from the particle start to the second sort key, in number of u32.
211 sort_key2_offset: u32,
213 /// Atomic counter incremented each time a particle spawns. Useful for
214 /// things like RIBBON_ID or any other use where a unique value is needed.
215 /// The value loops back after some time, but unless some particle lives
216 /// forever there's little chance of repetition.
217 particle_counter: atomic<u32>,
219 /// Padding for storage buffer alignment. This struct is sometimes bound as part
220 /// of an array, or sometimes individually as a single unit. In the later case,
221 /// we need it to be aligned to the GPU limits of the device. That limit is only
222 /// known at runtime when initializing the WebGPU device.
223 {{EFFECT_METADATA_PADDING}}
226/// Stride, in u32 count, between elements of an array<EffectMetadata>.
227const EFFECT_METADATA_STRIDE: u32 = {{EFFECT_METADATA_STRIDE}} / 4u;
229var<private> seed : u32 = 0u;
231const tau: f32 = 6.283185307179586476925286766559;
233// Rand: PCG
234// https://www.reedbeta.com/blog/hash-functions-for-gpu-rendering/
235fn pcg_hash(input: u32) -> u32 {
236 var state: u32 = input * 747796405u + 2891336453u;
237 var word: u32 = ((state >> ((state >> 28u) + 4u)) ^ state) * 277803737u;
238 return (word >> 22u) ^ word;
241fn to_float01(u: u32) -> f32 {
242 // Note: could generate only 24 bits of randomness
243 return bitcast<f32>((u & 0x007fffffu) | 0x3f800000u) - 1.;
246// Random floating-point number in [0:1]
247fn frand() -> f32 {
248 seed = pcg_hash(seed);
249 return to_float01(pcg_hash(seed));
252// Random floating-point number in [0:1]^2
253fn frand2() -> vec2<f32> {
254 seed = pcg_hash(seed);
255 var x = to_float01(seed);
256 seed = pcg_hash(seed);
257 var y = to_float01(seed);
258 return vec2<f32>(x, y);
261// Random floating-point number in [0:1]^3
262fn frand3() -> vec3<f32> {
263 seed = pcg_hash(seed);
264 var x = to_float01(seed);
265 seed = pcg_hash(seed);
266 var y = to_float01(seed);
267 seed = pcg_hash(seed);
268 var z = to_float01(seed);
269 return vec3<f32>(x, y, z);
272// Random floating-point number in [0:1]^4
273fn frand4() -> vec4<f32> {
274 // Each rand() produces 32 bits, and we need 24 bits per component,
275 // so can get away with only 3 calls.
276 var r0 = pcg_hash(seed);
277 var r1 = pcg_hash(r0);
278 var r2 = pcg_hash(r1);
279 seed = r2;
280 var x = to_float01(r0);
281 var r01 = (r0 & 0xff000000u) >> 8u | (r1 & 0x0000ffffu);
282 var y = to_float01(r01);
283 var r12 = (r1 & 0xffff0000u) >> 8u | (r2 & 0x000000ffu);
284 var z = to_float01(r12);
285 var r22 = r2 >> 8u;
286 var w = to_float01(r22);
287 return vec4<f32>(x, y, z, w);
290fn rand_uniform_f(a: f32, b: f32) -> f32 {
291 return a + frand() * (b - a);
294fn rand_uniform_vec2(a: vec2<f32>, b: vec2<f32>) -> vec2<f32> {
295 return a + frand2() * (b - a);
298fn rand_uniform_vec3(a: vec3<f32>, b: vec3<f32>) -> vec3<f32> {
299 return a + frand3() * (b - a);
302fn rand_uniform_vec4(a: vec4<f32>, b: vec4<f32>) -> vec4<f32> {
303 return a + frand4() * (b - a);
306// Normal distribution computed using Box-Muller transform
307fn rand_normal_f(mean: f32, std_dev: f32) -> f32 {
308 var u = frand();
309 var v = frand();
310 var r = sqrt(-2.0 * log(u));
311 return mean + std_dev * r * cos(tau * v);
314fn rand_normal_vec2(mean: vec2f, std_dev: vec2f) -> vec2f {
315 var u = frand();
316 var v = frand2();
317 var r = sqrt(-2.0 * log(u));
318 return mean + std_dev * r * cos(tau * v);
321fn rand_normal_vec3(mean: vec3f, std_dev: vec3f) -> vec3f {
322 var u = frand();
323 var v = frand3();
324 var r = sqrt(-2.0 * log(u));
325 return mean + std_dev * r * cos(tau * v);
328fn rand_normal_vec4(mean: vec4f, std_dev: vec4f) -> vec4f {
329 var u = frand();
330 var v = frand4();
331 var r = sqrt(-2.0 * log(u));
332 return mean + std_dev * r * cos(tau * v);
335fn proj(u: vec3<f32>, v: vec3<f32>) -> vec3<f32> {
336 return dot(v, u) / dot(u,u) * u;
Contents