Add a workaround for inaccurate interpolants on Adreno 3xx.

Also ensure that sk_FragCoord x and y values are at pixel centers when
workaround is used.

Change-Id: Ib748af9e496a406a50622e00e96e1346cbb5eb26
Reviewed-on: https://skia-review.googlesource.com/97064
Reviewed-by: Brian Osman <brianosman@google.com>
Commit-Queue: Brian Salomon <bsalomon@google.com>
diff --git a/include/gpu/GrShaderCaps.h b/include/gpu/GrShaderCaps.h
index 66e7d42..887dc2e 100644
--- a/include/gpu/GrShaderCaps.h
+++ b/include/gpu/GrShaderCaps.h
@@ -124,6 +124,9 @@
     // If false, SkSL uses a workaround so that sk_FragCoord doesn't actually query gl_FragCoord
     bool canUseFragCoord() const { return fCanUseFragCoord; }
 
+    // If true interpolated vertex shader outputs are inaccurate.
+    bool interpolantsAreInaccurate() const { return fInterpolantsAreInaccurate; }
+
     bool requiresLocalOutputColorForFBFetch() const { return fRequiresLocalOutputColorForFBFetch; }
 
     bool mustObfuscateUniformColor() const { return fMustObfuscateUniformColor; }
@@ -279,6 +282,7 @@
     bool fMustObfuscateUniformColor : 1;
     bool fMustGuardDivisionEvenAfterExplicitZeroCheck : 1;
     bool fCanUseFragCoord : 1;
+    bool fInterpolantsAreInaccurate : 1;
 
     const char* fVersionDeclString;
 
diff --git a/src/gpu/GrShaderCaps.cpp b/src/gpu/GrShaderCaps.cpp
index 2ab1947..6c6e000 100644
--- a/src/gpu/GrShaderCaps.cpp
+++ b/src/gpu/GrShaderCaps.cpp
@@ -39,6 +39,7 @@
     fMustObfuscateUniformColor = false;
     fMustGuardDivisionEvenAfterExplicitZeroCheck = false;
     fCanUseFragCoord = true;
+    fInterpolantsAreInaccurate = false;
     fFlatInterpolationSupport = false;
     fPreferFlatInterpolation = false;
     fNoPerspectiveInterpolationSupport = false;
@@ -115,6 +116,7 @@
     writer->appendBool("Must guard division even after explicit zero check",
                        fMustGuardDivisionEvenAfterExplicitZeroCheck);
     writer->appendBool("Can use gl_FragCoord", fCanUseFragCoord);
+    writer->appendBool("Interpolants are inaccurate", fInterpolantsAreInaccurate);
     writer->appendBool("Flat interpolation support", fFlatInterpolationSupport);
     writer->appendBool("Prefer flat interpolation", fPreferFlatInterpolation);
     writer->appendBool("No perspective interpolation support", fNoPerspectiveInterpolationSupport);
diff --git a/src/gpu/gl/GrGLCaps.cpp b/src/gpu/gl/GrGLCaps.cpp
index 14be215..cec02a1 100644
--- a/src/gpu/gl/GrGLCaps.cpp
+++ b/src/gpu/gl/GrGLCaps.cpp
@@ -1045,9 +1045,11 @@
 #endif
 
     // We've seen Adreno 3xx devices produce incorrect (flipped) values for gl_FragCoord, in some
-    // (rare) situations. It's sporadic, and mostly on older drviers.
+    // (rare) situations. It's sporadic, and mostly on older drivers. It also seems to be the case
+    // that the interpolation of vertex shader outputs is quite inaccurate.
     if (kAdreno3xx_GrGLRenderer == ctxInfo.renderer()) {
         shaderCaps->fCanUseFragCoord = false;
+        shaderCaps->fInterpolantsAreInaccurate = true;
     }
 }
 
