1 /*
2 * Copyright 2021 Google Inc.
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/skottie/src/effects/Effects.h"
9
10 #include "include/effects/SkRuntimeEffect.h"
11 #include "include/utils/SkRandom.h"
12 #include "modules/skottie/src/Adapter.h"
13 #include "modules/skottie/src/SkottieJson.h"
14 #include "modules/skottie/src/SkottieValue.h"
15 #include "modules/sksg/include/SkSGRenderNode.h"
16
17 namespace skottie::internal {
18 namespace {
19
20 // An implementation of the ADBE Fractal Noise effect:
21 //
22 // - multiple noise sublayers (octaves) are combined using a weighted average
23 // - each layer is subject to a (cumulative) transform, filter and post-sampling options
24 //
25 // Parameters:
26 //
27 // * Noise Type -- controls noise layer post-sampling filtering
28 // (Block, Linear, Soft Linear, Spline)
29 // * Fractal Type -- determines a noise layer post-filtering transformation
30 // (Basic, Turbulent Smooth, Turbulent Basic, etc)
31 // * Transform -- offset/scale/rotate the noise effect (local matrix)
32 //
33 // * Complexity -- number of sublayers;
34 // can be fractional, where the fractional part modulates the last layer
35 // * Evolution -- controls noise topology in a gradual manner (can be animated for smooth
36 // noise transitions)
37 // * Sub Influence -- relative amplitude weight for sublayers (cumulative)
38 //
39 // * Sub Scaling/Rotation/Offset -- relative scale for sublayers (cumulative)
40 //
41 // * Invert -- invert noise values
42 //
43 // * Contrast -- apply a contrast to the noise result
44 //
45 // * Brightness -- apply a brightness effect to the noise result
46 //
47 //
48 // TODO:
49 // - Invert
50 // - Contrast/Brightness
51
52 static constexpr char gNoiseEffectSkSL[] =
53 "uniform half3x3 u_submatrix;" // sublayer transform
54
55 "uniform half u_octaves," // number of octaves (can be fractional)
56 "u_persistence," // relative octave weight
57 "u_evolution;" // evolution/seed
58
59 // Hash based on hash13 (https://www.shadertoy.com/view/4djSRW).
60 "half hash(half3 v) {"
61 "v = fract(v*0.1031);"
62 "v += dot(v, v.zxy + 31.32);"
63 "return fract((v.x + v.y)*v.z);"
64 "}"
65
66 // The general idea is to compute a coherent hash for two planes in discretized (x,y,e) space,
67 // and interpolate between them. This yields gradual changes when animating |e| - which is the
68 // desired outcome.
69 "half sample_noise(vec2 xy) {"
70 "xy = floor(xy);"
71
72 "half e_ = floor(u_evolution),"
73 "t = u_evolution - e_,"
74 "n0 = hash(half3(xy, e_ + 0)),"
75 "n1 = hash(half3(xy, e_ + 1));"
76
77 // Note: Ideally we would use 4 samples (-1, 0, 1, 2) and cubic interpolation for
78 // better results -- but that's significantly more expensive than lerp.
79
80 "return mix(n0, n1, t);"
81 "}"
82
83 // filter() placeholder
84 "%s"
85
86 // fractal() placeholder
87 "%s"
88
89 // Generate ceil(u_octaves) noise layers and combine based on persistentce and sublayer xform.
90 "half4 main(vec2 xy) {"
91 "half oct = u_octaves," // initial octave count (this is the effective loop counter)
92 "amp = 1," // initial layer amplitude
93 "wacc = 0," // weight accumulator
94 "n = 0;" // noise accumulator
95
96 // Constant loop counter chosen to be >= ceil(u_octaves).
97 // The logical counter is actually 'oct'.
98 "for (half i = 0; i < %u; ++i) {"
99 // effective layer weight computed to accommodate fixed loop counters
100 //
101 // -- for full octaves: layer amplitude
102 // -- for fractional octave: layer amplitude modulated by fractional part
103 // -- for octaves > ceil(u_octaves): 0
104 //
105 // e.g. for 6 loops and u_octaves = 2.3, this generates the sequence [1,1,.3,0,0]
106 "half w = amp*saturate(oct);"
107
108 "n += w*fractal(filter(xy));"
109
110 "wacc += w;"
111 "amp *= u_persistence;"
112 "oct -= 1;"
113
114 "xy = (u_submatrix*half3(xy,1)).xy;"
115 "}"
116
117 "n /= wacc;"
118
119 // TODO: fractal functions
120
121 "return half4(n,n,n,1);"
122 "}";
123
124 static constexpr char gFilterNearestSkSL[] =
125 "half filter(half2 xy) {"
126 "return sample_noise(xy);"
127 "}";
128
129 static constexpr char gFilterLinearSkSL[] =
130 "half filter(half2 xy) {"
131 "xy -= 0.5;"
132
133 "half n00 = sample_noise(xy + half2(0,0)),"
134 "n10 = sample_noise(xy + half2(1,0)),"
135 "n01 = sample_noise(xy + half2(0,1)),"
136 "n11 = sample_noise(xy + half2(1,1));"
137
138 "half2 t = fract(xy);"
139
140 "return mix(mix(n00, n10, t.x), mix(n01, n11, t.x), t.y);"
141 "}";
142
143 static constexpr char gFilterSoftLinearSkSL[] =
144 "half filter(half2 xy) {"
145 "xy -= 0.5;"
146
147 "half n00 = sample_noise(xy + half2(0,0)),"
148 "n10 = sample_noise(xy + half2(1,0)),"
149 "n01 = sample_noise(xy + half2(0,1)),"
150 "n11 = sample_noise(xy + half2(1,1));"
151
152 "half2 t = smoothstep(0, 1, fract(xy));"
153
154 "return mix(mix(n00, n10, t.x), mix(n01, n11, t.x), t.y);"
155 "}";
156
157 static constexpr char gFractalBasicSkSL[] =
158 "half fractal(half n) {"
159 "return n;"
160 "}";
161
162 static constexpr char gFractalTurbulentBasicSkSL[] =
163 "half fractal(half n) {"
164 "return 2*abs(0.5 - n);"
165 "}";
166
167 static constexpr char gFractalTurbulentSmoothSkSL[] =
168 "half fractal(half n) {"
169 "n = 2*abs(0.5 - n);"
170 "return n*n;"
171 "}";
172
173 static constexpr char gFractalTurbulentSharpSkSL[] =
174 "half fractal(half n) {"
175 "return sqrt(2*abs(0.5 - n));"
176 "}";
177
178 enum class NoiseFilter {
179 kNearest,
180 kLinear,
181 kSoftLinear,
182 // TODO: kSpline?
183 };
184
185 enum class NoiseFractal {
186 kBasic,
187 kTurbulentBasic,
188 kTurbulentSmooth,
189 kTurbulentSharp,
190 };
191
make_noise_effect(unsigned loops,const char * filter,const char * fractal)192 sk_sp<SkRuntimeEffect> make_noise_effect(unsigned loops, const char* filter, const char* fractal) {
193 auto result = SkRuntimeEffect::MakeForShader(
194 SkStringPrintf(gNoiseEffectSkSL, filter, fractal, loops), {});
195
196 if (0 && !result.effect) {
197 printf("!!! %s\n", result.errorText.c_str());
198 }
199
200 return std::move(result.effect);
201 }
202
203 template <unsigned LOOPS, NoiseFilter FILTER, NoiseFractal FRACTAL>
noise_effect()204 sk_sp<SkRuntimeEffect> noise_effect() {
205 static constexpr char const* gFilters[] = {
206 gFilterNearestSkSL,
207 gFilterLinearSkSL,
208 gFilterSoftLinearSkSL
209 };
210
211 static constexpr char const* gFractals[] = {
212 gFractalBasicSkSL,
213 gFractalTurbulentBasicSkSL,
214 gFractalTurbulentSmoothSkSL,
215 gFractalTurbulentSharpSkSL
216 };
217
218 static_assert(static_cast<size_t>(FILTER) < SK_ARRAY_COUNT(gFilters));
219 static_assert(static_cast<size_t>(FRACTAL) < SK_ARRAY_COUNT(gFractals));
220
221 static const SkRuntimeEffect* effect =
222 make_noise_effect(LOOPS,
223 gFilters[static_cast<size_t>(FILTER)],
224 gFractals[static_cast<size_t>(FRACTAL)])
225 .release();
226
227 SkASSERT(effect);
228 return sk_ref_sp(effect);
229 }
230
231 class FractalNoiseNode final : public sksg::CustomRenderNode {
232 public:
FractalNoiseNode(sk_sp<RenderNode> child)233 explicit FractalNoiseNode(sk_sp<RenderNode> child) : INHERITED({std::move(child)}) {}
234
235 SG_ATTRIBUTE(Matrix , SkMatrix , fMatrix )
236 SG_ATTRIBUTE(SubMatrix , SkMatrix , fSubMatrix )
237
238 SG_ATTRIBUTE(NoiseFilter , NoiseFilter , fFilter )
239 SG_ATTRIBUTE(NoiseFractal, NoiseFractal, fFractal )
240 SG_ATTRIBUTE(Octaves , float , fOctaves )
241 SG_ATTRIBUTE(Persistence , float , fPersistence)
242 SG_ATTRIBUTE(Evolution , float , fEvolution )
243
244 private:
245 template <NoiseFilter FI, NoiseFractal FR>
getEffect() const246 sk_sp<SkRuntimeEffect> getEffect() const {
247 // Bin the loop counter based on the number of octaves (range: [1..20]).
248 // Low complexities are common, so we maximize resolution for the low end.
249 if (fOctaves > 8) return noise_effect<20, FI, FR>();
250 if (fOctaves > 4) return noise_effect< 8, FI, FR>();
251 if (fOctaves > 3) return noise_effect< 4, FI, FR>();
252 if (fOctaves > 2) return noise_effect< 3, FI, FR>();
253 if (fOctaves > 1) return noise_effect< 2, FI, FR>();
254
255 return noise_effect<1, FI, FR>();
256 }
257
258 template <NoiseFilter FI>
getEffect() const259 sk_sp<SkRuntimeEffect> getEffect() const {
260 switch (fFractal) {
261 case NoiseFractal::kBasic:
262 return this->getEffect<FI, NoiseFractal::kBasic>();
263 case NoiseFractal::kTurbulentBasic:
264 return this->getEffect<FI, NoiseFractal::kTurbulentBasic>();
265 case NoiseFractal::kTurbulentSmooth:
266 return this->getEffect<FI, NoiseFractal::kTurbulentSmooth>();
267 case NoiseFractal::kTurbulentSharp:
268 return this->getEffect<FI, NoiseFractal::kTurbulentSharp>();
269 }
270 SkUNREACHABLE;
271 }
272
getEffect() const273 sk_sp<SkRuntimeEffect> getEffect() const {
274 switch (fFilter) {
275 case NoiseFilter::kNearest : return this->getEffect<NoiseFilter::kNearest>();
276 case NoiseFilter::kLinear : return this->getEffect<NoiseFilter::kLinear>();
277 case NoiseFilter::kSoftLinear: return this->getEffect<NoiseFilter::kSoftLinear>();
278 }
279 SkUNREACHABLE;
280 }
281
buildEffectShader() const282 sk_sp<SkShader> buildEffectShader() const {
283 SkRuntimeShaderBuilder builder(this->getEffect());
284
285 builder.uniform("u_octaves") = fOctaves;
286 builder.uniform("u_persistence") = fPersistence;
287 builder.uniform("u_evolution") = fEvolution;
288 builder.uniform("u_submatrix") = std::array<float,9>{
289 fSubMatrix.rc(0,0), fSubMatrix.rc(1,0), fSubMatrix.rc(2,0),
290 fSubMatrix.rc(0,1), fSubMatrix.rc(1,1), fSubMatrix.rc(2,1),
291 fSubMatrix.rc(0,2), fSubMatrix.rc(1,2), fSubMatrix.rc(2,2),
292 };
293
294 return builder.makeShader(&fMatrix, false);
295 }
296
onRevalidate(sksg::InvalidationController * ic,const SkMatrix & ctm)297 SkRect onRevalidate(sksg::InvalidationController* ic, const SkMatrix& ctm) override {
298 const auto& child = this->children()[0];
299 const auto bounds = child->revalidate(nullptr, SkMatrix::I());
300
301 fEffectShader = this->buildEffectShader();
302
303 return bounds;
304 }
305
onRender(SkCanvas * canvas,const RenderContext * ctx) const306 void onRender(SkCanvas* canvas, const RenderContext* ctx) const override {
307 const auto& bounds = this->bounds();
308 const auto local_ctx = ScopedRenderContext(canvas, ctx)
309 .setIsolation(bounds, canvas->getTotalMatrix(), true);
310
311 canvas->saveLayer(&bounds, nullptr);
312 this->children()[0]->render(canvas, local_ctx);
313
314 SkPaint effect_paint;
315 effect_paint.setShader(fEffectShader);
316 effect_paint.setBlendMode(SkBlendMode::kSrcIn);
317
318 canvas->drawPaint(effect_paint);
319 }
320
onNodeAt(const SkPoint &) const321 const RenderNode* onNodeAt(const SkPoint&) const override { return nullptr; } // no hit-testing
322
323 sk_sp<SkShader> fEffectShader;
324
325 SkMatrix fMatrix,
326 fSubMatrix;
327 NoiseFilter fFilter = NoiseFilter::kNearest;
328 NoiseFractal fFractal = NoiseFractal::kBasic;
329 float fOctaves = 1,
330 fPersistence = 1,
331 fEvolution = 0;
332
333 using INHERITED = sksg::CustomRenderNode;
334 };
335
336 class FractalNoiseAdapter final : public DiscardableAdapterBase<FractalNoiseAdapter,
337 FractalNoiseNode> {
338 public:
FractalNoiseAdapter(const skjson::ArrayValue & jprops,const AnimationBuilder * abuilder,sk_sp<FractalNoiseNode> node)339 FractalNoiseAdapter(const skjson::ArrayValue& jprops,
340 const AnimationBuilder* abuilder,
341 sk_sp<FractalNoiseNode> node)
342 : INHERITED(std::move(node))
343 {
344 EffectBinder(jprops, *abuilder, this)
345 .bind( 0, fFractalType )
346 .bind( 1, fNoiseType )
347 .bind( 2, fInvert )
348 .bind( 3, fContrast )
349 .bind( 4, fBrightness )
350 // 5 -- overflow
351 // 6 -- transform begin-group
352 .bind( 7, fRotation )
353 .bind( 8, fUniformScaling )
354 .bind( 9, fScale )
355 .bind(10, fScaleWidth )
356 .bind(11, fScaleHeight )
357 .bind(12, fOffset )
358 // 13 -- TODO: perspective offset
359 // 14 -- transform end-group
360 .bind(15, fComplexity )
361 // 16 -- sub settings begin-group
362 .bind(17, fSubInfluence )
363 .bind(18, fSubScale )
364 .bind(19, fSubRotation )
365 .bind(20, fSubOffset )
366 // 21 -- center subscale
367 // 22 -- sub settings end-group
368 .bind(23, fEvolution )
369 // 24 -- evolution options begin-group
370 // 25 -- cycle evolution
371 // 26 -- cycle revolution
372 .bind(27, fRandomSeed )
373 // 28 -- evolution options end-group
374 .bind(29, fOpacity );
375 // 30 -- TODO: blending mode
376 }
377
378 private:
evolution() const379 float evolution() const {
380 // Constant chosen to visually match AE's evolution rate.
381 const auto evo = SkDegreesToRadians(fEvolution) * 0.25f;
382
383 // The random seed determines an arbitrary start plane.
384 const auto base = SkRandom(static_cast<uint32_t>(fRandomSeed)).nextRangeU(0, 100);
385
386 return evo + base;
387 }
388
shaderMatrix() const389 SkMatrix shaderMatrix() const {
390 static constexpr float kGridSize = 64;
391
392 const auto scale = (SkScalarRoundToInt(fUniformScaling) == 1)
393 ? SkV2{fScale, fScale}
394 : SkV2{fScaleWidth, fScaleHeight};
395
396 return SkMatrix::Translate(fOffset.x, fOffset.y)
397 * SkMatrix::RotateDeg(fRotation)
398 * SkMatrix::Scale(SkTPin(scale.x, 1.0f, 10000.0f) * 0.01f,
399 SkTPin(scale.y, 1.0f, 10000.0f) * 0.01f)
400 * SkMatrix::Scale(kGridSize, kGridSize);
401 }
402
subMatrix() const403 SkMatrix subMatrix() const {
404 const auto scale = 100 / SkTPin(fSubScale, 10.0f, 10000.0f);
405
406 return SkMatrix::Translate(-fSubOffset.x * 0.01f, -fSubOffset.y * 0.01f)
407 * SkMatrix::RotateDeg(-fSubRotation)
408 * SkMatrix::Scale(scale, scale);
409 }
410
noiseFilter() const411 NoiseFilter noiseFilter() const {
412 switch (SkScalarRoundToInt(fNoiseType)) {
413 case 1: return NoiseFilter::kNearest;
414 case 2: return NoiseFilter::kLinear;
415 default: return NoiseFilter::kSoftLinear;
416 }
417 SkUNREACHABLE;
418 }
419
noiseFractal() const420 NoiseFractal noiseFractal() const {
421 switch (SkScalarRoundToInt(fFractalType)) {
422 case 1: return NoiseFractal::kBasic;
423 case 3: return NoiseFractal::kTurbulentSmooth;
424 case 4: return NoiseFractal::kTurbulentBasic;
425 default: return NoiseFractal::kTurbulentSharp;
426 }
427 SkUNREACHABLE;
428 }
429
onSync()430 void onSync() override {
431 const auto& n = this->node();
432
433 n->setOctaves(SkTPin(fComplexity, 1.0f, 20.0f));
434 n->setPersistence(SkTPin(fSubInfluence * 0.01f, 0.0f, 100.0f));
435 n->setEvolution(this->evolution());
436 n->setNoiseFilter(this->noiseFilter());
437 n->setNoiseFractal(this->noiseFractal());
438 n->setMatrix(this->shaderMatrix());
439 n->setSubMatrix(this->subMatrix());
440 }
441
442 Vec2Value fOffset = {0,0},
443 fSubOffset = {0,0};
444
445 ScalarValue fFractalType = 0,
446 fNoiseType = 0,
447
448 fRotation = 0,
449 fUniformScaling = 0,
450 fScale = 100, // used when uniform scaling is selected
451 fScaleWidth = 100, // used when uniform scaling is not selected
452 fScaleHeight = 100, // ^
453
454 fComplexity = 1,
455 fSubInfluence = 100,
456 fSubScale = 50,
457 fSubRotation = 0,
458
459 fEvolution = 0,
460 fRandomSeed = 0,
461
462 fOpacity = 100, // TODO
463 fInvert = 0, // TODO
464 fContrast = 100, // TODO
465 fBrightness = 0; // TODO
466
467 using INHERITED = DiscardableAdapterBase<FractalNoiseAdapter, FractalNoiseNode>;
468 };
469
470 } // namespace
471
attachFractalNoiseEffect(const skjson::ArrayValue & jprops,sk_sp<sksg::RenderNode> layer) const472 sk_sp<sksg::RenderNode> EffectBuilder::attachFractalNoiseEffect(
473 const skjson::ArrayValue& jprops, sk_sp<sksg::RenderNode> layer) const {
474 auto fractal_noise = sk_make_sp<FractalNoiseNode>(std::move(layer));
475
476 return fBuilder->attachDiscardableAdapter<FractalNoiseAdapter>(jprops, fBuilder,
477 std::move(fractal_noise));
478 }
479
480 } // namespace skottie::internal
481