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 "../Caches.h" 21 #include "../Debug.h" 22 #include "../Extensions.h" 23 #include "../PixelBuffer.h" 24 25 namespace android { 26 namespace uirenderer { 27 28 /////////////////////////////////////////////////////////////////////////////// 29 // CacheBlock 30 /////////////////////////////////////////////////////////////////////////////// 31 32 /** 33 * Insert new block into existing linked list of blocks. Blocks are sorted in increasing-width 34 * order, except for the final block (the remainder space at the right, since we fill from the 35 * left). 36 */ 37 CacheBlock* CacheBlock::insertBlock(CacheBlock* head, CacheBlock* newBlock) { 38 #if DEBUG_FONT_RENDERER 39 ALOGD("insertBlock: this, x, y, w, h = %p, %d, %d, %d, %d", 40 newBlock, newBlock->mX, newBlock->mY, 41 newBlock->mWidth, newBlock->mHeight); 42 #endif 43 44 CacheBlock* currBlock = head; 45 CacheBlock* prevBlock = NULL; 46 47 while (currBlock && currBlock->mY != TEXTURE_BORDER_SIZE) { 48 if (newBlock->mWidth < currBlock->mWidth) { 49 newBlock->mNext = currBlock; 50 newBlock->mPrev = prevBlock; 51 currBlock->mPrev = newBlock; 52 53 if (prevBlock) { 54 prevBlock->mNext = newBlock; 55 return head; 56 } else { 57 return newBlock; 58 } 59 } 60 61 prevBlock = currBlock; 62 currBlock = currBlock->mNext; 63 } 64 65 // new block larger than all others - insert at end (but before the remainder space, if there) 66 newBlock->mNext = currBlock; 67 newBlock->mPrev = prevBlock; 68 69 if (currBlock) { 70 currBlock->mPrev = newBlock; 71 } 72 73 if (prevBlock) { 74 prevBlock->mNext = newBlock; 75 return head; 76 } else { 77 return newBlock; 78 } 79 } 80 81 CacheBlock* CacheBlock::removeBlock(CacheBlock* head, CacheBlock* blockToRemove) { 82 #if DEBUG_FONT_RENDERER 83 ALOGD("removeBlock: this, x, y, w, h = %p, %d, %d, %d, %d", 84 blockToRemove, blockToRemove->mX, blockToRemove->mY, 85 blockToRemove->mWidth, blockToRemove->mHeight); 86 #endif 87 88 CacheBlock* newHead = head; 89 CacheBlock* nextBlock = blockToRemove->mNext; 90 CacheBlock* prevBlock = blockToRemove->mPrev; 91 92 if (prevBlock) { 93 prevBlock->mNext = nextBlock; 94 } else { 95 newHead = nextBlock; 96 } 97 98 if (nextBlock) { 99 nextBlock->mPrev = prevBlock; 100 } 101 102 delete blockToRemove; 103 104 return newHead; 105 } 106 107 /////////////////////////////////////////////////////////////////////////////// 108 // CacheTexture 109 /////////////////////////////////////////////////////////////////////////////// 110 111 CacheTexture::CacheTexture(uint16_t width, uint16_t height, GLenum format, uint32_t maxQuadCount) : 112 mTexture(NULL), mTextureId(0), mWidth(width), mHeight(height), mFormat(format), 113 mLinearFiltering(false), mDirty(false), mNumGlyphs(0), 114 mMesh(NULL), mCurrentQuad(0), mMaxQuadCount(maxQuadCount), 115 mCaches(Caches::getInstance()) { 116 mCacheBlocks = new CacheBlock(TEXTURE_BORDER_SIZE, TEXTURE_BORDER_SIZE, 117 mWidth - TEXTURE_BORDER_SIZE, mHeight - TEXTURE_BORDER_SIZE, true); 118 119 // OpenGL ES 3.0+ lets us specify the row length for unpack operations such 120 // as glTexSubImage2D(). This allows us to upload a sub-rectangle of a texture. 121 // With OpenGL ES 2.0 we have to upload entire stripes instead. 122 mHasUnpackRowLength = Extensions::getInstance().hasUnpackRowLength(); 123 } 124 125 CacheTexture::~CacheTexture() { 126 releaseMesh(); 127 releaseTexture(); 128 reset(); 129 } 130 131 void CacheTexture::reset() { 132 // Delete existing cache blocks 133 while (mCacheBlocks != NULL) { 134 CacheBlock* tmpBlock = mCacheBlocks; 135 mCacheBlocks = mCacheBlocks->mNext; 136 delete tmpBlock; 137 } 138 mNumGlyphs = 0; 139 mCurrentQuad = 0; 140 } 141 142 void CacheTexture::init() { 143 // reset, then create a new remainder space to start again 144 reset(); 145 mCacheBlocks = new CacheBlock(TEXTURE_BORDER_SIZE, TEXTURE_BORDER_SIZE, 146 mWidth - TEXTURE_BORDER_SIZE, mHeight - TEXTURE_BORDER_SIZE, true); 147 } 148 149 void CacheTexture::releaseMesh() { 150 delete[] mMesh; 151 } 152 153 void CacheTexture::releaseTexture() { 154 if (mTexture) { 155 delete mTexture; 156 mTexture = NULL; 157 } 158 if (mTextureId) { 159 mCaches.deleteTexture(mTextureId); 160 mTextureId = 0; 161 } 162 mDirty = false; 163 mCurrentQuad = 0; 164 } 165 166 void CacheTexture::setLinearFiltering(bool linearFiltering, bool bind) { 167 if (linearFiltering != mLinearFiltering) { 168 mLinearFiltering = linearFiltering; 169 170 const GLenum filtering = linearFiltering ? GL_LINEAR : GL_NEAREST; 171 if (bind) mCaches.bindTexture(getTextureId()); 172 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, filtering); 173 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, filtering); 174 } 175 } 176 177 void CacheTexture::allocateMesh() { 178 if (!mMesh) { 179 mMesh = new TextureVertex[mMaxQuadCount * 4]; 180 } 181 } 182 183 void CacheTexture::allocateTexture() { 184 if (!mTexture) { 185 mTexture = PixelBuffer::create(mFormat, mWidth, mHeight); 186 } 187 188 if (!mTextureId) { 189 glGenTextures(1, &mTextureId); 190 191 mCaches.bindTexture(mTextureId); 192 glPixelStorei(GL_UNPACK_ALIGNMENT, 1); 193 // Initialize texture dimensions 194 glTexImage2D(GL_TEXTURE_2D, 0, mFormat, mWidth, mHeight, 0, 195 mFormat, GL_UNSIGNED_BYTE, 0); 196 197 const GLenum filtering = getLinearFiltering() ? GL_LINEAR : GL_NEAREST; 198 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, filtering); 199 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, filtering); 200 201 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); 202 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); 203 } 204 } 205 206 bool CacheTexture::upload() { 207 const Rect& dirtyRect = mDirtyRect; 208 209 uint32_t x = mHasUnpackRowLength ? dirtyRect.left : 0; 210 uint32_t y = dirtyRect.top; 211 uint32_t width = mHasUnpackRowLength ? dirtyRect.getWidth() : mWidth; 212 uint32_t height = dirtyRect.getHeight(); 213 214 // The unpack row length only needs to be specified when a new 215 // texture is bound 216 if (mHasUnpackRowLength) { 217 glPixelStorei(GL_UNPACK_ROW_LENGTH, mWidth); 218 } 219 220 mTexture->upload(x, y, width, height); 221 setDirty(false); 222 223 return mHasUnpackRowLength; 224 } 225 226 void CacheTexture::setDirty(bool dirty) { 227 mDirty = dirty; 228 if (!dirty) { 229 mDirtyRect.setEmpty(); 230 } 231 } 232 233 bool CacheTexture::fitBitmap(const SkGlyph& glyph, uint32_t* retOriginX, uint32_t* retOriginY) { 234 switch (glyph.fMaskFormat) { 235 case SkMask::kA8_Format: 236 case SkMask::kBW_Format: 237 if (mFormat != GL_ALPHA) { 238 #if DEBUG_FONT_RENDERER 239 ALOGD("fitBitmap: texture format %x is inappropriate for monochromatic glyphs", 240 mFormat); 241 #endif 242 return false; 243 } 244 break; 245 case SkMask::kARGB32_Format: 246 if (mFormat != GL_RGBA) { 247 #if DEBUG_FONT_RENDERER 248 ALOGD("fitBitmap: texture format %x is inappropriate for colour glyphs", mFormat); 249 #endif 250 return false; 251 } 252 break; 253 default: 254 #if DEBUG_FONT_RENDERER 255 ALOGD("fitBitmap: unknown glyph format %x encountered", glyph.fMaskFormat); 256 #endif 257 return false; 258 } 259 260 if (glyph.fHeight + TEXTURE_BORDER_SIZE * 2 > mHeight) { 261 return false; 262 } 263 264 uint16_t glyphW = glyph.fWidth + TEXTURE_BORDER_SIZE; 265 uint16_t glyphH = glyph.fHeight + TEXTURE_BORDER_SIZE; 266 267 // roundedUpW equals glyphW to the next multiple of CACHE_BLOCK_ROUNDING_SIZE. 268 // This columns for glyphs that are close but not necessarily exactly the same size. It trades 269 // off the loss of a few pixels for some glyphs against the ability to store more glyphs 270 // of varying sizes in one block. 271 uint16_t roundedUpW = (glyphW + CACHE_BLOCK_ROUNDING_SIZE - 1) & -CACHE_BLOCK_ROUNDING_SIZE; 272 273 CacheBlock* cacheBlock = mCacheBlocks; 274 while (cacheBlock) { 275 // Store glyph in this block iff: it fits the block's remaining space and: 276 // it's the remainder space (mY == 0) or there's only enough height for this one glyph 277 // or it's within ROUNDING_SIZE of the block width 278 if (roundedUpW <= cacheBlock->mWidth && glyphH <= cacheBlock->mHeight && 279 (cacheBlock->mY == TEXTURE_BORDER_SIZE || 280 (cacheBlock->mWidth - roundedUpW < CACHE_BLOCK_ROUNDING_SIZE))) { 281 if (cacheBlock->mHeight - glyphH < glyphH) { 282 // Only enough space for this glyph - don't bother rounding up the width 283 roundedUpW = glyphW; 284 } 285 286 *retOriginX = cacheBlock->mX; 287 *retOriginY = cacheBlock->mY; 288 289 // If this is the remainder space, create a new cache block for this column. Otherwise, 290 // adjust the info about this column. 291 if (cacheBlock->mY == TEXTURE_BORDER_SIZE) { 292 uint16_t oldX = cacheBlock->mX; 293 // Adjust remainder space dimensions 294 cacheBlock->mWidth -= roundedUpW; 295 cacheBlock->mX += roundedUpW; 296 297 if (mHeight - glyphH >= glyphH) { 298 // There's enough height left over to create a new CacheBlock 299 CacheBlock* newBlock = new CacheBlock(oldX, glyphH + TEXTURE_BORDER_SIZE, 300 roundedUpW, mHeight - glyphH - TEXTURE_BORDER_SIZE); 301 #if DEBUG_FONT_RENDERER 302 ALOGD("fitBitmap: Created new block: this, x, y, w, h = %p, %d, %d, %d, %d", 303 newBlock, newBlock->mX, newBlock->mY, 304 newBlock->mWidth, newBlock->mHeight); 305 #endif 306 mCacheBlocks = CacheBlock::insertBlock(mCacheBlocks, newBlock); 307 } 308 } else { 309 // Insert into current column and adjust column dimensions 310 cacheBlock->mY += glyphH; 311 cacheBlock->mHeight -= glyphH; 312 #if DEBUG_FONT_RENDERER 313 ALOGD("fitBitmap: Added to existing block: this, x, y, w, h = %p, %d, %d, %d, %d", 314 cacheBlock, cacheBlock->mX, cacheBlock->mY, 315 cacheBlock->mWidth, cacheBlock->mHeight); 316 #endif 317 } 318 319 if (cacheBlock->mHeight < fmin(glyphH, glyphW)) { 320 // If remaining space in this block is too small to be useful, remove it 321 mCacheBlocks = CacheBlock::removeBlock(mCacheBlocks, cacheBlock); 322 } 323 324 mDirty = true; 325 const Rect r(*retOriginX - TEXTURE_BORDER_SIZE, *retOriginY - TEXTURE_BORDER_SIZE, 326 *retOriginX + glyphW, *retOriginY + glyphH); 327 mDirtyRect.unionWith(r); 328 mNumGlyphs++; 329 330 #if DEBUG_FONT_RENDERER 331 ALOGD("fitBitmap: current block list:"); 332 mCacheBlocks->output(); 333 #endif 334 335 return true; 336 } 337 cacheBlock = cacheBlock->mNext; 338 } 339 #if DEBUG_FONT_RENDERER 340 ALOGD("fitBitmap: returning false for glyph of size %d, %d", glyphW, glyphH); 341 #endif 342 return false; 343 } 344 345 }; // namespace uirenderer 346 }; // namespace android 347