blob: 4e97c8862d7f4e85ce1d600cb13a7c39931b1e28 [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();
Chris Craikcb4d6002012-09-25 12:00:29 -0700377 caches.bindPositionVertexPointer(force, buffer);
378 caches.bindTexCoordsVertexPointer(force, buffer + offset);
Romain Guy15bc6432011-12-13 13:11:32 -0800379 }
380
Romain Guy694b5192010-07-21 21:33:20 -0700381 glDrawElements(GL_TRIANGLES, mCurrentQuadIndex * 6, GL_UNSIGNED_SHORT, NULL);
Romain Guy5b3b3522010-10-27 18:57:51 -0700382
383 mDrawn = true;
Romain Guy694b5192010-07-21 21:33:20 -0700384}
385
Romain Guy97771732012-02-28 18:17:02 -0800386void FontRenderer::appendMeshQuadNoClip(float x1, float y1, float u1, float v1,
387 float x2, float y2, float u2, float v2, float x3, float y3, float u3, float v3,
Chet Haase7de0cb12011-12-05 16:35:38 -0800388 float x4, float y4, float u4, float v4, CacheTexture* texture) {
Chet Haase7de0cb12011-12-05 16:35:38 -0800389 if (texture != mCurrentCacheTexture) {
390 if (mCurrentQuadIndex != 0) {
391 // First, draw everything stored already which uses the previous texture
392 issueDrawCommand();
393 mCurrentQuadIndex = 0;
394 }
395 // Now use the new texture id
396 mCurrentCacheTexture = texture;
397 }
Romain Guy09147fb2010-07-22 13:08:20 -0700398
Romain Guy694b5192010-07-21 21:33:20 -0700399 const uint32_t vertsPerQuad = 4;
Romain Guyd71dd362011-12-12 19:03:35 -0800400 const uint32_t floatsPerVert = 4;
Romain Guy9b1204b2012-09-04 15:22:57 -0700401 float* currentPos = mTextMesh + mCurrentQuadIndex * vertsPerQuad * floatsPerVert;
Romain Guy694b5192010-07-21 21:33:20 -0700402
Romain Guy694b5192010-07-21 21:33:20 -0700403 (*currentPos++) = x1;
404 (*currentPos++) = y1;
Romain Guy694b5192010-07-21 21:33:20 -0700405 (*currentPos++) = u1;
406 (*currentPos++) = v1;
407
408 (*currentPos++) = x2;
409 (*currentPos++) = y2;
Romain Guy694b5192010-07-21 21:33:20 -0700410 (*currentPos++) = u2;
411 (*currentPos++) = v2;
412
413 (*currentPos++) = x3;
414 (*currentPos++) = y3;
Romain Guy694b5192010-07-21 21:33:20 -0700415 (*currentPos++) = u3;
416 (*currentPos++) = v3;
417
418 (*currentPos++) = x4;
419 (*currentPos++) = y4;
Romain Guy694b5192010-07-21 21:33:20 -0700420 (*currentPos++) = u4;
421 (*currentPos++) = v4;
422
423 mCurrentQuadIndex++;
Romain Guy97771732012-02-28 18:17:02 -0800424}
425
426void FontRenderer::appendMeshQuad(float x1, float y1, float u1, float v1,
427 float x2, float y2, float u2, float v2, float x3, float y3, float u3, float v3,
428 float x4, float y4, float u4, float v4, CacheTexture* texture) {
429
430 if (mClip &&
431 (x1 > mClip->right || y1 < mClip->top || x2 < mClip->left || y4 > mClip->bottom)) {
432 return;
433 }
434
435 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 -0700436
Romain Guy5b3b3522010-10-27 18:57:51 -0700437 if (mBounds) {
438 mBounds->left = fmin(mBounds->left, x1);
439 mBounds->top = fmin(mBounds->top, y3);
440 mBounds->right = fmax(mBounds->right, x3);
441 mBounds->bottom = fmax(mBounds->bottom, y1);
442 }
443
Romain Guy694b5192010-07-21 21:33:20 -0700444 if (mCurrentQuadIndex == mMaxNumberOfQuads) {
445 issueDrawCommand();
446 mCurrentQuadIndex = 0;
447 }
448}
449
Romain Guy97771732012-02-28 18:17:02 -0800450void FontRenderer::appendRotatedMeshQuad(float x1, float y1, float u1, float v1,
451 float x2, float y2, float u2, float v2, float x3, float y3, float u3, float v3,
452 float x4, float y4, float u4, float v4, CacheTexture* texture) {
453
454 appendMeshQuadNoClip(x1, y1, u1, v1, x2, y2, u2, v2, x3, y3, u3, v3, x4, y4, u4, v4, texture);
455
456 if (mBounds) {
457 mBounds->left = fmin(mBounds->left, fmin(x1, fmin(x2, fmin(x3, x4))));
458 mBounds->top = fmin(mBounds->top, fmin(y1, fmin(y2, fmin(y3, y4))));
459 mBounds->right = fmax(mBounds->right, fmax(x1, fmax(x2, fmax(x3, x4))));
460 mBounds->bottom = fmax(mBounds->bottom, fmax(y1, fmax(y2, fmax(y3, y4))));
461 }
462
463 if (mCurrentQuadIndex == mMaxNumberOfQuads) {
464 issueDrawCommand();
465 mCurrentQuadIndex = 0;
466 }
467}
468
Alex Sakhartchouk65ef9092010-07-26 11:09:33 -0700469void FontRenderer::setFont(SkPaint* paint, uint32_t fontId, float fontSize) {
Romain Guy325a0f92011-01-05 15:26:55 -0800470 int flags = 0;
471 if (paint->isFakeBoldText()) {
472 flags |= Font::kFakeBold;
473 }
Romain Guy2577db12011-01-18 13:02:38 -0800474
475 const float skewX = paint->getTextSkewX();
476 uint32_t italicStyle = *(uint32_t*) &skewX;
Chet Haase8668f8a2011-03-02 13:51:36 -0800477 const float scaleXFloat = paint->getTextScaleX();
478 uint32_t scaleX = *(uint32_t*) &scaleXFloat;
Romain Guybd496bc2011-08-02 17:32:41 -0700479 SkPaint::Style style = paint->getStyle();
480 const float strokeWidthFloat = paint->getStrokeWidth();
481 uint32_t strokeWidth = *(uint32_t*) &strokeWidthFloat;
482 mCurrentFont = Font::create(this, fontId, fontSize, flags, italicStyle,
483 scaleX, style, strokeWidth);
Alex Sakhartchouk65ef9092010-07-26 11:09:33 -0700484
Romain Guy694b5192010-07-21 21:33:20 -0700485}
Romain Guy7975fb62010-10-01 16:36:14 -0700486
Alex Sakhartchoukf18136c2010-08-06 14:49:04 -0700487FontRenderer::DropShadow FontRenderer::renderDropShadow(SkPaint* paint, const char *text,
Raph Levien416a8472012-07-19 22:48:17 -0700488 uint32_t startIndex, uint32_t len, int numGlyphs, uint32_t radius, const float* positions) {
Romain Guy1e45aae2010-08-13 19:39:53 -0700489 checkInit();
490
491 if (!mCurrentFont) {
492 DropShadow image;
493 image.width = 0;
494 image.height = 0;
495 image.image = NULL;
496 image.penX = 0;
497 image.penY = 0;
498 return image;
499 }
Alex Sakhartchoukf18136c2010-08-06 14:49:04 -0700500
Romain Guy2d4fd362011-12-13 22:00:19 -0800501 mDrawn = false;
Romain Guyff98fa52011-11-28 09:35:09 -0800502 mClip = NULL;
503 mBounds = NULL;
504
Alex Sakhartchoukf18136c2010-08-06 14:49:04 -0700505 Rect bounds;
Raph Levien416a8472012-07-19 22:48:17 -0700506 mCurrentFont->measure(paint, text, startIndex, len, numGlyphs, &bounds, positions);
Romain Guyff98fa52011-11-28 09:35:09 -0800507
Romain Guy1e45aae2010-08-13 19:39:53 -0700508 uint32_t paddedWidth = (uint32_t) (bounds.right - bounds.left) + 2 * radius;
509 uint32_t paddedHeight = (uint32_t) (bounds.top - bounds.bottom) + 2 * radius;
Alex Sakhartchoukf18136c2010-08-06 14:49:04 -0700510 uint8_t* dataBuffer = new uint8_t[paddedWidth * paddedHeight];
Romain Guyff98fa52011-11-28 09:35:09 -0800511
Romain Guy1e45aae2010-08-13 19:39:53 -0700512 for (uint32_t i = 0; i < paddedWidth * paddedHeight; i++) {
Alex Sakhartchoukf18136c2010-08-06 14:49:04 -0700513 dataBuffer[i] = 0;
514 }
Romain Guy1e45aae2010-08-13 19:39:53 -0700515
Alex Sakhartchoukf18136c2010-08-06 14:49:04 -0700516 int penX = radius - bounds.left;
517 int penY = radius - bounds.bottom;
518
Romain Guy726aeba2011-06-01 14:52:00 -0700519 mCurrentFont->render(paint, text, startIndex, len, numGlyphs, penX, penY,
Raph Levien416a8472012-07-19 22:48:17 -0700520 Font::BITMAP, dataBuffer, paddedWidth, paddedHeight, NULL, positions);
Alex Sakhartchoukf18136c2010-08-06 14:49:04 -0700521 blurImage(dataBuffer, paddedWidth, paddedHeight, radius);
522
523 DropShadow image;
524 image.width = paddedWidth;
525 image.height = paddedHeight;
526 image.image = dataBuffer;
527 image.penX = penX;
528 image.penY = penY;
Romain Guy2d4fd362011-12-13 22:00:19 -0800529
Alex Sakhartchoukf18136c2010-08-06 14:49:04 -0700530 return image;
531}
Romain Guy694b5192010-07-21 21:33:20 -0700532
Romain Guy671d6cf2012-01-18 12:39:17 -0800533void FontRenderer::initRender(const Rect* clip, Rect* bounds) {
Romain Guy694b5192010-07-21 21:33:20 -0700534 checkInit();
535
Romain Guy5b3b3522010-10-27 18:57:51 -0700536 mDrawn = false;
537 mBounds = bounds;
Romain Guy09147fb2010-07-22 13:08:20 -0700538 mClip = clip;
Romain Guy671d6cf2012-01-18 12:39:17 -0800539}
Romain Guyff98fa52011-11-28 09:35:09 -0800540
Romain Guy671d6cf2012-01-18 12:39:17 -0800541void FontRenderer::finishRender() {
Romain Guy5b3b3522010-10-27 18:57:51 -0700542 mBounds = NULL;
Romain Guyff98fa52011-11-28 09:35:09 -0800543 mClip = NULL;
Romain Guy694b5192010-07-21 21:33:20 -0700544
545 if (mCurrentQuadIndex != 0) {
546 issueDrawCommand();
547 mCurrentQuadIndex = 0;
548 }
Romain Guy671d6cf2012-01-18 12:39:17 -0800549}
550
Chet Haasee816bae2012-08-09 13:39:02 -0700551void FontRenderer::precache(SkPaint* paint, const char* text, int numGlyphs) {
552 int flags = 0;
553 if (paint->isFakeBoldText()) {
554 flags |= Font::kFakeBold;
555 }
556 const float skewX = paint->getTextSkewX();
557 uint32_t italicStyle = *(uint32_t*) &skewX;
558 const float scaleXFloat = paint->getTextScaleX();
559 uint32_t scaleX = *(uint32_t*) &scaleXFloat;
560 SkPaint::Style style = paint->getStyle();
561 const float strokeWidthFloat = paint->getStrokeWidth();
562 uint32_t strokeWidth = *(uint32_t*) &strokeWidthFloat;
563 float fontSize = paint->getTextSize();
564 Font* font = Font::create(this, SkTypeface::UniqueID(paint->getTypeface()),
565 fontSize, flags, italicStyle, scaleX, style, strokeWidth);
566
567 font->precache(paint, text, numGlyphs);
568}
569
Romain Guy671d6cf2012-01-18 12:39:17 -0800570bool FontRenderer::renderText(SkPaint* paint, const Rect* clip, const char *text,
571 uint32_t startIndex, uint32_t len, int numGlyphs, int x, int y, Rect* bounds) {
572 if (!mCurrentFont) {
573 ALOGE("No font set");
574 return false;
575 }
576
577 initRender(clip, bounds);
578 mCurrentFont->render(paint, text, startIndex, len, numGlyphs, x, y);
579 finishRender();
580
581 return mDrawn;
582}
583
584bool FontRenderer::renderPosText(SkPaint* paint, const Rect* clip, const char *text,
585 uint32_t startIndex, uint32_t len, int numGlyphs, int x, int y,
586 const float* positions, Rect* bounds) {
587 if (!mCurrentFont) {
588 ALOGE("No font set");
589 return false;
590 }
591
592 initRender(clip, bounds);
593 mCurrentFont->render(paint, text, startIndex, len, numGlyphs, x, y, positions);
594 finishRender();
Romain Guy5b3b3522010-10-27 18:57:51 -0700595
596 return mDrawn;
Romain Guy694b5192010-07-21 21:33:20 -0700597}
598
Romain Guy97771732012-02-28 18:17:02 -0800599bool FontRenderer::renderTextOnPath(SkPaint* paint, const Rect* clip, const char *text,
600 uint32_t startIndex, uint32_t len, int numGlyphs, SkPath* path,
601 float hOffset, float vOffset, Rect* bounds) {
602 if (!mCurrentFont) {
603 ALOGE("No font set");
604 return false;
605 }
606
607 initRender(clip, bounds);
608 mCurrentFont->render(paint, text, startIndex, len, numGlyphs, path, hOffset, vOffset);
609 finishRender();
610
611 return mDrawn;
612}
613
Romain Guy9b1204b2012-09-04 15:22:57 -0700614void FontRenderer::removeFont(const Font* font) {
615 for (uint32_t ct = 0; ct < mActiveFonts.size(); ct++) {
616 if (mActiveFonts[ct] == font) {
617 mActiveFonts.removeAt(ct);
618 break;
619 }
620 }
621
622 if (mCurrentFont == font) {
623 mCurrentFont = NULL;
624 }
625}
626
Alex Sakhartchouk89a524a2010-08-02 17:52:30 -0700627void FontRenderer::computeGaussianWeights(float* weights, int32_t radius) {
628 // Compute gaussian weights for the blur
629 // e is the euler's number
630 float e = 2.718281828459045f;
631 float pi = 3.1415926535897932f;
632 // g(x) = ( 1 / sqrt( 2 * pi ) * sigma) * e ^ ( -x^2 / 2 * sigma^2 )
633 // x is of the form [-radius .. 0 .. radius]
634 // and sigma varies with radius.
635 // Based on some experimental radius values and sigma's
636 // we approximately fit sigma = f(radius) as
Alex Sakhartchoukf18136c2010-08-06 14:49:04 -0700637 // sigma = radius * 0.3 + 0.6
Alex Sakhartchouk89a524a2010-08-02 17:52:30 -0700638 // The larger the radius gets, the more our gaussian blur
639 // will resemble a box blur since with large sigma
640 // the gaussian curve begins to lose its shape
Romain Guy325a0f92011-01-05 15:26:55 -0800641 float sigma = 0.3f * (float) radius + 0.6f;
Alex Sakhartchouk89a524a2010-08-02 17:52:30 -0700642
643 // Now compute the coefficints
644 // We will store some redundant values to save some math during
645 // the blur calculations
646 // precompute some values
647 float coeff1 = 1.0f / (sqrt( 2.0f * pi ) * sigma);
648 float coeff2 = - 1.0f / (2.0f * sigma * sigma);
649
650 float normalizeFactor = 0.0f;
Romain Guy325a0f92011-01-05 15:26:55 -0800651 for (int32_t r = -radius; r <= radius; r ++) {
Romain Guy7975fb62010-10-01 16:36:14 -0700652 float floatR = (float) r;
Alex Sakhartchouk89a524a2010-08-02 17:52:30 -0700653 weights[r + radius] = coeff1 * pow(e, floatR * floatR * coeff2);
654 normalizeFactor += weights[r + radius];
655 }
656
657 //Now we need to normalize the weights because all our coefficients need to add up to one
658 normalizeFactor = 1.0f / normalizeFactor;
Romain Guy325a0f92011-01-05 15:26:55 -0800659 for (int32_t r = -radius; r <= radius; r ++) {
Alex Sakhartchouk89a524a2010-08-02 17:52:30 -0700660 weights[r + radius] *= normalizeFactor;
661 }
662}
663
664void FontRenderer::horizontalBlur(float* weights, int32_t radius,
Romain Guy1e45aae2010-08-13 19:39:53 -0700665 const uint8_t* source, uint8_t* dest, int32_t width, int32_t height) {
Alex Sakhartchouk89a524a2010-08-02 17:52:30 -0700666 float blurredPixel = 0.0f;
667 float currentPixel = 0.0f;
668
Romain Guy325a0f92011-01-05 15:26:55 -0800669 for (int32_t y = 0; y < height; y ++) {
Alex Sakhartchouk89a524a2010-08-02 17:52:30 -0700670
671 const uint8_t* input = source + y * width;
672 uint8_t* output = dest + y * width;
673
Romain Guy325a0f92011-01-05 15:26:55 -0800674 for (int32_t x = 0; x < width; x ++) {
Alex Sakhartchouk89a524a2010-08-02 17:52:30 -0700675 blurredPixel = 0.0f;
676 const float* gPtr = weights;
677 // Optimization for non-border pixels
Romain Guy325a0f92011-01-05 15:26:55 -0800678 if (x > radius && x < (width - radius)) {
Alex Sakhartchouk89a524a2010-08-02 17:52:30 -0700679 const uint8_t *i = input + (x - radius);
Romain Guy325a0f92011-01-05 15:26:55 -0800680 for (int r = -radius; r <= radius; r ++) {
Romain Guy7975fb62010-10-01 16:36:14 -0700681 currentPixel = (float) (*i);
Alex Sakhartchouk89a524a2010-08-02 17:52:30 -0700682 blurredPixel += currentPixel * gPtr[0];
683 gPtr++;
684 i++;
685 }
686 } else {
Romain Guy325a0f92011-01-05 15:26:55 -0800687 for (int32_t r = -radius; r <= radius; r ++) {
Alex Sakhartchouk89a524a2010-08-02 17:52:30 -0700688 // Stepping left and right away from the pixel
689 int validW = x + r;
Romain Guy325a0f92011-01-05 15:26:55 -0800690 if (validW < 0) {
Alex Sakhartchouk89a524a2010-08-02 17:52:30 -0700691 validW = 0;
692 }
Romain Guy325a0f92011-01-05 15:26:55 -0800693 if (validW > width - 1) {
Alex Sakhartchouk89a524a2010-08-02 17:52:30 -0700694 validW = width - 1;
695 }
696
Romain Guy325a0f92011-01-05 15:26:55 -0800697 currentPixel = (float) input[validW];
Alex Sakhartchouk89a524a2010-08-02 17:52:30 -0700698 blurredPixel += currentPixel * gPtr[0];
699 gPtr++;
700 }
701 }
702 *output = (uint8_t)blurredPixel;
703 output ++;
704 }
705 }
706}
707
708void FontRenderer::verticalBlur(float* weights, int32_t radius,
Romain Guy1e45aae2010-08-13 19:39:53 -0700709 const uint8_t* source, uint8_t* dest, int32_t width, int32_t height) {
Alex Sakhartchouk89a524a2010-08-02 17:52:30 -0700710 float blurredPixel = 0.0f;
711 float currentPixel = 0.0f;
712
Romain Guy325a0f92011-01-05 15:26:55 -0800713 for (int32_t y = 0; y < height; y ++) {
Alex Sakhartchouk89a524a2010-08-02 17:52:30 -0700714 uint8_t* output = dest + y * width;
715
Romain Guy325a0f92011-01-05 15:26:55 -0800716 for (int32_t x = 0; x < width; x ++) {
Alex Sakhartchouk89a524a2010-08-02 17:52:30 -0700717 blurredPixel = 0.0f;
718 const float* gPtr = weights;
719 const uint8_t* input = source + x;
720 // Optimization for non-border pixels
Romain Guy325a0f92011-01-05 15:26:55 -0800721 if (y > radius && y < (height - radius)) {
Alex Sakhartchouk89a524a2010-08-02 17:52:30 -0700722 const uint8_t *i = input + ((y - radius) * width);
Romain Guy325a0f92011-01-05 15:26:55 -0800723 for (int32_t r = -radius; r <= radius; r ++) {
Alex Sakhartchouk89a524a2010-08-02 17:52:30 -0700724 currentPixel = (float)(*i);
725 blurredPixel += currentPixel * gPtr[0];
726 gPtr++;
727 i += width;
728 }
729 } else {
Romain Guy325a0f92011-01-05 15:26:55 -0800730 for (int32_t r = -radius; r <= radius; r ++) {
Alex Sakhartchouk89a524a2010-08-02 17:52:30 -0700731 int validH = y + r;
732 // Clamp to zero and width
Romain Guy325a0f92011-01-05 15:26:55 -0800733 if (validH < 0) {
Alex Sakhartchouk89a524a2010-08-02 17:52:30 -0700734 validH = 0;
735 }
Romain Guy325a0f92011-01-05 15:26:55 -0800736 if (validH > height - 1) {
Alex Sakhartchouk89a524a2010-08-02 17:52:30 -0700737 validH = height - 1;
738 }
739
740 const uint8_t *i = input + validH * width;
Romain Guy325a0f92011-01-05 15:26:55 -0800741 currentPixel = (float) (*i);
Alex Sakhartchouk89a524a2010-08-02 17:52:30 -0700742 blurredPixel += currentPixel * gPtr[0];
743 gPtr++;
744 }
745 }
Romain Guy325a0f92011-01-05 15:26:55 -0800746 *output = (uint8_t) blurredPixel;
Romain Guy9b1204b2012-09-04 15:22:57 -0700747 output++;
Alex Sakhartchouk89a524a2010-08-02 17:52:30 -0700748 }
749 }
750}
751
752
753void FontRenderer::blurImage(uint8_t *image, int32_t width, int32_t height, int32_t radius) {
754 float *gaussian = new float[2 * radius + 1];
755 computeGaussianWeights(gaussian, radius);
Romain Guyd71dd362011-12-12 19:03:35 -0800756
Alex Sakhartchouk89a524a2010-08-02 17:52:30 -0700757 uint8_t* scratch = new uint8_t[width * height];
Romain Guyd71dd362011-12-12 19:03:35 -0800758
Alex Sakhartchouk89a524a2010-08-02 17:52:30 -0700759 horizontalBlur(gaussian, radius, image, scratch, width, height);
760 verticalBlur(gaussian, radius, scratch, image, width, height);
Romain Guyd71dd362011-12-12 19:03:35 -0800761
Alex Sakhartchouk89a524a2010-08-02 17:52:30 -0700762 delete[] gaussian;
763 delete[] scratch;
764}
765
Romain Guy694b5192010-07-21 21:33:20 -0700766}; // namespace uirenderer
767}; // namespace android