• 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 
144         // We use two separate blocks of uniforms (ie two args of stride 0). The first is for skvm
145         // uniforms generated by any external functions. These are managed with a Uniforms instance,
146         // and after it's populated, the values never need to be touched again.
147         // The second uniform arg is for things declared as 'uniform' in the SkSL (including the
148         // built-in declarations of 'dt' and 'effect').
149         skvm::Uniforms efUniforms(skvm::UPtr{0}, 0);
150         auto alloc = std::make_unique<SkArenaAlloc>(0);
151 
152         std::vector<std::unique_ptr<SkSL::ExternalFunction>> externalFns;
153         externalFns.reserve(fBindings.size());
154         for (const auto& binding : fBindings) {
155             if (binding) {
156                 externalFns.push_back(binding->toFunction(compiler, &efUniforms, alloc.get()));
157             }
158         }
159 
160         SkSL::Program::Settings settings;
161         settings.fRemoveDeadFunctions = false;
162         settings.fExternalFunctions = &externalFns;
163 
164         auto program = compiler.convertProgram(SkSL::ProgramKind::kGeneric, code, settings);
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::UPtr 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             if (!SkSL::ProgramToSkVM(*program, *fn, &b, /*debugInfo=*/nullptr,
190                                      SkMakeSpan(uniformIDs))) {
191                 return skvm::Program{};
192             }
193             return b.done();
194         };
195 
196         skvm::Program effectSpawn  = buildFunction("effectSpawn"),
197                       effectUpdate = buildFunction("effectUpdate"),
198                       spawn        = buildFunction("spawn"),
199                       update       = buildFunction("update");
200 
201         return std::make_unique<SkParticleProgram>(std::move(effectSpawn),
202                                                    std::move(effectUpdate),
203                                                    std::move(spawn),
204                                                    std::move(update),
205                                                    std::move(externalFns),
206                                                    std::move(efUniforms),
207                                                    std::move(alloc),
208                                                    std::move(uniformInfo));
209     };
210 
211     SkSL::String particleCode(kCommonHeader);
212     particleCode.append(fCode.c_str());
213 
214     if (auto prog = buildProgram(particleCode)) {
215         fProgram = std::move(prog);
216     }
217 }
218 
SkParticleEffect(sk_sp<SkParticleEffectParams> params)219 SkParticleEffect::SkParticleEffect(sk_sp<SkParticleEffectParams> params)
220         : fParams(std::move(params))
221         , fLooping(false)
222         , fCount(0)
223         , fLastTime(-1.0)
224         , fSpawnRemainder(0.0f) {
225     fState.fAge = -1.0f;
226     this->updateStorage();
227 }
228 
updateStorage()229 void SkParticleEffect::updateStorage() {
230     // Handle user edits to fMaxCount
231     if (fParams->fMaxCount != fCapacity) {
232         this->setCapacity(fParams->fMaxCount);
233     }
234 
235     // Ensure our storage block for uniforms is large enough
236     if (this->uniformInfo()) {
237         int newCount = this->uniformInfo()->fUniformSlotCount;
238         if (newCount > fUniforms.count()) {
239             fUniforms.push_back_n(newCount - fUniforms.count(), 0.0f);
240         } else {
241             fUniforms.resize(newCount);
242         }
243     }
244 }
245 
setUniform(const char * name,const float * val,int count)246 bool SkParticleEffect::setUniform(const char* name, const float* val, int count) {
247     const SkSL::UniformInfo* info = this->uniformInfo();
248     if (!info) {
249         return false;
250     }
251 
252     auto it = std::find_if(info->fUniforms.begin(), info->fUniforms.end(),
253                            [name](const auto& u) { return u.fName == name; });
254     if (it == info->fUniforms.end()) {
255         return false;
256     }
257     if (it->fRows * it->fColumns != count) {
258         return false;
259     }
260 
261     std::copy(val, val + count, this->uniformData() + it->fSlot);
262     return true;
263 }
264 
start(double now,bool looping,SkPoint position,SkVector heading,float scale,SkVector velocity,float spin,SkColor4f color,float frame,float seed)265 void SkParticleEffect::start(double now, bool looping, SkPoint position, SkVector heading,
266                              float scale, SkVector velocity, float spin, SkColor4f color,
267                              float frame, float seed) {
268     fCount = 0;
269     fLastTime = now;
270     fSpawnRemainder = 0.0f;
271     fLooping = looping;
272 
273     fState.fAge = 0.0f;
274 
275     // A default lifetime makes sense - many effects are simple loops that don't really care.
276     // Every effect should define its own rate of emission, or only use bursts, so leave that as
277     // zero initially.
278     fState.fLifetime = 1.0f;
279     fState.fLoopCount = 0;
280     fState.fRate = 0.0f;
281     fState.fBurst = 0;
282 
283     fState.fPosition = position;
284     fState.fHeading  = heading;
285     fState.fScale    = scale;
286     fState.fVelocity = velocity;
287     fState.fSpin     = spin;
288     fState.fColor    = color;
289     fState.fFrame    = frame;
290     fState.fRandom   = seed;
291 
292     // Defer running effectSpawn until the first update (to reuse the code when looping)
293 }
294 
295 // Just the update step from our "rand" function
advance_seed(float x)296 static float advance_seed(float x) {
297     return sinf(31*x) + sinf(19*x + 1);
298 }
299 
runEffectScript(EntryPoint entryPoint)300 void SkParticleEffect::runEffectScript(EntryPoint entryPoint) {
301     if (!fParams->fProgram) {
302         return;
303     }
304 
305     const skvm::Program& prog = entryPoint == EntryPoint::kSpawn ? fParams->fProgram->fEffectSpawn
306                                                                  : fParams->fProgram->fEffectUpdate;
307     if (prog.empty()) {
308         return;
309     }
310 
311     constexpr size_t kNumEffectArgs = sizeof(EffectState) / sizeof(int);
312     void* args[kNumEffectArgs
313                + 1    // external function uniforms
314                + 1];  // SkSL uniforms
315 
316     args[0] = fParams->fProgram->fExternalFunctionUniforms.buf.data();
317     args[1] = fUniforms.data();
318     for (size_t i = 0; i < kNumEffectArgs; ++i) {
319         args[i + 2] = SkTAddOffset<void>(&fState, i * sizeof(int));
320     }
321 
322     memcpy(&fUniforms[1], &fState.fAge, sizeof(EffectState));
323     prog.eval(1, args);
324 }
325 
runParticleScript(EntryPoint entryPoint,int start,int count)326 void SkParticleEffect::runParticleScript(EntryPoint entryPoint, int start, int count) {
327     if (!fParams->fProgram) {
328         return;
329     }
330 
331     const skvm::Program& prog = entryPoint == EntryPoint::kSpawn ? fParams->fProgram->fSpawn
332                                                                  : fParams->fProgram->fUpdate;
333     if (prog.empty()) {
334         return;
335     }
336 
337     void* args[SkParticles::kNumChannels
338                + 1    // external function uniforms
339                + 1];  // SkSL uniforms
340     args[0] = fParams->fProgram->fExternalFunctionUniforms.buf.data();
341     args[1] = fUniforms.data();
342     for (int i = 0; i < SkParticles::kNumChannels; ++i) {
343         args[i + 2] = fParticles.fData[i].get() + start;
344     }
345 
346     memcpy(&fUniforms[1], &fState.fAge, sizeof(EffectState));
347     prog.eval(count, args);
348 }
349 
advanceTime(double now)350 void SkParticleEffect::advanceTime(double now) {
351     // TODO: Sub-frame spawning. Tricky with script driven position. Supply variable effect.age?
352     // Could be done if effect.age were an external value that offset by particle lane, perhaps.
353     float deltaTime = static_cast<float>(now - fLastTime);
354     if (deltaTime <= 0.0f) {
355         return;
356     }
357     fLastTime = now;
358 
359     // Possibly re-allocate cached storage, if our params have changed
360     this->updateStorage();
361 
362     // Copy known values into the uniform blocks
363     if (fParams->fProgram) {
364         fUniforms[0] = deltaTime;
365     }
366 
367     // Is this the first update after calling start()?
368     // Run 'effectSpawn' to set initial emitter properties.
369     if (fState.fAge == 0.0f && fState.fLoopCount == 0) {
370         this->runEffectScript(EntryPoint::kSpawn);
371     }
372 
373     fState.fAge += deltaTime / fState.fLifetime;
374     if (fState.fAge > 1) {
375         if (fLooping) {
376             // If we looped, then run effectSpawn again (with the updated loop count)
377             fState.fLoopCount += sk_float_floor2int(fState.fAge);
378             fState.fAge = fmodf(fState.fAge, 1.0f);
379             this->runEffectScript(EntryPoint::kSpawn);
380         } else {
381             // Effect is dead if we've reached the end (and are not looping)
382             return;
383         }
384     }
385 
386     // Advance age for existing particles, and remove any that have reached their end of life
387     for (int i = 0; i < fCount; ++i) {
388         fParticles.fData[SkParticles::kAge][i] +=
389                 fParticles.fData[SkParticles::kLifetime][i] * deltaTime;
390         if (fParticles.fData[SkParticles::kAge][i] > 1.0f) {
391             // NOTE: This is fast, but doesn't preserve drawing order. Could be a problem...
392             for (int j = 0; j < SkParticles::kNumChannels; ++j) {
393                 fParticles.fData[j][i] = fParticles.fData[j][fCount - 1];
394             }
395             fStableRandoms[i] = fStableRandoms[fCount - 1];
396             --i;
397             --fCount;
398         }
399     }
400 
401     // Run 'effectUpdate' to adjust emitter properties
402     this->runEffectScript(EntryPoint::kUpdate);
403 
404     // Do integration of effect position and orientation
405     {
406         fState.fPosition += fState.fVelocity * deltaTime;
407         float s = sk_float_sin(fState.fSpin * deltaTime),
408               c = sk_float_cos(fState.fSpin * deltaTime);
409         // Using setNormalize to prevent scale drift
410         fState.fHeading.setNormalize(fState.fHeading.fX * c - fState.fHeading.fY * s,
411                                      fState.fHeading.fX * s + fState.fHeading.fY * c);
412     }
413 
414     // Spawn new particles
415     float desired = fState.fRate * deltaTime + fSpawnRemainder + fState.fBurst;
416     fState.fBurst = 0;
417     int numToSpawn = sk_float_round2int(desired);
418     fSpawnRemainder = desired - numToSpawn;
419     numToSpawn = SkTPin(numToSpawn, 0, fParams->fMaxCount - fCount);
420     if (numToSpawn) {
421         const int spawnBase = fCount;
422 
423         for (int i = 0; i < numToSpawn; ++i) {
424             // Mutate our random seed so each particle definitely gets a different generator
425             fState.fRandom = advance_seed(fState.fRandom);
426             fParticles.fData[SkParticles::kAge            ][fCount] = 0.0f;
427             fParticles.fData[SkParticles::kLifetime       ][fCount] = 0.0f;
428             fParticles.fData[SkParticles::kPositionX      ][fCount] = fState.fPosition.fX;
429             fParticles.fData[SkParticles::kPositionY      ][fCount] = fState.fPosition.fY;
430             fParticles.fData[SkParticles::kHeadingX       ][fCount] = fState.fHeading.fX;
431             fParticles.fData[SkParticles::kHeadingY       ][fCount] = fState.fHeading.fY;
432             fParticles.fData[SkParticles::kScale          ][fCount] = fState.fScale;
433             fParticles.fData[SkParticles::kVelocityX      ][fCount] = fState.fVelocity.fX;
434             fParticles.fData[SkParticles::kVelocityY      ][fCount] = fState.fVelocity.fY;
435             fParticles.fData[SkParticles::kVelocityAngular][fCount] = fState.fSpin;
436             fParticles.fData[SkParticles::kColorR         ][fCount] = fState.fColor.fR;
437             fParticles.fData[SkParticles::kColorG         ][fCount] = fState.fColor.fG;
438             fParticles.fData[SkParticles::kColorB         ][fCount] = fState.fColor.fB;
439             fParticles.fData[SkParticles::kColorA         ][fCount] = fState.fColor.fA;
440             fParticles.fData[SkParticles::kSpriteFrame    ][fCount] = fState.fFrame;
441             fParticles.fData[SkParticles::kRandom         ][fCount] = fState.fRandom;
442             fCount++;
443         }
444 
445         // Run the spawn script
446         this->runParticleScript(EntryPoint::kSpawn, spawnBase, numToSpawn);
447 
448         // Now stash copies of the random seeds and compute inverse particle lifetimes
449         // (so that subsequent updates are faster)
450         for (int i = spawnBase; i < fCount; ++i) {
451             fParticles.fData[SkParticles::kLifetime][i] =
452                     sk_ieee_float_divide(1.0f, fParticles.fData[SkParticles::kLifetime][i]);
453             fStableRandoms[i] = fParticles.fData[SkParticles::kRandom][i];
454         }
455     }
456 
457     // Restore all stable random seeds so update scripts get consistent behavior each frame
458     for (int i = 0; i < fCount; ++i) {
459         fParticles.fData[SkParticles::kRandom][i] = fStableRandoms[i];
460     }
461 
462     // Run the update script
463     this->runParticleScript(EntryPoint::kUpdate, 0, fCount);
464 
465     // Do fixed-function update work (integration of position and orientation)
466     for (int i = 0; i < fCount; ++i) {
467         fParticles.fData[SkParticles::kPositionX][i] +=
468                 fParticles.fData[SkParticles::kVelocityX][i] * deltaTime;
469         fParticles.fData[SkParticles::kPositionY][i] +=
470                 fParticles.fData[SkParticles::kVelocityY][i] * deltaTime;
471 
472         float spin = fParticles.fData[SkParticles::kVelocityAngular][i];
473         float s = sk_float_sin(spin * deltaTime),
474               c = sk_float_cos(spin * deltaTime);
475         float oldHeadingX = fParticles.fData[SkParticles::kHeadingX][i],
476               oldHeadingY = fParticles.fData[SkParticles::kHeadingY][i];
477         fParticles.fData[SkParticles::kHeadingX][i] = oldHeadingX * c - oldHeadingY * s;
478         fParticles.fData[SkParticles::kHeadingY][i] = oldHeadingX * s + oldHeadingY * c;
479     }
480 }
481 
update(double now)482 void SkParticleEffect::update(double now) {
483     if (this->isAlive()) {
484         this->advanceTime(now);
485     }
486 }
487 
draw(SkCanvas * canvas)488 void SkParticleEffect::draw(SkCanvas* canvas) {
489     if (this->isAlive() && fParams->fDrawable) {
490         SkPaint paint;
491         fParams->fDrawable->draw(canvas, fParticles, fCount, paint);
492     }
493 }
494 
setCapacity(int capacity)495 void SkParticleEffect::setCapacity(int capacity) {
496     for (int i = 0; i < SkParticles::kNumChannels; ++i) {
497         fParticles.fData[i].realloc(capacity);
498     }
499     fStableRandoms.realloc(capacity);
500 
501     fCapacity = capacity;
502     fCount = std::min(fCount, fCapacity);
503 }
504 
uniformInfo() const505 const SkSL::UniformInfo* SkParticleEffect::uniformInfo() const {
506     return fParams->fProgram ? fParams->fProgram->fUniformInfo.get() : nullptr;
507 }
508 
RegisterParticleTypes()509 void SkParticleEffect::RegisterParticleTypes() {
510     static SkOnce once;
511     once([]{
512         REGISTER_REFLECTED(SkReflected);
513         SkParticleBinding::RegisterBindingTypes();
514         SkParticleDrawable::RegisterDrawableTypes();
515     });
516 }
517