CacheTexture.cpp revision 8aa195d7081b889f3a7b1f426cbd8556377aae5e
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 25namespace android { 26namespace 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 */ 37CacheBlock* 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 81CacheBlock* 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 111CacheTexture::CacheTexture(uint16_t width, uint16_t height, uint32_t maxQuadCount) : 112 mTexture(NULL), mTextureId(0), mWidth(width), mHeight(height), 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 mHasES3 = Extensions::getInstance().getMajorGlVersion() >= 3; 123} 124 125CacheTexture::~CacheTexture() { 126 releaseMesh(); 127 releaseTexture(); 128 reset(); 129} 130 131void 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 142void 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 149void CacheTexture::releaseMesh() { 150 delete[] mMesh; 151} 152 153void CacheTexture::releaseTexture() { 154 if (mTexture) { 155 delete mTexture; 156 mTexture = NULL; 157 } 158 if (mTextureId) { 159 glDeleteTextures(1, &mTextureId); 160 mTextureId = 0; 161 } 162 mDirty = false; 163 mCurrentQuad = 0; 164} 165 166void 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 177void CacheTexture::allocateMesh() { 178 if (!mMesh) { 179 mMesh = new TextureVertex[mMaxQuadCount * 4]; 180 } 181} 182 183void CacheTexture::allocateTexture() { 184 if (!mTexture) { 185 mTexture = PixelBuffer::create(GL_ALPHA, 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, GL_ALPHA, mWidth, mHeight, 0, 195 GL_ALPHA, 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 206bool CacheTexture::upload() { 207 const Rect& dirtyRect = mDirtyRect; 208 209 uint32_t x = mHasES3 ? dirtyRect.left : 0; 210 uint32_t y = dirtyRect.top; 211 uint32_t width = mHasES3 ? 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 (mHasES3) { 217 glPixelStorei(GL_UNPACK_ROW_LENGTH, mWidth); 218 } 219 220 mTexture->upload(x, y, width, height, y * mWidth + x); 221 222 setDirty(false); 223 224 return mHasES3; 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 if (glyph.fHeight + TEXTURE_BORDER_SIZE * 2 > mHeight) { 236 return false; 237 } 238 239 uint16_t glyphW = glyph.fWidth + TEXTURE_BORDER_SIZE; 240 uint16_t glyphH = glyph.fHeight + TEXTURE_BORDER_SIZE; 241 242 // roundedUpW equals glyphW to the next multiple of CACHE_BLOCK_ROUNDING_SIZE. 243 // This columns for glyphs that are close but not necessarily exactly the same size. It trades 244 // off the loss of a few pixels for some glyphs against the ability to store more glyphs 245 // of varying sizes in one block. 246 uint16_t roundedUpW = (glyphW + CACHE_BLOCK_ROUNDING_SIZE - 1) & -CACHE_BLOCK_ROUNDING_SIZE; 247 248 CacheBlock* cacheBlock = mCacheBlocks; 249 while (cacheBlock) { 250 // Store glyph in this block iff: it fits the block's remaining space and: 251 // it's the remainder space (mY == 0) or there's only enough height for this one glyph 252 // or it's within ROUNDING_SIZE of the block width 253 if (roundedUpW <= cacheBlock->mWidth && glyphH <= cacheBlock->mHeight && 254 (cacheBlock->mY == TEXTURE_BORDER_SIZE || 255 (cacheBlock->mWidth - roundedUpW < CACHE_BLOCK_ROUNDING_SIZE))) { 256 if (cacheBlock->mHeight - glyphH < glyphH) { 257 // Only enough space for this glyph - don't bother rounding up the width 258 roundedUpW = glyphW; 259 } 260 261 *retOriginX = cacheBlock->mX; 262 *retOriginY = cacheBlock->mY; 263 264 // If this is the remainder space, create a new cache block for this column. Otherwise, 265 // adjust the info about this column. 266 if (cacheBlock->mY == TEXTURE_BORDER_SIZE) { 267 uint16_t oldX = cacheBlock->mX; 268 // Adjust remainder space dimensions 269 cacheBlock->mWidth -= roundedUpW; 270 cacheBlock->mX += roundedUpW; 271 272 if (mHeight - glyphH >= glyphH) { 273 // There's enough height left over to create a new CacheBlock 274 CacheBlock* newBlock = new CacheBlock(oldX, glyphH + TEXTURE_BORDER_SIZE, 275 roundedUpW, mHeight - glyphH - TEXTURE_BORDER_SIZE); 276#if DEBUG_FONT_RENDERER 277 ALOGD("fitBitmap: Created new block: this, x, y, w, h = %p, %d, %d, %d, %d", 278 newBlock, newBlock->mX, newBlock->mY, 279 newBlock->mWidth, newBlock->mHeight); 280#endif 281 mCacheBlocks = CacheBlock::insertBlock(mCacheBlocks, newBlock); 282 } 283 } else { 284 // Insert into current column and adjust column dimensions 285 cacheBlock->mY += glyphH; 286 cacheBlock->mHeight -= glyphH; 287#if DEBUG_FONT_RENDERER 288 ALOGD("fitBitmap: Added to existing block: this, x, y, w, h = %p, %d, %d, %d, %d", 289 cacheBlock, cacheBlock->mX, cacheBlock->mY, 290 cacheBlock->mWidth, cacheBlock->mHeight); 291#endif 292 } 293 294 if (cacheBlock->mHeight < fmin(glyphH, glyphW)) { 295 // If remaining space in this block is too small to be useful, remove it 296 mCacheBlocks = CacheBlock::removeBlock(mCacheBlocks, cacheBlock); 297 } 298 299 mDirty = true; 300 const Rect r(*retOriginX - TEXTURE_BORDER_SIZE, *retOriginY - TEXTURE_BORDER_SIZE, 301 *retOriginX + glyphW, *retOriginY + glyphH); 302 mDirtyRect.unionWith(r); 303 mNumGlyphs++; 304 305#if DEBUG_FONT_RENDERER 306 ALOGD("fitBitmap: current block list:"); 307 mCacheBlocks->output(); 308#endif 309 310 return true; 311 } 312 cacheBlock = cacheBlock->mNext; 313 } 314#if DEBUG_FONT_RENDERER 315 ALOGD("fitBitmap: returning false for glyph of size %d, %d", glyphW, glyphH); 316#endif 317 return false; 318} 319 320}; // namespace uirenderer 321}; // namespace android 322