Support mirror-repeat in GrTextureDomain

Bug: skia:9570
Change-Id: Iccfbb36b130ec2827164efda33470e2fa75ad833
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/258638
Commit-Queue: Brian Salomon <bsalomon@google.com>
Reviewed-by: Michael Ludwig <michaelludwig@google.com>
diff --git a/src/gpu/effects/GrGaussianConvolutionFragmentProcessor.cpp b/src/gpu/effects/GrGaussianConvolutionFragmentProcessor.cpp
index 231c1b6..cc6b8d2 100644
--- a/src/gpu/effects/GrGaussianConvolutionFragmentProcessor.cpp
+++ b/src/gpu/effects/GrGaussianConvolutionFragmentProcessor.cpp
@@ -89,6 +89,9 @@
                                              component, component, bounds, bounds);
                     break;
                 }
+                // Deferring implementing kMirrorRepeat until we use DomainEffects as
+                // child processors. Fallback to Repeat.
+                case GrTextureDomain::kMirrorRepeat_Mode:
                 case GrTextureDomain::kRepeat_Mode: {
                     fragBuilder->codeAppendf("coordSampled.%s = "
                                              "mod(coord.%s - %s.x, %s.y - %s.x) + %s.x;\n",
diff --git a/src/gpu/effects/GrTextureDomain.cpp b/src/gpu/effects/GrTextureDomain.cpp
index 8a0014a..f09857a 100644
--- a/src/gpu/effects/GrTextureDomain.cpp
+++ b/src/gpu/effects/GrTextureDomain.cpp
@@ -55,31 +55,33 @@
 
 //////////////////////////////////////////////////////////////////////////////
 
-static SkString clamp_expression(GrTextureDomain::Mode mode, const char* inCoord,
-                                 const char* coordSwizzle, const char* domain,
-                                 const char* minSwizzle, const char* maxSwizzle) {
-    SkString clampedExpr;
+static void append_wrap(GrGLSLShaderBuilder* builder, GrTextureDomain::Mode mode,
+                        const char* inCoord, const char* domainStart, const char* domainEnd,
+                        const char* out) {
     switch(mode) {
         case GrTextureDomain::kIgnore_Mode:
-            clampedExpr.printf("%s.%s\n", inCoord, coordSwizzle);
+            builder->codeAppendf("%s = %s;\n", out, inCoord);
             break;
         case GrTextureDomain::kDecal_Mode:
             // The lookup coordinate to use for decal will be clamped just like kClamp_Mode,
             // it's just that the post-processing will be different, so fall through
         case GrTextureDomain::kClamp_Mode:
-            clampedExpr.printf("clamp(%s.%s, %s.%s, %s.%s)",
-                               inCoord, coordSwizzle, domain, minSwizzle, domain, maxSwizzle);
+            builder->codeAppendf("%s = clamp(%s, %s, %s);", out, inCoord, domainStart, domainEnd);
             break;
         case GrTextureDomain::kRepeat_Mode:
-            clampedExpr.printf("mod(%s.%s - %s.%s, %s.%s - %s.%s) + %s.%s",
-                               inCoord, coordSwizzle, domain, minSwizzle, domain, maxSwizzle,
-                               domain, minSwizzle, domain, minSwizzle);
+            builder->codeAppendf("%s = mod(%s - %s, %s - %s) + %s;", out, inCoord, domainStart,
+                                 domainEnd, domainStart, domainStart);
             break;
-        default:
-            SkASSERTF(false, "Unknown texture domain mode: %u\n", (uint32_t) mode);
+        case GrTextureDomain::kMirrorRepeat_Mode: {
+            builder->codeAppend("{");
+            builder->codeAppendf("float w = %s - %s;", domainEnd, domainStart);
+            builder->codeAppendf("float w2 = 2 * w;");
+            builder->codeAppendf("float m = mod(%s - %s, w2);", inCoord, domainStart);
+            builder->codeAppendf("%s = mix(m, w2 - m, step(w, m)) + %s;", out, domainStart);
+            builder->codeAppend("}");
             break;
+        }
     }
-    return clampedExpr;
 }
 
 void GrTextureDomain::GLDomain::sampleProcessor(const GrTextureDomain& textureDomain,
@@ -158,23 +160,20 @@
     // Always use a local variable for the input coordinates; often callers pass in an expression
     // and we want to cache it across all of its references in the code below
     builder->codeAppendf("float2 origCoord = %s;", inCoords.c_str());
-    builder->codeAppend("float2 clampedCoord = ");
-    if (textureDomain.modeX() != textureDomain.modeY()) {
-        // The wrap modes differ on the two axes, so build up a coordinate that respects each axis'
-        // domain rule independently before sampling the texture.
-        SkString tcX = clamp_expression(textureDomain.modeX(), "origCoord", "x",
-                                        fDomainName.c_str(), "x", "z");
-        SkString tcY = clamp_expression(textureDomain.modeY(), "origCoord", "y",
-                                        fDomainName.c_str(), "y", "w");
-        builder->codeAppendf("float2(%s, %s)", tcX.c_str(), tcY.c_str());
-    } else {
-        // Since the x and y axis wrap modes are the same, they can be calculated together using
-        // more efficient vector operations
-        SkString tc = clamp_expression(textureDomain.modeX(), "origCoord", "xy",
-                                       fDomainName.c_str(), "xy", "zw");
-        builder->codeAppend(tc.c_str());
-    }
-    builder->codeAppend(";");
+    builder->codeAppend("float2 clampedCoord;");
+    SkString start;
+    SkString end;
+    // Apply x mode to the x coordinate using the left and right edges of the domain rect
+    // (stored as the x and z components of the domain uniform).
+    start.printf("%s.x", fDomainName.c_str());
+    end.printf("%s.z", fDomainName.c_str());
+    append_wrap(builder, textureDomain.modeX(), "origCoord.x", start.c_str(), end.c_str(),
+                "clampedCoord.x");
+    // Repeat the same logic for y.
+    start.printf("%s.y", fDomainName.c_str());
+    end.printf("%s.w", fDomainName.c_str());
+    append_wrap(builder, textureDomain.modeY(), "origCoord.y", start.c_str(), end.c_str(),
+                "clampedCoord.y");
 
     // Sample 'appendSample' at the clamped coordinate location.
     SkString color = appendSample("clampedCoord");
diff --git a/src/gpu/effects/GrTextureDomain.h b/src/gpu/effects/GrTextureDomain.h
index 073a15b..53f3fcb 100644
--- a/src/gpu/effects/GrTextureDomain.h
+++ b/src/gpu/effects/GrTextureDomain.h
@@ -38,8 +38,10 @@
         // read texels outside of the domain.  We could perform additional texture reads and filter
         // in the shader, but are not currently doing this for performance reasons
         kRepeat_Mode,
+        // Mirror wrap texture coordinates. NOTE: suffers the same filtering limitation as kRepeat.
+        kMirrorRepeat_Mode,
 
-        kLastMode = kRepeat_Mode
+        kLastMode = kMirrorRepeat_Mode
     };
     static const int kModeCount = kLastMode + 1;
 
@@ -183,7 +185,7 @@
                      bool filterIfDecal);
 
         enum {
-            kModeBits = 2, // See DomainKey().
+            kModeBits = 3,  // See DomainKey().
             kDomainKeyBits = 4
         };