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#ifndef SkParticleEffect_DEFINED
9#define SkParticleEffect_DEFINED
10
11#include "include/core/SkColor.h"
12#include "include/core/SkPoint.h"
13#include "include/core/SkRefCnt.h"
14#include "include/core/SkString.h"
15#include "include/private/SkTArray.h"
16#include "include/private/SkTemplates.h"
17#include "modules/particles/include/SkParticleData.h"
18
19#include <memory>
20
21class SkCanvas;
22class SkFieldVisitor;
23class SkParticleBinding;
24class SkParticleDrawable;
25class SkParticleExternalValue;
26
27namespace skresources {
28 class ResourceProvider;
29}
30
31namespace SkSL {
32 class ByteCode;
33}
34
35class SkParticleEffectParams : public SkRefCnt {
36public:
37 SkParticleEffectParams();
38
39 // Maximum number of particles per instance of the effect
40 int fMaxCount;
41
42 // What is drawn for each particle? (Image, shape, sprite sheet, etc.)
43 // See SkParticleDrawable::Make*
44 sk_sp<SkParticleDrawable> fDrawable;
45
46 // Particle behavior is driven by two chunks of SkSL code. Effect functions are defined in
47 // fEffectCode, and get a mutable Effect struct:
48 //
49 // struct Effect {
50 // float age; // Normalized age of the effect
51 // float lifetime; // Effect's duration, in seconds - script should set this in effectSpawn
52 // int loop; // Number of loops that have elapsed (0 on initial spawn)
53 // float rate; // Rate to generate new particles (particles / second)
54 // int burst; // Number of particles to emit in a single update
55 // // Set during spawn to emit that many at once on each loop
56 //
57 // // Everything below this line controls the state of the effect, which is also the
58 // // default values for new particles.
59 // float2 pos = { 0, 0 }; // Local position
60 // float2 dir = { 0, -1 }; // Heading. Should be a normalized vector.
61 // float scale = 1; // Size, normalized relative to the drawable's native size
62 // float2 vel = { 0, 0 }; // Linear velocity, in (units / second)
63 // float spin = 0; // Angular velocity, in (radians / second)
64 // float4 color = { 1, 1, 1, 1 }; // RGBA color
65 // float frame = 0; // Normalized sprite index for multi-frame drawables
66 // uint flags = 0; // Arbitrary state for use by script
67 // uint seed = 0; // Random seed, used with rand() (see below)
68 // };
69 //
70 // Particle functions are defined in fParticleCode, and get a mutable Particle struct, as well
71 // as a uniform copy of the current Effect, named 'effect'.
72 //
73 // struct Particle {
74 // float age;
75 // float lifetime;
76 // float2 pos;
77 // float2 dir;
78 // float scale;
79 // float2 vel;
80 // float spin;
81 // float4 color;
82 // float frame;
83 // uint flags;
84 // uint seed;
85 // };
86 //
87 // All functions have access to a global function named 'rand'. It takes a uint seed value,
88 // which it uses and updates (using a linear congruential RNG). It returns a random floating
89 // point value in [0, 1]. Typical usage is to pass the particle or effect's seed value to rand.
90 // For particle functions, the seed is rewound after each update, so calls to 'rand(p.seed)'
91 // will return consistent values from one update to the next.
92 //
93 // Finally, there is one global uniform values available, 'dt'. This is a floating point
94 // number of seconds that have elapsed since the last update.
95 //
96 // Effect code should define two functions:
97 //
98 // 'void effectSpawn(inout Effect e)' is called when an instance of the effect is first
99 // created, and again at every loop point (if the effect is played with the looping flag).
100 //
101 // 'void effectUpdate(inout Effect e)' is called once per update to adjust properties of the
102 // effect (ie emitter).
103 //
104 // Particle code should also define two functions:
105 //
106 // 'void spawn(inout Particle p)' is called once for each particle when it is first created,
107 // to set initial values. At a minimum, this should set 'lifetime' to the number of seconds
108 // that the particle will exist. Other parameters will get default values from the effect.
109 //
110 // 'void update(inout Particle p)' is called for each particle on every call to the running
111 // SkParticleEffect's update() method. It can animate any of the particle's values. Note that
112 // the 'lifetime' field has a different meaning in 'update', and should not be used or changed.
113
114 SkString fEffectCode;
115 SkString fParticleCode;
116
117 // External objects accessible by the effect's SkSL code. Each binding is a name and particular
118 // kind of object. See SkParticleBinding::Make* for details.
119 SkTArray<sk_sp<SkParticleBinding>> fBindings;
120
121 void visitFields(SkFieldVisitor* v);
122
123 // Load/compute cached resources
124 void prepare(const skresources::ResourceProvider*);
125
126private:
127 friend class SkParticleEffect;
128
129 // Cached
130 struct Program {
131 std::unique_ptr<SkSL::ByteCode> fByteCode;
132 SkTArray<std::unique_ptr<SkParticleExternalValue>> fExternalValues;
133 };
134
135 Program fEffectProgram;
136 Program fParticleProgram;
137};
138
139class SkParticleEffect : public SkRefCnt {
140public:
141 SkParticleEffect(sk_sp<SkParticleEffectParams> params);
142
143 // Start playing this effect, specifying initial values for the emitter's properties
144 void start(double now, bool looping, SkPoint position, SkVector heading, float scale,
145 SkVector velocity, float spin, SkColor4f color, float frame, uint32_t flags,
146 uint32_t seed);
147
148 // Start playing this effect, with default values for the emitter's properties
149 void start(double now, bool looping) {
150 this->start(now, looping,
151 { 0.0f, 0.0f }, // position
152 { 0.0f, -1.0f }, // heading
153 1.0f, // scale
154 { 0.0f, 0.0f }, // velocity
155 0.0f, // spin
156 { 1.0f, 1.0f, 1.0f, 1.0f }, // color
157 0.0f, // sprite frame
158 0, // flags
159 0); // seed
160 }
161
162 void update(double now);
163 void draw(SkCanvas* canvas);
164
165 bool isAlive(bool includeSubEffects = true) const {
166 return (fState.fAge >= 0 && fState.fAge <= 1)
167 || (includeSubEffects && !fSubEffects.empty());
168 }
169 int getCount() const { return fCount; }
170
171 float getRate() const { return fState.fRate; }
172 int getBurst() const { return fState.fBurst; }
173 SkPoint getPosition() const { return fState.fPosition; }
174 SkVector getHeading() const { return fState.fHeading; }
175 float getScale() const { return fState.fScale; }
176 SkVector getVelocity() const { return fState.fVelocity; }
177 float getSpin() const { return fState.fSpin; }
178 SkColor4f getColor() const { return fState.fColor; }
179 float getFrame() const { return fState.fFrame; }
180 uint32_t getFlags() const { return fState.fFlags; }
181
182 void setRate (float r) { fState.fRate = r; }
183 void setBurst (int b) { fState.fBurst = b; }
184 void setPosition(SkPoint p) { fState.fPosition = p; }
185 void setHeading (SkVector h) { fState.fHeading = h; }
186 void setScale (float s) { fState.fScale = s; }
187 void setVelocity(SkVector v) { fState.fVelocity = v; }
188 void setSpin (float s) { fState.fSpin = s; }
189 void setColor (SkColor4f c) { fState.fColor = c; }
190 void setFrame (float f) { fState.fFrame = f; }
191 void setFlags (uint32_t f) { fState.fFlags = f; }
192
193 const SkSL::ByteCode* effectCode() const { return fParams->fEffectProgram.fByteCode.get(); }
194 const SkSL::ByteCode* particleCode() const { return fParams->fParticleProgram.fByteCode.get(); }
195
196 float* effectUniforms() { return fEffectUniforms.data(); }
197 float* particleUniforms() { return fParticleUniforms.data(); }
198
199 static void RegisterParticleTypes();
200
201private:
202 void setCapacity(int capacity);
203
204 // Helpers to break down update
205 void advanceTime(double now);
206
207 void processEffectSpawnRequests(double now);
208 void runEffectScript(double now, const char* entry);
209
210 void processParticleSpawnRequests(double now, int start);
211 void runParticleScript(double now, const char* entry, int start, int count);
212
213 sk_sp<SkParticleEffectParams> fParams;
214
215 bool fLooping;
216 int fCount;
217 double fLastTime;
218 float fSpawnRemainder;
219
220 // C++ version of the SkSL Effect struct. This is the inout parameter to per-effect scripts,
221 // and provided as a uniform (named 'effect') to the per-particle scripts.
222 struct EffectState {
223 float fAge;
224 float fLifetime;
225 int fLoopCount;
226 float fRate;
227 int fBurst;
228
229 // Properties that determine default values for new particles
230 SkPoint fPosition;
231 SkVector fHeading;
232 float fScale;
233 SkVector fVelocity;
234 float fSpin;
235 SkColor4f fColor;
236 float fFrame;
237 uint32_t fFlags;
238 uint32_t fRandom;
239 };
240 EffectState fState;
241
242 SkParticles fParticles;
243 SkAutoTMalloc<float> fStableRandoms;
244
245 // Cached
246 int fCapacity;
247 SkTArray<float, true> fEffectUniforms;
248 SkTArray<float, true> fParticleUniforms;
249
250 // Private interface used by SkEffectBinding and SkEffectExternalValue to spawn sub effects
251 friend class SkEffectExternalValue;
252 struct SpawnRequest {
253 SpawnRequest(int index, bool loop, sk_sp<SkParticleEffectParams> params)
254 : fIndex(index)
255 , fLoop(loop)
256 , fParams(std::move(params)) {}
257
258 int fIndex;
259 bool fLoop;
260 sk_sp<SkParticleEffectParams> fParams;
261 };
262 void addSpawnRequest(int index, bool loop, sk_sp<SkParticleEffectParams> params) {
263 fSpawnRequests.emplace_back(index, loop, std::move(params));
264 }
265 SkTArray<SpawnRequest> fSpawnRequests;
266
267 SkTArray<sk_sp<SkParticleEffect>> fSubEffects;
268};
269
270#endif // SkParticleEffect_DEFINED
271