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