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