Loading libs/hwui/FontRenderer.cpp +73 −147 Original line number Diff line number Diff line Loading @@ -109,11 +109,11 @@ CacheBlock* CacheBlock::removeBlock(CacheBlock* head, CacheBlock *blockToRemove) } /////////////////////////////////////////////////////////////////////////////// // CacheTextureLine // CacheTexture /////////////////////////////////////////////////////////////////////////////// bool CacheTextureLine::fitBitmap(const SkGlyph& glyph, uint32_t *retOriginX, uint32_t *retOriginY) { if (glyph.fHeight + TEXTURE_BORDER_SIZE > mMaxHeight) { bool CacheTexture::fitBitmap(const SkGlyph& glyph, uint32_t *retOriginX, uint32_t *retOriginY) { if (glyph.fHeight + TEXTURE_BORDER_SIZE > mHeight) { return false; } Loading @@ -138,7 +138,7 @@ bool CacheTextureLine::fitBitmap(const SkGlyph& glyph, uint32_t *retOriginX, uin roundedUpW = glyphW; } *retOriginX = cacheBlock->mX; *retOriginY = mCurrentRow + cacheBlock->mY; *retOriginY = cacheBlock->mY; // If this is the remainder space, create a new cache block for this column. Otherwise, // adjust the info about this column. if (cacheBlock->mY == TEXTURE_BORDER_SIZE) { Loading @@ -146,10 +146,10 @@ bool CacheTextureLine::fitBitmap(const SkGlyph& glyph, uint32_t *retOriginX, uin // Adjust remainder space dimensions cacheBlock->mWidth -= roundedUpW; cacheBlock->mX += roundedUpW; if (mMaxHeight - glyphH >= glyphH) { if (mHeight - glyphH >= glyphH) { // There's enough height left over to create a new CacheBlock CacheBlock *newBlock = new CacheBlock(oldX, glyphH, roundedUpW, mMaxHeight - glyphH); mHeight - glyphH); #if DEBUG_FONT_RENDERER ALOGD("fitBitmap: Created new block: this, x, y, w, h = %p, %d, %d, %d, %d", newBlock, newBlock->mX, newBlock->mY, Loading Loading @@ -213,10 +213,10 @@ Font::~Font() { } } void Font::invalidateTextureCache(CacheTextureLine *cacheLine) { void Font::invalidateTextureCache(CacheTexture *cacheTexture) { for (uint32_t i = 0; i < mCachedGlyphs.size(); i++) { CachedGlyphInfo* cachedGlyph = mCachedGlyphs.valueAt(i); if (cacheLine == NULL || cachedGlyph->mCachedTextureLine == cacheLine) { if (cacheTexture == NULL || cachedGlyph->mCacheTexture == cacheTexture) { cachedGlyph->mIsValid = false; } } Loading Loading @@ -260,7 +260,7 @@ void Font::drawCachedGlyph(CachedGlyphInfo* glyph, int x, int y, mState->appendMeshQuad(nPenX, nPenY, u1, v2, nPenX + width, nPenY, u2, v2, nPenX + width, nPenY - height, u2, v1, nPenX, nPenY - height, u1, v1, glyph->mCachedTextureLine->mCacheTexture); nPenX, nPenY - height, u1, v1, glyph->mCacheTexture); } void Font::drawCachedGlyphBitmap(CachedGlyphInfo* glyph, int x, int y, Loading @@ -271,7 +271,7 @@ void Font::drawCachedGlyphBitmap(CachedGlyphInfo* glyph, int x, int y, uint32_t endX = glyph->mStartX + glyph->mBitmapWidth; uint32_t endY = glyph->mStartY + glyph->mBitmapHeight; CacheTexture *cacheTexture = glyph->mCachedTextureLine->mCacheTexture; CacheTexture *cacheTexture = glyph->mCacheTexture; uint32_t cacheWidth = cacheTexture->mWidth; const uint8_t* cacheBuffer = cacheTexture->mTexture; Loading Loading @@ -325,7 +325,7 @@ void Font::drawCachedGlyph(CachedGlyphInfo* glyph, float x, float hOffset, float position->fY + destination[2].fY, u2, v1, position->fX + destination[3].fX, position->fY + destination[3].fY, u1, v1, glyph->mCachedTextureLine->mCacheTexture); glyph->mCacheTexture); } CachedGlyphInfo* Font::getCachedGlyph(SkPaint* paint, glyph_t textUnit) { Loading Loading @@ -556,8 +556,8 @@ void Font::updateGlyphCache(SkPaint* paint, const SkGlyph& skiaGlyph, CachedGlyp glyph->mBitmapWidth = skiaGlyph.fWidth; glyph->mBitmapHeight = skiaGlyph.fHeight; uint32_t cacheWidth = glyph->mCachedTextureLine->mCacheTexture->mWidth; uint32_t cacheHeight = glyph->mCachedTextureLine->mCacheTexture->mHeight; uint32_t cacheWidth = glyph->mCacheTexture->mWidth; uint32_t cacheHeight = glyph->mCacheTexture->mHeight; glyph->mBitmapMinU = startX / (float) cacheWidth; glyph->mBitmapMinV = startY / (float) cacheHeight; Loading Loading @@ -620,10 +620,6 @@ FontRenderer::FontRenderer() { mTextMeshPtr = NULL; mCurrentCacheTexture = NULL; mLastCacheTexture = NULL; mCacheTextureSmall = NULL; mCacheTexture128 = NULL; mCacheTexture256 = NULL; mCacheTexture512 = NULL; mLinearFiltering = false; Loading Loading @@ -659,10 +655,10 @@ FontRenderer::FontRenderer() { } FontRenderer::~FontRenderer() { for (uint32_t i = 0; i < mCacheLines.size(); i++) { delete mCacheLines[i]; for (uint32_t i = 0; i < mCacheTextures.size(); i++) { delete mCacheTextures[i]; } mCacheLines.clear(); mCacheTextures.clear(); if (mInitialized) { // Unbinding the buffer shouldn't be necessary but it crashes with some drivers Loading @@ -670,10 +666,6 @@ FontRenderer::~FontRenderer() { glDeleteBuffers(1, &mIndexBufferID); delete[] mTextMeshPtr; delete mCacheTextureSmall; delete mCacheTexture128; delete mCacheTexture256; delete mCacheTexture512; } Vector<Font*> fontsToDereference = mActiveFonts; Loading @@ -692,29 +684,19 @@ void FontRenderer::flushAllAndInvalidate() { mActiveFonts[i]->invalidateTextureCache(); } uint16_t totalGlyphs = 0; for (uint32_t i = 0; i < mCacheLines.size(); i++) { totalGlyphs += mCacheLines[i]->mNumGlyphs; mCacheLines[i]->init(); for (uint32_t i = 0; i < mCacheTextures.size(); i++) { mCacheTextures[i]->init(); } #if DEBUG_FONT_RENDERER uint16_t totalGlyphs = 0; for (uint32_t i = 0; i < mCacheTextures.size(); i++) { totalGlyphs += mCacheTextures[i]->mNumGlyphs; // Erase caches, just as a debugging facility if (mCacheTextureSmall && mCacheTextureSmall->mTexture) { memset(mCacheTextureSmall->mTexture, 0, mCacheTextureSmall->mWidth * mCacheTextureSmall->mHeight); } if (mCacheTexture128 && mCacheTexture128->mTexture) { memset(mCacheTexture128->mTexture, 0, mCacheTexture128->mWidth * mCacheTexture128->mHeight); } if (mCacheTexture256 && mCacheTexture256->mTexture) { memset(mCacheTexture256->mTexture, 0, mCacheTexture256->mWidth * mCacheTexture256->mHeight); if (mCacheTextures[i]->mTexture) { memset(mCacheTextures[i]->mTexture, 0, mCacheTextures[i]->mWidth * mCacheTextures[i]->mHeight); } if (mCacheTexture512 && mCacheTexture512->mTexture) { memset(mCacheTexture512->mTexture, 0, mCacheTexture512->mWidth * mCacheTexture512->mHeight); } ALOGD("Flushing caches: glyphs cached = %d", totalGlyphs); #endif Loading @@ -730,40 +712,19 @@ void FontRenderer::deallocateTextureMemory(CacheTexture *cacheTexture) { } void FontRenderer::flushLargeCaches() { if ((!mCacheTexture128 || !mCacheTexture128->mTexture) && (!mCacheTexture256 || !mCacheTexture256->mTexture) && (!mCacheTexture512 || !mCacheTexture512->mTexture)) { // Typical case; no large glyph caches allocated return; } for (uint32_t i = 0; i < mCacheLines.size(); i++) { CacheTextureLine* cacheLine = mCacheLines[i]; if ((cacheLine->mCacheTexture == mCacheTexture128 || cacheLine->mCacheTexture == mCacheTexture256 || cacheLine->mCacheTexture == mCacheTexture512) && cacheLine->mCacheTexture->mTexture != NULL) { #if DEBUG_FONT_RENDERER if (cacheLine->mCacheTexture == mCacheTexture128) { ALOGD("flushing cacheTexture128"); } else if (cacheLine->mCacheTexture == mCacheTexture256) { ALOGD("flushing cacheTexture256"); } else { ALOGD("flushing cacheTexture512"); // Start from 1; don't deallocate smallest/default texture for (uint32_t i = 1; i < mCacheTextures.size(); i++) { CacheTexture* cacheTexture = mCacheTextures[i]; if (cacheTexture->mTexture != NULL) { cacheTexture->init(); for (uint32_t j = 0; j < mActiveFonts.size(); j++) { mActiveFonts[j]->invalidateTextureCache(cacheTexture); } #endif cacheLine->init(); for (uint32_t i = 0; i < mActiveFonts.size(); i++) { mActiveFonts[i]->invalidateTextureCache(cacheLine); deallocateTextureMemory(cacheTexture); } } } deallocateTextureMemory(mCacheTexture128); deallocateTextureMemory(mCacheTexture256); deallocateTextureMemory(mCacheTexture512); } void FontRenderer::allocateTextureMemory(CacheTexture* cacheTexture) { int width = cacheTexture->mWidth; int height = cacheTexture->mHeight; Loading @@ -789,12 +750,24 @@ void FontRenderer::allocateTextureMemory(CacheTexture* cacheTexture) { glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); } CacheTexture* FontRenderer::cacheBitmapInTexture(const SkGlyph& glyph, uint32_t* startX, uint32_t* startY) { for (uint32_t i = 0; i < mCacheTextures.size(); i++) { if (mCacheTextures[i]->fitBitmap(glyph, startX, startY)) { return mCacheTextures[i]; } } // Could not fit glyph into current cache textures return NULL; } void FontRenderer::cacheBitmap(const SkGlyph& glyph, CachedGlyphInfo* cachedGlyph, uint32_t* retOriginX, uint32_t* retOriginY) { checkInit(); cachedGlyph->mIsValid = false; // If the glyph is too tall, don't cache it if (glyph.fHeight + TEXTURE_BORDER_SIZE * 2 > mCacheLines[mCacheLines.size() - 1]->mMaxHeight) { if (glyph.fHeight + TEXTURE_BORDER_SIZE * 2 > mCacheTextures[mCacheTextures.size() - 1]->mHeight) { ALOGE("Font size too large to fit in cache. width, height = %i, %i", (int) glyph.fWidth, (int) glyph.fHeight); return; Loading @@ -804,36 +777,22 @@ void FontRenderer::cacheBitmap(const SkGlyph& glyph, CachedGlyphInfo* cachedGlyp uint32_t startX = 0; 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; } } CacheTexture* cacheTexture = cacheBitmapInTexture(glyph, &startX, &startY); // If the new glyph didn't fit, flush the state so far and invalidate everything if (!bitmapFit) { if (!cacheTexture) { flushAllAndInvalidate(); // Try to fit it again for (uint32_t i = 0; i < mCacheLines.size(); i++) { bitmapFit = mCacheLines[i]->fitBitmap(glyph, &startX, &startY); if (bitmapFit) { cacheLine = mCacheLines[i]; break; } } cacheTexture = cacheBitmapInTexture(glyph, &startX, &startY); // if we still don't fit, something is wrong and we shouldn't draw if (!bitmapFit) { if (!cacheTexture) { return; } } cachedGlyph->mCachedTextureLine = cacheLine; cachedGlyph->mCacheTexture = cacheTexture; *retOriginX = startX; *retOriginY = startY; Loading @@ -841,9 +800,8 @@ void FontRenderer::cacheBitmap(const SkGlyph& glyph, CachedGlyphInfo* cachedGlyp uint32_t endX = startX + glyph.fWidth; uint32_t endY = startY + glyph.fHeight; uint32_t cacheWidth = cacheLine->mMaxWidth; uint32_t cacheWidth = cacheTexture->mWidth; CacheTexture* cacheTexture = cacheLine->mCacheTexture; if (!cacheTexture->mTexture) { // Large-glyph texture memory is allocated only as needed allocateTextureMemory(cacheTexture); Loading Loading @@ -896,17 +854,10 @@ CacheTexture* FontRenderer::createCacheTexture(int width, int height, bool alloc } void FontRenderer::initTextTexture() { for (uint32_t i = 0; i < mCacheLines.size(); i++) { delete mCacheLines[i]; } mCacheLines.clear(); if (mCacheTextureSmall) { delete mCacheTextureSmall; delete mCacheTexture128; delete mCacheTexture256; delete mCacheTexture512; for (uint32_t i = 0; i < mCacheTextures.size(); i++) { delete mCacheTextures[i]; } mCacheTextures.clear(); // Next, use other, separate caches for large glyphs. uint16_t maxWidth = 0; Loading @@ -918,35 +869,12 @@ void FontRenderer::initTextTexture() { maxWidth = MAX_TEXT_CACHE_WIDTH; } 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, mCacheTextureSmall)); nextLine += mCacheLines.top()->mMaxHeight; mCacheLines.push(new CacheTextureLine(mSmallCacheWidth, 26, nextLine, mCacheTextureSmall)); nextLine += mCacheLines.top()->mMaxHeight; mCacheLines.push(new CacheTextureLine(mSmallCacheWidth, 26, nextLine, mCacheTextureSmall)); nextLine += mCacheLines.top()->mMaxHeight; mCacheLines.push(new CacheTextureLine(mSmallCacheWidth, 34, nextLine, mCacheTextureSmall)); nextLine += mCacheLines.top()->mMaxHeight; mCacheLines.push(new CacheTextureLine(mSmallCacheWidth, 34, nextLine, mCacheTextureSmall)); nextLine += mCacheLines.top()->mMaxHeight; mCacheLines.push(new CacheTextureLine(mSmallCacheWidth, 42, nextLine, mCacheTextureSmall)); nextLine += mCacheLines.top()->mMaxHeight; mCacheLines.push(new CacheTextureLine(mSmallCacheWidth, mSmallCacheHeight - nextLine, nextLine, 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, mCacheTexture128)); mCacheLines.push(new CacheTextureLine(maxWidth, 128, 128, mCacheTexture128)); mCacheLines.push(new CacheTextureLine(maxWidth, 256, 0, mCacheTexture256)); mCacheLines.push(new CacheTextureLine(maxWidth, 512, 0, mCacheTexture512)); mCacheTextures.push(createCacheTexture(mSmallCacheWidth, mSmallCacheHeight, true)); mCacheTextures.push(createCacheTexture(maxWidth, 256, false)); mCacheTextures.push(createCacheTexture(maxWidth, 256, false)); mCacheTextures.push(createCacheTexture(maxWidth, 512, false)); mCurrentCacheTexture = mCacheTextures[0]; } // Avoid having to reallocate memory and render quad by quad Loading Loading @@ -1001,16 +929,14 @@ void FontRenderer::checkTextureUpdate() { Caches& caches = Caches::getInstance(); GLuint lastTextureId = 0; // 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 && cl->mCacheTexture->mTexture != NULL) { CacheTexture* cacheTexture = cl->mCacheTexture; // Iterate over all the cache textures and see which ones need to be updated for (uint32_t i = 0; i < mCacheTextures.size(); i++) { CacheTexture* cacheTexture = mCacheTextures[i]; if (cacheTexture->mDirty && cacheTexture->mTexture != NULL) { uint32_t xOffset = 0; uint32_t yOffset = cl->mCurrentRow; uint32_t width = cl->mMaxWidth; uint32_t height = cl->mMaxHeight; void* textureData = cacheTexture->mTexture + (yOffset * width); uint32_t width = cacheTexture->mWidth; uint32_t height = cacheTexture->mHeight; void* textureData = cacheTexture->mTexture; if (cacheTexture->mTextureId != lastTextureId) { caches.activeTexture(0); Loading @@ -1018,13 +944,13 @@ void FontRenderer::checkTextureUpdate() { lastTextureId = cacheTexture->mTextureId; } #if DEBUG_FONT_RENDERER ALOGD("glTextSubimage for cacheLine %d: xOff, yOff, width height = %d, %d, %d, %d", i, xOffset, yOffset, width, height); ALOGD("glTextSubimage for cacheTexture %d: xOff, width height = %d, %d, %d", i, xOffset, width, height); #endif glTexSubImage2D(GL_TEXTURE_2D, 0, xOffset, yOffset, width, height, glTexSubImage2D(GL_TEXTURE_2D, 0, xOffset, 0, width, height, GL_ALPHA, GL_UNSIGNED_BYTE, textureData); cl->mDirty = false; cacheTexture->mDirty = false; } } Loading libs/hwui/FontRenderer.h +30 −58 Original line number Diff line number Diff line Loading @@ -61,35 +61,14 @@ namespace uirenderer { class FontRenderer; class CacheTexture { public: CacheTexture(uint16_t width, uint16_t height) : mTexture(NULL), mTextureId(0), mWidth(width), mHeight(height), mLinearFiltering(false) { } ~CacheTexture() { if (mTexture) { delete[] mTexture; } if (mTextureId) { glDeleteTextures(1, &mTextureId); } } uint8_t* mTexture; GLuint mTextureId; uint16_t mWidth; uint16_t mHeight; bool mLinearFiltering; }; /** * CacheBlock is a noce in a linked list of current free space areas in a CacheTextureLine. * Using CacheBlocks enables us to pack the cache line from top to bottom as well as left to right. * CacheBlock is a node in a linked list of current free space areas in a CacheTexture. * Using CacheBlocks enables us to pack the cache from top to bottom as well as left to right. * When we add a glyph to the cache, we see if it fits within one of the existing columns that * have already been started (this is the case if the glyph fits vertically as well as * horizontally, and if its width is sufficiently close to the column width to avoid * sub-optimal packing of small glyphs into wide columns). If there is no column in which the * glyph fits, we check the final node, which is the remaining space in the cache line, creating * glyph fits, we check the final node, which is the remaining space in the cache, creating * a new column as appropriate. * * As columns fill up, we remove their CacheBlock from the list to avoid having to check Loading Loading @@ -122,21 +101,22 @@ struct CacheBlock { } }; class CacheTextureLine { class CacheTexture { public: CacheTextureLine(uint16_t maxWidth, uint16_t maxHeight, uint32_t currentRow, CacheTexture* cacheTexture): mMaxHeight(maxHeight), mMaxWidth(maxWidth), mCurrentRow(currentRow), mDirty(false), mNumGlyphs(0), mCacheTexture(cacheTexture) { 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, maxWidth - TEXTURE_BORDER_SIZE, maxHeight - TEXTURE_BORDER_SIZE, true); mWidth - TEXTURE_BORDER_SIZE, mHeight - TEXTURE_BORDER_SIZE, true); } ~CacheTextureLine() { ~CacheTexture() { if (mTexture) { delete[] mTexture; } if (mTextureId) { glDeleteTextures(1, &mTextureId); } reset(); } Loading @@ -154,17 +134,18 @@ public: // reset, then create a new remainder space to start again reset(); mCacheBlocks = new CacheBlock(TEXTURE_BORDER_SIZE, TEXTURE_BORDER_SIZE, mMaxWidth - TEXTURE_BORDER_SIZE, mMaxHeight - TEXTURE_BORDER_SIZE, true); mWidth - TEXTURE_BORDER_SIZE, mHeight - TEXTURE_BORDER_SIZE, true); } bool fitBitmap(const SkGlyph& glyph, uint32_t *retOriginX, uint32_t *retOriginY); uint16_t mMaxHeight; uint16_t mMaxWidth; uint32_t mCurrentRow; uint8_t* mTexture; GLuint mTextureId; uint16_t mWidth; uint16_t mHeight; bool mLinearFiltering; bool mDirty; uint16_t mNumGlyphs; CacheTexture* mCacheTexture; CacheBlock* mCacheBlocks; }; Loading Loading @@ -193,7 +174,7 @@ struct CachedGlyphInfo { // Auto-kerning SkFixed mLsbDelta; SkFixed mRsbDelta; CacheTextureLine* mCachedTextureLine; CacheTexture* mCacheTexture; }; Loading Loading @@ -260,7 +241,7 @@ protected: // Cache of glyphs DefaultKeyedVector<glyph_t, CachedGlyphInfo*> mCachedGlyphs; void invalidateTextureCache(CacheTextureLine *cacheLine = NULL); void invalidateTextureCache(CacheTexture *cacheTexture = NULL); CachedGlyphInfo* cacheGlyph(SkPaint* paint, glyph_t glyph); void updateGlyphCache(SkPaint* paint, const SkGlyph& skiaGlyph, CachedGlyphInfo* glyph); Loading Loading @@ -364,17 +345,11 @@ public: 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; for (uint32_t i = 0; i < mCacheTextures.size(); i++) { CacheTexture* cacheTexture = mCacheTextures[i]; if (cacheTexture != NULL && cacheTexture->mTexture != NULL) { size += cacheTexture->mWidth * cacheTexture->mHeight; } if (mCacheTexture512 != NULL && mCacheTexture512->mTexture != NULL) { size += mCacheTexture512->mWidth * mCacheTexture512->mHeight; } return size; } Loading @@ -390,6 +365,7 @@ protected: CacheTexture* createCacheTexture(int width, int height, bool allocate); void cacheBitmap(const SkGlyph& glyph, CachedGlyphInfo* cachedGlyph, uint32_t *retOriginX, uint32_t *retOriginY); CacheTexture* cacheBitmapInTexture(const SkGlyph& glyph, uint32_t* startX, uint32_t* startY); void flushAllAndInvalidate(); void initVertexArrayBuffers(); Loading @@ -415,17 +391,13 @@ protected: uint32_t mSmallCacheWidth; uint32_t mSmallCacheHeight; Vector<CacheTextureLine*> mCacheLines; Vector<CacheTexture*> mCacheTextures; Font* mCurrentFont; Vector<Font*> mActiveFonts; CacheTexture* mCurrentCacheTexture; CacheTexture* mLastCacheTexture; CacheTexture* mCacheTextureSmall; CacheTexture* mCacheTexture128; CacheTexture* mCacheTexture256; CacheTexture* mCacheTexture512; void checkTextureUpdate(); bool mUploadTexture; Loading Loading
libs/hwui/FontRenderer.cpp +73 −147 Original line number Diff line number Diff line Loading @@ -109,11 +109,11 @@ CacheBlock* CacheBlock::removeBlock(CacheBlock* head, CacheBlock *blockToRemove) } /////////////////////////////////////////////////////////////////////////////// // CacheTextureLine // CacheTexture /////////////////////////////////////////////////////////////////////////////// bool CacheTextureLine::fitBitmap(const SkGlyph& glyph, uint32_t *retOriginX, uint32_t *retOriginY) { if (glyph.fHeight + TEXTURE_BORDER_SIZE > mMaxHeight) { bool CacheTexture::fitBitmap(const SkGlyph& glyph, uint32_t *retOriginX, uint32_t *retOriginY) { if (glyph.fHeight + TEXTURE_BORDER_SIZE > mHeight) { return false; } Loading @@ -138,7 +138,7 @@ bool CacheTextureLine::fitBitmap(const SkGlyph& glyph, uint32_t *retOriginX, uin roundedUpW = glyphW; } *retOriginX = cacheBlock->mX; *retOriginY = mCurrentRow + cacheBlock->mY; *retOriginY = cacheBlock->mY; // If this is the remainder space, create a new cache block for this column. Otherwise, // adjust the info about this column. if (cacheBlock->mY == TEXTURE_BORDER_SIZE) { Loading @@ -146,10 +146,10 @@ bool CacheTextureLine::fitBitmap(const SkGlyph& glyph, uint32_t *retOriginX, uin // Adjust remainder space dimensions cacheBlock->mWidth -= roundedUpW; cacheBlock->mX += roundedUpW; if (mMaxHeight - glyphH >= glyphH) { if (mHeight - glyphH >= glyphH) { // There's enough height left over to create a new CacheBlock CacheBlock *newBlock = new CacheBlock(oldX, glyphH, roundedUpW, mMaxHeight - glyphH); mHeight - glyphH); #if DEBUG_FONT_RENDERER ALOGD("fitBitmap: Created new block: this, x, y, w, h = %p, %d, %d, %d, %d", newBlock, newBlock->mX, newBlock->mY, Loading Loading @@ -213,10 +213,10 @@ Font::~Font() { } } void Font::invalidateTextureCache(CacheTextureLine *cacheLine) { void Font::invalidateTextureCache(CacheTexture *cacheTexture) { for (uint32_t i = 0; i < mCachedGlyphs.size(); i++) { CachedGlyphInfo* cachedGlyph = mCachedGlyphs.valueAt(i); if (cacheLine == NULL || cachedGlyph->mCachedTextureLine == cacheLine) { if (cacheTexture == NULL || cachedGlyph->mCacheTexture == cacheTexture) { cachedGlyph->mIsValid = false; } } Loading Loading @@ -260,7 +260,7 @@ void Font::drawCachedGlyph(CachedGlyphInfo* glyph, int x, int y, mState->appendMeshQuad(nPenX, nPenY, u1, v2, nPenX + width, nPenY, u2, v2, nPenX + width, nPenY - height, u2, v1, nPenX, nPenY - height, u1, v1, glyph->mCachedTextureLine->mCacheTexture); nPenX, nPenY - height, u1, v1, glyph->mCacheTexture); } void Font::drawCachedGlyphBitmap(CachedGlyphInfo* glyph, int x, int y, Loading @@ -271,7 +271,7 @@ void Font::drawCachedGlyphBitmap(CachedGlyphInfo* glyph, int x, int y, uint32_t endX = glyph->mStartX + glyph->mBitmapWidth; uint32_t endY = glyph->mStartY + glyph->mBitmapHeight; CacheTexture *cacheTexture = glyph->mCachedTextureLine->mCacheTexture; CacheTexture *cacheTexture = glyph->mCacheTexture; uint32_t cacheWidth = cacheTexture->mWidth; const uint8_t* cacheBuffer = cacheTexture->mTexture; Loading Loading @@ -325,7 +325,7 @@ void Font::drawCachedGlyph(CachedGlyphInfo* glyph, float x, float hOffset, float position->fY + destination[2].fY, u2, v1, position->fX + destination[3].fX, position->fY + destination[3].fY, u1, v1, glyph->mCachedTextureLine->mCacheTexture); glyph->mCacheTexture); } CachedGlyphInfo* Font::getCachedGlyph(SkPaint* paint, glyph_t textUnit) { Loading Loading @@ -556,8 +556,8 @@ void Font::updateGlyphCache(SkPaint* paint, const SkGlyph& skiaGlyph, CachedGlyp glyph->mBitmapWidth = skiaGlyph.fWidth; glyph->mBitmapHeight = skiaGlyph.fHeight; uint32_t cacheWidth = glyph->mCachedTextureLine->mCacheTexture->mWidth; uint32_t cacheHeight = glyph->mCachedTextureLine->mCacheTexture->mHeight; uint32_t cacheWidth = glyph->mCacheTexture->mWidth; uint32_t cacheHeight = glyph->mCacheTexture->mHeight; glyph->mBitmapMinU = startX / (float) cacheWidth; glyph->mBitmapMinV = startY / (float) cacheHeight; Loading Loading @@ -620,10 +620,6 @@ FontRenderer::FontRenderer() { mTextMeshPtr = NULL; mCurrentCacheTexture = NULL; mLastCacheTexture = NULL; mCacheTextureSmall = NULL; mCacheTexture128 = NULL; mCacheTexture256 = NULL; mCacheTexture512 = NULL; mLinearFiltering = false; Loading Loading @@ -659,10 +655,10 @@ FontRenderer::FontRenderer() { } FontRenderer::~FontRenderer() { for (uint32_t i = 0; i < mCacheLines.size(); i++) { delete mCacheLines[i]; for (uint32_t i = 0; i < mCacheTextures.size(); i++) { delete mCacheTextures[i]; } mCacheLines.clear(); mCacheTextures.clear(); if (mInitialized) { // Unbinding the buffer shouldn't be necessary but it crashes with some drivers Loading @@ -670,10 +666,6 @@ FontRenderer::~FontRenderer() { glDeleteBuffers(1, &mIndexBufferID); delete[] mTextMeshPtr; delete mCacheTextureSmall; delete mCacheTexture128; delete mCacheTexture256; delete mCacheTexture512; } Vector<Font*> fontsToDereference = mActiveFonts; Loading @@ -692,29 +684,19 @@ void FontRenderer::flushAllAndInvalidate() { mActiveFonts[i]->invalidateTextureCache(); } uint16_t totalGlyphs = 0; for (uint32_t i = 0; i < mCacheLines.size(); i++) { totalGlyphs += mCacheLines[i]->mNumGlyphs; mCacheLines[i]->init(); for (uint32_t i = 0; i < mCacheTextures.size(); i++) { mCacheTextures[i]->init(); } #if DEBUG_FONT_RENDERER uint16_t totalGlyphs = 0; for (uint32_t i = 0; i < mCacheTextures.size(); i++) { totalGlyphs += mCacheTextures[i]->mNumGlyphs; // Erase caches, just as a debugging facility if (mCacheTextureSmall && mCacheTextureSmall->mTexture) { memset(mCacheTextureSmall->mTexture, 0, mCacheTextureSmall->mWidth * mCacheTextureSmall->mHeight); } if (mCacheTexture128 && mCacheTexture128->mTexture) { memset(mCacheTexture128->mTexture, 0, mCacheTexture128->mWidth * mCacheTexture128->mHeight); } if (mCacheTexture256 && mCacheTexture256->mTexture) { memset(mCacheTexture256->mTexture, 0, mCacheTexture256->mWidth * mCacheTexture256->mHeight); if (mCacheTextures[i]->mTexture) { memset(mCacheTextures[i]->mTexture, 0, mCacheTextures[i]->mWidth * mCacheTextures[i]->mHeight); } if (mCacheTexture512 && mCacheTexture512->mTexture) { memset(mCacheTexture512->mTexture, 0, mCacheTexture512->mWidth * mCacheTexture512->mHeight); } ALOGD("Flushing caches: glyphs cached = %d", totalGlyphs); #endif Loading @@ -730,40 +712,19 @@ void FontRenderer::deallocateTextureMemory(CacheTexture *cacheTexture) { } void FontRenderer::flushLargeCaches() { if ((!mCacheTexture128 || !mCacheTexture128->mTexture) && (!mCacheTexture256 || !mCacheTexture256->mTexture) && (!mCacheTexture512 || !mCacheTexture512->mTexture)) { // Typical case; no large glyph caches allocated return; } for (uint32_t i = 0; i < mCacheLines.size(); i++) { CacheTextureLine* cacheLine = mCacheLines[i]; if ((cacheLine->mCacheTexture == mCacheTexture128 || cacheLine->mCacheTexture == mCacheTexture256 || cacheLine->mCacheTexture == mCacheTexture512) && cacheLine->mCacheTexture->mTexture != NULL) { #if DEBUG_FONT_RENDERER if (cacheLine->mCacheTexture == mCacheTexture128) { ALOGD("flushing cacheTexture128"); } else if (cacheLine->mCacheTexture == mCacheTexture256) { ALOGD("flushing cacheTexture256"); } else { ALOGD("flushing cacheTexture512"); // Start from 1; don't deallocate smallest/default texture for (uint32_t i = 1; i < mCacheTextures.size(); i++) { CacheTexture* cacheTexture = mCacheTextures[i]; if (cacheTexture->mTexture != NULL) { cacheTexture->init(); for (uint32_t j = 0; j < mActiveFonts.size(); j++) { mActiveFonts[j]->invalidateTextureCache(cacheTexture); } #endif cacheLine->init(); for (uint32_t i = 0; i < mActiveFonts.size(); i++) { mActiveFonts[i]->invalidateTextureCache(cacheLine); deallocateTextureMemory(cacheTexture); } } } deallocateTextureMemory(mCacheTexture128); deallocateTextureMemory(mCacheTexture256); deallocateTextureMemory(mCacheTexture512); } void FontRenderer::allocateTextureMemory(CacheTexture* cacheTexture) { int width = cacheTexture->mWidth; int height = cacheTexture->mHeight; Loading @@ -789,12 +750,24 @@ void FontRenderer::allocateTextureMemory(CacheTexture* cacheTexture) { glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); } CacheTexture* FontRenderer::cacheBitmapInTexture(const SkGlyph& glyph, uint32_t* startX, uint32_t* startY) { for (uint32_t i = 0; i < mCacheTextures.size(); i++) { if (mCacheTextures[i]->fitBitmap(glyph, startX, startY)) { return mCacheTextures[i]; } } // Could not fit glyph into current cache textures return NULL; } void FontRenderer::cacheBitmap(const SkGlyph& glyph, CachedGlyphInfo* cachedGlyph, uint32_t* retOriginX, uint32_t* retOriginY) { checkInit(); cachedGlyph->mIsValid = false; // If the glyph is too tall, don't cache it if (glyph.fHeight + TEXTURE_BORDER_SIZE * 2 > mCacheLines[mCacheLines.size() - 1]->mMaxHeight) { if (glyph.fHeight + TEXTURE_BORDER_SIZE * 2 > mCacheTextures[mCacheTextures.size() - 1]->mHeight) { ALOGE("Font size too large to fit in cache. width, height = %i, %i", (int) glyph.fWidth, (int) glyph.fHeight); return; Loading @@ -804,36 +777,22 @@ void FontRenderer::cacheBitmap(const SkGlyph& glyph, CachedGlyphInfo* cachedGlyp uint32_t startX = 0; 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; } } CacheTexture* cacheTexture = cacheBitmapInTexture(glyph, &startX, &startY); // If the new glyph didn't fit, flush the state so far and invalidate everything if (!bitmapFit) { if (!cacheTexture) { flushAllAndInvalidate(); // Try to fit it again for (uint32_t i = 0; i < mCacheLines.size(); i++) { bitmapFit = mCacheLines[i]->fitBitmap(glyph, &startX, &startY); if (bitmapFit) { cacheLine = mCacheLines[i]; break; } } cacheTexture = cacheBitmapInTexture(glyph, &startX, &startY); // if we still don't fit, something is wrong and we shouldn't draw if (!bitmapFit) { if (!cacheTexture) { return; } } cachedGlyph->mCachedTextureLine = cacheLine; cachedGlyph->mCacheTexture = cacheTexture; *retOriginX = startX; *retOriginY = startY; Loading @@ -841,9 +800,8 @@ void FontRenderer::cacheBitmap(const SkGlyph& glyph, CachedGlyphInfo* cachedGlyp uint32_t endX = startX + glyph.fWidth; uint32_t endY = startY + glyph.fHeight; uint32_t cacheWidth = cacheLine->mMaxWidth; uint32_t cacheWidth = cacheTexture->mWidth; CacheTexture* cacheTexture = cacheLine->mCacheTexture; if (!cacheTexture->mTexture) { // Large-glyph texture memory is allocated only as needed allocateTextureMemory(cacheTexture); Loading Loading @@ -896,17 +854,10 @@ CacheTexture* FontRenderer::createCacheTexture(int width, int height, bool alloc } void FontRenderer::initTextTexture() { for (uint32_t i = 0; i < mCacheLines.size(); i++) { delete mCacheLines[i]; } mCacheLines.clear(); if (mCacheTextureSmall) { delete mCacheTextureSmall; delete mCacheTexture128; delete mCacheTexture256; delete mCacheTexture512; for (uint32_t i = 0; i < mCacheTextures.size(); i++) { delete mCacheTextures[i]; } mCacheTextures.clear(); // Next, use other, separate caches for large glyphs. uint16_t maxWidth = 0; Loading @@ -918,35 +869,12 @@ void FontRenderer::initTextTexture() { maxWidth = MAX_TEXT_CACHE_WIDTH; } 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, mCacheTextureSmall)); nextLine += mCacheLines.top()->mMaxHeight; mCacheLines.push(new CacheTextureLine(mSmallCacheWidth, 26, nextLine, mCacheTextureSmall)); nextLine += mCacheLines.top()->mMaxHeight; mCacheLines.push(new CacheTextureLine(mSmallCacheWidth, 26, nextLine, mCacheTextureSmall)); nextLine += mCacheLines.top()->mMaxHeight; mCacheLines.push(new CacheTextureLine(mSmallCacheWidth, 34, nextLine, mCacheTextureSmall)); nextLine += mCacheLines.top()->mMaxHeight; mCacheLines.push(new CacheTextureLine(mSmallCacheWidth, 34, nextLine, mCacheTextureSmall)); nextLine += mCacheLines.top()->mMaxHeight; mCacheLines.push(new CacheTextureLine(mSmallCacheWidth, 42, nextLine, mCacheTextureSmall)); nextLine += mCacheLines.top()->mMaxHeight; mCacheLines.push(new CacheTextureLine(mSmallCacheWidth, mSmallCacheHeight - nextLine, nextLine, 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, mCacheTexture128)); mCacheLines.push(new CacheTextureLine(maxWidth, 128, 128, mCacheTexture128)); mCacheLines.push(new CacheTextureLine(maxWidth, 256, 0, mCacheTexture256)); mCacheLines.push(new CacheTextureLine(maxWidth, 512, 0, mCacheTexture512)); mCacheTextures.push(createCacheTexture(mSmallCacheWidth, mSmallCacheHeight, true)); mCacheTextures.push(createCacheTexture(maxWidth, 256, false)); mCacheTextures.push(createCacheTexture(maxWidth, 256, false)); mCacheTextures.push(createCacheTexture(maxWidth, 512, false)); mCurrentCacheTexture = mCacheTextures[0]; } // Avoid having to reallocate memory and render quad by quad Loading Loading @@ -1001,16 +929,14 @@ void FontRenderer::checkTextureUpdate() { Caches& caches = Caches::getInstance(); GLuint lastTextureId = 0; // 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 && cl->mCacheTexture->mTexture != NULL) { CacheTexture* cacheTexture = cl->mCacheTexture; // Iterate over all the cache textures and see which ones need to be updated for (uint32_t i = 0; i < mCacheTextures.size(); i++) { CacheTexture* cacheTexture = mCacheTextures[i]; if (cacheTexture->mDirty && cacheTexture->mTexture != NULL) { uint32_t xOffset = 0; uint32_t yOffset = cl->mCurrentRow; uint32_t width = cl->mMaxWidth; uint32_t height = cl->mMaxHeight; void* textureData = cacheTexture->mTexture + (yOffset * width); uint32_t width = cacheTexture->mWidth; uint32_t height = cacheTexture->mHeight; void* textureData = cacheTexture->mTexture; if (cacheTexture->mTextureId != lastTextureId) { caches.activeTexture(0); Loading @@ -1018,13 +944,13 @@ void FontRenderer::checkTextureUpdate() { lastTextureId = cacheTexture->mTextureId; } #if DEBUG_FONT_RENDERER ALOGD("glTextSubimage for cacheLine %d: xOff, yOff, width height = %d, %d, %d, %d", i, xOffset, yOffset, width, height); ALOGD("glTextSubimage for cacheTexture %d: xOff, width height = %d, %d, %d", i, xOffset, width, height); #endif glTexSubImage2D(GL_TEXTURE_2D, 0, xOffset, yOffset, width, height, glTexSubImage2D(GL_TEXTURE_2D, 0, xOffset, 0, width, height, GL_ALPHA, GL_UNSIGNED_BYTE, textureData); cl->mDirty = false; cacheTexture->mDirty = false; } } Loading
libs/hwui/FontRenderer.h +30 −58 Original line number Diff line number Diff line Loading @@ -61,35 +61,14 @@ namespace uirenderer { class FontRenderer; class CacheTexture { public: CacheTexture(uint16_t width, uint16_t height) : mTexture(NULL), mTextureId(0), mWidth(width), mHeight(height), mLinearFiltering(false) { } ~CacheTexture() { if (mTexture) { delete[] mTexture; } if (mTextureId) { glDeleteTextures(1, &mTextureId); } } uint8_t* mTexture; GLuint mTextureId; uint16_t mWidth; uint16_t mHeight; bool mLinearFiltering; }; /** * CacheBlock is a noce in a linked list of current free space areas in a CacheTextureLine. * Using CacheBlocks enables us to pack the cache line from top to bottom as well as left to right. * CacheBlock is a node in a linked list of current free space areas in a CacheTexture. * Using CacheBlocks enables us to pack the cache from top to bottom as well as left to right. * When we add a glyph to the cache, we see if it fits within one of the existing columns that * have already been started (this is the case if the glyph fits vertically as well as * horizontally, and if its width is sufficiently close to the column width to avoid * sub-optimal packing of small glyphs into wide columns). If there is no column in which the * glyph fits, we check the final node, which is the remaining space in the cache line, creating * glyph fits, we check the final node, which is the remaining space in the cache, creating * a new column as appropriate. * * As columns fill up, we remove their CacheBlock from the list to avoid having to check Loading Loading @@ -122,21 +101,22 @@ struct CacheBlock { } }; class CacheTextureLine { class CacheTexture { public: CacheTextureLine(uint16_t maxWidth, uint16_t maxHeight, uint32_t currentRow, CacheTexture* cacheTexture): mMaxHeight(maxHeight), mMaxWidth(maxWidth), mCurrentRow(currentRow), mDirty(false), mNumGlyphs(0), mCacheTexture(cacheTexture) { 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, maxWidth - TEXTURE_BORDER_SIZE, maxHeight - TEXTURE_BORDER_SIZE, true); mWidth - TEXTURE_BORDER_SIZE, mHeight - TEXTURE_BORDER_SIZE, true); } ~CacheTextureLine() { ~CacheTexture() { if (mTexture) { delete[] mTexture; } if (mTextureId) { glDeleteTextures(1, &mTextureId); } reset(); } Loading @@ -154,17 +134,18 @@ public: // reset, then create a new remainder space to start again reset(); mCacheBlocks = new CacheBlock(TEXTURE_BORDER_SIZE, TEXTURE_BORDER_SIZE, mMaxWidth - TEXTURE_BORDER_SIZE, mMaxHeight - TEXTURE_BORDER_SIZE, true); mWidth - TEXTURE_BORDER_SIZE, mHeight - TEXTURE_BORDER_SIZE, true); } bool fitBitmap(const SkGlyph& glyph, uint32_t *retOriginX, uint32_t *retOriginY); uint16_t mMaxHeight; uint16_t mMaxWidth; uint32_t mCurrentRow; uint8_t* mTexture; GLuint mTextureId; uint16_t mWidth; uint16_t mHeight; bool mLinearFiltering; bool mDirty; uint16_t mNumGlyphs; CacheTexture* mCacheTexture; CacheBlock* mCacheBlocks; }; Loading Loading @@ -193,7 +174,7 @@ struct CachedGlyphInfo { // Auto-kerning SkFixed mLsbDelta; SkFixed mRsbDelta; CacheTextureLine* mCachedTextureLine; CacheTexture* mCacheTexture; }; Loading Loading @@ -260,7 +241,7 @@ protected: // Cache of glyphs DefaultKeyedVector<glyph_t, CachedGlyphInfo*> mCachedGlyphs; void invalidateTextureCache(CacheTextureLine *cacheLine = NULL); void invalidateTextureCache(CacheTexture *cacheTexture = NULL); CachedGlyphInfo* cacheGlyph(SkPaint* paint, glyph_t glyph); void updateGlyphCache(SkPaint* paint, const SkGlyph& skiaGlyph, CachedGlyphInfo* glyph); Loading Loading @@ -364,17 +345,11 @@ public: 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; for (uint32_t i = 0; i < mCacheTextures.size(); i++) { CacheTexture* cacheTexture = mCacheTextures[i]; if (cacheTexture != NULL && cacheTexture->mTexture != NULL) { size += cacheTexture->mWidth * cacheTexture->mHeight; } if (mCacheTexture512 != NULL && mCacheTexture512->mTexture != NULL) { size += mCacheTexture512->mWidth * mCacheTexture512->mHeight; } return size; } Loading @@ -390,6 +365,7 @@ protected: CacheTexture* createCacheTexture(int width, int height, bool allocate); void cacheBitmap(const SkGlyph& glyph, CachedGlyphInfo* cachedGlyph, uint32_t *retOriginX, uint32_t *retOriginY); CacheTexture* cacheBitmapInTexture(const SkGlyph& glyph, uint32_t* startX, uint32_t* startY); void flushAllAndInvalidate(); void initVertexArrayBuffers(); Loading @@ -415,17 +391,13 @@ protected: uint32_t mSmallCacheWidth; uint32_t mSmallCacheHeight; Vector<CacheTextureLine*> mCacheLines; Vector<CacheTexture*> mCacheTextures; Font* mCurrentFont; Vector<Font*> mActiveFonts; CacheTexture* mCurrentCacheTexture; CacheTexture* mLastCacheTexture; CacheTexture* mCacheTextureSmall; CacheTexture* mCacheTexture128; CacheTexture* mCacheTexture256; CacheTexture* mCacheTexture512; void checkTextureUpdate(); bool mUploadTexture; Loading