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