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