Merge "Support 3D rotations when drawing text" into jb-mr2-dev
diff --git a/libs/hwui/DisplayListRenderer.cpp b/libs/hwui/DisplayListRenderer.cpp
index 710f12f..16218fa 100644
--- a/libs/hwui/DisplayListRenderer.cpp
+++ b/libs/hwui/DisplayListRenderer.cpp
@@ -406,7 +406,7 @@
     if (addDrawOp(op)) {
         // precache if draw operation is visible
         FontRenderer& fontRenderer = mCaches.fontRenderer->getFontRenderer(paint);
-        fontRenderer.precache(paint, text, count, *mSnapshot->transform);
+        fontRenderer.precache(paint, text, count, mat4::identity());
     }
     return DrawGlInfo::kStatusDone;
 }
@@ -423,7 +423,7 @@
     if (addDrawOp(op)) {
         // precache if draw operation is visible
         FontRenderer& fontRenderer = mCaches.fontRenderer->getFontRenderer(paint);
-        fontRenderer.precache(paint, text, count, *mSnapshot->transform);
+        fontRenderer.precache(paint, text, count, mat4::identity());
     }
     return DrawGlInfo::kStatusDone;
 }
@@ -442,7 +442,9 @@
     if (addDrawOp(op)) {
         // precache if draw operation is visible
         FontRenderer& fontRenderer = mCaches.fontRenderer->getFontRenderer(paint);
-        fontRenderer.precache(paint, text, count, *mSnapshot->transform);
+        const bool pureTranslate = mSnapshot->transform->isPureTranslate();
+        fontRenderer.precache(paint, text, count,
+                pureTranslate ? mat4::identity() : *mSnapshot->transform);
     }
     return DrawGlInfo::kStatusDone;
 }
