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