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