blob: 5dd8bb8119e9cef2dd07bd558d7f11a4ec1c1a7b [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 Guy8aa195d2013-06-04 18:00:09 -070019#include "../Caches.h"
Romain Guy09087642013-04-04 12:27:54 -070020#include "../Debug.h"
Romain Guycf51a412013-04-08 19:40:31 -070021#include "../Extensions.h"
22#include "../PixelBuffer.h"
John Reck1bcacfd2017-11-03 10:12:19 -070023#include "CacheTexture.h"
24#include "FontUtil.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
John Reck1bcacfd2017-11-03 10:12:19 -070040 ALOGD("insertBlock: this, x, y, w, h = %p, %d, %d, %d, %d", newBlock, newBlock->mX,
41 newBlock->mY, newBlock->mWidth, newBlock->mHeight);
Romain Guy9f5dab32012-09-04 12:55:44 -070042#endif
Romain Guy9b1204b2012-09-04 15:22:57 -070043
Romain Guye43f7852012-09-04 18:58:46 -070044 CacheBlock* currBlock = head;
Chris Craikd41c4d82015-01-05 15:51:13 -080045 CacheBlock* prevBlock = nullptr;
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
John Reck1bcacfd2017-11-03 10:12:19 -070083 ALOGD("removeBlock: this, x, y, w, h = %p, %d, %d, %d, %d", blockToRemove, blockToRemove->mX,
84 blockToRemove->mY, blockToRemove->mWidth, blockToRemove->mHeight);
Romain Guy9f5dab32012-09-04 12:55:44 -070085#endif
Romain Guy9b1204b2012-09-04 15:22:57 -070086
Romain Guy9f5dab32012-09-04 12:55:44 -070087 CacheBlock* newHead = head;
88 CacheBlock* nextBlock = blockToRemove->mNext;
89 CacheBlock* prevBlock = blockToRemove->mPrev;
Romain Guy9b1204b2012-09-04 15:22:57 -070090
Romain Guy9f5dab32012-09-04 12:55:44 -070091 if (prevBlock) {
George Burgess IVfa4eaa62017-07-18 16:28:28 -070092 // If this doesn't hold, we have a use-after-free below.
93 LOG_ALWAYS_FATAL_IF(head == blockToRemove,
John Reck1bcacfd2017-11-03 10:12:19 -070094 "removeBlock: head should not have a previous block");
Romain Guy9f5dab32012-09-04 12:55:44 -070095 prevBlock->mNext = nextBlock;
96 } else {
97 newHead = nextBlock;
98 }
Romain Guy9b1204b2012-09-04 15:22:57 -070099
Romain Guy9f5dab32012-09-04 12:55:44 -0700100 if (nextBlock) {
101 nextBlock->mPrev = prevBlock;
102 }
Romain Guy9b1204b2012-09-04 15:22:57 -0700103
Romain Guy9f5dab32012-09-04 12:55:44 -0700104 delete blockToRemove;
Romain Guy9b1204b2012-09-04 15:22:57 -0700105
Romain Guy9f5dab32012-09-04 12:55:44 -0700106 return newHead;
107}
108
109///////////////////////////////////////////////////////////////////////////////
110// CacheTexture
111///////////////////////////////////////////////////////////////////////////////
112
Chris Craike2bb3802015-03-13 15:07:52 -0700113CacheTexture::CacheTexture(uint16_t width, uint16_t height, GLenum format, uint32_t maxQuadCount)
114 : mTexture(Caches::getInstance())
John Reck38e0c322015-11-10 12:19:17 -0800115 , mWidth(width)
116 , mHeight(height)
Chris Craike2bb3802015-03-13 15:07:52 -0700117 , mFormat(format)
118 , mMaxQuadCount(maxQuadCount)
119 , mCaches(Caches::getInstance()) {
Chris Craike2bb3802015-03-13 15:07:52 -0700120 mTexture.blend = true;
121
John Reck1bcacfd2017-11-03 10:12:19 -0700122 mCacheBlocks =
123 new CacheBlock(TEXTURE_BORDER_SIZE, TEXTURE_BORDER_SIZE,
124 getWidth() - TEXTURE_BORDER_SIZE, getHeight() - TEXTURE_BORDER_SIZE);
Romain Guycf51a412013-04-08 19:40:31 -0700125
126 // OpenGL ES 3.0+ lets us specify the row length for unpack operations such
127 // as glTexSubImage2D(). This allows us to upload a sub-rectangle of a texture.
128 // With OpenGL ES 2.0 we have to upload entire stripes instead.
Chris Craik117bdbc2015-02-05 10:12:38 -0800129 mHasUnpackRowLength = mCaches.extensions().hasUnpackRowLength();
Romain Guy661a87e2013-03-19 15:24:36 -0700130}
131
132CacheTexture::~CacheTexture() {
133 releaseMesh();
Chris Craike2bb3802015-03-13 15:07:52 -0700134 releasePixelBuffer();
Romain Guy661a87e2013-03-19 15:24:36 -0700135 reset();
136}
137
138void CacheTexture::reset() {
139 // Delete existing cache blocks
Chris Craikd41c4d82015-01-05 15:51:13 -0800140 while (mCacheBlocks != nullptr) {
Romain Guy661a87e2013-03-19 15:24:36 -0700141 CacheBlock* tmpBlock = mCacheBlocks;
142 mCacheBlocks = mCacheBlocks->mNext;
143 delete tmpBlock;
144 }
145 mNumGlyphs = 0;
146 mCurrentQuad = 0;
147}
148
149void CacheTexture::init() {
150 // reset, then create a new remainder space to start again
151 reset();
John Reck1bcacfd2017-11-03 10:12:19 -0700152 mCacheBlocks =
153 new CacheBlock(TEXTURE_BORDER_SIZE, TEXTURE_BORDER_SIZE,
154 getWidth() - TEXTURE_BORDER_SIZE, getHeight() - TEXTURE_BORDER_SIZE);
Romain Guy661a87e2013-03-19 15:24:36 -0700155}
156
157void CacheTexture::releaseMesh() {
158 delete[] mMesh;
159}
160
Chris Craike2bb3802015-03-13 15:07:52 -0700161void CacheTexture::releasePixelBuffer() {
162 if (mPixelBuffer) {
163 delete mPixelBuffer;
164 mPixelBuffer = nullptr;
Romain Guy661a87e2013-03-19 15:24:36 -0700165 }
John Reck38e0c322015-11-10 12:19:17 -0800166 mTexture.deleteTexture();
Romain Guy661a87e2013-03-19 15:24:36 -0700167 mDirty = false;
168 mCurrentQuad = 0;
169}
170
Chris Craike2bb3802015-03-13 15:07:52 -0700171void CacheTexture::setLinearFiltering(bool linearFiltering) {
172 mTexture.setFilter(linearFiltering ? GL_LINEAR : GL_NEAREST);
Romain Guycf51a412013-04-08 19:40:31 -0700173}
174
Romain Guy661a87e2013-03-19 15:24:36 -0700175void CacheTexture::allocateMesh() {
176 if (!mMesh) {
177 mMesh = new TextureVertex[mMaxQuadCount * 4];
178 }
179}
180
Chris Craike2bb3802015-03-13 15:07:52 -0700181void CacheTexture::allocatePixelBuffer() {
182 if (!mPixelBuffer) {
183 mPixelBuffer = PixelBuffer::create(mFormat, getWidth(), getHeight());
Romain Guy661a87e2013-03-19 15:24:36 -0700184 }
185
Romain Guy253f2c22016-09-28 17:34:42 -0700186 GLint internalFormat = mFormat;
187 if (mFormat == GL_RGBA) {
188 internalFormat = mCaches.rgbaInternalFormat();
189 }
190
191 mTexture.resize(mWidth, mHeight, internalFormat, mFormat);
John Reck38e0c322015-11-10 12:19:17 -0800192 mTexture.setFilter(getLinearFiltering() ? GL_LINEAR : GL_NEAREST);
193 mTexture.setWrap(GL_CLAMP_TO_EDGE);
Romain Guy661a87e2013-03-19 15:24:36 -0700194}
195
Romain Guycf51a412013-04-08 19:40:31 -0700196bool CacheTexture::upload() {
197 const Rect& dirtyRect = mDirtyRect;
198
ywen229cad02016-02-15 16:09:40 +0800199 // align the x direction to 32 and y direction to 4 for better performance
200 uint32_t x = (((uint32_t)dirtyRect.left) & (~0x1F));
201 uint32_t y = (((uint32_t)dirtyRect.top) & (~0x3));
202 uint32_t r = ((((uint32_t)dirtyRect.right) + 0x1F) & (~0x1F)) - x;
203 uint32_t b = ((((uint32_t)dirtyRect.bottom) + 0x3) & (~0x3)) - y;
204 uint32_t width = (r > getWidth() ? getWidth() : r);
205 uint32_t height = (b > getHeight() ? getHeight() : b);
Romain Guycf51a412013-04-08 19:40:31 -0700206
207 // The unpack row length only needs to be specified when a new
208 // texture is bound
Romain Guy318ae7b2013-09-24 18:44:54 -0700209 if (mHasUnpackRowLength) {
Chris Craike2bb3802015-03-13 15:07:52 -0700210 glPixelStorei(GL_UNPACK_ROW_LENGTH, getWidth());
ywen229cad02016-02-15 16:09:40 +0800211 } else {
212 x = 0;
213 width = getWidth();
Romain Guycf51a412013-04-08 19:40:31 -0700214 }
215
Chris Craike2bb3802015-03-13 15:07:52 -0700216 mPixelBuffer->upload(x, y, width, height);
Romain Guycf51a412013-04-08 19:40:31 -0700217 setDirty(false);
218
Romain Guy318ae7b2013-09-24 18:44:54 -0700219 return mHasUnpackRowLength;
Romain Guycf51a412013-04-08 19:40:31 -0700220}
221
222void CacheTexture::setDirty(bool dirty) {
223 mDirty = dirty;
224 if (!dirty) {
225 mDirtyRect.setEmpty();
226 }
227}
228
Romain Guye43f7852012-09-04 18:58:46 -0700229bool CacheTexture::fitBitmap(const SkGlyph& glyph, uint32_t* retOriginX, uint32_t* retOriginY) {
Victoria Lease1e546812013-06-25 14:25:17 -0700230 switch (glyph.fMaskFormat) {
231 case SkMask::kA8_Format:
Victoria Lease723b2fe2013-08-12 14:38:44 -0700232 case SkMask::kBW_Format:
Victoria Lease1e546812013-06-25 14:25:17 -0700233 if (mFormat != GL_ALPHA) {
234#if DEBUG_FONT_RENDERER
Victoria Lease723b2fe2013-08-12 14:38:44 -0700235 ALOGD("fitBitmap: texture format %x is inappropriate for monochromatic glyphs",
John Reck1bcacfd2017-11-03 10:12:19 -0700236 mFormat);
Victoria Lease1e546812013-06-25 14:25:17 -0700237#endif
238 return false;
239 }
240 break;
241 case SkMask::kARGB32_Format:
242 if (mFormat != GL_RGBA) {
243#if DEBUG_FONT_RENDERER
Victoria Lease723b2fe2013-08-12 14:38:44 -0700244 ALOGD("fitBitmap: texture format %x is inappropriate for colour glyphs", mFormat);
Victoria Lease1e546812013-06-25 14:25:17 -0700245#endif
246 return false;
247 }
248 break;
249 default:
250#if DEBUG_FONT_RENDERER
251 ALOGD("fitBitmap: unknown glyph format %x encountered", glyph.fMaskFormat);
252#endif
253 return false;
254 }
255
Chris Craike2bb3802015-03-13 15:07:52 -0700256 if (glyph.fHeight + TEXTURE_BORDER_SIZE * 2 > getHeight()) {
Romain Guy9f5dab32012-09-04 12:55:44 -0700257 return false;
258 }
259
260 uint16_t glyphW = glyph.fWidth + TEXTURE_BORDER_SIZE;
261 uint16_t glyphH = glyph.fHeight + TEXTURE_BORDER_SIZE;
Romain Guy9b1204b2012-09-04 15:22:57 -0700262
Romain Guy9f5dab32012-09-04 12:55:44 -0700263 // roundedUpW equals glyphW to the next multiple of CACHE_BLOCK_ROUNDING_SIZE.
264 // This columns for glyphs that are close but not necessarily exactly the same size. It trades
265 // off the loss of a few pixels for some glyphs against the ability to store more glyphs
266 // of varying sizes in one block.
Romain Guye43f7852012-09-04 18:58:46 -0700267 uint16_t roundedUpW = (glyphW + CACHE_BLOCK_ROUNDING_SIZE - 1) & -CACHE_BLOCK_ROUNDING_SIZE;
Romain Guy9b1204b2012-09-04 15:22:57 -0700268
Romain Guye43f7852012-09-04 18:58:46 -0700269 CacheBlock* cacheBlock = mCacheBlocks;
Romain Guy9f5dab32012-09-04 12:55:44 -0700270 while (cacheBlock) {
271 // Store glyph in this block iff: it fits the block's remaining space and:
272 // it's the remainder space (mY == 0) or there's only enough height for this one glyph
273 // or it's within ROUNDING_SIZE of the block width
274 if (roundedUpW <= cacheBlock->mWidth && glyphH <= cacheBlock->mHeight &&
John Reck1bcacfd2017-11-03 10:12:19 -0700275 (cacheBlock->mY == TEXTURE_BORDER_SIZE ||
276 (cacheBlock->mWidth - roundedUpW < CACHE_BLOCK_ROUNDING_SIZE))) {
Romain Guy9f5dab32012-09-04 12:55:44 -0700277 if (cacheBlock->mHeight - glyphH < glyphH) {
278 // Only enough space for this glyph - don't bother rounding up the width
279 roundedUpW = glyphW;
280 }
Romain Guy9b1204b2012-09-04 15:22:57 -0700281
Romain Guy9f5dab32012-09-04 12:55:44 -0700282 *retOriginX = cacheBlock->mX;
283 *retOriginY = cacheBlock->mY;
Romain Guy9b1204b2012-09-04 15:22:57 -0700284
Romain Guy9f5dab32012-09-04 12:55:44 -0700285 // If this is the remainder space, create a new cache block for this column. Otherwise,
286 // adjust the info about this column.
287 if (cacheBlock->mY == TEXTURE_BORDER_SIZE) {
288 uint16_t oldX = cacheBlock->mX;
289 // Adjust remainder space dimensions
290 cacheBlock->mWidth -= roundedUpW;
291 cacheBlock->mX += roundedUpW;
Romain Guy9b1204b2012-09-04 15:22:57 -0700292
Chris Craike2bb3802015-03-13 15:07:52 -0700293 if (getHeight() - glyphH >= glyphH) {
Romain Guy9f5dab32012-09-04 12:55:44 -0700294 // There's enough height left over to create a new CacheBlock
John Reck1bcacfd2017-11-03 10:12:19 -0700295 CacheBlock* newBlock =
296 new CacheBlock(oldX, glyphH + TEXTURE_BORDER_SIZE, roundedUpW,
297 getHeight() - glyphH - TEXTURE_BORDER_SIZE);
Romain Guy9f5dab32012-09-04 12:55:44 -0700298#if DEBUG_FONT_RENDERER
299 ALOGD("fitBitmap: Created new block: this, x, y, w, h = %p, %d, %d, %d, %d",
John Reck1bcacfd2017-11-03 10:12:19 -0700300 newBlock, newBlock->mX, newBlock->mY, newBlock->mWidth,
301 newBlock->mHeight);
Romain Guy9f5dab32012-09-04 12:55:44 -0700302#endif
303 mCacheBlocks = CacheBlock::insertBlock(mCacheBlocks, newBlock);
304 }
305 } else {
306 // Insert into current column and adjust column dimensions
307 cacheBlock->mY += glyphH;
308 cacheBlock->mHeight -= glyphH;
309#if DEBUG_FONT_RENDERER
310 ALOGD("fitBitmap: Added to existing block: this, x, y, w, h = %p, %d, %d, %d, %d",
John Reck1bcacfd2017-11-03 10:12:19 -0700311 cacheBlock, cacheBlock->mX, cacheBlock->mY, cacheBlock->mWidth,
312 cacheBlock->mHeight);
Romain Guy9f5dab32012-09-04 12:55:44 -0700313#endif
314 }
Romain Guy9b1204b2012-09-04 15:22:57 -0700315
Chris Craike6a15ee2015-07-07 18:42:17 -0700316 if (cacheBlock->mHeight < std::min(glyphH, glyphW)) {
Romain Guy9f5dab32012-09-04 12:55:44 -0700317 // If remaining space in this block is too small to be useful, remove it
318 mCacheBlocks = CacheBlock::removeBlock(mCacheBlocks, cacheBlock);
319 }
Romain Guy9b1204b2012-09-04 15:22:57 -0700320
Romain Guy9f5dab32012-09-04 12:55:44 -0700321 mDirty = true;
Chet Haaseb92d8f72012-09-21 08:40:46 -0700322 const Rect r(*retOriginX - TEXTURE_BORDER_SIZE, *retOriginY - TEXTURE_BORDER_SIZE,
John Reck1bcacfd2017-11-03 10:12:19 -0700323 *retOriginX + glyphW, *retOriginY + glyphH);
Chet Haaseb92d8f72012-09-21 08:40:46 -0700324 mDirtyRect.unionWith(r);
Romain Guy9b1204b2012-09-04 15:22:57 -0700325 mNumGlyphs++;
326
Romain Guy9f5dab32012-09-04 12:55:44 -0700327#if DEBUG_FONT_RENDERER
328 ALOGD("fitBitmap: current block list:");
329 mCacheBlocks->output();
330#endif
Romain Guy9b1204b2012-09-04 15:22:57 -0700331
Romain Guy9f5dab32012-09-04 12:55:44 -0700332 return true;
333 }
334 cacheBlock = cacheBlock->mNext;
335 }
336#if DEBUG_FONT_RENDERER
337 ALOGD("fitBitmap: returning false for glyph of size %d, %d", glyphW, glyphH);
338#endif
339 return false;
340}
341
sergeyvbaf29e72016-09-08 11:09:34 -0700342uint32_t CacheTexture::calculateFreeMemory() const {
343 CacheBlock* cacheBlock = mCacheBlocks;
344 uint32_t free = 0;
345 // currently only two formats are supported: GL_ALPHA or GL_RGBA;
346 uint32_t bpp = mFormat == GL_RGBA ? 4 : 1;
347 while (cacheBlock) {
348 free += bpp * cacheBlock->mWidth * cacheBlock->mHeight;
349 cacheBlock = cacheBlock->mNext;
350 }
351 return free;
352}
353
John Reck1bcacfd2017-11-03 10:12:19 -0700354}; // namespace uirenderer
355}; // namespace android