Make glyph cache more flexible

Some GPU architectures could not handle the previous implementation
of our glyph cache. Frequent uploads would cause memory problems in the GPU
and eventually a crash due to these memory issues. The solution is to move to
a system of several, smaller caches instead of one monolythic cache for all
glyphs.

Change-Id: I0fc7a323360940d16d5a33eeb33abfab194c5920
diff --git a/libs/hwui/FontRenderer.cpp b/libs/hwui/FontRenderer.cpp
index f04ea6f..29dff04 100644
--- a/libs/hwui/FontRenderer.cpp
+++ b/libs/hwui/FontRenderer.cpp
@@ -24,6 +24,7 @@
 
 #include "Debug.h"
 #include "FontRenderer.h"
+#include "Caches.h"
 
 namespace android {
 namespace uirenderer {
@@ -34,10 +35,28 @@
 
 #define DEFAULT_TEXT_CACHE_WIDTH 1024
 #define DEFAULT_TEXT_CACHE_HEIGHT 256
-
-// We should query these values from the GL context
 #define MAX_TEXT_CACHE_WIDTH 2048
-#define MAX_TEXT_CACHE_HEIGHT 2048
+#define TEXTURE_BORDER_SIZE 2
+
+///////////////////////////////////////////////////////////////////////////////
+// CacheTextureLine
+///////////////////////////////////////////////////////////////////////////////
+
+bool CacheTextureLine::fitBitmap(const SkGlyph& glyph, uint32_t *retOriginX, uint32_t *retOriginY) {
+    if (glyph.fHeight + TEXTURE_BORDER_SIZE > mMaxHeight) {
+        return false;
+    }
+
+    if (mCurrentCol + glyph.fWidth + TEXTURE_BORDER_SIZE < mMaxWidth) {
+        *retOriginX = mCurrentCol + 1;
+        *retOriginY = mCurrentRow + 1;
+        mCurrentCol += glyph.fWidth + TEXTURE_BORDER_SIZE;
+        mDirty = true;
+        return true;
+    }
+
+    return false;
+}
 
 ///////////////////////////////////////////////////////////////////////////////
 // Font
@@ -107,7 +126,7 @@
     mState->appendMeshQuad(nPenX, nPenY, u1, v2,
             nPenX + width, nPenY, u2, v2,
             nPenX + width, nPenY - height, u2, v1,
-            nPenX, nPenY - height, u1, v1);
+            nPenX, nPenY - height, u1, v1, glyph->mCachedTextureLine->mCacheTexture);
 }
 
 void Font::drawCachedGlyph(CachedGlyphInfo* glyph, int x, int y,
@@ -118,8 +137,9 @@
     uint32_t endX = glyph->mStartX + glyph->mBitmapWidth;
     uint32_t endY = glyph->mStartY + glyph->mBitmapHeight;
 
-    uint32_t cacheWidth = mState->getCacheWidth();
-    const uint8_t* cacheBuffer = mState->getTextTextureData();
+    CacheTexture *cacheTexture = glyph->mCachedTextureLine->mCacheTexture;
+    uint32_t cacheWidth = cacheTexture->mWidth;
+    const uint8_t* cacheBuffer = cacheTexture->mTexture;
 
     uint32_t cacheX = 0, cacheY = 0;
     int32_t bX = 0, bY = 0;
@@ -133,10 +153,9 @@
             bitmap[bY * bitmapW + bX] = tempCol;
         }
     }
-
 }
 
