Add SkMatrix::cheapEqualTo, use in Gr code

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



git-svn-id: http://skia.googlecode.com/svn/trunk@3488 2bbb7eff-a529-9590-31e7-b0007b416f81
diff --git a/include/core/SkMatrix.h b/include/core/SkMatrix.h
index df3275e..3c3d105 100644
--- a/include/core/SkMatrix.h
+++ b/include/core/SkMatrix.h
@@ -483,20 +483,28 @@
     */
     bool fixedStepInX(SkScalar y, SkFixed* stepX, SkFixed* stepY) const;
 
-#ifdef SK_SCALAR_IS_FIXED
-    friend bool operator==(const SkMatrix& a, const SkMatrix& b) {
-        return memcmp(a.fMat, b.fMat, sizeof(a.fMat)) == 0;
+    /** Efficient comparison of two matrices. It distinguishes between zero and
+     *  negative zero. It will return false when the sign of zero values is the
+     *  only difference between the two matrices. It considers NaN values to be
+     *  equal to themselves. So a matrix full of NaNs is "cheap equal" to
+     *  another matrix full of NaNs iff the NaN values are bitwise identical
+     *  while according to strict the strict == test a matrix with a NaN value
+     *  is equal to nothing, including itself.
+     */
+    bool cheapEqualTo(const SkMatrix& m) const {
+        return 0 == memcmp(fMat, m.fMat, sizeof(fMat));
     }
 
-    friend bool operator!=(const SkMatrix& a, const SkMatrix& b) {
-        return memcmp(a.fMat, b.fMat, sizeof(a.fMat)) != 0;
+#ifdef SK_SCALAR_IS_FIXED
+    friend bool operator==(const SkMatrix& a, const SkMatrix& b) {
+        return a->cheapEquals(b);
     }
 #else
-    friend bool operator==(const SkMatrix& a, const SkMatrix& b);    
+    friend bool operator==(const SkMatrix& a, const SkMatrix& b);
+#endif
     friend bool operator!=(const SkMatrix& a, const SkMatrix& b) {
         return !(a == b);
     }
-#endif
 
     enum {
         // flatten/unflatten will never return a value larger than this
diff --git a/src/gpu/GrDrawState.h b/src/gpu/GrDrawState.h
index c0ff42f..92ee2cb 100644
--- a/src/gpu/GrDrawState.h
+++ b/src/gpu/GrDrawState.h
@@ -85,8 +85,8 @@
         // are tightly packed
         GrAssert(kMemsetSize +  sizeof(fColor) + sizeof(fCoverage) +
                  sizeof(fFirstCoverageStage) + sizeof(fColorFilterMode) +
-                 sizeof(fSrcBlend) + sizeof(fDstBlend) + sizeof(GrMatrix) ==
-                 reinterpret_cast<uintptr_t>(&fEdgeAANumEdges) -
+                 sizeof(fSrcBlend) + sizeof(fDstBlend) ==
+                 reinterpret_cast<uintptr_t>(&fViewMatrix) -
                  reinterpret_cast<uintptr_t>(this));
 
         fEdgeAANumEdges = 0;
@@ -740,7 +740,13 @@
     // Most stages are usually not used, so conditionals here
     // reduce the expected number of bytes touched by 50%.
     bool operator ==(const GrDrawState& s) const {
-        if (memcmp(this, &s, this->leadingBytes())) return false;
+        if (memcmp(this, &s, this->leadingBytes())) {
+            return false;
+        }
+
+        if (!s.fViewMatrix.cheapEqualTo(fViewMatrix)) {
+            return false;
+        }
 
         for (int i = 0; i < kNumStages; i++) {
             if (fTextures[i] &&
@@ -766,6 +772,8 @@
     GrDrawState& operator =(const GrDrawState& s) {
         memcpy(this, &s, this->leadingBytes());
 
+        fViewMatrix = s.fViewMatrix;
+
         for (int i = 0; i < kNumStages; i++) {
             if (s.fTextures[i]) {
                 memcpy(&this->fSamplerStates[i], &s.fSamplerStates[i],
@@ -799,9 +807,10 @@
     SkXfermode::Mode    fColorFilterMode;
     GrBlendCoeff        fSrcBlend;
     GrBlendCoeff        fDstBlend;
-    GrMatrix            fViewMatrix;
     // @}
 
+    GrMatrix            fViewMatrix;
+
     // @{ Data for GrTesselatedPathRenderer
     // TODO: currently ignored in copying & comparison for performance.
     // Must be considered if GrTesselatedPathRenderer is being used.
@@ -820,7 +829,7 @@
         // TODO: ignores GrTesselatedPathRenderer data structures. We don't
         // have a compile-time flag that lets us know if it's being used, and
         // checking at runtime seems to cost 5% performance.
-        return (size_t) ((unsigned char*)&fEdgeAANumEdges -
+        return (size_t) ((unsigned char*)&fViewMatrix -
                          (unsigned char*)&fBlendConstant);
     }
 
diff --git a/src/gpu/gl/GrGpuGLShaders.cpp b/src/gpu/gl/GrGpuGLShaders.cpp
index cb156f7..ec2d4e2 100644
--- a/src/gpu/gl/GrGpuGLShaders.cpp
+++ b/src/gpu/gl/GrGpuGLShaders.cpp
@@ -409,7 +409,7 @@
 
 void GrGpuGLShaders::flushViewMatrix() {
     const GrMatrix& vm = this->getDrawState().getViewMatrix();
-    if (GrGpuGLShaders::getHWViewMatrix() != vm) {
+    if (!GrGpuGLShaders::getHWViewMatrix().cheapEqualTo(vm)) {
 
         const GrRenderTarget* rt = this->getDrawState().getRenderTarget();
         GrAssert(NULL != rt);
@@ -492,11 +492,13 @@
     const GrGLTexture* texture =
         static_cast<const GrGLTexture*>(drawState.getTexture(s));
     if (NULL != texture) {
+        const GrMatrix& hwMatrix = this->getHWSamplerMatrix(s);
+        const GrMatrix& samplerMatrix = drawState.getSampler(s).getMatrix();
         if (GrGLProgram::kUnusedUniform != uni &&
             (((1 << s) & fDirtyFlags.fTextureChangedMask) ||
-            this->getHWSamplerMatrix(s) != drawState.getSampler(s).getMatrix())) {
+            !hwMatrix.cheapEqualTo(samplerMatrix))) {
 
-            GrMatrix m = drawState.getSampler(s).getMatrix();
+            GrMatrix m = samplerMatrix;
             GrSamplerState::SampleMode mode =
                 drawState.getSampler(s).getSampleMode();
             AdjustTextureMatrix(texture, mode, &m);
diff --git a/tests/MatrixTest.cpp b/tests/MatrixTest.cpp
index c9a696c..316e670 100644
--- a/tests/MatrixTest.cpp
+++ b/tests/MatrixTest.cpp
@@ -32,6 +32,49 @@
     return true;
 }
 
+static bool are_equal(skiatest::Reporter* reporter,
+                      const SkMatrix& a,
+                      const SkMatrix& b) {
+    bool equal = a == b;
+    bool cheapEqual = a.cheapEqualTo(b);
+    if (equal != cheapEqual) {
+#if SK_SCALAR_IS_FLOAT
+        if (equal) {
+            bool foundZeroSignDiff = false;
+            for (int i = 0; i < 9; ++i) {
+                float aVal = a.get(i);
+                float bVal = b.get(i);
+                int aValI = *reinterpret_cast<int*>(&aVal);
+                int bValI = *reinterpret_cast<int*>(&bVal);
+                if (0 == aVal && 0 == bVal && aValI != bValI) {
+                    foundZeroSignDiff = true;
+                } else {
+                    REPORTER_ASSERT(reporter, aVal == bVal && aValI == aValI);
+                }
+            }
+            REPORTER_ASSERT(reporter, foundZeroSignDiff);
+        } else {
+            bool foundNaN = false;
+            for (int i = 0; i < 9; ++i) {
+                float aVal = a.get(i);
+                float bVal = b.get(i);
+                int aValI = *reinterpret_cast<int*>(&aVal);
+                int bValI = *reinterpret_cast<int*>(&bVal);
+                if (sk_float_isnan(aVal) && aValI == bValI) {
+                    foundNaN = true;
+                } else {
+                    REPORTER_ASSERT(reporter, aVal == bVal && aValI == bValI);
+                }
+            }
+            REPORTER_ASSERT(reporter, foundNaN);
+        }
+#else
+        REPORTER_ASSERT(reporter, false);
+#endif
+    }
+    return equal;
+}
+
 static bool is_identity(const SkMatrix& m) {
     SkMatrix identity;
     identity.reset();
@@ -49,7 +92,7 @@
     SkMatrix m2;
     uint32_t size3 = m2.unflatten(buffer);
     REPORTER_ASSERT(reporter, size1 == size2);
-    REPORTER_ASSERT(reporter, m == m2);
+    REPORTER_ASSERT(reporter, are_equal(reporter, m, m2));
     
     char buffer2[SkMatrix::kMaxFlattenSize + 100];
     size3 = m2.flatten(buffer2);
@@ -237,6 +280,19 @@
     mat.set(SkMatrix::kMPersp1, SkScalarToPersp(SK_Scalar1 / 2));
     REPORTER_ASSERT(reporter, !mat.asAffine(affine));
 
+    SkMatrix mat2;
+    mat2.reset();
+    mat.reset();
+    SkScalar zero = 0;
+    mat.set(SkMatrix::kMSkewX, -zero);
+    REPORTER_ASSERT(reporter, are_equal(reporter, mat, mat2));
+
+    mat2.reset();
+    mat.reset();
+    mat.set(SkMatrix::kMSkewX, SK_ScalarNaN);
+    mat2.set(SkMatrix::kMSkewX, SK_ScalarNaN);
+    REPORTER_ASSERT(reporter, !are_equal(reporter, mat, mat2));
+
     test_matrix_max_stretch(reporter);
 }