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 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(NoisePlanes , SkV2 , fNoisePlanes )
241 SG_ATTRIBUTE(NoiseWeight , float , fNoiseWeight )
242 SG_ATTRIBUTE(Octaves , float , fOctaves )
243 SG_ATTRIBUTE(Persistence , float , fPersistence )
244
245 private:
246 template <NoiseFilter FI, NoiseFractal FR>
getEffect() const247 sk_sp<SkRuntimeEffect> getEffect() const {
248 // Bin the loop counter based on the number of octaves (range: [1..20]).
249 // Low complexities are common, so we maximize resolution for the low end.
250 if (fOctaves > 8) return noise_effect<20, FI, FR>();
251 if (fOctaves > 4) return noise_effect< 8, FI, FR>();
252 if (fOctaves > 3) return noise_effect< 4, FI, FR>();
253 if (fOctaves > 2) return noise_effect< 3, FI, FR>();
254 if (fOctaves > 1) return noise_effect< 2, FI, FR>();
255
256 return noise_effect<1, FI, FR>();
257 }
258
259 template <NoiseFilter FI>
getEffect() const260 sk_sp<SkRuntimeEffect> getEffect() const {
261 switch (fFractal) {
262 case NoiseFractal::kBasic:
263 return this->getEffect<FI, NoiseFractal::kBasic>();
264 case NoiseFractal::kTurbulentBasic:
265 return this->getEffect<FI, NoiseFractal::kTurbulentBasic>();
266 case NoiseFractal::kTurbulentSmooth:
267 return this->getEffect<FI, NoiseFractal::kTurbulentSmooth>();
268 case NoiseFractal::kTurbulentSharp:
269 return this->getEffect<FI, NoiseFractal::kTurbulentSharp>();
270 }
271 SkUNREACHABLE;
272 }
273
getEffect() const274 sk_sp<SkRuntimeEffect> getEffect() const {
275 switch (fFilter) {
276 case NoiseFilter::kNearest : return this->getEffect<NoiseFilter::kNearest>();
277 case NoiseFilter::kLinear : return this->getEffect<NoiseFilter::kLinear>();
278 case NoiseFilter::kSoftLinear: return this->getEffect<NoiseFilter::kSoftLinear>();
279 }
280 SkUNREACHABLE;
281 }
282
buildEffectShader() const283 sk_sp<SkShader> buildEffectShader() const {
284 SkRuntimeShaderBuilder builder(this->getEffect());
285
286 builder.uniform("u_noise_planes") = fNoisePlanes;
287 builder.uniform("u_noise_weight") = fNoiseWeight;
288 builder.uniform("u_octaves" ) = fOctaves;
289 builder.uniform("u_persistence" ) = fPersistence;
290 builder.uniform("u_submatrix" ) = std::array<float,9>{
291 fSubMatrix.rc(0,0), fSubMatrix.rc(1,0), fSubMatrix.rc(2,0),
292 fSubMatrix.rc(0,1), fSubMatrix.rc(1,1), fSubMatrix.rc(2,1),
293 fSubMatrix.rc(0,2), fSubMatrix.rc(1,2), fSubMatrix.rc(2,2),
294 };
295
296 return builder.makeShader(&fMatrix);
297 }
298
onRevalidate(sksg::InvalidationController * ic,const SkMatrix & ctm)299 SkRect onRevalidate(sksg::InvalidationController* ic, const SkMatrix& ctm) override {
300 const auto& child = this->children()[0];
301 const auto bounds = child->revalidate(ic, ctm);
302
303 fEffectShader = this->buildEffectShader();
304
305 return bounds;
306 }
307
onRender(SkCanvas * canvas,const RenderContext * ctx) const308 void onRender(SkCanvas* canvas, const RenderContext* ctx) const override {
309 const auto& bounds = this->bounds();
310 const auto local_ctx = ScopedRenderContext(canvas, ctx)
311 .setIsolation(bounds, canvas->getTotalMatrix(), true);
312
313 canvas->saveLayer(&bounds, nullptr);
314 this->children()[0]->render(canvas, local_ctx);
315
316 SkPaint effect_paint;
317 effect_paint.setShader(fEffectShader);
318 effect_paint.setBlendMode(SkBlendMode::kSrcIn);
319
320 canvas->drawPaint(effect_paint);
321 }
322
onNodeAt(const SkPoint &) const323 const RenderNode* onNodeAt(const SkPoint&) const override { return nullptr; } // no hit-testing
324
325 sk_sp<SkShader> fEffectShader;
326
327 SkMatrix fMatrix,
328 fSubMatrix;
329 NoiseFilter fFilter = NoiseFilter::kNearest;
330 NoiseFractal fFractal = NoiseFractal::kBasic;
331 SkV2 fNoisePlanes = {0,0};
332 float fNoiseWeight = 0,
333 fOctaves = 1,
334 fPersistence = 1;
335
336 using INHERITED = sksg::CustomRenderNode;
337 };
338
339 class FractalNoiseAdapter final : public DiscardableAdapterBase<FractalNoiseAdapter,
340 FractalNoiseNode> {
341 public:
FractalNoiseAdapter(const skjson::ArrayValue & jprops,const AnimationBuilder * abuilder,sk_sp<FractalNoiseNode> node)342 FractalNoiseAdapter(const skjson::ArrayValue& jprops,
343 const AnimationBuilder* abuilder,
344 sk_sp<FractalNoiseNode> node)
345 : INHERITED(std::move(node))
346 {
347 EffectBinder(jprops, *abuilder, this)
348 .bind( 0, fFractalType )
349 .bind( 1, fNoiseType )
350 .bind( 2, fInvert )
351 .bind( 3, fContrast )
352 .bind( 4, fBrightness )
353 // 5 -- overflow
354 // 6 -- transform begin-group
355 .bind( 7, fRotation )
356 .bind( 8, fUniformScaling )
357 .bind( 9, fScale )
358 .bind(10, fScaleWidth )
359 .bind(11, fScaleHeight )
360 .bind(12, fOffset )
361 // 13 -- TODO: perspective offset
362 // 14 -- transform end-group
363 .bind(15, fComplexity )
364 // 16 -- sub settings begin-group
365 .bind(17, fSubInfluence )
366 .bind(18, fSubScale )
367 .bind(19, fSubRotation )
368 .bind(20, fSubOffset )
369 // 21 -- center subscale
370 // 22 -- sub settings end-group
371 .bind(23, fEvolution )
372 // 24 -- evolution options begin-group
373 .bind(25, fCycleEvolution )
374 .bind(26, fCycleRevolutions)
375 .bind(27, fRandomSeed )
376 // 28 -- evolution options end-group
377 .bind(29, fOpacity );
378 // 30 -- TODO: blending mode
379 }
380
381 private:
noise() const382 std::tuple<SkV2, float> noise() const {
383 // Constant chosen to visually match AE's evolution rate.
384 static constexpr auto kEvolutionScale = 0.25f;
385
386 // Evolution inputs:
387 //
388 // * evolution - main evolution control (degrees)
389 // * cycle evolution - flag controlling whether evolution cycles
390 // * cycle revolutions - number of revolutions after which evolution cycles (period)
391 // * random seed - determines an arbitrary starting plane (evolution offset)
392 //
393 // The shader uses evolution floor/ceil to select two noise planes, and the fractional part
394 // to interpolate between the two -> in order to wrap around smoothly, the cycle/period
395 // must be integral.
396 const float
397 evo_rad = SkDegreesToRadians(fEvolution),
398 rev_rad = std::max(fCycleRevolutions, 1.0f)*SK_FloatPI*2,
399 cycle = fCycleEvolution
400 ? SkScalarRoundToScalar(rev_rad*kEvolutionScale)
401 : SK_ScalarMax,
402 // Adjust scale when cycling to ensure an integral period (post scaling).
403 scale = fCycleEvolution
404 ? cycle/rev_rad
405 : kEvolutionScale,
406 offset = SkRandom(static_cast<uint32_t>(fRandomSeed)).nextRangeU(0, 100),
407 evo = evo_rad*scale,
408 evo_ = std::floor(evo),
409 weight = evo - evo_;
410
411 // We want the GLSL mod() flavor.
412 auto glsl_mod = [](float x, float y) {
413 return x - y*std::floor(x/y);
414 };
415
416 const SkV2 noise_planes = {
417 glsl_mod(evo_ + 0, cycle) + offset,
418 glsl_mod(evo_ + 1, cycle) + offset,
419 };
420
421 return std::make_tuple(noise_planes, weight);
422 }
423
shaderMatrix() const424 SkMatrix shaderMatrix() const {
425 static constexpr float kGridSize = 64;
426
427 const auto scale = (SkScalarRoundToInt(fUniformScaling) == 1)
428 ? SkV2{fScale, fScale}
429 : SkV2{fScaleWidth, fScaleHeight};
430
431 return SkMatrix::Translate(fOffset.x, fOffset.y)
432 * SkMatrix::Scale(SkTPin(scale.x, 1.0f, 10000.0f) * 0.01f,
433 SkTPin(scale.y, 1.0f, 10000.0f) * 0.01f)
434 * SkMatrix::RotateDeg(fRotation)
435 * SkMatrix::Scale(kGridSize, kGridSize);
436 }
437
subMatrix() const438 SkMatrix subMatrix() const {
439 const auto scale = 100 / SkTPin(fSubScale, 10.0f, 10000.0f);
440
441 return SkMatrix::Translate(-fSubOffset.x * 0.01f, -fSubOffset.y * 0.01f)
442 * SkMatrix::RotateDeg(-fSubRotation)
443 * SkMatrix::Scale(scale, scale);
444 }
445
noiseFilter() const446 NoiseFilter noiseFilter() const {
447 switch (SkScalarRoundToInt(fNoiseType)) {
448 case 1: return NoiseFilter::kNearest;
449 case 2: return NoiseFilter::kLinear;
450 default: return NoiseFilter::kSoftLinear;
451 }
452 SkUNREACHABLE;
453 }
454
noiseFractal() const455 NoiseFractal noiseFractal() const {
456 switch (SkScalarRoundToInt(fFractalType)) {
457 case 1: return NoiseFractal::kBasic;
458 case 3: return NoiseFractal::kTurbulentSmooth;
459 case 4: return NoiseFractal::kTurbulentBasic;
460 default: return NoiseFractal::kTurbulentSharp;
461 }
462 SkUNREACHABLE;
463 }
464
onSync()465 void onSync() override {
466 const auto& n = this->node();
467
468 const auto [noise_planes, noise_weight] = this->noise();
469
470 n->setOctaves(SkTPin(fComplexity, 1.0f, 20.0f));
471 n->setPersistence(SkTPin(fSubInfluence * 0.01f, 0.0f, 100.0f));
472 n->setNoisePlanes(noise_planes);
473 n->setNoiseWeight(noise_weight);
474 n->setNoiseFilter(this->noiseFilter());
475 n->setNoiseFractal(this->noiseFractal());
476 n->setMatrix(this->shaderMatrix());
477 n->setSubMatrix(this->subMatrix());
478 }
479
480 Vec2Value fOffset = {0,0},
481 fSubOffset = {0,0};
482
483 ScalarValue fFractalType = 0,
484 fNoiseType = 0,
485
486 fRotation = 0,
487 fUniformScaling = 0,
488 fScale = 100, // used when uniform scaling is selected
489 fScaleWidth = 100, // used when uniform scaling is not selected
490 fScaleHeight = 100, // ^
491
492 fComplexity = 1,
493 fSubInfluence = 100,
494 fSubScale = 50,
495 fSubRotation = 0,
496
497 fEvolution = 0,
498 fCycleEvolution = 0,
499 fCycleRevolutions = 0,
500 fRandomSeed = 0,
501
502 fOpacity = 100, // TODO
503 fInvert = 0, // TODO
504 fContrast = 100, // TODO
505 fBrightness = 0; // TODO
506
507 using INHERITED = DiscardableAdapterBase<FractalNoiseAdapter, FractalNoiseNode>;
508 };
509
510 } // namespace
511
512 #endif // SK_ENABLE_SKSL
513
attachFractalNoiseEffect(const skjson::ArrayValue & jprops,sk_sp<sksg::RenderNode> layer) const514 sk_sp<sksg::RenderNode> EffectBuilder::attachFractalNoiseEffect(
515 const skjson::ArrayValue& jprops, sk_sp<sksg::RenderNode> layer) const {
516 #ifdef SK_ENABLE_SKSL
517 auto fractal_noise = sk_make_sp<FractalNoiseNode>(std::move(layer));
518
519 return fBuilder->attachDiscardableAdapter<FractalNoiseAdapter>(jprops, fBuilder,
520 std::move(fractal_noise));
521 #else
522 // TODO(skia:12197)
523 return layer;
524 #endif
525 }
526
527 } // namespace skottie::internal
528