Implement the color matrix filter in Ganesh.  Also, fix and enable the color
matrix test slide.  This was basically implemented in the same places where
the blending-based color filter was being done.  The shader simply does a mat4
matrix multiply and a vec4 add.

Review URL:  http://codereview.appspot.com/5489107/



git-svn-id: http://skia.googlecode.com/svn/trunk@2948 2bbb7eff-a529-9590-31e7-b0007b416f81
diff --git a/src/gpu/GrContext.cpp b/src/gpu/GrContext.cpp
index be3b7be..a406629 100644
--- a/src/gpu/GrContext.cpp
+++ b/src/gpu/GrContext.cpp
@@ -1938,8 +1938,14 @@
     } else {
         drawState->disableState(GrDrawState::kHWAntialias_StateBit);
     }
+    if (paint.fColorMatrixEnabled) {
+        drawState->enableState(GrDrawState::kColorMatrix_StateBit);
+    } else {
+        drawState->disableState(GrDrawState::kColorMatrix_StateBit);
+    }
     drawState->setBlendFunc(paint.fSrcBlendCoeff, paint.fDstBlendCoeff);
     drawState->setColorFilter(paint.fColorFilterColor, paint.fColorFilterXfermode);
+    drawState->setColorMatrix(paint.fColorMatrix);
 
     if (paint.getActiveMaskStageMask() && !target->canApplyCoverage()) {
         GrPrintf("Partial pixel coverage will be incorrectly blended.\n");
diff --git a/src/gpu/GrDrawState.h b/src/gpu/GrDrawState.h
index a119115..dfe0d4b 100644
--- a/src/gpu/GrDrawState.h
+++ b/src/gpu/GrDrawState.h
@@ -455,6 +455,22 @@
     /// @}
 
     ///////////////////////////////////////////////////////////////////////////
+    /// @name Color Matrix
+    ////
+
+    /**
+     * Sets the color matrix to use for the next draw.
+     * @param matrix  the 5x4 matrix to apply to the incoming color
+     */
+    void setColorMatrix(const float matrix[20]) {
+        memcpy(fColorMatrix, matrix, sizeof(fColorMatrix));
+    }
+
+    const float* getColorMatrix() const { return fColorMatrix; }
+
+    /// @}
+
+    ///////////////////////////////////////////////////////////////////////////
     // @name Edge AA
     // There are two ways to perform antialiasing using edge equations. One
     // is to specify an (linear or quadratic) edge eq per-vertex. This requires
@@ -569,6 +585,11 @@
          * source polygon is non-convex.
          */
         kEdgeAAConcave_StateBit = 0x10,
+        /**
+         * Draws will apply the color matrix, otherwise the color matrix is
+         * ignored.
+         */
+        kColorMatrix_StateBit   = 0x20,
 
         // Users of the class may add additional bits to the vector
         kDummyStateBit,
@@ -703,6 +724,7 @@
     GrRenderTarget*         fRenderTarget;
     GrColor                 fColor;
     GrColor                 fColorFilterColor;
+    float                   fColorMatrix[20];
     GrStencilSettings       fStencilSettings;
     GrMatrix                fViewMatrix;
     // @{ Data for GrTesselatedPathRenderer
diff --git a/src/gpu/GrGLProgram.cpp b/src/gpu/GrGLProgram.cpp
index b7e902d..ce87b85 100644
--- a/src/gpu/GrGLProgram.cpp
+++ b/src/gpu/GrGLProgram.cpp
@@ -94,6 +94,8 @@
 #define COL_UNI_NAME "uColor"
 #define EDGES_UNI_NAME "uEdges"
 #define COL_FILTER_UNI_NAME "uColorFilter"
+#define COL_MATRIX_UNI_NAME "uColorMatrix"
+#define COL_MATRIX_VEC_UNI_NAME "uColorMatrixVec"
 
 namespace {
 inline void tex_attr_name(int coordIdx, GrStringBuilder* s) {
@@ -365,6 +367,14 @@
 
     add_helper(outputVar, colorStr.c_str(), constStr.c_str(), fsCode);
 }
+/**
+ * Adds code to the fragment shader code which modifies the color by
+ * the specified color matrix.
+ */
+static void addColorMatrix(GrStringBuilder* fsCode, const char * outputVar,
+                           const char* inColor) {
+    fsCode->appendf("%s = %s * %s + %s;\n", outputVar, COL_MATRIX_UNI_NAME, inColor, COL_MATRIX_VEC_UNI_NAME);
+}
 
 namespace {
 
@@ -798,10 +808,10 @@
                                          COL_FILTER_UNI_NAME);
         programData->fUniLocations.fColorFilterUni = kUseUniform;
     }
-
     bool wroteFragColorZero = false;
     if (SkXfermode::kZero_Coeff == uniformCoeff &&
-        SkXfermode::kZero_Coeff == colorCoeff) {
+        SkXfermode::kZero_Coeff == colorCoeff &&
+        !fProgramDesc.fColorMatrixEnabled) {
         segments.fFSCode.appendf("\t%s = %s;\n",
                                  fsColorOutput,
                                  all_zeros_vec(4));
@@ -822,6 +832,19 @@
                        colorCoeff, color);
         inColor = "filteredColor";
     }
