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