Correctly render text when coordinates are not integers.
Bug #3225632

Change-Id: If09759e6e95eb2885362ab3ba088cf5aae64c7bf
diff --git a/libs/hwui/Matrix.cpp b/libs/hwui/Matrix.cpp
index 7462d5b..fe7f883 100644
--- a/libs/hwui/Matrix.cpp
+++ b/libs/hwui/Matrix.cpp
@@ -62,6 +62,11 @@
              ALMOST_EQUAL(data[10], 1.0f));
 }
 
+bool Matrix4::isPureTranslate() {
+    return mSimpleMatrix &&
+            ALMOST_EQUAL(data[kScaleX], 1.0f) && ALMOST_EQUAL(data[kScaleY], 1.0f);
+}
+
 void Matrix4::load(const float* v) {
     memcpy(data, v, sizeof(data));
     mSimpleMatrix = false;
diff --git a/libs/hwui/Matrix.h b/libs/hwui/Matrix.h
index d678bd0..23fc6c3 100644
--- a/libs/hwui/Matrix.h
+++ b/libs/hwui/Matrix.h
@@ -103,6 +103,8 @@
         multiply(u);
     }
 
+    bool isPureTranslate();
+
     bool changesBounds();
 
     void copyTo(float* v) const;
diff --git a/libs/hwui/OpenGLRenderer.cpp b/libs/hwui/OpenGLRenderer.cpp
index f3e5b16..b24c04f 100644
--- a/libs/hwui/OpenGLRenderer.cpp
+++ b/libs/hwui/OpenGLRenderer.cpp
@@ -909,9 +909,19 @@
     SkXfermode::Mode mode;
     getAlphaAndMode(paint, &alpha, &mode);
 
-    drawTextureMesh(dstLeft, dstTop, dstRight, dstBottom, texture->id, alpha / 255.0f,
-            mode, texture->blend, &mMeshVertices[0].position[0], &mMeshVertices[0].texture[0],
-            GL_TRIANGLE_STRIP, gMeshCount);
+    if (mSnapshot->transform->isPureTranslate()) {
+        const float x = (int) floorf(dstLeft + mSnapshot->transform->getTranslateX() + 0.5f);
+        const float y = (int) floorf(dstTop + mSnapshot->transform->getTranslateY() + 0.5f);
+
+        drawTextureMesh(x, y, x + (dstRight - dstLeft), y + (dstBottom - dstTop),
+                texture->id, alpha / 255.0f, mode, texture->blend,
+                &mMeshVertices[0].position[0], &mMeshVertices[0].texture[0],
+                GL_TRIANGLE_STRIP, gMeshCount, false, true);
+    } else {
+        drawTextureMesh(dstLeft, dstTop, dstRight, dstBottom, texture->id, alpha / 255.0f,
+                mode, texture->blend, &mMeshVertices[0].position[0], &mMeshVertices[0].texture[0],
+                GL_TRIANGLE_STRIP, gMeshCount);
+    }
 
     resetDrawTextureTexCoords(0.0f, 0.0f, 1.0f, 1.0f);
 }
@@ -937,6 +947,7 @@
             right - left, bottom - top, xDivs, yDivs, colors, width, height, numColors);
 
     if (mesh && mesh->verticesCount > 0) {
+        const bool pureTranslate = mSnapshot->transform->isPureTranslate();
 #if RENDER_LAYERS_AS_REGIONS
         // Mark the current layer dirty where we are going to draw the patch
         if ((mSnapshot->flags & Snapshot::kFlagFboTarget) &&
@@ -944,16 +955,33 @@
             const size_t count = mesh->quads.size();
             for (size_t i = 0; i < count; i++) {
                 Rect bounds = mesh->quads.itemAt(i);
-                dirtyLayer(bounds.left, bounds.top, bounds.right, bounds.bottom,
-                        *mSnapshot->transform);
+                if (pureTranslate) {
+                    const float x = (int) floorf(bounds.left + 0.5f);
+                    const float y = (int) floorf(bounds.top + 0.5f);
+                    dirtyLayer(x, y, x + bounds.getWidth(), y + bounds.getBottom(),
+                            *mSnapshot->transform);
+                } else {
+                    dirtyLayer(bounds.left, bounds.top, bounds.right, bounds.bottom,
+                            *mSnapshot->transform);
+                }
             }
         }
 #endif
 
-        drawTextureMesh(left, top, right, bottom, texture->id, alpha / 255.0f,
-                mode, texture->blend, (GLvoid*) 0, (GLvoid*) gMeshTextureOffset,
-                GL_TRIANGLES, mesh->verticesCount, false, false, mesh->meshBuffer,
-                true, !mesh->hasEmptyQuads);
+        if (pureTranslate) {
+            const float x = (int) floorf(left + mSnapshot->transform->getTranslateX() + 0.5f);
+            const float y = (int) floorf(top + mSnapshot->transform->getTranslateY() + 0.5f);
+
+            drawTextureMesh(x, y, x + right - left, y + bottom - top, texture->id, alpha / 255.0f,
+                    mode, texture->blend, (GLvoid*) 0, (GLvoid*) gMeshTextureOffset,
+                    GL_TRIANGLES, mesh->verticesCount, false, true, mesh->meshBuffer,
+                    true, !mesh->hasEmptyQuads);
+        } else {
+            drawTextureMesh(left, top, right, bottom, texture->id, alpha / 255.0f,
+                    mode, texture->blend, (GLvoid*) 0, (GLvoid*) gMeshTextureOffset,
+                    GL_TRIANGLES, mesh->verticesCount, false, false, mesh->meshBuffer,
+                    true, !mesh->hasEmptyQuads);
+        }
     }
 }
 
