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