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