• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 #include "../Extensions.h"
22 #include "../PixelBuffer.h"
23 
24 namespace android {
25 namespace uirenderer {
26 
27 ///////////////////////////////////////////////////////////////////////////////
28 // CacheBlock
29 ///////////////////////////////////////////////////////////////////////////////
30 
31 /**
32  * Insert new block into existing linked list of blocks. Blocks are sorted in increasing-width
33  * order, except for the final block (the remainder space at the right, since we fill from the
34  * left).
35  */
insertBlock(CacheBlock * head,CacheBlock * newBlock)36 CacheBlock* CacheBlock::insertBlock(CacheBlock* head, CacheBlock* newBlock) {
37 #if DEBUG_FONT_RENDERER
38     ALOGD("insertBlock: this, x, y, w, h = %p, %d, %d, %d, %d",
39             newBlock, newBlock->mX, newBlock->mY,
40             newBlock->mWidth, newBlock->mHeight);
41 #endif
42 
43     CacheBlock* currBlock = head;
44     CacheBlock* prevBlock = NULL;
45 
46     while (currBlock && currBlock->mY != TEXTURE_BORDER_SIZE) {
47         if (newBlock->mWidth < currBlock->mWidth) {
48             newBlock->mNext = currBlock;
49             newBlock->mPrev = prevBlock;
50             currBlock->mPrev = newBlock;
51 
52             if (prevBlock) {
53                 prevBlock->mNext = newBlock;
54                 return head;
55             } else {
56                 return newBlock;
57             }
58         }
59 
60         prevBlock = currBlock;
61         currBlock = currBlock->mNext;
62     }
63 
64     // new block larger than all others - insert at end (but before the remainder space, if there)
65     newBlock->mNext = currBlock;
66     newBlock->mPrev = prevBlock;
67 
68     if (currBlock) {
69         currBlock->mPrev = newBlock;
70     }
71 
72     if (prevBlock) {
73         prevBlock->mNext = newBlock;
74         return head;
75     } else {
76         return newBlock;
77     }
78 }
79 
removeBlock(CacheBlock * head,CacheBlock * blockToRemove)80 CacheBlock* CacheBlock::removeBlock(CacheBlock* head, CacheBlock* blockToRemove) {
81 #if DEBUG_FONT_RENDERER
82     ALOGD("removeBlock: this, x, y, w, h = %p, %d, %d, %d, %d",
83             blockToRemove, blockToRemove->mX, blockToRemove->mY,
84             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         prevBlock->mNext = nextBlock;
93     } else {
94         newHead = nextBlock;
95     }
96 
97     if (nextBlock) {
98         nextBlock->mPrev = prevBlock;
99     }
100 
101     delete blockToRemove;
102 
103     return newHead;
104 }
105 
106 ///////////////////////////////////////////////////////////////////////////////
107 // CacheTexture
108 ///////////////////////////////////////////////////////////////////////////////
109 
CacheTexture(uint16_t width,uint16_t height,uint32_t maxQuadCount)110 CacheTexture::CacheTexture(uint16_t width, uint16_t height, uint32_t maxQuadCount) :
111             mTexture(NULL), mTextureId(0), mWidth(width), mHeight(height),
112             mLinearFiltering(false), mDirty(false), mNumGlyphs(0),
113             mMesh(NULL), mCurrentQuad(0), mMaxQuadCount(maxQuadCount) {
114     mCacheBlocks = new CacheBlock(TEXTURE_BORDER_SIZE, TEXTURE_BORDER_SIZE,
115             mWidth - TEXTURE_BORDER_SIZE, mHeight - TEXTURE_BORDER_SIZE, true);
116 
117     // OpenGL ES 3.0+ lets us specify the row length for unpack operations such
118     // as glTexSubImage2D(). This allows us to upload a sub-rectangle of a texture.
119     // With OpenGL ES 2.0 we have to upload entire stripes instead.
120     mHasES3 = Extensions::getInstance().getMajorGlVersion() >= 3;
121 }
122 
~CacheTexture()123 CacheTexture::~CacheTexture() {
124     releaseMesh();
125     releaseTexture();
126     reset();
127 }
128 
reset()129 void CacheTexture::reset() {
130     // Delete existing cache blocks
131     while (mCacheBlocks != NULL) {
132         CacheBlock* tmpBlock = mCacheBlocks;
133         mCacheBlocks = mCacheBlocks->mNext;
134         delete tmpBlock;
135     }
136     mNumGlyphs = 0;
137     mCurrentQuad = 0;
138 }
139 
init()140 void CacheTexture::init() {
141     // reset, then create a new remainder space to start again
142     reset();
143     mCacheBlocks = new CacheBlock(TEXTURE_BORDER_SIZE, TEXTURE_BORDER_SIZE,
144             mWidth - TEXTURE_BORDER_SIZE, mHeight - TEXTURE_BORDER_SIZE, true);
145 }
146 
releaseMesh()147 void CacheTexture::releaseMesh() {
148     delete[] mMesh;
149 }
150 
releaseTexture()151 void CacheTexture::releaseTexture() {
152     if (mTexture) {
153         delete mTexture;
154         mTexture = NULL;
155     }
156     if (mTextureId) {
157         glDeleteTextures(1, &mTextureId);
158         mTextureId = 0;
159     }
160     mDirty = false;
161     mCurrentQuad = 0;
162 }
163 
setLinearFiltering(bool linearFiltering,bool bind)164 void CacheTexture::setLinearFiltering(bool linearFiltering, bool bind) {
165    if (linearFiltering != mLinearFiltering) {
166        mLinearFiltering = linearFiltering;
167 
168        const GLenum filtering = linearFiltering ? GL_LINEAR : GL_NEAREST;
169        if (bind) glBindTexture(GL_TEXTURE_2D, getTextureId());
170        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, filtering);
171        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, filtering);
172    }
173 }
174 
allocateMesh()175 void CacheTexture::allocateMesh() {
176     if (!mMesh) {
177         mMesh = new TextureVertex[mMaxQuadCount * 4];
178     }
179 }
180 
allocateTexture()181 void CacheTexture::allocateTexture() {
182     if (!mTexture) {
183         mTexture = PixelBuffer::create(GL_ALPHA, mWidth, mHeight);
184     }
185 
186     if (!mTextureId) {
187         glGenTextures(1, &mTextureId);
188 
189         glBindTexture(GL_TEXTURE_2D, mTextureId);
190         glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
191         // Initialize texture dimensions
192         glTexImage2D(GL_TEXTURE_2D, 0, GL_ALPHA, mWidth, mHeight, 0,
193                 GL_ALPHA, GL_UNSIGNED_BYTE, 0);
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 
upload()204 bool CacheTexture::upload() {
205     const Rect& dirtyRect = mDirtyRect;
206 
207     uint32_t x = mHasES3 ? dirtyRect.left : 0;
208     uint32_t y = dirtyRect.top;
209     uint32_t width = mHasES3 ? dirtyRect.getWidth() : mWidth;
210     uint32_t height = dirtyRect.getHeight();
211 
212     // The unpack row length only needs to be specified when a new
213     // texture is bound
214     if (mHasES3) {
215         glPixelStorei(GL_UNPACK_ROW_LENGTH, mWidth);
216     }
217 
218     mTexture->upload(x, y, width, height, y * mWidth + x);
219 
220     setDirty(false);
221 
222     return mHasES3;
223 }
224 
setDirty(bool dirty)225 void CacheTexture::setDirty(bool dirty) {
226     mDirty = dirty;
227     if (!dirty) {
228         mDirtyRect.setEmpty();
229     }
230 }
231 
fitBitmap(const SkGlyph & glyph,uint32_t * retOriginX,uint32_t * retOriginY)232 bool CacheTexture::fitBitmap(const SkGlyph& glyph, uint32_t* retOriginX, uint32_t* retOriginY) {
233     if (glyph.fHeight + TEXTURE_BORDER_SIZE * 2 > mHeight) {
234         return false;
235     }
236 
237     uint16_t glyphW = glyph.fWidth + TEXTURE_BORDER_SIZE;
238     uint16_t glyphH = glyph.fHeight + TEXTURE_BORDER_SIZE;
239 
240     // roundedUpW equals glyphW to the next multiple of CACHE_BLOCK_ROUNDING_SIZE.
241     // This columns for glyphs that are close but not necessarily exactly the same size. It trades
242     // off the loss of a few pixels for some glyphs against the ability to store more glyphs
243     // of varying sizes in one block.
244     uint16_t roundedUpW = (glyphW + CACHE_BLOCK_ROUNDING_SIZE - 1) & -CACHE_BLOCK_ROUNDING_SIZE;
245 
246     CacheBlock* cacheBlock = mCacheBlocks;
247     while (cacheBlock) {
248         // Store glyph in this block iff: it fits the block's remaining space and:
249         // it's the remainder space (mY == 0) or there's only enough height for this one glyph
250         // or it's within ROUNDING_SIZE of the block width
251         if (roundedUpW <= cacheBlock->mWidth && glyphH <= cacheBlock->mHeight &&
252                 (cacheBlock->mY == TEXTURE_BORDER_SIZE ||
253                         (cacheBlock->mWidth - roundedUpW < CACHE_BLOCK_ROUNDING_SIZE))) {
254             if (cacheBlock->mHeight - glyphH < glyphH) {
255                 // Only enough space for this glyph - don't bother rounding up the width
256                 roundedUpW = glyphW;
257             }
258 
259             *retOriginX = cacheBlock->mX;
260             *retOriginY = cacheBlock->mY;
261 
262             // If this is the remainder space, create a new cache block for this column. Otherwise,
263             // adjust the info about this column.
264             if (cacheBlock->mY == TEXTURE_BORDER_SIZE) {
265                 uint16_t oldX = cacheBlock->mX;
266                 // Adjust remainder space dimensions
267                 cacheBlock->mWidth -= roundedUpW;
268                 cacheBlock->mX += roundedUpW;
269 
270                 if (mHeight - glyphH >= glyphH) {
271                     // There's enough height left over to create a new CacheBlock
272                     CacheBlock* newBlock = new CacheBlock(oldX, glyphH + TEXTURE_BORDER_SIZE,
273                             roundedUpW, mHeight - glyphH - TEXTURE_BORDER_SIZE);
274 #if DEBUG_FONT_RENDERER
275                     ALOGD("fitBitmap: Created new block: this, x, y, w, h = %p, %d, %d, %d, %d",
276                             newBlock, newBlock->mX, newBlock->mY,
277                             newBlock->mWidth, newBlock->mHeight);
278 #endif
279                     mCacheBlocks = CacheBlock::insertBlock(mCacheBlocks, newBlock);
280                 }
281             } else {
282                 // Insert into current column and adjust column dimensions
283                 cacheBlock->mY += glyphH;
284                 cacheBlock->mHeight -= glyphH;
285 #if DEBUG_FONT_RENDERER
286                 ALOGD("fitBitmap: Added to existing block: this, x, y, w, h = %p, %d, %d, %d, %d",
287                         cacheBlock, cacheBlock->mX, cacheBlock->mY,
288                         cacheBlock->mWidth, cacheBlock->mHeight);
289 #endif
290             }
291 
292             if (cacheBlock->mHeight < fmin(glyphH, glyphW)) {
293                 // If remaining space in this block is too small to be useful, remove it
294                 mCacheBlocks = CacheBlock::removeBlock(mCacheBlocks, cacheBlock);
295             }
296 
297             mDirty = true;
298             const Rect r(*retOriginX - TEXTURE_BORDER_SIZE, *retOriginY - TEXTURE_BORDER_SIZE,
299                     *retOriginX + glyphW, *retOriginY + glyphH);
300             mDirtyRect.unionWith(r);
301             mNumGlyphs++;
302 
303 #if DEBUG_FONT_RENDERER
304             ALOGD("fitBitmap: current block list:");
305             mCacheBlocks->output();
306 #endif
307 
308             return true;
309         }
310         cacheBlock = cacheBlock->mNext;
311     }
312 #if DEBUG_FONT_RENDERER
313     ALOGD("fitBitmap: returning false for glyph of size %d, %d", glyphW, glyphH);
314 #endif
315     return false;
316 }
317 
318 }; // namespace uirenderer
319 }; // namespace android
320