• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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