FontRenderer.cpp revision 527a3aace1dd72432c2e0472a570e030ad04bf16
1/*
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
19#include <SkGlyph.h>
20#include <SkUtils.h>
21
22#include <cutils/properties.h>
23
24#include <utils/Functor.h>
25#include <utils/Log.h>
26
27#include <RenderScript.h>
28
29#include "utils/Blur.h"
30#include "utils/Timing.h"
31
32#include "Caches.h"
33#include "Debug.h"
34#include "Extensions.h"
35#include "FontRenderer.h"
36#include "PixelBuffer.h"
37#include "Rect.h"
38
39namespace android {
40namespace uirenderer {
41
42// blur inputs smaller than this constant will bypass renderscript
43#define RS_MIN_INPUT_CUTOFF 10000
44
45///////////////////////////////////////////////////////////////////////////////
46// FontRenderer
47///////////////////////////////////////////////////////////////////////////////
48
49static bool sLogFontRendererCreate = true;
50
51FontRenderer::FontRenderer() :
52        mActiveFonts(LruCache<Font::FontDescription, Font*>::kUnlimitedCapacity) {
53
54    if (sLogFontRendererCreate) {
55        INIT_LOGD("Creating FontRenderer");
56    }
57
58    mGammaTable = NULL;
59    mInitialized = false;
60
61    mCurrentCacheTexture = NULL;
62
63    mLinearFiltering = false;
64
65    mIndexBufferID = 0;
66
67    mSmallCacheWidth = DEFAULT_TEXT_SMALL_CACHE_WIDTH;
68    mSmallCacheHeight = DEFAULT_TEXT_SMALL_CACHE_HEIGHT;
69    mLargeCacheWidth = DEFAULT_TEXT_LARGE_CACHE_WIDTH;
70    mLargeCacheHeight = DEFAULT_TEXT_LARGE_CACHE_HEIGHT;
71
72    char property[PROPERTY_VALUE_MAX];
73    if (property_get(PROPERTY_TEXT_SMALL_CACHE_WIDTH, property, NULL) > 0) {
74        mSmallCacheWidth = atoi(property);
75    }
76
77    if (property_get(PROPERTY_TEXT_SMALL_CACHE_HEIGHT, property, NULL) > 0) {
78        mSmallCacheHeight = atoi(property);
79    }
80
81    if (property_get(PROPERTY_TEXT_LARGE_CACHE_WIDTH, property, NULL) > 0) {
82        mLargeCacheWidth = atoi(property);
83    }
84
85    if (property_get(PROPERTY_TEXT_LARGE_CACHE_HEIGHT, property, NULL) > 0) {
86        mLargeCacheHeight = atoi(property);
87    }
88
89    uint32_t maxTextureSize = (uint32_t) Caches::getInstance().maxTextureSize;
90    mSmallCacheWidth = mSmallCacheWidth > maxTextureSize ? maxTextureSize : mSmallCacheWidth;
91    mSmallCacheHeight = mSmallCacheHeight > maxTextureSize ? maxTextureSize : mSmallCacheHeight;
92    mLargeCacheWidth = mLargeCacheWidth > maxTextureSize ? maxTextureSize : mLargeCacheWidth;
93    mLargeCacheHeight = mLargeCacheHeight > maxTextureSize ? maxTextureSize : mLargeCacheHeight;
94
95    if (sLogFontRendererCreate) {
96        INIT_LOGD("  Text cache sizes, in pixels: %i x %i, %i x %i, %i x %i, %i x %i",
97                mSmallCacheWidth, mSmallCacheHeight,
98                mLargeCacheWidth, mLargeCacheHeight >> 1,
99                mLargeCacheWidth, mLargeCacheHeight >> 1,
100                mLargeCacheWidth, mLargeCacheHeight);
101    }
102
103    sLogFontRendererCreate = false;
104}
105
106FontRenderer::~FontRenderer() {
107    for (uint32_t i = 0; i < mCacheTextures.size(); i++) {
108        delete mCacheTextures[i];
109    }
110    mCacheTextures.clear();
111
112    if (mInitialized) {
113        // Unbinding the buffer shouldn't be necessary but it crashes with some drivers
114        Caches::getInstance().unbindIndicesBuffer();
115        glDeleteBuffers(1, &mIndexBufferID);
116    }
117
118    LruCache<Font::FontDescription, Font*>::Iterator it(mActiveFonts);
119    while (it.next()) {
120        delete it.value();
121    }
122    mActiveFonts.clear();
123}
124
125void FontRenderer::flushAllAndInvalidate() {
126    issueDrawCommand();
127
128    LruCache<Font::FontDescription, Font*>::Iterator it(mActiveFonts);
129    while (it.next()) {
130        it.value()->invalidateTextureCache();
131    }
132
133    for (uint32_t i = 0; i < mCacheTextures.size(); i++) {
134        mCacheTextures[i]->init();
135    }
136}
137
138void FontRenderer::flushLargeCaches() {
139    // Start from 1; don't deallocate smallest/default texture
140    for (uint32_t i = 1; i < mCacheTextures.size(); i++) {
141        CacheTexture* cacheTexture = mCacheTextures[i];
142        if (cacheTexture->getPixelBuffer()) {
143            cacheTexture->init();
144            LruCache<Font::FontDescription, Font*>::Iterator it(mActiveFonts);
145            while (it.next()) {
146                it.value()->invalidateTextureCache(cacheTexture);
147            }
148            cacheTexture->releaseTexture();
149        }
150    }
151}
152
153CacheTexture* FontRenderer::cacheBitmapInTexture(const SkGlyph& glyph,
154        uint32_t* startX, uint32_t* startY) {
155    for (uint32_t i = 0; i < mCacheTextures.size(); i++) {
156        if (mCacheTextures[i]->fitBitmap(glyph, startX, startY)) {
157            return mCacheTextures[i];
158        }
159    }
160    // Could not fit glyph into current cache textures
161    return NULL;
162}
163
164void FontRenderer::cacheBitmap(const SkGlyph& glyph, CachedGlyphInfo* cachedGlyph,
165        uint32_t* retOriginX, uint32_t* retOriginY, bool precaching) {
166    checkInit();
167
168    // If the glyph bitmap is empty let's assum the glyph is valid
169    // so we can avoid doing extra work later on
170    if (glyph.fWidth == 0 || glyph.fHeight == 0) {
171        cachedGlyph->mIsValid = true;
172        cachedGlyph->mCacheTexture = NULL;
173        return;
174    }
175
176    cachedGlyph->mIsValid = false;
177
178    // If the glyph is too tall, don't cache it
179    if (glyph.fHeight + TEXTURE_BORDER_SIZE * 2 >
180                mCacheTextures[mCacheTextures.size() - 1]->getHeight()) {
181        ALOGE("Font size too large to fit in cache. width, height = %i, %i",
182                (int) glyph.fWidth, (int) glyph.fHeight);
183        return;
184    }
185
186    // Now copy the bitmap into the cache texture
187    uint32_t startX = 0;
188    uint32_t startY = 0;
189
190    CacheTexture* cacheTexture = cacheBitmapInTexture(glyph, &startX, &startY);
191
192    if (!cacheTexture) {
193        if (!precaching) {
194            // If the new glyph didn't fit and we are not just trying to precache it,
195            // clear out the cache and try again
196            flushAllAndInvalidate();
197            cacheTexture = cacheBitmapInTexture(glyph, &startX, &startY);
198        }
199
200        if (!cacheTexture) {
201            // either the glyph didn't fit or we're precaching and will cache it when we draw
202            return;
203        }
204    }
205
206    cachedGlyph->mCacheTexture = cacheTexture;
207
208    *retOriginX = startX;
209    *retOriginY = startY;
210
211    uint32_t endX = startX + glyph.fWidth;
212    uint32_t endY = startY + glyph.fHeight;
213
214    uint32_t cacheWidth = cacheTexture->getWidth();
215
216    if (!cacheTexture->getPixelBuffer()) {
217        Caches::getInstance().activeTexture(0);
218        // Large-glyph texture memory is allocated only as needed
219        cacheTexture->allocateTexture();
220    }
221    if (!cacheTexture->mesh()) {
222        cacheTexture->allocateMesh();
223    }
224
225    // Tells us whether the glyphs is B&W (1 bit per pixel)
226    // or anti-aliased (8 bits per pixel)
227    SkMask::Format format = static_cast<SkMask::Format>(glyph.fMaskFormat);
228
229    uint8_t* cacheBuffer = cacheTexture->getPixelBuffer()->map();
230    uint32_t cacheX = 0, bX = 0, cacheY = 0, bY = 0;
231
232    // Copy the glyph image, taking the mask format into account
233    uint8_t* bitmapBuffer = (uint8_t*) glyph.fImage;
234    int stride = glyph.rowBytes();
235
236    uint32_t row = (startY - TEXTURE_BORDER_SIZE) * cacheWidth + startX - TEXTURE_BORDER_SIZE;
237    memset(&cacheBuffer[row], 0, glyph.fWidth + 2 * TEXTURE_BORDER_SIZE);
238
239    switch (format) {
240        case SkMask::kA8_Format: {
241            if (mGammaTable) {
242                for (cacheY = startY, bY = 0; cacheY < endY; cacheY++, bY += stride) {
243                    row = cacheY * cacheWidth;
244                    cacheBuffer[row + startX - TEXTURE_BORDER_SIZE] = 0;
245                    for (cacheX = startX, bX = 0; cacheX < endX; cacheX++, bX++) {
246                        uint8_t tempCol = bitmapBuffer[bY + bX];
247                        cacheBuffer[row + cacheX] = mGammaTable[tempCol];
248                    }
249                    cacheBuffer[row + endX + TEXTURE_BORDER_SIZE - 1] = 0;
250                }
251            } else {
252                for (cacheY = startY, bY = 0; cacheY < endY; cacheY++, bY += stride) {
253                    row = cacheY * cacheWidth;
254                    memcpy(&cacheBuffer[row + startX], &bitmapBuffer[bY], glyph.fWidth);
255                    cacheBuffer[row + startX - TEXTURE_BORDER_SIZE] = 0;
256                    cacheBuffer[row + endX + TEXTURE_BORDER_SIZE - 1] = 0;
257                }
258            }
259            break;
260        }
261        case SkMask::kBW_Format: {
262            static const uint8_t COLORS[2] = { 0, 255 };
263
264            for (cacheY = startY; cacheY < endY; cacheY++) {
265                cacheX = startX;
266                int rowBytes = stride;
267                uint8_t* buffer = bitmapBuffer;
268
269                row = cacheY * cacheWidth;
270                cacheBuffer[row + startX - TEXTURE_BORDER_SIZE] = 0;
271                while (--rowBytes >= 0) {
272                    uint8_t b = *buffer++;
273                    for (int8_t mask = 7; mask >= 0 && cacheX < endX; mask--) {
274                        cacheBuffer[cacheY * cacheWidth + cacheX++] = COLORS[(b >> mask) & 0x1];
275                    }
276                }
277                cacheBuffer[row + endX + TEXTURE_BORDER_SIZE - 1] = 0;
278
279                bitmapBuffer += stride;
280            }
281            break;
282        }
283        default:
284            ALOGW("Unkown glyph format: 0x%x", format);
285            break;
286    }
287
288    row = (endY + TEXTURE_BORDER_SIZE - 1) * cacheWidth + startX - TEXTURE_BORDER_SIZE;
289    memset(&cacheBuffer[row], 0, glyph.fWidth + 2 * TEXTURE_BORDER_SIZE);
290
291    cachedGlyph->mIsValid = true;
292}
293
294CacheTexture* FontRenderer::createCacheTexture(int width, int height, bool allocate) {
295    CacheTexture* cacheTexture = new CacheTexture(width, height, gMaxNumberOfQuads);
296
297    if (allocate) {
298        Caches::getInstance().activeTexture(0);
299        cacheTexture->allocateTexture();
300        cacheTexture->allocateMesh();
301    }
302
303    return cacheTexture;
304}
305
306void FontRenderer::initTextTexture() {
307    for (uint32_t i = 0; i < mCacheTextures.size(); i++) {
308        delete mCacheTextures[i];
309    }
310    mCacheTextures.clear();
311
312    mUploadTexture = false;
313    mCacheTextures.push(createCacheTexture(mSmallCacheWidth, mSmallCacheHeight, true));
314    mCacheTextures.push(createCacheTexture(mLargeCacheWidth, mLargeCacheHeight >> 1, false));
315    mCacheTextures.push(createCacheTexture(mLargeCacheWidth, mLargeCacheHeight >> 1, false));
316    mCacheTextures.push(createCacheTexture(mLargeCacheWidth, mLargeCacheHeight, false));
317    mCurrentCacheTexture = mCacheTextures[0];
318}
319
320// Avoid having to reallocate memory and render quad by quad
321void FontRenderer::initVertexArrayBuffers() {
322    uint32_t numIndices = gMaxNumberOfQuads * 6;
323    uint32_t indexBufferSizeBytes = numIndices * sizeof(uint16_t);
324    uint16_t* indexBufferData = (uint16_t*) malloc(indexBufferSizeBytes);
325
326    // Four verts, two triangles , six indices per quad
327    for (uint32_t i = 0; i < gMaxNumberOfQuads; i++) {
328        int i6 = i * 6;
329        int i4 = i * 4;
330
331        indexBufferData[i6 + 0] = i4 + 0;
332        indexBufferData[i6 + 1] = i4 + 1;
333        indexBufferData[i6 + 2] = i4 + 2;
334
335        indexBufferData[i6 + 3] = i4 + 0;
336        indexBufferData[i6 + 4] = i4 + 2;
337        indexBufferData[i6 + 5] = i4 + 3;
338    }
339
340    glGenBuffers(1, &mIndexBufferID);
341    Caches::getInstance().bindIndicesBuffer(mIndexBufferID);
342    glBufferData(GL_ELEMENT_ARRAY_BUFFER, indexBufferSizeBytes, indexBufferData, GL_STATIC_DRAW);
343
344    free(indexBufferData);
345}
346
347// We don't want to allocate anything unless we actually draw text
348void FontRenderer::checkInit() {
349    if (mInitialized) {
350        return;
351    }
352
353    initTextTexture();
354    initVertexArrayBuffers();
355
356    mInitialized = true;
357}
358
359void FontRenderer::checkTextureUpdate() {
360    if (!mUploadTexture) {
361        return;
362    }
363
364    Caches& caches = Caches::getInstance();
365    GLuint lastTextureId = 0;
366
367    bool resetPixelStore = false;
368    glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
369
370    // Iterate over all the cache textures and see which ones need to be updated
371    for (uint32_t i = 0; i < mCacheTextures.size(); i++) {
372        CacheTexture* cacheTexture = mCacheTextures[i];
373        if (cacheTexture->isDirty() && cacheTexture->getPixelBuffer()) {
374            if (cacheTexture->getTextureId() != lastTextureId) {
375                lastTextureId = cacheTexture->getTextureId();
376                caches.activeTexture(0);
377                glBindTexture(GL_TEXTURE_2D, lastTextureId);
378            }
379
380            if (cacheTexture->upload()) {
381                resetPixelStore = true;
382            }
383
384#if DEBUG_FONT_RENDERER
385            ALOGD("glTexSubimage for cacheTexture %d: x, y, width height = %d, %d, %d, %d",
386                    i, x, y, width, height);
387#endif
388        }
389    }
390
391    // Unbind any PBO we might have used to update textures
392    caches.unbindPixelBuffer();
393
394    // Reset to default unpack row length to avoid affecting texture
395    // uploads in other parts of the renderer
396    if (resetPixelStore) {
397        glPixelStorei(GL_UNPACK_ROW_LENGTH, 0);
398    }
399
400    mUploadTexture = false;
401}
402
403void FontRenderer::issueDrawCommand() {
404    bool first = true;
405    bool force = false;
406
407    GLuint lastId = 0;
408    Caches& caches = Caches::getInstance();
409
410    for (uint32_t i = 0; i < mCacheTextures.size(); i++) {
411        CacheTexture* texture = mCacheTextures[i];
412        if (texture->canDraw()) {
413            if (first) {
414                if (mFunctor) (*mFunctor)(0, NULL);
415
416                checkTextureUpdate();
417                caches.bindIndicesBuffer(mIndexBufferID);
418
419                if (!mDrawn) {
420                    // If returns true, a VBO was bound and we must
421                    // rebind our vertex attrib pointers even if
422                    // they have the same values as the current pointers
423                    force = caches.unbindMeshBuffer();
424                }
425
426                caches.activeTexture(0);
427                first = false;
428            }
429
430            glBindTexture(GL_TEXTURE_2D, texture->getTextureId());
431            texture->setLinearFiltering(mLinearFiltering, false);
432
433            TextureVertex* mesh = texture->mesh();
434            caches.bindPositionVertexPointer(force, &mesh[0].position[0]);
435            caches.bindTexCoordsVertexPointer(force, &mesh[0].texture[0]);
436            force = false;
437
438            glDrawElements(GL_TRIANGLES, texture->meshElementCount(),
439                    GL_UNSIGNED_SHORT, texture->indices());
440
441            texture->resetMesh();
442        }
443    }
444
445    mDrawn = true;
446}
447
448void FontRenderer::appendMeshQuadNoClip(float x1, float y1, float u1, float v1,
449        float x2, float y2, float u2, float v2, float x3, float y3, float u3, float v3,
450        float x4, float y4, float u4, float v4, CacheTexture* texture) {
451    if (texture != mCurrentCacheTexture) {
452        // Now use the new texture id
453        mCurrentCacheTexture = texture;
454    }
455
456    mCurrentCacheTexture->addQuad(x1, y1, u1, v1, x2, y2, u2, v2,
457            x3, y3, u3, v3, x4, y4, u4, v4);
458}
459
460void FontRenderer::appendMeshQuad(float x1, float y1, float u1, float v1,
461        float x2, float y2, float u2, float v2, float x3, float y3, float u3, float v3,
462        float x4, float y4, float u4, float v4, CacheTexture* texture) {
463
464    if (mClip &&
465            (x1 > mClip->right || y1 < mClip->top || x2 < mClip->left || y4 > mClip->bottom)) {
466        return;
467    }
468
469    appendMeshQuadNoClip(x1, y1, u1, v1, x2, y2, u2, v2, x3, y3, u3, v3, x4, y4, u4, v4, texture);
470
471    if (mBounds) {
472        mBounds->left = fmin(mBounds->left, x1);
473        mBounds->top = fmin(mBounds->top, y3);
474        mBounds->right = fmax(mBounds->right, x3);
475        mBounds->bottom = fmax(mBounds->bottom, y1);
476    }
477
478    if (mCurrentCacheTexture->endOfMesh()) {
479        issueDrawCommand();
480    }
481}
482
483void FontRenderer::appendRotatedMeshQuad(float x1, float y1, float u1, float v1,
484        float x2, float y2, float u2, float v2, float x3, float y3, float u3, float v3,
485        float x4, float y4, float u4, float v4, CacheTexture* texture) {
486
487    appendMeshQuadNoClip(x1, y1, u1, v1, x2, y2, u2, v2, x3, y3, u3, v3, x4, y4, u4, v4, texture);
488
489    if (mBounds) {
490        mBounds->left = fmin(mBounds->left, fmin(x1, fmin(x2, fmin(x3, x4))));
491        mBounds->top = fmin(mBounds->top, fmin(y1, fmin(y2, fmin(y3, y4))));
492        mBounds->right = fmax(mBounds->right, fmax(x1, fmax(x2, fmax(x3, x4))));
493        mBounds->bottom = fmax(mBounds->bottom, fmax(y1, fmax(y2, fmax(y3, y4))));
494    }
495
496    if (mCurrentCacheTexture->endOfMesh()) {
497        issueDrawCommand();
498    }
499}
500
501void FontRenderer::setFont(SkPaint* paint, const mat4& matrix) {
502    mCurrentFont = Font::create(this, paint, matrix);
503}
504
505FontRenderer::DropShadow FontRenderer::renderDropShadow(SkPaint* paint, const char *text,
506        uint32_t startIndex, uint32_t len, int numGlyphs, uint32_t radius, const float* positions) {
507    checkInit();
508
509    DropShadow image;
510    image.width = 0;
511    image.height = 0;
512    image.image = NULL;
513    image.penX = 0;
514    image.penY = 0;
515
516    if (!mCurrentFont) {
517        return image;
518    }
519
520    mDrawn = false;
521    mClip = NULL;
522    mBounds = NULL;
523
524    Rect bounds;
525    mCurrentFont->measure(paint, text, startIndex, len, numGlyphs, &bounds, positions);
526
527    uint32_t paddedWidth = (uint32_t) (bounds.right - bounds.left) + 2 * radius;
528    uint32_t paddedHeight = (uint32_t) (bounds.top - bounds.bottom) + 2 * radius;
529
530    uint32_t maxSize = Caches::getInstance().maxTextureSize;
531    if (paddedWidth > maxSize || paddedHeight > maxSize) {
532        return image;
533    }
534
535    // Align buffers for renderscript usage
536    if (paddedWidth & (RS_CPU_ALLOCATION_ALIGNMENT - 1)) {
537        paddedWidth += RS_CPU_ALLOCATION_ALIGNMENT - paddedWidth % RS_CPU_ALLOCATION_ALIGNMENT;
538    }
539
540    int size = paddedWidth * paddedHeight;
541    uint8_t* dataBuffer = (uint8_t*) memalign(RS_CPU_ALLOCATION_ALIGNMENT, size);
542    memset(dataBuffer, 0, size);
543
544    int penX = radius - bounds.left;
545    int penY = radius - bounds.bottom;
546
547    if ((bounds.right > bounds.left) && (bounds.top > bounds.bottom)) {
548        // text has non-whitespace, so draw and blur to create the shadow
549        // NOTE: bounds.isEmpty() can't be used here, since vertical coordinates are inverted
550        // TODO: don't draw pure whitespace in the first place, and avoid needing this check
551        mCurrentFont->render(paint, text, startIndex, len, numGlyphs, penX, penY,
552                Font::BITMAP, dataBuffer, paddedWidth, paddedHeight, NULL, positions);
553
554        // Unbind any PBO we might have used
555        Caches::getInstance().unbindPixelBuffer();
556
557        blurImage(&dataBuffer, paddedWidth, paddedHeight, radius);
558    }
559
560    image.width = paddedWidth;
561    image.height = paddedHeight;
562    image.image = dataBuffer;
563    image.penX = penX;
564    image.penY = penY;
565
566    return image;
567}
568
569void FontRenderer::initRender(const Rect* clip, Rect* bounds, Functor* functor) {
570    checkInit();
571
572    mDrawn = false;
573    mBounds = bounds;
574    mFunctor = functor;
575    mClip = clip;
576}
577
578void FontRenderer::finishRender() {
579    mBounds = NULL;
580    mClip = NULL;
581
582    issueDrawCommand();
583}
584
585void FontRenderer::precache(SkPaint* paint, const char* text, int numGlyphs, const mat4& matrix) {
586    Font* font = Font::create(this, paint, matrix);
587    font->precache(paint, text, numGlyphs);
588}
589
590void FontRenderer::endPrecaching() {
591    checkTextureUpdate();
592}
593
594bool FontRenderer::renderPosText(SkPaint* paint, const Rect* clip, const char *text,
595        uint32_t startIndex, uint32_t len, int numGlyphs, int x, int y,
596        const float* positions, Rect* bounds, Functor* functor, bool forceFinish) {
597    if (!mCurrentFont) {
598        ALOGE("No font set");
599        return false;
600    }
601
602    initRender(clip, bounds, functor);
603    mCurrentFont->render(paint, text, startIndex, len, numGlyphs, x, y, positions);
604
605    if (forceFinish) {
606        finishRender();
607    }
608
609    return mDrawn;
610}
611
612bool FontRenderer::renderTextOnPath(SkPaint* paint, const Rect* clip, const char *text,
613        uint32_t startIndex, uint32_t len, int numGlyphs, SkPath* path,
614        float hOffset, float vOffset, Rect* bounds) {
615    if (!mCurrentFont) {
616        ALOGE("No font set");
617        return false;
618    }
619
620    initRender(clip, bounds, NULL);
621    mCurrentFont->render(paint, text, startIndex, len, numGlyphs, path, hOffset, vOffset);
622    finishRender();
623
624    return mDrawn;
625}
626
627void FontRenderer::removeFont(const Font* font) {
628    mActiveFonts.remove(font->getDescription());
629
630    if (mCurrentFont == font) {
631        mCurrentFont = NULL;
632    }
633}
634
635void FontRenderer::blurImage(uint8_t** image, int32_t width, int32_t height, int32_t radius) {
636    if (width * height * radius < RS_MIN_INPUT_CUTOFF) {
637        float *gaussian = new float[2 * radius + 1];
638        Blur::generateGaussianWeights(gaussian, radius);
639
640        uint8_t* scratch = new uint8_t[width * height];
641        Blur::horizontal(gaussian, radius, *image, scratch, width, height);
642        Blur::vertical(gaussian, radius, scratch, *image, width, height);
643
644        delete[] gaussian;
645        delete[] scratch;
646        return;
647    }
648
649    uint8_t* outImage = (uint8_t*) memalign(RS_CPU_ALLOCATION_ALIGNMENT, width * height);
650
651    if (mRs.get() == 0) {
652        mRs = new RSC::RS();
653        if (!mRs->init(true, true)) {
654            ALOGE("blur RS failed to init");
655        }
656
657        mRsElement = RSC::Element::A_8(mRs);
658        mRsScript = new RSC::ScriptIntrinsicBlur(mRs, mRsElement);
659    }
660
661    sp<const RSC::Type> t = RSC::Type::create(mRs, mRsElement, width, height, 0);
662    sp<RSC::Allocation> ain = RSC::Allocation::createTyped(mRs, t, RS_ALLOCATION_MIPMAP_NONE,
663            RS_ALLOCATION_USAGE_SCRIPT | RS_ALLOCATION_USAGE_SHARED, *image);
664    sp<RSC::Allocation> aout = RSC::Allocation::createTyped(mRs, t, RS_ALLOCATION_MIPMAP_NONE,
665            RS_ALLOCATION_USAGE_SCRIPT | RS_ALLOCATION_USAGE_SHARED, outImage);
666
667    mRsScript->setRadius(radius);
668    mRsScript->blur(ain, aout);
669
670    // replace the original image's pointer, avoiding a copy back to the original buffer
671    free(*image);
672    *image = outImage;
673}
674
675uint32_t FontRenderer::getCacheSize() const {
676    uint32_t size = 0;
677    for (uint32_t i = 0; i < mCacheTextures.size(); i++) {
678        CacheTexture* cacheTexture = mCacheTextures[i];
679        if (cacheTexture && cacheTexture->getPixelBuffer()) {
680            size += cacheTexture->getPixelBuffer()->getSize();
681        }
682    }
683    return size;
684}
685
686}; // namespace uirenderer
687}; // namespace android
688