@@ -1087,6 +1115,13 @@
             break;
     }
 
+    // TODO: Handle paint->getTextScaleX()
+    const bool pureTranslate = mSnapshot->transform->isPureTranslate();
+    if (pureTranslate) {
+        x = (int) floorf(x + mSnapshot->transform->getTranslateX() + 0.5f);
+        y = (int) floorf(y + mSnapshot->transform->getTranslateY() + 0.5f);
+    }
+
     int alpha;
     SkXfermode::Mode mode;
     getAlphaAndMode(paint, &alpha, &mode);
@@ -1110,7 +1145,7 @@
                 count, mShadowRadius);
         const AutoTexture autoCleanup(shadow);
 
-        setupShadow(shadow, x, y, mode, a);
+        setupShadow(shadow, x, y, mode, a, pureTranslate);
 
         // Draw the mesh
         glDrawArrays(GL_TRIANGLE_STRIP, 0, gMeshCount);
@@ -1120,14 +1155,17 @@
     GLuint textureUnit = 0;
     glActiveTexture(gTextureUnits[textureUnit]);
 
-    // Assume that the modelView matrix does not force scales, rotates, etc.
-    const bool linearFilter = mSnapshot->transform->changesBounds();
+    // Pick the appropriate texture filtering
+    bool linearFilter = mSnapshot->transform->changesBounds();
+    if (pureTranslate && !linearFilter) {
+        linearFilter = fabs(y - (int) y) > 0.0f || fabs(x - (int) x) > 0.0f;
+    }
 
     // Dimensions are set to (0,0), the layer (if any) won't be dirtied
     setupTextureAlpha8(fontRenderer.getTexture(linearFilter), 0, 0, textureUnit,
-            x, y, r, g, b, a, mode, false, true, NULL, NULL);
+            x, y, r, g, b, a, mode, false, true, NULL, NULL, 0, pureTranslate);
 
-    const Rect& clip = mSnapshot->getLocalClip();
+    const Rect* clip = pureTranslate ? mSnapshot->clipRect : &mSnapshot->getLocalClip();
     Rect bounds(FLT_MAX / 2.0f, FLT_MAX / 2.0f, FLT_MIN / 2.0f, FLT_MIN / 2.0f);
 
 #if RENDER_LAYERS_AS_REGIONS
@@ -1137,11 +1175,13 @@
 #endif
 
     mCaches.unbindMeshBuffer();
