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