Add workaround flag for color space transform math on Mali G GPUs

This will let us use GrGLColorSpaceXform in place of GrSRGBEffect.

Change-Id: I478cde3cf5009e4af97056fbc733ac0ee0ba7785
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/279139
Reviewed-by: Brian Salomon <bsalomon@google.com>
Commit-Queue: Brian Osman <brianosman@google.com>
diff --git a/src/gpu/GrShaderCaps.cpp b/src/gpu/GrShaderCaps.cpp
index 049d823..7837601 100644
--- a/src/gpu/GrShaderCaps.cpp
+++ b/src/gpu/GrShaderCaps.cpp
@@ -56,6 +56,7 @@
     fFloatIs32Bits = true;
     fHalfIs32Bits = false;
     fHasLowFragmentPrecision = false;
+    fColorSpaceMathNeedsFloat = false;
     // Backed API support is required to be able to make swizzle-neutral shaders (e.g.
     // GL_ARB_texture_swizzle).
     fTextureSwizzleAppliedInShader = true;
@@ -138,6 +139,7 @@
     writer->appendBool("float == fp32", fFloatIs32Bits);
     writer->appendBool("half == fp32", fHalfIs32Bits);
     writer->appendBool("Has poor fragment precision", fHasLowFragmentPrecision);
+    writer->appendBool("Color space math needs float", fColorSpaceMathNeedsFloat);
     writer->appendBool("Texture swizzle applied in shader", fTextureSwizzleAppliedInShader);
     writer->appendBool("Builtin fma() support", fBuiltinFMASupport);
 
diff --git a/src/gpu/GrShaderCaps.h b/src/gpu/GrShaderCaps.h
index 2563d66..b6a3cc0 100644
--- a/src/gpu/GrShaderCaps.h
+++ b/src/gpu/GrShaderCaps.h
@@ -128,6 +128,8 @@
     // required by the spec. SKSL will always emit full ints.
     bool incompleteShortIntPrecision() const { return fIncompleteShortIntPrecision; }
 
+    bool colorSpaceMathNeedsFloat() const { return fColorSpaceMathNeedsFloat; }
+
     // If true, then conditions in for loops need "&& true" to work around driver bugs.
     bool addAndTrueToLoopCondition() const { return fAddAndTrueToLoopCondition; }
 
@@ -298,6 +300,7 @@
     bool fMustWriteToFragColor                        : 1;
     bool fNoDefaultPrecisionForExternalSamplers       : 1;
     bool fCanOnlyUseSampleMaskWithStencil             : 1;
+    bool fColorSpaceMathNeedsFloat                    : 1;
 
     const char* fVersionDeclString;
 
diff --git a/src/gpu/gl/GrGLCaps.cpp b/src/gpu/gl/GrGLCaps.cpp
index 18c9657..06f37e2 100644
--- a/src/gpu/gl/GrGLCaps.cpp
+++ b/src/gpu/gl/GrGLCaps.cpp
@@ -3600,6 +3600,14 @@
     if (kMaliT_GrGLRenderer == ctxInfo.renderer()) {
         shaderCaps->fMustObfuscateUniformColor = true;
     }
+
+    // On Mali G series GPUs, applying transfer functions in the fragment shader with half-floats
+    // produces answers that are much less accurate than expected/required. This forces full floats
+    // for some intermediate values to get acceptable results.
+    if (kMaliG_GrGLRenderer == ctxInfo.renderer()) {
+        fShaderCaps->fColorSpaceMathNeedsFloat = true;
+    }
+
 #ifdef SK_BUILD_FOR_WIN
     // Check for ANGLE on Windows, so we can workaround a bug in D3D itself (anglebug.com/2098).
     //
diff --git a/src/gpu/gl/GrGLUtil.cpp b/src/gpu/gl/GrGLUtil.cpp
index a132c46..e04e4af 100644
--- a/src/gpu/gl/GrGLUtil.cpp
+++ b/src/gpu/gl/GrGLUtil.cpp
@@ -512,6 +512,10 @@
         if (strstr(rendererString, "llvmpipe")) {
             return kGalliumLLVM_GrGLRenderer;
         }
