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