/* * 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 #include class SkShader; ///////////////////////////////////////////////////////////////////// GR_DEFINE_FRAGMENT_PROCESSOR_TEST(GrPerlinNoise2Effect) #if defined(GPU_TEST_UTILS) std::unique_ptr 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 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(); 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/10506 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(); 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(); 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); }