Reduce number of glDraw calls when drawing text

This change moves the mesh buffer from FontRenderer to CacheTexture
to help reduce the number of texture binds and glDraw calls when
drawing text that spans across multiple textures.

Change-Id: I7de574d88313ca3672879ca878c253ff5f131fc1
diff --git a/libs/hwui/FontRenderer.cpp b/libs/hwui/FontRenderer.cpp
index 0c70e27..009dcb9 100644
--- a/libs/hwui/FontRenderer.cpp
+++ b/libs/hwui/FontRenderer.cpp
@@ -55,10 +55,7 @@
     mGammaTable = NULL;
     mInitialized = false;
     mMaxNumberOfQuads = 1024;
-    mCurrentQuadIndex = 0;
-    mLastQuadIndex = 0;
 
-    mTextMesh = NULL;
     mCurrentCacheTexture = NULL;
 
     mLinearFiltering = false;
@@ -114,8 +111,6 @@
         // Unbinding the buffer shouldn't be necessary but it crashes with some drivers
         Caches::getInstance().unbindIndicesBuffer();
         glDeleteBuffers(1, &mIndexBufferID);
-
-        delete[] mTextMesh;
     }
 
     LruCache<Font::FontDescription, Font*>::Iterator it(mActiveFonts);
@@ -126,9 +121,7 @@
 }
 
 void FontRenderer::flushAllAndInvalidate() {
-    if (mCurrentQuadIndex != 0) {
-        issueDrawCommand();
-    }
+    issueDrawCommand();
 
     LruCache<Font::FontDescription, Font*>::Iterator it(mActiveFonts);
     while (it.next()) {
@@ -236,6 +229,9 @@
         // Large-glyph texture memory is allocated only as needed
         cacheTexture->allocateTexture();
     }
+    if (!cacheTexture->mesh()) {
+        cacheTexture->allocateMesh();
+    }
 
     // Tells us whether the glyphs is B&W (1 bit per pixel)
     // or anti-aliased (8 bits per pixel)
@@ -307,11 +303,12 @@
 }
 
 CacheTexture* FontRenderer::createCacheTexture(int width, int height, bool allocate) {
-    CacheTexture* cacheTexture = new CacheTexture(width, height);
+    CacheTexture* cacheTexture = new CacheTexture(width, height, mMaxNumberOfQuads);
 
     if (allocate) {
         Caches::getInstance().activeTexture(0);
         cacheTexture->allocateTexture();
+        cacheTexture->allocateMesh();
     }
 
     return cacheTexture;
@@ -356,12 +353,6 @@
     glBufferData(GL_ELEMENT_ARRAY_BUFFER, indexBufferSizeBytes, indexBufferData, GL_STATIC_DRAW);
 
     free(indexBufferData);
-
-    uint32_t coordSize = 2;
-    uint32_t uvSize = 2;
-    uint32_t vertsPerQuad = 4;
-    uint32_t vertexBufferSize = mMaxNumberOfQuads * vertsPerQuad * coordSize * uvSize;
-    mTextMesh = new float[vertexBufferSize];
 }
 
 // We don't want to allocate anything unless we actually draw text
@@ -376,15 +367,6 @@
     mInitialized = true;
 }
 
