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