-Font::CachedGlyphInfo* Font::getCachedGlyph(SkPaint* paint, glyph_t textUnit) {
+CachedGlyphInfo* Font::getCachedGlyph(SkPaint* paint, glyph_t textUnit) {
     CachedGlyphInfo* cachedGlyph = NULL;
     ssize_t index = mCachedGlyphs.indexOfKey(textUnit);
     if (index >= 0) {
@@ -245,7 +264,7 @@
 
     // Get the bitmap for the glyph
     paint->findImage(skiaGlyph);
-    glyph->mIsValid = mState->cacheBitmap(skiaGlyph, &startX, &startY);
+    mState->cacheBitmap(skiaGlyph, glyph, &startX, &startY);
 
     if (!glyph->mIsValid) {
         return;
@@ -259,8 +278,8 @@
     glyph->mBitmapWidth = skiaGlyph.fWidth;
     glyph->mBitmapHeight = skiaGlyph.fHeight;
 
-    uint32_t cacheWidth = mState->getCacheWidth();
-    uint32_t cacheHeight = mState->getCacheHeight();
+    uint32_t cacheWidth = glyph->mCachedTextureLine->mCacheTexture->mWidth;
+    uint32_t cacheHeight = glyph->mCachedTextureLine->mCacheTexture->mHeight;
 
     glyph->mBitmapMinU = (float) startX / (float) cacheWidth;
     glyph->mBitmapMinV = (float) startY / (float) cacheHeight;
@@ -270,7 +289,7 @@
     mState->mUploadTexture = true;
 }
 
-Font::CachedGlyphInfo* Font::cacheGlyph(SkPaint* paint, glyph_t glyph) {
+CachedGlyphInfo* Font::cacheGlyph(SkPaint* paint, glyph_t glyph) {
     CachedGlyphInfo* newGlyph = new CachedGlyphInfo();
     mCachedGlyphs.add(glyph, newGlyph);
 
@@ -319,25 +338,29 @@
     mInitialized = false;
     mMaxNumberOfQuads = 1024;
     mCurrentQuadIndex = 0;
-    mTextureId = 0;
 
     mTextMeshPtr = NULL;
-    mTextTexture = NULL;
+    mCurrentCacheTexture = NULL;
+    mLastCacheTexture = NULL;
+    mCacheTextureSmall = NULL;
+    mCacheTexture128 = NULL;
+    mCacheTexture256 = NULL;
+    mCacheTexture512 = NULL;
 
     mIndexBufferID = 0;
 
-    mCacheWidth = DEFAULT_TEXT_CACHE_WIDTH;
-    mCacheHeight = DEFAULT_TEXT_CACHE_HEIGHT;
+    mSmallCacheWidth = DEFAULT_TEXT_CACHE_WIDTH;
+    mSmallCacheHeight = DEFAULT_TEXT_CACHE_HEIGHT;
 
     char property[PROPERTY_VALUE_MAX];
     if (property_get(PROPERTY_TEXT_CACHE_WIDTH, property, NULL) > 0) {
         if (sLogFontRendererCreate) {
             INIT_LOGD("  Setting text cache width to %s pixels", property);
         }
-        mCacheWidth = atoi(property);
+        mSmallCacheWidth = atoi(property);
     } else {
         if (sLogFontRendererCreate) {
-            INIT_LOGD("  Using default text cache width of %i pixels", mCacheWidth);
+            INIT_LOGD("  Using default text cache width of %i pixels", mSmallCacheWidth);
         }
     }
 
@@ -345,10 +368,10 @@
         if (sLogFontRendererCreate) {
             INIT_LOGD("  Setting text cache width to %s pixels", property);
         }
-        mCacheHeight = atoi(property);
+        mSmallCacheHeight = atoi(property);
     } else {
         if (sLogFontRendererCreate) {
-            INIT_LOGD("  Using default text cache height of %i pixels", mCacheHeight);
+            INIT_LOGD("  Using default text cache height of %i pixels", mSmallCacheHeight);
         }
     }
 
@@ -363,11 +386,10 @@
 
     if (mInitialized) {
         delete[] mTextMeshPtr;
-        delete[] mTextTexture;
-    }
-
-    if (mTextureId) {
-        glDeleteTextures(1, &mTextureId);
+        delete mCacheTextureSmall;
+        delete mCacheTexture128;
+        delete mCacheTexture256;
+        delete mCacheTexture512;
     }
 
     Vector<Font*> fontsToDereference = mActiveFonts;
@@ -389,20 +411,21 @@
     }
 }
 
-bool FontRenderer::cacheBitmap(const SkGlyph& glyph, uint32_t* retOriginX, uint32_t* retOriginY) {
+uint8_t* FontRenderer::allocateTextureMemory(int width, int height) {
+    uint8_t* textureMemory = new uint8_t[width * height];
+    memset(textureMemory, 0, width * height * sizeof(uint8_t));
+
+    return textureMemory;
+}
+
+void FontRenderer::cacheBitmap(const SkGlyph& glyph, CachedGlyphInfo* cachedGlyph,
+        uint32_t* retOriginX, uint32_t* retOriginY) {
+    cachedGlyph->mIsValid = false;
     // If the glyph is too tall, don't cache it
-    if (glyph.fHeight + 2 > mCacheLines[mCacheLines.size() - 1]->mMaxHeight) {
-        if (mCacheHeight < MAX_TEXT_CACHE_HEIGHT) {
-            // Default cache not large enough for large glyphs - resize cache to
-            // max size and try again
-            flushAllAndInvalidate();
-            initTextTexture(true);
-        }
-        if (glyph.fHeight + 2 > mCacheLines[mCacheLines.size() - 1]->mMaxHeight) {
-            LOGE("Font size to large to fit in cache. width, height = %i, %i",
-                    (int) glyph.fWidth, (int) glyph.fHeight);
-            return false;
-        }
+    if (glyph.fHeight + TEXTURE_BORDER_SIZE > mCacheLines[mCacheLines.size() - 1]->mMaxHeight) {
+        LOGE("Font size to large to fit in cache. width, height = %i, %i",
+                (int) glyph.fWidth, (int) glyph.fHeight);
+        return;
     }
 
     // Now copy the bitmap into the cache texture
@@ -410,9 +433,11 @@
     uint32_t startY = 0;
 
     bool bitmapFit = false;
+    CacheTextureLine *cacheLine;
     for (uint32_t i = 0; i < mCacheLines.size(); i++) {
         bitmapFit = mCacheLines[i]->fitBitmap(glyph, &startX, &startY);
         if (bitmapFit) {
+            cacheLine = mCacheLines[i];
             break;
         }
     }
@@ -425,27 +450,33 @@
         for (uint32_t i = 0; i < mCacheLines.size(); i++) {
             bitmapFit = mCacheLines[i]->fitBitmap(glyph, &startX, &startY);
             if (bitmapFit) {
+                cacheLine = mCacheLines[i];
                 break;
             }
         }
 
         // if we still don't fit, something is wrong and we shouldn't draw
         if (!bitmapFit) {
-            LOGE("Bitmap doesn't fit in cache. width, height = %i, %i",
-                    (int) glyph.fWidth, (int) glyph.fHeight);
-            return false;
+            return;
         }
     }
 
+    cachedGlyph->mCachedTextureLine = cacheLine;
+
     *retOriginX = startX;
     *retOriginY = startY;
 
     uint32_t endX = startX + glyph.fWidth;
     uint32_t endY = startY + glyph.fHeight;
 
-    uint32_t cacheWidth = mCacheWidth;
+    uint32_t cacheWidth = cacheLine->mMaxWidth;
 
-    uint8_t* cacheBuffer = mTextTexture;
+    CacheTexture *cacheTexture = cacheLine->mCacheTexture;
+    if (cacheTexture->mTexture == NULL) {
+        // Large-glyph texture memory is allocated only as needed
+        cacheTexture->mTexture = allocateTextureMemory(cacheTexture->mWidth, cacheTexture->mHeight);
+    }
+    uint8_t* cacheBuffer = cacheTexture->mTexture;
     uint8_t* bitmapBuffer = (uint8_t*) glyph.fImage;
     unsigned int stride = glyph.rowBytes();
 
@@ -456,30 +487,17 @@
             cacheBuffer[cacheY * cacheWidth + cacheX] = mGammaTable[tempCol];
         }
     }
-
-    return true;
+    cachedGlyph->mIsValid = true;
 }
 
-void FontRenderer::initTextTexture(bool largeFonts) {
-    mCacheLines.clear();
-    if (largeFonts) {
-        mCacheWidth = MAX_TEXT_CACHE_WIDTH;
-        mCacheHeight = MAX_TEXT_CACHE_HEIGHT;
-    }
-
-    mTextTexture = new uint8_t[mCacheWidth * mCacheHeight];
-    memset(mTextTexture, 0, mCacheWidth * mCacheHeight * sizeof(uint8_t));
-
-    mUploadTexture = false;
-
-    if (mTextureId != 0) {
-        glDeleteTextures(1, &mTextureId);
-    }
-    glGenTextures(1, &mTextureId);
-    glBindTexture(GL_TEXTURE_2D, mTextureId);
+CacheTexture* FontRenderer::createCacheTexture(int width, int height, bool allocate) {
+    uint8_t* textureMemory = allocate ? allocateTextureMemory(width, height) : NULL;
+    GLuint textureId;
+    glGenTextures(1, &textureId);
+    glBindTexture(GL_TEXTURE_2D, textureId);
     glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
     // Initialize texture dimensions
-    glTexImage2D(GL_TEXTURE_2D, 0, GL_ALPHA, mCacheWidth, mCacheHeight, 0,
+    glTexImage2D(GL_TEXTURE_2D, 0, GL_ALPHA, width, height, 0,
             GL_ALPHA, GL_UNSIGNED_BYTE, 0);
 
     mLinearFiltering = false;
@@ -489,30 +507,55 @@
     glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
     glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
 
-    // Split up our cache texture into lines of certain widths
-    int nextLine = 0;
-    mCacheLines.push(new CacheTextureLine(mCacheWidth, 18, nextLine, 0));
-    nextLine += mCacheLines.top()->mMaxHeight;
-    mCacheLines.push(new CacheTextureLine(mCacheWidth, 26, nextLine, 0));
-    nextLine += mCacheLines.top()->mMaxHeight;
-    mCacheLines.push(new CacheTextureLine(mCacheWidth, 26, nextLine, 0));
-    nextLine += mCacheLines.top()->mMaxHeight;
-    mCacheLines.push(new CacheTextureLine(mCacheWidth, 34, nextLine, 0));
-    nextLine += mCacheLines.top()->mMaxHeight;
-    mCacheLines.push(new CacheTextureLine(mCacheWidth, 34, nextLine, 0));
-    nextLine += mCacheLines.top()->mMaxHeight;
-    mCacheLines.push(new CacheTextureLine(mCacheWidth, 42, nextLine, 0));
-    nextLine += mCacheLines.top()->mMaxHeight;
-    if (largeFonts) {
-        int nextSize = 76;
-        // Make several new lines with increasing font sizes
-        while (nextSize < (int)(mCacheHeight - nextLine - (2 * nextSize))) {
-            mCacheLines.push(new CacheTextureLine(mCacheWidth, nextSize, nextLine, 0));
-            nextLine += mCacheLines.top()->mMaxHeight;
-            nextSize += 50;
-        }
+    return new CacheTexture(textureMemory, textureId, width, height);
+}
+
+void FontRenderer::initTextTexture() {
+    mCacheLines.clear();
+
+    // Next, use other, separate caches for large glyphs.
+    uint16_t maxWidth = 0;
+    if (Caches::hasInstance()) {
+        maxWidth = Caches::getInstance().maxTextureSize;
     }
-    mCacheLines.push(new CacheTextureLine(mCacheWidth, mCacheHeight - nextLine, nextLine, 0));
+    if (maxWidth > MAX_TEXT_CACHE_WIDTH || maxWidth == 0) {
+        maxWidth = MAX_TEXT_CACHE_WIDTH;
+    }
+    if (mCacheTextureSmall != NULL) {
+        delete mCacheTextureSmall;
+        delete mCacheTexture128;
+        delete mCacheTexture256;
+        delete mCacheTexture512;
+    }
+    mCacheTextureSmall = createCacheTexture(mSmallCacheWidth, mSmallCacheHeight, true);
+    mCacheTexture128 = createCacheTexture(maxWidth, 256, false);
+    mCacheTexture256 = createCacheTexture(maxWidth, 256, false);
+    mCacheTexture512 = createCacheTexture(maxWidth, 512, false);
+    mCurrentCacheTexture = mCacheTextureSmall;
+
+    mUploadTexture = false;
+    // Split up our default cache texture into lines of certain widths
+    int nextLine = 0;
+    mCacheLines.push(new CacheTextureLine(mSmallCacheWidth, 18, nextLine, 0, mCacheTextureSmall));
+    nextLine += mCacheLines.top()->mMaxHeight;
+    mCacheLines.push(new CacheTextureLine(mSmallCacheWidth, 26, nextLine, 0, mCacheTextureSmall));
+    nextLine += mCacheLines.top()->mMaxHeight;
+    mCacheLines.push(new CacheTextureLine(mSmallCacheWidth, 26, nextLine, 0, mCacheTextureSmall));
+    nextLine += mCacheLines.top()->mMaxHeight;
+    mCacheLines.push(new CacheTextureLine(mSmallCacheWidth, 34, nextLine, 0, mCacheTextureSmall));
+    nextLine += mCacheLines.top()->mMaxHeight;
+    mCacheLines.push(new CacheTextureLine(mSmallCacheWidth, 34, nextLine, 0, mCacheTextureSmall));
+    nextLine += mCacheLines.top()->mMaxHeight;
+    mCacheLines.push(new CacheTextureLine(mSmallCacheWidth, 42, nextLine, 0, mCacheTextureSmall));
+    nextLine += mCacheLines.top()->mMaxHeight;
+    mCacheLines.push(new CacheTextureLine(mSmallCacheWidth, mSmallCacheHeight - nextLine,
+            nextLine, 0, mCacheTextureSmall));
+
+    //  The first cache is split into 2 lines of height 128, the rest have just one cache line.
+    mCacheLines.push(new CacheTextureLine(maxWidth, 128, 0, 0, mCacheTexture128));
+    mCacheLines.push(new CacheTextureLine(maxWidth, 128, 128, 0, mCacheTexture128));
+    mCacheLines.push(new CacheTextureLine(maxWidth, 256, 0, 0, mCacheTexture256));
+    mCacheLines.push(new CacheTextureLine(maxWidth, 512, 0, 0, mCacheTexture512));
 }
 
 // Avoid having to reallocate memory and render quad by quad
@@ -568,22 +611,22 @@
 }
 
 void FontRenderer::checkTextureUpdate() {
-    if (!mUploadTexture) {
+    if (!mUploadTexture && mLastCacheTexture == mCurrentCacheTexture) {
         return;
     }
 
-    glBindTexture(GL_TEXTURE_2D, mTextureId);
-
     // Iterate over all the cache lines and see which ones need to be updated
     for (uint32_t i = 0; i < mCacheLines.size(); i++) {
         CacheTextureLine* cl = mCacheLines[i];
-        if (cl->mDirty) {
+        if (cl->mDirty && cl->mCacheTexture->mTexture != NULL) {
+            CacheTexture* cacheTexture = cl->mCacheTexture;
             uint32_t xOffset = 0;
             uint32_t yOffset = cl->mCurrentRow;
-            uint32_t width   = mCacheWidth;
+            uint32_t width   = cl->mMaxWidth;
             uint32_t height  = cl->mMaxHeight;
-            void* textureData = mTextTexture + yOffset * width;
+            void* textureData = cacheTexture->mTexture + (yOffset * width);
 
+            glBindTexture(GL_TEXTURE_2D, cacheTexture->mTextureId);
             glTexSubImage2D(GL_TEXTURE_2D, 0, xOffset, yOffset, width, height,
                     GL_ALPHA, GL_UNSIGNED_BYTE, textureData);
 
@@ -591,6 +634,9 @@
         }
     }
 
+    glBindTexture(GL_TEXTURE_2D, mCurrentCacheTexture->mTextureId);
+    mLastCacheTexture = mCurrentCacheTexture;
+
     mUploadTexture = false;
 }
 
@@ -606,12 +652,21 @@
 void FontRenderer::appendMeshQuad(float x1, float y1, float u1, float v1,
         float x2, float y2, float u2, float v2,
         float x3, float y3, float u3, float v3,
-        float x4, float y4, float u4, float v4) {
+        float x4, float y4, float u4, float v4, CacheTexture* texture) {
 
     if (mClip &&
             (x1 > mClip->right || y1 < mClip->top || x2 < mClip->left || y4 > mClip->bottom)) {
         return;
     }
+    if (texture != mCurrentCacheTexture) {
+        if (mCurrentQuadIndex != 0) {
+            // First, draw everything stored already which uses the previous texture
+            issueDrawCommand();
+            mCurrentQuadIndex = 0;
+        }
+        // Now use the new texture id
+        mCurrentCacheTexture = texture;
+    }
 
     const uint32_t vertsPerQuad = 4;
     const uint32_t floatsPerVert = 4;
diff --git a/libs/hwui/FontRenderer.h b/libs/hwui/FontRenderer.h
index f945873..cac915d 100644
--- a/libs/hwui/FontRenderer.h
+++ b/libs/hwui/FontRenderer.h
@@ -55,6 +55,77 @@
 
 class FontRenderer;
 
+class CacheTexture {
+public:
+    CacheTexture(){}
+    CacheTexture(uint8_t* texture, GLuint textureId, uint16_t width, uint16_t height) :
+        mTexture(texture), mTextureId(textureId), mWidth(width), mHeight(height) {}
+    ~CacheTexture() {
+        if (mTexture != NULL) {
+            delete[] mTexture;
+        }
+        if (mTextureId != 0) {
+            glDeleteTextures(1, &mTextureId);
+        }
+    }
+
+    uint8_t* mTexture;
+    GLuint mTextureId;
+    uint16_t mWidth;
+    uint16_t mHeight;
+};
+
+class CacheTextureLine {
+public:
+    CacheTextureLine(uint16_t maxWidth, uint16_t maxHeight, uint32_t currentRow,
+            uint32_t currentCol, CacheTexture* cacheTexture):
+                mMaxHeight(maxHeight),
+                mMaxWidth(maxWidth),
+                mCurrentRow(currentRow),
+                mCurrentCol(currentCol),
+                mDirty(false),
+                mCacheTexture(cacheTexture){
+    }
+
+    bool fitBitmap(const SkGlyph& glyph, uint32_t *retOriginX, uint32_t *retOriginY);
+
+    uint16_t mMaxHeight;
+    uint16_t mMaxWidth;
+    uint32_t mCurrentRow;
+    uint32_t mCurrentCol;
+    bool mDirty;
+    CacheTexture *mCacheTexture;
+};
+
+struct CachedGlyphInfo {
+    // Has the cache been invalidated?
+    bool mIsValid;
+    // Location of the cached glyph in the bitmap
+    // in case we need to resize the texture or
+    // render to bitmap
+    uint32_t mStartX;
+    uint32_t mStartY;
+    uint32_t mBitmapWidth;
+    uint32_t mBitmapHeight;
+    // Also cache texture coords for the quad
+    float mBitmapMinU;
+    float mBitmapMinV;
+    float mBitmapMaxU;
+    float mBitmapMaxV;
+    // Minimize how much we call freetype
+    uint32_t mGlyphIndex;
+    uint32_t mAdvanceX;
+    uint32_t mAdvanceY;
+    // Values below contain a glyph's origin in the bitmap
+    int32_t mBitmapLeft;
+    int32_t mBitmapTop;
+    // Auto-kerning
+    SkFixed mLsbDelta;
+    SkFixed mRsbDelta;
+    CacheTextureLine* mCachedTextureLine;
+};
+
+
 ///////////////////////////////////////////////////////////////////////////////
 // Font
 ///////////////////////////////////////////////////////////////////////////////
@@ -101,33 +172,6 @@
     void measure(SkPaint* paint, const char* text, uint32_t start, uint32_t len,
             int numGlyphs, Rect *bounds);
 
-    struct CachedGlyphInfo {
-        // Has the cache been invalidated?
-        bool mIsValid;
-        // Location of the cached glyph in the bitmap
-        // in case we need to resize the texture or
-        // render to bitmap
-        uint32_t mStartX;
-        uint32_t mStartY;
-        uint32_t mBitmapWidth;
-        uint32_t mBitmapHeight;
-        // Also cache texture coords for the quad
-        float mBitmapMinU;
-        float mBitmapMinV;
-        float mBitmapMaxU;
-        float mBitmapMaxV;
-        // Minimize how much we call freetype
-        uint32_t mGlyphIndex;
-        uint32_t mAdvanceX;
-        uint32_t mAdvanceY;
-        // Values below contain a glyph's origin in the bitmap
-        int32_t mBitmapLeft;
-        int32_t mBitmapTop;
-        // Auto-kerning
-        SkFixed mLsbDelta;
-        SkFixed mRsbDelta;
-    };
-
     Font(FontRenderer* state, uint32_t fontId, float fontSize, int flags, uint32_t italicStyle,
             uint32_t scaleX, SkPaint::Style style, uint32_t strokeWidth);
 
@@ -218,19 +262,28 @@
             mLinearFiltering = linearFiltering;
             const GLenum filtering = linearFiltering ? GL_LINEAR : GL_NEAREST;
 
-            glBindTexture(GL_TEXTURE_2D, mTextureId);
+            glBindTexture(GL_TEXTURE_2D, mCurrentCacheTexture->mTextureId);
             glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, filtering);
             glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, filtering);
         }
-        return mTextureId;
+        return mCurrentCacheTexture->mTextureId;
     }
 
-    uint32_t getCacheWidth() const {
-        return mCacheWidth;
-    }
-
-    uint32_t getCacheHeight() const {
-        return mCacheHeight;
+    uint32_t getCacheSize() const {
+        uint32_t size = 0;
+        if (mCacheTextureSmall != NULL && mCacheTextureSmall->mTexture != NULL) {
+            size += mCacheTextureSmall->mWidth * mCacheTextureSmall->mHeight;
+        }
+        if (mCacheTexture128 != NULL && mCacheTexture128->mTexture != NULL) {
+            size += mCacheTexture128->mWidth * mCacheTexture128->mHeight;
+        }
+        if (mCacheTexture256 != NULL && mCacheTexture256->mTexture != NULL) {
+            size += mCacheTexture256->mWidth * mCacheTexture256->mHeight;
+        }
+        if (mCacheTexture512 != NULL && mCacheTexture512->mTexture != NULL) {
+            size += mCacheTexture512->mWidth * mCacheTexture512->mHeight;
+        }
+        return size;
     }
 
 protected:
@@ -238,41 +291,11 @@
 
     const uint8_t* mGammaTable;
 
-    struct CacheTextureLine {
-        uint16_t mMaxHeight;
-        uint16_t mMaxWidth;
-        uint32_t mCurrentRow;
-        uint32_t mCurrentCol;
-        bool mDirty;
-
-        CacheTextureLine(uint16_t maxWidth, uint16_t maxHeight, uint32_t currentRow,
-                uint32_t currentCol):
-                    mMaxHeight(maxHeight),
-                    mMaxWidth(maxWidth),
-                    mCurrentRow(currentRow),
-                    mCurrentCol(currentCol),
-                    mDirty(false) {
-        }
-
-        bool fitBitmap(const SkGlyph& glyph, uint32_t *retOriginX, uint32_t *retOriginY) {
-            if (glyph.fHeight + 2 > mMaxHeight) {
-                return false;
-            }
-
-            if (mCurrentCol + glyph.fWidth + 2 < mMaxWidth) {
-                *retOriginX = mCurrentCol + 1;
-                *retOriginY = mCurrentRow + 1;
-                mCurrentCol += glyph.fWidth + 2;
-                mDirty = true;
-                return true;
-            }
-
-            return false;
-        }
-    };
-
-    void initTextTexture(bool largeFonts = false);
-    bool cacheBitmap(const SkGlyph& glyph, uint32_t *retOriginX, uint32_t *retOriginY);
+    uint8_t* allocateTextureMemory(int width, int height);
+    void initTextTexture();
+    CacheTexture *createCacheTexture(int width, int height, bool allocate);
+    void cacheBitmap(const SkGlyph& glyph, CachedGlyphInfo* cachedGlyph,
+            uint32_t *retOriginX, uint32_t *retOriginY);
 
     void flushAllAndInvalidate();
     void initVertexArrayBuffers();
@@ -286,10 +309,10 @@
     void appendMeshQuad(float x1, float y1, float u1, float v1,
             float x2, float y2, float u2, float v2,
             float x3, float y3, float u3, float v3,
-            float x4, float y4, float u4, float v4);
+            float x4, float y4, float u4, float v4, CacheTexture* texture);
 
-    uint32_t mCacheWidth;
-    uint32_t mCacheHeight;
+    uint32_t mSmallCacheWidth;
+    uint32_t mSmallCacheHeight;
 
     Vector<CacheTextureLine*> mCacheLines;
     uint32_t getRemainingCacheCapacity();
@@ -297,12 +320,14 @@
     Font* mCurrentFont;
     Vector<Font*> mActiveFonts;
 
-    // Texture to cache glyph bitmaps
-    uint8_t* mTextTexture;
-    const uint8_t* getTextTextureData() const {
-        return mTextTexture;
-    }
-    GLuint mTextureId;
+    CacheTexture* mCurrentCacheTexture;
+    CacheTexture* mLastCacheTexture;
+    CacheTexture* mCacheTextureSmall;
+    CacheTexture* mCacheTexture128;
+    CacheTexture* mCacheTexture256;
+    CacheTexture* mCacheTexture512;
+
+
     void checkTextureUpdate();
     bool mUploadTexture;
 
diff --git a/libs/hwui/GammaFontRenderer.h b/libs/hwui/GammaFontRenderer.h
index 54c208e..99f08f0 100644
--- a/libs/hwui/GammaFontRenderer.h
+++ b/libs/hwui/GammaFontRenderer.h
@@ -50,7 +50,7 @@
         FontRenderer* renderer = mRenderers[fontRenderer];
         if (!renderer) return 0;
 
-        return renderer->getCacheHeight() * renderer->getCacheWidth();
+        return renderer->getCacheSize();
     }
 
 private: