blob: 109664282374ddc6026cc99412ebae7866fcbf41 [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#include <utils/Log.h>
19
20#include "Debug.h"
21#include "CacheTexture.h"
22
23namespace android {
24namespace uirenderer {
25
26///////////////////////////////////////////////////////////////////////////////
27// CacheBlock
28///////////////////////////////////////////////////////////////////////////////
29
30/**
31 * Insert new block into existing linked list of blocks. Blocks are sorted in increasing-width
32 * order, except for the final block (the remainder space at the right, since we fill from the
33 * left).
34 */
Romain Guye43f7852012-09-04 18:58:46 -070035CacheBlock* CacheBlock::insertBlock(CacheBlock* head, CacheBlock* newBlock) {
Romain Guy9f5dab32012-09-04 12:55:44 -070036#if DEBUG_FONT_RENDERER
37 ALOGD("insertBlock: this, x, y, w, h = %p, %d, %d, %d, %d",
38 newBlock, newBlock->mX, newBlock->mY,
39 newBlock->mWidth, newBlock->mHeight);
40#endif
Romain Guy9b1204b2012-09-04 15:22:57 -070041
Romain Guye43f7852012-09-04 18:58:46 -070042 CacheBlock* currBlock = head;
43 CacheBlock* prevBlock = NULL;
Romain Guy9b1204b2012-09-04 15:22:57 -070044
Romain Guy9f5dab32012-09-04 12:55:44 -070045 while (currBlock && currBlock->mY != TEXTURE_BORDER_SIZE) {
46 if (newBlock->mWidth < currBlock->mWidth) {
47 newBlock->mNext = currBlock;
48 newBlock->mPrev = prevBlock;
49 currBlock->mPrev = newBlock;
Romain Guy9b1204b2012-09-04 15:22:57 -070050
Romain Guy9f5dab32012-09-04 12:55:44 -070051 if (prevBlock) {
52 prevBlock->mNext = newBlock;
53 return head;
54 } else {
55 return newBlock;
56 }
57 }
Romain Guy9b1204b2012-09-04 15:22:57 -070058
Romain Guy9f5dab32012-09-04 12:55:44 -070059 prevBlock = currBlock;
60 currBlock = currBlock->mNext;
61 }
Romain Guy9b1204b2012-09-04 15:22:57 -070062
Romain Guy9f5dab32012-09-04 12:55:44 -070063 // new block larger than all others - insert at end (but before the remainder space, if there)
64 newBlock->mNext = currBlock;
65 newBlock->mPrev = prevBlock;
Romain Guy9b1204b2012-09-04 15:22:57 -070066
Romain Guy9f5dab32012-09-04 12:55:44 -070067 if (currBlock) {
68 currBlock->mPrev = newBlock;
69 }
Romain Guy9b1204b2012-09-04 15:22:57 -070070
Romain Guy9f5dab32012-09-04 12:55:44 -070071 if (prevBlock) {
72 prevBlock->mNext = newBlock;
73 return head;
74 } else {
75 return newBlock;
76 }
77}
78
Romain Guye43f7852012-09-04 18:58:46 -070079CacheBlock* CacheBlock::removeBlock(CacheBlock* head, CacheBlock* blockToRemove) {
Romain Guy9f5dab32012-09-04 12:55:44 -070080#if DEBUG_FONT_RENDERER
81 ALOGD("removeBlock: this, x, y, w, h = %p, %d, %d, %d, %d",
82 blockToRemove, blockToRemove->mX, blockToRemove->mY,
83 blockToRemove->mWidth, blockToRemove->mHeight);
84#endif
Romain Guy9b1204b2012-09-04 15:22:57 -070085
Romain Guy9f5dab32012-09-04 12:55:44 -070086 CacheBlock* newHead = head;
87 CacheBlock* nextBlock = blockToRemove->mNext;
88 CacheBlock* prevBlock = blockToRemove->mPrev;
Romain Guy9b1204b2012-09-04 15:22:57 -070089
Romain Guy9f5dab32012-09-04 12:55:44 -070090 if (prevBlock) {
91 prevBlock->mNext = nextBlock;
92 } else {
93 newHead = nextBlock;
94 }
Romain Guy9b1204b2012-09-04 15:22:57 -070095
Romain Guy9f5dab32012-09-04 12:55:44 -070096 if (nextBlock) {
97 nextBlock->mPrev = prevBlock;
98 }
Romain Guy9b1204b2012-09-04 15:22:57 -070099
Romain Guy9f5dab32012-09-04 12:55:44 -0700100 delete blockToRemove;
Romain Guy9b1204b2012-09-04 15:22:57 -0700101
Romain Guy9f5dab32012-09-04 12:55:44 -0700102 return newHead;
103}
104
105///////////////////////////////////////////////////////////////////////////////
106// CacheTexture
107///////////////////////////////////////////////////////////////////////////////
108
Romain Guy661a87e2013-03-19 15:24:36 -0700109CacheTexture::CacheTexture(uint16_t width, uint16_t height, uint32_t maxQuadCount) :
110 mTexture(NULL), mTextureId(0), mWidth(width), mHeight(height),
111 mLinearFiltering(false), mDirty(false), mNumGlyphs(0),
112 mMesh(NULL), mCurrentQuad(0), mMaxQuadCount(maxQuadCount) {
113 mCacheBlocks = new CacheBlock(TEXTURE_BORDER_SIZE, TEXTURE_BORDER_SIZE,
114 mWidth - TEXTURE_BORDER_SIZE, mHeight - TEXTURE_BORDER_SIZE, true);
115}
116
117CacheTexture::~CacheTexture() {
118 releaseMesh();
119 releaseTexture();
120 reset();
121}
122
123void CacheTexture::reset() {
124 // Delete existing cache blocks
125 while (mCacheBlocks != NULL) {
126 CacheBlock* tmpBlock = mCacheBlocks;
127 mCacheBlocks = mCacheBlocks->mNext;
128 delete tmpBlock;
129 }
130 mNumGlyphs = 0;
131 mCurrentQuad = 0;
132}
133
134void CacheTexture::init() {
135 // reset, then create a new remainder space to start again
136 reset();
137 mCacheBlocks = new CacheBlock(TEXTURE_BORDER_SIZE, TEXTURE_BORDER_SIZE,
138 mWidth - TEXTURE_BORDER_SIZE, mHeight - TEXTURE_BORDER_SIZE, true);
139}
140
141void CacheTexture::releaseMesh() {
142 delete[] mMesh;
143}
144
145void CacheTexture::releaseTexture() {
146 if (mTexture) {
147 delete[] mTexture;
148 mTexture = NULL;
149 }
150 if (mTextureId) {
151 glDeleteTextures(1, &mTextureId);
152 mTextureId = 0;
153 }
154 mDirty = false;
155 mCurrentQuad = 0;
156}
157
158void CacheTexture::allocateMesh() {
159 if (!mMesh) {
160 mMesh = new TextureVertex[mMaxQuadCount * 4];
161 }
162}
163
164void CacheTexture::allocateTexture() {
165 if (!mTexture) {
166 mTexture = new uint8_t[mWidth * mHeight];
167 }
168
169 if (!mTextureId) {
170 glGenTextures(1, &mTextureId);
171
172 glBindTexture(GL_TEXTURE_2D, mTextureId);
173 glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
174 // Initialize texture dimensions
175 glTexImage2D(GL_TEXTURE_2D, 0, GL_ALPHA, mWidth, mHeight, 0,
176 GL_ALPHA, GL_UNSIGNED_BYTE, 0);
177
178 const GLenum filtering = getLinearFiltering() ? GL_LINEAR : GL_NEAREST;
179 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, filtering);
180 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, filtering);
181
182 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
183 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
184 }
185}
186
Romain Guye43f7852012-09-04 18:58:46 -0700187bool CacheTexture::fitBitmap(const SkGlyph& glyph, uint32_t* retOriginX, uint32_t* retOriginY) {
188 if (glyph.fHeight + TEXTURE_BORDER_SIZE * 2 > mHeight) {
Romain Guy9f5dab32012-09-04 12:55:44 -0700189 return false;
190 }
191
192 uint16_t glyphW = glyph.fWidth + TEXTURE_BORDER_SIZE;
193 uint16_t glyphH = glyph.fHeight + TEXTURE_BORDER_SIZE;
Romain Guy9b1204b2012-09-04 15:22:57 -0700194
Romain Guy9f5dab32012-09-04 12:55:44 -0700195 // roundedUpW equals glyphW to the next multiple of CACHE_BLOCK_ROUNDING_SIZE.
196 // This columns for glyphs that are close but not necessarily exactly the same size. It trades
197 // off the loss of a few pixels for some glyphs against the ability to store more glyphs
198 // of varying sizes in one block.
Romain Guye43f7852012-09-04 18:58:46 -0700199 uint16_t roundedUpW = (glyphW + CACHE_BLOCK_ROUNDING_SIZE - 1) & -CACHE_BLOCK_ROUNDING_SIZE;
Romain Guy9b1204b2012-09-04 15:22:57 -0700200
Romain Guye43f7852012-09-04 18:58:46 -0700201 CacheBlock* cacheBlock = mCacheBlocks;
Romain Guy9f5dab32012-09-04 12:55:44 -0700202 while (cacheBlock) {
203 // Store glyph in this block iff: it fits the block's remaining space and:
204 // it's the remainder space (mY == 0) or there's only enough height for this one glyph
205 // or it's within ROUNDING_SIZE of the block width
206 if (roundedUpW <= cacheBlock->mWidth && glyphH <= cacheBlock->mHeight &&
207 (cacheBlock->mY == TEXTURE_BORDER_SIZE ||
208 (cacheBlock->mWidth - roundedUpW < CACHE_BLOCK_ROUNDING_SIZE))) {
209 if (cacheBlock->mHeight - glyphH < glyphH) {
210 // Only enough space for this glyph - don't bother rounding up the width
211 roundedUpW = glyphW;
212 }
Romain Guy9b1204b2012-09-04 15:22:57 -0700213
Romain Guy9f5dab32012-09-04 12:55:44 -0700214 *retOriginX = cacheBlock->mX;
215 *retOriginY = cacheBlock->mY;
Romain Guy9b1204b2012-09-04 15:22:57 -0700216
Romain Guy9f5dab32012-09-04 12:55:44 -0700217 // If this is the remainder space, create a new cache block for this column. Otherwise,
218 // adjust the info about this column.
219 if (cacheBlock->mY == TEXTURE_BORDER_SIZE) {
220 uint16_t oldX = cacheBlock->mX;
221 // Adjust remainder space dimensions
222 cacheBlock->mWidth -= roundedUpW;
223 cacheBlock->mX += roundedUpW;
Romain Guy9b1204b2012-09-04 15:22:57 -0700224
Romain Guy9f5dab32012-09-04 12:55:44 -0700225 if (mHeight - glyphH >= glyphH) {
226 // There's enough height left over to create a new CacheBlock
Romain Guye43f7852012-09-04 18:58:46 -0700227 CacheBlock* newBlock = new CacheBlock(oldX, glyphH + TEXTURE_BORDER_SIZE,
Romain Guy9f5dab32012-09-04 12:55:44 -0700228 roundedUpW, mHeight - glyphH - TEXTURE_BORDER_SIZE);
229#if DEBUG_FONT_RENDERER
230 ALOGD("fitBitmap: Created new block: this, x, y, w, h = %p, %d, %d, %d, %d",
231 newBlock, newBlock->mX, newBlock->mY,
232 newBlock->mWidth, newBlock->mHeight);
233#endif
234 mCacheBlocks = CacheBlock::insertBlock(mCacheBlocks, newBlock);
235 }
236 } else {
237 // Insert into current column and adjust column dimensions
238 cacheBlock->mY += glyphH;
239 cacheBlock->mHeight -= glyphH;
240#if DEBUG_FONT_RENDERER
241 ALOGD("fitBitmap: Added to existing block: this, x, y, w, h = %p, %d, %d, %d, %d",
242 cacheBlock, cacheBlock->mX, cacheBlock->mY,
243 cacheBlock->mWidth, cacheBlock->mHeight);
244#endif
245 }
Romain Guy9b1204b2012-09-04 15:22:57 -0700246
Romain Guy9f5dab32012-09-04 12:55:44 -0700247 if (cacheBlock->mHeight < fmin(glyphH, glyphW)) {
248 // If remaining space in this block is too small to be useful, remove it
249 mCacheBlocks = CacheBlock::removeBlock(mCacheBlocks, cacheBlock);
250 }
Romain Guy9b1204b2012-09-04 15:22:57 -0700251
Romain Guy9f5dab32012-09-04 12:55:44 -0700252 mDirty = true;
Chet Haaseb92d8f72012-09-21 08:40:46 -0700253 const Rect r(*retOriginX - TEXTURE_BORDER_SIZE, *retOriginY - TEXTURE_BORDER_SIZE,
254 *retOriginX + glyphW, *retOriginY + glyphH);
255 mDirtyRect.unionWith(r);
Romain Guy9b1204b2012-09-04 15:22:57 -0700256 mNumGlyphs++;
257
Romain Guy9f5dab32012-09-04 12:55:44 -0700258#if DEBUG_FONT_RENDERER
259 ALOGD("fitBitmap: current block list:");
260 mCacheBlocks->output();
261#endif
Romain Guy9b1204b2012-09-04 15:22:57 -0700262
Romain Guy9f5dab32012-09-04 12:55:44 -0700263 return true;
264 }
265 cacheBlock = cacheBlock->mNext;
266 }
267#if DEBUG_FONT_RENDERER
268 ALOGD("fitBitmap: returning false for glyph of size %d, %d", glyphW, glyphH);
269#endif
270 return false;
271}
272
273}; // namespace uirenderer
274}; // namespace android