Do color correction of vertex colors in GPU drawVertices

SkColor vertex colors need to be linearized (from sRGB),
and possibly converted from sRGB gamut to destination gamut.

Bug: skia:6659
Change-Id: I2b1b1dd0fa5938519693f56a728fed5957f13fd5
Reviewed-on: https://skia-review.googlesource.com/17534
Commit-Queue: Brian Osman <brianosman@google.com>
Reviewed-by: Brian Salomon <bsalomon@google.com>
diff --git a/src/gpu/GrDefaultGeoProcFactory.cpp b/src/gpu/GrDefaultGeoProcFactory.cpp
index 90d3239..3f50bea 100644
--- a/src/gpu/GrDefaultGeoProcFactory.cpp
+++ b/src/gpu/GrDefaultGeoProcFactory.cpp
@@ -8,6 +8,7 @@
 #include "GrDefaultGeoProcFactory.h"
 
 #include "SkRefCnt.h"
+#include "glsl/GrGLSLColorSpaceXformHelper.h"
 #include "glsl/GrGLSLFragmentShaderBuilder.h"
 #include "glsl/GrGLSLGeometryProcessor.h"
 #include "glsl/GrGLSLVertexShaderBuilder.h"
@@ -26,18 +27,22 @@
     kColorAttributeIsSkColor_GPFlag = 0x2,
     kLocalCoordAttribute_GPFlag     = 0x4,
     kCoverageAttribute_GPFlag       = 0x8,
+
+    kLinearizeColorAttribute_GPFlag = 0x10,
 };
 
 class DefaultGeoProc : public GrGeometryProcessor {
 public:
     static sk_sp<GrGeometryProcessor> Make(uint32_t gpTypeFlags,
                                            GrColor color,
+                                           sk_sp<GrColorSpaceXform> colorSpaceXform,
                                            const SkMatrix& viewMatrix,
                                            const SkMatrix& localMatrix,
                                            bool localCoordsWillBeRead,
                                            uint8_t coverage) {
         return sk_sp<GrGeometryProcessor>(new DefaultGeoProc(
-                gpTypeFlags, color, viewMatrix, localMatrix, coverage, localCoordsWillBeRead));
+                gpTypeFlags, color, std::move(colorSpaceXform), viewMatrix, localMatrix, coverage,
+                localCoordsWillBeRead));
     }
 
     const char* name() const override { return "DefaultGeometryProcessor"; }
@@ -53,6 +58,12 @@
     bool localCoordsWillBeRead() const { return fLocalCoordsWillBeRead; }
     uint8_t coverage() const { return fCoverage; }
     bool hasVertexCoverage() const { return SkToBool(fInCoverage); }
