| /* | |
| * Copyright 2023 Google LLC | |
| * | |
| * Use of this source code is governed by a BSD-style license that can be | |
| * found in the LICENSE file. | |
| */ | |
| #include "src/gpu/ganesh/effects/GrPerlinNoise2Effect.h" | |
| #include "include/core/SkRefCnt.h" | |
| #include "include/core/SkScalar.h" | |
| #include "include/core/SkSize.h" | |
| #include "include/effects/SkPerlinNoiseShader.h" | |
| #include "include/private/gpu/ganesh/GrTypesPriv.h" | |
| #include "src/base/SkRandom.h" | |
| #include "src/core/SkSLTypeShared.h" | |
| #include "src/gpu/KeyBuilder.h" | |
| #include "src/gpu/ganesh/GrFragmentProcessor.h" | |
| #include "src/gpu/ganesh/GrFragmentProcessors.h" | |
| #include "src/gpu/ganesh/GrProcessorUnitTest.h" | |
| #include "src/gpu/ganesh/GrShaderCaps.h" | |
| #include "src/gpu/ganesh/GrShaderVar.h" | |
| #include "src/gpu/ganesh/GrTestUtils.h" | |
| #include "src/gpu/ganesh/glsl/GrGLSLFragmentShaderBuilder.h" | |
| #include "src/gpu/ganesh/glsl/GrGLSLProgramDataManager.h" | |
| #include "src/gpu/ganesh/glsl/GrGLSLUniformHandler.h" | |
| #include "src/shaders/SkPerlinNoiseShaderType.h" | |
| #include <cstdint> | |
| #include <iterator> | |
| class SkShader; | |
| ///////////////////////////////////////////////////////////////////// | |
| GR_DEFINE_FRAGMENT_PROCESSOR_TEST(GrPerlinNoise2Effect) | |
| #if defined(GPU_TEST_UTILS) | |
| std::unique_ptr<GrFragmentProcessor> GrPerlinNoise2Effect::TestCreate(GrProcessorTestData* d) { | |
| int numOctaves = d->fRandom->nextRangeU(2, 10); | |
| bool stitchTiles = d->fRandom->nextBool(); | |
| SkScalar seed = SkIntToScalar(d->fRandom->nextU()); | |
| SkISize tileSize; | |
| tileSize.fWidth = d->fRandom->nextRangeU(4, 4096); | |
| tileSize.fHeight = d->fRandom->nextRangeU(4, 4096); | |
| SkScalar baseFrequencyX = d->fRandom->nextRangeScalar(0.01f, 0.99f); | |
| SkScalar baseFrequencyY = d->fRandom->nextRangeScalar(0.01f, 0.99f); | |
| sk_sp<SkShader> shader(d->fRandom->nextBool() | |
| ? SkShaders::MakeFractalNoise(baseFrequencyX, | |
| baseFrequencyY, | |
| numOctaves, | |
| seed, | |
| stitchTiles ? &tileSize : nullptr) | |
| : SkShaders::MakeTurbulence(baseFrequencyX, | |
| baseFrequencyY, | |
| numOctaves, | |
| seed, | |
| stitchTiles ? &tileSize : nullptr)); | |
| GrTest::TestAsFPArgs asFPArgs(d); | |
| return GrFragmentProcessors::Make( | |
| shader.get(), asFPArgs.args(), GrTest::TestMatrix(d->fRandom)); | |
| } | |
| #endif | |
| SkString GrPerlinNoise2Effect::Impl::emitHelper(EmitArgs& args) { | |
| const GrPerlinNoise2Effect& pne = args.fFp.cast<GrPerlinNoise2Effect>(); | |
| GrGLSLFPFragmentBuilder* fragBuilder = args.fFragBuilder; | |
| // Add noise function | |
| const GrShaderVar gPerlinNoiseArgs[] = {{"chanCoord", SkSLType::kHalf}, | |
| {"noiseVec ", SkSLType::kHalf2}}; | |
| const GrShaderVar gPerlinNoiseStitchArgs[] = {{"chanCoord", SkSLType::kHalf}, | |
| {"noiseVec", SkSLType::kHalf2}, | |
| {"stitchData", SkSLType::kHalf2}}; | |
| SkString noiseCode; | |
| noiseCode.append( | |
| "half4 floorVal;" | |
| "floorVal.xy = floor(noiseVec);" | |
| "floorVal.zw = floorVal.xy + half2(1);" | |
| "half2 fractVal = fract(noiseVec);" | |
| // Hermite interpolation : t^2*(3 - 2*t) | |
| "half2 noiseSmooth = smoothstep(0, 1, fractVal);" | |
| ); | |
| // Adjust frequencies if we're stitching tiles | |
| if (pne.stitchTiles()) { | |
| noiseCode.append("floorVal -= step(stitchData.xyxy, floorVal) * stitchData.xyxy;"); | |
| } | |
| // NOTE: We need to explicitly pass half4(1) as input color here, because the helper function | |
| // can't see fInputColor (which is "_input" in the FP's outer function). skbug.com/40041839 | |
| SkString sampleX = this->invokeChild(0, "half4(1)", args, "half2(floorVal.x + 0.5, 0.5)"); | |
| SkString sampleY = this->invokeChild(0, "half4(1)", args, "half2(floorVal.z + 0.5, 0.5)"); | |
| noiseCode.appendf("half2 latticeIdx = half2(%s.a, %s.a);", sampleX.c_str(), sampleY.c_str()); | |
| if (args.fShaderCaps->fPerlinNoiseRoundingFix) { | |
| // Android rounding for Tegra devices, like, for example: Xoom (Tegra 2), Nexus 7 (Tegra 3). | |
| // The issue is that colors aren't accurate enough on Tegra devices. For example, if an | |
| // 8 bit value of 124 (or 0.486275 here) is entered, we can get a texture value of | |
| // 123.513725 (or 0.484368 here). The following rounding operation prevents these precision | |
| // issues from affecting the result of the noise by making sure that we only have multiples | |
| // of 1/255. (Note that 1/255 is about 0.003921569, which is the value used here). | |
| noiseCode.append( | |
| "latticeIdx = floor(latticeIdx * half2(255.0) + half2(0.5)) * half2(0.003921569);"); | |
| } | |
| // Get (x,y) coordinates with the permuted x | |
| noiseCode.append("half4 bcoords = 256*latticeIdx.xyxy + floorVal.yyww;"); | |
| // This is the math to convert the two 16bit integer packed into rgba 8 bit input into a | |
| // [-1,1] vector and perform a dot product between that vector and the provided vector. | |
| // Save it as a string because we will repeat it 4x. | |
| static constexpr const char* inc8bit = "0.00390625"; // 1.0 / 256.0 | |
| SkString dotLattice = | |
| SkStringPrintf("dot((lattice.ga + lattice.rb*%s)*2 - half2(1), fractVal)", inc8bit); | |
| SkString sampleA = this->invokeChild(1, "half4(1)", args, "half2(bcoords.x, chanCoord)"); | |
| SkString sampleB = this->invokeChild(1, "half4(1)", args, "half2(bcoords.y, chanCoord)"); | |
| SkString sampleC = this->invokeChild(1, "half4(1)", args, "half2(bcoords.w, chanCoord)"); | |
| SkString sampleD = this->invokeChild(1, "half4(1)", args, "half2(bcoords.z, chanCoord)"); | |
| // Compute u, at offset (0,0) | |
| noiseCode.appendf("half4 lattice = %s;", sampleA.c_str()); | |
| noiseCode.appendf("half u = %s;", dotLattice.c_str()); | |
| // Compute v, at offset (-1,0) | |
| noiseCode.append("fractVal.x -= 1.0;"); | |
| noiseCode.appendf("lattice = %s;", sampleB.c_str()); | |
| noiseCode.appendf("half v = %s;", dotLattice.c_str()); | |
| // Compute 'a' as a linear interpolation of 'u' and 'v' | |
| noiseCode.append("half a = mix(u, v, noiseSmooth.x);"); | |
| // Compute v, at offset (-1,-1) | |
| noiseCode.append("fractVal.y -= 1.0;"); | |
| noiseCode.appendf("lattice = %s;", sampleC.c_str()); | |
| noiseCode.appendf("v = %s;", dotLattice.c_str()); | |
| // Compute u, at offset (0,-1) | |
| noiseCode.append("fractVal.x += 1.0;"); | |
| noiseCode.appendf("lattice = %s;", sampleD.c_str()); | |
| noiseCode.appendf("u = %s;", dotLattice.c_str()); | |
| // Compute 'b' as a linear interpolation of 'u' and 'v' | |
| noiseCode.append("half b = mix(u, v, noiseSmooth.x);"); | |
| // Compute the noise as a linear interpolation of 'a' and 'b' | |
| noiseCode.append("return mix(a, b, noiseSmooth.y);"); | |
| SkString noiseFuncName = fragBuilder->getMangledFunctionName("noiseFuncName"); | |
| if (pne.stitchTiles()) { | |
| fragBuilder->emitFunction(SkSLType::kHalf, | |
| noiseFuncName.c_str(), | |
| {gPerlinNoiseStitchArgs, std::size(gPerlinNoiseStitchArgs)}, | |
| noiseCode.c_str()); | |
| } else { | |
| fragBuilder->emitFunction(SkSLType::kHalf, | |
| noiseFuncName.c_str(), | |
| {gPerlinNoiseArgs, std::size(gPerlinNoiseArgs)}, | |
| noiseCode.c_str()); | |
| } | |
| return noiseFuncName; | |
| } | |
| void GrPerlinNoise2Effect::Impl::emitCode(EmitArgs& args) { | |
| SkString noiseFuncName = this->emitHelper(args); | |
| const GrPerlinNoise2Effect& pne = args.fFp.cast<GrPerlinNoise2Effect>(); | |
| GrGLSLFPFragmentBuilder* fragBuilder = args.fFragBuilder; | |
| GrGLSLUniformHandler* uniformHandler = args.fUniformHandler; | |
| fBaseFrequencyUni = uniformHandler->addUniform( | |
| &pne, kFragment_GrShaderFlag, SkSLType::kHalf2, "baseFrequency"); | |
| const char* baseFrequencyUni = uniformHandler->getUniformCStr(fBaseFrequencyUni); | |
| const char* stitchDataUni = nullptr; | |
| if (pne.stitchTiles()) { | |
| fStitchDataUni = uniformHandler->addUniform( | |
| &pne, kFragment_GrShaderFlag, SkSLType::kHalf2, "stitchData"); | |
| stitchDataUni = uniformHandler->getUniformCStr(fStitchDataUni); | |
| } | |
| // In the past, Perlin noise handled coordinates a bit differently than most shaders. | |
| // It operated in device space, floored; it also had a one-pixel transform matrix applied to | |
| // both the X and Y coordinates. This is roughly equivalent to adding 0.5 to the coordinates. | |
| // This was originally done in order to better match preexisting golden images from WebKit. | |
| // Perlin noise now operates in local space, which allows rotation to work correctly. To better | |
| // approximate past behavior, we add 0.5 to the coordinates here. This is _not_ the same because | |
| // this adjustment is occurring in local space, not device space, but it means that the "same" | |
| // noise will be calculated regardless of CTM. | |
| fragBuilder->codeAppendf( | |
| "half2 noiseVec = half2((%s + 0.5) * %s);", args.fSampleCoord, baseFrequencyUni); | |
| // Clear the color accumulator | |
| fragBuilder->codeAppendf("half4 color = half4(0);"); | |
| if (pne.stitchTiles()) { | |
| fragBuilder->codeAppendf("half2 stitchData = %s;", stitchDataUni); | |
| } | |
| fragBuilder->codeAppendf("half ratio = 1.0;"); | |
| // Loop over all octaves | |
| fragBuilder->codeAppendf("for (int octave = 0; octave < %d; ++octave) {", pne.numOctaves()); | |
| fragBuilder->codeAppendf("color += "); | |
| if (pne.type() != SkPerlinNoiseShaderType::kFractalNoise) { | |
| fragBuilder->codeAppend("abs("); | |
| } | |
| // There are 4 lines, put y coords at center of each. | |
| static constexpr const char* chanCoordR = "0.5"; | |
| static constexpr const char* chanCoordG = "1.5"; | |
| static constexpr const char* chanCoordB = "2.5"; | |
| static constexpr const char* chanCoordA = "3.5"; | |
| if (pne.stitchTiles()) { | |
| fragBuilder->codeAppendf( | |
| "half4(%s(%s, noiseVec, stitchData), %s(%s, noiseVec, stitchData)," | |
| "%s(%s, noiseVec, stitchData), %s(%s, noiseVec, stitchData))", | |
| noiseFuncName.c_str(), | |
| chanCoordR, | |
| noiseFuncName.c_str(), | |
| chanCoordG, | |
| noiseFuncName.c_str(), | |
| chanCoordB, | |
| noiseFuncName.c_str(), | |
| chanCoordA); | |
| } else { | |
| fragBuilder->codeAppendf( | |
| "half4(%s(%s, noiseVec), %s(%s, noiseVec)," | |
| "%s(%s, noiseVec), %s(%s, noiseVec))", | |
| noiseFuncName.c_str(), | |
| chanCoordR, | |
| noiseFuncName.c_str(), | |
| chanCoordG, | |
| noiseFuncName.c_str(), | |
| chanCoordB, | |
| noiseFuncName.c_str(), | |
| chanCoordA); | |
| } | |
| if (pne.type() != SkPerlinNoiseShaderType::kFractalNoise) { | |
| fragBuilder->codeAppend(")"); // end of "abs(" | |
| } | |
| fragBuilder->codeAppend(" * ratio;"); | |
| fragBuilder->codeAppend( | |
| "noiseVec *= half2(2.0);" | |
| "ratio *= 0.5;"); | |
| if (pne.stitchTiles()) { | |
| fragBuilder->codeAppend("stitchData *= half2(2.0);"); | |
| } | |
| fragBuilder->codeAppend("}"); // end of the for loop on octaves | |
| if (pne.type() == SkPerlinNoiseShaderType::kFractalNoise) { | |
| // The value of turbulenceFunctionResult comes from ((turbulenceFunctionResult) + 1) / 2 | |
| // by fractalNoise and (turbulenceFunctionResult) by turbulence. | |
| fragBuilder->codeAppendf("color = color * half4(0.5) + half4(0.5);"); | |
| } | |
| // Clamp values | |
| fragBuilder->codeAppendf("color = saturate(color);"); | |
| // Pre-multiply the result | |
| fragBuilder->codeAppendf("return half4(color.rgb * color.aaa, color.a);"); | |
| } | |
| void GrPerlinNoise2Effect::Impl::onSetData(const GrGLSLProgramDataManager& pdman, | |
| const GrFragmentProcessor& processor) { | |
| const GrPerlinNoise2Effect& turbulence = processor.cast<GrPerlinNoise2Effect>(); | |
| const SkVector& baseFrequency = turbulence.baseFrequency(); | |
| pdman.set2f(fBaseFrequencyUni, baseFrequency.fX, baseFrequency.fY); | |
| if (turbulence.stitchTiles()) { | |
| const SkPerlinNoiseShader::StitchData& stitchData = turbulence.stitchData(); | |
| pdman.set2f(fStitchDataUni, | |
| SkIntToScalar(stitchData.fWidth), | |
| SkIntToScalar(stitchData.fHeight)); | |
| } | |
| } | |
| void GrPerlinNoise2Effect::onAddToKey(const GrShaderCaps& caps, skgpu::KeyBuilder* b) const { | |
| uint32_t key = fNumOctaves; | |
| key = key << 3; // Make room for next 3 bits | |
| switch (fType) { | |
| case SkPerlinNoiseShaderType::kFractalNoise: | |
| key |= 0x1; | |
| break; | |
| case SkPerlinNoiseShaderType::kTurbulence: | |
| key |= 0x2; | |
| break; | |
| default: | |
| // leave key at 0 | |
| break; | |
| } | |
| if (fStitchTiles) { | |
| key |= 0x4; // Flip the 3rd bit if tile stitching is on | |
| } | |
| b->add32(key); | |
| } |