+        static const char kMaliGStr[] = "Mali-G";
+        if (0 == strncmp(rendererString, kMaliGStr, SK_ARRAY_COUNT(kMaliGStr) - 1)) {
+            return kMaliG_GrGLRenderer;
+        }
         static const char kMaliTStr[] = "Mali-T";
         if (0 == strncmp(rendererString, kMaliTStr, SK_ARRAY_COUNT(kMaliTStr) - 1)) {
             return kMaliT_GrGLRenderer;
diff --git a/src/gpu/gl/GrGLUtil.h b/src/gpu/gl/GrGLUtil.h
index d9172e6..921f0d5 100644
--- a/src/gpu/gl/GrGLUtil.h
+++ b/src/gpu/gl/GrGLUtil.h
@@ -91,6 +91,8 @@
 
     kGalliumLLVM_GrGLRenderer,
     kMali4xx_GrGLRenderer,
+    /** G-7x */
+    kMaliG_GrGLRenderer,
     /** T-6xx, T-7xx, or T-8xx */
     kMaliT_GrGLRenderer,
     kANGLE_GrGLRenderer,
diff --git a/src/gpu/glsl/GrGLSLShaderBuilder.cpp b/src/gpu/glsl/GrGLSLShaderBuilder.cpp
index 42ac1eb..95f97fe 100644
--- a/src/gpu/glsl/GrGLSLShaderBuilder.cpp
+++ b/src/gpu/glsl/GrGLSLShaderBuilder.cpp
@@ -188,29 +188,37 @@
 
     // Now define a wrapper function that applies all the intermediate steps
     {
-        const GrShaderVar gColorXformArgs[] = { GrShaderVar("color", kHalf4_GrSLType) };
+        // Some GPUs require full float to get results that are as accurate as expected/required.
+        // Most GPUs work just fine with half float. Strangely, the GPUs that have this bug
+        // (Mali G series) only require us to promote the type of a few temporaries here --
+        // the helper functions above can always be written to use half.
+        bool useFloat = fProgramBuilder->shaderCaps()->colorSpaceMathNeedsFloat();
+
+        const GrShaderVar gColorXformArgs[] = {
+                GrShaderVar("color", useFloat ? kFloat4_GrSLType : kHalf4_GrSLType)};
         SkString body;
         if (colorXformHelper->applyUnpremul()) {
-            body.append("half nonZeroAlpha = max(color.a, 0.0001);");
-            body.append("color = half4(color.rgb / nonZeroAlpha, nonZeroAlpha);");
+            body.appendf("%s nonZeroAlpha = max(color.a, 0.0001);", useFloat ? "float" : "half");
+            body.appendf("color = %s(color.rgb / nonZeroAlpha, nonZeroAlpha);",
+                         useFloat ? "float4" : "half4");
         }
         if (colorXformHelper->applySrcTF()) {
-            body.appendf("color.r = %s(color.r);", srcTFFuncName.c_str());
-            body.appendf("color.g = %s(color.g);", srcTFFuncName.c_str());
-            body.appendf("color.b = %s(color.b);", srcTFFuncName.c_str());
+            body.appendf("color.r = %s(half(color.r));", srcTFFuncName.c_str());
+            body.appendf("color.g = %s(half(color.g));", srcTFFuncName.c_str());
+            body.appendf("color.b = %s(half(color.b));", srcTFFuncName.c_str());
         }
         if (colorXformHelper->applyGamutXform()) {
-            body.appendf("color = %s(color);", gamutXformFuncName.c_str());
+            body.appendf("color = %s(half4(color));", gamutXformFuncName.c_str());
         }
         if (colorXformHelper->applyDstTF()) {
-            body.appendf("color.r = %s(color.r);", dstTFFuncName.c_str());
-            body.appendf("color.g = %s(color.g);", dstTFFuncName.c_str());
-            body.appendf("color.b = %s(color.b);", dstTFFuncName.c_str());
+            body.appendf("color.r = %s(half(color.r));", dstTFFuncName.c_str());
+            body.appendf("color.g = %s(half(color.g));", dstTFFuncName.c_str());
+            body.appendf("color.b = %s(half(color.b));", dstTFFuncName.c_str());
         }
         if (colorXformHelper->applyPremul()) {
             body.append("color.rgb *= color.a;");
         }
-        body.append("return color;");
+        body.append("return half4(color);");
         SkString colorXformFuncName;
         this->emitFunction(kHalf4_GrSLType, "color_xform", SK_ARRAY_COUNT(gColorXformArgs),
                            gColorXformArgs, body.c_str(), &colorXformFuncName);
diff --git a/src/gpu/vk/GrVkCaps.cpp b/src/gpu/vk/GrVkCaps.cpp
index ddb4f2d..cdac50f 100644
--- a/src/gpu/vk/GrVkCaps.cpp
+++ b/src/gpu/vk/GrVkCaps.cpp
@@ -444,6 +444,13 @@
         fPreferPrimaryOverSecondaryCommandBuffers = false;
     }
 
+    // On Mali G series GPUs, applying transfer functions in the fragment shader with half-floats
+    // produces answers that are much less accurate than expected/required. This forces full floats
+    // for some intermediate values to get acceptable results.
+    if (kARM_VkVendor == properties.vendorID) {
+        fShaderCaps->fColorSpaceMathNeedsFloat = true;
+    }
+
     // On various devices, when calling vkCmdClearAttachments on a primary command buffer, it
     // corrupts the bound buffers on the command buffer. As a workaround we invalidate our knowledge
     // of bound buffers so that we will rebind them on the next draw.