-void FontRenderer::updateDrawParams() {
-    if (mCurrentQuadIndex != mLastQuadIndex) {
-        uint16_t* offset = (uint16_t*)(mLastQuadIndex * sizeof(uint16_t) * 6);
-        uint32_t count = mCurrentQuadIndex - mLastQuadIndex;
-        mDrawBatch.add(TextBatch(offset, count, mCurrentCacheTexture));
-        mLastQuadIndex = mCurrentQuadIndex;
-    }
-}
-
 void FontRenderer::checkTextureUpdate() {
     if (!mUploadTexture) {
         return;
@@ -424,77 +406,58 @@
 }
 
 void FontRenderer::issueDrawCommand() {
-    updateDrawParams();
-    checkTextureUpdate();
+    bool first = true;
+    bool force = false;
 
-    Caches& caches = Caches::getInstance();
-    caches.bindIndicesBuffer(mIndexBufferID);
-    if (!mDrawn) {
-        float* buffer = mTextMesh;
-        int offset = 2;
-
-        bool force = caches.unbindMeshBuffer();
-        caches.bindPositionVertexPointer(force, buffer);
-        caches.bindTexCoordsVertexPointer(force, buffer + offset);
-    }
-
-    caches.activeTexture(0);
     GLuint lastId = 0;
+    Caches& caches = Caches::getInstance();
 
-    for (uint32_t i = 0; i < mDrawBatch.size(); i++) {
-        const TextBatch& batch = mDrawBatch[i];
+    for (uint32_t i = 0; i < mCacheTextures.size(); i++) {
+        CacheTexture* texture = mCacheTextures[i];
+        if (texture->canDraw()) {
+            if (first) {
+                checkTextureUpdate();
+                caches.bindIndicesBuffer(mIndexBufferID);
 
-        GLuint id = batch.texture->getTextureId();
-        if (id != lastId) {
-            glBindTexture(GL_TEXTURE_2D, id);
-            batch.texture->setLinearFiltering(mLinearFiltering, false);
-            lastId = id;
+                if (!mDrawn) {
+                    // If returns true, a VBO was bound and we must
+                    // rebind our vertex attrib pointers even if
+                    // they have the same values as the current pointers
+                    force = caches.unbindMeshBuffer();
+                }
+
+                caches.activeTexture(0);
+                first = false;
+            }
+
+            glBindTexture(GL_TEXTURE_2D, texture->getTextureId());
+            texture->setLinearFiltering(mLinearFiltering, false);
+
+            TextureVertex* mesh = texture->mesh();
+            caches.bindPositionVertexPointer(force, &mesh[0].position[0]);
+            caches.bindTexCoordsVertexPointer(force, &mesh[0].texture[0]);
+            force = false;
+
+            glDrawElements(GL_TRIANGLES, texture->meshElementCount(),
+                    GL_UNSIGNED_SHORT, texture->indices());
+
+            texture->resetMesh();
         }
-
-        glDrawElements(GL_TRIANGLES, batch.count * 6, GL_UNSIGNED_SHORT, batch.offset);
     }
 
     mDrawn = true;
-
-    mCurrentQuadIndex = 0;
-    mLastQuadIndex = 0;
-    mDrawBatch.clear();
 }
 
 void FontRenderer::appendMeshQuadNoClip(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, CacheTexture* texture) {
     if (texture != mCurrentCacheTexture) {
-        updateDrawParams();
         // Now use the new texture id
         mCurrentCacheTexture = texture;
     }
 
-    const uint32_t vertsPerQuad = 4;
-    const uint32_t floatsPerVert = 4;
-    float* currentPos = mTextMesh + mCurrentQuadIndex * vertsPerQuad * floatsPerVert;
-
-    (*currentPos++) = x1;
-    (*currentPos++) = y1;
-    (*currentPos++) = u1;
-    (*currentPos++) = v1;
-
-    (*currentPos++) = x2;
-    (*currentPos++) = y2;
-    (*currentPos++) = u2;
-    (*currentPos++) = v2;
-
-    (*currentPos++) = x3;
-    (*currentPos++) = y3;
-    (*currentPos++) = u3;
-    (*currentPos++) = v3;
-
-    (*currentPos++) = x4;
-    (*currentPos++) = y4;
-    (*currentPos++) = u4;
-    (*currentPos++) = v4;
-
-    mCurrentQuadIndex++;
+    mCurrentCacheTexture->addQuad(x1, y1, u1, v1, x2, y2, u2, v2,
+            x3, y3, u3, v3, x4, y4, u4, v4);
 }
 
 void FontRenderer::appendMeshQuad(float x1, float y1, float u1, float v1,
@@ -515,7 +478,7 @@
         mBounds->bottom = fmax(mBounds->bottom, y1);
     }
 
-    if (mCurrentQuadIndex == mMaxNumberOfQuads) {
+    if (mCurrentCacheTexture->endOfMesh()) {
         issueDrawCommand();
     }
 }
@@ -533,7 +496,7 @@
         mBounds->bottom = fmax(mBounds->bottom, fmax(y1, fmax(y2, fmax(y3, y4))));
     }
 
-    if (mCurrentQuadIndex == mMaxNumberOfQuads) {
+    if (mCurrentCacheTexture->endOfMesh()) {
         issueDrawCommand();
     }
 }
@@ -610,9 +573,7 @@
     mBounds = NULL;
     mClip = NULL;
 
-    if (mCurrentQuadIndex != 0) {
-        issueDrawCommand();
-    }
+    issueDrawCommand();
 }
 
 void FontRenderer::precache(SkPaint* paint, const char* text, int numGlyphs, const mat4& matrix) {
diff --git a/libs/hwui/FontRenderer.h b/libs/hwui/FontRenderer.h
index 442f4e2..080cc71 100644
--- a/libs/hwui/FontRenderer.h
+++ b/libs/hwui/FontRenderer.h
@@ -144,7 +144,6 @@
 
     void removeFont(const Font* font);
 
-    void updateDrawParams();
     void checkTextureUpdate();
 
     void setTextureDirty() {
@@ -165,12 +164,7 @@
 
     bool mUploadTexture;
 
-    // Pointer to vertex data to speed up frame to frame work
-    float* mTextMesh;
-    uint32_t mCurrentQuadIndex;
-    uint32_t mLastQuadIndex;
     uint32_t mMaxNumberOfQuads;
-
     uint32_t mIndexBufferID;
 
     const Rect* mClip;
@@ -181,33 +175,6 @@
 
     bool mLinearFiltering;
 
-    struct TextBatch {
-        TextBatch(): offset(NULL), count(0), texture(NULL) {
-        }
-
-        TextBatch(uint16_t* offset, uint32_t count, CacheTexture* texture):
-                offset(offset), count(count), texture(texture) {
-        }
-
-        static int compare(const TextBatch& lhs, const TextBatch& rhs) {
-            return lhs.texture->getTextureId() - rhs.texture->getTextureId();
-        }
-
-        friend inline int strictly_order_type(const TextBatch& lhs, const TextBatch& rhs) {
-            return compare(lhs, rhs) < 0;
-        }
-
-        friend inline int compare_type(const TextBatch& lhs, const TextBatch& rhs) {
-            return compare(lhs, rhs);
-        }
-
-        uint16_t* offset;
-        uint32_t count;
-        CacheTexture* texture;
-    };
-
-    SortedList<TextBatch> mDrawBatch;
-
     // RS constructs
     sp<RSC::RS> mRs;
     sp<const RSC::Element> mRsElement;
diff --git a/libs/hwui/font/CacheTexture.cpp b/libs/hwui/font/CacheTexture.cpp
index 24b0523..1096642 100644
--- a/libs/hwui/font/CacheTexture.cpp
+++ b/libs/hwui/font/CacheTexture.cpp
@@ -106,6 +106,84 @@
 // CacheTexture
 ///////////////////////////////////////////////////////////////////////////////
 
+CacheTexture::CacheTexture(uint16_t width, uint16_t height, uint32_t maxQuadCount) :
+            mTexture(NULL), mTextureId(0), mWidth(width), mHeight(height),
+            mLinearFiltering(false), mDirty(false), mNumGlyphs(0),
+            mMesh(NULL), mCurrentQuad(0), mMaxQuadCount(maxQuadCount) {
+    mCacheBlocks = new CacheBlock(TEXTURE_BORDER_SIZE, TEXTURE_BORDER_SIZE,
+            mWidth - TEXTURE_BORDER_SIZE, mHeight - TEXTURE_BORDER_SIZE, true);
+}
+
+CacheTexture::~CacheTexture() {
+    releaseMesh();
+    releaseTexture();
+    reset();
+}
+
+void CacheTexture::reset() {
+    // Delete existing cache blocks
+    while (mCacheBlocks != NULL) {
+        CacheBlock* tmpBlock = mCacheBlocks;
+        mCacheBlocks = mCacheBlocks->mNext;
+        delete tmpBlock;
+    }
+    mNumGlyphs = 0;
+    mCurrentQuad = 0;
+}
+
+void CacheTexture::init() {
+    // reset, then create a new remainder space to start again
+    reset();
+    mCacheBlocks = new CacheBlock(TEXTURE_BORDER_SIZE, TEXTURE_BORDER_SIZE,
+            mWidth - TEXTURE_BORDER_SIZE, mHeight - TEXTURE_BORDER_SIZE, true);
+}
+
+void CacheTexture::releaseMesh() {
+    delete[] mMesh;
+}
+
+void CacheTexture::releaseTexture() {
+    if (mTexture) {
+        delete[] mTexture;
+        mTexture = NULL;
+    }
+    if (mTextureId) {
+        glDeleteTextures(1, &mTextureId);
+        mTextureId = 0;
+    }
+    mDirty = false;
+    mCurrentQuad = 0;
+}
+
+void CacheTexture::allocateMesh() {
+    if (!mMesh) {
+        mMesh = new TextureVertex[mMaxQuadCount * 4];
+    }
+}
+
+void CacheTexture::allocateTexture() {
+    if (!mTexture) {
+        mTexture = new uint8_t[mWidth * mHeight];
+    }
+
+    if (!mTextureId) {
+        glGenTextures(1, &mTextureId);
+
+        glBindTexture(GL_TEXTURE_2D, mTextureId);
+        glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
+        // Initialize texture dimensions
+        glTexImage2D(GL_TEXTURE_2D, 0, GL_ALPHA, mWidth, mHeight, 0,
+                GL_ALPHA, GL_UNSIGNED_BYTE, 0);
+
+        const GLenum filtering = getLinearFiltering() ? GL_LINEAR : GL_NEAREST;
+        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, filtering);
+        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, filtering);
+
+        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
+        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
+    }
+}
+
 bool CacheTexture::fitBitmap(const SkGlyph& glyph, uint32_t* retOriginX, uint32_t* retOriginY) {
     if (glyph.fHeight + TEXTURE_BORDER_SIZE * 2 > mHeight) {
         return false;
diff --git a/libs/hwui/font/CacheTexture.h b/libs/hwui/font/CacheTexture.h
index fdd1623..5742941 100644
--- a/libs/hwui/font/CacheTexture.h
+++ b/libs/hwui/font/CacheTexture.h
@@ -24,7 +24,8 @@
 #include <utils/Log.h>
 
 #include "FontUtil.h"
-#include "Rect.h"
+#include "../Rect.h"
+#include "../Vertex.h"
 
 namespace android {
 namespace uirenderer {
@@ -55,14 +56,14 @@
     }
 
     static CacheBlock* insertBlock(CacheBlock* head, CacheBlock* newBlock);
-
     static CacheBlock* removeBlock(CacheBlock* head, CacheBlock* blockToRemove);
 
     void output() {
         CacheBlock* currBlock = this;
         while (currBlock) {
             ALOGD("Block: this, x, y, w, h = %p, %d, %d, %d, %d",
-                    currBlock, currBlock->mX, currBlock->mY, currBlock->mWidth, currBlock->mHeight);
+                    currBlock, currBlock->mX, currBlock->mY,
+                    currBlock->mWidth, currBlock->mHeight);
             currBlock = currBlock->mNext;
         }
     }
@@ -70,72 +71,17 @@
 
 class CacheTexture {
 public:
-    CacheTexture(uint16_t width, uint16_t height) :
-            mTexture(NULL), mTextureId(0), mWidth(width), mHeight(height),
-            mLinearFiltering(false), mDirty(false), mNumGlyphs(0) {
-        mCacheBlocks = new CacheBlock(TEXTURE_BORDER_SIZE, TEXTURE_BORDER_SIZE,
-                mWidth - TEXTURE_BORDER_SIZE, mHeight - TEXTURE_BORDER_SIZE, true);
-    }
+    CacheTexture(uint16_t width, uint16_t height, uint32_t maxQuadCount);
+    ~CacheTexture();
 
-    ~CacheTexture() {
-        releaseTexture();
-        reset();
-    }
+    void reset();
+    void init();
 
-    void reset() {
-        // Delete existing cache blocks
-        while (mCacheBlocks != NULL) {
-            CacheBlock* tmpBlock = mCacheBlocks;
-            mCacheBlocks = mCacheBlocks->mNext;
-            delete tmpBlock;
-        }
-        mNumGlyphs = 0;
-    }
+    void releaseMesh();
+    void releaseTexture();
 
-    void init() {
-        // reset, then create a new remainder space to start again
-        reset();
-        mCacheBlocks = new CacheBlock(TEXTURE_BORDER_SIZE, TEXTURE_BORDER_SIZE,
-                mWidth - TEXTURE_BORDER_SIZE, mHeight - TEXTURE_BORDER_SIZE, true);
-    }
-
-    void releaseTexture() {
-        if (mTexture) {
-            delete[] mTexture;
-            mTexture = NULL;
-        }
-        if (mTextureId) {
-            glDeleteTextures(1, &mTextureId);
-            mTextureId = 0;
-        }
-        mDirty = false;
-    }
-
-    /**
-     * This method assumes that the proper texture unit is active.
-     */
-    void allocateTexture() {
-        if (!mTexture) {
-            mTexture = new uint8_t[mWidth * mHeight];
-        }
-
-        if (!mTextureId) {
-            glGenTextures(1, &mTextureId);
-
-            glBindTexture(GL_TEXTURE_2D, mTextureId);
-            glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
-            // Initialize texture dimensions
-            glTexImage2D(GL_TEXTURE_2D, 0, GL_ALPHA, mWidth, mHeight, 0,
-                    GL_ALPHA, GL_UNSIGNED_BYTE, 0);
-
-            const GLenum filtering = getLinearFiltering() ? GL_LINEAR : GL_NEAREST;
-            glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, filtering);
-            glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, filtering);
-
-            glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
-            glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
-        }
-    }
+    void allocateTexture();
+    void allocateMesh();
 
     bool fitBitmap(const SkGlyph& glyph, uint32_t* retOriginX, uint32_t* retOriginY);
 
@@ -193,6 +139,42 @@
         return mNumGlyphs;
     }
 
+    TextureVertex* mesh() const {
+        return mMesh;
+    }
+
+    uint32_t meshElementCount() const {
+        return mCurrentQuad * 6;
+    }
+
+    uint16_t* indices() const {
+        return (uint16_t*) 0;
+    }
+
+    void resetMesh() {
+        mCurrentQuad = 0;
+    }
+
+    inline void addQuad(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) {
+        TextureVertex* mesh = mMesh + mCurrentQuad * 4;
+        TextureVertex::set(mesh++, x1, y1, u1, v1);
+        TextureVertex::set(mesh++, x2, y2, u2, v2);
+        TextureVertex::set(mesh++, x3, y3, u3, v3);
+        TextureVertex::set(mesh++, x4, y4, u4, v4);
+        mCurrentQuad++;
+    }
+
+    bool canDraw() const {
+        return mCurrentQuad > 0;
+    }
+
+    bool endOfMesh() const {
+        return mCurrentQuad == mMaxQuadCount;
+    }
+
 private:
     uint8_t* mTexture;
     GLuint mTextureId;
@@ -201,6 +183,9 @@
     bool mLinearFiltering;
     bool mDirty;
     uint16_t mNumGlyphs;
+    TextureVertex* mMesh;
+    uint32_t mCurrentQuad;
+    uint32_t mMaxQuadCount;
     CacheBlock* mCacheBlocks;
     Rect mDirtyRect;
 };