• 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/SkPaint.h"
11 #include "include/private/SkOnce.h"
12 #include "include/private/SkTPin.h"
13 #include "modules/particles/include/SkParticleBinding.h"
14 #include "modules/particles/include/SkParticleDrawable.h"
15 #include "modules/particles/include/SkReflected.h"
16 #include "modules/skresources/include/SkResources.h"
17 #include "src/core/SkArenaAlloc.h"
18 #include "src/core/SkPaintPriv.h"
19 #include "src/core/SkVM.h"
20 #include "src/sksl/SkSLCompiler.h"
21 #include "src/sksl/SkSLUtil.h"
22 #include "src/sksl/codegen/SkSLVMCodeGenerator.h"
23 
24 // Cached state for a single program (either all Effect code, or all Particle code)
25 struct SkParticleProgram {
SkParticleProgramSkParticleProgram26     SkParticleProgram(skvm::Program effectSpawn,
27                       skvm::Program effectUpdate,
28                       skvm::Program spawn,
29                       skvm::Program update,
30                       std::vector<std::unique_ptr<SkSL::ExternalFunction>> externalFunctions,
31                       skvm::Uniforms externalFunctionUniforms,
32                       std::unique_ptr<SkArenaAlloc> alloc,
33                       std::unique_ptr<SkSL::UniformInfo> uniformInfo)
34             : fEffectSpawn(std::move(effectSpawn))
35             , fEffectUpdate(std::move(effectUpdate))
36             , fSpawn(std::move(spawn))
37             , fUpdate(std::move(update))
38             , fExternalFunctions(std::move(externalFunctions))
39             , fExternalFunctionUniforms(std::move(externalFunctionUniforms))
40             , fAlloc(std::move(alloc))
41             , fUniformInfo(std::move(uniformInfo)) {}
42 
43     // Programs for each entry point
44     skvm::Program fEffectSpawn;
45     skvm::Program fEffectUpdate;
46     skvm::Program fSpawn;
47     skvm::Program fUpdate;
48 
49     // External functions created by each SkParticleBinding
50     std::vector<std::unique_ptr<SkSL::ExternalFunction>> fExternalFunctions;
51 
52     // Storage for uniforms generated by external functions
53     skvm::Uniforms fExternalFunctionUniforms;
54     std::unique_ptr<SkArenaAlloc> fAlloc;
55 
56     // Information about uniforms declared in the SkSL
57     std::unique_ptr<SkSL::UniformInfo> fUniformInfo;
58 };
59 
60 static const char* kCommonHeader =
61 R"(
62 struct Effect {
63   float  age;
64   float  lifetime;
65   int    loop;
66   float  rate;
67   int    burst;
68 
69   float2 pos;
70   float2 dir;
71   float  scale;
72   float2 vel;
73   float  spin;
74   float4 color;
75   float  frame;
76   float  seed;
77 };
78 
79 struct Particle {
80   float  age;
81   float  lifetime;
82   float2 pos;
83   float2 dir;
84   float  scale;
85   float2 vel;
86   float  spin;
87   float4 color;
88   float  frame;
89   float  seed;
90 };
91 
92 uniform float dt;
93 uniform Effect effect;
94 
95 // We use a not-very-random pure-float PRNG. It does have nice properties for our situation:
96 // It's fast-ish. Importantly, it only uses types and operations that exist in public SkSL's
97 // minimum spec (no bitwise operations on integers).
98 float rand(inout float seed) {
99   seed = sin(31*seed) + sin(19*seed + 1);
100   return fract(abs(10*seed));
101 }
102 )";
103 
104 static const char* kDefaultCode =
105 R"(void effectSpawn(inout Effect effect) {
106 }
107 
108 void effectUpdate(inout Effect effect) {
109 }
110 
111 void spawn(inout Particle p) {
112 }
113 
114 void update(inout Particle p) {
115 }
116 )";
117 
SkParticleEffectParams()118 SkParticleEffectParams::SkParticleEffectParams()
119         : fMaxCount(128)
120         , fDrawable(nullptr)
121         , fCode(kDefaultCode) {}
122 
visitFields(SkFieldVisitor * v)123 void SkParticleEffectParams::visitFields(SkFieldVisitor* v) {
124     v->visit("MaxCount", fMaxCount);
125     v->visit("Drawable", fDrawable);
126     v->visit("Code",     fCode);
127     v->visit("Bindings", fBindings);
128 }
129 
prepare(const skresources::ResourceProvider * resourceProvider)130 void SkParticleEffectParams::prepare(const skresources::ResourceProvider* resourceProvider) {
131     for (auto& binding : fBindings) {
132         if (binding) {
133             binding->prepare(resourceProvider);
134         }
135     }
136     if (fDrawable) {
137         fDrawable->prepare(resourceProvider);
138     }
139 
140     auto buildProgram = [this](const SkSL::String& code) -> std::unique_ptr<SkParticleProgram> {
141         SkSL::ShaderCapsPointer caps = SkSL::ShaderCapsFactory::Standalone();
142         SkSL::Compiler compiler(caps.get());
143         SkSL::Program::Settings settings;
144         settings.fRemoveDeadFunctions = false;
145 
146         // We use two separate blocks of uniforms (ie two args of stride 0). The first is for skvm
147         // uniforms generated by any external functions. These are managed with a Uniforms instance,
148         // and after it's populated, the values never need to be touched again.
149         // The second uniform arg is for things declared as 'uniform' in the SkSL (including the
150         // built-in declarations of 'dt' and 'effect').
151         skvm::Uniforms efUniforms(skvm::Ptr{0}, 0);
152         auto alloc = std::make_unique<SkArenaAlloc>(0);
153 
154         std::vector<std::unique_ptr<SkSL::ExternalFunction>> externalFns;
155         externalFns.reserve(fBindings.size());
156 
157         for (const auto& binding : fBindings) {
158             if (binding) {
159                 externalFns.push_back(binding->toFunction(compiler, &efUniforms, alloc.get()));
160             }
161         }
162 
163         auto program =
164                 compiler.convertProgram(SkSL::ProgramKind::kGeneric, code, settings, &externalFns);
165         if (!program) {
166             SkDebugf("%s\n", compiler.errorText().c_str());
167             return nullptr;
168         }
169 
170         std::unique_ptr<SkSL::UniformInfo> uniformInfo = SkSL::Program_GetUniformInfo(*program);
171 
172         // For each entry point, convert to an skvm::Program. We need a fresh Builder and uniform
173         // IDs (though we can reuse the Uniforms object, thanks to how it works).
174         auto buildFunction = [&](const char* name){
175             auto fn = SkSL::Program_GetFunction(*program, name);
176             if (!fn) {
177                 return skvm::Program{};
178             }
179 
180             skvm::Builder b;
181             skvm::Ptr efUniformPtr   = b.uniform(),  // aka efUniforms.base
182                       skslUniformPtr = b.uniform();
183             (void)efUniformPtr;
184 
185             std::vector<skvm::Val> uniformIDs;
186             for (int i = 0; i < uniformInfo->fUniformSlotCount; ++i) {
187                 uniformIDs.push_back(b.uniform32(skslUniformPtr, i * sizeof(int)).id);
188             }
189             SkSL::ProgramToSkVM(*program, *fn, &b, SkMakeSpan(uniformIDs));
190             return b.done();
191         };
192 
193         skvm::Program effectSpawn  = buildFunction("effectSpawn"),
194                       effectUpdate = buildFunction("effectUpdate"),
195                       spawn        = buildFunction("spawn"),
196                       update       = buildFunction("update");
197 
198         return std::make_unique<SkParticleProgram>(std::move(effectSpawn),
199                                                    std::move(effectUpdate),
200                                                    std::move(spawn),
201                                                    std::move(update),
202                                                    std::move(externalFns),
203                                                    std::move(efUniforms),
204                                                    std::move(alloc),
205                                                    std::move(uniformInfo));
206     };
207 
208     SkSL::String particleCode(kCommonHeader);
209     particleCode.append(fCode.c_str());
210 
211     if (auto prog = buildProgram(particleCode)) {
212         fProgram = std::move(prog);
213     }
214 }
215 
SkParticleEffect(sk_sp<SkParticleEffectParams> params)216 SkParticleEffect::SkParticleEffect(sk_sp<SkParticleEffectParams> params)
217         : fParams(std::move(params))
218         , fLooping(false)
219         , fCount(0)
220         , fLastTime(-1.0)
221         , fSpawnRemainder(0.0f) {
222     fState.fAge = -1.0f;
223     this->updateStorage();
224 }
225 
updateStorage()226 void SkParticleEffect::updateStorage() {
227     // Handle user edits to fMaxCount
228     if (fParams->fMaxCount != fCapacity) {
229         this->setCapacity(fParams->fMaxCount);
230     }
231 
232     // Ensure our storage block for uniforms is large enough
233     if (this->uniformInfo()) {
234         int newCount = this->uniformInfo()->fUniformSlotCount;
235         if (newCount > fUniforms.count()) {
236             fUniforms.push_back_n(newCount - fUniforms.count(), 0.0f);
237         } else {
238             fUniforms.resize(newCount);
239         }
240     }
241 }
242 
setUniform(const char * name,const float * val,int count)243 bool SkParticleEffect::setUniform(const char* name, const float* val, int count) {
244     const SkSL::UniformInfo* info = this->uniformInfo();
245     if (!info) {
246         return false;
247     }
248 
249     auto it = std::find_if(info->fUniforms.begin(), info->fUniforms.end(),
250                            [name](const auto& u) { return u.fName == name; });
251     if (it == info->fUniforms.end()) {
252         return false;
253     }
254     if (it->fRows * it->fColumns != count) {
255         return false;
256     }
257 
258     std::copy(val, val + count, this->uniformData() + it->fSlot);
259     return true;
260 }
261 
start(double now,bool looping,SkPoint position,SkVector heading,float scale,SkVector velocity,float spin,SkColor4f color,float frame,float seed)262 void SkParticleEffect::start(double now, bool looping, SkPoint position, SkVector heading,
263                              float scale, SkVector velocity, float spin, SkColor4f color,
264                              float frame, float seed) {
265     fCount = 0;
266     fLastTime = now;
267     fSpawnRemainder = 0.0f;
268     fLooping = looping;
269 
270     fState.fAge = 0.0f;
271 
272     // A default lifetime makes sense - many effects are simple loops that don't really care.
273     // Every effect should define its own rate of emission, or only use bursts, so leave that as
274     // zero initially.
275     fState.fLifetime = 1.0f;
276     fState.fLoopCount = 0;
277     fState.fRate = 0.0f;
278     fState.fBurst = 0;
279 
280     fState.fPosition = position;
281     fState.fHeading  = heading;
282     fState.fScale    = scale;
283     fState.fVelocity = velocity;
284     fState.fSpin     = spin;
285     fState.fColor    = color;
286     fState.fFrame    = frame;
287     fState.fRandom   = seed;
288 
289     // Defer running effectSpawn until the first update (to reuse the code when looping)
290 }
291 
292 // Just the update step from our "rand" function
advance_seed(float x)293 static float advance_seed(float x) {
294     return sinf(31*x) + sinf(19*x + 1);
295 }
296 
runEffectScript(EntryPoint entryPoint)297 void SkParticleEffect::runEffectScript(EntryPoint entryPoint) {
298     if (!fParams->fProgram) {
299         return;
300     }
301 
302     const skvm::Program& prog = entryPoint == EntryPoint::kSpawn ? fParams->fProgram->fEffectSpawn
303                                                                  : fParams->fProgram->fEffectUpdate;
304     if (prog.empty()) {
305         return;
306     }
307 
308     constexpr size_t kNumEffectArgs = sizeof(EffectState) / sizeof(int);
309     void* args[kNumEffectArgs
310                + 1    // external function uniforms
311                + 1];  // SkSL uniforms
312 
313     args[0] = fParams->fProgram->fExternalFunctionUniforms.buf.data();
314     args[1] = fUniforms.data();
315     for (size_t i = 0; i < kNumEffectArgs; ++i) {
316         args[i + 2] = SkTAddOffset<void>(&fState, i * sizeof(int));
317     }
318 
319     memcpy(&fUniforms[1], &fState.fAge, sizeof(EffectState));
320     prog.eval(1, args);
321 }
322 
runParticleScript(EntryPoint entryPoint,int start,int count)323 void SkParticleEffect::runParticleScript(EntryPoint entryPoint, int start, int count) {
324     if (!fParams->fProgram) {
325         return;
326     }
327 
328     const skvm::Program& prog = entryPoint == EntryPoint::kSpawn ? fParams->fProgram->fSpawn
329                                                                  : fParams->fProgram->fUpdate;
330     if (prog.empty()) {
331         return;
332     }
333 
334     void* args[SkParticles::kNumChannels
335                + 1    // external function uniforms
336                + 1];  // SkSL uniforms
337     args[0] = fParams->fProgram->fExternalFunctionUniforms.buf.data();
338     args[1] = fUniforms.data();
339     for (int i = 0; i < SkParticles::kNumChannels; ++i) {
340         args[i + 2] = fParticles.fData[i].get() + start;
341     }
342 
343     memcpy(&fUniforms[1], &fState.fAge, sizeof(EffectState));
344     prog.eval(count, args);
345 }
346 
advanceTime(double now)347 void SkParticleEffect::advanceTime(double now) {
348     // TODO: Sub-frame spawning. Tricky with script driven position. Supply variable effect.age?
349     // Could be done if effect.age were an external value that offset by particle lane, perhaps.
350     float deltaTime = static_cast<float>(now - fLastTime);
351     if (deltaTime <= 0.0f) {
352         return;
353     }
354     fLastTime = now;
355 
356     // Possibly re-allocate cached storage, if our params have changed
357     this->updateStorage();
358 
359     // Copy known values into the uniform blocks
360     if (fParams->fProgram) {
361         fUniforms[0] = deltaTime;
362     }
363 
364     // Is this the first update after calling start()?
365     // Run 'effectSpawn' to set initial emitter properties.
366     if (fState.fAge == 0.0f && fState.fLoopCount == 0) {
367         this->runEffectScript(EntryPoint::kSpawn);
368     }
369 
370     fState.fAge += deltaTime / fState.fLifetime;
371     if (fState.fAge > 1) {
372         if (fLooping) {
373             // If we looped, then run effectSpawn again (with the updated loop count)
374             fState.fLoopCount += sk_float_floor2int(fState.fAge);
375             fState.fAge = fmodf(fState.fAge, 1.0f);
376             this->runEffectScript(EntryPoint::kSpawn);
377         } else {
378             // Effect is dead if we've reached the end (and are not looping)
379             return;
380         }
381     }
382 
383     // Advance age for existing particles, and remove any that have reached their end of life
384     for (int i = 0; i < fCount; ++i) {
385         fParticles.fData[SkParticles::kAge][i] +=
386                 fParticles.fData[SkParticles::kLifetime][i] * deltaTime;
387         if (fParticles.fData[SkParticles::kAge][i] > 1.0f) {
388             // NOTE: This is fast, but doesn't preserve drawing order. Could be a problem...
389             for (int j = 0; j < SkParticles::kNumChannels; ++j) {
390                 fParticles.fData[j][i] = fParticles.fData[j][fCount - 1];
391             }
392             fStableRandoms[i] = fStableRandoms[fCount - 1];
393             --i;
394             --fCount;
395         }
396     }
397 
398     // Run 'effectUpdate' to adjust emitter properties
399     this->runEffectScript(EntryPoint::kUpdate);
400 
401     // Do integration of effect position and orientation
402     {
403         fState.fPosition += fState.fVelocity * deltaTime;
404         float s = sk_float_sin(fState.fSpin * deltaTime),
405               c = sk_float_cos(fState.fSpin * deltaTime);
406         // Using setNormalize to prevent scale drift
407         fState.fHeading.setNormalize(fState.fHeading.fX * c - fState.fHeading.fY * s,
408                                      fState.fHeading.fX * s + fState.fHeading.fY * c);
409     }
410 
411     // Spawn new particles
412     float desired = fState.fRate * deltaTime + fSpawnRemainder + fState.fBurst;
413     fState.fBurst = 0;
414     int numToSpawn = sk_float_round2int(desired);
415     fSpawnRemainder = desired - numToSpawn;
416     numToSpawn = SkTPin(numToSpawn, 0, fParams->fMaxCount - fCount);
417     if (numToSpawn) {
418         const int spawnBase = fCount;
419 
420         for (int i = 0; i < numToSpawn; ++i) {
421             // Mutate our random seed so each particle definitely gets a different generator
422             fState.fRandom = advance_seed(fState.fRandom);
423             fParticles.fData[SkParticles::kAge            ][fCount] = 0.0f;
424             fParticles.fData[SkParticles::kLifetime       ][fCount] = 0.0f;
425             fParticles.fData[SkParticles::kPositionX      ][fCount] = fState.fPosition.fX;
426             fParticles.fData[SkParticles::kPositionY      ][fCount] = fState.fPosition.fY;
427             fParticles.fData[SkParticles::kHeadingX       ][fCount] = fState.fHeading.fX;
428             fParticles.fData[SkParticles::kHeadingY       ][fCount] = fState.fHeading.fY;
429             fParticles.fData[SkParticles::kScale          ][fCount] = fState.fScale;
430             fParticles.fData[SkParticles::kVelocityX      ][fCount] = fState.fVelocity.fX;
431             fParticles.fData[SkParticles::kVelocityY      ][fCount] = fState.fVelocity.fY;
432             fParticles.fData[SkParticles::kVelocityAngular][fCount] = fState.fSpin;
433             fParticles.fData[SkParticles::kColorR         ][fCount] = fState.fColor.fR;
434             fParticles.fData[SkParticles::kColorG         ][fCount] = fState.fColor.fG;
435             fParticles.fData[SkParticles::kColorB         ][fCount] = fState.fColor.fB;
436             fParticles.fData[SkParticles::kColorA         ][fCount] = fState.fColor.fA;
437             fParticles.fData[SkParticles::kSpriteFrame    ][fCount] = fState.fFrame;
438             fParticles.fData[SkParticles::kRandom         ][fCount] = fState.fRandom;
439             fCount++;
440         }
441 
442         // Run the spawn script
443         this->runParticleScript(EntryPoint::kSpawn, spawnBase, numToSpawn);
444 
445         // Now stash copies of the random seeds and compute inverse particle lifetimes
446         // (so that subsequent updates are faster)
447         for (int i = spawnBase; i < fCount; ++i) {
448             fParticles.fData[SkParticles::kLifetime][i] =
449                     sk_ieee_float_divide(1.0f, fParticles.fData[SkParticles::kLifetime][i]);
450             fStableRandoms[i] = fParticles.fData[SkParticles::kRandom][i];
451         }
452     }
453 
454     // Restore all stable random seeds so update scripts get consistent behavior each frame
455     for (int i = 0; i < fCount; ++i) {
456         fParticles.fData[SkParticles::kRandom][i] = fStableRandoms[i];
457     }
458 
459     // Run the update script
460     this->runParticleScript(EntryPoint::kUpdate, 0, fCount);
461 
462     // Do fixed-function update work (integration of position and orientation)
463     for (int i = 0; i < fCount; ++i) {
464         fParticles.fData[SkParticles::kPositionX][i] +=
465                 fParticles.fData[SkParticles::kVelocityX][i] * deltaTime;
466         fParticles.fData[SkParticles::kPositionY][i] +=
467                 fParticles.fData[SkParticles::kVelocityY][i] * deltaTime;
468 
469         float spin = fParticles.fData[SkParticles::kVelocityAngular][i];
470         float s = sk_float_sin(spin * deltaTime),
471               c = sk_float_cos(spin * deltaTime);
472         float oldHeadingX = fParticles.fData[SkParticles::kHeadingX][i],
473               oldHeadingY = fParticles.fData[SkParticles::kHeadingY][i];
474         fParticles.fData[SkParticles::kHeadingX][i] = oldHeadingX * c - oldHeadingY * s;
475         fParticles.fData[SkParticles::kHeadingY][i] = oldHeadingX * s + oldHeadingY * c;
476     }
477 }
478 
update(double now)479 void SkParticleEffect::update(double now) {
480     if (this->isAlive()) {
481         this->advanceTime(now);
482     }
483 }
484 
draw(SkCanvas * canvas)485 void SkParticleEffect::draw(SkCanvas* canvas) {
486     if (this->isAlive() && fParams->fDrawable) {
487         SkPaint paint;
488         fParams->fDrawable->draw(canvas, fParticles, fCount, paint);
489     }
490 }
491 
setCapacity(int capacity)492 void SkParticleEffect::setCapacity(int capacity) {
493     for (int i = 0; i < SkParticles::kNumChannels; ++i) {
494         fParticles.fData[i].realloc(capacity);
495     }
496     fStableRandoms.realloc(capacity);
497 
498     fCapacity = capacity;
499     fCount = std::min(fCount, fCapacity);
500 }
501 
uniformInfo() const502 const SkSL::UniformInfo* SkParticleEffect::uniformInfo() const {
503     return fParams->fProgram ? fParams->fProgram->fUniformInfo.get() : nullptr;
504 }
505 
RegisterParticleTypes()506 void SkParticleEffect::RegisterParticleTypes() {
507     static SkOnce once;
508     once([]{
509         REGISTER_REFLECTED(SkReflected);
510         SkParticleBinding::RegisterBindingTypes();
511         SkParticleDrawable::RegisterDrawableTypes();
512     });
513 }
514