Render Ganesh's Perlin noise in local coordinates, not device space.
This fixes the behavior of scaling and rotation for Perlin noise in
Ganesh. Software and Graphite will need fixes as well.
Bug: b/40045243
Change-Id: I91764b78faa5bb60e9ac61d08def8c5290253575
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/815436
Auto-Submit: John Stiles <johnstiles@google.com>
Commit-Queue: John Stiles <johnstiles@google.com>
Reviewed-by: Michael Ludwig <michaelludwig@google.com>
diff --git a/src/gpu/ganesh/GrFragmentProcessors.cpp b/src/gpu/ganesh/GrFragmentProcessors.cpp
index 865c9cc..a726a22 100644
--- a/src/gpu/ganesh/GrFragmentProcessors.cpp
+++ b/src/gpu/ganesh/GrFragmentProcessors.cpp
@@ -662,18 +662,14 @@
SkASSERT(args.fContext);
SkASSERT(shader->numOctaves());
- const SkMatrix& totalMatrix = mRec.totalMatrix();
-
// Either we don't stitch tiles, or we have a valid tile size
SkASSERT(!shader->stitchTiles() || !shader->tileSize().isEmpty());
- auto paintingData = shader->getPaintingData(totalMatrix);
+
+ auto paintingData = shader->getPaintingData(SkMatrix::I());
paintingData->generateBitmaps();
- // Like shadeSpan, we start from device space. We will account for that below with a device
- // space effect.
-
- auto context = args.fContext;
+ GrRecordingContext* context = args.fContext;
const SkBitmap& permutationsBitmap = paintingData->getPermutationsBitmap();
const SkBitmap& noiseBitmap = paintingData->getNoiseBitmap();
@@ -682,22 +678,30 @@
context,
permutationsBitmap,
/*label=*/"PerlinNoiseShader_FragmentProcessor_PermutationsView"));
+
auto noiseView = std::get<0>(GrMakeCachedBitmapProxyView(
context, noiseBitmap, /*label=*/"PerlinNoiseShader_FragmentProcessor_NoiseView"));
- if (permutationsView && noiseView) {
- return GrFragmentProcessor::DeviceSpace(
- GrMatrixEffect::Make(SkMatrix::Translate(1 - totalMatrix.getTranslateX(),
- 1 - totalMatrix.getTranslateY()),
- GrPerlinNoise2Effect::Make(shader->noiseType(),
- shader->numOctaves(),
- shader->stitchTiles(),
- std::move(paintingData),
- std::move(permutationsView),
- std::move(noiseView),
- *context->priv().caps())));
+ if (!permutationsView || !noiseView) {
+ return nullptr;
}
- return nullptr;
+
+ std::unique_ptr<GrFragmentProcessor> fp =
+ GrPerlinNoise2Effect::Make(shader->noiseType(),
+ shader->numOctaves(),
+ shader->stitchTiles(),
+ std::move(paintingData),
+ std::move(permutationsView),
+ std::move(noiseView),
+ *context->priv().caps());
+ if (!fp) {
+ return nullptr;
+ }
+ auto [total, ok] = mRec.applyForFragmentProcessor({});
+ if (!ok) {
+ return nullptr;
+ }
+ return GrMatrixEffect::Make(total, std::move(fp));
}
static std::unique_ptr<GrFragmentProcessor> make_shader_fp(const SkPictureShader* shader,
diff --git a/src/gpu/ganesh/effects/GrPerlinNoise2Effect.cpp b/src/gpu/ganesh/effects/GrPerlinNoise2Effect.cpp
index dab51ae..f155192 100644
--- a/src/gpu/ganesh/effects/GrPerlinNoise2Effect.cpp
+++ b/src/gpu/ganesh/effects/GrPerlinNoise2Effect.cpp
@@ -83,8 +83,8 @@
"floorVal.zw = floorVal.xy + half2(1);"
"half2 fractVal = fract(noiseVec);"
- // smooth curve : t^2*(3 - 2*t)
- "half2 noiseSmooth = fractVal*fractVal*(half2(3) - 2*fractVal);"
+ // Hermite interpolation : t^2*(3 - 2*t)
+ "half2 noiseSmooth = smoothstep(0, 1, fractVal);"
);
// Adjust frequencies if we're stitching tiles
@@ -187,9 +187,16 @@
stitchDataUni = uniformHandler->getUniformCStr(fStitchDataUni);
}
- // There are rounding errors if the floor operation is not performed here
+ // 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(floor(%s.xy) * %s);", args.fSampleCoord, baseFrequencyUni);
+ "half2 noiseVec = half2((%s + 0.5) * %s);", args.fSampleCoord, baseFrequencyUni);
// Clear the color accumulator
fragBuilder->codeAppendf("half4 color = half4(0);");
diff --git a/src/shaders/SkPerlinNoiseShaderImpl.h b/src/shaders/SkPerlinNoiseShaderImpl.h
index 8d975d0..9d5b6e1 100644
--- a/src/shaders/SkPerlinNoiseShaderImpl.h
+++ b/src/shaders/SkPerlinNoiseShaderImpl.h
@@ -295,6 +295,8 @@
SkISize tileSize() const { return fTileSize; }
std::unique_ptr<PaintingData> getPaintingData(const SkMatrix& mat) const {
+ // TODO(b/40045243): the passed-in matrix should be removed once Graphite switches over to
+ // local coordinates
return std::make_unique<PaintingData>(
fTileSize, fSeed, fBaseFrequencyX, fBaseFrequencyY, mat);
}