blob: 24dee55041abcfdd7bbac662892314441d90ecf5 [file] [log] [blame]
Romain Guy694b5192010-07-21 21:33:20 -07001/*
2 * Copyright (C) 2010 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#define LOG_TAG "OpenGLRenderer"
18
Romain Guy694b5192010-07-21 21:33:20 -070019#include <SkUtils.h>
20
Romain Guy51769a62010-07-23 00:28:00 -070021#include <cutils/properties.h>
22#include <utils/Log.h>
23
24#include "FontRenderer.h"
25
Romain Guy694b5192010-07-21 21:33:20 -070026namespace android {
27namespace uirenderer {
28
29///////////////////////////////////////////////////////////////////////////////
Romain Guy51769a62010-07-23 00:28:00 -070030// Defines
31///////////////////////////////////////////////////////////////////////////////
32
33#define DEFAULT_TEXT_CACHE_WIDTH 1024
34#define DEFAULT_TEXT_CACHE_HEIGHT 256
35
36///////////////////////////////////////////////////////////////////////////////
Romain Guy694b5192010-07-21 21:33:20 -070037// Font
38///////////////////////////////////////////////////////////////////////////////
39
40Font::Font(FontRenderer* state, uint32_t fontId, float fontSize) :
41 mState(state), mFontId(fontId), mFontSize(fontSize) {
42}
43
44
45Font::~Font() {
46 for (uint32_t ct = 0; ct < mState->mActiveFonts.size(); ct++) {
47 if (mState->mActiveFonts[ct] == this) {
48 mState->mActiveFonts.removeAt(ct);
49 break;
50 }
51 }
52
53 for (uint32_t i = 0; i < mCachedGlyphs.size(); i++) {
Romain Guy51769a62010-07-23 00:28:00 -070054 CachedGlyphInfo* glyph = mCachedGlyphs.valueAt(i);
Romain Guy694b5192010-07-21 21:33:20 -070055 delete glyph;
56 }
57}
58
59void Font::invalidateTextureCache() {
60 for (uint32_t i = 0; i < mCachedGlyphs.size(); i++) {
61 mCachedGlyphs.valueAt(i)->mIsValid = false;
62 }
63}
64
65void Font::drawCachedGlyph(CachedGlyphInfo* glyph, int x, int y) {
Romain Guy694b5192010-07-21 21:33:20 -070066 int nPenX = x + glyph->mBitmapLeft;
67 int nPenY = y + glyph->mBitmapTop + glyph->mBitmapHeight;
68
Romain Guy51769a62010-07-23 00:28:00 -070069 float u1 = glyph->mBitmapMinU;
70 float u2 = glyph->mBitmapMaxU;
71 float v1 = glyph->mBitmapMinV;
72 float v2 = glyph->mBitmapMaxV;
73
74 int width = (int) glyph->mBitmapWidth;
75 int height = (int) glyph->mBitmapHeight;
76
77 mState->appendMeshQuad(nPenX, nPenY, 0, u1, v2,
78 nPenX + width, nPenY, 0, u2, v2,
79 nPenX + width, nPenY - height, 0, u2, v1,
80 nPenX, nPenY - height, 0, u1, v1);
Romain Guy694b5192010-07-21 21:33:20 -070081}
82
Romain Guy51769a62010-07-23 00:28:00 -070083void Font::renderUTF(SkPaint* paint, const char* text, uint32_t start, uint32_t len,
Romain Guy09147fb2010-07-22 13:08:20 -070084 int numGlyphs, int x, int y) {
Romain Guy694b5192010-07-21 21:33:20 -070085 if (numGlyphs == 0 || text == NULL || len == 0) {
86 return;
87 }
88
89 int penX = x, penY = y;
90 int glyphsLeft = 1;
91 if (numGlyphs > 0) {
92 glyphsLeft = numGlyphs;
93 }
94
Romain Guy694b5192010-07-21 21:33:20 -070095 text += start;
96
97 while (glyphsLeft > 0) {
Romain Guy694b5192010-07-21 21:33:20 -070098 int32_t utfChar = SkUTF16_NextUnichar((const uint16_t**) &text);
99
100 // Reached the end of the string or encountered
101 if (utfChar < 0) {
102 break;
103 }
104
Romain Guy51769a62010-07-23 00:28:00 -0700105 CachedGlyphInfo* cachedGlyph = mCachedGlyphs.valueFor(utfChar);
Romain Guy694b5192010-07-21 21:33:20 -0700106 if (cachedGlyph == NULL) {
107 cachedGlyph = cacheGlyph(paint, utfChar);
108 }
Romain Guy51769a62010-07-23 00:28:00 -0700109
Romain Guy694b5192010-07-21 21:33:20 -0700110 // Is the glyph still in texture cache?
111 if (!cachedGlyph->mIsValid) {
112 const SkGlyph& skiaGlyph = paint->getUnicharMetrics(utfChar);
113 updateGlyphCache(paint, skiaGlyph, cachedGlyph);
114 }
115
116 // If it's still not valid, we couldn't cache it, so we shouldn't draw garbage
117 if (cachedGlyph->mIsValid) {
118 drawCachedGlyph(cachedGlyph, penX, penY);
119 }
120
Romain Guy09147fb2010-07-22 13:08:20 -0700121 penX += SkFixedFloor(cachedGlyph->mAdvanceX);
Romain Guy694b5192010-07-21 21:33:20 -0700122
123 // If we were given a specific number of glyphs, decrement
124 if (numGlyphs > 0) {
125 glyphsLeft--;
126 }
127 }
128}
129
Romain Guy51769a62010-07-23 00:28:00 -0700130void Font::updateGlyphCache(SkPaint* paint, const SkGlyph& skiaGlyph, CachedGlyphInfo* glyph) {
Romain Guy694b5192010-07-21 21:33:20 -0700131 glyph->mAdvanceX = skiaGlyph.fAdvanceX;
132 glyph->mAdvanceY = skiaGlyph.fAdvanceY;
133 glyph->mBitmapLeft = skiaGlyph.fLeft;
134 glyph->mBitmapTop = skiaGlyph.fTop;
135
136 uint32_t startX = 0;
137 uint32_t startY = 0;
138
Romain Guy694b5192010-07-21 21:33:20 -0700139 // Get the bitmap for the glyph
140 paint->findImage(skiaGlyph);
Romain Guy51769a62010-07-23 00:28:00 -0700141 glyph->mIsValid = mState->cacheBitmap(skiaGlyph, &startX, &startY);
Romain Guy694b5192010-07-21 21:33:20 -0700142
143 if (!glyph->mIsValid) {
144 return;
145 }
146
147 uint32_t endX = startX + skiaGlyph.fWidth;
148 uint32_t endY = startY + skiaGlyph.fHeight;
149
150 glyph->mBitmapWidth = skiaGlyph.fWidth;
151 glyph->mBitmapHeight = skiaGlyph.fHeight;
152
Romain Guy51769a62010-07-23 00:28:00 -0700153 uint32_t cacheWidth = mState->getCacheWidth();
154 uint32_t cacheHeight = mState->getCacheHeight();
Romain Guy694b5192010-07-21 21:33:20 -0700155
156 glyph->mBitmapMinU = (float) startX / (float) cacheWidth;
157 glyph->mBitmapMinV = (float) startY / (float) cacheHeight;
158 glyph->mBitmapMaxU = (float) endX / (float) cacheWidth;
159 glyph->mBitmapMaxV = (float) endY / (float) cacheHeight;
160
Romain Guy51769a62010-07-23 00:28:00 -0700161 mState->mUploadTexture = true;
Romain Guy694b5192010-07-21 21:33:20 -0700162}
163
Romain Guy51769a62010-07-23 00:28:00 -0700164Font::CachedGlyphInfo* Font::cacheGlyph(SkPaint* paint, int32_t glyph) {
165 CachedGlyphInfo* newGlyph = new CachedGlyphInfo();
Romain Guy694b5192010-07-21 21:33:20 -0700166 mCachedGlyphs.add(glyph, newGlyph);
167
168 const SkGlyph& skiaGlyph = paint->getUnicharMetrics(glyph);
169 newGlyph->mGlyphIndex = skiaGlyph.fID;
170 newGlyph->mIsValid = false;
171
172 updateGlyphCache(paint, skiaGlyph, newGlyph);
173
174 return newGlyph;
175}
176
177Font* Font::create(FontRenderer* state, uint32_t fontId, float fontSize) {
178 Vector<Font*> &activeFonts = state->mActiveFonts;
179
180 for (uint32_t i = 0; i < activeFonts.size(); i++) {
Romain Guy51769a62010-07-23 00:28:00 -0700181 Font* font = activeFonts[i];
182 if (font->mFontId == fontId && font->mFontSize == fontSize) {
183 return font;
Romain Guy694b5192010-07-21 21:33:20 -0700184 }
185 }
186
187 Font* newFont = new Font(state, fontId, fontSize);
188 activeFonts.push(newFont);
189 return newFont;
190}
191
192///////////////////////////////////////////////////////////////////////////////
193// FontRenderer
194///////////////////////////////////////////////////////////////////////////////
195
196FontRenderer::FontRenderer() {
Romain Guy51769a62010-07-23 00:28:00 -0700197 LOGD("Creating FontRenderer");
198
Romain Guy694b5192010-07-21 21:33:20 -0700199 mInitialized = false;
200 mMaxNumberOfQuads = 1024;
201 mCurrentQuadIndex = 0;
202
203 mIndexBufferID = 0;
204
Romain Guy51769a62010-07-23 00:28:00 -0700205 mCacheWidth = DEFAULT_TEXT_CACHE_WIDTH;
206 mCacheHeight = DEFAULT_TEXT_CACHE_WIDTH;
207
208 char property[PROPERTY_VALUE_MAX];
209 if (property_get(PROPERTY_TEXT_CACHE_WIDTH, property, NULL) > 0) {
210 LOGD(" Setting text cache width to %s pixels", property);
211 mCacheWidth = atoi(property);
212 } else {
213 LOGD(" Using default text cache width of %i pixels", mCacheWidth);
214 }
215
216 if (property_get(PROPERTY_TEXT_CACHE_HEIGHT, property, NULL) > 0) {
217 LOGD(" Setting text cache width to %s pixels", property);
218 mCacheHeight = atoi(property);
219 } else {
220 LOGD(" Using default text cache height of %i pixels", mCacheHeight);
221 }
Romain Guy694b5192010-07-21 21:33:20 -0700222}
223
224FontRenderer::~FontRenderer() {
225 for (uint32_t i = 0; i < mCacheLines.size(); i++) {
226 delete mCacheLines[i];
227 }
228 mCacheLines.clear();
229
230 delete mTextTexture;
231
232 Vector<Font*> fontsToDereference = mActiveFonts;
233 for (uint32_t i = 0; i < fontsToDereference.size(); i++) {
234 delete fontsToDereference[i];
235 }
236}
237
238void FontRenderer::flushAllAndInvalidate() {
239 if (mCurrentQuadIndex != 0) {
240 issueDrawCommand();
241 mCurrentQuadIndex = 0;
242 }
243 for (uint32_t i = 0; i < mActiveFonts.size(); i++) {
244 mActiveFonts[i]->invalidateTextureCache();
245 }
246 for (uint32_t i = 0; i < mCacheLines.size(); i++) {
247 mCacheLines[i]->mCurrentCol = 0;
248 }
249}
250
Romain Guy51769a62010-07-23 00:28:00 -0700251bool FontRenderer::cacheBitmap(const SkGlyph& glyph, uint32_t* retOriginX, uint32_t* retOriginY) {
Romain Guy694b5192010-07-21 21:33:20 -0700252 // If the glyph is too tall, don't cache it
253 if (glyph.fWidth > mCacheLines[mCacheLines.size() - 1]->mMaxHeight) {
254 LOGE("Font size to large to fit in cache. width, height = %i, %i",
255 (int) glyph.fWidth, (int) glyph.fHeight);
256 return false;
257 }
258
259 // Now copy the bitmap into the cache texture
260 uint32_t startX = 0;
261 uint32_t startY = 0;
262
263 bool bitmapFit = false;
264 for (uint32_t i = 0; i < mCacheLines.size(); i++) {
265 bitmapFit = mCacheLines[i]->fitBitmap(glyph, &startX, &startY);
266 if (bitmapFit) {
267 break;
268 }
269 }
270
271 // If the new glyph didn't fit, flush the state so far and invalidate everything
272 if (!bitmapFit) {
273 flushAllAndInvalidate();
274
275 // Try to fit it again
276 for (uint32_t i = 0; i < mCacheLines.size(); i++) {
277 bitmapFit = mCacheLines[i]->fitBitmap(glyph, &startX, &startY);
278 if (bitmapFit) {
279 break;
280 }
281 }
282
283 // if we still don't fit, something is wrong and we shouldn't draw
284 if (!bitmapFit) {
285 LOGE("Bitmap doesn't fit in cache. width, height = %i, %i",
286 (int) glyph.fWidth, (int) glyph.fHeight);
287 return false;
288 }
289 }
290
291 *retOriginX = startX;
292 *retOriginY = startY;
293
294 uint32_t endX = startX + glyph.fWidth;
295 uint32_t endY = startY + glyph.fHeight;
296
297 uint32_t cacheWidth = mCacheWidth;
298
Romain Guy51769a62010-07-23 00:28:00 -0700299 unsigned char* cacheBuffer = mTextTexture;
300 unsigned char* bitmapBuffer = (unsigned char*) glyph.fImage;
Romain Guy694b5192010-07-21 21:33:20 -0700301 unsigned int stride = glyph.rowBytes();
302
303 uint32_t cacheX = 0, bX = 0, cacheY = 0, bY = 0;
304 for (cacheX = startX, bX = 0; cacheX < endX; cacheX++, bX++) {
305 for (cacheY = startY, bY = 0; cacheY < endY; cacheY++, bY++) {
306 unsigned char tempCol = bitmapBuffer[bY * stride + bX];
307 cacheBuffer[cacheY * cacheWidth + cacheX] = tempCol;
308 }
309 }
310
311 return true;
312}
313
314void FontRenderer::initTextTexture() {
315 mTextTexture = new unsigned char[mCacheWidth * mCacheHeight];
316 mUploadTexture = false;
317
318 glGenTextures(1, &mTextureId);
319 glBindTexture(GL_TEXTURE_2D, mTextureId);
320 glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
321
322 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
323 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
324
325 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
326 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
327
328 // Split up our cache texture into lines of certain widths
329 int nextLine = 0;
Romain Guy51769a62010-07-23 00:28:00 -0700330 mCacheLines.push(new CacheTextureLine(mCacheWidth, 16, nextLine, 0));
Romain Guy694b5192010-07-21 21:33:20 -0700331 nextLine += mCacheLines.top()->mMaxHeight;
Romain Guy51769a62010-07-23 00:28:00 -0700332 mCacheLines.push(new CacheTextureLine(mCacheWidth, 24, nextLine, 0));
Romain Guy694b5192010-07-21 21:33:20 -0700333 nextLine += mCacheLines.top()->mMaxHeight;
Romain Guy51769a62010-07-23 00:28:00 -0700334 mCacheLines.push(new CacheTextureLine(mCacheWidth, 32, nextLine, 0));
Romain Guy694b5192010-07-21 21:33:20 -0700335 nextLine += mCacheLines.top()->mMaxHeight;
Romain Guy51769a62010-07-23 00:28:00 -0700336 mCacheLines.push(new CacheTextureLine(mCacheWidth, 32, nextLine, 0));
Romain Guy694b5192010-07-21 21:33:20 -0700337 nextLine += mCacheLines.top()->mMaxHeight;
Romain Guy51769a62010-07-23 00:28:00 -0700338 mCacheLines.push(new CacheTextureLine(mCacheWidth, 40, nextLine, 0));
Romain Guy694b5192010-07-21 21:33:20 -0700339 nextLine += mCacheLines.top()->mMaxHeight;
Romain Guy51769a62010-07-23 00:28:00 -0700340 mCacheLines.push(new CacheTextureLine(mCacheWidth, mCacheHeight - nextLine, nextLine, 0));
Romain Guy694b5192010-07-21 21:33:20 -0700341}
342
343// Avoid having to reallocate memory and render quad by quad
344void FontRenderer::initVertexArrayBuffers() {
345 uint32_t numIndicies = mMaxNumberOfQuads * 6;
346 uint32_t indexBufferSizeBytes = numIndicies * sizeof(uint16_t);
Romain Guy51769a62010-07-23 00:28:00 -0700347 uint16_t* indexBufferData = (uint16_t*) malloc(indexBufferSizeBytes);
Romain Guy694b5192010-07-21 21:33:20 -0700348
349 // Four verts, two triangles , six indices per quad
350 for (uint32_t i = 0; i < mMaxNumberOfQuads; i++) {
351 int i6 = i * 6;
352 int i4 = i * 4;
353
354 indexBufferData[i6 + 0] = i4 + 0;
355 indexBufferData[i6 + 1] = i4 + 1;
356 indexBufferData[i6 + 2] = i4 + 2;
357
358 indexBufferData[i6 + 3] = i4 + 0;
359 indexBufferData[i6 + 4] = i4 + 2;
360 indexBufferData[i6 + 5] = i4 + 3;
361 }
362
363 glGenBuffers(1, &mIndexBufferID);
364 glBindBuffer(GL_ARRAY_BUFFER, mIndexBufferID);
365 glBufferData(GL_ARRAY_BUFFER, indexBufferSizeBytes, indexBufferData, GL_DYNAMIC_DRAW);
366 glBindBuffer(GL_ARRAY_BUFFER, 0);
367
368 free(indexBufferData);
369
370 uint32_t coordSize = 3;
371 uint32_t uvSize = 2;
372 uint32_t vertsPerQuad = 4;
373 uint32_t vertexBufferSizeBytes = mMaxNumberOfQuads * vertsPerQuad * coordSize *
374 uvSize * sizeof(float);
375 mTextMeshPtr = (float*) malloc(vertexBufferSizeBytes);
376}
377
378// We don't want to allocate anything unless we actually draw text
379void FontRenderer::checkInit() {
380 if (mInitialized) {
381 return;
382 }
383
384 initTextTexture();
385 initVertexArrayBuffers();
386
387 mInitialized = true;
388}
389
390void FontRenderer::issueDrawCommand() {
391 if (mUploadTexture) {
392 glBindTexture(GL_TEXTURE_2D, mTextureId);
393 glTexImage2D(GL_TEXTURE_2D, 0, GL_ALPHA, mCacheWidth, mCacheHeight, 0, GL_ALPHA,
394 GL_UNSIGNED_BYTE, mTextTexture);
395 mUploadTexture = false;
396 }
397
Romain Guy51769a62010-07-23 00:28:00 -0700398 float* vtx = mTextMeshPtr;
399 float* tex = vtx + 3;
Romain Guy694b5192010-07-21 21:33:20 -0700400
401 // position is slot 0
402 uint32_t slot = 0;
403 glVertexAttribPointer(slot, 3, GL_FLOAT, false, 20, vtx);
404
405 // texture0 is slot 1
406 slot = 1;
407 glVertexAttribPointer(slot, 2, GL_FLOAT, false, 20, tex);
408
409 glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, mIndexBufferID);
410 glDrawElements(GL_TRIANGLES, mCurrentQuadIndex * 6, GL_UNSIGNED_SHORT, NULL);
411}
412
413void FontRenderer::appendMeshQuad(float x1, float y1, float z1, float u1, float v1, float x2,
414 float y2, float z2, float u2, float v2, float x3, float y3, float z3, float u3, float v3,
415 float x4, float y4, float z4, float u4, float v4) {
Romain Guy09147fb2010-07-22 13:08:20 -0700416 if (x1 > mClip->right || y1 < mClip->top || x2 < mClip->left || y4 > mClip->bottom) {
417 return;
418 }
419
Romain Guy694b5192010-07-21 21:33:20 -0700420 const uint32_t vertsPerQuad = 4;
421 const uint32_t floatsPerVert = 5;
Romain Guy51769a62010-07-23 00:28:00 -0700422 float* currentPos = mTextMeshPtr + mCurrentQuadIndex * vertsPerQuad * floatsPerVert;
Romain Guy694b5192010-07-21 21:33:20 -0700423
Romain Guy694b5192010-07-21 21:33:20 -0700424 (*currentPos++) = x1;
425 (*currentPos++) = y1;
426 (*currentPos++) = z1;
427 (*currentPos++) = u1;
428 (*currentPos++) = v1;
429
430 (*currentPos++) = x2;
431 (*currentPos++) = y2;
432 (*currentPos++) = z2;
433 (*currentPos++) = u2;
434 (*currentPos++) = v2;
435
436 (*currentPos++) = x3;
437 (*currentPos++) = y3;
438 (*currentPos++) = z3;
439 (*currentPos++) = u3;
440 (*currentPos++) = v3;
441
442 (*currentPos++) = x4;
443 (*currentPos++) = y4;
444 (*currentPos++) = z4;
445 (*currentPos++) = u4;
446 (*currentPos++) = v4;
447
448 mCurrentQuadIndex++;
449
450 if (mCurrentQuadIndex == mMaxNumberOfQuads) {
451 issueDrawCommand();
452 mCurrentQuadIndex = 0;
453 }
454}
455
456void FontRenderer::setFont(uint32_t fontId, float fontSize) {
457 mCurrentFont = Font::create(this, fontId, fontSize);
458}
459
Romain Guy51769a62010-07-23 00:28:00 -0700460void FontRenderer::renderText(SkPaint* paint, const Rect* clip, const char *text,
461 uint32_t startIndex, uint32_t len, int numGlyphs, int x, int y) {
Romain Guy694b5192010-07-21 21:33:20 -0700462 checkInit();
463
Romain Guy09147fb2010-07-22 13:08:20 -0700464 if (!mCurrentFont) {
465 LOGE("No font set");
Romain Guy694b5192010-07-21 21:33:20 -0700466 return;
467 }
468
Romain Guy09147fb2010-07-22 13:08:20 -0700469 mClip = clip;
Romain Guy51769a62010-07-23 00:28:00 -0700470 mCurrentFont->renderUTF(paint, text, startIndex, len, numGlyphs, x, y);
Romain Guy694b5192010-07-21 21:33:20 -0700471
472 if (mCurrentQuadIndex != 0) {
473 issueDrawCommand();
474 mCurrentQuadIndex = 0;
475 }
476}
477
Romain Guy694b5192010-07-21 21:33:20 -0700478}; // namespace uirenderer
479}; // namespace android