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 "../Caches.h"
20 #include "../Debug.h"
21 #include "../Extensions.h"
22 #include "../PixelBuffer.h"
23 #include "CacheTexture.h"
24 #include "FontUtil.h"
25 
26 namespace android {
27 namespace 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  */
insertBlock(CacheBlock * head,CacheBlock * newBlock)38 CacheBlock* CacheBlock::insertBlock(CacheBlock* head, CacheBlock* newBlock) {
39 #if DEBUG_FONT_RENDERER
40     ALOGD("insertBlock: this, x, y, w, h = %p, %d, %d, %d, %d", newBlock, newBlock->mX,
41           newBlock->mY, newBlock->mWidth, newBlock->mHeight);
42 #endif
43 
44     CacheBlock* currBlock = head;
45     CacheBlock* prevBlock = nullptr;
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", blockToRemove, blockToRemove->mX,
84           blockToRemove->mY, 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         // If this doesn't hold, we have a use-after-free below.
93         LOG_ALWAYS_FATAL_IF(head == blockToRemove,
94                             "removeBlock: head should not have a previous block");
95         prevBlock->mNext = nextBlock;
96     } else {
97         newHead = nextBlock;
98     }
99 
100     if (nextBlock) {
101         nextBlock->mPrev = prevBlock;
102     }
103 
104     delete blockToRemove;
105 
106     return newHead;
107 }
108 
109 ///////////////////////////////////////////////////////////////////////////////
110 // CacheTexture
111 ///////////////////////////////////////////////////////////////////////////////
112 
CacheTexture(uint16_t width,uint16_t height,GLenum format,uint32_t maxQuadCount)113 CacheTexture::CacheTexture(uint16_t width, uint16_t height, GLenum format, uint32_t maxQuadCount)
114         : mTexture(Caches::getInstance())
115         , mWidth(width)
116         , mHeight(height)
117         , mFormat(format)
118         , mMaxQuadCount(maxQuadCount)
119         , mCaches(Caches::getInstance()) {
120     mTexture.blend = true;
121 
122     mCacheBlocks =
123             new CacheBlock(TEXTURE_BORDER_SIZE, TEXTURE_BORDER_SIZE,
124                            getWidth() - TEXTURE_BORDER_SIZE, getHeight() - TEXTURE_BORDER_SIZE);
125 
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.
129     mHasUnpackRowLength = mCaches.extensions().hasUnpackRowLength();
130 }
131 
~CacheTexture()132 CacheTexture::~CacheTexture() {
133     releaseMesh();
134     releasePixelBuffer();
135     reset();
136 }
137 
reset()138 void CacheTexture::reset() {
139     // Delete existing cache blocks
140     while (mCacheBlocks != nullptr) {
141         CacheBlock* tmpBlock = mCacheBlocks;
142         mCacheBlocks = mCacheBlocks->mNext;
143         delete tmpBlock;
144     }
145     mNumGlyphs = 0;
146     mCurrentQuad = 0;
147 }
148 
init()149 void CacheTexture::init() {
150     // reset, then create a new remainder space to start again
151     reset();
152     mCacheBlocks =
153             new CacheBlock(TEXTURE_BORDER_SIZE, TEXTURE_BORDER_SIZE,
154                            getWidth() - TEXTURE_BORDER_SIZE, getHeight() - TEXTURE_BORDER_SIZE);
155 }
156 
releaseMesh()157 void CacheTexture::releaseMesh() {
158     delete[] mMesh;
159 }
160 
releasePixelBuffer()161 void CacheTexture::releasePixelBuffer() {
162     if (mPixelBuffer) {
163         delete mPixelBuffer;
164         mPixelBuffer = nullptr;
165     }
166     mTexture.deleteTexture();
167     mDirty = false;
168     mCurrentQuad = 0;
169 }
170 
setLinearFiltering(bool linearFiltering)171 void CacheTexture::setLinearFiltering(bool linearFiltering) {
172     mTexture.setFilter(linearFiltering ? GL_LINEAR : GL_NEAREST);
173 }
174 
allocateMesh()175 void CacheTexture::allocateMesh() {
176     if (!mMesh) {
177         mMesh = new TextureVertex[mMaxQuadCount * 4];
178     }
179 }
180 
allocatePixelBuffer()181 void CacheTexture::allocatePixelBuffer() {
182     if (!mPixelBuffer) {
183         mPixelBuffer = PixelBuffer::create(mFormat, getWidth(), getHeight());
184     }
185 
186     GLint internalFormat = mFormat;
187     if (mFormat == GL_RGBA) {
188         internalFormat = mCaches.rgbaInternalFormat();
189     }
190 
191     mTexture.resize(mWidth, mHeight, internalFormat, mFormat);
192     mTexture.setFilter(getLinearFiltering() ? GL_LINEAR : GL_NEAREST);
193     mTexture.setWrap(GL_CLAMP_TO_EDGE);
194 }
195 
upload()196 bool CacheTexture::upload() {
197     const Rect& dirtyRect = mDirtyRect;
198 
199     // 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);
206 
207     // The unpack row length only needs to be specified when a new
208     // texture is bound
209     if (mHasUnpackRowLength) {
210         glPixelStorei(GL_UNPACK_ROW_LENGTH, getWidth());
211     } else {
212         x = 0;
213         width = getWidth();
214     }
215 
216     mPixelBuffer->upload(x, y, width, height);
217     setDirty(false);
218 
219     return mHasUnpackRowLength;
220 }
221 
setDirty(bool dirty)222 void CacheTexture::setDirty(bool dirty) {
223     mDirty = dirty;
224     if (!dirty) {
225         mDirtyRect.setEmpty();
226     }
227 }
228 
fitBitmap(const SkGlyph & glyph,uint32_t * retOriginX,uint32_t * retOriginY)229 bool CacheTexture::fitBitmap(const SkGlyph& glyph, uint32_t* retOriginX, uint32_t* retOriginY) {
230     switch (glyph.fMaskFormat) {
231         case SkMask::kA8_Format:
232         case SkMask::kBW_Format:
233             if (mFormat != GL_ALPHA) {
234 #if DEBUG_FONT_RENDERER
235                 ALOGD("fitBitmap: texture format %x is inappropriate for monochromatic glyphs",
236                       mFormat);
237 #endif
238                 return false;
239             }
240             break;
241         case SkMask::kARGB32_Format:
242             if (mFormat != GL_RGBA) {
243 #if DEBUG_FONT_RENDERER
244                 ALOGD("fitBitmap: texture format %x is inappropriate for colour glyphs", mFormat);
245 #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 
256     if (glyph.fHeight + TEXTURE_BORDER_SIZE * 2 > getHeight()) {
257         return false;
258     }
259 
260     uint16_t glyphW = glyph.fWidth + TEXTURE_BORDER_SIZE;
261     uint16_t glyphH = glyph.fHeight + TEXTURE_BORDER_SIZE;
262 
263     // 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.
267     uint16_t roundedUpW = (glyphW + CACHE_BLOCK_ROUNDING_SIZE - 1) & -CACHE_BLOCK_ROUNDING_SIZE;
268 
269     CacheBlock* cacheBlock = mCacheBlocks;
270     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 &&
275             (cacheBlock->mY == TEXTURE_BORDER_SIZE ||
276              (cacheBlock->mWidth - roundedUpW < CACHE_BLOCK_ROUNDING_SIZE))) {
277             if (cacheBlock->mHeight - glyphH < glyphH) {
278                 // Only enough space for this glyph - don't bother rounding up the width
279                 roundedUpW = glyphW;
280             }
281 
282             *retOriginX = cacheBlock->mX;
283             *retOriginY = cacheBlock->mY;
284 
285             // 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;
292 
293                 if (getHeight() - glyphH >= glyphH) {
294                     // There's enough height left over to create a new CacheBlock
295                     CacheBlock* newBlock =
296                             new CacheBlock(oldX, glyphH + TEXTURE_BORDER_SIZE, roundedUpW,
297                                            getHeight() - glyphH - TEXTURE_BORDER_SIZE);
298 #if DEBUG_FONT_RENDERER
299                     ALOGD("fitBitmap: Created new block: this, x, y, w, h = %p, %d, %d, %d, %d",
300                           newBlock, newBlock->mX, newBlock->mY, newBlock->mWidth,
301                           newBlock->mHeight);
302 #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",
311                       cacheBlock, cacheBlock->mX, cacheBlock->mY, cacheBlock->mWidth,
312                       cacheBlock->mHeight);
313 #endif
314             }
315 
316             if (cacheBlock->mHeight < std::min(glyphH, glyphW)) {
317                 // If remaining space in this block is too small to be useful, remove it
318                 mCacheBlocks = CacheBlock::removeBlock(mCacheBlocks, cacheBlock);
319             }
320 
321             mDirty = true;
322             const Rect r(*retOriginX - TEXTURE_BORDER_SIZE, *retOriginY - TEXTURE_BORDER_SIZE,
323                          *retOriginX + glyphW, *retOriginY + glyphH);
324             mDirtyRect.unionWith(r);
325             mNumGlyphs++;
326 
327 #if DEBUG_FONT_RENDERER
328             ALOGD("fitBitmap: current block list:");
329             mCacheBlocks->output();
330 #endif
331 
332             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 
calculateFreeMemory() const342 uint32_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 
354 };  // namespace uirenderer
355 };  // namespace android
356