blob: cab68f013edfdf7e9a330bc5a835cd5f293cd84e [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>
Romain Guye2d345e2010-09-24 18:39:22 -070022
Romain Guy51769a62010-07-23 00:28:00 -070023#include <utils/Log.h>
24
Romain Guy15bc6432011-12-13 13:11:32 -080025#include "Caches.h"
Romain Guyc9855a52011-01-21 21:14:15 -080026#include "Debug.h"
Romain Guy51769a62010-07-23 00:28:00 -070027#include "FontRenderer.h"
Romain Guy9f5dab32012-09-04 12:55:44 -070028#include "Rect.h"
Romain Guy51769a62010-07-23 00:28:00 -070029
Romain Guy694b5192010-07-21 21:33:20 -070030namespace android {
31namespace uirenderer {
32
33///////////////////////////////////////////////////////////////////////////////
Romain Guy694b5192010-07-21 21:33:20 -070034// FontRenderer
35///////////////////////////////////////////////////////////////////////////////
36
Romain Guy514fb182011-01-19 14:38:29 -080037static bool sLogFontRendererCreate = true;
38
Romain Guy694b5192010-07-21 21:33:20 -070039FontRenderer::FontRenderer() {
Romain Guyc9855a52011-01-21 21:14:15 -080040 if (sLogFontRendererCreate) {
41 INIT_LOGD("Creating FontRenderer");
42 }
Romain Guy51769a62010-07-23 00:28:00 -070043
Romain Guyb45c0c92010-08-26 20:35:23 -070044 mGammaTable = NULL;
Romain Guy694b5192010-07-21 21:33:20 -070045 mInitialized = false;
46 mMaxNumberOfQuads = 1024;
47 mCurrentQuadIndex = 0;
48
Romain Guy9b1204b2012-09-04 15:22:57 -070049 mTextMesh = NULL;
Chet Haase7de0cb12011-12-05 16:35:38 -080050 mCurrentCacheTexture = NULL;
51 mLastCacheTexture = NULL;
Romain Guy9cccc2b92010-08-07 23:46:15 -070052
Chet Haase2a47c142011-12-14 15:22:56 -080053 mLinearFiltering = false;
54
Romain Guy694b5192010-07-21 21:33:20 -070055 mIndexBufferID = 0;
56
Chet Haaseeb32a492012-08-31 13:54:03 -070057 mSmallCacheWidth = DEFAULT_TEXT_SMALL_CACHE_WIDTH;
58 mSmallCacheHeight = DEFAULT_TEXT_SMALL_CACHE_HEIGHT;
59 mLargeCacheWidth = DEFAULT_TEXT_LARGE_CACHE_WIDTH;
60 mLargeCacheHeight = DEFAULT_TEXT_LARGE_CACHE_HEIGHT;
Romain Guy51769a62010-07-23 00:28:00 -070061
62 char property[PROPERTY_VALUE_MAX];
Chet Haaseeb32a492012-08-31 13:54:03 -070063 if (property_get(PROPERTY_TEXT_SMALL_CACHE_WIDTH, property, NULL) > 0) {
Chet Haase7de0cb12011-12-05 16:35:38 -080064 mSmallCacheWidth = atoi(property);
Romain Guy51769a62010-07-23 00:28:00 -070065 }
Romain Guy9f5dab32012-09-04 12:55:44 -070066
Chet Haaseeb32a492012-08-31 13:54:03 -070067 if (property_get(PROPERTY_TEXT_SMALL_CACHE_HEIGHT, property, NULL) > 0) {
Chet Haase7de0cb12011-12-05 16:35:38 -080068 mSmallCacheHeight = atoi(property);
Chet Haaseeb32a492012-08-31 13:54:03 -070069 }
Romain Guy9f5dab32012-09-04 12:55:44 -070070
Chet Haaseeb32a492012-08-31 13:54:03 -070071 if (property_get(PROPERTY_TEXT_LARGE_CACHE_WIDTH, property, NULL) > 0) {
72 mLargeCacheWidth = atoi(property);
73 }
Romain Guy9f5dab32012-09-04 12:55:44 -070074
Chet Haaseeb32a492012-08-31 13:54:03 -070075 if (property_get(PROPERTY_TEXT_LARGE_CACHE_HEIGHT, property, NULL) > 0) {
76 mLargeCacheHeight = atoi(property);
77 }
Romain Guy9f5dab32012-09-04 12:55:44 -070078
79 uint32_t maxTextureSize = (uint32_t) Caches::getInstance().maxTextureSize;
80 mSmallCacheWidth = mSmallCacheWidth > maxTextureSize ? maxTextureSize : mSmallCacheWidth;
81 mSmallCacheHeight = mSmallCacheHeight > maxTextureSize ? maxTextureSize : mSmallCacheHeight;
82 mLargeCacheWidth = mLargeCacheWidth > maxTextureSize ? maxTextureSize : mLargeCacheWidth;
83 mLargeCacheHeight = mLargeCacheHeight > maxTextureSize ? maxTextureSize : mLargeCacheHeight;
84
Chet Haaseeb32a492012-08-31 13:54:03 -070085 if (sLogFontRendererCreate) {
86 INIT_LOGD(" Text cache sizes, in pixels: %i x %i, %i x %i, %i x %i, %i x %i",
87 mSmallCacheWidth, mSmallCacheHeight,
88 mLargeCacheWidth, mLargeCacheHeight >> 1,
89 mLargeCacheWidth, mLargeCacheHeight >> 1,
90 mLargeCacheWidth, mLargeCacheHeight);
Romain Guy51769a62010-07-23 00:28:00 -070091 }
Romain Guy514fb182011-01-19 14:38:29 -080092
93 sLogFontRendererCreate = false;
Romain Guy694b5192010-07-21 21:33:20 -070094}
95
96FontRenderer::~FontRenderer() {
Chet Haase378e9192012-08-15 15:54:54 -070097 for (uint32_t i = 0; i < mCacheTextures.size(); i++) {
98 delete mCacheTextures[i];
Romain Guy694b5192010-07-21 21:33:20 -070099 }
Chet Haase378e9192012-08-15 15:54:54 -0700100 mCacheTextures.clear();
Romain Guy694b5192010-07-21 21:33:20 -0700101
Romain Guy9cccc2b92010-08-07 23:46:15 -0700102 if (mInitialized) {
Romain Guya9dd8202012-03-26 14:52:00 -0700103 // Unbinding the buffer shouldn't be necessary but it crashes with some drivers
104 Caches::getInstance().unbindIndicesBuffer();
Romain Guyb0317982012-03-21 11:52:52 -0700105 glDeleteBuffers(1, &mIndexBufferID);
106
Romain Guy9b1204b2012-09-04 15:22:57 -0700107 delete[] mTextMesh;
Alex Sakhartchouk9b9902d2010-07-23 14:45:49 -0700108 }
Romain Guy694b5192010-07-21 21:33:20 -0700109
110 Vector<Font*> fontsToDereference = mActiveFonts;
111 for (uint32_t i = 0; i < fontsToDereference.size(); i++) {
112 delete fontsToDereference[i];
113 }
114}
115
116void FontRenderer::flushAllAndInvalidate() {
117 if (mCurrentQuadIndex != 0) {
118 issueDrawCommand();
119 mCurrentQuadIndex = 0;
120 }
Romain Guy9d9758a2012-05-14 15:19:58 -0700121
Romain Guy694b5192010-07-21 21:33:20 -0700122 for (uint32_t i = 0; i < mActiveFonts.size(); i++) {
123 mActiveFonts[i]->invalidateTextureCache();
124 }
Romain Guy9d9758a2012-05-14 15:19:58 -0700125
Chet Haase378e9192012-08-15 15:54:54 -0700126 for (uint32_t i = 0; i < mCacheTextures.size(); i++) {
127 mCacheTextures[i]->init();
Romain Guy694b5192010-07-21 21:33:20 -0700128 }
Chet Haasee816bae2012-08-09 13:39:02 -0700129
Romain Guy80872462012-09-04 16:42:01 -0700130#if DEBUG_FONT_RENDERER
Chet Haase378e9192012-08-15 15:54:54 -0700131 uint16_t totalGlyphs = 0;
132 for (uint32_t i = 0; i < mCacheTextures.size(); i++) {
Romain Guy80872462012-09-04 16:42:01 -0700133 totalGlyphs += mCacheTextures[i]->getGlyphCount();
Chet Haase378e9192012-08-15 15:54:54 -0700134 // Erase caches, just as a debugging facility
Romain Guy80872462012-09-04 16:42:01 -0700135 if (mCacheTextures[i]->getTexture()) {
136 memset(mCacheTextures[i]->getTexture(), 0,
137 mCacheTextures[i]->getWidth() * mCacheTextures[i]->getHeight());
Chet Haase378e9192012-08-15 15:54:54 -0700138 }
Chet Haasee816bae2012-08-09 13:39:02 -0700139 }
140 ALOGD("Flushing caches: glyphs cached = %d", totalGlyphs);
141#endif
Romain Guy694b5192010-07-21 21:33:20 -0700142}
143
Chet Haase9a824562011-12-16 15:44:59 -0800144void FontRenderer::flushLargeCaches() {
Chet Haase378e9192012-08-15 15:54:54 -0700145 // Start from 1; don't deallocate smallest/default texture
146 for (uint32_t i = 1; i < mCacheTextures.size(); i++) {
147 CacheTexture* cacheTexture = mCacheTextures[i];
Romain Guy80872462012-09-04 16:42:01 -0700148 if (cacheTexture->getTexture()) {
Chet Haase378e9192012-08-15 15:54:54 -0700149 cacheTexture->init();
150 for (uint32_t j = 0; j < mActiveFonts.size(); j++) {
151 mActiveFonts[j]->invalidateTextureCache(cacheTexture);
Chet Haasee816bae2012-08-09 13:39:02 -0700152 }
Romain Guy80872462012-09-04 16:42:01 -0700153 cacheTexture->releaseTexture();
Chet Haase9a824562011-12-16 15:44:59 -0800154 }
155 }
Chet Haase9a824562011-12-16 15:44:59 -0800156}
157
Chet Haase378e9192012-08-15 15:54:54 -0700158CacheTexture* FontRenderer::cacheBitmapInTexture(const SkGlyph& glyph,
159 uint32_t* startX, uint32_t* startY) {
160 for (uint32_t i = 0; i < mCacheTextures.size(); i++) {
161 if (mCacheTextures[i]->fitBitmap(glyph, startX, startY)) {
162 return mCacheTextures[i];
163 }
164 }
165 // Could not fit glyph into current cache textures
166 return NULL;
167}
168
Chet Haase7de0cb12011-12-05 16:35:38 -0800169void FontRenderer::cacheBitmap(const SkGlyph& glyph, CachedGlyphInfo* cachedGlyph,
Chet Haasef942cf12012-08-30 09:06:46 -0700170 uint32_t* retOriginX, uint32_t* retOriginY, bool precaching) {
Chet Haase2efd5c52012-08-15 13:15:16 -0700171 checkInit();
Chet Haase7de0cb12011-12-05 16:35:38 -0800172 cachedGlyph->mIsValid = false;
Romain Guy694b5192010-07-21 21:33:20 -0700173 // If the glyph is too tall, don't cache it
Chet Haase378e9192012-08-15 15:54:54 -0700174 if (glyph.fHeight + TEXTURE_BORDER_SIZE * 2 >
Romain Guy80872462012-09-04 16:42:01 -0700175 mCacheTextures[mCacheTextures.size() - 1]->getHeight()) {
Chet Haase2efd5c52012-08-15 13:15:16 -0700176 ALOGE("Font size too large to fit in cache. width, height = %i, %i",
177 (int) glyph.fWidth, (int) glyph.fHeight);
Chet Haase7de0cb12011-12-05 16:35:38 -0800178 return;
Romain Guy694b5192010-07-21 21:33:20 -0700179 }
180
181 // Now copy the bitmap into the cache texture
182 uint32_t startX = 0;
183 uint32_t startY = 0;
184
Chet Haase378e9192012-08-15 15:54:54 -0700185 CacheTexture* cacheTexture = cacheBitmapInTexture(glyph, &startX, &startY);
Romain Guy694b5192010-07-21 21:33:20 -0700186
Chet Haase378e9192012-08-15 15:54:54 -0700187 if (!cacheTexture) {
Chet Haasef942cf12012-08-30 09:06:46 -0700188 if (!precaching) {
189 // If the new glyph didn't fit and we are not just trying to precache it,
190 // clear out the cache and try again
191 flushAllAndInvalidate();
192 cacheTexture = cacheBitmapInTexture(glyph, &startX, &startY);
193 }
Romain Guy694b5192010-07-21 21:33:20 -0700194
Chet Haase378e9192012-08-15 15:54:54 -0700195 if (!cacheTexture) {
Chet Haasef942cf12012-08-30 09:06:46 -0700196 // either the glyph didn't fit or we're precaching and will cache it when we draw
Chet Haase7de0cb12011-12-05 16:35:38 -0800197 return;
Romain Guy694b5192010-07-21 21:33:20 -0700198 }
199 }
200
Chet Haase378e9192012-08-15 15:54:54 -0700201 cachedGlyph->mCacheTexture = cacheTexture;
Chet Haase7de0cb12011-12-05 16:35:38 -0800202
Romain Guy694b5192010-07-21 21:33:20 -0700203 *retOriginX = startX;
204 *retOriginY = startY;
205
206 uint32_t endX = startX + glyph.fWidth;
207 uint32_t endY = startY + glyph.fHeight;
208
Romain Guy80872462012-09-04 16:42:01 -0700209 uint32_t cacheWidth = cacheTexture->getWidth();
Romain Guy694b5192010-07-21 21:33:20 -0700210
Romain Guy80872462012-09-04 16:42:01 -0700211 if (!cacheTexture->getTexture()) {
212 Caches::getInstance().activeTexture(0);
Chet Haase7de0cb12011-12-05 16:35:38 -0800213 // Large-glyph texture memory is allocated only as needed
Romain Guy80872462012-09-04 16:42:01 -0700214 cacheTexture->allocateTexture();
Chet Haase7de0cb12011-12-05 16:35:38 -0800215 }
Romain Guy9d9758a2012-05-14 15:19:58 -0700216
Romain Guy80872462012-09-04 16:42:01 -0700217 uint8_t* cacheBuffer = cacheTexture->getTexture();
Alex Sakhartchouk89a524a2010-08-02 17:52:30 -0700218 uint8_t* bitmapBuffer = (uint8_t*) glyph.fImage;
Romain Guy694b5192010-07-21 21:33:20 -0700219 unsigned int stride = glyph.rowBytes();
220
221 uint32_t cacheX = 0, bX = 0, cacheY = 0, bY = 0;
Romain Guy33fa1f72012-08-07 19:09:57 -0700222
223 for (cacheX = startX - TEXTURE_BORDER_SIZE; cacheX < endX + TEXTURE_BORDER_SIZE; cacheX++) {
224 cacheBuffer[(startY - TEXTURE_BORDER_SIZE) * cacheWidth + cacheX] = 0;
225 cacheBuffer[(endY + TEXTURE_BORDER_SIZE - 1) * cacheWidth + cacheX] = 0;
226 }
227
228 for (cacheY = startY - TEXTURE_BORDER_SIZE + 1;
229 cacheY < endY + TEXTURE_BORDER_SIZE - 1; cacheY++) {
230 cacheBuffer[cacheY * cacheWidth + startX - TEXTURE_BORDER_SIZE] = 0;
231 cacheBuffer[cacheY * cacheWidth + endX + TEXTURE_BORDER_SIZE - 1] = 0;
232 }
233
Romain Guyb1d0a4e2012-07-13 18:25:35 -0700234 if (mGammaTable) {
235 for (cacheX = startX, bX = 0; cacheX < endX; cacheX++, bX++) {
236 for (cacheY = startY, bY = 0; cacheY < endY; cacheY++, bY++) {
237 uint8_t tempCol = bitmapBuffer[bY * stride + bX];
238 cacheBuffer[cacheY * cacheWidth + cacheX] = mGammaTable[tempCol];
239 }
240 }
241 } else {
242 for (cacheX = startX, bX = 0; cacheX < endX; cacheX++, bX++) {
243 for (cacheY = startY, bY = 0; cacheY < endY; cacheY++, bY++) {
244 uint8_t tempCol = bitmapBuffer[bY * stride + bX];
245 cacheBuffer[cacheY * cacheWidth + cacheX] = tempCol;
246 }
Romain Guy694b5192010-07-21 21:33:20 -0700247 }
248 }
Romain Guy97771732012-02-28 18:17:02 -0800249
Chet Haase7de0cb12011-12-05 16:35:38 -0800250 cachedGlyph->mIsValid = true;
Romain Guy694b5192010-07-21 21:33:20 -0700251}
252
Chet Haase7de0cb12011-12-05 16:35:38 -0800253CacheTexture* FontRenderer::createCacheTexture(int width, int height, bool allocate) {
Romain Guy0aa87bb2012-07-20 11:14:32 -0700254 CacheTexture* cacheTexture = new CacheTexture(width, height);
Romain Guy9d9758a2012-05-14 15:19:58 -0700255
Chet Haase2a47c142011-12-14 15:22:56 -0800256 if (allocate) {
Romain Guy80872462012-09-04 16:42:01 -0700257 Caches::getInstance().activeTexture(0);
258 cacheTexture->allocateTexture();
Chet Haase2a47c142011-12-14 15:22:56 -0800259 }
Romain Guy9d9758a2012-05-14 15:19:58 -0700260
Chet Haase2a47c142011-12-14 15:22:56 -0800261 return cacheTexture;
Chet Haase7de0cb12011-12-05 16:35:38 -0800262}
263
264void FontRenderer::initTextTexture() {
Chet Haase378e9192012-08-15 15:54:54 -0700265 for (uint32_t i = 0; i < mCacheTextures.size(); i++) {
266 delete mCacheTextures[i];
Romain Guy9d9758a2012-05-14 15:19:58 -0700267 }
Chet Haase378e9192012-08-15 15:54:54 -0700268 mCacheTextures.clear();
Romain Guy9d9758a2012-05-14 15:19:58 -0700269
Chet Haase7de0cb12011-12-05 16:35:38 -0800270 mUploadTexture = false;
Chet Haase378e9192012-08-15 15:54:54 -0700271 mCacheTextures.push(createCacheTexture(mSmallCacheWidth, mSmallCacheHeight, true));
Chet Haaseeb32a492012-08-31 13:54:03 -0700272 mCacheTextures.push(createCacheTexture(mLargeCacheWidth, mLargeCacheHeight >> 1, false));
273 mCacheTextures.push(createCacheTexture(mLargeCacheWidth, mLargeCacheHeight >> 1, false));
274 mCacheTextures.push(createCacheTexture(mLargeCacheWidth, mLargeCacheHeight, false));
Chet Haase378e9192012-08-15 15:54:54 -0700275 mCurrentCacheTexture = mCacheTextures[0];
Romain Guy694b5192010-07-21 21:33:20 -0700276}
277
278// Avoid having to reallocate memory and render quad by quad
279void FontRenderer::initVertexArrayBuffers() {
Romain Guyd71dd362011-12-12 19:03:35 -0800280 uint32_t numIndices = mMaxNumberOfQuads * 6;
281 uint32_t indexBufferSizeBytes = numIndices * sizeof(uint16_t);
Romain Guy51769a62010-07-23 00:28:00 -0700282 uint16_t* indexBufferData = (uint16_t*) malloc(indexBufferSizeBytes);
Romain Guy694b5192010-07-21 21:33:20 -0700283
284 // Four verts, two triangles , six indices per quad
285 for (uint32_t i = 0; i < mMaxNumberOfQuads; i++) {
286 int i6 = i * 6;
287 int i4 = i * 4;
288
289 indexBufferData[i6 + 0] = i4 + 0;
290 indexBufferData[i6 + 1] = i4 + 1;
291 indexBufferData[i6 + 2] = i4 + 2;
292
293 indexBufferData[i6 + 3] = i4 + 0;
294 indexBufferData[i6 + 4] = i4 + 2;
295 indexBufferData[i6 + 5] = i4 + 3;
296 }
297
298 glGenBuffers(1, &mIndexBufferID);
Romain Guy15bc6432011-12-13 13:11:32 -0800299 Caches::getInstance().bindIndicesBuffer(mIndexBufferID);
Romain Guy5d794412010-10-13 16:36:22 -0700300 glBufferData(GL_ELEMENT_ARRAY_BUFFER, indexBufferSizeBytes, indexBufferData, GL_STATIC_DRAW);
Romain Guy694b5192010-07-21 21:33:20 -0700301
302 free(indexBufferData);
303
Romain Guyd71dd362011-12-12 19:03:35 -0800304 uint32_t coordSize = 2;
Romain Guy694b5192010-07-21 21:33:20 -0700305 uint32_t uvSize = 2;
306 uint32_t vertsPerQuad = 4;
Alex Sakhartchouk9b9902d2010-07-23 14:45:49 -0700307 uint32_t vertexBufferSize = mMaxNumberOfQuads * vertsPerQuad * coordSize * uvSize;
Romain Guy9b1204b2012-09-04 15:22:57 -0700308 mTextMesh = new float[vertexBufferSize];
Romain Guy694b5192010-07-21 21:33:20 -0700309}
310
311// We don't want to allocate anything unless we actually draw text
312void FontRenderer::checkInit() {
313 if (mInitialized) {
314 return;
315 }
316
317 initTextTexture();
318 initVertexArrayBuffers();
319
320 mInitialized = true;
321}
322
Alex Sakhartchouk9b9902d2010-07-23 14:45:49 -0700323void FontRenderer::checkTextureUpdate() {
Chet Haase7de0cb12011-12-05 16:35:38 -0800324 if (!mUploadTexture && mLastCacheTexture == mCurrentCacheTexture) {
Alex Sakhartchouk9b9902d2010-07-23 14:45:49 -0700325 return;
Romain Guy694b5192010-07-21 21:33:20 -0700326 }
327
Romain Guy2d4fd362011-12-13 22:00:19 -0800328 Caches& caches = Caches::getInstance();
329 GLuint lastTextureId = 0;
Chet Haase378e9192012-08-15 15:54:54 -0700330 // Iterate over all the cache textures and see which ones need to be updated
331 for (uint32_t i = 0; i < mCacheTextures.size(); i++) {
332 CacheTexture* cacheTexture = mCacheTextures[i];
Romain Guy80872462012-09-04 16:42:01 -0700333 if (cacheTexture->isDirty() && cacheTexture->getTexture()) {
Chet Haaseb92d8f72012-09-21 08:40:46 -0700334 // Can't copy inner rect; glTexSubimage expects pointer to deal with entire buffer
335 // of data. So expand the dirty rect to the encompassing horizontal stripe.
336 const Rect* dirtyRect = cacheTexture->getDirtyRect();
337 uint32_t x = 0;
338 uint32_t y = dirtyRect->top;
Romain Guy80872462012-09-04 16:42:01 -0700339 uint32_t width = cacheTexture->getWidth();
Chet Haaseb92d8f72012-09-21 08:40:46 -0700340 uint32_t height = dirtyRect->getHeight();
341 void* textureData = cacheTexture->getTexture() + y * width;
Alex Sakhartchouk9b9902d2010-07-23 14:45:49 -0700342
Romain Guy80872462012-09-04 16:42:01 -0700343 if (cacheTexture->getTextureId() != lastTextureId) {
344 lastTextureId = cacheTexture->getTextureId();
Romain Guy2d4fd362011-12-13 22:00:19 -0800345 caches.activeTexture(0);
Romain Guy80872462012-09-04 16:42:01 -0700346 glBindTexture(GL_TEXTURE_2D, lastTextureId);
Romain Guy2d4fd362011-12-13 22:00:19 -0800347 }
Chet Haasee816bae2012-08-09 13:39:02 -0700348#if DEBUG_FONT_RENDERER
Chet Haaseb92d8f72012-09-21 08:40:46 -0700349 ALOGD("glTexSubimage for cacheTexture %d: x, y, width height = %d, %d, %d, %d",
350 i, x, y, width, height);
Chet Haasee816bae2012-08-09 13:39:02 -0700351#endif
Chet Haaseb92d8f72012-09-21 08:40:46 -0700352 glTexSubImage2D(GL_TEXTURE_2D, 0, x, y, width, height,
Romain Guy1e45aae2010-08-13 19:39:53 -0700353 GL_ALPHA, GL_UNSIGNED_BYTE, textureData);
Romain Guy80872462012-09-04 16:42:01 -0700354 cacheTexture->setDirty(false);
Alex Sakhartchouk9b9902d2010-07-23 14:45:49 -0700355 }
356 }
357
Romain Guy16c88082012-06-11 16:03:47 -0700358 caches.activeTexture(0);
Romain Guy80872462012-09-04 16:42:01 -0700359 glBindTexture(GL_TEXTURE_2D, mCurrentCacheTexture->getTextureId());
360
361 mCurrentCacheTexture->setLinearFiltering(mLinearFiltering, false);
Chet Haase7de0cb12011-12-05 16:35:38 -0800362 mLastCacheTexture = mCurrentCacheTexture;
363
Alex Sakhartchouk9b9902d2010-07-23 14:45:49 -0700364 mUploadTexture = false;
365}
366
367void FontRenderer::issueDrawCommand() {
Alex Sakhartchouk9b9902d2010-07-23 14:45:49 -0700368 checkTextureUpdate();
369
Romain Guy15bc6432011-12-13 13:11:32 -0800370 Caches& caches = Caches::getInstance();
Romain Guy2d4fd362011-12-13 22:00:19 -0800371 caches.bindIndicesBuffer(mIndexBufferID);
Romain Guy15bc6432011-12-13 13:11:32 -0800372 if (!mDrawn) {
Romain Guy9b1204b2012-09-04 15:22:57 -0700373 float* buffer = mTextMesh;
Romain Guy15bc6432011-12-13 13:11:32 -0800374 int offset = 2;
375
376 bool force = caches.unbindMeshBuffer();
377 caches.bindPositionVertexPointer(force, caches.currentProgram->position, buffer);
378 caches.bindTexCoordsVertexPointer(force, caches.currentProgram->texCoords,
379 buffer + offset);
380 }
381
Romain Guy694b5192010-07-21 21:33:20 -0700382 glDrawElements(GL_TRIANGLES, mCurrentQuadIndex * 6, GL_UNSIGNED_SHORT, NULL);
Romain Guy5b3b3522010-10-27 18:57:51 -0700383
384 mDrawn = true;
Romain Guy694b5192010-07-21 21:33:20 -0700385}
386
Romain Guy97771732012-02-28 18:17:02 -0800387void FontRenderer::appendMeshQuadNoClip(float x1, float y1, float u1, float v1,
388 float x2, float y2, float u2, float v2, float x3, float y3, float u3, float v3,
Chet Haase7de0cb12011-12-05 16:35:38 -0800389 float x4, float y4, float u4, float v4, CacheTexture* texture) {
Chet Haase7de0cb12011-12-05 16:35:38 -0800390 if (texture != mCurrentCacheTexture) {
391 if (mCurrentQuadIndex != 0) {
392 // First, draw everything stored already which uses the previous texture
393 issueDrawCommand();
394 mCurrentQuadIndex = 0;
395 }
396 // Now use the new texture id
397 mCurrentCacheTexture = texture;
398 }
Romain Guy09147fb2010-07-22 13:08:20 -0700399
Romain Guy694b5192010-07-21 21:33:20 -0700400 const uint32_t vertsPerQuad = 4;
Romain Guyd71dd362011-12-12 19:03:35 -0800401 const uint32_t floatsPerVert = 4;
Romain Guy9b1204b2012-09-04 15:22:57 -0700402 float* currentPos = mTextMesh + mCurrentQuadIndex * vertsPerQuad * floatsPerVert;
Romain Guy694b5192010-07-21 21:33:20 -0700403
Romain Guy694b5192010-07-21 21:33:20 -0700404 (*currentPos++) = x1;
405 (*currentPos++) = y1;
Romain Guy694b5192010-07-21 21:33:20 -0700406 (*currentPos++) = u1;
407 (*currentPos++) = v1;
408
409 (*currentPos++) = x2;
410 (*currentPos++) = y2;
Romain Guy694b5192010-07-21 21:33:20 -0700411 (*currentPos++) = u2;
412 (*currentPos++) = v2;
413
414 (*currentPos++) = x3;
415 (*currentPos++) = y3;
Romain Guy694b5192010-07-21 21:33:20 -0700416 (*currentPos++) = u3;
417 (*currentPos++) = v3;
418
419 (*currentPos++) = x4;
420 (*currentPos++) = y4;
Romain Guy694b5192010-07-21 21:33:20 -0700421 (*currentPos++) = u4;
422 (*currentPos++) = v4;
423
424 mCurrentQuadIndex++;
Romain Guy97771732012-02-28 18:17:02 -0800425}
426
427void FontRenderer::appendMeshQuad(float x1, float y1, float u1, float v1,
428 float x2, float y2, float u2, float v2, float x3, float y3, float u3, float v3,
429 float x4, float y4, float u4, float v4, CacheTexture* texture) {
430
431 if (mClip &&
432 (x1 > mClip->right || y1 < mClip->top || x2 < mClip->left || y4 > mClip->bottom)) {
433 return;
434 }
435
436 appendMeshQuadNoClip(x1, y1, u1, v1, x2, y2, u2, v2, x3, y3, u3, v3, x4, y4, u4, v4, texture);
Romain Guy694b5192010-07-21 21:33:20 -0700437
Romain Guy5b3b3522010-10-27 18:57:51 -0700438 if (mBounds) {
439 mBounds->left = fmin(mBounds->left, x1);
440 mBounds->top = fmin(mBounds->top, y3);
441 mBounds->right = fmax(mBounds->right, x3);
442 mBounds->bottom = fmax(mBounds->bottom, y1);
443 }
444
Romain Guy694b5192010-07-21 21:33:20 -0700445 if (mCurrentQuadIndex == mMaxNumberOfQuads) {
446 issueDrawCommand();
447 mCurrentQuadIndex = 0;
448 }
449}
450
Romain Guy97771732012-02-28 18:17:02 -0800451void FontRenderer::appendRotatedMeshQuad(float x1, float y1, float u1, float v1,
452 float x2, float y2, float u2, float v2, float x3, float y3, float u3, float v3,
453 float x4, float y4, float u4, float v4, CacheTexture* texture) {
454
455 appendMeshQuadNoClip(x1, y1, u1, v1, x2, y2, u2, v2, x3, y3, u3, v3, x4, y4, u4, v4, texture);
456
457 if (mBounds) {
458 mBounds->left = fmin(mBounds->left, fmin(x1, fmin(x2, fmin(x3, x4))));
459 mBounds->top = fmin(mBounds->top, fmin(y1, fmin(y2, fmin(y3, y4))));
460 mBounds->right = fmax(mBounds->right, fmax(x1, fmax(x2, fmax(x3, x4))));
461 mBounds->bottom = fmax(mBounds->bottom, fmax(y1, fmax(y2, fmax(y3, y4))));
462 }
463
464 if (mCurrentQuadIndex == mMaxNumberOfQuads) {
465 issueDrawCommand();
466 mCurrentQuadIndex = 0;
467 }
468}
469
Alex Sakhartchouk65ef9092010-07-26 11:09:33 -0700470void FontRenderer::setFont(SkPaint* paint, uint32_t fontId, float fontSize) {
Romain Guy325a0f92011-01-05 15:26:55 -0800471 int flags = 0;
472 if (paint->isFakeBoldText()) {
473 flags |= Font::kFakeBold;
474 }
Romain Guy2577db12011-01-18 13:02:38 -0800475
476 const float skewX = paint->getTextSkewX();
477 uint32_t italicStyle = *(uint32_t*) &skewX;
Chet Haase8668f8a2011-03-02 13:51:36 -0800478 const float scaleXFloat = paint->getTextScaleX();
479 uint32_t scaleX = *(uint32_t*) &scaleXFloat;
Romain Guybd496bc2011-08-02 17:32:41 -0700480 SkPaint::Style style = paint->getStyle();
481 const float strokeWidthFloat = paint->getStrokeWidth();
482 uint32_t strokeWidth = *(uint32_t*) &strokeWidthFloat;
483 mCurrentFont = Font::create(this, fontId, fontSize, flags, italicStyle,
484 scaleX, style, strokeWidth);
Alex Sakhartchouk65ef9092010-07-26 11:09:33 -0700485
Romain Guy694b5192010-07-21 21:33:20 -0700486}
Romain Guy7975fb62010-10-01 16:36:14 -0700487
Alex Sakhartchoukf18136c2010-08-06 14:49:04 -0700488FontRenderer::DropShadow FontRenderer::renderDropShadow(SkPaint* paint, const char *text,
Raph Levien416a8472012-07-19 22:48:17 -0700489 uint32_t startIndex, uint32_t len, int numGlyphs, uint32_t radius, const float* positions) {
Romain Guy1e45aae2010-08-13 19:39:53 -0700490 checkInit();
491
492 if (!mCurrentFont) {
493 DropShadow image;
494 image.width = 0;
495 image.height = 0;
496 image.image = NULL;
497 image.penX = 0;
498 image.penY = 0;
499 return image;
500 }
Alex Sakhartchoukf18136c2010-08-06 14:49:04 -0700501
Romain Guy2d4fd362011-12-13 22:00:19 -0800502 mDrawn = false;
Romain Guyff98fa52011-11-28 09:35:09 -0800503 mClip = NULL;
504 mBounds = NULL;
505
Alex Sakhartchoukf18136c2010-08-06 14:49:04 -0700506 Rect bounds;
Raph Levien416a8472012-07-19 22:48:17 -0700507 mCurrentFont->measure(paint, text, startIndex, len, numGlyphs, &bounds, positions);
Romain Guyff98fa52011-11-28 09:35:09 -0800508
Romain Guy1e45aae2010-08-13 19:39:53 -0700509 uint32_t paddedWidth = (uint32_t) (bounds.right - bounds.left) + 2 * radius;
510 uint32_t paddedHeight = (uint32_t) (bounds.top - bounds.bottom) + 2 * radius;
Alex Sakhartchoukf18136c2010-08-06 14:49:04 -0700511 uint8_t* dataBuffer = new uint8_t[paddedWidth * paddedHeight];
Romain Guyff98fa52011-11-28 09:35:09 -0800512
Romain Guy1e45aae2010-08-13 19:39:53 -0700513 for (uint32_t i = 0; i < paddedWidth * paddedHeight; i++) {
Alex Sakhartchoukf18136c2010-08-06 14:49:04 -0700514 dataBuffer[i] = 0;
515 }
Romain Guy1e45aae2010-08-13 19:39:53 -0700516
Alex Sakhartchoukf18136c2010-08-06 14:49:04 -0700517 int penX = radius - bounds.left;
518 int penY = radius - bounds.bottom;
519
Romain Guy726aeba2011-06-01 14:52:00 -0700520 mCurrentFont->render(paint, text, startIndex, len, numGlyphs, penX, penY,
Raph Levien416a8472012-07-19 22:48:17 -0700521 Font::BITMAP, dataBuffer, paddedWidth, paddedHeight, NULL, positions);
Alex Sakhartchoukf18136c2010-08-06 14:49:04 -0700522 blurImage(dataBuffer, paddedWidth, paddedHeight, radius);
523
524 DropShadow image;
525 image.width = paddedWidth;
526 image.height = paddedHeight;
527 image.image = dataBuffer;
528 image.penX = penX;
529 image.penY = penY;
Romain Guy2d4fd362011-12-13 22:00:19 -0800530
Alex Sakhartchoukf18136c2010-08-06 14:49:04 -0700531 return image;
532}
Romain Guy694b5192010-07-21 21:33:20 -0700533
Romain Guy671d6cf2012-01-18 12:39:17 -0800534void FontRenderer::initRender(const Rect* clip, Rect* bounds) {
Romain Guy694b5192010-07-21 21:33:20 -0700535 checkInit();
536
Romain Guy5b3b3522010-10-27 18:57:51 -0700537 mDrawn = false;
538 mBounds = bounds;
Romain Guy09147fb2010-07-22 13:08:20 -0700539 mClip = clip;
Romain Guy671d6cf2012-01-18 12:39:17 -0800540}
Romain Guyff98fa52011-11-28 09:35:09 -0800541
Romain Guy671d6cf2012-01-18 12:39:17 -0800542void FontRenderer::finishRender() {
Romain Guy5b3b3522010-10-27 18:57:51 -0700543 mBounds = NULL;
Romain Guyff98fa52011-11-28 09:35:09 -0800544 mClip = NULL;
Romain Guy694b5192010-07-21 21:33:20 -0700545
546 if (mCurrentQuadIndex != 0) {
547 issueDrawCommand();
548 mCurrentQuadIndex = 0;
549 }
Romain Guy671d6cf2012-01-18 12:39:17 -0800550}
551
Chet Haasee816bae2012-08-09 13:39:02 -0700552void FontRenderer::precache(SkPaint* paint, const char* text, int numGlyphs) {
553 int flags = 0;
554 if (paint->isFakeBoldText()) {
555 flags |= Font::kFakeBold;
556 }
557 const float skewX = paint->getTextSkewX();
558 uint32_t italicStyle = *(uint32_t*) &skewX;
559 const float scaleXFloat = paint->getTextScaleX();
560 uint32_t scaleX = *(uint32_t*) &scaleXFloat;
561 SkPaint::Style style = paint->getStyle();
562 const float strokeWidthFloat = paint->getStrokeWidth();
563 uint32_t strokeWidth = *(uint32_t*) &strokeWidthFloat;
564 float fontSize = paint->getTextSize();
565 Font* font = Font::create(this, SkTypeface::UniqueID(paint->getTypeface()),
566 fontSize, flags, italicStyle, scaleX, style, strokeWidth);
567
568 font->precache(paint, text, numGlyphs);
569}
570
Romain Guy671d6cf2012-01-18 12:39:17 -0800571bool FontRenderer::renderText(SkPaint* paint, const Rect* clip, const char *text,
572 uint32_t startIndex, uint32_t len, int numGlyphs, int x, int y, Rect* bounds) {
573 if (!mCurrentFont) {
574 ALOGE("No font set");
575 return false;
576 }
577
578 initRender(clip, bounds);
579 mCurrentFont->render(paint, text, startIndex, len, numGlyphs, x, y);
580 finishRender();
581
582 return mDrawn;
583}
584
585bool FontRenderer::renderPosText(SkPaint* paint, const Rect* clip, const char *text,
586 uint32_t startIndex, uint32_t len, int numGlyphs, int x, int y,
587 const float* positions, Rect* bounds) {
588 if (!mCurrentFont) {
589 ALOGE("No font set");
590 return false;
591 }
592
593 initRender(clip, bounds);
594 mCurrentFont->render(paint, text, startIndex, len, numGlyphs, x, y, positions);
595 finishRender();
Romain Guy5b3b3522010-10-27 18:57:51 -0700596
597 return mDrawn;
Romain Guy694b5192010-07-21 21:33:20 -0700598}
599
Romain Guy97771732012-02-28 18:17:02 -0800600bool FontRenderer::renderTextOnPath(SkPaint* paint, const Rect* clip, const char *text,
601 uint32_t startIndex, uint32_t len, int numGlyphs, SkPath* path,
602 float hOffset, float vOffset, Rect* bounds) {
603 if (!mCurrentFont) {
604 ALOGE("No font set");
605 return false;
606 }
607
608 initRender(clip, bounds);
609 mCurrentFont->render(paint, text, startIndex, len, numGlyphs, path, hOffset, vOffset);
610 finishRender();
611
612 return mDrawn;
613}
614
Romain Guy9b1204b2012-09-04 15:22:57 -0700615void FontRenderer::removeFont(const Font* font) {
616 for (uint32_t ct = 0; ct < mActiveFonts.size(); ct++) {
617 if (mActiveFonts[ct] == font) {
618 mActiveFonts.removeAt(ct);
619 break;
620 }
621 }
622
623 if (mCurrentFont == font) {
624 mCurrentFont = NULL;
625 }
626}
627
Alex Sakhartchouk89a524a2010-08-02 17:52:30 -0700628void FontRenderer::computeGaussianWeights(float* weights, int32_t radius) {
629 // Compute gaussian weights for the blur
630 // e is the euler's number
631 float e = 2.718281828459045f;
632 float pi = 3.1415926535897932f;
633 // g(x) = ( 1 / sqrt( 2 * pi ) * sigma) * e ^ ( -x^2 / 2 * sigma^2 )
634 // x is of the form [-radius .. 0 .. radius]
635 // and sigma varies with radius.
636 // Based on some experimental radius values and sigma's
637 // we approximately fit sigma = f(radius) as
Alex Sakhartchoukf18136c2010-08-06 14:49:04 -0700638 // sigma = radius * 0.3 + 0.6
Alex Sakhartchouk89a524a2010-08-02 17:52:30 -0700639 // The larger the radius gets, the more our gaussian blur
640 // will resemble a box blur since with large sigma
641 // the gaussian curve begins to lose its shape
Romain Guy325a0f92011-01-05 15:26:55 -0800642 float sigma = 0.3f * (float) radius + 0.6f;
Alex Sakhartchouk89a524a2010-08-02 17:52:30 -0700643
644 // Now compute the coefficints
645 // We will store some redundant values to save some math during
646 // the blur calculations
647 // precompute some values
648 float coeff1 = 1.0f / (sqrt( 2.0f * pi ) * sigma);
649 float coeff2 = - 1.0f / (2.0f * sigma * sigma);
650
651 float normalizeFactor = 0.0f;
Romain Guy325a0f92011-01-05 15:26:55 -0800652 for (int32_t r = -radius; r <= radius; r ++) {
Romain Guy7975fb62010-10-01 16:36:14 -0700653 float floatR = (float) r;
Alex Sakhartchouk89a524a2010-08-02 17:52:30 -0700654 weights[r + radius] = coeff1 * pow(e, floatR * floatR * coeff2);
655 normalizeFactor += weights[r + radius];
656 }
657
658 //Now we need to normalize the weights because all our coefficients need to add up to one
659 normalizeFactor = 1.0f / normalizeFactor;
Romain Guy325a0f92011-01-05 15:26:55 -0800660 for (int32_t r = -radius; r <= radius; r ++) {
Alex Sakhartchouk89a524a2010-08-02 17:52:30 -0700661 weights[r + radius] *= normalizeFactor;
662 }
663}
664
665void FontRenderer::horizontalBlur(float* weights, int32_t radius,
Romain Guy1e45aae2010-08-13 19:39:53 -0700666 const uint8_t* source, uint8_t* dest, int32_t width, int32_t height) {
Alex Sakhartchouk89a524a2010-08-02 17:52:30 -0700667 float blurredPixel = 0.0f;
668 float currentPixel = 0.0f;
669
Romain Guy325a0f92011-01-05 15:26:55 -0800670 for (int32_t y = 0; y < height; y ++) {
Alex Sakhartchouk89a524a2010-08-02 17:52:30 -0700671
672 const uint8_t* input = source + y * width;
673 uint8_t* output = dest + y * width;
674
Romain Guy325a0f92011-01-05 15:26:55 -0800675 for (int32_t x = 0; x < width; x ++) {
Alex Sakhartchouk89a524a2010-08-02 17:52:30 -0700676 blurredPixel = 0.0f;
677 const float* gPtr = weights;
678 // Optimization for non-border pixels
Romain Guy325a0f92011-01-05 15:26:55 -0800679 if (x > radius && x < (width - radius)) {
Alex Sakhartchouk89a524a2010-08-02 17:52:30 -0700680 const uint8_t *i = input + (x - radius);
Romain Guy325a0f92011-01-05 15:26:55 -0800681 for (int r = -radius; r <= radius; r ++) {
Romain Guy7975fb62010-10-01 16:36:14 -0700682 currentPixel = (float) (*i);
Alex Sakhartchouk89a524a2010-08-02 17:52:30 -0700683 blurredPixel += currentPixel * gPtr[0];
684 gPtr++;
685 i++;
686 }
687 } else {
Romain Guy325a0f92011-01-05 15:26:55 -0800688 for (int32_t r = -radius; r <= radius; r ++) {
Alex Sakhartchouk89a524a2010-08-02 17:52:30 -0700689 // Stepping left and right away from the pixel
690 int validW = x + r;
Romain Guy325a0f92011-01-05 15:26:55 -0800691 if (validW < 0) {
Alex Sakhartchouk89a524a2010-08-02 17:52:30 -0700692 validW = 0;
693 }
Romain Guy325a0f92011-01-05 15:26:55 -0800694 if (validW > width - 1) {
Alex Sakhartchouk89a524a2010-08-02 17:52:30 -0700695 validW = width - 1;
696 }
697
Romain Guy325a0f92011-01-05 15:26:55 -0800698 currentPixel = (float) input[validW];
Alex Sakhartchouk89a524a2010-08-02 17:52:30 -0700699 blurredPixel += currentPixel * gPtr[0];
700 gPtr++;
701 }
702 }
703 *output = (uint8_t)blurredPixel;
704 output ++;
705 }
706 }
707}
708
709void FontRenderer::verticalBlur(float* weights, int32_t radius,
Romain Guy1e45aae2010-08-13 19:39:53 -0700710 const uint8_t* source, uint8_t* dest, int32_t width, int32_t height) {
Alex Sakhartchouk89a524a2010-08-02 17:52:30 -0700711 float blurredPixel = 0.0f;
712 float currentPixel = 0.0f;
713
Romain Guy325a0f92011-01-05 15:26:55 -0800714 for (int32_t y = 0; y < height; y ++) {
Alex Sakhartchouk89a524a2010-08-02 17:52:30 -0700715 uint8_t* output = dest + y * width;
716
Romain Guy325a0f92011-01-05 15:26:55 -0800717 for (int32_t x = 0; x < width; x ++) {
Alex Sakhartchouk89a524a2010-08-02 17:52:30 -0700718 blurredPixel = 0.0f;
719 const float* gPtr = weights;
720 const uint8_t* input = source + x;
721 // Optimization for non-border pixels
Romain Guy325a0f92011-01-05 15:26:55 -0800722 if (y > radius && y < (height - radius)) {
Alex Sakhartchouk89a524a2010-08-02 17:52:30 -0700723 const uint8_t *i = input + ((y - radius) * width);
Romain Guy325a0f92011-01-05 15:26:55 -0800724 for (int32_t r = -radius; r <= radius; r ++) {
Alex Sakhartchouk89a524a2010-08-02 17:52:30 -0700725 currentPixel = (float)(*i);
726 blurredPixel += currentPixel * gPtr[0];
727 gPtr++;
728 i += width;
729 }
730 } else {
Romain Guy325a0f92011-01-05 15:26:55 -0800731 for (int32_t r = -radius; r <= radius; r ++) {
Alex Sakhartchouk89a524a2010-08-02 17:52:30 -0700732 int validH = y + r;
733 // Clamp to zero and width
Romain Guy325a0f92011-01-05 15:26:55 -0800734 if (validH < 0) {
Alex Sakhartchouk89a524a2010-08-02 17:52:30 -0700735 validH = 0;
736 }
Romain Guy325a0f92011-01-05 15:26:55 -0800737 if (validH > height - 1) {
Alex Sakhartchouk89a524a2010-08-02 17:52:30 -0700738 validH = height - 1;
739 }
740
741 const uint8_t *i = input + validH * width;
Romain Guy325a0f92011-01-05 15:26:55 -0800742 currentPixel = (float) (*i);
Alex Sakhartchouk89a524a2010-08-02 17:52:30 -0700743 blurredPixel += currentPixel * gPtr[0];
744 gPtr++;
745 }
746 }
Romain Guy325a0f92011-01-05 15:26:55 -0800747 *output = (uint8_t) blurredPixel;
Romain Guy9b1204b2012-09-04 15:22:57 -0700748 output++;
Alex Sakhartchouk89a524a2010-08-02 17:52:30 -0700749 }
750 }
751}
752
753
754void FontRenderer::blurImage(uint8_t *image, int32_t width, int32_t height, int32_t radius) {
755 float *gaussian = new float[2 * radius + 1];
756 computeGaussianWeights(gaussian, radius);
Romain Guyd71dd362011-12-12 19:03:35 -0800757
Alex Sakhartchouk89a524a2010-08-02 17:52:30 -0700758 uint8_t* scratch = new uint8_t[width * height];
Romain Guyd71dd362011-12-12 19:03:35 -0800759
Alex Sakhartchouk89a524a2010-08-02 17:52:30 -0700760 horizontalBlur(gaussian, radius, image, scratch, width, height);
761 verticalBlur(gaussian, radius, scratch, image, width, height);
Romain Guyd71dd362011-12-12 19:03:35 -0800762
Alex Sakhartchouk89a524a2010-08-02 17:52:30 -0700763 delete[] gaussian;
764 delete[] scratch;
765}
766
Romain Guy694b5192010-07-21 21:33:20 -0700767}; // namespace uirenderer
768}; // namespace android