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