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