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