Add support for implementing ShaderMode::kRepeat with MIPs in GrTextureEffect.
Change-Id: I12ea00b35967f13e81410c584e34b62eea6c2ffe
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/270445
Commit-Queue: Brian Salomon <bsalomon@google.com>
Reviewed-by: Michael Ludwig <michaelludwig@google.com>
diff --git a/gm/texelsubset.cpp b/gm/texelsubset.cpp
index 95dc059..e1d7b03 100644
--- a/gm/texelsubset.cpp
+++ b/gm/texelsubset.cpp
@@ -63,8 +63,8 @@
SkISize onISize() override {
static constexpr int kN = GrSamplerState::kWrapModeCount;
- int w = kTestPad + 2*kN * (kImageSize.width() + 2*kDrawPad + kTestPad);
- int h = kTestPad + 2*kN * (kImageSize.height() + 2*kDrawPad + kTestPad);
+ int w = kTestPad + 2*kN*(kImageSize.width() + 2*kDrawPad + kTestPad);
+ int h = kTestPad + 2*kN*(kImageSize.height() + 2*kDrawPad + kTestPad);
return {w, h};
}
@@ -134,29 +134,19 @@
GrSamplerState sampler(wmx, wmy, fFilter);
- // kRepeat doesn't work with MIP map filtering yet.
- bool shouldWork =
- fFilter != GrSamplerState::Filter::kMipMap ||
- (sampler.wrapModeX() != GrSamplerState::WrapMode::kRepeat &&
- sampler.wrapModeY() != GrSamplerState::WrapMode::kRepeat);
-
drawRect = localRect.makeOffset(x, y);
std::unique_ptr<GrFragmentProcessor> fp1;
- if (shouldWork) {
- fp1 = GrTextureEffect::MakeTexelSubset(view,
- fBitmap.alphaType(),
- textureMatrices[tm],
- sampler,
- texelSubset,
- caps);
- if (!fp1) {
- continue;
- }
- } else {
- fp1 = GrConstColorProcessor::Make(
- {0, 0, 0, 0.3f}, GrConstColorProcessor::InputMode::kModulateRGBA);
+ fp1 = GrTextureEffect::MakeTexelSubset(view,
+ fBitmap.alphaType(),
+ textureMatrices[tm],
+ sampler,
+ texelSubset,
+ caps);
+ if (!fp1) {
+ continue;
}
+
// Throw a translate in the local matrix just to test having something other
// than identity. Compensate with an offset local rect.
static constexpr SkVector kT = {-100, 300};
diff --git a/src/gpu/effects/GrTextureEffect.cpp b/src/gpu/effects/GrTextureEffect.cpp
index 5f3ff77..e58297d 100644
--- a/src/gpu/effects/GrTextureEffect.cpp
+++ b/src/gpu/effects/GrTextureEffect.cpp
@@ -102,13 +102,6 @@
: Span{SK_FloatNegativeInfinity, SK_FloatInfinity};
auto y = resolve(dim.height(), sampler.wrapModeY(), subsetY, domainY);
- if (filter == Filter::kMipMap && (x.fShaderMode == ShaderMode::kRepeat ||
- y.fShaderMode == ShaderMode::kRepeat)) {
- // Have not yet implemented necessary shader filter code to get LOD selection
- // correct with kRepeat.
- filter = Filter::kBilerp;
- }
-
fHWSampler = {x.fHWMode, y.fHWMode, filter};
fShaderModes[0] = x.fShaderMode;
fShaderModes[1] = y.fShaderMode;
@@ -203,8 +196,7 @@
case GrSamplerState::Filter::kBilerp:
return FilterLogic::kRepeatBilerp;
case GrSamplerState::Filter::kMipMap:
- // return FilterLogic::kRepeatMipMap;
- SkUNREACHABLE;
+ return FilterLogic::kRepeatMipMap;
}
SkUNREACHABLE;
case ShaderMode::kDecal:
@@ -308,7 +300,7 @@
}
// Generates a string to read at a coordinate, normalizing coords if necessary.
- auto read = [fb, norm, &sampler = args.fTexSamplers[0]](const char* coord) {
+ auto read = [&](const char* coord) {
SkString result;
SkString normCoord;
if (norm) {
@@ -316,15 +308,17 @@
} else {
normCoord = coord;
}
- fb->appendTextureLookup(&result, sampler, normCoord.c_str());
+ fb->appendTextureLookup(&result, args.fTexSamplers[0], normCoord.c_str());
return result;
};
// Implements coord wrapping for kRepeat and kMirrorRepeat
- auto subsetCoord = [fb, subsetName](ShaderMode mode,
- const char* coordSwizzle,
- const char* subsetStartSwizzle,
- const char* subsetStopSwizzle) {
+ auto subsetCoord = [&](ShaderMode mode,
+ const char* coordSwizzle,
+ const char* subsetStartSwizzle,
+ const char* subsetStopSwizzle,
+ const char* extraCoord,
+ const char* coordWeight) {
switch (mode) {
// These modes either don't use the subset rect or don't need to map the
// coords to be within the subset.
@@ -335,12 +329,47 @@
coordSwizzle);
break;
case ShaderMode::kRepeat:
- fb->codeAppendf(
- "subsetCoord.%s = mod(inCoord.%s - %s.%s, %s.%s - %s.%s) + "
- "%s.%s;",
- coordSwizzle, coordSwizzle, subsetName, subsetStartSwizzle,
- subsetName, subsetStopSwizzle, subsetName, subsetStartSwizzle,
- subsetName, subsetStartSwizzle);
+ if (filter == Filter::kMipMap) {
+ // The approach here is to generate two sets of texture coords that
+ // are both "moving" at the same speed (if not direction) as
+ // inCoords. We accomplish that by using two out of phase mirror
+ // repeat coords. We will always sample using both coords but the
+ // read from the upward sloping one is selected using a weight
+ // that transitions from one set to the other near the reflection
+ // point. Like the coords, the weight is a saw-tooth function,
+ // phase-shifted, vertically translated, and then clamped to 0..1.
+ // TODO: Skip this and use textureGrad() when available.
+ SkASSERT(extraCoord);
+ SkASSERT(coordWeight);
+ fb->codeAppend("{");
+ fb->codeAppendf("float w = %s.%s - %s.%s;", subsetName,
+ subsetStopSwizzle, subsetName, subsetStartSwizzle);
+ fb->codeAppendf("float w2 = 2 * w;");
+ fb->codeAppendf("float d = inCoord.%s - %s.%s;", coordSwizzle,
+ subsetName, subsetStartSwizzle);
+ fb->codeAppend("float m = mod(d, w2);");
+ fb->codeAppend("float o = mix(m, w2 - m, step(w, m));");
+ fb->codeAppendf("subsetCoord.%s = o + %s.%s;", coordSwizzle,
+ subsetName, subsetStartSwizzle);
+ fb->codeAppendf("%s = w - o + %s.%s;", extraCoord, subsetName,
+ subsetStartSwizzle);
+ // coordWeight is used as the third param of mix() to blend between a
+ // sample taken using subsetCoord and a sample at extraCoord.
+ fb->codeAppend("float hw = w/2;");
+ fb->codeAppend("float n = mod(d - hw, w2);");
+ fb->codeAppendf(
+ "%s = saturate(half(mix(n, w2 - n, step(w, n)) - hw + "
+ "0.5));",
+ coordWeight);
+ fb->codeAppend("}");
+ } else {
+ fb->codeAppendf(
+ "subsetCoord.%s = mod(inCoord.%s - %s.%s, %s.%s - %s.%s) + "
+ "%s.%s;",
+ coordSwizzle, coordSwizzle, subsetName, subsetStartSwizzle,
+ subsetName, subsetStopSwizzle, subsetName,
+ subsetStartSwizzle, subsetName, subsetStartSwizzle);
+ }
break;
case ShaderMode::kMirrorRepeat: {
fb->codeAppend("{");
@@ -357,10 +386,10 @@
}
};
- auto clampCoord = [fb, clampName](bool clamp,
- const char* coordSwizzle,
- const char* clampStartSwizzle,
- const char* clampStopSwizzle) {
+ auto clampCoord = [&](bool clamp,
+ const char* coordSwizzle,
+ const char* clampStartSwizzle,
+ const char* clampStopSwizzle) {
if (clamp) {
fb->codeAppendf("clampedCoord.%s = clamp(subsetCoord.%s, %s.%s, %s.%s);",
coordSwizzle, coordSwizzle, clampName, clampStartSwizzle,
@@ -371,14 +400,68 @@
}
};
+ // Insert vars for extra coords and blending weights for kRepeatMipMap.
+ const char* extraRepeatCoordX = nullptr;
+ const char* repeatCoordWeightX = nullptr;
+ const char* extraRepeatCoordY = nullptr;
+ const char* repeatCoordWeightY = nullptr;
+ if (filterLogic[0] == FilterLogic::kRepeatMipMap) {
+ fb->codeAppend("float extraRepeatCoordX; half repeatCoordWeightX;");
+ extraRepeatCoordX = "extraRepeatCoordX";
+ repeatCoordWeightX = "repeatCoordWeightX";
+ }
+ if (filterLogic[1] == FilterLogic::kRepeatMipMap) {
+ fb->codeAppend("float extraRepeatCoordY; half repeatCoordWeightY;");
+ extraRepeatCoordY = "extraRepeatCoordY";
+ repeatCoordWeightY = "repeatCoordWeightY";
+ }
+
+ // Apply subset rect and clamp rect to coords.
fb->codeAppend("float2 subsetCoord;");
- subsetCoord(te.fShaderModes[0], "x", "x", "z");
- subsetCoord(te.fShaderModes[1], "y", "y", "w");
+ subsetCoord(te.fShaderModes[0], "x", "x", "z", extraRepeatCoordX,
+ repeatCoordWeightX);
+ subsetCoord(te.fShaderModes[1], "y", "y", "w", extraRepeatCoordY,
+ repeatCoordWeightY);
fb->codeAppend("float2 clampedCoord;");
clampCoord(useClamp[0], "x", "x", "z");
clampCoord(useClamp[1], "y", "y", "w");
- fb->codeAppendf("half4 textureColor = %s;", read("clampedCoord").c_str());
+ // Additional clamping for the extra coords for kRepeatMipMap.
+ if (filterLogic[0] == FilterLogic::kRepeatMipMap) {
+ fb->codeAppendf("extraRepeatCoordX = clamp(extraRepeatCoordX, %s.x, %s.z);",
+ clampName, clampName);
+ }
+ if (filterLogic[1] == FilterLogic::kRepeatMipMap) {
+ fb->codeAppendf("extraRepeatCoordY = clamp(extraRepeatCoordY, %s.y, %s.w);",
+ clampName, clampName);
+ }
+
+ // Do the 2 or 4 texture reads for kRepeatMipMap and then apply the weight(s)
+ // to blend between them. If neither direction is kRepeatMipMap do a single
+ // read at clampedCoord.
+ if (filterLogic[0] == FilterLogic::kRepeatMipMap &&
+ filterLogic[1] == FilterLogic::kRepeatMipMap) {
+ fb->codeAppendf(
+ "half4 textureColor ="
+ " mix(mix(%s, %s, repeatCoordWeightX),"
+ " mix(%s, %s, repeatCoordWeightX),"
+ " repeatCoordWeightY);",
+ read("clampedCoord").c_str(),
+ read("float2(extraRepeatCoordX, clampedCoord.y)").c_str(),
+ read("float2(clampedCoord.x, extraRepeatCoordY)").c_str(),
+ read("float2(extraRepeatCoordX, extraRepeatCoordY)").c_str());
+
+ } else if (filterLogic[0] == FilterLogic::kRepeatMipMap) {
+ fb->codeAppendf("half4 textureColor = mix(%s, %s, repeatCoordWeightX);",
+ read("clampedCoord").c_str(),
+ read("float2(extraRepeatCoordX, clampedCoord.y)").c_str());
+ } else if (filterLogic[1] == FilterLogic::kRepeatMipMap) {
+ fb->codeAppendf("half4 textureColor = mix(%s, %s, repeatCoordWeightY);",
+ read("clampedCoord").c_str(),
+ read("float2(clampedCoord.x, extraRepeatCoordY)").c_str());
+ } else {
+ fb->codeAppendf("half4 textureColor = %s;", read("clampedCoord").c_str());
+ }
// Strings for extra texture reads used only in kRepeatBilerp
SkString repeatBilerpReadX;
diff --git a/src/gpu/effects/GrTextureEffect.h b/src/gpu/effects/GrTextureEffect.h
index 3f8dd18..90a4321 100644
--- a/src/gpu/effects/GrTextureEffect.h
+++ b/src/gpu/effects/GrTextureEffect.h
@@ -117,7 +117,7 @@
enum class FilterLogic {
kNone, // The shader isn't specialized for the filter.
kRepeatBilerp, // Filter across the subset boundary for kRepeat mode
- // kRepeatMipMap, // Logic for LOD selection with kRepeat mode. Not yet implemented.
+ kRepeatMipMap, // Logic for LOD selection with kRepeat mode.
kDecalFilter, // Logic for fading to transparent black when filtering with kDecal.
kDecalNearest, // Logic for hard transition to transparent black when not filtering.
};