Flutter Engine
The Flutter Engine
GrPerlinNoise2Effect.cpp
Go to the documentation of this file.
1/*
2 * Copyright 2023 Google LLC
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
9
12#include "include/core/SkSize.h"
15#include "src/base/SkRandom.h"
17#include "src/gpu/KeyBuilder.h"
28
29#include <cstdint>
30#include <iterator>
31
32class SkShader;
33
34/////////////////////////////////////////////////////////////////////
36
37#if defined(GR_TEST_UTILS)
38std::unique_ptr<GrFragmentProcessor> GrPerlinNoise2Effect::TestCreate(GrProcessorTestData* d) {
39 int numOctaves = d->fRandom->nextRangeU(2, 10);
40 bool stitchTiles = d->fRandom->nextBool();
41 SkScalar seed = SkIntToScalar(d->fRandom->nextU());
42 SkISize tileSize;
43 tileSize.fWidth = d->fRandom->nextRangeU(4, 4096);
44 tileSize.fHeight = d->fRandom->nextRangeU(4, 4096);
45 SkScalar baseFrequencyX = d->fRandom->nextRangeScalar(0.01f, 0.99f);
46 SkScalar baseFrequencyY = d->fRandom->nextRangeScalar(0.01f, 0.99f);
47
48 sk_sp<SkShader> shader(d->fRandom->nextBool()
49 ? SkShaders::MakeFractalNoise(baseFrequencyX,
50 baseFrequencyY,
52 seed,
53 stitchTiles ? &tileSize : nullptr)
54 : SkShaders::MakeTurbulence(baseFrequencyX,
55 baseFrequencyY,
57 seed,
58 stitchTiles ? &tileSize : nullptr));
59
60 GrTest::TestAsFPArgs asFPArgs(d);
62 shader.get(), asFPArgs.args(), GrTest::TestMatrix(d->fRandom));
63}
64#endif
65
66SkString GrPerlinNoise2Effect::Impl::emitHelper(EmitArgs& args) {
67 const GrPerlinNoise2Effect& pne = args.fFp.cast<GrPerlinNoise2Effect>();
68
69 GrGLSLFPFragmentBuilder* fragBuilder = args.fFragBuilder;
70
71 // Add noise function
72 const GrShaderVar gPerlinNoiseArgs[] = {{"chanCoord", SkSLType::kHalf},
73 {"noiseVec ", SkSLType::kHalf2}};
74
75 const GrShaderVar gPerlinNoiseStitchArgs[] = {{"chanCoord", SkSLType::kHalf},
76 {"noiseVec", SkSLType::kHalf2},
77 {"stitchData", SkSLType::kHalf2}};
78
79 SkString noiseCode;
80
81 noiseCode.append(
82 "half4 floorVal;"
83 "floorVal.xy = floor(noiseVec);"
84 "floorVal.zw = floorVal.xy + half2(1);"
85 "half2 fractVal = fract(noiseVec);"
86
87 // Hermite interpolation : t^2*(3 - 2*t)
88 "half2 noiseSmooth = smoothstep(0, 1, fractVal);"
89 );
90
91 // Adjust frequencies if we're stitching tiles
92 if (pne.stitchTiles()) {
93 noiseCode.append("floorVal -= step(stitchData.xyxy, floorVal) * stitchData.xyxy;");
94 }
95
96 // NOTE: We need to explicitly pass half4(1) as input color here, because the helper function
97 // can't see fInputColor (which is "_input" in the FP's outer function). skbug.com/10506
98 SkString sampleX = this->invokeChild(0, "half4(1)", args, "half2(floorVal.x + 0.5, 0.5)");
99 SkString sampleY = this->invokeChild(0, "half4(1)", args, "half2(floorVal.z + 0.5, 0.5)");
100 noiseCode.appendf("half2 latticeIdx = half2(%s.a, %s.a);", sampleX.c_str(), sampleY.c_str());
101
102 if (args.fShaderCaps->fPerlinNoiseRoundingFix) {
103 // Android rounding for Tegra devices, like, for example: Xoom (Tegra 2), Nexus 7 (Tegra 3).
104 // The issue is that colors aren't accurate enough on Tegra devices. For example, if an
105 // 8 bit value of 124 (or 0.486275 here) is entered, we can get a texture value of
106 // 123.513725 (or 0.484368 here). The following rounding operation prevents these precision
107 // issues from affecting the result of the noise by making sure that we only have multiples
108 // of 1/255. (Note that 1/255 is about 0.003921569, which is the value used here).
109 noiseCode.append(
110 "latticeIdx = floor(latticeIdx * half2(255.0) + half2(0.5)) * half2(0.003921569);");
111 }
112
113 // Get (x,y) coordinates with the permuted x
114 noiseCode.append("half4 bcoords = 256*latticeIdx.xyxy + floorVal.yyww;");
115
116 // This is the math to convert the two 16bit integer packed into rgba 8 bit input into a
117 // [-1,1] vector and perform a dot product between that vector and the provided vector.
118 // Save it as a string because we will repeat it 4x.
119 static constexpr const char* inc8bit = "0.00390625"; // 1.0 / 256.0
120 SkString dotLattice =
121 SkStringPrintf("dot((lattice.ga + lattice.rb*%s)*2 - half2(1), fractVal)", inc8bit);
122
123 SkString sampleA = this->invokeChild(1, "half4(1)", args, "half2(bcoords.x, chanCoord)");
124 SkString sampleB = this->invokeChild(1, "half4(1)", args, "half2(bcoords.y, chanCoord)");
125 SkString sampleC = this->invokeChild(1, "half4(1)", args, "half2(bcoords.w, chanCoord)");
126 SkString sampleD = this->invokeChild(1, "half4(1)", args, "half2(bcoords.z, chanCoord)");
127
128 // Compute u, at offset (0,0)
129 noiseCode.appendf("half4 lattice = %s;", sampleA.c_str());
130 noiseCode.appendf("half u = %s;", dotLattice.c_str());
131
132 // Compute v, at offset (-1,0)
133 noiseCode.append("fractVal.x -= 1.0;");
134 noiseCode.appendf("lattice = %s;", sampleB.c_str());
135 noiseCode.appendf("half v = %s;", dotLattice.c_str());
136
137 // Compute 'a' as a linear interpolation of 'u' and 'v'
138 noiseCode.append("half a = mix(u, v, noiseSmooth.x);");
139
140 // Compute v, at offset (-1,-1)
141 noiseCode.append("fractVal.y -= 1.0;");
142 noiseCode.appendf("lattice = %s;", sampleC.c_str());
143 noiseCode.appendf("v = %s;", dotLattice.c_str());
144
145 // Compute u, at offset (0,-1)
146 noiseCode.append("fractVal.x += 1.0;");
147 noiseCode.appendf("lattice = %s;", sampleD.c_str());
148 noiseCode.appendf("u = %s;", dotLattice.c_str());
149
150 // Compute 'b' as a linear interpolation of 'u' and 'v'
151 noiseCode.append("half b = mix(u, v, noiseSmooth.x);");
152
153 // Compute the noise as a linear interpolation of 'a' and 'b'
154 noiseCode.append("return mix(a, b, noiseSmooth.y);");
155
156 SkString noiseFuncName = fragBuilder->getMangledFunctionName("noiseFuncName");
157 if (pne.stitchTiles()) {
158 fragBuilder->emitFunction(SkSLType::kHalf,
159 noiseFuncName.c_str(),
160 {gPerlinNoiseStitchArgs, std::size(gPerlinNoiseStitchArgs)},
161 noiseCode.c_str());
162 } else {
163 fragBuilder->emitFunction(SkSLType::kHalf,
164 noiseFuncName.c_str(),
165 {gPerlinNoiseArgs, std::size(gPerlinNoiseArgs)},
166 noiseCode.c_str());
167 }
168
169 return noiseFuncName;
170}
171
172void GrPerlinNoise2Effect::Impl::emitCode(EmitArgs& args) {
173 SkString noiseFuncName = this->emitHelper(args);
174
175 const GrPerlinNoise2Effect& pne = args.fFp.cast<GrPerlinNoise2Effect>();
176
177 GrGLSLFPFragmentBuilder* fragBuilder = args.fFragBuilder;
178 GrGLSLUniformHandler* uniformHandler = args.fUniformHandler;
179
180 fBaseFrequencyUni = uniformHandler->addUniform(
181 &pne, kFragment_GrShaderFlag, SkSLType::kHalf2, "baseFrequency");
182 const char* baseFrequencyUni = uniformHandler->getUniformCStr(fBaseFrequencyUni);
183
184 const char* stitchDataUni = nullptr;
185 if (pne.stitchTiles()) {
186 fStitchDataUni = uniformHandler->addUniform(
187 &pne, kFragment_GrShaderFlag, SkSLType::kHalf2, "stitchData");
188 stitchDataUni = uniformHandler->getUniformCStr(fStitchDataUni);
189 }
190
191 // In the past, Perlin noise handled coordinates a bit differently than most shaders.
192 // It operated in device space, floored; it also had a one-pixel transform matrix applied to
193 // both the X and Y coordinates. This is roughly equivalent to adding 0.5 to the coordinates.
194 // This was originally done in order to better match preexisting golden images from WebKit.
195 // Perlin noise now operates in local space, which allows rotation to work correctly. To better
196 // approximate past behavior, we add 0.5 to the coordinates here. This is _not_ the same because
197 // this adjustment is occurring in local space, not device space, but it means that the "same"
198 // noise will be calculated regardless of CTM.
199 fragBuilder->codeAppendf(
200 "half2 noiseVec = half2((%s + 0.5) * %s);", args.fSampleCoord, baseFrequencyUni);
201
202 // Clear the color accumulator
203 fragBuilder->codeAppendf("half4 color = half4(0);");
204
205 if (pne.stitchTiles()) {
206 fragBuilder->codeAppendf("half2 stitchData = %s;", stitchDataUni);
207 }
208
209 fragBuilder->codeAppendf("half ratio = 1.0;");
210
211 // Loop over all octaves
212 fragBuilder->codeAppendf("for (int octave = 0; octave < %d; ++octave) {", pne.numOctaves());
213 fragBuilder->codeAppendf("color += ");
215 fragBuilder->codeAppend("abs(");
216 }
217
218 // There are 4 lines, put y coords at center of each.
219 static constexpr const char* chanCoordR = "0.5";
220 static constexpr const char* chanCoordG = "1.5";
221 static constexpr const char* chanCoordB = "2.5";
222 static constexpr const char* chanCoordA = "3.5";
223 if (pne.stitchTiles()) {
224 fragBuilder->codeAppendf(
225 "half4(%s(%s, noiseVec, stitchData), %s(%s, noiseVec, stitchData),"
226 "%s(%s, noiseVec, stitchData), %s(%s, noiseVec, stitchData))",
227 noiseFuncName.c_str(),
228 chanCoordR,
229 noiseFuncName.c_str(),
230 chanCoordG,
231 noiseFuncName.c_str(),
232 chanCoordB,
233 noiseFuncName.c_str(),
234 chanCoordA);
235 } else {
236 fragBuilder->codeAppendf(
237 "half4(%s(%s, noiseVec), %s(%s, noiseVec),"
238 "%s(%s, noiseVec), %s(%s, noiseVec))",
239 noiseFuncName.c_str(),
240 chanCoordR,
241 noiseFuncName.c_str(),
242 chanCoordG,
243 noiseFuncName.c_str(),
244 chanCoordB,
245 noiseFuncName.c_str(),
246 chanCoordA);
247 }
249 fragBuilder->codeAppend(")"); // end of "abs("
250 }
251 fragBuilder->codeAppend(" * ratio;");
252
253 fragBuilder->codeAppend(
254 "noiseVec *= half2(2.0);"
255 "ratio *= 0.5;");
256
257 if (pne.stitchTiles()) {
258 fragBuilder->codeAppend("stitchData *= half2(2.0);");
259 }
260 fragBuilder->codeAppend("}"); // end of the for loop on octaves
261
263 // The value of turbulenceFunctionResult comes from ((turbulenceFunctionResult) + 1) / 2
264 // by fractalNoise and (turbulenceFunctionResult) by turbulence.
265 fragBuilder->codeAppendf("color = color * half4(0.5) + half4(0.5);");
266 }
267
268 // Clamp values
269 fragBuilder->codeAppendf("color = saturate(color);");
270
271 // Pre-multiply the result
272 fragBuilder->codeAppendf("return half4(color.rgb * color.aaa, color.a);");
273}
274
275void GrPerlinNoise2Effect::Impl::onSetData(const GrGLSLProgramDataManager& pdman,
276 const GrFragmentProcessor& processor) {
277 const GrPerlinNoise2Effect& turbulence = processor.cast<GrPerlinNoise2Effect>();
278
279 const SkVector& baseFrequency = turbulence.baseFrequency();
280 pdman.set2f(fBaseFrequencyUni, baseFrequency.fX, baseFrequency.fY);
281
282 if (turbulence.stitchTiles()) {
283 const SkPerlinNoiseShader::StitchData& stitchData = turbulence.stitchData();
284 pdman.set2f(fStitchDataUni,
285 SkIntToScalar(stitchData.fWidth),
286 SkIntToScalar(stitchData.fHeight));
287 }
288}
289
290void GrPerlinNoise2Effect::onAddToKey(const GrShaderCaps& caps, skgpu::KeyBuilder* b) const {
291 uint32_t key = fNumOctaves;
292 key = key << 3; // Make room for next 3 bits
293 switch (fType) {
295 key |= 0x1;
296 break;
298 key |= 0x2;
299 break;
300 default:
301 // leave key at 0
302 break;
303 }
304 if (fStitchTiles) {
305 key |= 0x4; // Flip the 3rd bit if tile stitching is on
306 }
307 b->add32(key);
308}
#define GR_DEFINE_FRAGMENT_PROCESSOR_TEST(...)
@ kFragment_GrShaderFlag
Definition: GrTypesPriv.h:287
#define SkIntToScalar(x)
Definition: SkScalar.h:57
SK_API SkString SkStringPrintf(const char *format,...) SK_PRINTF_LIKE(1
Creates a new string and writes into it using a printf()-style format.
virtual void set2f(UniformHandle, float, float) const =0
virtual const char * getUniformCStr(UniformHandle u) const =0
UniformHandle addUniform(const GrProcessor *owner, uint32_t visibility, SkSLType type, const char *name, const char **outName=nullptr)
const SkVector & baseFrequency() const
const SkPerlinNoiseShader::StitchData & stitchData() const
SkPerlinNoiseShaderType type() const
const T & cast() const
Definition: GrProcessor.h:127
void append(const char text[])
Definition: SkString.h:203
const char * c_str() const
Definition: SkString.h:133
void void void appendf(const char format[],...) SK_PRINTF_LIKE(2
Definition: SkString.cpp:550
VULKAN_HPP_DEFAULT_DISPATCH_LOADER_DYNAMIC_STORAGE auto & d
Definition: main.cc:19
float SkScalar
Definition: extension.cpp:12
static bool b
G_BEGIN_DECLS G_MODULE_EXPORT FlValue * args
std::unique_ptr< GrFragmentProcessor > Make(const SkMaskFilter *maskfilter, const GrFPArgs &args, const SkMatrix &ctm)
SK_API sk_sp< SkShader > MakeTurbulence(SkScalar baseFrequencyX, SkScalar baseFrequencyY, int numOctaves, SkScalar seed, const SkISize *tileSize=nullptr)
SK_API sk_sp< SkShader > MakeFractalNoise(SkScalar baseFrequencyX, SkScalar baseFrequencyY, int numOctaves, SkScalar seed, const SkISize *tileSize=nullptr)
Definition: SkSize.h:16
int32_t fHeight
Definition: SkSize.h:18
int32_t fWidth
Definition: SkSize.h:17