diff --git a/src/gpu/ops/GrTextureOp.cpp b/src/gpu/ops/GrTextureOp.cpp
index 7c4842f..46d2630 100644
--- a/src/gpu/ops/GrTextureOp.cpp
+++ b/src/gpu/ops/GrTextureOp.cpp
@@ -168,22 +168,51 @@
                 }
                 args.fFragBuilder->codeAppend(";");
                 if (textureGP.usesCoverageEdgeAA()) {
-                    GrGLSLVarying aaDistVarying(kFloat4_GrSLType,
-                                                GrGLSLVarying::Scope::kVertToFrag);
-                    args.fVaryingHandler->addVarying("aaDists", &aaDistVarying);
-                    args.fVertBuilder->codeAppendf(
-                            R"(%s = float4(dot(aaEdge0.xy, %s.xy) + aaEdge0.z,
-                                           dot(aaEdge1.xy, %s.xy) + aaEdge1.z,
-                                           dot(aaEdge2.xy, %s.xy) + aaEdge2.z,
-                                           dot(aaEdge3.xy, %s.xy) + aaEdge3.z);)",
-                            aaDistVarying.vsOut(), textureGP.fPositions.fName,
-                            textureGP.fPositions.fName, textureGP.fPositions.fName,
-                            textureGP.fPositions.fName);
-
+                    const char* aaDistName = nullptr;
+                    // When interpolation is innacurate we perform the evaluation of the edge
+                    // equations in the fragment shader rather than interpolating values computed
+                    // in the vertex shader.
+                    if (!args.fShaderCaps->interpolantsAreInaccurate()) {
+                        GrGLSLVarying aaDistVarying(kFloat4_GrSLType,
+                                                    GrGLSLVarying::Scope::kVertToFrag);
+                        args.fVaryingHandler->addVarying("aaDists", &aaDistVarying);
+                        args.fVertBuilder->codeAppendf(
+                                R"(%s = float4(dot(aaEdge0.xy, %s.xy) + aaEdge0.z,
+                                               dot(aaEdge1.xy, %s.xy) + aaEdge1.z,
+                                               dot(aaEdge2.xy, %s.xy) + aaEdge2.z,
+                                               dot(aaEdge3.xy, %s.xy) + aaEdge3.z);)",
+                                aaDistVarying.vsOut(), textureGP.fPositions.fName,
+                                textureGP.fPositions.fName, textureGP.fPositions.fName,
+                                textureGP.fPositions.fName);
+                        aaDistName = aaDistVarying.fsIn();
+                    } else {
+                        GrGLSLVarying aaEdgeVarying[4]{
+                                {kFloat3_GrSLType, GrGLSLVarying::Scope::kVertToFrag},
+                                {kFloat3_GrSLType, GrGLSLVarying::Scope::kVertToFrag},
+                                {kFloat3_GrSLType, GrGLSLVarying::Scope::kVertToFrag},
+                                {kFloat3_GrSLType, GrGLSLVarying::Scope::kVertToFrag}
+                        };
+                        for (int i = 0; i < 4; ++i) {
+                            SkString name;
+                            name.printf("aaEdge%d", i);
+                            args.fVaryingHandler->addVarying(name.c_str(), &aaEdgeVarying[i]);
+                            args.fVertBuilder->codeAppendf(
+                                    "%s = aaEdge%d;", aaEdgeVarying[i].vsOut(), i);
+                        }
+                        args.fFragBuilder->codeAppendf(
+                                R"(float4 aaDists = float4(dot(%s.xy, sk_FragCoord.xy) + %s.z,
+                                                           dot(%s.xy, sk_FragCoord.xy) + %s.z,
+                                                           dot(%s.xy, sk_FragCoord.xy) + %s.z,
+                                                           dot(%s.xy, sk_FragCoord.xy) + %s.z);)",
+                        aaEdgeVarying[0].fsIn(), aaEdgeVarying[0].fsIn(),
+                        aaEdgeVarying[1].fsIn(), aaEdgeVarying[1].fsIn(),
+                        aaEdgeVarying[2].fsIn(), aaEdgeVarying[2].fsIn(),
+                        aaEdgeVarying[3].fsIn(), aaEdgeVarying[3].fsIn());
+                        aaDistName = "aaDists";
+                    }
                     args.fFragBuilder->codeAppendf(
                             "float mindist = min(min(%s.x, %s.y), min(%s.z, %s.w));",
-                            aaDistVarying.fsIn(), aaDistVarying.fsIn(), aaDistVarying.fsIn(),
-                            aaDistVarying.fsIn());
+                            aaDistName, aaDistName, aaDistName, aaDistName);
                     args.fFragBuilder->codeAppendf("%s = float4(clamp(mindist, 0, 1));",
                                                    args.fOutputCoverage);
                 } else {
diff --git a/src/sksl/SkSLGLSLCodeGenerator.cpp b/src/sksl/SkSLGLSLCodeGenerator.cpp
index f3400c7..d02948f 100644
--- a/src/sksl/SkSLGLSLCodeGenerator.cpp
+++ b/src/sksl/SkSLGLSLCodeGenerator.cpp
@@ -598,8 +598,19 @@
 
 void GLSLCodeGenerator::writeFragCoord() {
     if (!fProgram.fSettings.fCaps->canUseFragCoord()) {
-        this->write("vec4(sk_FragCoord_Workaround.xyz / sk_FragCoord_Workaround.w, "
-                    "1.0 / sk_FragCoord_Workaround.w)");
+        if (!fSetupFragCoordWorkaround) {
+            const char* precision = usesPrecisionModifiers() ? "highp " : "";
+            fFunctionHeader += precision;
+            fFunctionHeader += "    float sk_FragCoord_InvW = 1. / sk_FragCoord_Workaround.w;\n";
+            fFunctionHeader += precision;
+            fFunctionHeader += "    vec4 sk_FragCoord_Resolved = "
+                "vec4(sk_FragCoord_Workaround.xyz * sk_FragCoord_InvW, sk_FragCoord_InvW);\n";
+            // Ensure that we get exact .5 values for x and y.
+            fFunctionHeader += "    sk_FragCoord_Resolved.xy = floor(sk_FragCoord_Resolved.xy) + "
+                               "vec2(.5);\n";
+            fSetupFragCoordWorkaround = true;
+        }
+        this->write("sk_FragCoord_Resolved");
         return;
     }
 
diff --git a/src/sksl/SkSLGLSLCodeGenerator.h b/src/sksl/SkSLGLSLCodeGenerator.h
index 70ce920..2b308e3 100644
--- a/src/sksl/SkSLGLSLCodeGenerator.h
+++ b/src/sksl/SkSLGLSLCodeGenerator.h
@@ -212,6 +212,7 @@
     bool fFoundGSInvocations = false;
     bool fSetupFragPositionGlobal = false;
     bool fSetupFragPositionLocal = false;
+    bool fSetupFragCoordWorkaround = false;
 
     typedef CodeGenerator INHERITED;
 };
diff --git a/tests/SkSLGLSLTest.cpp b/tests/SkSLGLSLTest.cpp
index 10b94b8..62c5cd6 100644
--- a/tests/SkSLGLSLTest.cpp
+++ b/tests/SkSLGLSLTest.cpp
@@ -1093,8 +1093,11 @@
          "in vec4 sk_FragCoord_Workaround;\n"
          "out vec4 sk_FragColor;\n"
          "void main() {\n"
-         "    sk_FragColor.xy = vec4(sk_FragCoord_Workaround.xyz / sk_FragCoord_Workaround.w, "
-             "1.0 / sk_FragCoord_Workaround.w).xy;\n"
+         "    float sk_FragCoord_InvW = 1. / sk_FragCoord_Workaround.w;\n"
+         "    vec4 sk_FragCoord_Resolved = vec4(sk_FragCoord_Workaround.xyz * "
+              "sk_FragCoord_InvW, sk_FragCoord_InvW);\n"
+         "    sk_FragCoord_Resolved.xy = floor(sk_FragCoord_Resolved.xy) + vec2(.5);\n"
+         "    sk_FragColor.xy = sk_FragCoord_Resolved.xy;\n"
          "}\n");
 }