Flutter Engine
The Flutter Engine
FractalNoiseEffect.cpp
Go to the documentation of this file.
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
10#include "include/core/SkM44.h"
13#include "include/core/SkRect.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
39struct SkPoint;
40
41namespace skjson {
42class ArrayValue;
43}
44namespace sksg {
45class InvalidationController;
46}
47
48namespace skottie::internal {
49namespace {
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
83static 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
151static constexpr char gFilterNearestSkSL[] =
152 "float filter(float2 xy) {"
153 "return sample_noise(xy);"
154 "}";
155
156static 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
170static 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
184static constexpr char gFractalBasicSkSL[] =
185 "float fractal(float n) {"
186 "return n;"
187 "}";
188
189static constexpr char gFractalTurbulentBasicSkSL[] =
190 "float fractal(float n) {"
191 "return 2*abs(0.5 - n);"
192 "}";
193
194static constexpr char gFractalTurbulentSmoothSkSL[] =
195 "float fractal(float n) {"
196 "n = 2*abs(0.5 - n);"
197 "return n*n;"
198 "}";
199
200static constexpr char gFractalTurbulentSharpSkSL[] =
201 "float fractal(float n) {"
202 "return sqrt(2*abs(0.5 - n));"
203 "}";
204
205enum class NoiseFilter {
206 kNearest,
207 kLinear,
208 kSoftLinear,
209 // TODO: kSpline?
210};
211
212enum class NoiseFractal {
213 kBasic,
214 kTurbulentBasic,
215 kTurbulentSmooth,
216 kTurbulentSharp,
217};
218
219sk_sp<SkRuntimeEffect> make_noise_effect(unsigned loops, const char* filter, const char* fractal) {
221 SkStringPrintf(gNoiseEffectSkSL, filter, fractal, loops), {});
222
223 return std::move(result.effect);
224}
225
226sk_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 }
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
289class FractalNoiseNode final : public sksg::CustomRenderNode {
290public:
291 explicit FractalNoiseNode(sk_sp<RenderNode> child) : INHERITED({std::move(child)}) {}
292
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
303private:
304 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 }
316 }
317
318 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 }
325 }
326
327 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
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
352 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
367 const RenderNode* onNodeAt(const SkPoint&) const override { return nullptr; } // no hit-testing
368
369 sk_sp<SkShader> fEffectShader;
370
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
381};
382
383class FractalNoiseAdapter final : public DiscardableAdapterBase<FractalNoiseAdapter,
384 FractalNoiseNode> {
385public:
386 FractalNoiseAdapter(const skjson::ArrayValue& jprops,
387 const AnimationBuilder* abuilder,
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
425private:
426 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
468 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
482 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)
488 }
489
490 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 }
497 }
498
499 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 }
507 }
508
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
556sk_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
SkMatrix fMatrix
Definition: FillRRectOp.cpp:74
#define SkUNREACHABLE
Definition: SkAssert.h:135
#define SkASSERT(cond)
Definition: SkAssert.h:116
@ kSrcIn
r = s * da
constexpr float SK_FloatPI
#define INHERITED(method,...)
Definition: SkRecorder.cpp:128
sk_sp< T > sk_ref_sp(T *obj)
Definition: SkRefCnt.h:381
#define SG_ATTRIBUTE(attr_name, attr_type, attr_container)
Definition: SkSGNode.h:100
#define SkDegreesToRadians(degrees)
Definition: SkScalar.h:77
#define SK_ScalarMax
Definition: SkScalar.h:24
#define SkScalarRoundToInt(x)
Definition: SkScalar.h:37
#define SkScalarRoundToScalar(x)
Definition: SkScalar.h:32
SK_API SkString SkStringPrintf(const char *format,...) SK_PRINTF_LIKE(1
Creates a new string and writes into it using a printf()-style format.
static constexpr const T & SkTPin(const T &x, const T &lo, const T &hi)
Definition: SkTPin.h:19
int saveLayer(const SkRect *bounds, const SkPaint *paint)
Definition: SkCanvas.cpp:496
void drawPaint(const SkPaint &paint)
Definition: SkCanvas.cpp:1668
SkMatrix getTotalMatrix() const
Definition: SkCanvas.cpp:1629
static SkMatrix Scale(SkScalar sx, SkScalar sy)
Definition: SkMatrix.h:75
static SkMatrix RotateDeg(SkScalar deg)
Definition: SkMatrix.h:104
static SkMatrix Translate(SkScalar dx, SkScalar dy)
Definition: SkMatrix.h:91
SkScalar rc(int r, int c) const
Definition: SkMatrix.h:404
void setShader(sk_sp< SkShader > shader)
void setBlendMode(SkBlendMode mode)
Definition: SkPaint.cpp:151
static Result MakeForShader(SkString sksl, const Options &)
void attachDiscardableAdapter(sk_sp< T > adapter) const
Definition: SkottiePriv.h:139
GAsyncResult * result
static float max(float r, float g, float b)
Definition: hsl.cpp:49
double y
double x
static constexpr skcms_TransferFunction kLinear
Definition: SkColorSpace.h:51
Optional< SkRect > bounds
Definition: SkRecords.h:189
it will be possible to load the file into Perfetto s trace viewer disable asset Prevents usage of any non test fonts unless they were explicitly Loaded via prefetched default font Indicates whether the embedding started a prefetch of the default font manager before creating the engine run In non interactive keep the shell running after the Dart script has completed enable serial On low power devices with low core running concurrent GC tasks on threads can cause them to contend with the UI thread which could potentially lead to jank This option turns off all concurrent GC activities domain network JSON encoded network policy per domain This overrides the DisallowInsecureConnections switch Embedder can specify whether to allow or disallow insecure connections at a domain level old gen heap size
Definition: switches.h:259
SK_API sk_sp< PrecompileColorFilter > Matrix()
SkScalar ScalarValue
Definition: SkottieValue.h:22
SkV2 Vec2Value
Definition: SkottieValue.h:23
Definition: Skottie.h:32
SIN Vec< N, float > floor(const Vec< N, float > &x)
Definition: SkVx.h:703
Definition: ref_ptr.h:256
const Scalar scale
SeparatedVector2 offset
Definition: SkM44.h:19