+    if (fProgramDesc.fColorMatrixEnabled) {
+        segments.fFSUnis.push_back().set(GrGLShaderVar::kMat44f_Type,
+                                         GrGLShaderVar::kUniform_TypeModifier,
+                                         COL_MATRIX_UNI_NAME);
+        segments.fFSUnis.push_back().set(GrGLShaderVar::kVec4f_Type,
+                                         GrGLShaderVar::kUniform_TypeModifier,
+                                         COL_MATRIX_VEC_UNI_NAME);
+        programData->fUniLocations.fColorMatrixUni = kUseUniform;
+        programData->fUniLocations.fColorMatrixVecUni = kUseUniform;
+        segments.fFSCode.appendf("\tvec4 matrixedColor;\n");
+        addColorMatrix(&segments.fFSCode, "matrixedColor", inColor.c_str());
+        inColor = "matrixedColor";
+    }
 
     ///////////////////////////////////////////////////////////////////////////
     // compute the partial coverage (coverage stages and edge aa)
@@ -1243,6 +1266,16 @@
         GrAssert(kUnusedUniform != programData->fUniLocations.fColorFilterUni);
     }
 
+    if (kUseUniform == programData->fUniLocations.fColorMatrixUni) {
+        GR_GL_CALL_RET(gl, programData->fUniLocations.fColorMatrixUni,
+                       GetUniformLocation(progID, COL_MATRIX_UNI_NAME));
+    }
+
+    if (kUseUniform == programData->fUniLocations.fColorMatrixVecUni) {
+        GR_GL_CALL_RET(gl, programData->fUniLocations.fColorMatrixVecUni,
+                       GetUniformLocation(progID, COL_MATRIX_VEC_UNI_NAME));
+    }
+
     if (kUseUniform == programData->fUniLocations.fEdgesUni) {
         GR_GL_CALL_RET(gl, programData->fUniLocations.fEdgesUni,
                        GetUniformLocation(progID, EDGES_UNI_NAME));
diff --git a/src/gpu/GrGLProgram.h b/src/gpu/GrGLProgram.h
index 450da05..7d19f7a 100644
--- a/src/gpu/GrGLProgram.h
+++ b/src/gpu/GrGLProgram.h
@@ -216,9 +216,11 @@
         int8_t fFirstCoverageStage;
         SkBool8 fEmitsPointSize;
         SkBool8 fEdgeAAConcave;
+        SkBool8 fColorMatrixEnabled;
 
         int8_t fEdgeAANumEdges;
         uint8_t fColorFilterXfermode;  // casts to enum SkXfermode::Mode
+        int8_t fPadding[3];
 
     } fProgramDesc;
     GR_STATIC_ASSERT(!(sizeof(ProgramDesc) % 4));
@@ -260,12 +262,16 @@
         GrGLint fColorUni;
         GrGLint fEdgesUni;
         GrGLint fColorFilterUni;
+        GrGLint fColorMatrixUni;
+        GrGLint fColorMatrixVecUni;
         StageUniLocations fStages[GrDrawState::kNumStages];
         void reset() {
             fViewMatrixUni = kUnusedUniform;
             fColorUni = kUnusedUniform;
             fEdgesUni = kUnusedUniform;
             fColorFilterUni = kUnusedUniform;
+            fColorMatrixUni = kUnusedUniform;
+            fColorMatrixVecUni = kUnusedUniform;
             for (int s = 0; s < GrDrawState::kNumStages; ++s) {
                 fStages[s].reset();
             }
diff --git a/src/gpu/GrGLShaderVar.h b/src/gpu/GrGLShaderVar.h
index 5f1ba03..1d5d7ca 100644
--- a/src/gpu/GrGLShaderVar.h
+++ b/src/gpu/GrGLShaderVar.h
@@ -27,6 +27,7 @@
         kVec3f_Type,
         kVec4f_Type,
         kMat33f_Type,
+        kMat44f_Type,
         kSampler2D_Type,
     };
 
@@ -242,6 +243,8 @@
                 return "vec4";
             case kMat33f_Type:
                 return "mat3";
+            case kMat44f_Type:
+                return "mat4";
             case kSampler2D_Type:
                 return "sampler2D";
             default:
