| /* |
| * Copyright (C) 2010 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| #define LOG_TAG "OpenGLRenderer" |
| |
| #include <SkGlyph.h> |
| #include <SkUtils.h> |
| |
| #include <cutils/properties.h> |
| |
| #include <utils/Log.h> |
| |
| #ifdef ANDROID_ENABLE_RENDERSCRIPT |
| #include <RenderScript.h> |
| #endif |
| |
| #include "utils/Blur.h" |
| #include "utils/Timing.h" |
| |
| #include "Caches.h" |
| #include "Debug.h" |
| #include "Extensions.h" |
| #include "FontRenderer.h" |
| #include "OpenGLRenderer.h" |
| #include "PixelBuffer.h" |
| #include "Rect.h" |
| |
| namespace android { |
| namespace uirenderer { |
| |
| // blur inputs smaller than this constant will bypass renderscript |
| #define RS_MIN_INPUT_CUTOFF 10000 |
| |
| /////////////////////////////////////////////////////////////////////////////// |
| // TextSetupFunctor |
| /////////////////////////////////////////////////////////////////////////////// |
| status_t TextSetupFunctor::operator ()(int what, void* data) { |
| Data* typedData = reinterpret_cast<Data*>(data); |
| GLenum glyphFormat = typedData ? typedData->glyphFormat : GL_ALPHA; |
| |
| renderer->setupDraw(); |
| renderer->setupDrawTextGamma(paint); |
| renderer->setupDrawDirtyRegionsDisabled(); |
| renderer->setupDrawWithTexture(glyphFormat == GL_ALPHA); |
| switch (glyphFormat) { |
| case GL_ALPHA: { |
| renderer->setupDrawAlpha8Color(paint->getColor(), alpha); |
| break; |
| } |
| case GL_RGBA: { |
| float floatAlpha = alpha / 255.0f; |
| renderer->setupDrawColor(floatAlpha, floatAlpha, floatAlpha, floatAlpha); |
| break; |
| } |
| default: { |
| #if DEBUG_FONT_RENDERER |
| ALOGD("TextSetupFunctor: called with unknown glyph format %x", glyphFormat); |
| #endif |
| break; |
| } |
| } |
| renderer->setupDrawColorFilter(); |
| renderer->setupDrawShader(); |
| renderer->setupDrawBlending(true, mode); |
| renderer->setupDrawProgram(); |
| renderer->setupDrawModelView(x, y, x, y, pureTranslate, true); |
| // Calling setupDrawTexture with the name 0 will enable the |
| // uv attributes and increase the texture unit count |
| // texture binding will be performed by the font renderer as |
| // needed |
| renderer->setupDrawTexture(0); |
| renderer->setupDrawPureColorUniforms(); |
| renderer->setupDrawColorFilterUniforms(); |
| renderer->setupDrawShaderUniforms(pureTranslate); |
| renderer->setupDrawTextGammaUniforms(); |
| |
| return NO_ERROR; |
| } |
| |
| /////////////////////////////////////////////////////////////////////////////// |
| // FontRenderer |
| /////////////////////////////////////////////////////////////////////////////// |
| |
| static bool sLogFontRendererCreate = true; |
| |
| FontRenderer::FontRenderer() : |
| mActiveFonts(LruCache<Font::FontDescription, Font*>::kUnlimitedCapacity) { |
| |
| if (sLogFontRendererCreate) { |
| INIT_LOGD("Creating FontRenderer"); |
| } |
| |
| mGammaTable = NULL; |
| mInitialized = false; |
| |
| mCurrentCacheTexture = NULL; |
| |
| mLinearFiltering = false; |
| |
| mSmallCacheWidth = DEFAULT_TEXT_SMALL_CACHE_WIDTH; |
| mSmallCacheHeight = DEFAULT_TEXT_SMALL_CACHE_HEIGHT; |
| mLargeCacheWidth = DEFAULT_TEXT_LARGE_CACHE_WIDTH; |
| mLargeCacheHeight = DEFAULT_TEXT_LARGE_CACHE_HEIGHT; |
| |
| char property[PROPERTY_VALUE_MAX]; |
| if (property_get(PROPERTY_TEXT_SMALL_CACHE_WIDTH, property, NULL) > 0) { |
| mSmallCacheWidth = atoi(property); |
| } |
| |
| if (property_get(PROPERTY_TEXT_SMALL_CACHE_HEIGHT, property, NULL) > 0) { |
| mSmallCacheHeight = atoi(property); |
| } |
| |
| if (property_get(PROPERTY_TEXT_LARGE_CACHE_WIDTH, property, NULL) > 0) { |
| mLargeCacheWidth = atoi(property); |
| } |
| |
| if (property_get(PROPERTY_TEXT_LARGE_CACHE_HEIGHT, property, NULL) > 0) { |
| mLargeCacheHeight = atoi(property); |
| } |
| |
| uint32_t maxTextureSize = (uint32_t) Caches::getInstance().maxTextureSize; |
| mSmallCacheWidth = mSmallCacheWidth > maxTextureSize ? maxTextureSize : mSmallCacheWidth; |
| mSmallCacheHeight = mSmallCacheHeight > maxTextureSize ? maxTextureSize : mSmallCacheHeight; |
| mLargeCacheWidth = mLargeCacheWidth > maxTextureSize ? maxTextureSize : mLargeCacheWidth; |
| mLargeCacheHeight = mLargeCacheHeight > maxTextureSize ? maxTextureSize : mLargeCacheHeight; |
| |
| if (sLogFontRendererCreate) { |
| INIT_LOGD(" Text cache sizes, in pixels: %i x %i, %i x %i, %i x %i, %i x %i", |
| mSmallCacheWidth, mSmallCacheHeight, |
| mLargeCacheWidth, mLargeCacheHeight >> 1, |
| mLargeCacheWidth, mLargeCacheHeight >> 1, |
| mLargeCacheWidth, mLargeCacheHeight); |
| } |
| |
| sLogFontRendererCreate = false; |
| } |
| |
| void clearCacheTextures(Vector<CacheTexture*>& cacheTextures) { |
| for (uint32_t i = 0; i < cacheTextures.size(); i++) { |
| delete cacheTextures[i]; |
| } |
| cacheTextures.clear(); |
| } |
| |
| FontRenderer::~FontRenderer() { |
| clearCacheTextures(mACacheTextures); |
| clearCacheTextures(mRGBACacheTextures); |
| |
| LruCache<Font::FontDescription, Font*>::Iterator it(mActiveFonts); |
| while (it.next()) { |
| delete it.value(); |
| } |
| mActiveFonts.clear(); |
| } |
| |
| void FontRenderer::flushAllAndInvalidate() { |
| issueDrawCommand(); |
| |
| LruCache<Font::FontDescription, Font*>::Iterator it(mActiveFonts); |
| while (it.next()) { |
| it.value()->invalidateTextureCache(); |
| } |
| |
| for (uint32_t i = 0; i < mACacheTextures.size(); i++) { |
| mACacheTextures[i]->init(); |
| } |
| |
| for (uint32_t i = 0; i < mRGBACacheTextures.size(); i++) { |
| mRGBACacheTextures[i]->init(); |
| } |
| } |
| |
| void FontRenderer::flushLargeCaches(Vector<CacheTexture*>& cacheTextures) { |
| // Start from 1; don't deallocate smallest/default texture |
| for (uint32_t i = 1; i < cacheTextures.size(); i++) { |
| CacheTexture* cacheTexture = cacheTextures[i]; |
| if (cacheTexture->getPixelBuffer()) { |
| cacheTexture->init(); |
| LruCache<Font::FontDescription, Font*>::Iterator it(mActiveFonts); |
| while (it.next()) { |
| it.value()->invalidateTextureCache(cacheTexture); |
| } |
| cacheTexture->releaseTexture(); |
| } |
| } |
| } |
| |
| void FontRenderer::flushLargeCaches() { |
| flushLargeCaches(mACacheTextures); |
| flushLargeCaches(mRGBACacheTextures); |
| } |
| |
| CacheTexture* FontRenderer::cacheBitmapInTexture(Vector<CacheTexture*>& cacheTextures, |
| const SkGlyph& glyph, uint32_t* startX, uint32_t* startY) { |
| for (uint32_t i = 0; i < cacheTextures.size(); i++) { |
| if (cacheTextures[i]->fitBitmap(glyph, startX, startY)) { |
| return cacheTextures[i]; |
| } |
| } |
| // Could not fit glyph into current cache textures |
| return NULL; |
| } |
| |
| void FontRenderer::cacheBitmap(const SkGlyph& glyph, CachedGlyphInfo* cachedGlyph, |
| uint32_t* retOriginX, uint32_t* retOriginY, bool precaching) { |
| checkInit(); |
| |
| // If the glyph bitmap is empty let's assum the glyph is valid |
| // so we can avoid doing extra work later on |
| if (glyph.fWidth == 0 || glyph.fHeight == 0) { |
| cachedGlyph->mIsValid = true; |
| cachedGlyph->mCacheTexture = NULL; |
| return; |
| } |
| |
| cachedGlyph->mIsValid = false; |
| |
| // choose an appropriate cache texture list for this glyph format |
| SkMask::Format format = static_cast<SkMask::Format>(glyph.fMaskFormat); |
| Vector<CacheTexture*>* cacheTextures = NULL; |
| switch (format) { |
| case SkMask::kA8_Format: |
| case SkMask::kBW_Format: |
| cacheTextures = &mACacheTextures; |
| break; |
| case SkMask::kARGB32_Format: |
| cacheTextures = &mRGBACacheTextures; |
| break; |
| default: |
| #if DEBUG_FONT_RENDERER |
| ALOGD("getCacheTexturesForFormat: unknown SkMask format %x", format); |
| #endif |
| return; |
| } |
| |
| // If the glyph is too tall, don't cache it |
| if (glyph.fHeight + TEXTURE_BORDER_SIZE * 2 > |
| (*cacheTextures)[cacheTextures->size() - 1]->getHeight()) { |
| ALOGE("Font size too large to fit in cache. width, height = %i, %i", |
| (int) glyph.fWidth, (int) glyph.fHeight); |
| return; |
| } |
| |
| // Now copy the bitmap into the cache texture |
| uint32_t startX = 0; |
| uint32_t startY = 0; |
| |
| CacheTexture* cacheTexture = cacheBitmapInTexture(*cacheTextures, glyph, &startX, &startY); |
| |
| if (!cacheTexture) { |
| if (!precaching) { |
| // If the new glyph didn't fit and we are not just trying to precache it, |
| // clear out the cache and try again |
| flushAllAndInvalidate(); |
| cacheTexture = cacheBitmapInTexture(*cacheTextures, glyph, &startX, &startY); |
| } |
| |
| if (!cacheTexture) { |
| // either the glyph didn't fit or we're precaching and will cache it when we draw |
| return; |
| } |
| } |
| |
| cachedGlyph->mCacheTexture = cacheTexture; |
| |
| *retOriginX = startX; |
| *retOriginY = startY; |
| |
| uint32_t endX = startX + glyph.fWidth; |
| uint32_t endY = startY + glyph.fHeight; |
| |
| uint32_t cacheWidth = cacheTexture->getWidth(); |
| |
| if (!cacheTexture->getPixelBuffer()) { |
| Caches::getInstance().activeTexture(0); |
| // Large-glyph texture memory is allocated only as needed |
| cacheTexture->allocateTexture(); |
| } |
| if (!cacheTexture->mesh()) { |
| cacheTexture->allocateMesh(); |
| } |
| |
| uint8_t* cacheBuffer = cacheTexture->getPixelBuffer()->map(); |
| uint8_t* bitmapBuffer = (uint8_t*) glyph.fImage; |
| int srcStride = glyph.rowBytes(); |
| |
| // Copy the glyph image, taking the mask format into account |
| switch (format) { |
| case SkMask::kA8_Format: { |
| uint32_t cacheX = 0, bX = 0, cacheY = 0, bY = 0; |
| uint32_t row = (startY - TEXTURE_BORDER_SIZE) * cacheWidth + startX |
| - TEXTURE_BORDER_SIZE; |
| // write leading border line |
| memset(&cacheBuffer[row], 0, glyph.fWidth + 2 * TEXTURE_BORDER_SIZE); |
| // write glyph data |
| if (mGammaTable) { |
| for (cacheY = startY, bY = 0; cacheY < endY; cacheY++, bY += srcStride) { |
| row = cacheY * cacheWidth; |
| cacheBuffer[row + startX - TEXTURE_BORDER_SIZE] = 0; |
| for (cacheX = startX, bX = 0; cacheX < endX; cacheX++, bX++) { |
| uint8_t tempCol = bitmapBuffer[bY + bX]; |
| cacheBuffer[row + cacheX] = mGammaTable[tempCol]; |
| } |
| cacheBuffer[row + endX + TEXTURE_BORDER_SIZE - 1] = 0; |
| } |
| } else { |
| for (cacheY = startY, bY = 0; cacheY < endY; cacheY++, bY += srcStride) { |
| row = cacheY * cacheWidth; |
| memcpy(&cacheBuffer[row + startX], &bitmapBuffer[bY], glyph.fWidth); |
| cacheBuffer[row + startX - TEXTURE_BORDER_SIZE] = 0; |
| cacheBuffer[row + endX + TEXTURE_BORDER_SIZE - 1] = 0; |
| } |
| } |
| // write trailing border line |
| row = (endY + TEXTURE_BORDER_SIZE - 1) * cacheWidth + startX - TEXTURE_BORDER_SIZE; |
| memset(&cacheBuffer[row], 0, glyph.fWidth + 2 * TEXTURE_BORDER_SIZE); |
| break; |
| } |
| case SkMask::kARGB32_Format: { |
| // prep data lengths |
| const size_t formatSize = PixelBuffer::formatSize(GL_RGBA); |
| const size_t borderSize = formatSize * TEXTURE_BORDER_SIZE; |
| size_t rowSize = formatSize * glyph.fWidth; |
| // prep advances |
| size_t dstStride = formatSize * cacheWidth; |
| // prep indices |
| // - we actually start one row early, and then increment before first copy |
| uint8_t* src = &bitmapBuffer[0 - srcStride]; |
| uint8_t* dst = &cacheBuffer[cacheTexture->getOffset(startX, startY - 1)]; |
| uint8_t* dstEnd = &cacheBuffer[cacheTexture->getOffset(startX, endY - 1)]; |
| uint8_t* dstL = dst - borderSize; |
| uint8_t* dstR = dst + rowSize; |
| // write leading border line |
| memset(dstL, 0, rowSize + 2 * borderSize); |
| // write glyph data |
| while (dst < dstEnd) { |
| memset(dstL += dstStride, 0, borderSize); // leading border column |
| memcpy(dst += dstStride, src += srcStride, rowSize); // glyph data |
| memset(dstR += dstStride, 0, borderSize); // trailing border column |
| } |
| // write trailing border line |
| memset(dstL += dstStride, 0, rowSize + 2 * borderSize); |
| break; |
| } |
| case SkMask::kBW_Format: { |
| uint32_t cacheX = 0, bX = 0, cacheY = 0, bY = 0; |
| uint32_t row = (startY - TEXTURE_BORDER_SIZE) * cacheWidth + startX |
| - TEXTURE_BORDER_SIZE; |
| static const uint8_t COLORS[2] = { 0, 255 }; |
| // write leading border line |
| memset(&cacheBuffer[row], 0, glyph.fWidth + 2 * TEXTURE_BORDER_SIZE); |
| // write glyph data |
| for (cacheY = startY; cacheY < endY; cacheY++) { |
| cacheX = startX; |
| int rowBytes = srcStride; |
| uint8_t* buffer = bitmapBuffer; |
| |
| row = cacheY * cacheWidth; |
| cacheBuffer[row + startX - TEXTURE_BORDER_SIZE] = 0; |
| while (--rowBytes >= 0) { |
| uint8_t b = *buffer++; |
| for (int8_t mask = 7; mask >= 0 && cacheX < endX; mask--) { |
| cacheBuffer[cacheY * cacheWidth + cacheX++] = COLORS[(b >> mask) & 0x1]; |
| } |
| } |
| cacheBuffer[row + endX + TEXTURE_BORDER_SIZE - 1] = 0; |
| |
| bitmapBuffer += srcStride; |
| } |
| // write trailing border line |
| row = (endY + TEXTURE_BORDER_SIZE - 1) * cacheWidth + startX - TEXTURE_BORDER_SIZE; |
| memset(&cacheBuffer[row], 0, glyph.fWidth + 2 * TEXTURE_BORDER_SIZE); |
| break; |
| } |
| default: |
| ALOGW("Unknown glyph format: 0x%x", format); |
| break; |
| } |
| |
| cachedGlyph->mIsValid = true; |
| } |
| |
| CacheTexture* FontRenderer::createCacheTexture(int width, int height, GLenum format, |
| bool allocate) { |
| CacheTexture* cacheTexture = new CacheTexture(width, height, format, gMaxNumberOfQuads); |
| |
| if (allocate) { |
| Caches::getInstance().activeTexture(0); |
| cacheTexture->allocateTexture(); |
| cacheTexture->allocateMesh(); |
| } |
| |
| return cacheTexture; |
| } |
| |
| void FontRenderer::initTextTexture() { |
| clearCacheTextures(mACacheTextures); |
| clearCacheTextures(mRGBACacheTextures); |
| |
| mUploadTexture = false; |
| mACacheTextures.push(createCacheTexture(mSmallCacheWidth, mSmallCacheHeight, |
| GL_ALPHA, true)); |
| mACacheTextures.push(createCacheTexture(mLargeCacheWidth, mLargeCacheHeight >> 1, |
| GL_ALPHA, false)); |
| mACacheTextures.push(createCacheTexture(mLargeCacheWidth, mLargeCacheHeight >> 1, |
| GL_ALPHA, false)); |
| mACacheTextures.push(createCacheTexture(mLargeCacheWidth, mLargeCacheHeight, |
| GL_ALPHA, false)); |
| mRGBACacheTextures.push(createCacheTexture(mSmallCacheWidth, mSmallCacheHeight, |
| GL_RGBA, false)); |
| mRGBACacheTextures.push(createCacheTexture(mLargeCacheWidth, mLargeCacheHeight >> 1, |
| GL_RGBA, false)); |
| mCurrentCacheTexture = mACacheTextures[0]; |
| } |
| |
| // We don't want to allocate anything unless we actually draw text |
| void FontRenderer::checkInit() { |
| if (mInitialized) { |
| return; |
| } |
| |
| initTextTexture(); |
| |
| mInitialized = true; |
| } |
| |
| void checkTextureUpdateForCache(Caches& caches, Vector<CacheTexture*>& cacheTextures, |
| bool& resetPixelStore, GLuint& lastTextureId) { |
| for (uint32_t i = 0; i < cacheTextures.size(); i++) { |
| CacheTexture* cacheTexture = cacheTextures[i]; |
| if (cacheTexture->isDirty() && cacheTexture->getPixelBuffer()) { |
| if (cacheTexture->getTextureId() != lastTextureId) { |
| lastTextureId = cacheTexture->getTextureId(); |
| caches.activeTexture(0); |
| caches.bindTexture(lastTextureId); |
| } |
| |
| if (cacheTexture->upload()) { |
| resetPixelStore = true; |
| } |
| } |
| } |
| } |
| |
| void FontRenderer::checkTextureUpdate() { |
| if (!mUploadTexture) { |
| return; |
| } |
| |
| Caches& caches = Caches::getInstance(); |
| GLuint lastTextureId = 0; |
| |
| bool resetPixelStore = false; |
| glPixelStorei(GL_UNPACK_ALIGNMENT, 1); |
| |
| // Iterate over all the cache textures and see which ones need to be updated |
| checkTextureUpdateForCache(caches, mACacheTextures, resetPixelStore, lastTextureId); |
| checkTextureUpdateForCache(caches, mRGBACacheTextures, resetPixelStore, lastTextureId); |
| |
| // Unbind any PBO we might have used to update textures |
| caches.unbindPixelBuffer(); |
| |
| // Reset to default unpack row length to avoid affecting texture |
| // uploads in other parts of the renderer |
| if (resetPixelStore) { |
| glPixelStorei(GL_UNPACK_ROW_LENGTH, 0); |
| } |
| |
| mUploadTexture = false; |
| } |
| |
| void FontRenderer::issueDrawCommand(Vector<CacheTexture*>& cacheTextures) { |
| Caches& caches = Caches::getInstance(); |
| bool first = true; |
| bool force = false; |
| for (uint32_t i = 0; i < cacheTextures.size(); i++) { |
| CacheTexture* texture = cacheTextures[i]; |
| if (texture->canDraw()) { |
| if (first) { |
| if (mFunctor) { |
| TextSetupFunctor::Data functorData(texture->getFormat()); |
| (*mFunctor)(0, &functorData); |
| } |
| |
| checkTextureUpdate(); |
| caches.bindIndicesBuffer(); |
| |
| if (!mDrawn) { |
| // If returns true, a VBO was bound and we must |
| // rebind our vertex attrib pointers even if |
| // they have the same values as the current pointers |
| force = caches.unbindMeshBuffer(); |
| } |
| |
| caches.activeTexture(0); |
| first = false; |
| } |
| |
| caches.bindTexture(texture->getTextureId()); |
| texture->setLinearFiltering(mLinearFiltering, false); |
| |
| TextureVertex* mesh = texture->mesh(); |
| caches.bindPositionVertexPointer(force, &mesh[0].position[0]); |
| caches.bindTexCoordsVertexPointer(force, &mesh[0].texture[0]); |
| force = false; |
| |
| glDrawElements(GL_TRIANGLES, texture->meshElementCount(), |
| GL_UNSIGNED_SHORT, texture->indices()); |
| |
| texture->resetMesh(); |
| } |
| } |
| } |
| |
| void FontRenderer::issueDrawCommand() { |
| issueDrawCommand(mACacheTextures); |
| issueDrawCommand(mRGBACacheTextures); |
| |
| mDrawn = true; |
| } |
| |
| void FontRenderer::appendMeshQuadNoClip(float x1, float y1, float u1, float v1, |
| float x2, float y2, float u2, float v2, float x3, float y3, float u3, float v3, |
| float x4, float y4, float u4, float v4, CacheTexture* texture) { |
| if (texture != mCurrentCacheTexture) { |
| // Now use the new texture id |
| mCurrentCacheTexture = texture; |
| } |
| |
| mCurrentCacheTexture->addQuad(x1, y1, u1, v1, x2, y2, u2, v2, |
| x3, y3, u3, v3, x4, y4, u4, v4); |
| } |
| |
| void FontRenderer::appendMeshQuad(float x1, float y1, float u1, float v1, |
| float x2, float y2, float u2, float v2, float x3, float y3, float u3, float v3, |
| float x4, float y4, float u4, float v4, CacheTexture* texture) { |
| |
| if (mClip && |
| (x1 > mClip->right || y1 < mClip->top || x2 < mClip->left || y4 > mClip->bottom)) { |
| return; |
| } |
| |
| appendMeshQuadNoClip(x1, y1, u1, v1, x2, y2, u2, v2, x3, y3, u3, v3, x4, y4, u4, v4, texture); |
| |
| if (mBounds) { |
| mBounds->left = fmin(mBounds->left, x1); |
| mBounds->top = fmin(mBounds->top, y3); |
| mBounds->right = fmax(mBounds->right, x3); |
| mBounds->bottom = fmax(mBounds->bottom, y1); |
| } |
| |
| if (mCurrentCacheTexture->endOfMesh()) { |
| issueDrawCommand(); |
| } |
| } |
| |
| void FontRenderer::appendRotatedMeshQuad(float x1, float y1, float u1, float v1, |
| float x2, float y2, float u2, float v2, float x3, float y3, float u3, float v3, |
| float x4, float y4, float u4, float v4, CacheTexture* texture) { |
| |
| appendMeshQuadNoClip(x1, y1, u1, v1, x2, y2, u2, v2, x3, y3, u3, v3, x4, y4, u4, v4, texture); |
| |
| if (mBounds) { |
| mBounds->left = fmin(mBounds->left, fmin(x1, fmin(x2, fmin(x3, x4)))); |
| mBounds->top = fmin(mBounds->top, fmin(y1, fmin(y2, fmin(y3, y4)))); |
| mBounds->right = fmax(mBounds->right, fmax(x1, fmax(x2, fmax(x3, x4)))); |
| mBounds->bottom = fmax(mBounds->bottom, fmax(y1, fmax(y2, fmax(y3, y4)))); |
| } |
| |
| if (mCurrentCacheTexture->endOfMesh()) { |
| issueDrawCommand(); |
| } |
| } |
| |
| void FontRenderer::setFont(SkPaint* paint, const mat4& matrix) { |
| mCurrentFont = Font::create(this, paint, matrix); |
| } |
| |
| FontRenderer::DropShadow FontRenderer::renderDropShadow(SkPaint* paint, const char *text, |
| uint32_t startIndex, uint32_t len, int numGlyphs, uint32_t radius, const float* positions) { |
| checkInit(); |
| |
| DropShadow image; |
| image.width = 0; |
| image.height = 0; |
| image.image = NULL; |
| image.penX = 0; |
| image.penY = 0; |
| |
| if (!mCurrentFont) { |
| return image; |
| } |
| |
| mDrawn = false; |
| mClip = NULL; |
| mBounds = NULL; |
| |
| Rect bounds; |
| mCurrentFont->measure(paint, text, startIndex, len, numGlyphs, &bounds, positions); |
| |
| uint32_t paddedWidth = (uint32_t) (bounds.right - bounds.left) + 2 * radius; |
| uint32_t paddedHeight = (uint32_t) (bounds.top - bounds.bottom) + 2 * radius; |
| |
| uint32_t maxSize = Caches::getInstance().maxTextureSize; |
| if (paddedWidth > maxSize || paddedHeight > maxSize) { |
| return image; |
| } |
| |
| #ifdef ANDROID_ENABLE_RENDERSCRIPT |
| // Align buffers for renderscript usage |
| if (paddedWidth & (RS_CPU_ALLOCATION_ALIGNMENT - 1)) { |
| paddedWidth += RS_CPU_ALLOCATION_ALIGNMENT - paddedWidth % RS_CPU_ALLOCATION_ALIGNMENT; |
| } |
| int size = paddedWidth * paddedHeight; |
| uint8_t* dataBuffer = (uint8_t*) memalign(RS_CPU_ALLOCATION_ALIGNMENT, size); |
| #else |
| int size = paddedWidth * paddedHeight; |
| uint8_t* dataBuffer = (uint8_t*) malloc(size); |
| #endif |
| |
| memset(dataBuffer, 0, size); |
| |
| int penX = radius - bounds.left; |
| int penY = radius - bounds.bottom; |
| |
| if ((bounds.right > bounds.left) && (bounds.top > bounds.bottom)) { |
| // text has non-whitespace, so draw and blur to create the shadow |
| // NOTE: bounds.isEmpty() can't be used here, since vertical coordinates are inverted |
| // TODO: don't draw pure whitespace in the first place, and avoid needing this check |
| mCurrentFont->render(paint, text, startIndex, len, numGlyphs, penX, penY, |
| Font::BITMAP, dataBuffer, paddedWidth, paddedHeight, NULL, positions); |
| |
| // Unbind any PBO we might have used |
| Caches::getInstance().unbindPixelBuffer(); |
| |
| blurImage(&dataBuffer, paddedWidth, paddedHeight, radius); |
| } |
| |
| image.width = paddedWidth; |
| image.height = paddedHeight; |
| image.image = dataBuffer; |
| image.penX = penX; |
| image.penY = penY; |
| |
| return image; |
| } |
| |
| void FontRenderer::initRender(const Rect* clip, Rect* bounds, Functor* functor) { |
| checkInit(); |
| |
| mDrawn = false; |
| mBounds = bounds; |
| mFunctor = functor; |
| mClip = clip; |
| } |
| |
| void FontRenderer::finishRender() { |
| mBounds = NULL; |
| mClip = NULL; |
| |
| issueDrawCommand(); |
| } |
| |
| void FontRenderer::precache(SkPaint* paint, const char* text, int numGlyphs, const mat4& matrix) { |
| Font* font = Font::create(this, paint, matrix); |
| font->precache(paint, text, numGlyphs); |
| } |
| |
| void FontRenderer::endPrecaching() { |
| checkTextureUpdate(); |
| } |
| |
| bool FontRenderer::renderPosText(SkPaint* paint, const Rect* clip, const char *text, |
| uint32_t startIndex, uint32_t len, int numGlyphs, int x, int y, |
| const float* positions, Rect* bounds, Functor* functor, bool forceFinish) { |
| if (!mCurrentFont) { |
| ALOGE("No font set"); |
| return false; |
| } |
| |
| initRender(clip, bounds, functor); |
| mCurrentFont->render(paint, text, startIndex, len, numGlyphs, x, y, positions); |
| |
| if (forceFinish) { |
| finishRender(); |
| } |
| |
| return mDrawn; |
| } |
| |
| bool FontRenderer::renderTextOnPath(SkPaint* paint, const Rect* clip, const char *text, |
| uint32_t startIndex, uint32_t len, int numGlyphs, SkPath* path, |
| float hOffset, float vOffset, Rect* bounds, Functor* functor) { |
| if (!mCurrentFont) { |
| ALOGE("No font set"); |
| return false; |
| } |
| |
| initRender(clip, bounds, functor); |
| mCurrentFont->render(paint, text, startIndex, len, numGlyphs, path, hOffset, vOffset); |
| finishRender(); |
| |
| return mDrawn; |
| } |
| |
| void FontRenderer::removeFont(const Font* font) { |
| mActiveFonts.remove(font->getDescription()); |
| |
| if (mCurrentFont == font) { |
| mCurrentFont = NULL; |
| } |
| } |
| |
| void FontRenderer::blurImage(uint8_t** image, int32_t width, int32_t height, int32_t radius) { |
| #ifdef ANDROID_ENABLE_RENDERSCRIPT |
| if (width * height * radius >= RS_MIN_INPUT_CUTOFF) { |
| uint8_t* outImage = (uint8_t*) memalign(RS_CPU_ALLOCATION_ALIGNMENT, width * height); |
| |
| if (mRs == 0) { |
| mRs = new RSC::RS(); |
| // a null path is OK because there are no custom kernels used |
| // hence nothing gets cached by RS |
| if (!mRs->init("", RSC::RS_INIT_LOW_LATENCY | RSC::RS_INIT_SYNCHRONOUS)) { |
| ALOGE("blur RS failed to init"); |
| } |
| |
| mRsElement = RSC::Element::A_8(mRs); |
| mRsScript = RSC::ScriptIntrinsicBlur::create(mRs, mRsElement); |
| } |
| |
| RSC::sp<const RSC::Type> t = RSC::Type::create(mRs, mRsElement, width, height, 0); |
| RSC::sp<RSC::Allocation> ain = RSC::Allocation::createTyped(mRs, t, |
| RS_ALLOCATION_MIPMAP_NONE, RS_ALLOCATION_USAGE_SCRIPT | RS_ALLOCATION_USAGE_SHARED, |
| *image); |
| RSC::sp<RSC::Allocation> aout = RSC::Allocation::createTyped(mRs, t, |
| RS_ALLOCATION_MIPMAP_NONE, RS_ALLOCATION_USAGE_SCRIPT | RS_ALLOCATION_USAGE_SHARED, |
| outImage); |
| |
| mRsScript->setRadius(radius); |
| mRsScript->setInput(ain); |
| mRsScript->forEach(aout); |
| |
| // replace the original image's pointer, avoiding a copy back to the original buffer |
| free(*image); |
| *image = outImage; |
| |
| return; |
| } |
| #endif |
| |
| float *gaussian = new float[2 * radius + 1]; |
| Blur::generateGaussianWeights(gaussian, radius); |
| |
| uint8_t* scratch = new uint8_t[width * height]; |
| Blur::horizontal(gaussian, radius, *image, scratch, width, height); |
| Blur::vertical(gaussian, radius, scratch, *image, width, height); |
| |
| delete[] gaussian; |
| delete[] scratch; |
| } |
| |
| static uint32_t calculateCacheSize(const Vector<CacheTexture*>& cacheTextures) { |
| uint32_t size = 0; |
| for (uint32_t i = 0; i < cacheTextures.size(); i++) { |
| CacheTexture* cacheTexture = cacheTextures[i]; |
| if (cacheTexture && cacheTexture->getPixelBuffer()) { |
| size += cacheTexture->getPixelBuffer()->getSize(); |
| } |
| } |
| return size; |
| } |
| |
| uint32_t FontRenderer::getCacheSize(GLenum format) const { |
| switch (format) { |
| case GL_ALPHA: { |
| return calculateCacheSize(mACacheTextures); |
| } |
| case GL_RGBA: { |
| return calculateCacheSize(mRGBACacheTextures); |
| } |
| default: { |
| return 0; |
| } |
| } |
| } |
| |
| }; // namespace uirenderer |
| }; // namespace android |