blob: d5f38b55a38a04a1b9290ea4f33a3fbb8a34d3c5 [file] [log] [blame]
Romain Guy9f5dab32012-09-04 12:55:44 -07001/*
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
Derek Sollenbergerca79cf62012-08-14 16:44:52 -040017#include <SkGlyph.h>
Romain Guy9f5dab32012-09-04 12:55:44 -070018
Romain Guy9f5dab32012-09-04 12:55:44 -070019#include "CacheTexture.h"
Romain Guy8aa195d2013-06-04 18:00:09 -070020#include "../Caches.h"
Romain Guy09087642013-04-04 12:27:54 -070021#include "../Debug.h"
Romain Guycf51a412013-04-08 19:40:31 -070022#include "../Extensions.h"
23#include "../PixelBuffer.h"
Romain Guy9f5dab32012-09-04 12:55:44 -070024
25namespace android {
26namespace 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 */
Romain Guye43f7852012-09-04 18:58:46 -070037CacheBlock* CacheBlock::insertBlock(CacheBlock* head, CacheBlock* newBlock) {
Romain Guy9f5dab32012-09-04 12:55:44 -070038#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
Romain Guy9b1204b2012-09-04 15:22:57 -070043
Romain Guye43f7852012-09-04 18:58:46 -070044 CacheBlock* currBlock = head;
45 CacheBlock* prevBlock = NULL;
Romain Guy9b1204b2012-09-04 15:22:57 -070046
Romain Guy9f5dab32012-09-04 12:55:44 -070047 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;
Romain Guy9b1204b2012-09-04 15:22:57 -070052
Romain Guy9f5dab32012-09-04 12:55:44 -070053 if (prevBlock) {
54 prevBlock->mNext = newBlock;
55 return head;
56 } else {
57 return newBlock;
58 }
59 }
Romain Guy9b1204b2012-09-04 15:22:57 -070060
Romain Guy9f5dab32012-09-04 12:55:44 -070061 prevBlock = currBlock;
62 currBlock = currBlock->mNext;
63 }
Romain Guy9b1204b2012-09-04 15:22:57 -070064
Romain Guy9f5dab32012-09-04 12:55:44 -070065 // new block larger than all others - insert at end (but before the remainder space, if there)
66 newBlock->mNext = currBlock;
67 newBlock->mPrev = prevBlock;
Romain Guy9b1204b2012-09-04 15:22:57 -070068
Romain Guy9f5dab32012-09-04 12:55:44 -070069 if (currBlock) {
70 currBlock->mPrev = newBlock;
71 }
Romain Guy9b1204b2012-09-04 15:22:57 -070072
Romain Guy9f5dab32012-09-04 12:55:44 -070073 if (prevBlock) {
74 prevBlock->mNext = newBlock;
75 return head;
76 } else {
77 return newBlock;
78 }
79}
80
Romain Guye43f7852012-09-04 18:58:46 -070081CacheBlock* CacheBlock::removeBlock(CacheBlock* head, CacheBlock* blockToRemove) {
Romain Guy9f5dab32012-09-04 12:55:44 -070082#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
Romain Guy9b1204b2012-09-04 15:22:57 -070087
Romain Guy9f5dab32012-09-04 12:55:44 -070088 CacheBlock* newHead = head;
89 CacheBlock* nextBlock = blockToRemove->mNext;
90 CacheBlock* prevBlock = blockToRemove->mPrev;
Romain Guy9b1204b2012-09-04 15:22:57 -070091
Romain Guy9f5dab32012-09-04 12:55:44 -070092 if (prevBlock) {
93 prevBlock->mNext = nextBlock;
94 } else {
95 newHead = nextBlock;
96 }
Romain Guy9b1204b2012-09-04 15:22:57 -070097
Romain Guy9f5dab32012-09-04 12:55:44 -070098 if (nextBlock) {
99 nextBlock->mPrev = prevBlock;
100 }
Romain Guy9b1204b2012-09-04 15:22:57 -0700101
Romain Guy9f5dab32012-09-04 12:55:44 -0700102 delete blockToRemove;
Romain Guy9b1204b2012-09-04 15:22:57 -0700103
Romain Guy9f5dab32012-09-04 12:55:44 -0700104 return newHead;
105}
106
107///////////////////////////////////////////////////////////////////////////////
108// CacheTexture
109///////////////////////////////////////////////////////////////////////////////
110
Victoria Lease1e546812013-06-25 14:25:17 -0700111CacheTexture::CacheTexture(uint16_t width, uint16_t height, GLenum format, uint32_t maxQuadCount) :
112 mTexture(NULL), mTextureId(0), mWidth(width), mHeight(height), mFormat(format),
Romain Guy661a87e2013-03-19 15:24:36 -0700113 mLinearFiltering(false), mDirty(false), mNumGlyphs(0),
Romain Guy8aa195d2013-06-04 18:00:09 -0700114 mMesh(NULL), mCurrentQuad(0), mMaxQuadCount(maxQuadCount),
115 mCaches(Caches::getInstance()) {
Romain Guy661a87e2013-03-19 15:24:36 -0700116 mCacheBlocks = new CacheBlock(TEXTURE_BORDER_SIZE, TEXTURE_BORDER_SIZE,
117 mWidth - TEXTURE_BORDER_SIZE, mHeight - TEXTURE_BORDER_SIZE, true);
Romain Guycf51a412013-04-08 19:40:31 -0700118
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.
Romain Guy318ae7b2013-09-24 18:44:54 -0700122 mHasUnpackRowLength = Extensions::getInstance().hasUnpackRowLength();
Romain Guy661a87e2013-03-19 15:24:36 -0700123}
124
125CacheTexture::~CacheTexture() {
126 releaseMesh();
127 releaseTexture();
128 reset();
129}
130
131void 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
142void 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, true);
147}
148
149void CacheTexture::releaseMesh() {
150 delete[] mMesh;
151}
152
153void CacheTexture::releaseTexture() {
154 if (mTexture) {
Romain Guycf51a412013-04-08 19:40:31 -0700155 delete mTexture;
Romain Guy661a87e2013-03-19 15:24:36 -0700156 mTexture = NULL;
157 }
158 if (mTextureId) {
Romain Guybe1b1272013-06-06 14:02:54 -0700159 mCaches.deleteTexture(mTextureId);
Romain Guy661a87e2013-03-19 15:24:36 -0700160 mTextureId = 0;
161 }
162 mDirty = false;
163 mCurrentQuad = 0;
164}
165
Romain Guycf51a412013-04-08 19:40:31 -0700166void CacheTexture::setLinearFiltering(bool linearFiltering, bool bind) {
167 if (linearFiltering != mLinearFiltering) {
168 mLinearFiltering = linearFiltering;
169
170 const GLenum filtering = linearFiltering ? GL_LINEAR : GL_NEAREST;
Romain Guy8aa195d2013-06-04 18:00:09 -0700171 if (bind) mCaches.bindTexture(getTextureId());
Romain Guycf51a412013-04-08 19:40:31 -0700172 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, filtering);
173 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, filtering);
174 }
175}
176
Romain Guy661a87e2013-03-19 15:24:36 -0700177void CacheTexture::allocateMesh() {
178 if (!mMesh) {
179 mMesh = new TextureVertex[mMaxQuadCount * 4];
180 }
181}
182
183void CacheTexture::allocateTexture() {
184 if (!mTexture) {
Victoria Lease1e546812013-06-25 14:25:17 -0700185 mTexture = PixelBuffer::create(mFormat, mWidth, mHeight);
Romain Guy661a87e2013-03-19 15:24:36 -0700186 }
187
188 if (!mTextureId) {
189 glGenTextures(1, &mTextureId);
190
Romain Guy8aa195d2013-06-04 18:00:09 -0700191 mCaches.bindTexture(mTextureId);
Romain Guy661a87e2013-03-19 15:24:36 -0700192 glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
193 // Initialize texture dimensions
Victoria Lease1e546812013-06-25 14:25:17 -0700194 glTexImage2D(GL_TEXTURE_2D, 0, mFormat, mWidth, mHeight, 0,
195 mFormat, GL_UNSIGNED_BYTE, 0);
Romain Guy661a87e2013-03-19 15:24:36 -0700196
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
Romain Guycf51a412013-04-08 19:40:31 -0700206bool CacheTexture::upload() {
207 const Rect& dirtyRect = mDirtyRect;
208
Romain Guy318ae7b2013-09-24 18:44:54 -0700209 uint32_t x = mHasUnpackRowLength ? dirtyRect.left : 0;
Romain Guycf51a412013-04-08 19:40:31 -0700210 uint32_t y = dirtyRect.top;
Romain Guy318ae7b2013-09-24 18:44:54 -0700211 uint32_t width = mHasUnpackRowLength ? dirtyRect.getWidth() : mWidth;
Romain Guycf51a412013-04-08 19:40:31 -0700212 uint32_t height = dirtyRect.getHeight();
213
214 // The unpack row length only needs to be specified when a new
215 // texture is bound
Romain Guy318ae7b2013-09-24 18:44:54 -0700216 if (mHasUnpackRowLength) {
Romain Guycf51a412013-04-08 19:40:31 -0700217 glPixelStorei(GL_UNPACK_ROW_LENGTH, mWidth);
218 }
219
Victoria Lease1e546812013-06-25 14:25:17 -0700220 mTexture->upload(x, y, width, height);
Romain Guycf51a412013-04-08 19:40:31 -0700221 setDirty(false);
222
Romain Guy318ae7b2013-09-24 18:44:54 -0700223 return mHasUnpackRowLength;
Romain Guycf51a412013-04-08 19:40:31 -0700224}
225
226void CacheTexture::setDirty(bool dirty) {
227 mDirty = dirty;
228 if (!dirty) {
229 mDirtyRect.setEmpty();
230 }
231}
232
Romain Guye43f7852012-09-04 18:58:46 -0700233bool CacheTexture::fitBitmap(const SkGlyph& glyph, uint32_t* retOriginX, uint32_t* retOriginY) {
Victoria Lease1e546812013-06-25 14:25:17 -0700234 switch (glyph.fMaskFormat) {
235 case SkMask::kA8_Format:
Victoria Lease723b2fe2013-08-12 14:38:44 -0700236 case SkMask::kBW_Format:
Victoria Lease1e546812013-06-25 14:25:17 -0700237 if (mFormat != GL_ALPHA) {
238#if DEBUG_FONT_RENDERER
Victoria Lease723b2fe2013-08-12 14:38:44 -0700239 ALOGD("fitBitmap: texture format %x is inappropriate for monochromatic glyphs",
240 mFormat);
Victoria Lease1e546812013-06-25 14:25:17 -0700241#endif
242 return false;
243 }
244 break;
245 case SkMask::kARGB32_Format:
246 if (mFormat != GL_RGBA) {
247#if DEBUG_FONT_RENDERER
Victoria Lease723b2fe2013-08-12 14:38:44 -0700248 ALOGD("fitBitmap: texture format %x is inappropriate for colour glyphs", mFormat);
Victoria Lease1e546812013-06-25 14:25:17 -0700249#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
Romain Guye43f7852012-09-04 18:58:46 -0700260 if (glyph.fHeight + TEXTURE_BORDER_SIZE * 2 > mHeight) {
Romain Guy9f5dab32012-09-04 12:55:44 -0700261 return false;
262 }
263
264 uint16_t glyphW = glyph.fWidth + TEXTURE_BORDER_SIZE;
265 uint16_t glyphH = glyph.fHeight + TEXTURE_BORDER_SIZE;
Romain Guy9b1204b2012-09-04 15:22:57 -0700266
Romain Guy9f5dab32012-09-04 12:55:44 -0700267 // 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.
Romain Guye43f7852012-09-04 18:58:46 -0700271 uint16_t roundedUpW = (glyphW + CACHE_BLOCK_ROUNDING_SIZE - 1) & -CACHE_BLOCK_ROUNDING_SIZE;
Romain Guy9b1204b2012-09-04 15:22:57 -0700272
Romain Guye43f7852012-09-04 18:58:46 -0700273 CacheBlock* cacheBlock = mCacheBlocks;
Romain Guy9f5dab32012-09-04 12:55:44 -0700274 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 }
Romain Guy9b1204b2012-09-04 15:22:57 -0700285
Romain Guy9f5dab32012-09-04 12:55:44 -0700286 *retOriginX = cacheBlock->mX;
287 *retOriginY = cacheBlock->mY;
Romain Guy9b1204b2012-09-04 15:22:57 -0700288
Romain Guy9f5dab32012-09-04 12:55:44 -0700289 // 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;
Romain Guy9b1204b2012-09-04 15:22:57 -0700296
Romain Guy9f5dab32012-09-04 12:55:44 -0700297 if (mHeight - glyphH >= glyphH) {
298 // There's enough height left over to create a new CacheBlock
Romain Guye43f7852012-09-04 18:58:46 -0700299 CacheBlock* newBlock = new CacheBlock(oldX, glyphH + TEXTURE_BORDER_SIZE,
Romain Guy9f5dab32012-09-04 12:55:44 -0700300 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 }
Romain Guy9b1204b2012-09-04 15:22:57 -0700318
Romain Guy9f5dab32012-09-04 12:55:44 -0700319 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 }
Romain Guy9b1204b2012-09-04 15:22:57 -0700323
Romain Guy9f5dab32012-09-04 12:55:44 -0700324 mDirty = true;
Chet Haaseb92d8f72012-09-21 08:40:46 -0700325 const Rect r(*retOriginX - TEXTURE_BORDER_SIZE, *retOriginY - TEXTURE_BORDER_SIZE,
326 *retOriginX + glyphW, *retOriginY + glyphH);
327 mDirtyRect.unionWith(r);
Romain Guy9b1204b2012-09-04 15:22:57 -0700328 mNumGlyphs++;
329
Romain Guy9f5dab32012-09-04 12:55:44 -0700330#if DEBUG_FONT_RENDERER
331 ALOGD("fitBitmap: current block list:");
332 mCacheBlocks->output();
333#endif
Romain Guy9b1204b2012-09-04 15:22:57 -0700334
Romain Guy9f5dab32012-09-04 12:55:44 -0700335 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