diff --git a/src/gpu/GrGpuGLShaders.cpp b/src/gpu/GrGpuGLShaders.cpp
index 98ac1bf..e9aea2a 100644
--- a/src/gpu/GrGpuGLShaders.cpp
+++ b/src/gpu/GrGpuGLShaders.cpp
@@ -234,6 +234,8 @@
             pdesc.fEdgeAANumEdges = 0;
         }
 
+        pdesc.fColorMatrixEnabled = random_bool(&random);
+
         if (this->getCaps().fDualSourceBlendingSupport) {
             pdesc.fDualSrcOutput = random_int(&random, ProgramDesc::kDualSrcOutputCnt);
         } else {
@@ -612,6 +614,28 @@
     }
 }
 
+void GrGpuGLShaders::flushColorMatrix() {
+    const ProgramDesc& desc = fCurrentProgram.getDesc();
+    int matrixUni = fProgramData->fUniLocations.fColorMatrixUni;
+    int vecUni = fProgramData->fUniLocations.fColorMatrixVecUni;
+    if (GrGLProgram::kUnusedUniform != matrixUni
+     && GrGLProgram::kUnusedUniform != vecUni) {
+        const float* m = this->getDrawState().getColorMatrix();
+        GrGLfloat mt[]  = {
+            m[0], m[5], m[10], m[15],
+            m[1], m[6], m[11], m[16],
+            m[2], m[7], m[12], m[17],
+            m[3], m[8], m[13], m[18],
+        };
+        static float scale = 1.0f / 255.0f;
+        GrGLfloat vec[] = {
+            m[4] * scale, m[9] * scale, m[14] * scale, m[19] * scale,
+        };
+        GL_CALL(UniformMatrix4fv(matrixUni, 1, false, mt));
+        GL_CALL(Uniform4fv(vecUni, 1, vec));
+    }
+}
+
 static const float ONE_OVER_255 = 1.f / 255.f;
 
 #define GR_COLOR_TO_VEC4(color) {\
@@ -731,6 +755,7 @@
         }
     }
     this->flushEdgeAAData();
+    this->flushColorMatrix();
     resetDirtyFlags();
     return true;
 }
@@ -910,6 +935,8 @@
                                 SkXfermode::kDst_Mode :
                                 drawState.getColorFilterMode();
 
+    desc.fColorMatrixEnabled = drawState.isStateFlagEnabled(GrDrawState::kColorMatrix_StateBit);
+
     // no reason to do edge aa or look at per-vertex coverage if coverage is
     // ignored
     if (skipCoverage) {
diff --git a/src/gpu/GrGpuGLShaders.h b/src/gpu/GrGpuGLShaders.h
index 3671296..4b972b5 100644
--- a/src/gpu/GrGpuGLShaders.h
+++ b/src/gpu/GrGpuGLShaders.h
@@ -75,6 +75,9 @@
     // flushes the edges for edge AA
     void flushEdgeAAData();
 
+    // flushes the color matrix
+    void flushColorMatrix();
+
     static void DeleteProgram(const GrGLInterface* gl,
                               CachedData* programData);
 
diff --git a/src/gpu/SkGpuDevice.cpp b/src/gpu/SkGpuDevice.cpp
index b80d3a6..6e9b366 100644
--- a/src/gpu/SkGpuDevice.cpp
+++ b/src/gpu/SkGpuDevice.cpp
@@ -441,16 +441,23 @@
     SkColorFilter* colorFilter = skPaint.getColorFilter();
     SkColor color;
     SkXfermode::Mode filterMode;
+    SkScalar matrix[20];
     if (colorFilter != NULL && colorFilter->asColorMode(&color, &filterMode)) {
+        grPaint->fColorMatrixEnabled = false;
         if (!constantColor) {
             grPaint->fColorFilterColor = SkGr::SkColor2GrColor(color);
             grPaint->fColorFilterXfermode = filterMode;
-            return true;
+        } else {
+            SkColor filtered = colorFilter->filterColor(skPaint.getColor());
+            grPaint->fColor = SkGr::SkColor2GrColor(filtered);
         }
-        SkColor filtered = colorFilter->filterColor(skPaint.getColor());
-        grPaint->fColor = SkGr::SkColor2GrColor(filtered);
+    } else if (colorFilter != NULL && colorFilter->asColorMatrix(matrix)) {
+        grPaint->fColorMatrixEnabled = true;
+        memcpy(grPaint->fColorMatrix, matrix, sizeof(matrix));
+        grPaint->fColorFilterXfermode = SkXfermode::kDst_Mode;
+    } else {
+        grPaint->resetColorFilter();
     }
-    grPaint->resetColorFilter();
     return true;
 }