CacheTexture.cpp revision d41c4d8c732095ae99c955b6b82f7306633004b1
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
26namespace android {
27namespace 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 */
38CacheBlock* 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
82CacheBlock* 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
112CacheTexture::CacheTexture(uint16_t width, uint16_t height, GLenum format, uint32_t maxQuadCount) :
113            mTexture(nullptr), mTextureId(0), mWidth(width), mHeight(height), mFormat(format),
114            mLinearFiltering(false), mDirty(false), mNumGlyphs(0),
115            mMesh(nullptr), mCurrentQuad(0), mMaxQuadCount(maxQuadCount),
116            mCaches(Caches::getInstance()) {
117    mCacheBlocks = new CacheBlock(TEXTURE_BORDER_SIZE, TEXTURE_BORDER_SIZE,
118            mWidth - TEXTURE_BORDER_SIZE, mHeight - TEXTURE_BORDER_SIZE);
119
120    // OpenGL ES 3.0+ lets us specify the row length for unpack operations such
121    // as glTexSubImage2D(). This allows us to upload a sub-rectangle of a texture.
122    // With OpenGL ES 2.0 we have to upload entire stripes instead.
123    mHasUnpackRowLength = Extensions::getInstance().hasUnpackRowLength();
124}
125
126CacheTexture::~CacheTexture() {
127    releaseMesh();
128    releaseTexture();
129    reset();
130}
131
132void CacheTexture::reset() {
133    // Delete existing cache blocks
134    while (mCacheBlocks != nullptr) {
135        CacheBlock* tmpBlock = mCacheBlocks;
136        mCacheBlocks = mCacheBlocks->mNext;
137        delete tmpBlock;
138    }
139    mNumGlyphs = 0;
140    mCurrentQuad = 0;
141}
142
143void CacheTexture::init() {
144    // reset, then create a new remainder space to start again
145    reset();
146    mCacheBlocks = new CacheBlock(TEXTURE_BORDER_SIZE, TEXTURE_BORDER_SIZE,
147            mWidth - TEXTURE_BORDER_SIZE, mHeight - TEXTURE_BORDER_SIZE);
148}
149
150void CacheTexture::releaseMesh() {
151    delete[] mMesh;
152}
153
154void CacheTexture::releaseTexture() {
155    if (mTexture) {
156        delete mTexture;
157        mTexture = nullptr;
158    }
159    if (mTextureId) {
160        mCaches.deleteTexture(mTextureId);
161        mTextureId = 0;
162    }
163    mDirty = false;
164    mCurrentQuad = 0;
165}
166
167void CacheTexture::setLinearFiltering(bool linearFiltering, bool bind) {
168   if (linearFiltering != mLinearFiltering) {
169       mLinearFiltering = linearFiltering;
170
171       const GLenum filtering = linearFiltering ? GL_LINEAR : GL_NEAREST;
172       if (bind) mCaches.bindTexture(getTextureId());
173       glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, filtering);
174       glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, filtering);
175   }
176}
177
178void CacheTexture::allocateMesh() {
179    if (!mMesh) {
180        mMesh = new TextureVertex[mMaxQuadCount * 4];
181    }
182}
183
184void CacheTexture::allocateTexture() {
185    if (!mTexture) {
186        mTexture = PixelBuffer::create(mFormat, mWidth, mHeight);
187    }
188
189    if (!mTextureId) {
190        glGenTextures(1, &mTextureId);
191
192        mCaches.bindTexture(mTextureId);
193        glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
194        // Initialize texture dimensions
195        glTexImage2D(GL_TEXTURE_2D, 0, mFormat, mWidth, mHeight, 0,
196                mFormat, GL_UNSIGNED_BYTE, nullptr);
197
198        const GLenum filtering = getLinearFiltering() ? GL_LINEAR : GL_NEAREST;
199        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, filtering);
200        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, filtering);
201
202        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
203        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
204    }
205}
206
207bool CacheTexture::upload() {
208    const Rect& dirtyRect = mDirtyRect;
209
210    uint32_t x = mHasUnpackRowLength ? dirtyRect.left : 0;
211    uint32_t y = dirtyRect.top;
212    uint32_t width = mHasUnpackRowLength ? dirtyRect.getWidth() : mWidth;
213    uint32_t height = dirtyRect.getHeight();
214
215    // The unpack row length only needs to be specified when a new
216    // texture is bound
217    if (mHasUnpackRowLength) {
218        glPixelStorei(GL_UNPACK_ROW_LENGTH, mWidth);
219    }
220
221    mTexture->upload(x, y, width, height);
222    setDirty(false);
223
224    return mHasUnpackRowLength;
225}
226
227void CacheTexture::setDirty(bool dirty) {
228    mDirty = dirty;
229    if (!dirty) {
230        mDirtyRect.setEmpty();
231    }
232}
233
234bool CacheTexture::fitBitmap(const SkGlyph& glyph, uint32_t* retOriginX, uint32_t* retOriginY) {
235    switch (glyph.fMaskFormat) {
236        case SkMask::kA8_Format:
237        case SkMask::kBW_Format:
238            if (mFormat != GL_ALPHA) {
239#if DEBUG_FONT_RENDERER
240                ALOGD("fitBitmap: texture format %x is inappropriate for monochromatic glyphs",
241                        mFormat);
242#endif
243                return false;
244            }
245            break;
246        case SkMask::kARGB32_Format:
247            if (mFormat != GL_RGBA) {
248#if DEBUG_FONT_RENDERER
249                ALOGD("fitBitmap: texture format %x is inappropriate for colour glyphs", mFormat);
250#endif
251                return false;
252            }
253            break;
254        default:
255#if DEBUG_FONT_RENDERER
256            ALOGD("fitBitmap: unknown glyph format %x encountered", glyph.fMaskFormat);
257#endif
258            return false;
259    }
260
261    if (glyph.fHeight + TEXTURE_BORDER_SIZE * 2 > mHeight) {
262        return false;
263    }
264
265    uint16_t glyphW = glyph.fWidth + TEXTURE_BORDER_SIZE;
266    uint16_t glyphH = glyph.fHeight + TEXTURE_BORDER_SIZE;
267
268    // roundedUpW equals glyphW to the next multiple of CACHE_BLOCK_ROUNDING_SIZE.
269    // This columns for glyphs that are close but not necessarily exactly the same size. It trades
270    // off the loss of a few pixels for some glyphs against the ability to store more glyphs
271    // of varying sizes in one block.
272    uint16_t roundedUpW = (glyphW + CACHE_BLOCK_ROUNDING_SIZE - 1) & -CACHE_BLOCK_ROUNDING_SIZE;
273
274    CacheBlock* cacheBlock = mCacheBlocks;
275    while (cacheBlock) {
276        // Store glyph in this block iff: it fits the block's remaining space and:
277        // it's the remainder space (mY == 0) or there's only enough height for this one glyph
278        // or it's within ROUNDING_SIZE of the block width
279        if (roundedUpW <= cacheBlock->mWidth && glyphH <= cacheBlock->mHeight &&
280                (cacheBlock->mY == TEXTURE_BORDER_SIZE ||
281                        (cacheBlock->mWidth - roundedUpW < CACHE_BLOCK_ROUNDING_SIZE))) {
282            if (cacheBlock->mHeight - glyphH < glyphH) {
283                // Only enough space for this glyph - don't bother rounding up the width
284                roundedUpW = glyphW;
285            }
286
287            *retOriginX = cacheBlock->mX;
288            *retOriginY = cacheBlock->mY;
289
290            // If this is the remainder space, create a new cache block for this column. Otherwise,
291            // adjust the info about this column.
292            if (cacheBlock->mY == TEXTURE_BORDER_SIZE) {
293                uint16_t oldX = cacheBlock->mX;
294                // Adjust remainder space dimensions
295                cacheBlock->mWidth -= roundedUpW;
296                cacheBlock->mX += roundedUpW;
297
298                if (mHeight - glyphH >= glyphH) {
299                    // There's enough height left over to create a new CacheBlock
300                    CacheBlock* newBlock = new CacheBlock(oldX, glyphH + TEXTURE_BORDER_SIZE,
301                            roundedUpW, mHeight - glyphH - TEXTURE_BORDER_SIZE);
302#if DEBUG_FONT_RENDERER
303                    ALOGD("fitBitmap: Created new block: this, x, y, w, h = %p, %d, %d, %d, %d",
304                            newBlock, newBlock->mX, newBlock->mY,
305                            newBlock->mWidth, newBlock->mHeight);
306#endif
307                    mCacheBlocks = CacheBlock::insertBlock(mCacheBlocks, newBlock);
308                }
309            } else {
310                // Insert into current column and adjust column dimensions
311                cacheBlock->mY += glyphH;
312                cacheBlock->mHeight -= glyphH;
313#if DEBUG_FONT_RENDERER
314                ALOGD("fitBitmap: Added to existing block: this, x, y, w, h = %p, %d, %d, %d, %d",
315                        cacheBlock, cacheBlock->mX, cacheBlock->mY,
316                        cacheBlock->mWidth, cacheBlock->mHeight);
317#endif
318            }
319
320            if (cacheBlock->mHeight < fmin(glyphH, glyphW)) {
321                // If remaining space in this block is too small to be useful, remove it
322                mCacheBlocks = CacheBlock::removeBlock(mCacheBlocks, cacheBlock);
323            }
324
325            mDirty = true;
326            const Rect r(*retOriginX - TEXTURE_BORDER_SIZE, *retOriginY - TEXTURE_BORDER_SIZE,
327                    *retOriginX + glyphW, *retOriginY + glyphH);
328            mDirtyRect.unionWith(r);
329            mNumGlyphs++;
330
331#if DEBUG_FONT_RENDERER
332            ALOGD("fitBitmap: current block list:");
333            mCacheBlocks->output();
334#endif
335
336            return true;
337        }
338        cacheBlock = cacheBlock->mNext;
339    }
340#if DEBUG_FONT_RENDERER
341    ALOGD("fitBitmap: returning false for glyph of size %d, %d", glyphW, glyphH);
342#endif
343    return false;
344}
345
346}; // namespace uirenderer
347}; // namespace android
348