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