| /* |
| * 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. |
| */ |
| |
| #include "FontRenderer.h" |
| |
| #include "BakedOpDispatcher.h" |
| #include "BakedOpRenderer.h" |
| #include "BakedOpState.h" |
| #include "Caches.h" |
| #include "Debug.h" |
| #include "Extensions.h" |
| #include "Glop.h" |
| #include "GlopBuilder.h" |
| #include "PixelBuffer.h" |
| #include "Rect.h" |
| #include "font/Font.h" |
| #include "renderstate/RenderState.h" |
| #include "utils/Blur.h" |
| #include "utils/Timing.h" |
| |
| #include <RenderScript.h> |
| #include <SkGlyph.h> |
| #include <SkUtils.h> |
| #include <utils/Log.h> |
| #include <algorithm> |
| |
| namespace android { |
| namespace uirenderer { |
| |
| // blur inputs smaller than this constant will bypass renderscript |
| #define RS_MIN_INPUT_CUTOFF 10000 |
| |
| /////////////////////////////////////////////////////////////////////////////// |
| // TextSetupFunctor |
| /////////////////////////////////////////////////////////////////////////////// |
| |
| void TextDrawFunctor::draw(CacheTexture& texture, bool linearFiltering) { |
| int textureFillFlags = TextureFillFlags::None; |
| if (texture.getFormat() == GL_ALPHA) { |
| textureFillFlags |= TextureFillFlags::IsAlphaMaskTexture; |
| } |
| if (linearFiltering) { |
| textureFillFlags |= TextureFillFlags::ForceFilter; |
| } |
| int transformFlags = |
| pureTranslate ? TransformFlags::MeshIgnoresCanvasTransform : TransformFlags::None; |
| #ifdef ANDROID_ENABLE_LINEAR_BLENDING |
| bool gammaCorrection = true; |
| #else |
| bool gammaCorrection = false; |
| #endif |
| Glop glop; |
| GlopBuilder(renderer->renderState(), renderer->caches(), &glop) |
| .setRoundRectClipState(bakedState->roundRectClipState) |
| .setMeshTexturedIndexedQuads(texture.mesh(), texture.meshElementCount()) |
| .setFillTexturePaint(texture.getTexture(), textureFillFlags, paint, bakedState->alpha) |
| .setGammaCorrection(gammaCorrection) |
| .setTransform(bakedState->computedState.transform, transformFlags) |
| .setModelViewIdentityEmptyBounds() |
| .build(); |
| // Note: don't pass dirty bounds here, so user must manage passing dirty bounds to renderer |
| renderer->renderGlop(nullptr, clip, glop); |
| } |
| |
| /////////////////////////////////////////////////////////////////////////////// |
| // FontRenderer |
| /////////////////////////////////////////////////////////////////////////////// |
| |
| static bool sLogFontRendererCreate = true; |
| |
| FontRenderer::FontRenderer(const uint8_t* gammaTable) |
| : mGammaTable(gammaTable) |
| , mCurrentFont(nullptr) |
| , mActiveFonts(LruCache<Font::FontDescription, Font*>::kUnlimitedCapacity) |
| , mCurrentCacheTexture(nullptr) |
| , mUploadTexture(false) |
| , mFunctor(nullptr) |
| , mClip(nullptr) |
| , mBounds(nullptr) |
| , mDrawn(false) |
| , mInitialized(false) |
| , mLinearFiltering(false) { |
| if (sLogFontRendererCreate) { |
| INIT_LOGD("Creating FontRenderer"); |
| } |
| |
| auto deviceInfo = DeviceInfo::get(); |
| auto displayInfo = deviceInfo->displayInfo(); |
| int maxTextureSize = deviceInfo->maxTextureSize(); |
| |
| // Adjust cache size based on Pixel's desnsity. |
| constexpr float PIXEL_DENSITY = 2.6; |
| const float densityRatio = displayInfo.density / PIXEL_DENSITY; |
| |
| // TODO: Most devices are hardcoded with this configuration, does it need to be dynamic? |
| mSmallCacheWidth = |
| OffscreenBuffer::computeIdealDimension(std::min(1024, maxTextureSize) * densityRatio); |
| mSmallCacheHeight = |
| OffscreenBuffer::computeIdealDimension(std::min(1024, maxTextureSize) * densityRatio); |
| mLargeCacheWidth = |
| OffscreenBuffer::computeIdealDimension(std::min(2048, maxTextureSize) * densityRatio); |
| mLargeCacheHeight = |
| OffscreenBuffer::computeIdealDimension(std::min(1024, maxTextureSize) * densityRatio); |
| |
| 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(std::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(); |
| |
| #ifdef BUGREPORT_FONT_CACHE_USAGE |
| mHistoryTracker.glyphsCleared(mACacheTextures[i]); |
| #endif |
| } |
| |
| for (uint32_t i = 0; i < mRGBACacheTextures.size(); i++) { |
| mRGBACacheTextures[i]->init(); |
| #ifdef BUGREPORT_FONT_CACHE_USAGE |
| mHistoryTracker.glyphsCleared(mRGBACacheTextures[i]); |
| #endif |
| } |
| |
| mDrawn = false; |
| } |
| |
| void FontRenderer::flushLargeCaches(std::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(); |
| #ifdef BUGREPORT_FONT_CACHE_USAGE |
| mHistoryTracker.glyphsCleared(cacheTexture); |
| #endif |
| LruCache<Font::FontDescription, Font*>::Iterator it(mActiveFonts); |
| while (it.next()) { |
| it.value()->invalidateTextureCache(cacheTexture); |
| } |
| cacheTexture->releasePixelBuffer(); |
| } |
| } |
| } |
| |
| void FontRenderer::flushLargeCaches() { |
| flushLargeCaches(mACacheTextures); |
| flushLargeCaches(mRGBACacheTextures); |
| } |
| |
| CacheTexture* FontRenderer::cacheBitmapInTexture(std::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 nullptr; |
| } |
| |
| 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 = nullptr; |
| return; |
| } |
| |
| cachedGlyph->mIsValid = false; |
| |
| // choose an appropriate cache texture list for this glyph format |
| SkMask::Format format = static_cast<SkMask::Format>(glyph.fMaskFormat); |
| std::vector<CacheTexture*>* cacheTextures = nullptr; |
| 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().textureState().activateTexture(0); |
| // Large-glyph texture memory is allocated only as needed |
| cacheTexture->allocatePixelBuffer(); |
| } |
| 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 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 (uint32_t cacheY = startY, bY = 0; cacheY < endY; cacheY++, bY += srcStride) { |
| row = cacheY * cacheWidth; |
| cacheBuffer[row + startX - TEXTURE_BORDER_SIZE] = 0; |
| for (uint32_t 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 (uint32_t 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, cacheY = 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; |
| |
| #ifdef BUGREPORT_FONT_CACHE_USAGE |
| mHistoryTracker.glyphUploaded(cacheTexture, startX, startY, glyph.fWidth, glyph.fHeight); |
| #endif |
| } |
| |
| CacheTexture* FontRenderer::createCacheTexture(int width, int height, GLenum format, |
| bool allocate) { |
| CacheTexture* cacheTexture = new CacheTexture(width, height, format, kMaxNumberOfQuads); |
| |
| if (allocate) { |
| Caches::getInstance().textureState().activateTexture(0); |
| cacheTexture->allocatePixelBuffer(); |
| cacheTexture->allocateMesh(); |
| } |
| |
| return cacheTexture; |
| } |
| |
| void FontRenderer::initTextTexture() { |
| clearCacheTextures(mACacheTextures); |
| clearCacheTextures(mRGBACacheTextures); |
| |
| mUploadTexture = false; |
| mACacheTextures.push_back( |
| createCacheTexture(mSmallCacheWidth, mSmallCacheHeight, GL_ALPHA, true)); |
| mACacheTextures.push_back( |
| createCacheTexture(mLargeCacheWidth, mLargeCacheHeight >> 1, GL_ALPHA, false)); |
| mACacheTextures.push_back( |
| createCacheTexture(mLargeCacheWidth, mLargeCacheHeight >> 1, GL_ALPHA, false)); |
| mACacheTextures.push_back( |
| createCacheTexture(mLargeCacheWidth, mLargeCacheHeight, GL_ALPHA, false)); |
| mRGBACacheTextures.push_back( |
| createCacheTexture(mSmallCacheWidth, mSmallCacheHeight, GL_RGBA, false)); |
| mRGBACacheTextures.push_back( |
| 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, std::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.textureState().activateTexture(0); |
| caches.textureState().bindTexture(lastTextureId); |
| } |
| |
| if (cacheTexture->upload()) { |
| resetPixelStore = true; |
| } |
| } |
| } |
| } |
| |
| void FontRenderer::checkTextureUpdate() { |
| if (!mUploadTexture) { |
| return; |
| } |
| |
| Caches& caches = Caches::getInstance(); |
| GLuint lastTextureId = 0; |
| |
| bool resetPixelStore = false; |
| |
| // 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.pixelBufferState().unbind(); |
| |
| // 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(std::vector<CacheTexture*>& cacheTextures) { |
| if (!mFunctor) return; |
| |
| bool first = true; |
| for (uint32_t i = 0; i < cacheTextures.size(); i++) { |
| CacheTexture* texture = cacheTextures[i]; |
| if (texture->canDraw()) { |
| if (first) { |
| checkTextureUpdate(); |
| first = false; |
| mDrawn = true; |
| } |
| |
| mFunctor->draw(*texture, mLinearFiltering); |
| |
| texture->resetMesh(); |
| } |
| } |
| } |
| |
| void FontRenderer::issueDrawCommand() { |
| issueDrawCommand(mACacheTextures); |
| issueDrawCommand(mRGBACacheTextures); |
| } |
| |
| 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 = std::min(mBounds->left, x1); |
| mBounds->top = std::min(mBounds->top, y3); |
| mBounds->right = std::max(mBounds->right, x3); |
| mBounds->bottom = std::max(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 = std::min(mBounds->left, std::min(x1, std::min(x2, std::min(x3, x4)))); |
| mBounds->top = std::min(mBounds->top, std::min(y1, std::min(y2, std::min(y3, y4)))); |
| mBounds->right = std::max(mBounds->right, std::max(x1, std::max(x2, std::max(x3, x4)))); |
| mBounds->bottom = std::max(mBounds->bottom, std::max(y1, std::max(y2, std::max(y3, y4)))); |
| } |
| |
| if (mCurrentCacheTexture->endOfMesh()) { |
| issueDrawCommand(); |
| } |
| } |
| |
| void FontRenderer::setFont(const SkPaint* paint, const SkMatrix& matrix) { |
| mCurrentFont = Font::create(this, paint, matrix); |
| } |
| |
| FontRenderer::DropShadow FontRenderer::renderDropShadow(const SkPaint* paint, const glyph_t* glyphs, |
| int numGlyphs, float radius, |
| const float* positions) { |
| checkInit(); |
| |
| DropShadow image; |
| image.width = 0; |
| image.height = 0; |
| image.image = nullptr; |
| image.penX = 0; |
| image.penY = 0; |
| |
| if (!mCurrentFont) { |
| return image; |
| } |
| |
| mDrawn = false; |
| mClip = nullptr; |
| mBounds = nullptr; |
| |
| Rect bounds; |
| mCurrentFont->measure(paint, glyphs, numGlyphs, &bounds, positions); |
| |
| uint32_t intRadius = Blur::convertRadiusToInt(radius); |
| uint32_t paddedWidth = (uint32_t)(bounds.right - bounds.left) + 2 * intRadius; |
| uint32_t paddedHeight = (uint32_t)(bounds.top - bounds.bottom) + 2 * intRadius; |
| |
| uint32_t maxSize = Caches::getInstance().maxTextureSize; |
| if (paddedWidth > maxSize || paddedHeight > maxSize) { |
| return image; |
| } |
| |
| // 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); |
| |
| memset(dataBuffer, 0, size); |
| |
| int penX = intRadius - bounds.left; |
| int penY = intRadius - 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, glyphs, numGlyphs, penX, penY, Font::BITMAP, dataBuffer, |
| paddedWidth, paddedHeight, nullptr, positions); |
| |
| // Unbind any PBO we might have used |
| Caches::getInstance().pixelBufferState().unbind(); |
| |
| 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, TextDrawFunctor* functor) { |
| checkInit(); |
| |
| mDrawn = false; |
| mBounds = bounds; |
| mFunctor = functor; |
| mClip = clip; |
| } |
| |
| void FontRenderer::finishRender() { |
| mBounds = nullptr; |
| mClip = nullptr; |
| |
| issueDrawCommand(); |
| } |
| |
| void FontRenderer::precache(const SkPaint* paint, const glyph_t* glyphs, int numGlyphs, |
| const SkMatrix& matrix) { |
| Font* font = Font::create(this, paint, matrix); |
| font->precache(paint, glyphs, numGlyphs); |
| } |
| |
| void FontRenderer::endPrecaching() { |
| checkTextureUpdate(); |
| } |
| |
| bool FontRenderer::renderPosText(const SkPaint* paint, const Rect* clip, const glyph_t* glyphs, |
| int numGlyphs, int x, int y, const float* positions, Rect* bounds, |
| TextDrawFunctor* functor, bool forceFinish) { |
| if (!mCurrentFont) { |
| ALOGE("No font set"); |
| return false; |
| } |
| |
| initRender(clip, bounds, functor); |
| mCurrentFont->render(paint, glyphs, numGlyphs, x, y, positions); |
| |
| if (forceFinish) { |
| finishRender(); |
| } |
| |
| return mDrawn; |
| } |
| |
| bool FontRenderer::renderTextOnPath(const SkPaint* paint, const Rect* clip, const glyph_t* glyphs, |
| int numGlyphs, const SkPath* path, float hOffset, float vOffset, |
| Rect* bounds, TextDrawFunctor* functor) { |
| if (!mCurrentFont) { |
| ALOGE("No font set"); |
| return false; |
| } |
| |
| initRender(clip, bounds, functor); |
| mCurrentFont->render(paint, glyphs, numGlyphs, path, hOffset, vOffset); |
| finishRender(); |
| |
| return mDrawn; |
| } |
| |
| void FontRenderer::blurImage(uint8_t** image, int32_t width, int32_t height, float radius) { |
| uint32_t intRadius = Blur::convertRadiusToInt(radius); |
| if (width * height * intRadius >= RS_MIN_INPUT_CUTOFF && radius <= 25.0f) { |
| uint8_t* outImage = (uint8_t*)memalign(RS_CPU_ALLOCATION_ALIGNMENT, width * height); |
| |
| if (mRs == nullptr) { |
| 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)) { |
| mRs.clear(); |
| ALOGE("blur RS failed to init"); |
| } else { |
| mRsElement = RSC::Element::A_8(mRs); |
| mRsScript = RSC::ScriptIntrinsicBlur::create(mRs, mRsElement); |
| } |
| } |
| if (mRs != nullptr) { |
| 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; |
| } |
| } |
| |
| std::unique_ptr<float[]> gaussian(new float[2 * intRadius + 1]); |
| Blur::generateGaussianWeights(gaussian.get(), radius); |
| |
| std::unique_ptr<uint8_t[]> scratch(new uint8_t[width * height]); |
| Blur::horizontal(gaussian.get(), intRadius, *image, scratch.get(), width, height); |
| Blur::vertical(gaussian.get(), intRadius, scratch.get(), *image, width, height); |
| } |
| |
| static uint32_t calculateCacheSize(const std::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; |
| } |
| |
| static uint32_t calculateFreeCacheSize(const std::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->calculateFreeMemory(); |
| } |
| } |
| return size; |
| } |
| |
| const std::vector<CacheTexture*>& FontRenderer::cacheTexturesForFormat(GLenum format) const { |
| switch (format) { |
| case GL_ALPHA: { |
| return mACacheTextures; |
| } |
| case GL_RGBA: { |
| return mRGBACacheTextures; |
| } |
| default: { |
| LOG_ALWAYS_FATAL("Unsupported format: %d", format); |
| // Impossible to hit this, but the compiler doesn't know that |
| return *(new std::vector<CacheTexture*>()); |
| } |
| } |
| } |
| |
| static void dumpTextures(String8& log, const char* tag, |
| const std::vector<CacheTexture*>& cacheTextures) { |
| for (uint32_t i = 0; i < cacheTextures.size(); i++) { |
| CacheTexture* cacheTexture = cacheTextures[i]; |
| if (cacheTexture && cacheTexture->getPixelBuffer()) { |
| uint32_t free = cacheTexture->calculateFreeMemory(); |
| uint32_t total = cacheTexture->getPixelBuffer()->getSize(); |
| log.appendFormat(" %-4s texture %d %8d / %8d\n", tag, i, total - free, total); |
| } |
| } |
| } |
| |
| void FontRenderer::dumpMemoryUsage(String8& log) const { |
| const uint32_t sizeA8 = getCacheSize(GL_ALPHA); |
| const uint32_t usedA8 = sizeA8 - getFreeCacheSize(GL_ALPHA); |
| const uint32_t sizeRGBA = getCacheSize(GL_RGBA); |
| const uint32_t usedRGBA = sizeRGBA - getFreeCacheSize(GL_RGBA); |
| log.appendFormat(" FontRenderer A8 %8d / %8d\n", usedA8, sizeA8); |
| dumpTextures(log, "A8", cacheTexturesForFormat(GL_ALPHA)); |
| log.appendFormat(" FontRenderer RGBA %8d / %8d\n", usedRGBA, sizeRGBA); |
| dumpTextures(log, "RGBA", cacheTexturesForFormat(GL_RGBA)); |
| log.appendFormat(" FontRenderer total %8d / %8d\n", usedA8 + usedRGBA, sizeA8 + sizeRGBA); |
| } |
| |
| uint32_t FontRenderer::getCacheSize(GLenum format) const { |
| return calculateCacheSize(cacheTexturesForFormat(format)); |
| } |
| |
| uint32_t FontRenderer::getFreeCacheSize(GLenum format) const { |
| return calculateFreeCacheSize(cacheTexturesForFormat(format)); |
| } |
| |
| uint32_t FontRenderer::getSize() const { |
| return getCacheSize(GL_ALPHA) + getCacheSize(GL_RGBA); |
| } |
| |
| }; // namespace uirenderer |
| }; // namespace android |