+    bool linearizeColor() const {
+        // Linearization should only happen with SkColor
+        bool linearize = SkToBool(fFlags & kLinearizeColorAttribute_GPFlag);
+        SkASSERT(!linearize || (fFlags & kColorAttributeIsSkColor_GPFlag));
+        return linearize;
+    }
 
     class GLSLProcessor : public GrGLSLGeometryProcessor {
     public:
@@ -73,14 +84,47 @@
             if (gp.hasVertexColor()) {
                 GrGLSLVertToFrag varying(kVec4f_GrSLType);
                 varyingHandler->addVarying("color", &varying);
-                if (gp.fFlags & kColorAttributeIsSkColor_GPFlag) {
-                    // Do a red/blue swap and premul the color.
-                    vertBuilder->codeAppendf("%s = vec4(%s.a*%s.bgr, %s.a);", varying.vsOut(),
-                                             gp.inColor()->fName, gp.inColor()->fName,
+
+                // There are several optional steps to process the color. Start with the attribute:
+                vertBuilder->codeAppendf("vec4 color = %s;", gp.inColor()->fName);
+
+                // Linearize
+                if (gp.linearizeColor()) {
+                    SkString srgbFuncName;
+                    static const GrShaderVar gSrgbArgs[] = {
+                        GrShaderVar("x", kFloat_GrSLType),
+                    };
+                    vertBuilder->emitFunction(kFloat_GrSLType,
+                                              "srgb_to_linear",
+                                              SK_ARRAY_COUNT(gSrgbArgs),
+                                              gSrgbArgs,
+                                              "return (x <= 0.04045) ? (x / 12.92) "
+                                              ": pow((x + 0.055) / 1.055, 2.4);",
+                                              &srgbFuncName);
+                    vertBuilder->codeAppendf("color = vec4(%s(%s.r), %s(%s.g), %s(%s.b), %s.a);",
+                                             srgbFuncName.c_str(), gp.inColor()->fName,
+                                             srgbFuncName.c_str(), gp.inColor()->fName,
+                                             srgbFuncName.c_str(), gp.inColor()->fName,
                                              gp.inColor()->fName);
-                } else {
-                    vertBuilder->codeAppendf("%s = %s;\n", varying.vsOut(), gp.inColor()->fName);
                 }
+
+                // For SkColor, do a red/blue swap and premul
+                if (gp.fFlags & kColorAttributeIsSkColor_GPFlag) {
+                    vertBuilder->codeAppend("color = vec4(color.a * color.bgr, color.a);");
+                }
+
+                // Do color-correction to destination gamut
+                if (gp.linearizeColor()) {
+                    fColorSpaceHelper.emitCode(uniformHandler, gp.fColorSpaceXform.get(),
+                                               kVertex_GrShaderFlag);
+                    if (fColorSpaceHelper.isValid()) {
+                        SkString xformedColor;
+                        vertBuilder->appendColorGamutXform(&xformedColor, "color",
+                                                           &fColorSpaceHelper);
+                        vertBuilder->codeAppendf("color = %s;", xformedColor.c_str());
+                    }
+                }
+                vertBuilder->codeAppendf("%s = color;\n", varying.vsOut());
                 fragBuilder->codeAppendf("%s = %s;", args.fOutputColor, varying.fsIn());
             } else {
                 this->setupUniformColor(fragBuilder, uniformHandler, args.fOutputColor,
@@ -142,6 +186,9 @@
             key |= (def.localCoordsWillBeRead() && def.localMatrix().hasPerspective()) ? 0x20 : 0x0;
             key |= ComputePosKey(def.viewMatrix()) << 20;
             b->add32(key);
+            if (def.linearizeColor()) {
+                b->add32(GrColorSpaceXform::XformKey(def.fColorSpaceXform.get()));
+            }
         }
 
         void setData(const GrGLSLProgramDataManager& pdman,
@@ -168,6 +215,10 @@
                 fCoverage = dgp.coverage();
             }
             this->setTransformDataHelper(dgp.fLocalMatrix, pdman, &transformIter);
+
+            if (dgp.linearizeColor() && dgp.fColorSpaceXform) {
+                fColorSpaceHelper.setData(pdman, dgp.fColorSpaceXform.get());
+            }
         }
 
     private:
@@ -177,6 +228,7 @@
         UniformHandle fViewMatrixUniform;
         UniformHandle fColorUniform;
         UniformHandle fCoverageUniform;
+        GrGLSLColorSpaceXformHelper fColorSpaceHelper;
 
         typedef GrGLSLGeometryProcessor INHERITED;
     };
@@ -192,6 +244,7 @@
 private:
     DefaultGeoProc(uint32_t gpTypeFlags,
                    GrColor color,
+                   sk_sp<GrColorSpaceXform> colorSpaceXform,
                    const SkMatrix& viewMatrix,
                    const SkMatrix& localMatrix,
                    uint8_t coverage,
@@ -201,7 +254,8 @@
             , fLocalMatrix(localMatrix)
             , fCoverage(coverage)
             , fFlags(gpTypeFlags)
-            , fLocalCoordsWillBeRead(localCoordsWillBeRead) {
+            , fLocalCoordsWillBeRead(localCoordsWillBeRead)
+            , fColorSpaceXform(std::move(colorSpaceXform)) {
         this->initClassID<DefaultGeoProc>();
         fInPosition = &this->addVertexAttrib("inPosition", kVec2f_GrVertexAttribType,
                                              kHigh_GrSLPrecision);
@@ -228,6 +282,7 @@
     uint8_t fCoverage;
     uint32_t fFlags;
     bool fLocalCoordsWillBeRead;
+    sk_sp<GrColorSpaceXform> fColorSpaceXform;
 
     GR_DECLARE_GEOMETRY_PROCESSOR_TEST;
 
@@ -254,9 +309,9 @@
 
     return DefaultGeoProc::Make(flags,
                                 GrRandomColor(d->fRandom),
+                                GrTest::TestColorXform(d->fRandom),
                                 GrTest::TestMatrix(d->fRandom),
                                 GrTest::TestMatrix(d->fRandom),
-
                                 d->fRandom->nextBool(),
                                 GrRandomCoverage(d->fRandom));
 }
@@ -272,6 +327,12 @@
     } else if (Color::kUnpremulSkColorAttribute_Type == color.fType) {
         flags |= kColorAttribute_GPFlag | kColorAttributeIsSkColor_GPFlag;
     }
+    if (color.fLinearize) {
+        // It only makes sense to linearize SkColors (which are always sRGB). GrColor values should
+        // have been linearized and gamut-converted during paint conversion
+        SkASSERT(Color::kUnpremulSkColorAttribute_Type == color.fType);
+        flags |= kLinearizeColorAttribute_GPFlag;
+    }
     flags |= coverage.fType == Coverage::kAttribute_Type ? kCoverageAttribute_GPFlag : 0;
     flags |= localCoords.fType == LocalCoords::kHasExplicit_Type ? kLocalCoordAttribute_GPFlag : 0;
 
@@ -281,6 +342,7 @@
     GrColor inColor = color.fColor;
     return DefaultGeoProc::Make(flags,
                                 inColor,
+                                color.fColorSpaceXform,
                                 viewMatrix,
                                 localCoords.fMatrix ? *localCoords.fMatrix : SkMatrix::I(),
                                 localCoordsWillBeRead,
diff --git a/src/gpu/GrDefaultGeoProcFactory.h b/src/gpu/GrDefaultGeoProcFactory.h
index 00ee90d..6a3c0b7 100644
--- a/src/gpu/GrDefaultGeoProcFactory.h
+++ b/src/gpu/GrDefaultGeoProcFactory.h
@@ -66,13 +66,26 @@
             kPremulGrColorAttribute_Type,
             kUnpremulSkColorAttribute_Type,
         };
-        explicit Color(GrColor color) : fType(kPremulGrColorUniform_Type), fColor(color) {}
-        Color(Type type) : fType(type), fColor(GrColor_ILLEGAL) {
+        explicit Color(GrColor color)
+                : fType(kPremulGrColorUniform_Type)
+                , fColor(color)
+                , fLinearize(false)
+                , fColorSpaceXform(nullptr) {}
+        Color(Type type)
+                : fType(type)
+                , fColor(GrColor_ILLEGAL)
+                , fLinearize(false)
+                , fColorSpaceXform(nullptr) {
             SkASSERT(type != kPremulGrColorUniform_Type);
         }
 
         Type fType;
         GrColor fColor;
+
+        // These options only apply to SkColor. Any GrColors are assumed to have been color managed
+        // during paint conversion.
+        bool fLinearize;
+        sk_sp<GrColorSpaceXform> fColorSpaceXform;
     };
 
     struct Coverage {
diff --git a/src/gpu/GrRenderTargetContext.cpp b/src/gpu/GrRenderTargetContext.cpp
index 7d18448..1574fc5 100644
--- a/src/gpu/GrRenderTargetContext.cpp
+++ b/src/gpu/GrRenderTargetContext.cpp
@@ -848,6 +848,8 @@
     SkASSERT(vertices);
     std::unique_ptr<GrLegacyMeshDrawOp> op = GrDrawVerticesOp::Make(paint.getColor(),
                                                                     std::move(vertices), viewMatrix,
+                                                                    this->isGammaCorrect(),
+                                                                    fColorXformFromSRGB,
                                                                     overridePrimType);
     if (!op) {
         return;
diff --git a/src/gpu/glsl/GrGLSLColorSpaceXformHelper.h b/src/gpu/glsl/GrGLSLColorSpaceXformHelper.h
index 1571b06..9653efb 100644
--- a/src/gpu/glsl/GrGLSLColorSpaceXformHelper.h
+++ b/src/gpu/glsl/GrGLSLColorSpaceXformHelper.h
@@ -20,10 +20,11 @@
 public:
     GrGLSLColorSpaceXformHelper() : fValid(false) {}
 
-    void emitCode(GrGLSLUniformHandler* uniformHandler, GrColorSpaceXform* colorSpaceXform) {
+    void emitCode(GrGLSLUniformHandler* uniformHandler, GrColorSpaceXform* colorSpaceXform,
+                  uint32_t visibility = kFragment_GrShaderFlag) {
         SkASSERT(uniformHandler);
         if (colorSpaceXform) {
-            fGamutXformVar = uniformHandler->addUniform(kFragment_GrShaderFlag, kMat44f_GrSLType,
+            fGamutXformVar = uniformHandler->addUniform(visibility, kMat44f_GrSLType,
                                                         kDefault_GrSLPrecision, "ColorXform");
             fValid = true;
         }
diff --git a/src/gpu/ops/GrDrawVerticesOp.cpp b/src/gpu/ops/GrDrawVerticesOp.cpp
index 1e04c68..e5b1383 100644
--- a/src/gpu/ops/GrDrawVerticesOp.cpp
+++ b/src/gpu/ops/GrDrawVerticesOp.cpp
@@ -13,24 +13,34 @@
 std::unique_ptr<GrLegacyMeshDrawOp> GrDrawVerticesOp::Make(GrColor color,
                                                            sk_sp<SkVertices> vertices,
                                                            const SkMatrix& viewMatrix,
+                                                           bool gammaCorrect,
+                                                           sk_sp<GrColorSpaceXform> colorSpaceXform,
                                                            GrPrimitiveType* overridePrimType) {
     SkASSERT(vertices);
     GrPrimitiveType primType = overridePrimType ? *overridePrimType
                                                 : SkVertexModeToGrPrimitiveType(vertices->mode());
-    return std::unique_ptr<GrLegacyMeshDrawOp>(
-            new GrDrawVerticesOp(std::move(vertices), primType, color, viewMatrix));
+    return std::unique_ptr<GrLegacyMeshDrawOp>(new GrDrawVerticesOp(std::move(vertices), primType,
+                                                                    color, gammaCorrect,
+                                                                    std::move(colorSpaceXform),
+                                                                    viewMatrix));
 }
 
 GrDrawVerticesOp::GrDrawVerticesOp(sk_sp<SkVertices> vertices, GrPrimitiveType primitiveType,
-                                   GrColor color, const SkMatrix& viewMatrix)
-        : INHERITED(ClassID()) {
+                                   GrColor color, bool gammaCorrect,
+                                   sk_sp<GrColorSpaceXform> colorSpaceXform,
+                                   const SkMatrix& viewMatrix)
+        : INHERITED(ClassID())
+        , fPrimitiveType(primitiveType)
+        , fColorSpaceXform(std::move(colorSpaceXform)) {
     SkASSERT(vertices);
 
     fVertexCount = vertices->vertexCount();
     fIndexCount = vertices->indexCount();
     fColorArrayType = vertices->hasColors() ? ColorArrayType::kSkColor
                                             : ColorArrayType::kPremulGrColor;
-    fPrimitiveType = primitiveType;
+    // GrColor is linearized (and gamut converted) during paint conversion, but SkColors need to be
+    // handled in the shader
+    fLinearizeColors = gammaCorrect && vertices->hasColors();
 
     Mesh& mesh = fMeshes.push_back();
     mesh.fColor = color;
@@ -74,6 +84,7 @@
         fMeshes[0].fIgnoreColors = true;
         fFlags &= ~kRequiresPerVertexColors_Flag;
         fColorArrayType = ColorArrayType::kPremulGrColor;
+        fLinearizeColors = false;
     }
     if (optimizations.readsLocalCoords()) {
         fFlags |= kPipelineRequiresLocalCoords_Flag;
@@ -107,6 +118,8 @@
         color.fType = (fColorArrayType == ColorArrayType::kPremulGrColor)
                               ? Color::kPremulGrColorAttribute_Type
                               : Color::kUnpremulSkColorAttribute_Type;
+        color.fLinearize = fLinearizeColors;
+        color.fColorSpaceXform = fColorSpaceXform;
         *hasColorAttribute = true;
     } else {
         *hasColorAttribute = false;
@@ -246,10 +259,18 @@
         return false;
     }
 
+    if (fLinearizeColors != that->fLinearizeColors) {
+        return false;
+    }
+
     if (fVertexCount + that->fVertexCount > SK_MaxU16) {
         return false;
     }
 
+    // NOTE: For SkColor vertex colors, the source color space is always sRGB, and the destination
+    // gamut is determined by the render target context. A mis-match should be impossible.
+    SkASSERT(GrColorSpaceXform::Equals(fColorSpaceXform.get(), that->fColorSpaceXform.get()));
+
     // If either op required explicit local coords or per-vertex colors the combined mesh does. Same
     // with multiple view matrices.
     fFlags |= that->fFlags;
@@ -349,6 +370,7 @@
     bool hasTexCoords = random->nextBool();
     bool hasIndices = random->nextBool();
     bool hasColors = random->nextBool();
+    bool linearizeColors = random->nextBool();
 
     uint32_t vertexCount = seed_vertices(type) + (primitiveCount - 1) * primitive_vertices(type);
 
@@ -367,6 +389,7 @@
     SkMatrix viewMatrix = GrTest::TestMatrix(random);
 
     GrColor color = GrRandomColor(random);
+    sk_sp<GrColorSpaceXform> colorSpaceXform = GrTest::TestColorXform(random);
 
     static constexpr SkVertices::VertexMode kIgnoredMode = SkVertices::kTriangles_VertexMode;
     sk_sp<SkVertices> vertices = SkVertices::MakeCopy(kIgnoredMode, vertexCount, positions.begin(),
@@ -374,7 +397,8 @@
                                                       hasIndices ? indices.count() : 0,
                                                       indices.begin());
     return std::unique_ptr<GrLegacyMeshDrawOp>(
-            new GrDrawVerticesOp(std::move(vertices), type, color, viewMatrix));
+            new GrDrawVerticesOp(std::move(vertices), type, color, linearizeColors,
+                                 std::move(colorSpaceXform), viewMatrix));
 }
 
 #endif
diff --git a/src/gpu/ops/GrDrawVerticesOp.h b/src/gpu/ops/GrDrawVerticesOp.h
index 0c75dcf..1d788ab 100644
--- a/src/gpu/ops/GrDrawVerticesOp.h
+++ b/src/gpu/ops/GrDrawVerticesOp.h
@@ -33,9 +33,12 @@
      * Draw a SkVertices. The GrColor param is used if the vertices lack per-vertex color. If the
      * vertices lack local coords then the vertex positions are used as local coords. The primitive
      * type drawn is derived from the SkVertices object, unless overridePrimType is specified.
+     * If gammaCorrect is true, the vertex colors will be linearized in the shader to get correct
+     * rendering.
      */
     static std::unique_ptr<GrLegacyMeshDrawOp> Make(GrColor color, sk_sp<SkVertices>,
-                                                    const SkMatrix& viewMatrix,
+                                                    const SkMatrix& viewMatrix, bool gammaCorrect,
+                                                    sk_sp<GrColorSpaceXform> colorSpaceXform,
                                                     GrPrimitiveType* overridePrimType = nullptr);
 
     const char* name() const override { return "DrawVerticesOp"; }
@@ -55,7 +58,8 @@
         kSkColor,
     };
 
-    GrDrawVerticesOp(sk_sp<SkVertices>, GrPrimitiveType, GrColor, const SkMatrix& viewMatrix);
+    GrDrawVerticesOp(sk_sp<SkVertices>, GrPrimitiveType, GrColor, bool gammaCorrect,
+                     sk_sp<GrColorSpaceXform>, const SkMatrix& viewMatrix);
 
     void getProcessorAnalysisInputs(GrProcessorAnalysisColor* color,
                                     GrProcessorAnalysisCoverage* coverage) const override;
@@ -125,6 +129,8 @@
     int fVertexCount;
     int fIndexCount;
     ColorArrayType fColorArrayType;
+    bool fLinearizeColors;
+    sk_sp<GrColorSpaceXform> fColorSpaceXform;
     SkSTArray<1, Mesh, true> fMeshes;
 
     typedef GrLegacyMeshDrawOp INHERITED;