-    if (fontRenderer.renderText(paint, &clip, text, 0, bytesCount, count, x, y,
+    if (fontRenderer.renderText(paint, clip, text, 0, bytesCount, count, x, y,
             hasLayer ? &bounds : NULL)) {
 #if RENDER_LAYERS_AS_REGIONS
         if (hasLayer) {
-            mSnapshot->transform->mapRect(bounds);
+            if (!pureTranslate) {
+                mSnapshot->transform->mapRect(bounds);
+            }
             bounds.intersect(*mSnapshot->clipRect);
             bounds.snapToPixelBoundaries();
 
@@ -1241,7 +1281,7 @@
 ///////////////////////////////////////////////////////////////////////////////
 
 void OpenGLRenderer::setupShadow(const ShadowTexture* texture, float x, float y,
-        SkXfermode::Mode mode, float alpha) {
+        SkXfermode::Mode mode, float alpha, bool ignoreTransforms) {
     const float sx = x - texture->left + mShadowDx;
     const float sy = y - texture->top + mShadowDy;
 
@@ -1252,7 +1292,9 @@
     const GLfloat b = a * ((mShadowColor      ) & 0xFF) / 255.0f;
 
     GLuint textureUnit = 0;
-    setupTextureAlpha8(texture, textureUnit, sx, sy, r, g, b, a, mode, true, false);
+    setupTextureAlpha8(texture->id, texture->width, texture->height, textureUnit,
+            sx, sy, r, g, b, a, mode, true, false,
+            (GLvoid*) 0, (GLvoid*) gMeshTextureOffset, 0, ignoreTransforms);
 }
 
 void OpenGLRenderer::setupTextureAlpha8(const Texture* texture, GLuint& textureUnit,
@@ -1273,7 +1315,7 @@
 void OpenGLRenderer::setupTextureAlpha8(GLuint texture, uint32_t width, uint32_t height,
         GLuint& textureUnit, float x, float y, float r, float g, float b, float a,
         SkXfermode::Mode mode, bool transforms, bool applyFilters,
-        GLvoid* vertices, GLvoid* texCoords, GLuint vbo) {
+        GLvoid* vertices, GLvoid* texCoords, GLuint vbo, bool ignoreTransform) {
      // Describe the required shaders
      ProgramDescription description;
      description.hasTexture = true;
@@ -1321,9 +1363,14 @@
          mModelView.loadIdentity();
      }
 
-     mCaches.currentProgram->set(mOrthoMatrix, mModelView, *mSnapshot->transform);
+     mat4 t;
+     if (!ignoreTransform) {
+         t.load(*mSnapshot->transform);
+     }
+
+     mCaches.currentProgram->set(mOrthoMatrix, mModelView, t);
      if (width > 0 && height > 0) {
-         dirtyLayer(x, y, x + width, y + height, *mSnapshot->transform);
+         dirtyLayer(x, y, x + width, y + height, t);
      }
 
      if (setColor) {
@@ -1334,6 +1381,9 @@
      if (applyFilters) {
          // Setup attributes and uniforms required by the shaders
          if (mShader) {
+             if (ignoreTransform) {
+                 mModelView.loadInverse(*mSnapshot->transform);
+             }
              mShader->setupProgram(mCaches.currentProgram, mModelView, *mSnapshot, &textureUnit);
          }
          if (mColorFilter) {
@@ -1473,7 +1523,11 @@
 
     // Setup attributes and uniforms required by the shaders
     if (mShader) {
-        if (ignoreMatrix) mModelView.loadIdentity();
+        if (ignoreMatrix) {
+            mModelView.loadIdentity();
+        } else if (ignoreTransform) {
+            mModelView.loadInverse(*mSnapshot->transform);
+        }
         mShader->setupProgram(mCaches.currentProgram, mModelView, *mSnapshot, &textureUnit);
     }
     if (mColorFilter) {
@@ -1489,9 +1543,18 @@
 
     setTextureWrapModes(texture, GL_CLAMP_TO_EDGE, GL_CLAMP_TO_EDGE);
 
-    drawTextureMesh(left, top, right, bottom, texture->id, alpha / 255.0f, mode,
-            texture->blend, (GLvoid*) NULL, (GLvoid*) gMeshTextureOffset,
-            GL_TRIANGLE_STRIP, gMeshCount);
+    if (mSnapshot->transform->isPureTranslate()) {
+        const float x = (int) floorf(left + mSnapshot->transform->getTranslateX() + 0.5f);
+        const float y = (int) floorf(top + mSnapshot->transform->getTranslateY() + 0.5f);
+
+        drawTextureMesh(x, y, x + texture->width, y + texture->height, texture->id,
+                alpha / 255.0f, mode, texture->blend, (GLvoid*) NULL,
+                (GLvoid*) gMeshTextureOffset, GL_TRIANGLE_STRIP, gMeshCount, false, true);
+    } else {
+        drawTextureMesh(left, top, right, bottom, texture->id, alpha / 255.0f, mode,
+                texture->blend, (GLvoid*) NULL, (GLvoid*) gMeshTextureOffset,
+                GL_TRIANGLE_STRIP, gMeshCount);
+    }
 }
 
 void OpenGLRenderer::drawTextureRect(float left, float top, float right, float bottom,
diff --git a/libs/hwui/OpenGLRenderer.h b/libs/hwui/OpenGLRenderer.h
index a942dde..e866d1b 100644
--- a/libs/hwui/OpenGLRenderer.h
+++ b/libs/hwui/OpenGLRenderer.h
@@ -294,9 +294,10 @@
      * @param y The y coordinate of the shadow
      * @param mode The blending mode
      * @param alpha The alpha value
+     * @param ignoreTransforms True if the coordinates are already in screen space
      */
     void setupShadow(const ShadowTexture* texture, float x, float y, SkXfermode::Mode mode,
-            float alpha);
+            float alpha, bool ignoreTransforms = false);
 
     /**
      * Prepares the renderer to draw the specified Alpha8 texture as a rectangle.
@@ -349,7 +350,7 @@
     void setupTextureAlpha8(GLuint texture, uint32_t width, uint32_t height,
             GLuint& textureUnit, float x, float y, float r, float g, float b, float a,
             SkXfermode::Mode mode, bool transforms, bool applyFilters,
-            GLvoid* vertices, GLvoid* texCoords, GLuint vbo = 0);
+            GLvoid* vertices, GLvoid* texCoords, GLuint vbo = 0, bool ignoreTransform = false);
 
     /**
      * Draws text underline and strike-through if needed.