blob: a2b89d8efce4d385dddf784d31fef428563039ae [file] [log] [blame]
/*
* 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(GR_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/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<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);
}