diff --git a/libs/hwui/FontRenderer.cpp b/libs/hwui/FontRenderer.cpp
index db65b88..d5ea0f9 100644
--- a/libs/hwui/FontRenderer.cpp
+++ b/libs/hwui/FontRenderer.cpp
@@ -180,7 +180,17 @@
 void FontRenderer::cacheBitmap(const SkGlyph& glyph, CachedGlyphInfo* cachedGlyph,
         uint32_t* retOriginX, uint32_t* retOriginY, bool precaching) {
     checkInit();
+
+    // If the glyph bitmap is empty let's assum the glyph is valid
+    // so we can avoid doing extra work later on
+    if (glyph.fWidth == 0 || glyph.fHeight == 0) {
+        cachedGlyph->mIsValid = true;
+        cachedGlyph->mCacheTexture = NULL;
+        return;
+    }
+
     cachedGlyph->mIsValid = false;
+
     // If the glyph is too tall, don't cache it
     if (glyph.fHeight + TEXTURE_BORDER_SIZE * 2 >
                 mCacheTextures[mCacheTextures.size() - 1]->getHeight()) {
diff --git a/libs/hwui/OpenGLRenderer.cpp b/libs/hwui/OpenGLRenderer.cpp
index fb77ef6..7e9734f 100644
--- a/libs/hwui/OpenGLRenderer.cpp
+++ b/libs/hwui/OpenGLRenderer.cpp
@@ -2151,17 +2151,17 @@
 
     alpha *= mSnapshot->alpha;
 
-    mCaches.activeTexture(0);
-    Texture* texture = mCaches.textureCache.get(bitmap);
-    if (!texture) return DrawGlInfo::kStatusDone;
-    const AutoTexture autoCleanup(texture);
-    texture->setWrap(GL_CLAMP_TO_EDGE, true);
-    texture->setFilter(GL_LINEAR, true);
-
     const Patch* mesh = mCaches.patchCache.get(bitmap->width(), bitmap->height(),
             right - left, bottom - top, xDivs, yDivs, colors, width, height, numColors);
 
     if (CC_LIKELY(mesh && mesh->verticesCount > 0)) {
+        mCaches.activeTexture(0);
+        Texture* texture = mCaches.textureCache.get(bitmap);
+        if (!texture) return DrawGlInfo::kStatusDone;
+        const AutoTexture autoCleanup(texture);
+        texture->setWrap(GL_CLAMP_TO_EDGE, true);
+        texture->setFilter(GL_LINEAR, true);
+
         const bool pureTranslate = mSnapshot->transform->isPureTranslate();
         // Mark the current layer dirty where we are going to draw the patch
         if (hasLayer() && mesh->hasEmptyQuads) {
@@ -2666,6 +2666,7 @@
     const float oldX = x;
     const float oldY = y;
     const bool pureTranslate = mSnapshot->transform->isPureTranslate();
+    const bool isPerspective = mSnapshot->transform->isPerspective();
 
     if (CC_LIKELY(pureTranslate)) {
         x = (int) floorf(x + mSnapshot->transform->getTranslateX() + 0.5f);
@@ -2687,8 +2688,7 @@
     fontRenderer.setFont(paint, pureTranslate ? mat4::identity() : *mSnapshot->transform);
 
     // Pick the appropriate texture filtering
-    bool linearFilter = !mSnapshot->transform->isPureTranslate() ||
-            fabs(y - (int) y) > 0.0f || fabs(x - (int) x) > 0.0f;
+    bool linearFilter = !pureTranslate || fabs(y - (int) y) > 0.0f || fabs(x - (int) x) > 0.0f;
 
     // The font renderer will always use texture unit 0
     mCaches.activeTexture(0);
@@ -2701,13 +2701,13 @@
     setupDrawShader();
     setupDrawBlending(true, mode);
     setupDrawProgram();
-    setupDrawModelView(x, y, x, y, true, true);
+    setupDrawModelView(x, y, x, y, !isPerspective, true);
     // See comment above; the font renderer must use texture unit 0
     // assert(mTextureUnit == 0)
     setupDrawTexture(fontRenderer.getTexture(linearFilter));
     setupDrawPureColorUniforms();
     setupDrawColorFilterUniforms();
-    setupDrawShaderUniforms(true);
+    setupDrawShaderUniforms(!isPerspective);
     setupDrawTextGammaUniforms();
 
     const Rect* clip = mSnapshot->hasPerspectiveTransform() ? NULL : mSnapshot->clipRect;
@@ -2727,6 +2727,9 @@
     }
 
     if (status && hasActiveLayer) {
+        if (isPerspective) {
+            mSnapshot->transform->mapRect(bounds);
+        }
         dirtyLayerUnchecked(bounds, getRegion());
     }
 
diff --git a/libs/hwui/font/Font.cpp b/libs/hwui/font/Font.cpp
index ea9fd03..d48b612 100644
--- a/libs/hwui/font/Font.cpp
+++ b/libs/hwui/font/Font.cpp
@@ -52,6 +52,7 @@
     mStyle = paint->getStyle();
     mStrokeWidth = paint->getStrokeWidth();
     mAntiAliasing = paint->isAntiAlias();
+    mLookupTransform.reset();
     mLookupTransform[SkMatrix::kMScaleX] = matrix.data[mat4::kScaleX];
     mLookupTransform[SkMatrix::kMScaleY] = matrix.data[mat4::kScaleY];
     mLookupTransform[SkMatrix::kMSkewX] = matrix.data[mat4::kSkewX];
@@ -165,7 +166,7 @@
 void Font::drawCachedGlyph(CachedGlyphInfo* glyph, int x, int y,
         uint8_t* bitmap, uint32_t bitmapW, uint32_t bitmapH, Rect* bounds, const float* pos) {
     float nPenX = x + glyph->mBitmapLeft;
-    float nPenY = y + (glyph->mBitmapTop + glyph->mBitmapHeight);
+    float nPenY = y + glyph->mBitmapTop + glyph->mBitmapHeight;
 
     float width = (float) glyph->mBitmapWidth;
     float height = (float) glyph->mBitmapHeight;
@@ -181,6 +182,38 @@
             nPenX, nPenY - height, u1, v1, glyph->mCacheTexture);
 }
 
+void Font::drawCachedGlyphPerspective(CachedGlyphInfo* glyph, int x, int y,
+        uint8_t* bitmap, uint32_t bitmapW, uint32_t bitmapH, Rect* bounds, const float* pos) {
+    SkMatrix i;
+    if (!mDescription.mLookupTransform.invert(&i)) {
+        return;
+    }
+
+    SkPoint p[4];
+    p[0].set(glyph->mBitmapLeft, glyph->mBitmapTop + glyph->mBitmapHeight);
+    p[1].set(glyph->mBitmapLeft + glyph->mBitmapWidth, glyph->mBitmapTop + glyph->mBitmapHeight);
+    p[2].set(glyph->mBitmapLeft + glyph->mBitmapWidth, glyph->mBitmapTop);
+    p[3].set(glyph->mBitmapLeft, glyph->mBitmapTop);
+
+    i.mapPoints(p, 4);
+
+    p[0].offset(x, y);
+    p[1].offset(x, y);
+    p[2].offset(x, y);
+    p[3].offset(x, y);
+
+    float u1 = glyph->mBitmapMinU;
+    float u2 = glyph->mBitmapMaxU;
+    float v1 = glyph->mBitmapMinV;
+    float v2 = glyph->mBitmapMaxV;
+
+    mState->appendRotatedMeshQuad(
+            p[0].fX, p[0].fY, u1, v2,
+            p[1].fX, p[1].fY, u2, v2,
+            p[2].fX, p[2].fY, u2, v1,
+            p[3].fX, p[3].fY, u1, v1, glyph->mCacheTexture);
+}
+
 void Font::drawCachedGlyphBitmap(CachedGlyphInfo* glyph, int x, int y,
         uint8_t* bitmap, uint32_t bitmapW, uint32_t bitmapH, Rect* bounds, const float* pos) {
     int nPenX = x + glyph->mBitmapLeft;
@@ -307,7 +340,7 @@
         penX += SkFixedToFloat(AUTO_KERN(prevRsbDelta, cachedGlyph->mLsbDelta));
         prevRsbDelta = cachedGlyph->mRsbDelta;
 
-        if (cachedGlyph->mIsValid) {
+        if (cachedGlyph->mIsValid && cachedGlyph->mCacheTexture) {
             drawCachedGlyph(cachedGlyph, penX, hOffset, vOffset, measure, &position, &tangent);
         }
 
@@ -328,7 +361,6 @@
 }
 
 void Font::precache(SkPaint* paint, const char* text, int numGlyphs) {
-
     if (numGlyphs == 0 || text == NULL) {
         return;
     }
@@ -357,14 +389,18 @@
 
     static RenderGlyph gRenderGlyph[] = {
             &android::uirenderer::Font::drawCachedGlyph,
+            &android::uirenderer::Font::drawCachedGlyphPerspective,
             &android::uirenderer::Font::drawCachedGlyphBitmap,
+            &android::uirenderer::Font::drawCachedGlyphBitmap,
+            &android::uirenderer::Font::measureCachedGlyph,
             &android::uirenderer::Font::measureCachedGlyph
     };
-    RenderGlyph render = gRenderGlyph[mode];
+    RenderGlyph render = gRenderGlyph[(mode << 1) + mTransform.isPerspective()];
 
     text += start;
     int glyphsCount = 0;
 
+    const bool applyTransform = !mTransform.isIdentity() && !mTransform.isPerspective();
     const SkPaint::Align align = paint->getTextAlign();
 
     while (glyphsCount < numGlyphs) {
@@ -377,12 +413,13 @@
 
         CachedGlyphInfo* cachedGlyph = getCachedGlyph(paint, glyph);
 
-        // If it's still not valid, we couldn't cache it, so we shouldn't draw garbage
-        if (cachedGlyph->mIsValid) {
+        // If it's still not valid, we couldn't cache it, so we shouldn't
+        // draw garbage; also skip empty glyphs (spaces)
+        if (cachedGlyph->mIsValid && cachedGlyph->mCacheTexture) {
             float penX = x + positions[(glyphsCount << 1)];
             float penY = y + positions[(glyphsCount << 1) + 1];
 
-            if (!mTransform.isIdentity()) {
+            if (applyTransform) {
                 mTransform.mapPoint(penX, penY);
             }
 
@@ -424,15 +461,18 @@
     glyph->mBitmapWidth = skiaGlyph.fWidth;
     glyph->mBitmapHeight = skiaGlyph.fHeight;
 
-    uint32_t cacheWidth = glyph->mCacheTexture->getWidth();
-    uint32_t cacheHeight = glyph->mCacheTexture->getHeight();
+    bool empty = skiaGlyph.fWidth == 0 || skiaGlyph.fHeight == 0;
+    if (!empty) {
+        uint32_t cacheWidth = glyph->mCacheTexture->getWidth();
+        uint32_t cacheHeight = glyph->mCacheTexture->getHeight();
 
-    glyph->mBitmapMinU = startX / (float) cacheWidth;
-    glyph->mBitmapMinV = startY / (float) cacheHeight;
-    glyph->mBitmapMaxU = endX / (float) cacheWidth;
-    glyph->mBitmapMaxV = endY / (float) cacheHeight;
+        glyph->mBitmapMinU = startX / (float) cacheWidth;
+        glyph->mBitmapMinV = startY / (float) cacheHeight;
+        glyph->mBitmapMaxU = endX / (float) cacheWidth;
+        glyph->mBitmapMaxV = endY / (float) cacheHeight;
 
-    mState->setTextureDirty();
+        mState->setTextureDirty();
+    }
 }
 
 CachedGlyphInfo* Font::cacheGlyph(SkPaint* paint, glyph_t glyph, bool precaching) {
@@ -440,8 +480,8 @@
     mCachedGlyphs.add(glyph, newGlyph);
 
     const SkGlyph& skiaGlyph = GET_METRICS(paint, glyph, &mDescription.mLookupTransform);
-    newGlyph->mGlyphIndex = skiaGlyph.fID;
     newGlyph->mIsValid = false;
+    newGlyph->mGlyphIndex = skiaGlyph.fID;
 
     updateGlyphCache(paint, skiaGlyph, newGlyph, precaching);
 
@@ -452,14 +492,13 @@
     FontDescription description(paint, matrix);
     Font* font = state->mActiveFonts.get(description);
 
-    if (font) {
-        font->mTransform.load(matrix);
-        return font;
+    if (!font) {
+        font = new Font(state, description);
+        state->mActiveFonts.put(description, font);
     }
+    font->mTransform.load(matrix);
 
-    Font* newFont = new Font(state, description);
-    state->mActiveFonts.put(description, newFont);
-    return newFont;
+    return font;
 }
 
 }; // namespace uirenderer
diff --git a/libs/hwui/font/Font.h b/libs/hwui/font/Font.h
index 26b10afd..542b552 100644
--- a/libs/hwui/font/Font.h
+++ b/libs/hwui/font/Font.h
@@ -124,6 +124,9 @@
     void drawCachedGlyph(CachedGlyphInfo* glyph, int x, int y,
             uint8_t *bitmap, uint32_t bitmapW, uint32_t bitmapH,
             Rect* bounds, const float* pos);
+    void drawCachedGlyphPerspective(CachedGlyphInfo* glyph, int x, int y,
+            uint8_t *bitmap, uint32_t bitmapW, uint32_t bitmapH,
+            Rect* bounds, const float* pos);
     void drawCachedGlyphBitmap(CachedGlyphInfo* glyph, int x, int y,
             uint8_t *bitmap, uint32_t bitmapW, uint32_t bitmapH,
             Rect* bounds, const float* pos);
diff --git a/tests/HwAccelerationTest/AndroidManifest.xml b/tests/HwAccelerationTest/AndroidManifest.xml
index 0c59f30..46a539e 100644
--- a/tests/HwAccelerationTest/AndroidManifest.xml
+++ b/tests/HwAccelerationTest/AndroidManifest.xml
@@ -51,6 +51,16 @@
         </activity>
 
         <activity
+                android:name="Rotate3dTextActivity"
+                android:label="Text/3D Rotation"
+                android:theme="@android:style/Theme.Holo.Light">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="com.android.test.hwui.TEST" />
+            </intent-filter>
+        </activity>
+
+        <activity
                 android:name="NoAATextActivity"
                 android:label="Text/Aliased">
             <intent-filter>
diff --git a/tests/HwAccelerationTest/src/com/android/test/hwui/Rotate3dTextActivity.java b/tests/HwAccelerationTest/src/com/android/test/hwui/Rotate3dTextActivity.java
new file mode 100644
index 0000000..93b8705
--- /dev/null
+++ b/tests/HwAccelerationTest/src/com/android/test/hwui/Rotate3dTextActivity.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.test.hwui;
+
+import android.app.Activity;
+import android.content.Context;
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.os.Bundle;
+import android.view.View;
+
+@SuppressWarnings({"UnusedDeclaration"})
+public class Rotate3dTextActivity extends Activity {
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        final Rotate3dTextView view = new Rotate3dTextView(this);
+        setContentView(view);
+    }
+
+    public static class Rotate3dTextView extends View {
+        private static final String TEXT = "Hello libhwui! ";
+
+        private final Paint mPaint;
+
+        public Rotate3dTextView(Context c) {
+            super(c);
+
+            mPaint = new Paint();
+            mPaint.setAntiAlias(true);
+            mPaint.setTextSize(50.0f);
+            mPaint.setTextAlign(Paint.Align.CENTER);
+
+            setRotationY(45.0f);
+            setScaleX(2.0f);
+            setScaleY(2.0f);
+        }
+
+        @Override
+        protected void onDraw(Canvas canvas) {
+            super.onDraw(canvas);
+
+            canvas.drawText(TEXT, getWidth() / 2.0f, getHeight() / 2.0f, mPaint);
+
+            invalidate();
+        }
+    }
+}