• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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