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 | |
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 | |
162 | SkParticleEffect::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 | |
172 | void 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 |
204 | static 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). |
210 | void 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 | |
223 | void 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 | |
237 | void 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 | |
263 | void 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 | |
283 | void 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 | |
442 | void 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 | |
458 | void 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 | |
470 | void 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 | |
480 | void SkParticleEffect::RegisterParticleTypes() { |
481 | REGISTER_REFLECTED(SkReflected); |
482 | SkParticleBinding::RegisterBindingTypes(); |
483 | SkParticleDrawable::RegisterDrawableTypes(); |
484 | } |
485 | |