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/SkCanvas.h"
11 #include "include/core/SkContourMeasure.h"
12 #include "include/core/SkPaint.h"
13 #include "include/core/SkPath.h"
14 #include "include/core/SkRSXform.h"
15 #include "include/private/SkColorData.h"
16 #include "include/utils/SkParsePath.h"
17 #include "include/utils/SkTextUtils.h"
18 #include "modules/particles/include/SkCurve.h"
19 #include "modules/particles/include/SkParticleDrawable.h"
20 #include "modules/particles/include/SkReflected.h"
21 #include "src/core/SkMakeUnique.h"
22 #include "src/sksl/SkSLByteCode.h"
23 #include "src/sksl/SkSLCompiler.h"
24 #include "src/sksl/SkSLExternalValue.h"
25
visitFields(SkFieldVisitor * v)26 void SkParticleBinding::visitFields(SkFieldVisitor* v) {
27 v->visit("Name", fName);
28 }
29
30 class SkParticleExternalValue : public SkSL::ExternalValue {
31 public:
SkParticleExternalValue(const char * name,SkSL::Compiler & compiler,const SkSL::Type & type)32 SkParticleExternalValue(const char* name, SkSL::Compiler& compiler, const SkSL::Type& type)
33 : INHERITED(name, type)
34 , fCompiler(compiler)
35 , fRandom(nullptr) {
36 }
37
setRandom(SkRandom * random)38 void setRandom(SkRandom* random) { fRandom = random; }
39
40 protected:
41 SkSL::Compiler& fCompiler;
42 SkRandom* fRandom;
43 typedef SkSL::ExternalValue INHERITED;
44 };
45
46 // Exposes an SkCurve as an external, callable value. c(x) returns a float.
47 class SkCurveExternalValue : public SkParticleExternalValue {
48 public:
SkCurveExternalValue(const char * name,SkSL::Compiler & compiler,const SkCurve & curve)49 SkCurveExternalValue(const char* name, SkSL::Compiler& compiler, const SkCurve& curve)
50 : INHERITED(name, compiler, *compiler.context().fFloat_Type)
51 , fCurve(curve) { }
52
canCall() const53 bool canCall() const override { return true; }
callParameterCount() const54 int callParameterCount() const override { return 1; }
getCallParameterTypes(const SkSL::Type ** outTypes) const55 void getCallParameterTypes(const SkSL::Type** outTypes) const override {
56 outTypes[0] = fCompiler.context().fFloat_Type.get();
57 }
58
call(int index,float * arguments,float * outReturn)59 void call(int index, float* arguments, float* outReturn) override {
60 *outReturn = fCurve.eval(*arguments, fRandom[index]);
61 }
62
63 private:
64 SkCurve fCurve;
65 typedef SkParticleExternalValue INHERITED;
66 };
67
68 class SkCurveBinding : public SkParticleBinding {
69 public:
SkCurveBinding(const char * name="",const SkCurve & curve=0.0f)70 SkCurveBinding(const char* name = "", const SkCurve& curve = 0.0f)
71 : SkParticleBinding(name)
72 , fCurve(curve) {}
73
REFLECTED(SkCurveBinding,SkParticleBinding)74 REFLECTED(SkCurveBinding, SkParticleBinding)
75
76 void visitFields(SkFieldVisitor* v) override {
77 SkParticleBinding::visitFields(v);
78 v->visit("Curve", fCurve);
79 }
80
toValue(SkSL::Compiler & compiler)81 std::unique_ptr<SkParticleExternalValue> toValue(SkSL::Compiler& compiler) override {
82 return std::unique_ptr<SkParticleExternalValue>(
83 new SkCurveExternalValue(fName.c_str(), compiler, fCurve));
84 }
85
86 private:
87 SkCurve fCurve;
88 };
89
90 // Exposes an SkColorCurve as an external, callable value. c(x) returns a float4.
91 class SkColorCurveExternalValue : public SkParticleExternalValue {
92 public:
SkColorCurveExternalValue(const char * name,SkSL::Compiler & compiler,const SkColorCurve & curve)93 SkColorCurveExternalValue(const char* name, SkSL::Compiler& compiler, const SkColorCurve& curve)
94 : INHERITED(name, compiler, *compiler.context().fFloat4_Type)
95 , fCurve(curve) {
96 }
97
canCall() const98 bool canCall() const override { return true; }
callParameterCount() const99 int callParameterCount() const override { return 1; }
getCallParameterTypes(const SkSL::Type ** outTypes) const100 void getCallParameterTypes(const SkSL::Type** outTypes) const override {
101 outTypes[0] = fCompiler.context().fFloat_Type.get();
102 }
103
call(int index,float * arguments,float * outReturn)104 void call(int index, float* arguments, float* outReturn) override {
105 SkColor4f color = fCurve.eval(*arguments, fRandom[index]);
106 memcpy(outReturn, color.vec(), 4 * sizeof(float));
107 }
108
109 private:
110 SkColorCurve fCurve;
111 typedef SkParticleExternalValue INHERITED;
112 };
113
114 class SkColorCurveBinding : public SkParticleBinding {
115 public:
SkColorCurveBinding(const char * name="",const SkColorCurve & curve=SkColor4f{ 1.0f, 1.0f, 1.0f, 1.0f })116 SkColorCurveBinding(const char* name = "",
117 const SkColorCurve& curve = SkColor4f{ 1.0f, 1.0f, 1.0f, 1.0f })
118 : SkParticleBinding(name)
119 , fCurve(curve) {
120 }
121
REFLECTED(SkColorCurveBinding,SkParticleBinding)122 REFLECTED(SkColorCurveBinding, SkParticleBinding)
123
124 void visitFields(SkFieldVisitor* v) override {
125 SkParticleBinding::visitFields(v);
126 v->visit("Curve", fCurve);
127 }
128
toValue(SkSL::Compiler & compiler)129 std::unique_ptr<SkParticleExternalValue> toValue(SkSL::Compiler& compiler) override {
130 return std::unique_ptr<SkParticleExternalValue>(
131 new SkColorCurveExternalValue(fName.c_str(), compiler, fCurve));
132 }
133
134 private:
135 SkColorCurve fCurve;
136 };
137
138 struct SkPathContours {
139 SkScalar fTotalLength;
140 SkTArray<sk_sp<SkContourMeasure>> fContours;
141
rebuildSkPathContours142 void rebuild(const SkPath& path) {
143 fTotalLength = 0;
144 fContours.reset();
145
146 SkContourMeasureIter iter(path, false);
147 while (auto contour = iter.next()) {
148 fContours.push_back(contour);
149 fTotalLength += contour->length();
150 }
151 }
152 };
153
154 // Exposes an SkPath as an external, callable value. p(x) returns a float4 { pos.xy, normal.xy }
155 class SkPathExternalValue : public SkParticleExternalValue {
156 public:
SkPathExternalValue(const char * name,SkSL::Compiler & compiler,const SkPathContours * path)157 SkPathExternalValue(const char* name, SkSL::Compiler& compiler, const SkPathContours* path)
158 : INHERITED(name, compiler, *compiler.context().fFloat4_Type)
159 , fPath(path) { }
160
canCall() const161 bool canCall() const override { return true; }
callParameterCount() const162 int callParameterCount() const override { return 1; }
getCallParameterTypes(const SkSL::Type ** outTypes) const163 void getCallParameterTypes(const SkSL::Type** outTypes) const override {
164 outTypes[0] = fCompiler.context().fFloat_Type.get();
165 }
166
call(int index,float * arguments,float * outReturn)167 void call(int index, float* arguments, float* outReturn) override {
168 SkScalar len = fPath->fTotalLength * arguments[0];
169 int idx = 0;
170 while (idx < fPath->fContours.count() && len > fPath->fContours[idx]->length()) {
171 len -= fPath->fContours[idx++]->length();
172 }
173 SkVector localXAxis;
174 if (!fPath->fContours[idx]->getPosTan(len, (SkPoint*)outReturn, &localXAxis)) {
175 outReturn[0] = outReturn[1] = 0.0f;
176 localXAxis = { 1, 0 };
177 }
178 outReturn[2] = localXAxis.fY;
179 outReturn[3] = -localXAxis.fX;
180 }
181
182 private:
183 const SkPathContours* fPath;
184 typedef SkParticleExternalValue INHERITED;
185 };
186
187 class SkPathBinding : public SkParticleBinding {
188 public:
SkPathBinding(const char * name="",const char * path="")189 SkPathBinding(const char* name = "", const char* path = "")
190 : SkParticleBinding(name)
191 , fPath(path) {
192 this->rebuild();
193 }
194
REFLECTED(SkPathBinding,SkParticleBinding)195 REFLECTED(SkPathBinding, SkParticleBinding)
196
197 void visitFields(SkFieldVisitor* v) override {
198 SkString oldPath = fPath;
199
200 SkParticleBinding::visitFields(v);
201 v->visit("Path", fPath);
202
203 if (fPath != oldPath) {
204 this->rebuild();
205 }
206 }
207
toValue(SkSL::Compiler & compiler)208 std::unique_ptr<SkParticleExternalValue> toValue(SkSL::Compiler& compiler) override {
209 return std::unique_ptr<SkParticleExternalValue>(
210 new SkPathExternalValue(fName.c_str(), compiler, &fContours));
211 }
212
213 private:
214 SkString fPath;
215
rebuild()216 void rebuild() {
217 SkPath path;
218 if (SkParsePath::FromSVGString(fPath.c_str(), &path)) {
219 fContours.rebuild(path);
220 }
221 }
222
223 // Cached
224 SkPathContours fContours;
225 };
226
227 class SkTextBinding : public SkParticleBinding {
228 public:
SkTextBinding(const char * name="",const char * text="",SkScalar fontSize=96)229 SkTextBinding(const char* name = "", const char* text = "", SkScalar fontSize = 96)
230 : SkParticleBinding(name)
231 , fText(text)
232 , fFontSize(fontSize) {
233 this->rebuild();
234 }
235
REFLECTED(SkTextBinding,SkParticleBinding)236 REFLECTED(SkTextBinding, SkParticleBinding)
237
238 void visitFields(SkFieldVisitor* v) override {
239 SkString oldText = fText;
240 SkScalar oldSize = fFontSize;
241
242 SkParticleBinding::visitFields(v);
243 v->visit("Text", fText);
244 v->visit("FontSize", fFontSize);
245
246 if (fText != oldText || fFontSize != oldSize) {
247 this->rebuild();
248 }
249 }
250
toValue(SkSL::Compiler & compiler)251 std::unique_ptr<SkParticleExternalValue> toValue(SkSL::Compiler& compiler) override {
252 return std::unique_ptr<SkParticleExternalValue>(
253 new SkPathExternalValue(fName.c_str(), compiler, &fContours));
254 }
255
256 private:
257 SkString fText;
258 SkScalar fFontSize;
259
rebuild()260 void rebuild() {
261 if (fText.isEmpty()) {
262 return;
263 }
264
265 SkFont font(nullptr, fFontSize);
266 SkPath path;
267 SkTextUtils::GetPath(fText.c_str(), fText.size(), SkTextEncoding::kUTF8, 0, 0, font, &path);
268 fContours.rebuild(path);
269 }
270
271 // Cached
272 SkPathContours fContours;
273 };
274
MakeCurve(const char * name,const SkCurve & curve)275 sk_sp<SkParticleBinding> SkParticleBinding::MakeCurve(const char* name, const SkCurve& curve) {
276 return sk_sp<SkParticleBinding>(new SkCurveBinding(name, curve));
277 }
278
MakeColorCurve(const char * name,const SkColorCurve & curve)279 sk_sp<SkParticleBinding> SkParticleBinding::MakeColorCurve(const char* name,
280 const SkColorCurve& curve) {
281 return sk_sp<SkParticleBinding>(new SkColorCurveBinding(name, curve));
282 }
283
MakePathBinding(const char * name,const char * path)284 sk_sp<SkParticleBinding> SkParticleBinding::MakePathBinding(const char* name, const char* path) {
285 return sk_sp<SkParticleBinding>(new SkPathBinding(name, path));
286 }
287
RegisterBindingTypes()288 void SkParticleBinding::RegisterBindingTypes() {
289 REGISTER_REFLECTED(SkParticleBinding);
290 REGISTER_REFLECTED(SkCurveBinding);
291 REGISTER_REFLECTED(SkColorCurveBinding);
292 REGISTER_REFLECTED(SkPathBinding);
293 REGISTER_REFLECTED(SkTextBinding);
294 }
295
296 // Exposes a particle's random generator as an external, readable value. read returns a float [0, 1)
297 class SkRandomExternalValue : public SkParticleExternalValue {
298 public:
SkRandomExternalValue(const char * name,SkSL::Compiler & compiler)299 SkRandomExternalValue(const char* name, SkSL::Compiler& compiler)
300 : INHERITED(name, compiler, *compiler.context().fFloat_Type) {}
301
canRead() const302 bool canRead() const override { return true; }
read(int index,float * target)303 void read(int index, float* target) override {
304 *target = fRandom[index].nextF();
305 }
306
307 private:
308 typedef SkParticleExternalValue INHERITED;
309 };
310
311 static const char* kDefaultCode =
312 R"(// float rand; Every read returns a random float [0 .. 1)
313 layout(ctype=float) in uniform float dt;
314 layout(ctype=float) in uniform float effectAge;
315
316 struct Particle {
317 float age;
318 float lifetime;
319 float2 pos;
320 float2 dir;
321 float scale;
322 float2 vel;
323 float spin;
324 float4 color;
325 float frame;
326 };
327
328 void spawn(inout Particle p) {
329 }
330
331 void update(inout Particle p) {
332 }
333 )";
334
SkParticleEffectParams()335 SkParticleEffectParams::SkParticleEffectParams()
336 : fMaxCount(128)
337 , fEffectDuration(1.0f)
338 , fRate(8.0f)
339 , fDrawable(nullptr)
340 , fCode(kDefaultCode) {
341 this->rebuild();
342 }
343
visitFields(SkFieldVisitor * v)344 void SkParticleEffectParams::visitFields(SkFieldVisitor* v) {
345 SkString oldCode = fCode;
346
347 v->visit("MaxCount", fMaxCount);
348 v->visit("Duration", fEffectDuration);
349 v->visit("Rate", fRate);
350
351 v->visit("Drawable", fDrawable);
352
353 v->visit("Code", fCode);
354
355 v->visit("Bindings", fBindings);
356
357 // TODO: Or, if any change to binding metadata?
358 if (fCode != oldCode) {
359 this->rebuild();
360 }
361 }
362
rebuild()363 void SkParticleEffectParams::rebuild() {
364 SkSL::Compiler compiler;
365 SkSL::Program::Settings settings;
366
367 SkTArray<std::unique_ptr<SkParticleExternalValue>> externalValues;
368
369 auto rand = skstd::make_unique<SkRandomExternalValue>("rand", compiler);
370 compiler.registerExternalValue(rand.get());
371 externalValues.push_back(std::move(rand));
372
373 for (const auto& binding : fBindings) {
374 if (binding) {
375 auto value = binding->toValue(compiler);
376 compiler.registerExternalValue(value.get());
377 externalValues.push_back(std::move(value));
378 }
379 }
380
381 auto program = compiler.convertProgram(SkSL::Program::kGeneric_Kind,
382 SkSL::String(fCode.c_str()), settings);
383 if (!program) {
384 SkDebugf("%s\n", compiler.errorText().c_str());
385 return;
386 }
387
388 auto byteCode = compiler.toByteCode(*program);
389 if (!byteCode) {
390 SkDebugf("%s\n", compiler.errorText().c_str());
391 return;
392 }
393
394 fByteCode = std::move(byteCode);
395 fExternalValues.swap(externalValues);
396 }
397
SkParticleEffect(sk_sp<SkParticleEffectParams> params,const SkRandom & random)398 SkParticleEffect::SkParticleEffect(sk_sp<SkParticleEffectParams> params, const SkRandom& random)
399 : fParams(std::move(params))
400 , fRandom(random)
401 , fLooping(false)
402 , fSpawnTime(-1.0)
403 , fCount(0)
404 , fLastTime(-1.0)
405 , fSpawnRemainder(0.0f) {
406 this->setCapacity(fParams->fMaxCount);
407 }
408
start(double now,bool looping)409 void SkParticleEffect::start(double now, bool looping) {
410 fCount = 0;
411 fLastTime = fSpawnTime = now;
412 fSpawnRemainder = 0.0f;
413 fLooping = looping;
414 }
415
update(double now)416 void SkParticleEffect::update(double now) {
417 if (!this->isAlive() || !fParams->fDrawable) {
418 return;
419 }
420
421 float deltaTime = static_cast<float>(now - fLastTime);
422 if (deltaTime <= 0.0f) {
423 return;
424 }
425 fLastTime = now;
426
427 // Handle user edits to fMaxCount
428 if (fParams->fMaxCount != fCapacity) {
429 this->setCapacity(fParams->fMaxCount);
430 }
431
432 float effectAge = static_cast<float>((now - fSpawnTime) / fParams->fEffectDuration);
433 effectAge = fLooping ? fmodf(effectAge, 1.0f) : SkTPin(effectAge, 0.0f, 1.0f);
434
435 float updateParams[2] = { deltaTime, effectAge };
436
437 // Advance age for existing particles, and remove any that have reached their end of life
438 for (int i = 0; i < fCount; ++i) {
439 fParticles.fData[SkParticles::kAge][i] +=
440 fParticles.fData[SkParticles::kLifetime][i] * deltaTime;
441 if (fParticles.fData[SkParticles::kAge][i] > 1.0f) {
442 // NOTE: This is fast, but doesn't preserve drawing order. Could be a problem...
443 for (int j = 0; j < SkParticles::kNumChannels; ++j) {
444 fParticles.fData[j][i] = fParticles.fData[j][fCount - 1];
445 }
446 fStableRandoms[i] = fStableRandoms[fCount - 1];
447 --i;
448 --fCount;
449 }
450 }
451
452 auto runProgram = [](const SkParticleEffectParams* params, const char* entry,
453 SkParticles& particles, float updateParams[], int start, int count) {
454 if (const auto& byteCode = params->fByteCode) {
455 float* args[SkParticles::kNumChannels];
456 for (int i = 0; i < SkParticles::kNumChannels; ++i) {
457 args[i] = particles.fData[i].get() + start;
458 }
459 SkRandom* randomBase = particles.fRandom.get() + start;
460 for (const auto& value : params->fExternalValues) {
461 value->setRandom(randomBase);
462 }
463 SkAssertResult(byteCode->runStriped(byteCode->getFunction(entry),
464 args, SkParticles::kNumChannels, count,
465 updateParams, 2, nullptr, 0));
466 }
467 };
468
469 // Spawn new particles
470 float desired = fParams->fRate * deltaTime + fSpawnRemainder;
471 int numToSpawn = sk_float_round2int(desired);
472 fSpawnRemainder = desired - numToSpawn;
473 numToSpawn = SkTPin(numToSpawn, 0, fParams->fMaxCount - fCount);
474 if (numToSpawn) {
475 const int spawnBase = fCount;
476
477 for (int i = 0; i < numToSpawn; ++i) {
478 // Mutate our SkRandom so each particle definitely gets a different generator
479 fRandom.nextU();
480 fParticles.fData[SkParticles::kAge ][fCount] = 0.0f;
481 fParticles.fData[SkParticles::kLifetime ][fCount] = 0.0f;
482 fParticles.fData[SkParticles::kPositionX ][fCount] = 0.0f;
483 fParticles.fData[SkParticles::kPositionY ][fCount] = 0.0f;
484 fParticles.fData[SkParticles::kHeadingX ][fCount] = 0.0f;
485 fParticles.fData[SkParticles::kHeadingY ][fCount] = -1.0f;
486 fParticles.fData[SkParticles::kScale ][fCount] = 1.0f;
487 fParticles.fData[SkParticles::kVelocityX ][fCount] = 0.0f;
488 fParticles.fData[SkParticles::kVelocityY ][fCount] = 0.0f;
489 fParticles.fData[SkParticles::kVelocityAngular][fCount] = 0.0f;
490 fParticles.fData[SkParticles::kColorR ][fCount] = 1.0f;
491 fParticles.fData[SkParticles::kColorG ][fCount] = 1.0f;
492 fParticles.fData[SkParticles::kColorB ][fCount] = 1.0f;
493 fParticles.fData[SkParticles::kColorA ][fCount] = 1.0f;
494 fParticles.fData[SkParticles::kSpriteFrame ][fCount] = 0.0f;
495 fParticles.fRandom[fCount] = fRandom;
496 fCount++;
497 }
498
499 // Run the spawn script
500 runProgram(fParams.get(), "spawn", fParticles, updateParams, spawnBase, numToSpawn);
501
502 // Now stash copies of the random generators and compute inverse particle lifetimes
503 // (so that subsequent updates are faster)
504 for (int i = spawnBase; i < fCount; ++i) {
505 fParticles.fData[SkParticles::kLifetime][i] =
506 sk_ieee_float_divide(1.0f, fParticles.fData[SkParticles::kLifetime][i]);
507 fStableRandoms[i] = fParticles.fRandom[i];
508 }
509 }
510
511 // Restore all stable random generators so update affectors get consistent behavior each frame
512 for (int i = 0; i < fCount; ++i) {
513 fParticles.fRandom[i] = fStableRandoms[i];
514 }
515
516 // Run the update script
517 runProgram(fParams.get(), "update", fParticles, updateParams, 0, fCount);
518
519 // Do fixed-function update work (integration of position and orientation)
520 for (int i = 0; i < fCount; ++i) {
521 fParticles.fData[SkParticles::kPositionX][i] +=
522 fParticles.fData[SkParticles::kVelocityX][i] * deltaTime;
523 fParticles.fData[SkParticles::kPositionY][i] +=
524 fParticles.fData[SkParticles::kVelocityY][i] * deltaTime;
525
526 SkScalar s = SkScalarSin(fParticles.fData[SkParticles::kVelocityAngular][i] * deltaTime),
527 c = SkScalarCos(fParticles.fData[SkParticles::kVelocityAngular][i] * deltaTime);
528 float oldHeadingX = fParticles.fData[SkParticles::kHeadingX][i],
529 oldHeadingY = fParticles.fData[SkParticles::kHeadingY][i];
530 fParticles.fData[SkParticles::kHeadingX][i] = oldHeadingX * c - oldHeadingY * s;
531 fParticles.fData[SkParticles::kHeadingY][i] = oldHeadingX * s + oldHeadingY * c;
532 }
533
534 // Mark effect as dead if we've reached the end (and are not looping)
535 if (!fLooping && (now - fSpawnTime) > fParams->fEffectDuration) {
536 fSpawnTime = -1.0;
537 }
538 }
539
draw(SkCanvas * canvas)540 void SkParticleEffect::draw(SkCanvas* canvas) {
541 if (this->isAlive() && fParams->fDrawable) {
542 SkPaint paint;
543 paint.setFilterQuality(SkFilterQuality::kMedium_SkFilterQuality);
544 fParams->fDrawable->draw(canvas, fParticles, fCount, &paint);
545 }
546 }
547
setCapacity(int capacity)548 void SkParticleEffect::setCapacity(int capacity) {
549 for (int i = 0; i < SkParticles::kNumChannels; ++i) {
550 fParticles.fData[i].realloc(capacity);
551 }
552 fParticles.fRandom.realloc(capacity);
553 fStableRandoms.realloc(capacity);
554
555 fCapacity = capacity;
556 fCount = SkTMin(fCount, fCapacity);
557 }
558