diff --git a/libs/hwui/DisplayListRenderer.cpp b/libs/hwui/DisplayListRenderer.cpp index 95fc2c5b797dd..2de70d462beaa 100644 --- a/libs/hwui/DisplayListRenderer.cpp +++ b/libs/hwui/DisplayListRenderer.cpp @@ -1699,7 +1699,9 @@ status_t DisplayListRenderer::drawTextOnPath(const char* text, int bytesCount, i addFloat(hOffset); addFloat(vOffset); paint->setAntiAlias(true); - addPaint(paint); + SkPaint* addedPaint = addPaint(paint); + FontRenderer& fontRenderer = mCaches.fontRenderer->getFontRenderer(addedPaint); + fontRenderer.precache(addedPaint, text, count); return DrawGlInfo::kStatusDone; } @@ -1711,7 +1713,9 @@ status_t DisplayListRenderer::drawPosText(const char* text, int bytesCount, int addInt(count); addFloats(positions, count * 2); paint->setAntiAlias(true); - addPaint(paint); + SkPaint* addedPaint = addPaint(paint); + FontRenderer& fontRenderer = mCaches.fontRenderer->getFontRenderer(addedPaint); + fontRenderer.precache(addedPaint, text, count); return DrawGlInfo::kStatusDone; } @@ -1742,7 +1746,11 @@ status_t DisplayListRenderer::drawText(const char* text, int bytesCount, int cou addFloat(x); addFloat(y); addFloats(positions, count * 2); - addPaint(paint); + SkPaint* addedPaint = addPaint(paint); + if (!reject) { + FontRenderer& fontRenderer = mCaches.fontRenderer->getFontRenderer(addedPaint); + fontRenderer.precache(addedPaint, text, count); + } addFloat(length); addSkip(location); return DrawGlInfo::kStatusDone; diff --git a/libs/hwui/DisplayListRenderer.h b/libs/hwui/DisplayListRenderer.h index 60a40c6b76be0..c8b3e479a0178 100644 --- a/libs/hwui/DisplayListRenderer.h +++ b/libs/hwui/DisplayListRenderer.h @@ -770,10 +770,10 @@ private: addInt((int) pathCopy); } - inline void addPaint(SkPaint* paint) { + inline SkPaint* addPaint(SkPaint* paint) { if (!paint) { addInt((int) NULL); - return; + return paint; } SkPaint* paintCopy = mPaintMap.valueFor(paint); @@ -785,6 +785,8 @@ private: } addInt((int) paintCopy); + + return paintCopy; } inline void addDisplayList(DisplayList* displayList) { diff --git a/libs/hwui/FontRenderer.cpp b/libs/hwui/FontRenderer.cpp index ccddd9155975e..a596fa9464675 100644 --- a/libs/hwui/FontRenderer.cpp +++ b/libs/hwui/FontRenderer.cpp @@ -37,10 +37,77 @@ namespace uirenderer { #define DEFAULT_TEXT_CACHE_WIDTH 1024 #define DEFAULT_TEXT_CACHE_HEIGHT 256 #define MAX_TEXT_CACHE_WIDTH 2048 -#define TEXTURE_BORDER_SIZE 1 +#define CACHE_BLOCK_ROUNDING_SIZE 4 #define AUTO_KERN(prev, next) (((next) - (prev) + 32) >> 6 << 16) +/////////////////////////////////////////////////////////////////////////////// +// CacheBlock +/////////////////////////////////////////////////////////////////////////////// + +/** + * Insert new block into existing linked list of blocks. Blocks are sorted in increasing-width + * order, except for the final block (the remainder space at the right, since we fill from the + * left). + */ +CacheBlock* CacheBlock::insertBlock(CacheBlock* head, CacheBlock *newBlock) { +#if DEBUG_FONT_RENDERER + ALOGD("insertBlock: this, x, y, w, h = %p, %d, %d, %d, %d", + newBlock, newBlock->mX, newBlock->mY, + newBlock->mWidth, newBlock->mHeight); +#endif + CacheBlock *currBlock = head; + CacheBlock *prevBlock = NULL; + while (currBlock && currBlock->mY != TEXTURE_BORDER_SIZE) { + if (newBlock->mWidth < currBlock->mWidth) { + newBlock->mNext = currBlock; + newBlock->mPrev = prevBlock; + currBlock->mPrev = newBlock; + if (prevBlock) { + prevBlock->mNext = newBlock; + return head; + } else { + return newBlock; + } + } + prevBlock = currBlock; + currBlock = currBlock->mNext; + } + // new block larger than all others - insert at end (but before the remainder space, if there) + newBlock->mNext = currBlock; + newBlock->mPrev = prevBlock; + if (currBlock) { + currBlock->mPrev = newBlock; + } + if (prevBlock) { + prevBlock->mNext = newBlock; + return head; + } else { + return newBlock; + } +} + +CacheBlock* CacheBlock::removeBlock(CacheBlock* head, CacheBlock *blockToRemove) { +#if DEBUG_FONT_RENDERER + ALOGD("removeBlock: this, x, y, w, h = %p, %d, %d, %d, %d", + blockToRemove, blockToRemove->mX, blockToRemove->mY, + blockToRemove->mWidth, blockToRemove->mHeight); +#endif + CacheBlock* newHead = head; + CacheBlock* nextBlock = blockToRemove->mNext; + CacheBlock* prevBlock = blockToRemove->mPrev; + if (prevBlock) { + prevBlock->mNext = nextBlock; + } else { + newHead = nextBlock; + } + if (nextBlock) { + nextBlock->mPrev = prevBlock; + } + delete blockToRemove; + return newHead; +} + /////////////////////////////////////////////////////////////////////////////// // CacheTextureLine /////////////////////////////////////////////////////////////////////////////// @@ -50,14 +117,73 @@ bool CacheTextureLine::fitBitmap(const SkGlyph& glyph, uint32_t *retOriginX, uin return false; } - if (mCurrentCol + glyph.fWidth + TEXTURE_BORDER_SIZE * 2 < mMaxWidth) { - *retOriginX = mCurrentCol + TEXTURE_BORDER_SIZE; - *retOriginY = mCurrentRow + TEXTURE_BORDER_SIZE; - mCurrentCol += glyph.fWidth + TEXTURE_BORDER_SIZE * 2; - mDirty = true; - return true; + uint16_t glyphW = glyph.fWidth + TEXTURE_BORDER_SIZE; + uint16_t glyphH = glyph.fHeight + TEXTURE_BORDER_SIZE; + // roundedUpW equals glyphW to the next multiple of CACHE_BLOCK_ROUNDING_SIZE. + // This columns for glyphs that are close but not necessarily exactly the same size. It trades + // off the loss of a few pixels for some glyphs against the ability to store more glyphs + // of varying sizes in one block. + uint16_t roundedUpW = + (glyphW + CACHE_BLOCK_ROUNDING_SIZE - 1) & -CACHE_BLOCK_ROUNDING_SIZE; + CacheBlock *cacheBlock = mCacheBlocks; + while (cacheBlock) { + // Store glyph in this block iff: it fits the block's remaining space and: + // it's the remainder space (mY == 0) or there's only enough height for this one glyph + // or it's within ROUNDING_SIZE of the block width + if (roundedUpW <= cacheBlock->mWidth && glyphH <= cacheBlock->mHeight && + (cacheBlock->mY == TEXTURE_BORDER_SIZE || + (cacheBlock->mWidth - roundedUpW < CACHE_BLOCK_ROUNDING_SIZE))) { + if (cacheBlock->mHeight - glyphH < glyphH) { + // Only enough space for this glyph - don't bother rounding up the width + roundedUpW = glyphW; + } + *retOriginX = cacheBlock->mX; + *retOriginY = mCurrentRow + 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) { + uint16_t oldX = cacheBlock->mX; + // Adjust remainder space dimensions + cacheBlock->mWidth -= roundedUpW; + cacheBlock->mX += roundedUpW; + if (mMaxHeight - glyphH >= glyphH) { + // There's enough height left over to create a new CacheBlock + CacheBlock *newBlock = new CacheBlock(oldX, glyphH, roundedUpW, + mMaxHeight - 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, + newBlock->mWidth, newBlock->mHeight); +#endif + mCacheBlocks = CacheBlock::insertBlock(mCacheBlocks, newBlock); + } + } else { + // Insert into current column and adjust column dimensions + cacheBlock->mY += glyphH; + cacheBlock->mHeight -= glyphH; +#if DEBUG_FONT_RENDERER + ALOGD("fitBitmap: Added to existing block: this, x, y, w, h = %p, %d, %d, %d, %d", + cacheBlock, cacheBlock->mX, cacheBlock->mY, + cacheBlock->mWidth, cacheBlock->mHeight); +#endif + } + if (cacheBlock->mHeight < fmin(glyphH, glyphW)) { + // If remaining space in this block is too small to be useful, remove it + mCacheBlocks = CacheBlock::removeBlock(mCacheBlocks, cacheBlock); + } + mDirty = true; +#if DEBUG_FONT_RENDERER + ALOGD("fitBitmap: current block list:"); + mCacheBlocks->output(); +#endif + ++mNumGlyphs; + return true; + } + cacheBlock = cacheBlock->mNext; } - +#if DEBUG_FONT_RENDERER + ALOGD("fitBitmap: returning false for glyph of size %d, %d", glyphW, glyphH); +#endif return false; } @@ -297,6 +423,27 @@ void Font::measure(SkPaint* paint, const char* text, uint32_t start, uint32_t le render(paint, text, start, len, numGlyphs, 0, 0, MEASURE, NULL, 0, 0, bounds, positions); } +void Font::precache(SkPaint* paint, const char* text, int numGlyphs) { + + if (numGlyphs == 0 || text == NULL) { + return; + } + int glyphsCount = 0; + + while (glyphsCount < numGlyphs) { + glyph_t glyph = GET_GLYPH(text); + + // Reached the end of the string + if (IS_END_OF_STRING(glyph)) { + break; + } + + CachedGlyphInfo* cachedGlyph = getCachedGlyph(paint, glyph); + + glyphsCount++; + } +} + void Font::render(SkPaint* paint, const char* text, uint32_t start, uint32_t len, int numGlyphs, int x, int y, RenderMode mode, uint8_t *bitmap, uint32_t bitmapW, uint32_t bitmapH, Rect* bounds, const float* positions) { @@ -545,9 +692,33 @@ void FontRenderer::flushAllAndInvalidate() { mActiveFonts[i]->invalidateTextureCache(); } + uint16_t totalGlyphs = 0; for (uint32_t i = 0; i < mCacheLines.size(); i++) { - mCacheLines[i]->mCurrentCol = 0; + totalGlyphs += mCacheLines[i]->mNumGlyphs; + mCacheLines[i]->init(); } + +#if DEBUG_FONT_RENDERER + ALOGD("FontRenderer: flushAllAndInvalidatel"); + // 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 (mCacheTexture512 && mCacheTexture512->mTexture) { + memset(mCacheTexture512->mTexture, 0, + mCacheTexture512->mWidth * mCacheTexture512->mHeight); + } + ALOGD("Flushing caches: glyphs cached = %d", totalGlyphs); +#endif } void FontRenderer::deallocateTextureMemory(CacheTexture *cacheTexture) { @@ -573,7 +744,16 @@ void FontRenderer::flushLargeCaches() { cacheLine->mCacheTexture == mCacheTexture256 || cacheLine->mCacheTexture == mCacheTexture512) && cacheLine->mCacheTexture->mTexture != NULL) { - cacheLine->mCurrentCol = 0; +#if DEBUG_FONT_RENDERER + if (cacheLine->mCacheTexture == mCacheTexture128) { + ALOGD("flushing cacheTexture128"); + } else if (cacheLine->mCacheTexture == mCacheTexture256) { + ALOGD("flushing cacheTexture256"); + } else { + ALOGD("flushing cacheTexture512"); + } +#endif + cacheLine->init(); for (uint32_t i = 0; i < mActiveFonts.size(); i++) { mActiveFonts[i]->invalidateTextureCache(cacheLine); } @@ -614,9 +794,12 @@ void FontRenderer::cacheBitmap(const SkGlyph& glyph, CachedGlyphInfo* cachedGlyp uint32_t* retOriginX, uint32_t* retOriginY) { 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) { - ALOGE("Font size to large to fit in cache. width, height = %i, %i", - (int) glyph.fWidth, (int) glyph.fHeight); + if (mCacheLines.size() == 0 || + glyph.fHeight + TEXTURE_BORDER_SIZE * 2 > mCacheLines[mCacheLines.size() - 1]->mMaxHeight) { + if (mCacheLines.size() != 0) { + ALOGE("Font size too large to fit in cache. width, height = %i, %i", + (int) glyph.fWidth, (int) glyph.fHeight); + } return; } @@ -747,26 +930,26 @@ void FontRenderer::initTextTexture() { 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)); + mCacheLines.push(new CacheTextureLine(mSmallCacheWidth, 18, nextLine, mCacheTextureSmall)); nextLine += mCacheLines.top()->mMaxHeight; - mCacheLines.push(new CacheTextureLine(mSmallCacheWidth, 26, nextLine, 0, mCacheTextureSmall)); + mCacheLines.push(new CacheTextureLine(mSmallCacheWidth, 26, nextLine, mCacheTextureSmall)); nextLine += mCacheLines.top()->mMaxHeight; - mCacheLines.push(new CacheTextureLine(mSmallCacheWidth, 26, nextLine, 0, mCacheTextureSmall)); + mCacheLines.push(new CacheTextureLine(mSmallCacheWidth, 26, nextLine, mCacheTextureSmall)); nextLine += mCacheLines.top()->mMaxHeight; - mCacheLines.push(new CacheTextureLine(mSmallCacheWidth, 34, nextLine, 0, mCacheTextureSmall)); + mCacheLines.push(new CacheTextureLine(mSmallCacheWidth, 34, nextLine, mCacheTextureSmall)); nextLine += mCacheLines.top()->mMaxHeight; - mCacheLines.push(new CacheTextureLine(mSmallCacheWidth, 34, nextLine, 0, mCacheTextureSmall)); + mCacheLines.push(new CacheTextureLine(mSmallCacheWidth, 34, nextLine, mCacheTextureSmall)); nextLine += mCacheLines.top()->mMaxHeight; - mCacheLines.push(new CacheTextureLine(mSmallCacheWidth, 42, nextLine, 0, mCacheTextureSmall)); + mCacheLines.push(new CacheTextureLine(mSmallCacheWidth, 42, nextLine, mCacheTextureSmall)); nextLine += mCacheLines.top()->mMaxHeight; mCacheLines.push(new CacheTextureLine(mSmallCacheWidth, mSmallCacheHeight - nextLine, - nextLine, 0, mCacheTextureSmall)); + 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, 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)); + 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)); } // Avoid having to reallocate memory and render quad by quad @@ -837,6 +1020,10 @@ void FontRenderer::checkTextureUpdate() { glBindTexture(GL_TEXTURE_2D, cacheTexture->mTextureId); 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); +#endif glTexSubImage2D(GL_TEXTURE_2D, 0, xOffset, yOffset, width, height, GL_ALPHA, GL_UNSIGNED_BYTE, textureData); @@ -960,43 +1147,7 @@ void FontRenderer::appendRotatedMeshQuad(float x1, float y1, float u1, float v1, } } -uint32_t FontRenderer::getRemainingCacheCapacity() { - uint32_t remainingCapacity = 0; - float totalPixels = 0; - - //avoid divide by zero if the size is 0 - if (mCacheLines.size() == 0) { - return 0; - } - for(uint32_t i = 0; i < mCacheLines.size(); i ++) { - remainingCapacity += (mCacheLines[i]->mMaxWidth - mCacheLines[i]->mCurrentCol); - totalPixels += mCacheLines[i]->mMaxWidth; - } - remainingCapacity = (remainingCapacity * 100) / totalPixels; - return remainingCapacity; -} - -void FontRenderer::precacheLatin(SkPaint* paint) { - // Remaining capacity is measured in % - uint32_t remainingCapacity = getRemainingCacheCapacity(); - uint32_t precacheIndex = 0; - - // We store a string with letters in a rough frequency of occurrence - String16 l("eisarntolcdugpmhbyfvkwzxjq EISARNTOLCDUGPMHBYFVKWZXJQ,.?!()-+@;:'0123456789"); - - size_t size = l.size(); - uint16_t latin[size]; - paint->utfToGlyphs(l.string(), SkPaint::kUTF16_TextEncoding, size * sizeof(char16_t), latin); - - while (remainingCapacity > 25 && precacheIndex < size) { - mCurrentFont->getCachedGlyph(paint, TO_GLYPH(latin[precacheIndex])); - remainingCapacity = getRemainingCacheCapacity(); - precacheIndex++; - } -} - void FontRenderer::setFont(SkPaint* paint, uint32_t fontId, float fontSize) { - uint32_t currentNumFonts = mActiveFonts.size(); int flags = 0; if (paint->isFakeBoldText()) { flags |= Font::kFakeBold; @@ -1012,12 +1163,6 @@ void FontRenderer::setFont(SkPaint* paint, uint32_t fontId, float fontSize) { mCurrentFont = Font::create(this, fontId, fontSize, flags, italicStyle, scaleX, style, strokeWidth); - const float maxPrecacheFontSize = 40.0f; - bool isNewFont = currentNumFonts != mActiveFonts.size(); - - if (isNewFont && fontSize <= maxPrecacheFontSize) { - precacheLatin(paint); - } } FontRenderer::DropShadow FontRenderer::renderDropShadow(SkPaint* paint, const char *text, @@ -1084,6 +1229,25 @@ void FontRenderer::finishRender() { } } +void FontRenderer::precache(SkPaint* paint, const char* text, int numGlyphs) { + int flags = 0; + if (paint->isFakeBoldText()) { + flags |= Font::kFakeBold; + } + const float skewX = paint->getTextSkewX(); + uint32_t italicStyle = *(uint32_t*) &skewX; + const float scaleXFloat = paint->getTextScaleX(); + uint32_t scaleX = *(uint32_t*) &scaleXFloat; + SkPaint::Style style = paint->getStyle(); + const float strokeWidthFloat = paint->getStrokeWidth(); + uint32_t strokeWidth = *(uint32_t*) &strokeWidthFloat; + float fontSize = paint->getTextSize(); + Font* font = Font::create(this, SkTypeface::UniqueID(paint->getTypeface()), + fontSize, flags, italicStyle, scaleX, style, strokeWidth); + + font->precache(paint, text, numGlyphs); +} + bool FontRenderer::renderText(SkPaint* paint, const Rect* clip, const char *text, uint32_t startIndex, uint32_t len, int numGlyphs, int x, int y, Rect* bounds) { if (!mCurrentFont) { diff --git a/libs/hwui/FontRenderer.h b/libs/hwui/FontRenderer.h index 9ed69325c9141..8b1d10c842e92 100644 --- a/libs/hwui/FontRenderer.h +++ b/libs/hwui/FontRenderer.h @@ -53,6 +53,8 @@ namespace uirenderer { #define IS_END_OF_STRING(glyph) glyph < 0 #endif +#define TEXTURE_BORDER_SIZE 1 + /////////////////////////////////////////////////////////////////////////////// // Declarations /////////////////////////////////////////////////////////////////////////////// @@ -80,16 +82,79 @@ public: 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. + * 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 + * a new column as appropriate. + * + * As columns fill up, we remove their CacheBlock from the list to avoid having to check + * small blocks in the future. + */ +struct CacheBlock { + uint16_t mX; + uint16_t mY; + uint16_t mWidth; + uint16_t mHeight; + CacheBlock* mNext; + CacheBlock* mPrev; + + CacheBlock(uint16_t x, uint16_t y, uint16_t width, uint16_t height, bool empty = false): + mX(x), mY(y), mWidth(width), mHeight(height), mNext(NULL), mPrev(NULL) + { + } + + 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->mNext; + } + } +}; + class CacheTextureLine { public: CacheTextureLine(uint16_t maxWidth, uint16_t maxHeight, uint32_t currentRow, - uint32_t currentCol, CacheTexture* cacheTexture): + CacheTexture* cacheTexture): mMaxHeight(maxHeight), mMaxWidth(maxWidth), mCurrentRow(currentRow), - mCurrentCol(currentCol), mDirty(false), + mNumGlyphs(0), mCacheTexture(cacheTexture) { + mCacheBlocks = new CacheBlock(TEXTURE_BORDER_SIZE, TEXTURE_BORDER_SIZE, + maxWidth - TEXTURE_BORDER_SIZE, maxHeight - TEXTURE_BORDER_SIZE, true); + } + + ~CacheTextureLine() { + reset(); + } + + void reset() { + // Delete existing cache blocks + while (mCacheBlocks != NULL) { + CacheBlock* tmpBlock = mCacheBlocks; + mCacheBlocks = mCacheBlocks->mNext; + delete tmpBlock; + } + mNumGlyphs = 0; + } + + void init() { + // 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); } bool fitBitmap(const SkGlyph& glyph, uint32_t *retOriginX, uint32_t *retOriginY); @@ -97,9 +162,10 @@ public: uint16_t mMaxHeight; uint16_t mMaxWidth; uint32_t mCurrentRow; - uint32_t mCurrentCol; bool mDirty; + uint16_t mNumGlyphs; CacheTexture* mCacheTexture; + CacheBlock* mCacheBlocks; }; struct CachedGlyphInfo { @@ -179,6 +245,8 @@ protected: MEASURE, }; + void precache(SkPaint* paint, const char* text, int numGlyphs); + void render(SkPaint* paint, const char *text, uint32_t start, uint32_t len, int numGlyphs, int x, int y, RenderMode mode, uint8_t *bitmap, uint32_t bitmapW, uint32_t bitmapH, Rect *bounds, const float* positions); @@ -244,6 +312,9 @@ public: } void setFont(SkPaint* paint, uint32_t fontId, float fontSize); + + void precache(SkPaint* paint, const char* text, int numGlyphs); + // bounds is an out parameter bool renderText(SkPaint* paint, const Rect* clip, const char *text, uint32_t startIndex, uint32_t len, int numGlyphs, int x, int y, Rect* bounds); @@ -327,8 +398,6 @@ protected: void initRender(const Rect* clip, Rect* bounds); void finishRender(); - void precacheLatin(SkPaint* paint); - void issueDrawCommand(); void appendMeshQuadNoClip(float x1, float y1, float u1, float v1, float x2, float y2, float u2, float v2, @@ -347,7 +416,6 @@ protected: uint32_t mSmallCacheHeight; Vector mCacheLines; - uint32_t getRemainingCacheCapacity(); Font* mCurrentFont; Vector mActiveFonts; diff --git a/tests/HwAccelerationTest/AndroidManifest.xml b/tests/HwAccelerationTest/AndroidManifest.xml index 185703309b2e6..c783ad62db8a5 100644 --- a/tests/HwAccelerationTest/AndroidManifest.xml +++ b/tests/HwAccelerationTest/AndroidManifest.xml @@ -176,6 +176,15 @@ + + + + + + + diff --git a/tests/HwAccelerationTest/src/com/android/test/hwui/GlyphCacheActivity.java b/tests/HwAccelerationTest/src/com/android/test/hwui/GlyphCacheActivity.java new file mode 100644 index 0000000000000..e89b2948062bb --- /dev/null +++ b/tests/HwAccelerationTest/src/com/android/test/hwui/GlyphCacheActivity.java @@ -0,0 +1,75 @@ +/* + * Copyright (C) 2012 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.os.Bundle; +import android.view.ViewGroup; +import android.widget.LinearLayout; +import android.widget.ScrollView; +import android.widget.TextView; + +import static android.widget.LinearLayout.LayoutParams; + +public class GlyphCacheActivity extends Activity { + + private static final String mCharacterSet = "abcdefghijklmnopqrstuvwxyz" + + "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + "0123456789" + "~!@#$%^&*()_+-={}[]:\";'<>?,./"; + private int mTotalChars = 0; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + ScrollView scrollView = new ScrollView(this); + scrollView.setLayoutParams(new LayoutParams(LayoutParams.MATCH_PARENT, + ViewGroup.LayoutParams.MATCH_PARENT)); + LinearLayout layout = new LinearLayout(this); + layout.setOrientation(LinearLayout.VERTICAL); + layout.setLayoutParams(new LayoutParams(LayoutParams.WRAP_CONTENT, + ViewGroup.LayoutParams.WRAP_CONTENT)); + scrollView.addView(layout); + + while (mTotalChars < 10000) { + layout.addView(createTextView()); + } + setContentView(scrollView); + } + + private TextView createTextView() { + TextView textview = new TextView(this); + textview.setTextSize(6 + (int) (Math.random() * 5) * 10); + textview.setTextColor(0xff << 24 | (int) (Math.random() * 255) << 16 | + (int) (Math.random() * 255) << 8 | (int) (Math.random() * 255) << 16); + textview.setLayoutParams(new LayoutParams(LayoutParams.WRAP_CONTENT, + ViewGroup.LayoutParams.WRAP_CONTENT)); + int numChars = 5 + (int) (Math.random() * 10); + mTotalChars += numChars; + textview.setText(createString(numChars)); + + return textview; + } + + private String createString(int length) { + StringBuilder sb = new StringBuilder(); + for (int i = 0; i < length; i++) { + sb.append(mCharacterSet.charAt((int)(Math.random() * mCharacterSet.length()))); + } + return sb.toString(); + } +}