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 "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         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 
    112 CacheTexture::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 
    130 CacheTexture::~CacheTexture() {
    131     releaseMesh();
    132     releasePixelBuffer();
    133     reset();
    134 }
    135 
    136 void 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 
    147 void 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 
    154 void CacheTexture::releaseMesh() {
    155     delete[] mMesh;
    156 }
    157 
    158 void 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 
    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     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 
    204 bool CacheTexture::upload() {
    205     const Rect& dirtyRect = mDirtyRect;
    206 
    207     uint32_t x = mHasUnpackRowLength ? dirtyRect.left : 0;
    208     uint32_t y = dirtyRect.top;
    209     uint32_t width = mHasUnpackRowLength ? dirtyRect.getWidth() : getWidth();
    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 (mHasUnpackRowLength) {
    215         glPixelStorei(GL_UNPACK_ROW_LENGTH, getWidth());
    216     }
    217 
    218     mPixelBuffer->upload(x, y, width, height);
    219     setDirty(false);
    220 
    221     return mHasUnpackRowLength;
    222 }
    223 
    224 void CacheTexture::setDirty(bool dirty) {
    225     mDirty = dirty;
    226     if (!dirty) {
    227         mDirtyRect.setEmpty();
    228     }
    229 }
    230 
    231 bool CacheTexture::fitBitmap(const SkGlyph& glyph, uint32_t* retOriginX, uint32_t* retOriginY) {
    232     switch (glyph.fMaskFormat) {
    233         case SkMask::kA8_Format:
    234         case SkMask::kBW_Format:
    235             if (mFormat != GL_ALPHA) {
    236 #if DEBUG_FONT_RENDERER
    237                 ALOGD("fitBitmap: texture format %x is inappropriate for monochromatic glyphs",
    238                         mFormat);
    239 #endif
    240                 return false;
    241             }
    242             break;
    243         case SkMask::kARGB32_Format:
    244             if (mFormat != GL_RGBA) {
    245 #if DEBUG_FONT_RENDERER
    246                 ALOGD("fitBitmap: texture format %x is inappropriate for colour glyphs", mFormat);
    247 #endif
    248                 return false;
    249             }
    250             break;
    251         default:
    252 #if DEBUG_FONT_RENDERER
    253             ALOGD("fitBitmap: unknown glyph format %x encountered", glyph.fMaskFormat);
    254 #endif
    255             return false;
    256     }
    257 
    258     if (glyph.fHeight + TEXTURE_BORDER_SIZE * 2 > getHeight()) {
    259         return false;
    260     }
    261 
    262     uint16_t glyphW = glyph.fWidth + TEXTURE_BORDER_SIZE;
    263     uint16_t glyphH = glyph.fHeight + TEXTURE_BORDER_SIZE;
    264 
    265     // roundedUpW equals glyphW to the next multiple of CACHE_BLOCK_ROUNDING_SIZE.
    266     // This columns for glyphs that are close but not necessarily exactly the same size. It trades
    267     // off the loss of a few pixels for some glyphs against the ability to store more glyphs
    268     // of varying sizes in one block.
    269     uint16_t roundedUpW = (glyphW + CACHE_BLOCK_ROUNDING_SIZE - 1) & -CACHE_BLOCK_ROUNDING_SIZE;
    270 
    271     CacheBlock* cacheBlock = mCacheBlocks;
    272     while (cacheBlock) {
    273         // Store glyph in this block iff: it fits the block's remaining space and:
    274         // it's the remainder space (mY == 0) or there's only enough height for this one glyph
    275         // or it's within ROUNDING_SIZE of the block width
    276         if (roundedUpW <= cacheBlock->mWidth && glyphH <= cacheBlock->mHeight &&
    277                 (cacheBlock->mY == TEXTURE_BORDER_SIZE ||
    278                         (cacheBlock->mWidth - roundedUpW < CACHE_BLOCK_ROUNDING_SIZE))) {
    279             if (cacheBlock->mHeight - glyphH < glyphH) {
    280                 // Only enough space for this glyph - don't bother rounding up the width
    281                 roundedUpW = glyphW;
    282             }
    283 
    284             *retOriginX = cacheBlock->mX;
    285             *retOriginY = cacheBlock->mY;
    286 
    287             // If this is the remainder space, create a new cache block for this column. Otherwise,
    288             // adjust the info about this column.
    289             if (cacheBlock->mY == TEXTURE_BORDER_SIZE) {
    290                 uint16_t oldX = cacheBlock->mX;
    291                 // Adjust remainder space dimensions
    292                 cacheBlock->mWidth -= roundedUpW;
    293                 cacheBlock->mX += roundedUpW;
    294 
    295                 if (getHeight() - glyphH >= glyphH) {
    296                     // There's enough height left over to create a new CacheBlock
    297                     CacheBlock* newBlock = new CacheBlock(oldX, glyphH + TEXTURE_BORDER_SIZE,
    298                             roundedUpW, getHeight() - glyphH - TEXTURE_BORDER_SIZE);
    299 #if DEBUG_FONT_RENDERER
    300                     ALOGD("fitBitmap: Created new block: this, x, y, w, h = %p, %d, %d, %d, %d",
    301                             newBlock, newBlock->mX, newBlock->mY,
    302                             newBlock->mWidth, newBlock->mHeight);
    303 #endif
    304                     mCacheBlocks = CacheBlock::insertBlock(mCacheBlocks, newBlock);
    305                 }
    306             } else {
    307                 // Insert into current column and adjust column dimensions
    308                 cacheBlock->mY += glyphH;
    309                 cacheBlock->mHeight -= glyphH;
    310 #if DEBUG_FONT_RENDERER
    311                 ALOGD("fitBitmap: Added to existing block: this, x, y, w, h = %p, %d, %d, %d, %d",
    312                         cacheBlock, cacheBlock->mX, cacheBlock->mY,
    313                         cacheBlock->mWidth, cacheBlock->mHeight);
    314 #endif
    315             }
    316 
    317             if (cacheBlock->mHeight < std::min(glyphH, glyphW)) {
    318                 // If remaining space in this block is too small to be useful, remove it
    319                 mCacheBlocks = CacheBlock::removeBlock(mCacheBlocks, cacheBlock);
    320             }
    321 
    322             mDirty = true;
    323             const Rect r(*retOriginX - TEXTURE_BORDER_SIZE, *retOriginY - TEXTURE_BORDER_SIZE,
    324                     *retOriginX + glyphW, *retOriginY + glyphH);
    325             mDirtyRect.unionWith(r);
    326             mNumGlyphs++;
    327 
    328 #if DEBUG_FONT_RENDERER
    329             ALOGD("fitBitmap: current block list:");
    330             mCacheBlocks->output();
    331 #endif
    332 
    333             return true;
    334         }
    335         cacheBlock = cacheBlock->mNext;
    336     }
    337 #if DEBUG_FONT_RENDERER
    338     ALOGD("fitBitmap: returning false for glyph of size %d, %d", glyphW, glyphH);
    339 #endif
    340     return false;
    341 }
    342 
    343 }; // namespace uirenderer
    344 }; // namespace android
    345