• 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/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