blob: 9314126a5be01e5af9ca7d2c531bcf2b7d9ea750 [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"
Tom Hudson2dc236b2014-10-15 15:46:42 -040020#include "FontUtil.h"
Romain Guy8aa195d2013-06-04 18:00:09 -070021#include "../Caches.h"
Romain Guy09087642013-04-04 12:27:54 -070022#include "../Debug.h"
Romain Guycf51a412013-04-08 19:40:31 -070023#include "../Extensions.h"
24#include "../PixelBuffer.h"
Romain Guy9f5dab32012-09-04 12:55:44 -070025
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 */
Romain Guye43f7852012-09-04 18:58:46 -070038CacheBlock* CacheBlock::insertBlock(CacheBlock* head, CacheBlock* newBlock) {
Romain Guy9f5dab32012-09-04 12:55:44 -070039#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
Romain Guy9b1204b2012-09-04 15:22:57 -070044
Romain Guye43f7852012-09-04 18:58:46 -070045 CacheBlock* currBlock = head;
Chris Craikd41c4d82015-01-05 15:51:13 -080046 CacheBlock* prevBlock = nullptr;
Romain Guy9b1204b2012-09-04 15:22:57 -070047
Romain Guy9f5dab32012-09-04 12:55:44 -070048 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;
Romain Guy9b1204b2012-09-04 15:22:57 -070053
Romain Guy9f5dab32012-09-04 12:55:44 -070054 if (prevBlock) {
55 prevBlock->mNext = newBlock;
56 return head;
57 } else {
58 return newBlock;
59 }
60 }
Romain Guy9b1204b2012-09-04 15:22:57 -070061
Romain Guy9f5dab32012-09-04 12:55:44 -070062 prevBlock = currBlock;
63 currBlock = currBlock->mNext;
64 }
Romain Guy9b1204b2012-09-04 15:22:57 -070065
Romain Guy9f5dab32012-09-04 12:55:44 -070066 // new block larger than all others - insert at end (but before the remainder space, if there)
67 newBlock->mNext = currBlock;
68 newBlock->mPrev = prevBlock;
Romain Guy9b1204b2012-09-04 15:22:57 -070069
Romain Guy9f5dab32012-09-04 12:55:44 -070070 if (currBlock) {
71 currBlock->mPrev = newBlock;
72 }
Romain Guy9b1204b2012-09-04 15:22:57 -070073
Romain Guy9f5dab32012-09-04 12:55:44 -070074 if (prevBlock) {
75 prevBlock->mNext = newBlock;
76 return head;
77 } else {
78 return newBlock;
79 }
80}
81
Romain Guye43f7852012-09-04 18:58:46 -070082CacheBlock* CacheBlock::removeBlock(CacheBlock* head, CacheBlock* blockToRemove) {
Romain Guy9f5dab32012-09-04 12:55:44 -070083#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
Romain Guy9b1204b2012-09-04 15:22:57 -070088
Romain Guy9f5dab32012-09-04 12:55:44 -070089 CacheBlock* newHead = head;
90 CacheBlock* nextBlock = blockToRemove->mNext;
91 CacheBlock* prevBlock = blockToRemove->mPrev;
Romain Guy9b1204b2012-09-04 15:22:57 -070092
Romain Guy9f5dab32012-09-04 12:55:44 -070093 if (prevBlock) {
94 prevBlock->mNext = nextBlock;
95 } else {
96 newHead = nextBlock;
97 }
Romain Guy9b1204b2012-09-04 15:22:57 -070098
Romain Guy9f5dab32012-09-04 12:55:44 -070099 if (nextBlock) {
100 nextBlock->mPrev = prevBlock;
101 }
Romain Guy9b1204b2012-09-04 15:22:57 -0700102
Romain Guy9f5dab32012-09-04 12:55:44 -0700103 delete blockToRemove;
Romain Guy9b1204b2012-09-04 15:22:57 -0700104
Romain Guy9f5dab32012-09-04 12:55:44 -0700105 return newHead;
106}
107
108///////////////////////////////////////////////////////////////////////////////
109// CacheTexture
110///////////////////////////////////////////////////////////////////////////////
111
Victoria Lease1e546812013-06-25 14:25:17 -0700112CacheTexture::CacheTexture(uint16_t width, uint16_t height, GLenum format, uint32_t maxQuadCount) :
Chris Craikd41c4d82015-01-05 15:51:13 -0800113 mTexture(nullptr), mTextureId(0), mWidth(width), mHeight(height), mFormat(format),
Romain Guy661a87e2013-03-19 15:24:36 -0700114 mLinearFiltering(false), mDirty(false), mNumGlyphs(0),
Chris Craikd41c4d82015-01-05 15:51:13 -0800115 mMesh(nullptr), mCurrentQuad(0), mMaxQuadCount(maxQuadCount),
Romain Guy8aa195d2013-06-04 18:00:09 -0700116 mCaches(Caches::getInstance()) {
Romain Guy661a87e2013-03-19 15:24:36 -0700117 mCacheBlocks = new CacheBlock(TEXTURE_BORDER_SIZE, TEXTURE_BORDER_SIZE,
Chris Craike63f7c622013-10-17 10:30:55 -0700118 mWidth - TEXTURE_BORDER_SIZE, mHeight - TEXTURE_BORDER_SIZE);
Romain Guycf51a412013-04-08 19:40:31 -0700119
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.
Chris Craik117bdbc2015-02-05 10:12:38 -0800123 mHasUnpackRowLength = mCaches.extensions().hasUnpackRowLength();
Romain Guy661a87e2013-03-19 15:24:36 -0700124}
125
126CacheTexture::~CacheTexture() {
127 releaseMesh();
128 releaseTexture();
129 reset();
130}
131
132void CacheTexture::reset() {
133 // Delete existing cache blocks
Chris Craikd41c4d82015-01-05 15:51:13 -0800134 while (mCacheBlocks != nullptr) {
Romain Guy661a87e2013-03-19 15:24:36 -0700135 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,
Chris Craike63f7c622013-10-17 10:30:55 -0700147 mWidth - TEXTURE_BORDER_SIZE, mHeight - TEXTURE_BORDER_SIZE);
Romain Guy661a87e2013-03-19 15:24:36 -0700148}
149
150void CacheTexture::releaseMesh() {
151 delete[] mMesh;
152}
153
154void CacheTexture::releaseTexture() {
155 if (mTexture) {
Romain Guycf51a412013-04-08 19:40:31 -0700156 delete mTexture;
Chris Craikd41c4d82015-01-05 15:51:13 -0800157 mTexture = nullptr;
Romain Guy661a87e2013-03-19 15:24:36 -0700158 }
159 if (mTextureId) {
Chris Craik44eb2c02015-01-29 09:45:09 -0800160 mCaches.textureState().deleteTexture(mTextureId);
Romain Guy661a87e2013-03-19 15:24:36 -0700161 mTextureId = 0;
162 }
163 mDirty = false;
164 mCurrentQuad = 0;
165}
166
Romain Guycf51a412013-04-08 19:40:31 -0700167void CacheTexture::setLinearFiltering(bool linearFiltering, bool bind) {
168 if (linearFiltering != mLinearFiltering) {
169 mLinearFiltering = linearFiltering;
170
171 const GLenum filtering = linearFiltering ? GL_LINEAR : GL_NEAREST;
Chris Craik44eb2c02015-01-29 09:45:09 -0800172 if (bind) mCaches.textureState().bindTexture(getTextureId());
Romain Guycf51a412013-04-08 19:40:31 -0700173 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, filtering);
174 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, filtering);
175 }
176}
177
Romain Guy661a87e2013-03-19 15:24:36 -0700178void CacheTexture::allocateMesh() {
179 if (!mMesh) {
180 mMesh = new TextureVertex[mMaxQuadCount * 4];
181 }
182}
183
184void CacheTexture::allocateTexture() {
185 if (!mTexture) {
Victoria Lease1e546812013-06-25 14:25:17 -0700186 mTexture = PixelBuffer::create(mFormat, mWidth, mHeight);
Romain Guy661a87e2013-03-19 15:24:36 -0700187 }
188
189 if (!mTextureId) {
190 glGenTextures(1, &mTextureId);
191
Chris Craik44eb2c02015-01-29 09:45:09 -0800192 mCaches.textureState().bindTexture(mTextureId);
Romain Guy661a87e2013-03-19 15:24:36 -0700193 glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
194 // Initialize texture dimensions
Victoria Lease1e546812013-06-25 14:25:17 -0700195 glTexImage2D(GL_TEXTURE_2D, 0, mFormat, mWidth, mHeight, 0,
Chris Craikd41c4d82015-01-05 15:51:13 -0800196 mFormat, GL_UNSIGNED_BYTE, nullptr);
Romain Guy661a87e2013-03-19 15:24:36 -0700197
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
Romain Guycf51a412013-04-08 19:40:31 -0700207bool CacheTexture::upload() {
208 const Rect& dirtyRect = mDirtyRect;
209
Romain Guy318ae7b2013-09-24 18:44:54 -0700210 uint32_t x = mHasUnpackRowLength ? dirtyRect.left : 0;
Romain Guycf51a412013-04-08 19:40:31 -0700211 uint32_t y = dirtyRect.top;
Romain Guy318ae7b2013-09-24 18:44:54 -0700212 uint32_t width = mHasUnpackRowLength ? dirtyRect.getWidth() : mWidth;
Romain Guycf51a412013-04-08 19:40:31 -0700213 uint32_t height = dirtyRect.getHeight();
214
215 // The unpack row length only needs to be specified when a new
216 // texture is bound
Romain Guy318ae7b2013-09-24 18:44:54 -0700217 if (mHasUnpackRowLength) {
Romain Guycf51a412013-04-08 19:40:31 -0700218 glPixelStorei(GL_UNPACK_ROW_LENGTH, mWidth);
219 }
220
Victoria Lease1e546812013-06-25 14:25:17 -0700221 mTexture->upload(x, y, width, height);
Romain Guycf51a412013-04-08 19:40:31 -0700222 setDirty(false);
223
Romain Guy318ae7b2013-09-24 18:44:54 -0700224 return mHasUnpackRowLength;
Romain Guycf51a412013-04-08 19:40:31 -0700225}
226
227void CacheTexture::setDirty(bool dirty) {
228 mDirty = dirty;
229 if (!dirty) {
230 mDirtyRect.setEmpty();
231 }
232}
233
Romain Guye43f7852012-09-04 18:58:46 -0700234bool CacheTexture::fitBitmap(const SkGlyph& glyph, uint32_t* retOriginX, uint32_t* retOriginY) {
Victoria Lease1e546812013-06-25 14:25:17 -0700235 switch (glyph.fMaskFormat) {
236 case SkMask::kA8_Format:
Victoria Lease723b2fe2013-08-12 14:38:44 -0700237 case SkMask::kBW_Format:
Victoria Lease1e546812013-06-25 14:25:17 -0700238 if (mFormat != GL_ALPHA) {
239#if DEBUG_FONT_RENDERER
Victoria Lease723b2fe2013-08-12 14:38:44 -0700240 ALOGD("fitBitmap: texture format %x is inappropriate for monochromatic glyphs",
241 mFormat);
Victoria Lease1e546812013-06-25 14:25:17 -0700242#endif
243 return false;
244 }
245 break;
246 case SkMask::kARGB32_Format:
247 if (mFormat != GL_RGBA) {
248#if DEBUG_FONT_RENDERER
Victoria Lease723b2fe2013-08-12 14:38:44 -0700249 ALOGD("fitBitmap: texture format %x is inappropriate for colour glyphs", mFormat);
Victoria Lease1e546812013-06-25 14:25:17 -0700250#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
Romain Guye43f7852012-09-04 18:58:46 -0700261 if (glyph.fHeight + TEXTURE_BORDER_SIZE * 2 > mHeight) {
Romain Guy9f5dab32012-09-04 12:55:44 -0700262 return false;
263 }
264
265 uint16_t glyphW = glyph.fWidth + TEXTURE_BORDER_SIZE;
266 uint16_t glyphH = glyph.fHeight + TEXTURE_BORDER_SIZE;
Romain Guy9b1204b2012-09-04 15:22:57 -0700267
Romain Guy9f5dab32012-09-04 12:55:44 -0700268 // 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.
Romain Guye43f7852012-09-04 18:58:46 -0700272 uint16_t roundedUpW = (glyphW + CACHE_BLOCK_ROUNDING_SIZE - 1) & -CACHE_BLOCK_ROUNDING_SIZE;
Romain Guy9b1204b2012-09-04 15:22:57 -0700273
Romain Guye43f7852012-09-04 18:58:46 -0700274 CacheBlock* cacheBlock = mCacheBlocks;
Romain Guy9f5dab32012-09-04 12:55:44 -0700275 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 }
Romain Guy9b1204b2012-09-04 15:22:57 -0700286
Romain Guy9f5dab32012-09-04 12:55:44 -0700287 *retOriginX = cacheBlock->mX;
288 *retOriginY = cacheBlock->mY;
Romain Guy9b1204b2012-09-04 15:22:57 -0700289
Romain Guy9f5dab32012-09-04 12:55:44 -0700290 // 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;
Romain Guy9b1204b2012-09-04 15:22:57 -0700297
Romain Guy9f5dab32012-09-04 12:55:44 -0700298 if (mHeight - glyphH >= glyphH) {
299 // There's enough height left over to create a new CacheBlock
Romain Guye43f7852012-09-04 18:58:46 -0700300 CacheBlock* newBlock = new CacheBlock(oldX, glyphH + TEXTURE_BORDER_SIZE,
Romain Guy9f5dab32012-09-04 12:55:44 -0700301 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 }
Romain Guy9b1204b2012-09-04 15:22:57 -0700319
Romain Guy9f5dab32012-09-04 12:55:44 -0700320 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 }
Romain Guy9b1204b2012-09-04 15:22:57 -0700324
Romain Guy9f5dab32012-09-04 12:55:44 -0700325 mDirty = true;
Chet Haaseb92d8f72012-09-21 08:40:46 -0700326 const Rect r(*retOriginX - TEXTURE_BORDER_SIZE, *retOriginY - TEXTURE_BORDER_SIZE,
327 *retOriginX + glyphW, *retOriginY + glyphH);
328 mDirtyRect.unionWith(r);
Romain Guy9b1204b2012-09-04 15:22:57 -0700329 mNumGlyphs++;
330
Romain Guy9f5dab32012-09-04 12:55:44 -0700331#if DEBUG_FONT_RENDERER
332 ALOGD("fitBitmap: current block list:");
333 mCacheBlocks->output();
334#endif
Romain Guy9b1204b2012-09-04 15:22:57 -0700335
Romain Guy9f5dab32012-09-04 12:55:44 -0700336 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