Merge "Optimize interactions with glyph cache" into jb-mr1-dev
This commit is contained in:
@@ -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;
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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<CacheTextureLine*> mCacheLines;
|
||||
uint32_t getRemainingCacheCapacity();
|
||||
|
||||
Font* mCurrentFont;
|
||||
Vector<Font*> mActiveFonts;
|
||||
|
||||
@@ -176,6 +176,15 @@
|
||||
</intent-filter>
|
||||
</activity>
|
||||
|
||||
<activity
|
||||
android:name="GlyphCacheActivity"
|
||||
android:label="_GlyphCache">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
|
||||
<activity
|
||||
android:name="CanvasTextureViewActivity"
|
||||
android:label="_CanvasTextureView">
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user