1/*
2* Copyright 2019 Google LLC
3*
4* Use of this source code is governed by a BSD-style license that can be
5* found in the LICENSE file.
6*/
7
8#include "modules/particles/include/SkParticleEffect.h"
9
10#include "include/core/SkPaint.h"
11#include "modules/particles/include/SkParticleBinding.h"
12#include "modules/particles/include/SkParticleDrawable.h"
13#include "modules/particles/include/SkReflected.h"
14#include "modules/skresources/include/SkResources.h"
15#include "src/sksl/SkSLByteCode.h"
16#include "src/sksl/SkSLCompiler.h"
17
18static inline float bits_to_float(uint32_t u) {
19 float f;
20 memcpy(&f, &u, sizeof(uint32_t));
21 return f;
22}
23
24static inline uint32_t float_to_bits(float f) {
25 uint32_t u;
26 memcpy(&u, &f, sizeof(uint32_t));
27 return u;
28}
29
30static const char* kCommonHeader =
31R"(
32struct Effect {
33 float age;
34 float lifetime;
35 int loop;
36 float rate;
37 int burst;
38
39 float2 pos;
40 float2 dir;
41 float scale;
42 float2 vel;
43 float spin;
44 float4 color;
45 float frame;
46 uint flags;
47 uint seed;
48};
49
50uniform float dt;
51
52// We use the LCG from Numerical Recipes. It's not great, but has nice properties for our situation:
53// It's fast (just integer mul + add), and only uses vectorized instructions. The modulus is 2^32,
54// so we can just rely on uint overflow. All output bits are used, again that simplifies the usage
55// here (although we shift down so that our float divisor retains precision).
56float rand(inout uint seed) {
57 seed = seed * 1664525 + 1013904223;
58 return float(seed >> 4) / 0xFFFFFFF;
59}
60)";
61
62static const char* kParticleHeader =
63R"(
64struct Particle {
65 float age;
66 float lifetime;
67 float2 pos;
68 float2 dir;
69 float scale;
70 float2 vel;
71 float spin;
72 float4 color;
73 float frame;
74 uint flags;
75 uint seed;
76};
77
78uniform Effect effect;
79)";
80
81static const char* kDefaultEffectCode =
82R"(void effectSpawn(inout Effect effect) {
83}
84
85void effectUpdate(inout Effect effect) {
86}
87)";
88
89static const char* kDefaultParticleCode =
90R"(void spawn(inout Particle p) {
91}
92
93void update(inout Particle p) {
94}
95)";
96
97SkParticleEffectParams::SkParticleEffectParams()
98 : fMaxCount(128)
99 , fDrawable(nullptr)
100 , fEffectCode(kDefaultEffectCode)
101 , fParticleCode(kDefaultParticleCode) {}
102
103void SkParticleEffectParams::visitFields(SkFieldVisitor* v) {
104 v->visit("MaxCount", fMaxCount);
105 v->visit("Drawable", fDrawable);
106 v->visit("EffectCode", fEffectCode);
107 v->visit("Code", fParticleCode);
108 v->visit("Bindings", fBindings);
109}
110
111void SkParticleEffectParams::prepare(const skresources::ResourceProvider* resourceProvider) {
112 for (auto& binding : fBindings) {
113 if (binding) {
114 binding->prepare(resourceProvider);
115 }
116 }
117 if (fDrawable) {
118 fDrawable->prepare(resourceProvider);
119 }
120
121 auto buildProgram = [this](const SkSL::String& code, Program* p) {
122 SkSL::Compiler compiler;
123 SkSL::Program::Settings settings;
124
125 SkTArray<std::unique_ptr<SkParticleExternalValue>> externalValues;
126
127 for (const auto& binding : fBindings) {
128 if (binding) {
129 auto value = binding->toValue(compiler);
130 compiler.registerExternalValue(value.get());
131 externalValues.push_back(std::move(value));
132 }
133 }
134
135 auto program = compiler.convertProgram(SkSL::Program::kGeneric_Kind, code, settings);
136 if (!program) {
137 SkDebugf("%s\n", compiler.errorText().c_str());
138 return;
139 }
140
141 auto byteCode = compiler.toByteCode(*program);
142 if (!byteCode) {
143 SkDebugf("%s\n", compiler.errorText().c_str());
144 return;
145 }
146
147 p->fByteCode = std::move(byteCode);
148 p->fExternalValues.swap(externalValues);
149 };
150
151 SkSL::String effectCode(kCommonHeader);
152 effectCode.append(fEffectCode.c_str());
153
154 SkSL::String particleCode(kCommonHeader);
155 particleCode.append(kParticleHeader);
156 particleCode.append(fParticleCode.c_str());
157
158 buildProgram(effectCode, &fEffectProgram);
159 buildProgram(particleCode, &fParticleProgram);
160}
161
162SkParticleEffect::SkParticleEffect(sk_sp<SkParticleEffectParams> params)
163 : fParams(std::move(params))
164 , fLooping(false)
165 , fCount(0)
166 , fLastTime(-1.0)
167 , fSpawnRemainder(0.0f) {
168 fState.fAge = -1.0f;
169 this->setCapacity(fParams->fMaxCount);
170}
171
172void SkParticleEffect::start(double now, bool looping, SkPoint position, SkVector heading,
173 float scale, SkVector velocity, float spin, SkColor4f color,
174 float frame, uint32_t flags, uint32_t seed) {
175 fCount = 0;
176 fLastTime = now;
177 fSpawnRemainder = 0.0f;
178 fLooping = looping;
179
180 fState.fAge = 0.0f;
181
182 // A default lifetime makes sense - many effects are simple loops that don't really care.
183 // Every effect should define its own rate of emission, or only use bursts, so leave that as
184 // zero initially.
185 fState.fLifetime = 1.0f;
186 fState.fLoopCount = 0;
187 fState.fRate = 0.0f;
188 fState.fBurst = 0;
189
190 fState.fPosition = position;
191 fState.fHeading = heading;
192 fState.fScale = scale;
193 fState.fVelocity = velocity;
194 fState.fSpin = spin;
195 fState.fColor = color;
196 fState.fFrame = frame;
197 fState.fFlags = flags;
198 fState.fRandom = seed;
199
200 // Defer running effectSpawn until the first update (to reuse the code when looping)
201}
202
203// Numerical Recipes
204static uint32_t nr_rand(uint32_t x) {
205 return x * 1664525 + 1013904223;
206}
207
208// Spawns new effects that were requested by any *effect* script (copies default values from
209// the current effect state).
210void SkParticleEffect::processEffectSpawnRequests(double now) {
211 for (const auto& spawnReq : fSpawnRequests) {
212 sk_sp<SkParticleEffect> newEffect(new SkParticleEffect(std::move(spawnReq.fParams)));
213 fState.fRandom = nr_rand(fState.fRandom);
214
215 newEffect->start(now, spawnReq.fLoop, fState.fPosition, fState.fHeading, fState.fScale,
216 fState.fVelocity, fState.fSpin, fState.fColor, fState.fFrame,
217 fState.fFlags, fState.fRandom);
218 fSubEffects.push_back(std::move(newEffect));
219 }
220 fSpawnRequests.reset();
221}
222
223void SkParticleEffect::runEffectScript(double now, const char* entry) {
224 if (const auto& byteCode = fParams->fEffectProgram.fByteCode) {
225 if (auto fun = byteCode->getFunction(entry)) {
226 for (const auto& value : fParams->fEffectProgram.fExternalValues) {
227 value->setEffect(this);
228 }
229 SkAssertResult(byteCode->run(fun, &fState.fAge, sizeof(EffectState) / sizeof(float),
230 nullptr, 0,
231 fEffectUniforms.data(), fEffectUniforms.count()));
232 this->processEffectSpawnRequests(now);
233 }
234 }
235}
236
237void SkParticleEffect::processParticleSpawnRequests(double now, int start) {
238 const auto& data = fParticles.fData;
239 for (const auto& spawnReq : fSpawnRequests) {
240 int idx = start + spawnReq.fIndex;
241 sk_sp<SkParticleEffect> newEffect(new SkParticleEffect(std::move(spawnReq.fParams)));
242 newEffect->start(now, spawnReq.fLoop,
243 { data[SkParticles::kPositionX ][idx],
244 data[SkParticles::kPositionY ][idx] },
245 { data[SkParticles::kHeadingX ][idx],
246 data[SkParticles::kHeadingY ][idx] },
247 data[SkParticles::kScale ][idx],
248 { data[SkParticles::kVelocityX ][idx],
249 data[SkParticles::kVelocityY ][idx] },
250 data[SkParticles::kVelocityAngular][idx],
251 { data[SkParticles::kColorR ][idx],
252 data[SkParticles::kColorG ][idx],
253 data[SkParticles::kColorB ][idx],
254 data[SkParticles::kColorA ][idx] },
255 data[SkParticles::kSpriteFrame ][idx],
256 float_to_bits(data[SkParticles::kFlags ][idx]),
257 float_to_bits(data[SkParticles::kRandom ][idx]));
258 fSubEffects.push_back(std::move(newEffect));
259 }
260 fSpawnRequests.reset();
261}
262
263void SkParticleEffect::runParticleScript(double now, const char* entry, int start, int count) {
264 if (const auto& byteCode = fParams->fParticleProgram.fByteCode) {
265 if (auto fun = byteCode->getFunction(entry)) {
266 float* args[SkParticles::kNumChannels];
267 for (int i = 0; i < SkParticles::kNumChannels; ++i) {
268 args[i] = fParticles.fData[i].get() + start;
269 }
270 for (const auto& value : fParams->fParticleProgram.fExternalValues) {
271 value->setEffect(this);
272 }
273 memcpy(&fParticleUniforms[1], &fState.fAge, sizeof(EffectState));
274 SkAssertResult(byteCode->runStriped(fun, count, args, SkParticles::kNumChannels,
275 nullptr, 0,
276 fParticleUniforms.data(),
277 fParticleUniforms.count()));
278 this->processParticleSpawnRequests(now, start);
279 }
280 }
281}
282
283void SkParticleEffect::advanceTime(double now) {
284 // TODO: Sub-frame spawning. Tricky with script driven position. Supply variable effect.age?
285 // Could be done if effect.age were an external value that offset by particle lane, perhaps.
286 float deltaTime = static_cast<float>(now - fLastTime);
287 if (deltaTime <= 0.0f) {
288 return;
289 }
290 fLastTime = now;
291
292 // Handle user edits to fMaxCount
293 if (fParams->fMaxCount != fCapacity) {
294 this->setCapacity(fParams->fMaxCount);
295 }
296
297 // Ensure our storage block for uniforms are large enough
298 auto resizeWithZero = [](SkTArray<float, true>* uniforms, const SkSL::ByteCode* byteCode) {
299 if (byteCode) {
300 int newCount = byteCode->getUniformSlotCount();
301 if (newCount > uniforms->count()) {
302 uniforms->push_back_n(newCount - uniforms->count(), 0.0f);
303 } else {
304 uniforms->resize(newCount);
305 }
306 }
307 };
308 resizeWithZero(&fEffectUniforms, this->effectCode());
309 resizeWithZero(&fParticleUniforms, this->particleCode());
310
311 // Copy known values into the uniform blocks
312 SkASSERT(!this->effectCode() || this->effectCode()->getUniformLocation("dt") == 0);
313 SkASSERT(!this->particleCode() || this->particleCode()->getUniformLocation("dt") == 0);
314 SkASSERT(!this->particleCode() || this->particleCode()->getUniformLocation("effect.age") == 1);
315 fEffectUniforms[0] = deltaTime;
316 fParticleUniforms[0] = deltaTime;
317
318 // Is this the first update after calling start()?
319 // Run 'effectSpawn' to set initial emitter properties.
320 if (fState.fAge == 0.0f && fState.fLoopCount == 0) {
321 this->runEffectScript(now, "effectSpawn");
322 }
323
324 fState.fAge += deltaTime / fState.fLifetime;
325 if (fState.fAge > 1) {
326 // We always run effectDeath when age crosses 1, whether we're looping or actually dying
327 this->runEffectScript(now, "effectDeath");
328
329 if (fLooping) {
330 // If we looped, then run effectSpawn again (with the updated loop count)
331 fState.fLoopCount += sk_float_floor2int(fState.fAge);
332 fState.fAge = fmodf(fState.fAge, 1.0f);
333 this->runEffectScript(now, "effectSpawn");
334 } else {
335 // Effect is dead if we've reached the end (and are not looping)
336 return;
337 }
338 }
339
340 // Advance age for existing particles, shuffle all dying particles to the end of the arrays
341 int numDyingParticles = 0;
342 for (int i = 0; i < fCount; ++i) {
343 fParticles.fData[SkParticles::kAge][i] +=
344 fParticles.fData[SkParticles::kLifetime][i] * deltaTime;
345 if (fParticles.fData[SkParticles::kAge][i] > 1.0f) {
346 // NOTE: This is fast, but doesn't preserve drawing order. Could be a problem...
347 for (int j = 0; j < SkParticles::kNumChannels; ++j) {
348 std::swap(fParticles.fData[j][i], fParticles.fData[j][fCount - 1]);
349 }
350 std::swap(fStableRandoms[i], fStableRandoms[fCount - 1]);
351 --i;
352 --fCount;
353 ++numDyingParticles;
354 }
355 }
356
357 // Run the death script for all particles that just died
358 this->runParticleScript(now, "death", fCount, numDyingParticles);
359
360 // Run 'effectUpdate' to adjust emitter properties
361 this->runEffectScript(now, "effectUpdate");
362
363 // Do integration of effect position and orientation
364 {
365 fState.fPosition += fState.fVelocity * deltaTime;
366 float s = sk_float_sin(fState.fSpin * deltaTime),
367 c = sk_float_cos(fState.fSpin * deltaTime);
368 // Using setNormalize to prevent scale drift
369 fState.fHeading.setNormalize(fState.fHeading.fX * c - fState.fHeading.fY * s,
370 fState.fHeading.fX * s + fState.fHeading.fY * c);
371 }
372
373 // Spawn new particles
374 float desired = fState.fRate * deltaTime + fSpawnRemainder + fState.fBurst;
375 fState.fBurst = 0;
376 int numToSpawn = sk_float_round2int(desired);
377 fSpawnRemainder = desired - numToSpawn;
378 numToSpawn = SkTPin(numToSpawn, 0, fParams->fMaxCount - fCount);
379 if (numToSpawn) {
380 const int spawnBase = fCount;
381
382 for (int i = 0; i < numToSpawn; ++i) {
383 // Mutate our random seed so each particle definitely gets a different generator
384 fState.fRandom = nr_rand(fState.fRandom);
385 fParticles.fData[SkParticles::kAge ][fCount] = 0.0f;
386 fParticles.fData[SkParticles::kLifetime ][fCount] = 0.0f;
387 fParticles.fData[SkParticles::kPositionX ][fCount] = fState.fPosition.fX;
388 fParticles.fData[SkParticles::kPositionY ][fCount] = fState.fPosition.fY;
389 fParticles.fData[SkParticles::kHeadingX ][fCount] = fState.fHeading.fX;
390 fParticles.fData[SkParticles::kHeadingY ][fCount] = fState.fHeading.fY;
391 fParticles.fData[SkParticles::kScale ][fCount] = fState.fScale;
392 fParticles.fData[SkParticles::kVelocityX ][fCount] = fState.fVelocity.fX;
393 fParticles.fData[SkParticles::kVelocityY ][fCount] = fState.fVelocity.fY;
394 fParticles.fData[SkParticles::kVelocityAngular][fCount] = fState.fSpin;
395 fParticles.fData[SkParticles::kColorR ][fCount] = fState.fColor.fR;
396 fParticles.fData[SkParticles::kColorG ][fCount] = fState.fColor.fG;
397 fParticles.fData[SkParticles::kColorB ][fCount] = fState.fColor.fB;
398 fParticles.fData[SkParticles::kColorA ][fCount] = fState.fColor.fA;
399 fParticles.fData[SkParticles::kSpriteFrame ][fCount] = fState.fFrame;
400 fParticles.fData[SkParticles::kFlags ][fCount] = bits_to_float(fState.fFlags);
401 fParticles.fData[SkParticles::kRandom ][fCount] = bits_to_float(fState.fRandom);
402 fCount++;
403 }
404
405 // Run the spawn script
406 this->runParticleScript(now, "spawn", spawnBase, numToSpawn);
407
408 // Now stash copies of the random seeds and compute inverse particle lifetimes
409 // (so that subsequent updates are faster)
410 for (int i = spawnBase; i < fCount; ++i) {
411 fParticles.fData[SkParticles::kLifetime][i] =
412 sk_ieee_float_divide(1.0f, fParticles.fData[SkParticles::kLifetime][i]);
413 fStableRandoms[i] = fParticles.fData[SkParticles::kRandom][i];
414 }
415 }
416
417 // Restore all stable random seeds so update scripts get consistent behavior each frame
418 for (int i = 0; i < fCount; ++i) {
419 fParticles.fData[SkParticles::kRandom][i] = fStableRandoms[i];
420 }
421
422 // Run the update script
423 this->runParticleScript(now, "update", 0, fCount);
424
425 // Do fixed-function update work (integration of position and orientation)
426 for (int i = 0; i < fCount; ++i) {
427 fParticles.fData[SkParticles::kPositionX][i] +=
428 fParticles.fData[SkParticles::kVelocityX][i] * deltaTime;
429 fParticles.fData[SkParticles::kPositionY][i] +=
430 fParticles.fData[SkParticles::kVelocityY][i] * deltaTime;
431
432 float spin = fParticles.fData[SkParticles::kVelocityAngular][i];
433 float s = sk_float_sin(spin * deltaTime),
434 c = sk_float_cos(spin * deltaTime);
435 float oldHeadingX = fParticles.fData[SkParticles::kHeadingX][i],
436 oldHeadingY = fParticles.fData[SkParticles::kHeadingY][i];
437 fParticles.fData[SkParticles::kHeadingX][i] = oldHeadingX * c - oldHeadingY * s;
438 fParticles.fData[SkParticles::kHeadingY][i] = oldHeadingX * s + oldHeadingY * c;
439 }
440}
441
442void SkParticleEffect::update(double now) {
443 if (this->isAlive(false)) {
444 this->advanceTime(now);
445 }
446
447 // Now update all of our sub-effects, removing any that have died
448 for (int i = 0; i < fSubEffects.count(); ++i) {
449 fSubEffects[i]->update(now);
450 if (!fSubEffects[i]->isAlive()) {
451 fSubEffects[i] = fSubEffects.back();
452 fSubEffects.pop_back();
453 --i;
454 }
455 }
456}
457
458void SkParticleEffect::draw(SkCanvas* canvas) {
459 if (this->isAlive(false) && fParams->fDrawable) {
460 SkPaint paint;
461 paint.setFilterQuality(SkFilterQuality::kMedium_SkFilterQuality);
462 fParams->fDrawable->draw(canvas, fParticles, fCount, paint);
463 }
464
465 for (const auto& subEffect : fSubEffects) {
466 subEffect->draw(canvas);
467 }
468}
469
470void SkParticleEffect::setCapacity(int capacity) {
471 for (int i = 0; i < SkParticles::kNumChannels; ++i) {
472 fParticles.fData[i].realloc(capacity);
473 }
474 fStableRandoms.realloc(capacity);
475
476 fCapacity = capacity;
477 fCount = std::min(fCount, fCapacity);
478}
479
480void SkParticleEffect::RegisterParticleTypes() {
481 REGISTER_REFLECTED(SkReflected);
482 SkParticleBinding::RegisterBindingTypes();
483 SkParticleDrawable::RegisterDrawableTypes();
484}
485