xref: /aosp_15_r20/external/skia/src/gpu/ganesh/effects/GrPerlinNoise2Effect.cpp (revision c8dee2aa9b3f27cf6c858bd81872bdeb2c07ed17)
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 
8 #include "src/gpu/ganesh/effects/GrPerlinNoise2Effect.h"
9 
10 #include "include/core/SkRefCnt.h"
11 #include "include/core/SkScalar.h"
12 #include "include/core/SkSize.h"
13 #include "include/effects/SkPerlinNoiseShader.h"
14 #include "include/private/gpu/ganesh/GrTypesPriv.h"
15 #include "src/base/SkRandom.h"
16 #include "src/core/SkSLTypeShared.h"
17 #include "src/gpu/KeyBuilder.h"
18 #include "src/gpu/ganesh/GrFragmentProcessor.h"
19 #include "src/gpu/ganesh/GrFragmentProcessors.h"
20 #include "src/gpu/ganesh/GrProcessorUnitTest.h"
21 #include "src/gpu/ganesh/GrShaderCaps.h"
22 #include "src/gpu/ganesh/GrShaderVar.h"
23 #include "src/gpu/ganesh/GrTestUtils.h"
24 #include "src/gpu/ganesh/glsl/GrGLSLFragmentShaderBuilder.h"
25 #include "src/gpu/ganesh/glsl/GrGLSLProgramDataManager.h"
26 #include "src/gpu/ganesh/glsl/GrGLSLUniformHandler.h"
27 #include "src/shaders/SkPerlinNoiseShaderType.h"
28 
29 #include <cstdint>
30 #include <iterator>
31 
32 class SkShader;
33 
34 /////////////////////////////////////////////////////////////////////
GR_DEFINE_FRAGMENT_PROCESSOR_TEST(GrPerlinNoise2Effect)35 GR_DEFINE_FRAGMENT_PROCESSOR_TEST(GrPerlinNoise2Effect)
36 
37 #if defined(GPU_TEST_UTILS)
38 std::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,
51                                                                  numOctaves,
52                                                                  seed,
53                                                                  stitchTiles ? &tileSize : nullptr)
54                                    : SkShaders::MakeTurbulence(baseFrequencyX,
55                                                                baseFrequencyY,
56                                                                numOctaves,
57                                                                seed,
58                                                                stitchTiles ? &tileSize : nullptr));
59 
60     GrTest::TestAsFPArgs asFPArgs(d);
61     return GrFragmentProcessors::Make(
62             shader.get(), asFPArgs.args(), GrTest::TestMatrix(d->fRandom));
63 }
64 #endif
65 
emitHelper(EmitArgs & args)66 SkString 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 
emitCode(EmitArgs & args)172 void 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 += ");
214     if (pne.type() != SkPerlinNoiseShaderType::kFractalNoise) {
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     }
248     if (pne.type() != SkPerlinNoiseShaderType::kFractalNoise) {
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 
262     if (pne.type() == SkPerlinNoiseShaderType::kFractalNoise) {
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 
onSetData(const GrGLSLProgramDataManager & pdman,const GrFragmentProcessor & processor)275 void 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 
onAddToKey(const GrShaderCaps & caps,skgpu::KeyBuilder * b) const290 void 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) {
294         case SkPerlinNoiseShaderType::kFractalNoise:
295             key |= 0x1;
296             break;
297         case SkPerlinNoiseShaderType::kTurbulence:
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 }
309