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