CacheTexture.cpp revision 0908764b2b3cf5075df4178a5f0a8547dcb7b317
1/* 2 * Copyright (C) 2012 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17#include <SkGlyph.h> 18 19#include "CacheTexture.h" 20#include "../Debug.h" 21 22namespace android { 23namespace uirenderer { 24 25/////////////////////////////////////////////////////////////////////////////// 26// CacheBlock 27/////////////////////////////////////////////////////////////////////////////// 28 29/** 30 * Insert new block into existing linked list of blocks. Blocks are sorted in increasing-width 31 * order, except for the final block (the remainder space at the right, since we fill from the 32 * left). 33 */ 34CacheBlock* CacheBlock::insertBlock(CacheBlock* head, CacheBlock* newBlock) { 35#if DEBUG_FONT_RENDERER 36 ALOGD("insertBlock: this, x, y, w, h = %p, %d, %d, %d, %d", 37 newBlock, newBlock->mX, newBlock->mY, 38 newBlock->mWidth, newBlock->mHeight); 39#endif 40 41 CacheBlock* currBlock = head; 42 CacheBlock* prevBlock = NULL; 43 44 while (currBlock && currBlock->mY != TEXTURE_BORDER_SIZE) { 45 if (newBlock->mWidth < currBlock->mWidth) { 46 newBlock->mNext = currBlock; 47 newBlock->mPrev = prevBlock; 48 currBlock->mPrev = newBlock; 49 50 if (prevBlock) { 51 prevBlock->mNext = newBlock; 52 return head; 53 } else { 54 return newBlock; 55 } 56 } 57 58 prevBlock = currBlock; 59 currBlock = currBlock->mNext; 60 } 61 62 // new block larger than all others - insert at end (but before the remainder space, if there) 63 newBlock->mNext = currBlock; 64 newBlock->mPrev = prevBlock; 65 66 if (currBlock) { 67 currBlock->mPrev = newBlock; 68 } 69 70 if (prevBlock) { 71 prevBlock->mNext = newBlock; 72 return head; 73 } else { 74 return newBlock; 75 } 76} 77 78CacheBlock* CacheBlock::removeBlock(CacheBlock* head, CacheBlock* blockToRemove) { 79#if DEBUG_FONT_RENDERER 80 ALOGD("removeBlock: this, x, y, w, h = %p, %d, %d, %d, %d", 81 blockToRemove, blockToRemove->mX, blockToRemove->mY, 82 blockToRemove->mWidth, blockToRemove->mHeight); 83#endif 84 85 CacheBlock* newHead = head; 86 CacheBlock* nextBlock = blockToRemove->mNext; 87 CacheBlock* prevBlock = blockToRemove->mPrev; 88 89 if (prevBlock) { 90 prevBlock->mNext = nextBlock; 91 } else { 92 newHead = nextBlock; 93 } 94 95 if (nextBlock) { 96 nextBlock->mPrev = prevBlock; 97 } 98 99 delete blockToRemove; 100 101 return newHead; 102} 103 104/////////////////////////////////////////////////////////////////////////////// 105// CacheTexture 106/////////////////////////////////////////////////////////////////////////////// 107 108CacheTexture::CacheTexture(uint16_t width, uint16_t height, uint32_t maxQuadCount) : 109 mTexture(NULL), mTextureId(0), mWidth(width), mHeight(height), 110 mLinearFiltering(false), mDirty(false), mNumGlyphs(0), 111 mMesh(NULL), mCurrentQuad(0), mMaxQuadCount(maxQuadCount) { 112 mCacheBlocks = new CacheBlock(TEXTURE_BORDER_SIZE, TEXTURE_BORDER_SIZE, 113 mWidth - TEXTURE_BORDER_SIZE, mHeight - TEXTURE_BORDER_SIZE, true); 114} 115 116CacheTexture::~CacheTexture() { 117 releaseMesh(); 118 releaseTexture(); 119 reset(); 120} 121 122void CacheTexture::reset() { 123 // Delete existing cache blocks 124 while (mCacheBlocks != NULL) { 125 CacheBlock* tmpBlock = mCacheBlocks; 126 mCacheBlocks = mCacheBlocks->mNext; 127 delete tmpBlock; 128 } 129 mNumGlyphs = 0; 130 mCurrentQuad = 0; 131} 132 133void CacheTexture::init() { 134 // reset, then create a new remainder space to start again 135 reset(); 136 mCacheBlocks = new CacheBlock(TEXTURE_BORDER_SIZE, TEXTURE_BORDER_SIZE, 137 mWidth - TEXTURE_BORDER_SIZE, mHeight - TEXTURE_BORDER_SIZE, true); 138} 139 140void CacheTexture::releaseMesh() { 141 delete[] mMesh; 142} 143 144void CacheTexture::releaseTexture() { 145 if (mTexture) { 146 delete[] mTexture; 147 mTexture = NULL; 148 } 149 if (mTextureId) { 150 glDeleteTextures(1, &mTextureId); 151 mTextureId = 0; 152 } 153 mDirty = false; 154 mCurrentQuad = 0; 155} 156 157void CacheTexture::allocateMesh() { 158 if (!mMesh) { 159 mMesh = new TextureVertex[mMaxQuadCount * 4]; 160 } 161} 162 163void CacheTexture::allocateTexture() { 164 if (!mTexture) { 165 mTexture = new uint8_t[mWidth * mHeight]; 166 } 167 168 if (!mTextureId) { 169 glGenTextures(1, &mTextureId); 170 171 glBindTexture(GL_TEXTURE_2D, mTextureId); 172 glPixelStorei(GL_UNPACK_ALIGNMENT, 1); 173 // Initialize texture dimensions 174 glTexImage2D(GL_TEXTURE_2D, 0, GL_ALPHA, mWidth, mHeight, 0, 175 GL_ALPHA, GL_UNSIGNED_BYTE, 0); 176 177 const GLenum filtering = getLinearFiltering() ? GL_LINEAR : GL_NEAREST; 178 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, filtering); 179 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, filtering); 180 181 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); 182 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); 183 } 184} 185 186bool CacheTexture::fitBitmap(const SkGlyph& glyph, uint32_t* retOriginX, uint32_t* retOriginY) { 187 if (glyph.fHeight + TEXTURE_BORDER_SIZE * 2 > mHeight) { 188 return false; 189 } 190 191 uint16_t glyphW = glyph.fWidth + TEXTURE_BORDER_SIZE; 192 uint16_t glyphH = glyph.fHeight + TEXTURE_BORDER_SIZE; 193 194 // roundedUpW equals glyphW to the next multiple of CACHE_BLOCK_ROUNDING_SIZE. 195 // This columns for glyphs that are close but not necessarily exactly the same size. It trades 196 // off the loss of a few pixels for some glyphs against the ability to store more glyphs 197 // of varying sizes in one block. 198 uint16_t roundedUpW = (glyphW + CACHE_BLOCK_ROUNDING_SIZE - 1) & -CACHE_BLOCK_ROUNDING_SIZE; 199 200 CacheBlock* cacheBlock = mCacheBlocks; 201 while (cacheBlock) { 202 // Store glyph in this block iff: it fits the block's remaining space and: 203 // it's the remainder space (mY == 0) or there's only enough height for this one glyph 204 // or it's within ROUNDING_SIZE of the block width 205 if (roundedUpW <= cacheBlock->mWidth && glyphH <= cacheBlock->mHeight && 206 (cacheBlock->mY == TEXTURE_BORDER_SIZE || 207 (cacheBlock->mWidth - roundedUpW < CACHE_BLOCK_ROUNDING_SIZE))) { 208 if (cacheBlock->mHeight - glyphH < glyphH) { 209 // Only enough space for this glyph - don't bother rounding up the width 210 roundedUpW = glyphW; 211 } 212 213 *retOriginX = cacheBlock->mX; 214 *retOriginY = cacheBlock->mY; 215 216 // If this is the remainder space, create a new cache block for this column. Otherwise, 217 // adjust the info about this column. 218 if (cacheBlock->mY == TEXTURE_BORDER_SIZE) { 219 uint16_t oldX = cacheBlock->mX; 220 // Adjust remainder space dimensions 221 cacheBlock->mWidth -= roundedUpW; 222 cacheBlock->mX += roundedUpW; 223 224 if (mHeight - glyphH >= glyphH) { 225 // There's enough height left over to create a new CacheBlock 226 CacheBlock* newBlock = new CacheBlock(oldX, glyphH + TEXTURE_BORDER_SIZE, 227 roundedUpW, mHeight - glyphH - TEXTURE_BORDER_SIZE); 228#if DEBUG_FONT_RENDERER 229 ALOGD("fitBitmap: Created new block: this, x, y, w, h = %p, %d, %d, %d, %d", 230 newBlock, newBlock->mX, newBlock->mY, 231 newBlock->mWidth, newBlock->mHeight); 232#endif 233 mCacheBlocks = CacheBlock::insertBlock(mCacheBlocks, newBlock); 234 } 235 } else { 236 // Insert into current column and adjust column dimensions 237 cacheBlock->mY += glyphH; 238 cacheBlock->mHeight -= glyphH; 239#if DEBUG_FONT_RENDERER 240 ALOGD("fitBitmap: Added to existing block: this, x, y, w, h = %p, %d, %d, %d, %d", 241 cacheBlock, cacheBlock->mX, cacheBlock->mY, 242 cacheBlock->mWidth, cacheBlock->mHeight); 243#endif 244 } 245 246 if (cacheBlock->mHeight < fmin(glyphH, glyphW)) { 247 // If remaining space in this block is too small to be useful, remove it 248 mCacheBlocks = CacheBlock::removeBlock(mCacheBlocks, cacheBlock); 249 } 250 251 mDirty = true; 252 const Rect r(*retOriginX - TEXTURE_BORDER_SIZE, *retOriginY - TEXTURE_BORDER_SIZE, 253 *retOriginX + glyphW, *retOriginY + glyphH); 254 mDirtyRect.unionWith(r); 255 mNumGlyphs++; 256 257#if DEBUG_FONT_RENDERER 258 ALOGD("fitBitmap: current block list:"); 259 mCacheBlocks->output(); 260#endif 261 262 return true; 263 } 264 cacheBlock = cacheBlock->mNext; 265 } 266#if DEBUG_FONT_RENDERER 267 ALOGD("fitBitmap: returning false for glyph of size %d, %d", glyphW, glyphH); 268#endif 269 return false; 270} 271 272}; // namespace uirenderer 273}; // namespace android 274