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