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