Merge "remove (dead) Font code"

This commit is contained in:
android-build-team Robot
2018-05-02 17:55:27 +00:00
committed by Android (Google) Code Review
24 changed files with 13 additions and 3158 deletions

View File

@@ -75,7 +75,6 @@ cc_defaults {
cc_defaults {
name: "hwui_bugreport_font_cache_usage",
srcs: ["font/FontCacheHistoryTracker.cpp"],
cflags: ["-DBUGREPORT_FONT_CACHE_USAGE"],
}
@@ -152,8 +151,6 @@ cc_defaults {
"hwui/AnimatedImageDrawable.cpp",
"hwui/AnimatedImageThread.cpp",
"hwui/Bitmap.cpp",
"font/CacheTexture.cpp",
"font/Font.cpp",
"hwui/Canvas.cpp",
"hwui/MinikinSkia.cpp",
"hwui/MinikinUtils.cpp",
@@ -215,11 +212,9 @@ cc_defaults {
"DeviceInfo.cpp",
"DisplayList.cpp",
"FboCache.cpp",
"FontRenderer.cpp",
"FrameBuilder.cpp",
"FrameInfo.cpp",
"FrameInfoVisualizer.cpp",
"GammaFontRenderer.cpp",
"GlLayer.cpp",
"GlopBuilder.cpp",
"GpuMemoryTracker.cpp",
@@ -259,7 +254,6 @@ cc_defaults {
"Snapshot.cpp",
"SpotShadow.cpp",
"TessellationCache.cpp",
"TextDropShadowCache.cpp",
"Texture.cpp",
"TextureCache.cpp",
"VectorDrawable.cpp",
@@ -413,7 +407,6 @@ cc_benchmark {
srcs: [
"tests/microbench/main.cpp",
"tests/microbench/DisplayListCanvasBench.cpp",
"tests/microbench/FontBench.cpp",
"tests/microbench/FrameBuilderBench.cpp",
"tests/microbench/LinearAllocatorBench.cpp",
"tests/microbench/PathParserBench.cpp",

View File

@@ -187,121 +187,14 @@ void BakedOpDispatcher::onMergedPatchOps(BakedOpRenderer& renderer,
static void renderTextShadow(BakedOpRenderer& renderer, const TextOp& op,
const BakedOpState& textOpState) {
if (CC_LIKELY(!PaintUtils::hasTextShadow(op.paint))) return;
FontRenderer& fontRenderer = renderer.caches().fontRenderer.getFontRenderer();
fontRenderer.setFont(op.paint, SkMatrix::I());
renderer.caches().textureState().activateTexture(0);
PaintUtils::TextShadow textShadow;
if (!PaintUtils::getTextShadow(op.paint, &textShadow)) {
LOG_ALWAYS_FATAL("failed to query shadow attributes");
}
renderer.caches().dropShadowCache.setFontRenderer(fontRenderer);
ShadowTexture* texture = renderer.caches().dropShadowCache.get(
op.paint, op.glyphs, op.glyphCount, textShadow.radius, op.positions);
// If the drop shadow exceeds the max texture size or couldn't be
// allocated, skip drawing
if (!texture) return;
const AutoTexture autoCleanup(texture);
const float sx = op.x - texture->left + textShadow.dx;
const float sy = op.y - texture->top + textShadow.dy;
Glop glop;
GlopBuilder(renderer.renderState(), renderer.caches(), &glop)
.setRoundRectClipState(textOpState.roundRectClipState)
.setMeshTexturedUnitQuad(nullptr)
.setFillShadowTexturePaint(*texture, textShadow.color, *op.paint, textOpState.alpha)
.setTransform(textOpState.computedState.transform, TransformFlags::None)
.setModelViewMapUnitToRect(Rect(sx, sy, sx + texture->width(), sy + texture->height()))
.build();
// Compute damage bounds and clip (since may differ from those in textOpState).
// Bounds should be same as text op, but with dx/dy offset and radius outset
// applied in local space.
auto& transform = textOpState.computedState.transform;
Rect shadowBounds = op.unmappedBounds; // STROKE
const bool expandForStroke = op.paint->getStyle() != SkPaint::kFill_Style;
if (expandForStroke) {
shadowBounds.outset(op.paint->getStrokeWidth() * 0.5f);
}
shadowBounds.translate(textShadow.dx, textShadow.dy);
shadowBounds.outset(textShadow.radius, textShadow.radius);
transform.mapRect(shadowBounds);
if (CC_UNLIKELY(expandForStroke &&
(!transform.isPureTranslate() || op.paint->getStrokeWidth() < 1.0f))) {
shadowBounds.outset(0.5f);
}
auto clipState = textOpState.computedState.clipState;
if (clipState->mode != ClipMode::Rectangle || !clipState->rect.contains(shadowBounds)) {
// need clip, so pass it and clip bounds
shadowBounds.doIntersect(clipState->rect);
} else {
// don't need clip, ignore
clipState = nullptr;
}
renderer.renderGlop(&shadowBounds, clipState, glop);
// DEAD CODE
}
enum class TextRenderType { Defer, Flush };
static void renderText(BakedOpRenderer& renderer, const TextOp& op, const BakedOpState& state,
const ClipBase* renderClip, TextRenderType renderType) {
FontRenderer& fontRenderer = renderer.caches().fontRenderer.getFontRenderer();
float x = op.x;
float y = op.y;
const Matrix4& transform = state.computedState.transform;
const bool pureTranslate = transform.isPureTranslate();
if (CC_LIKELY(pureTranslate)) {
x = floorf(x + transform.getTranslateX() + 0.5f);
y = floorf(y + transform.getTranslateY() + 0.5f);
fontRenderer.setFont(op.paint, SkMatrix::I());
fontRenderer.setTextureFiltering(false);
} else if (CC_UNLIKELY(transform.isPerspective())) {
fontRenderer.setFont(op.paint, SkMatrix::I());
fontRenderer.setTextureFiltering(true);
} else {
// We only pass a partial transform to the font renderer. That partial
// matrix defines how glyphs are rasterized. Typically we want glyphs
// to be rasterized at their final size on screen, which means the partial
// matrix needs to take the scale factor into account.
// When a partial matrix is used to transform glyphs during rasterization,
// the mesh is generated with the inverse transform (in the case of scale,
// the mesh is generated at 1.0 / scale for instance.) This allows us to
// apply the full transform matrix at draw time in the vertex shader.
// Applying the full matrix in the shader is the easiest way to handle
// rotation and perspective and allows us to always generated quads in the
// font renderer which greatly simplifies the code, clipping in particular.
float sx, sy;
transform.decomposeScale(sx, sy);
fontRenderer.setFont(op.paint, SkMatrix::MakeScale(roundf(std::max(1.0f, sx)),
roundf(std::max(1.0f, sy))));
fontRenderer.setTextureFiltering(true);
}
Rect layerBounds(FLT_MAX / 2.0f, FLT_MAX / 2.0f, FLT_MIN / 2.0f, FLT_MIN / 2.0f);
int alpha = PaintUtils::getAlphaDirect(op.paint) * state.alpha;
SkBlendMode mode = PaintUtils::getBlendModeDirect(op.paint);
TextDrawFunctor functor(&renderer, &state, renderClip, x, y, pureTranslate, alpha, mode,
op.paint);
bool forceFinish = (renderType == TextRenderType::Flush);
bool mustDirtyRenderTarget = renderer.offscreenRenderTarget();
const Rect* localOpClip = pureTranslate ? &state.computedState.clipRect() : nullptr;
fontRenderer.renderPosText(op.paint, localOpClip, op.glyphs, op.glyphCount, x, y, op.positions,
mustDirtyRenderTarget ? &layerBounds : nullptr, &functor,
forceFinish);
if (mustDirtyRenderTarget) {
if (!pureTranslate) {
transform.mapRect(layerBounds);
}
renderer.dirtyRenderTarget(layerBounds);
}
// DEAD CODE
}
void BakedOpDispatcher::onMergedTextOps(BakedOpRenderer& renderer,
@@ -747,32 +640,7 @@ void BakedOpDispatcher::onTextOp(BakedOpRenderer& renderer, const TextOp& op,
void BakedOpDispatcher::onTextOnPathOp(BakedOpRenderer& renderer, const TextOnPathOp& op,
const BakedOpState& state) {
// Note: can't trust clipSideFlags since we record with unmappedBounds == clip.
// TODO: respect clipSideFlags, once we record with bounds
auto renderTargetClip = state.computedState.clipState;
FontRenderer& fontRenderer = renderer.caches().fontRenderer.getFontRenderer();
fontRenderer.setFont(op.paint, SkMatrix::I());
fontRenderer.setTextureFiltering(true);
Rect layerBounds(FLT_MAX / 2.0f, FLT_MAX / 2.0f, FLT_MIN / 2.0f, FLT_MIN / 2.0f);
int alpha = PaintUtils::getAlphaDirect(op.paint) * state.alpha;
SkBlendMode mode = PaintUtils::getBlendModeDirect(op.paint);
TextDrawFunctor functor(&renderer, &state, renderTargetClip, 0.0f, 0.0f, false, alpha, mode,
op.paint);
bool mustDirtyRenderTarget = renderer.offscreenRenderTarget();
const Rect localSpaceClip = state.computedState.computeLocalSpaceClip();
if (fontRenderer.renderTextOnPath(op.paint, &localSpaceClip, op.glyphs, op.glyphCount, op.path,
op.hOffset, op.vOffset,
mustDirtyRenderTarget ? &layerBounds : nullptr, &functor)) {
if (mustDirtyRenderTarget) {
// manually dirty render target, since TextDrawFunctor won't
state.computedState.transform.mapRect(layerBounds);
renderer.dirtyRenderTarget(layerBounds);
}
}
// DEAD CODE
}
void BakedOpDispatcher::onTextureLayerOp(BakedOpRenderer& renderer, const TextureLayerOp& op,

View File

@@ -23,6 +23,8 @@
namespace android {
namespace uirenderer {
class BakedOpRenderer;
/**
* Provides all "onBitmapOp(...)" style static methods for every op type, which convert the
* RecordedOps and their state to Glops, and renders them with the provided BakedOpRenderer.

View File

@@ -25,6 +25,8 @@
namespace android {
namespace uirenderer {
class BakedOpState;
namespace OpClipSideFlags {
enum {
None = 0x0,

View File

@@ -16,14 +16,10 @@
#include "Caches.h"
#include "GammaFontRenderer.h"
#include "GlLayer.h"
#include "Properties.h"
#include "ShadowTessellator.h"
#include "renderstate/RenderState.h"
#ifdef BUGREPORT_FONT_CACHE_USAGE
#include "font/FontCacheHistoryTracker.h"
#endif
#include "utils/GLUtils.h"
#include <cutils/properties.h>
@@ -186,13 +182,9 @@ void Caches::dumpMemoryUsage(String8& log) {
pathCache.getMaxSize());
log.appendFormat(" TessellationCache %8d / %8d\n", tessellationCache.getSize(),
tessellationCache.getMaxSize());
log.appendFormat(" TextDropShadowCache %8d / %8d\n", dropShadowCache.getSize(),
dropShadowCache.getMaxSize());
log.appendFormat(" PatchCache %8d / %8d\n", patchCache.getSize(),
patchCache.getMaxSize());
fontRenderer.dumpMemoryUsage(log);
log.appendFormat("Other:\n");
log.appendFormat(" FboCache %8d / %8d\n", fboCache.getSize(),
fboCache.getMaxSize());
@@ -202,16 +194,10 @@ void Caches::dumpMemoryUsage(String8& log) {
total += gradientCache.getSize();
total += pathCache.getSize();
total += tessellationCache.getSize();
total += dropShadowCache.getSize();
total += patchCache.getSize();
total += fontRenderer.getSize();
log.appendFormat("Total memory usage:\n");
log.appendFormat(" %d bytes, %.2f MB\n", total, total / 1024.0f / 1024.0f);
#ifdef BUGREPORT_FONT_CACHE_USAGE
fontRenderer.getFontRenderer().historyTracker().dump(log);
#endif
}
///////////////////////////////////////////////////////////////////////////////
@@ -230,13 +216,10 @@ void Caches::flush(FlushMode mode) {
case FlushMode::Full:
textureCache.clear();
patchCache.clear();
dropShadowCache.clear();
gradientCache.clear();
fontRenderer.clear();
fboCache.clear();
// fall through
case FlushMode::Moderate:
fontRenderer.flush();
textureCache.flush();
pathCache.clear();
tessellationCache.clear();

View File

@@ -19,7 +19,6 @@
#include "DeviceInfo.h"
#include "Extensions.h"
#include "FboCache.h"
#include "GammaFontRenderer.h"
#include "GradientCache.h"
#include "PatchCache.h"
#include "PathCache.h"
@@ -27,7 +26,6 @@
#include "RenderBufferCache.h"
#include "ResourceCache.h"
#include "TessellationCache.h"
#include "TextDropShadowCache.h"
#include "TextureCache.h"
#include "renderstate/PixelBufferState.h"
#include "renderstate/TextureState.h"
@@ -149,11 +147,8 @@ public:
PathCache pathCache;
ProgramCache programCache;
TessellationCache tessellationCache;
TextDropShadowCache dropShadowCache;
FboCache fboCache;
GammaFontRenderer fontRenderer;
TaskManager tasks;
bool gpuPixelBuffersEnabled;

View File

@@ -50,6 +50,7 @@ class SkRegion;
namespace android {
namespace uirenderer {
struct ClipBase;
class Rect;
class Layer;

View File

@@ -1,802 +0,0 @@
/*
* 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

View File

@@ -1,208 +0,0 @@
/*
* 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.
*/
#pragma once
#include "font/CacheTexture.h"
#include "font/CachedGlyphInfo.h"
#include "font/Font.h"
#include "font/FontUtil.h"
#ifdef BUGREPORT_FONT_CACHE_USAGE
#include "font/FontCacheHistoryTracker.h"
#endif
#include <utils/LruCache.h>
#include <utils/String8.h>
#include <utils/StrongPointer.h>
#include <SkPaint.h>
#include <GLES2/gl2.h>
#include <vector>
#include "RenderScript.h"
namespace RSC {
class Element;
class RS;
class ScriptIntrinsicBlur;
class sp;
}
namespace android {
namespace uirenderer {
class BakedOpState;
class BakedOpRenderer;
struct ClipBase;
class TextDrawFunctor {
public:
TextDrawFunctor(BakedOpRenderer* renderer, const BakedOpState* bakedState, const ClipBase* clip,
float x, float y, bool pureTranslate, int alpha, SkBlendMode mode,
const SkPaint* paint)
: renderer(renderer)
, bakedState(bakedState)
, clip(clip)
, x(x)
, y(y)
, pureTranslate(pureTranslate)
, alpha(alpha)
, mode(mode)
, paint(paint) {}
void draw(CacheTexture& texture, bool linearFiltering);
BakedOpRenderer* renderer;
const BakedOpState* bakedState;
const ClipBase* clip;
float x;
float y;
bool pureTranslate;
int alpha;
SkBlendMode mode;
const SkPaint* paint;
};
class FontRenderer {
public:
explicit FontRenderer(const uint8_t* gammaTable);
~FontRenderer();
void flushLargeCaches(std::vector<CacheTexture*>& cacheTextures);
void flushLargeCaches();
void setFont(const SkPaint* paint, const SkMatrix& matrix);
void precache(const SkPaint* paint, const glyph_t* glyphs, int numGlyphs,
const SkMatrix& matrix);
void endPrecaching();
bool renderPosText(const SkPaint* paint, const Rect* clip, const glyph_t* glyphs, int numGlyphs,
int x, int y, const float* positions, Rect* outBounds,
TextDrawFunctor* functor, bool forceFinish = true);
bool renderTextOnPath(const SkPaint* paint, const Rect* clip, const glyph_t* glyphs,
int numGlyphs, const SkPath* path, float hOffset, float vOffset,
Rect* outBounds, TextDrawFunctor* functor);
struct DropShadow {
uint32_t width;
uint32_t height;
uint8_t* image;
int32_t penX;
int32_t penY;
};
// After renderDropShadow returns, the called owns the memory in DropShadow.image
// and is responsible for releasing it when it's done with it
DropShadow renderDropShadow(const SkPaint* paint, const glyph_t* glyphs, int numGlyphs,
float radius, const float* positions);
void setTextureFiltering(bool linearFiltering) { mLinearFiltering = linearFiltering; }
uint32_t getSize() const;
void dumpMemoryUsage(String8& log) const;
#ifdef BUGREPORT_FONT_CACHE_USAGE
FontCacheHistoryTracker& historyTracker() { return mHistoryTracker; }
#endif
private:
friend class Font;
const uint8_t* mGammaTable;
void allocateTextureMemory(CacheTexture* cacheTexture);
void deallocateTextureMemory(CacheTexture* cacheTexture);
void initTextTexture();
CacheTexture* createCacheTexture(int width, int height, GLenum format, bool allocate);
void cacheBitmap(const SkGlyph& glyph, CachedGlyphInfo* cachedGlyph, uint32_t* retOriginX,
uint32_t* retOriginY, bool precaching);
CacheTexture* cacheBitmapInTexture(std::vector<CacheTexture*>& cacheTextures,
const SkGlyph& glyph, uint32_t* startX, uint32_t* startY);
void flushAllAndInvalidate();
void checkInit();
void initRender(const Rect* clip, Rect* bounds, TextDrawFunctor* functor);
void finishRender();
void issueDrawCommand(std::vector<CacheTexture*>& cacheTextures);
void issueDrawCommand();
void 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);
void 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);
void 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);
void checkTextureUpdate();
void setTextureDirty() { mUploadTexture = true; }
const std::vector<CacheTexture*>& cacheTexturesForFormat(GLenum format) const;
uint32_t getCacheSize(GLenum format) const;
uint32_t getFreeCacheSize(GLenum format) const;
uint32_t mSmallCacheWidth;
uint32_t mSmallCacheHeight;
uint32_t mLargeCacheWidth;
uint32_t mLargeCacheHeight;
std::vector<CacheTexture*> mACacheTextures;
std::vector<CacheTexture*> mRGBACacheTextures;
Font* mCurrentFont;
LruCache<Font::FontDescription, Font*> mActiveFonts;
CacheTexture* mCurrentCacheTexture;
bool mUploadTexture;
TextDrawFunctor* mFunctor;
const Rect* mClip;
Rect* mBounds;
bool mDrawn;
bool mInitialized;
bool mLinearFiltering;
#ifdef BUGREPORT_FONT_CACHE_USAGE
FontCacheHistoryTracker mHistoryTracker;
#endif
// RS constructs
RSC::sp<RSC::RS> mRs;
RSC::sp<const RSC::Element> mRsElement;
RSC::sp<RSC::ScriptIntrinsicBlur> mRsScript;
static void computeGaussianWeights(float* weights, int32_t radius);
static void horizontalBlur(float* weights, int32_t radius, const uint8_t* source, uint8_t* dest,
int32_t width, int32_t height);
static void verticalBlur(float* weights, int32_t radius, const uint8_t* source, uint8_t* dest,
int32_t width, int32_t height);
// the input image handle may have its pointer replaced (to avoid copies)
void blurImage(uint8_t** image, int32_t width, int32_t height, float radius);
};
}; // namespace uirenderer
}; // namespace android

View File

@@ -722,48 +722,12 @@ void FrameBuilder::deferSimpleRectsOp(const SimpleRectsOp& op) {
currentLayer().deferUnmergeableOp(mAllocator, bakedState, OpBatchType::Vertices);
}
static batchid_t textBatchId(const SkPaint& paint) {
// TODO: better handling of shader (since we won't care about color then)
return paint.getColor() == SK_ColorBLACK ? OpBatchType::Text : OpBatchType::ColorText;
}
void FrameBuilder::deferTextOp(const TextOp& op) {
BakedOpState* bakedState = BakedOpState::tryStrokeableOpConstruct(
mAllocator, *mCanvasState.writableSnapshot(), op,
BakedOpState::StrokeBehavior::StyleDefined, false);
if (!bakedState) return; // quick rejected
batchid_t batchId = textBatchId(*(op.paint));
if (bakedState->computedState.transform.isPureTranslate() &&
PaintUtils::getBlendModeDirect(op.paint) == SkBlendMode::kSrcOver &&
hasMergeableClip(*bakedState)) {
mergeid_t mergeId = reinterpret_cast<mergeid_t>(op.paint->getColor());
currentLayer().deferMergeableOp(mAllocator, bakedState, batchId, mergeId);
} else {
currentLayer().deferUnmergeableOp(mAllocator, bakedState, batchId);
}
FontRenderer& fontRenderer = mCaches.fontRenderer.getFontRenderer();
auto& totalTransform = bakedState->computedState.transform;
if (totalTransform.isPureTranslate() || totalTransform.isPerspective()) {
fontRenderer.precache(op.paint, op.glyphs, op.glyphCount, SkMatrix::I());
} else {
// Partial transform case, see BakedOpDispatcher::renderTextOp
float sx, sy;
totalTransform.decomposeScale(sx, sy);
fontRenderer.precache(
op.paint, op.glyphs, op.glyphCount,
SkMatrix::MakeScale(roundf(std::max(1.0f, sx)), roundf(std::max(1.0f, sy))));
}
// DEAD CODE
}
void FrameBuilder::deferTextOnPathOp(const TextOnPathOp& op) {
BakedOpState* bakedState = tryBakeUnboundedOpState(op);
if (!bakedState) return; // quick rejected
currentLayer().deferUnmergeableOp(mAllocator, bakedState, textBatchId(*(op.paint)));
mCaches.fontRenderer.getFontRenderer().precache(op.paint, op.glyphs, op.glyphCount,
SkMatrix::I());
// DEAD CODE
}
void FrameBuilder::deferTextureLayerOp(const TextureLayerOp& op) {
@@ -969,7 +933,7 @@ void FrameBuilder::deferEndUnclippedLayerOp(const EndUnclippedLayerOp& /* ignore
}
void FrameBuilder::finishDefer() {
mCaches.fontRenderer.endPrecaching();
// DEAD CODE
}
} // namespace uirenderer

View File

@@ -1,43 +0,0 @@
/*
* 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 "GammaFontRenderer.h"
#include "Debug.h"
#include "Properties.h"
namespace android {
namespace uirenderer {
GammaFontRenderer::GammaFontRenderer() {
INIT_LOGD("Creating lookup gamma font renderer");
#ifndef ANDROID_ENABLE_LINEAR_BLENDING
// Compute the gamma tables
const float gamma = 1.0f / Properties::textGamma;
for (uint32_t i = 0; i <= 255; i++) {
mGammaTable[i] = uint8_t((float)::floor(pow(i / 255.0f, gamma) * 255.0f + 0.5f));
}
#endif
}
void GammaFontRenderer::endPrecaching() {
if (mRenderer) {
mRenderer->endPrecaching();
}
}
}; // namespace uirenderer
}; // namespace android

View File

@@ -1,70 +0,0 @@
/*
* 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.
*/
#ifndef ANDROID_HWUI_GAMMA_FONT_RENDERER_H
#define ANDROID_HWUI_GAMMA_FONT_RENDERER_H
#include "FontRenderer.h"
namespace android {
namespace uirenderer {
class GammaFontRenderer {
public:
GammaFontRenderer();
void clear() { mRenderer.reset(nullptr); }
void flush() {
if (mRenderer) {
mRenderer->flushLargeCaches();
}
}
FontRenderer& getFontRenderer() {
if (!mRenderer) {
const uint8_t* table = nullptr;
#ifndef ANDROID_ENABLE_LINEAR_BLENDING
table = &mGammaTable[0];
#endif
mRenderer.reset(new FontRenderer(table));
}
return *mRenderer;
}
void dumpMemoryUsage(String8& log) const {
if (mRenderer) {
mRenderer->dumpMemoryUsage(log);
} else {
log.appendFormat("FontRenderer doesn't exist.\n");
}
}
uint32_t getSize() const { return mRenderer ? mRenderer->getSize() : 0; }
void endPrecaching();
private:
std::unique_ptr<FontRenderer> mRenderer;
#ifndef ANDROID_ENABLE_LINEAR_BLENDING
uint8_t mGammaTable[256];
#endif
};
}; // namespace uirenderer
}; // namespace android
#endif // ANDROID_HWUI_GAMMA_FONT_RENDERER_H

View File

@@ -381,24 +381,7 @@ GlopBuilder& GlopBuilder::setFillPathTexturePaint(PathTexture& texture, const Sk
GlopBuilder& GlopBuilder::setFillShadowTexturePaint(ShadowTexture& texture, int shadowColor,
const SkPaint& paint, float alphaScale) {
TRIGGER_STAGE(kFillStage);
REQUIRE_STAGES(kMeshStage | kRoundRectClipStage);
// specify invalid filter/clamp, since these are always static for ShadowTextures
mOutGlop->fill.texture = {&texture, GL_INVALID_ENUM, GL_INVALID_ENUM, nullptr};
const int ALPHA_BITMASK = SK_ColorBLACK;
const int COLOR_BITMASK = ~ALPHA_BITMASK;
if ((shadowColor & ALPHA_BITMASK) == ALPHA_BITMASK) {
// shadow color is fully opaque: override its alpha with that of paint
shadowColor &= paint.getColor() | COLOR_BITMASK;
}
setFill(shadowColor, alphaScale, paint.getBlendMode(), Blend::ModeOrderSwap::NoSwap,
paint.getShader(), paint.getColorFilter());
mDescription.hasAlpha8Texture = true;
mDescription.modulate = mOutGlop->fill.color.isNotBlack();
// DEAD CODE
return *this;
}

View File

@@ -22,7 +22,6 @@
#include "RenderNode.h"
#include "TessellationCache.h"
#include "Vector.h"
#include "font/FontUtil.h"
#include "utils/LinearAllocator.h"
#include "utils/PaintUtils.h"
@@ -38,6 +37,7 @@ struct ClipBase;
class OffscreenBuffer;
class RenderNode;
class DeferredLayerUpdater;
typedef uint16_t glyph_t;
struct Vertex;

View File

@@ -1,209 +0,0 @@
/*
* 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 <utils/JenkinsHash.h>
#include "Caches.h"
#include "Debug.h"
#include "FontRenderer.h"
#include "Properties.h"
#include "TextDropShadowCache.h"
namespace android {
namespace uirenderer {
///////////////////////////////////////////////////////////////////////////////
// Cache support
///////////////////////////////////////////////////////////////////////////////
hash_t ShadowText::hash() const {
uint32_t hash = JenkinsHashMix(0, glyphCount);
hash = JenkinsHashMix(hash, android::hash_type(radius));
hash = JenkinsHashMix(hash, android::hash_type(textSize));
hash = JenkinsHashMix(hash, android::hash_type(typeface));
hash = JenkinsHashMix(hash, flags);
hash = JenkinsHashMix(hash, android::hash_type(italicStyle));
hash = JenkinsHashMix(hash, android::hash_type(scaleX));
if (glyphs) {
hash = JenkinsHashMixShorts(hash, reinterpret_cast<const uint16_t*>(glyphs), glyphCount);
}
if (positions) {
for (uint32_t i = 0; i < glyphCount * 2; i++) {
hash = JenkinsHashMix(hash, android::hash_type(positions[i]));
}
}
return JenkinsHashWhiten(hash);
}
int ShadowText::compare(const ShadowText& lhs, const ShadowText& rhs) {
int deltaInt = int(lhs.glyphCount) - int(rhs.glyphCount);
if (deltaInt != 0) return deltaInt;
deltaInt = lhs.flags - rhs.flags;
if (deltaInt != 0) return deltaInt;
if (lhs.radius < rhs.radius) return -1;
if (lhs.radius > rhs.radius) return +1;
if (lhs.typeface < rhs.typeface) return -1;
if (lhs.typeface > rhs.typeface) return +1;
if (lhs.textSize < rhs.textSize) return -1;
if (lhs.textSize > rhs.textSize) return +1;
if (lhs.italicStyle < rhs.italicStyle) return -1;
if (lhs.italicStyle > rhs.italicStyle) return +1;
if (lhs.scaleX < rhs.scaleX) return -1;
if (lhs.scaleX > rhs.scaleX) return +1;
if (lhs.glyphs != rhs.glyphs) {
if (!lhs.glyphs) return -1;
if (!rhs.glyphs) return +1;
deltaInt = memcmp(lhs.glyphs, rhs.glyphs, lhs.glyphCount * sizeof(glyph_t));
if (deltaInt != 0) return deltaInt;
}
if (lhs.positions != rhs.positions) {
if (!lhs.positions) return -1;
if (!rhs.positions) return +1;
return memcmp(lhs.positions, rhs.positions, lhs.glyphCount * sizeof(float) * 2);
}
return 0;
}
///////////////////////////////////////////////////////////////////////////////
// Constructors/destructor
///////////////////////////////////////////////////////////////////////////////
TextDropShadowCache::TextDropShadowCache()
: TextDropShadowCache(DeviceInfo::multiplyByResolution(2)) {}
TextDropShadowCache::TextDropShadowCache(uint32_t maxByteSize)
: mCache(LruCache<ShadowText, ShadowTexture*>::kUnlimitedCapacity)
, mSize(0)
, mMaxSize(maxByteSize) {
mCache.setOnEntryRemovedListener(this);
mDebugEnabled = Properties::debugLevel & kDebugMoreCaches;
}
TextDropShadowCache::~TextDropShadowCache() {
mCache.clear();
}
///////////////////////////////////////////////////////////////////////////////
// Size management
///////////////////////////////////////////////////////////////////////////////
uint32_t TextDropShadowCache::getSize() {
return mSize;
}
uint32_t TextDropShadowCache::getMaxSize() {
return mMaxSize;
}
///////////////////////////////////////////////////////////////////////////////
// Callbacks
///////////////////////////////////////////////////////////////////////////////
void TextDropShadowCache::operator()(ShadowText&, ShadowTexture*& texture) {
if (texture) {
mSize -= texture->objectSize();
if (mDebugEnabled) {
ALOGD("Shadow texture deleted, size = %d", texture->bitmapSize);
}
texture->deleteTexture();
delete texture;
}
}
///////////////////////////////////////////////////////////////////////////////
// Caching
///////////////////////////////////////////////////////////////////////////////
void TextDropShadowCache::clear() {
mCache.clear();
}
ShadowTexture* TextDropShadowCache::get(const SkPaint* paint, const glyph_t* glyphs, int numGlyphs,
float radius, const float* positions) {
ShadowText entry(paint, radius, numGlyphs, glyphs, positions);
ShadowTexture* texture = mCache.get(entry);
if (!texture) {
SkPaint paintCopy(*paint);
paintCopy.setTextAlign(SkPaint::kLeft_Align);
FontRenderer::DropShadow shadow =
mRenderer->renderDropShadow(&paintCopy, glyphs, numGlyphs, radius, positions);
if (!shadow.image) {
return nullptr;
}
Caches& caches = Caches::getInstance();
texture = new ShadowTexture(caches);
texture->left = shadow.penX;
texture->top = shadow.penY;
texture->generation = 0;
texture->blend = true;
const uint32_t size = shadow.width * shadow.height;
// Don't even try to cache a bitmap that's bigger than the cache
if (size < mMaxSize) {
while (mSize + size > mMaxSize) {
LOG_ALWAYS_FATAL_IF(!mCache.removeOldest(),
"Failed to remove oldest from cache. mSize = %" PRIu32
", mCache.size() = %zu",
mSize, mCache.size());
}
}
// Textures are Alpha8
texture->upload(GL_ALPHA, shadow.width, shadow.height, GL_ALPHA, GL_UNSIGNED_BYTE,
shadow.image);
texture->setFilter(GL_LINEAR);
texture->setWrap(GL_CLAMP_TO_EDGE);
if (size < mMaxSize) {
if (mDebugEnabled) {
ALOGD("Shadow texture created, size = %d", texture->bitmapSize);
}
entry.copyTextLocally();
mSize += texture->objectSize();
mCache.put(entry, texture);
} else {
texture->cleanup = true;
}
// Cleanup shadow
free(shadow.image);
}
return texture;
}
}; // namespace uirenderer
}; // namespace android

View File

@@ -1,164 +0,0 @@
/*
* 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.
*/
#ifndef ANDROID_HWUI_TEXT_DROP_SHADOW_CACHE_H
#define ANDROID_HWUI_TEXT_DROP_SHADOW_CACHE_H
#include <GLES2/gl2.h>
#include <SkPaint.h>
#include <utils/LruCache.h>
#include <utils/String16.h>
#include "Texture.h"
#include "font/Font.h"
namespace android {
namespace uirenderer {
class Caches;
class FontRenderer;
struct ShadowText {
ShadowText()
: glyphCount(0)
, radius(0.0f)
, textSize(0.0f)
, typeface(nullptr)
, flags(0)
, italicStyle(0.0f)
, scaleX(0)
, glyphs(nullptr)
, positions(nullptr) {}
// len is the number of bytes in text
ShadowText(const SkPaint* paint, float radius, uint32_t glyphCount, const glyph_t* srcGlyphs,
const float* positions)
: glyphCount(glyphCount)
, radius(radius)
, textSize(paint->getTextSize())
, typeface(paint->getTypeface())
, flags(paint->isFakeBoldText() ? Font::kFakeBold : 0)
, italicStyle(paint->getTextSkewX())
, scaleX(paint->getTextScaleX())
, glyphs(srcGlyphs)
, positions(positions) {}
~ShadowText() {}
hash_t hash() const;
static int compare(const ShadowText& lhs, const ShadowText& rhs);
bool operator==(const ShadowText& other) const { return compare(*this, other) == 0; }
bool operator!=(const ShadowText& other) const { return compare(*this, other) != 0; }
void copyTextLocally() {
str.setTo(reinterpret_cast<const char16_t*>(glyphs), glyphCount);
glyphs = reinterpret_cast<const glyph_t*>(str.string());
if (positions != nullptr) {
positionsCopy.clear();
positionsCopy.appendArray(positions, glyphCount * 2);
positions = positionsCopy.array();
}
}
uint32_t glyphCount;
float radius;
float textSize;
SkTypeface* typeface;
uint32_t flags;
float italicStyle;
float scaleX;
const glyph_t* glyphs;
const float* positions;
// Not directly used to compute the cache key
String16 str;
Vector<float> positionsCopy;
}; // struct ShadowText
// Caching support
inline int strictly_order_type(const ShadowText& lhs, const ShadowText& rhs) {
return ShadowText::compare(lhs, rhs) < 0;
}
inline int compare_type(const ShadowText& lhs, const ShadowText& rhs) {
return ShadowText::compare(lhs, rhs);
}
inline hash_t hash_type(const ShadowText& entry) {
return entry.hash();
}
/**
* Alpha texture used to represent a shadow.
*/
struct ShadowTexture : public Texture {
explicit ShadowTexture(Caches& caches) : Texture(caches) {}
float left;
float top;
}; // struct ShadowTexture
class TextDropShadowCache : public OnEntryRemoved<ShadowText, ShadowTexture*> {
public:
TextDropShadowCache();
explicit TextDropShadowCache(uint32_t maxByteSize);
~TextDropShadowCache();
/**
* Used as a callback when an entry is removed from the cache.
* Do not invoke directly.
*/
void operator()(ShadowText& text, ShadowTexture*& texture) override;
ShadowTexture* get(const SkPaint* paint, const glyph_t* text, int numGlyphs, float radius,
const float* positions);
/**
* Clears the cache. This causes all textures to be deleted.
*/
void clear();
void setFontRenderer(FontRenderer& fontRenderer) { mRenderer = &fontRenderer; }
/**
* Returns the maximum size of the cache in bytes.
*/
uint32_t getMaxSize();
/**
* Returns the current size of the cache in bytes.
*/
uint32_t getSize();
private:
LruCache<ShadowText, ShadowTexture*> mCache;
uint32_t mSize;
const uint32_t mMaxSize;
FontRenderer* mRenderer = nullptr;
bool mDebugEnabled;
}; // class TextDropShadowCache
}; // namespace uirenderer
}; // namespace android
#endif // ANDROID_HWUI_TEXT_DROP_SHADOW_CACHE_H

View File

@@ -1,355 +0,0 @@
/*
* Copyright (C) 2012 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 <SkGlyph.h>
#include "../Caches.h"
#include "../Debug.h"
#include "../Extensions.h"
#include "../PixelBuffer.h"
#include "CacheTexture.h"
#include "FontUtil.h"
namespace android {
namespace uirenderer {
///////////////////////////////////////////////////////////////////////////////
// CacheBlock
///////////////////////////////////////////////////////////////////////////////
/**
* Insert new block into existing linked list of blocks. Blocks are sorted in increasing-width
* order, except for the final block (the remainder space at the right, since we fill from the
* left).
*/
CacheBlock* CacheBlock::insertBlock(CacheBlock* head, CacheBlock* newBlock) {
#if DEBUG_FONT_RENDERER
ALOGD("insertBlock: this, x, y, w, h = %p, %d, %d, %d, %d", newBlock, newBlock->mX,
newBlock->mY, newBlock->mWidth, newBlock->mHeight);
#endif
CacheBlock* currBlock = head;
CacheBlock* prevBlock = nullptr;
while (currBlock && currBlock->mY != TEXTURE_BORDER_SIZE) {
if (newBlock->mWidth < currBlock->mWidth) {
newBlock->mNext = currBlock;
newBlock->mPrev = prevBlock;
currBlock->mPrev = newBlock;
if (prevBlock) {
prevBlock->mNext = newBlock;
return head;
} else {
return newBlock;
}
}
prevBlock = currBlock;
currBlock = currBlock->mNext;
}
// new block larger than all others - insert at end (but before the remainder space, if there)
newBlock->mNext = currBlock;
newBlock->mPrev = prevBlock;
if (currBlock) {
currBlock->mPrev = newBlock;
}
if (prevBlock) {
prevBlock->mNext = newBlock;
return head;
} else {
return newBlock;
}
}
CacheBlock* CacheBlock::removeBlock(CacheBlock* head, CacheBlock* blockToRemove) {
#if DEBUG_FONT_RENDERER
ALOGD("removeBlock: this, x, y, w, h = %p, %d, %d, %d, %d", blockToRemove, blockToRemove->mX,
blockToRemove->mY, blockToRemove->mWidth, blockToRemove->mHeight);
#endif
CacheBlock* newHead = head;
CacheBlock* nextBlock = blockToRemove->mNext;
CacheBlock* prevBlock = blockToRemove->mPrev;
if (prevBlock) {
// If this doesn't hold, we have a use-after-free below.
LOG_ALWAYS_FATAL_IF(head == blockToRemove,
"removeBlock: head should not have a previous block");
prevBlock->mNext = nextBlock;
} else {
newHead = nextBlock;
}
if (nextBlock) {
nextBlock->mPrev = prevBlock;
}
delete blockToRemove;
return newHead;
}
///////////////////////////////////////////////////////////////////////////////
// CacheTexture
///////////////////////////////////////////////////////////////////////////////
CacheTexture::CacheTexture(uint16_t width, uint16_t height, GLenum format, uint32_t maxQuadCount)
: mTexture(Caches::getInstance())
, mWidth(width)
, mHeight(height)
, mFormat(format)
, mMaxQuadCount(maxQuadCount)
, mCaches(Caches::getInstance()) {
mTexture.blend = true;
mCacheBlocks =
new CacheBlock(TEXTURE_BORDER_SIZE, TEXTURE_BORDER_SIZE,
getWidth() - TEXTURE_BORDER_SIZE, getHeight() - TEXTURE_BORDER_SIZE);
// OpenGL ES 3.0+ lets us specify the row length for unpack operations such
// as glTexSubImage2D(). This allows us to upload a sub-rectangle of a texture.
// With OpenGL ES 2.0 we have to upload entire stripes instead.
mHasUnpackRowLength = mCaches.extensions().hasUnpackRowLength();
}
CacheTexture::~CacheTexture() {
releaseMesh();
releasePixelBuffer();
reset();
}
void CacheTexture::reset() {
// Delete existing cache blocks
while (mCacheBlocks != nullptr) {
CacheBlock* tmpBlock = mCacheBlocks;
mCacheBlocks = mCacheBlocks->mNext;
delete tmpBlock;
}
mNumGlyphs = 0;
mCurrentQuad = 0;
}
void CacheTexture::init() {
// reset, then create a new remainder space to start again
reset();
mCacheBlocks =
new CacheBlock(TEXTURE_BORDER_SIZE, TEXTURE_BORDER_SIZE,
getWidth() - TEXTURE_BORDER_SIZE, getHeight() - TEXTURE_BORDER_SIZE);
}
void CacheTexture::releaseMesh() {
delete[] mMesh;
}
void CacheTexture::releasePixelBuffer() {
if (mPixelBuffer) {
delete mPixelBuffer;
mPixelBuffer = nullptr;
}
mTexture.deleteTexture();
mDirty = false;
mCurrentQuad = 0;
}
void CacheTexture::setLinearFiltering(bool linearFiltering) {
mTexture.setFilter(linearFiltering ? GL_LINEAR : GL_NEAREST);
}
void CacheTexture::allocateMesh() {
if (!mMesh) {
mMesh = new TextureVertex[mMaxQuadCount * 4];
}
}
void CacheTexture::allocatePixelBuffer() {
if (!mPixelBuffer) {
mPixelBuffer = PixelBuffer::create(mFormat, getWidth(), getHeight());
}
GLint internalFormat = mFormat;
if (mFormat == GL_RGBA) {
internalFormat = mCaches.rgbaInternalFormat();
}
mTexture.resize(mWidth, mHeight, internalFormat, mFormat);
mTexture.setFilter(getLinearFiltering() ? GL_LINEAR : GL_NEAREST);
mTexture.setWrap(GL_CLAMP_TO_EDGE);
}
bool CacheTexture::upload() {
const Rect& dirtyRect = mDirtyRect;
// align the x direction to 32 and y direction to 4 for better performance
uint32_t x = (((uint32_t)dirtyRect.left) & (~0x1F));
uint32_t y = (((uint32_t)dirtyRect.top) & (~0x3));
uint32_t r = ((((uint32_t)dirtyRect.right) + 0x1F) & (~0x1F)) - x;
uint32_t b = ((((uint32_t)dirtyRect.bottom) + 0x3) & (~0x3)) - y;
uint32_t width = (r > getWidth() ? getWidth() : r);
uint32_t height = (b > getHeight() ? getHeight() : b);
// The unpack row length only needs to be specified when a new
// texture is bound
if (mHasUnpackRowLength) {
glPixelStorei(GL_UNPACK_ROW_LENGTH, getWidth());
} else {
x = 0;
width = getWidth();
}
mPixelBuffer->upload(x, y, width, height);
setDirty(false);
return mHasUnpackRowLength;
}
void CacheTexture::setDirty(bool dirty) {
mDirty = dirty;
if (!dirty) {
mDirtyRect.setEmpty();
}
}
bool CacheTexture::fitBitmap(const SkGlyph& glyph, uint32_t* retOriginX, uint32_t* retOriginY) {
switch (glyph.fMaskFormat) {
case SkMask::kA8_Format:
case SkMask::kBW_Format:
if (mFormat != GL_ALPHA) {
#if DEBUG_FONT_RENDERER
ALOGD("fitBitmap: texture format %x is inappropriate for monochromatic glyphs",
mFormat);
#endif
return false;
}
break;
case SkMask::kARGB32_Format:
if (mFormat != GL_RGBA) {
#if DEBUG_FONT_RENDERER
ALOGD("fitBitmap: texture format %x is inappropriate for colour glyphs", mFormat);
#endif
return false;
}
break;
default:
#if DEBUG_FONT_RENDERER
ALOGD("fitBitmap: unknown glyph format %x encountered", glyph.fMaskFormat);
#endif
return false;
}
if (glyph.fHeight + TEXTURE_BORDER_SIZE * 2 > getHeight()) {
return false;
}
uint16_t glyphW = glyph.fWidth + TEXTURE_BORDER_SIZE;
uint16_t glyphH = glyph.fHeight + TEXTURE_BORDER_SIZE;
// roundedUpW equals glyphW to the next multiple of CACHE_BLOCK_ROUNDING_SIZE.
// This columns for glyphs that are close but not necessarily exactly the same size. It trades
// off the loss of a few pixels for some glyphs against the ability to store more glyphs
// of varying sizes in one block.
uint16_t roundedUpW = (glyphW + CACHE_BLOCK_ROUNDING_SIZE - 1) & -CACHE_BLOCK_ROUNDING_SIZE;
CacheBlock* cacheBlock = mCacheBlocks;
while (cacheBlock) {
// Store glyph in this block iff: it fits the block's remaining space and:
// it's the remainder space (mY == 0) or there's only enough height for this one glyph
// or it's within ROUNDING_SIZE of the block width
if (roundedUpW <= cacheBlock->mWidth && glyphH <= cacheBlock->mHeight &&
(cacheBlock->mY == TEXTURE_BORDER_SIZE ||
(cacheBlock->mWidth - roundedUpW < CACHE_BLOCK_ROUNDING_SIZE))) {
if (cacheBlock->mHeight - glyphH < glyphH) {
// Only enough space for this glyph - don't bother rounding up the width
roundedUpW = glyphW;
}
*retOriginX = cacheBlock->mX;
*retOriginY = cacheBlock->mY;
// If this is the remainder space, create a new cache block for this column. Otherwise,
// adjust the info about this column.
if (cacheBlock->mY == TEXTURE_BORDER_SIZE) {
uint16_t oldX = cacheBlock->mX;
// Adjust remainder space dimensions
cacheBlock->mWidth -= roundedUpW;
cacheBlock->mX += roundedUpW;
if (getHeight() - glyphH >= glyphH) {
// There's enough height left over to create a new CacheBlock
CacheBlock* newBlock =
new CacheBlock(oldX, glyphH + TEXTURE_BORDER_SIZE, roundedUpW,
getHeight() - glyphH - TEXTURE_BORDER_SIZE);
#if DEBUG_FONT_RENDERER
ALOGD("fitBitmap: Created new block: this, x, y, w, h = %p, %d, %d, %d, %d",
newBlock, newBlock->mX, newBlock->mY, newBlock->mWidth,
newBlock->mHeight);
#endif
mCacheBlocks = CacheBlock::insertBlock(mCacheBlocks, newBlock);
}
} else {
// Insert into current column and adjust column dimensions
cacheBlock->mY += glyphH;
cacheBlock->mHeight -= glyphH;
#if DEBUG_FONT_RENDERER
ALOGD("fitBitmap: Added to existing block: this, x, y, w, h = %p, %d, %d, %d, %d",
cacheBlock, cacheBlock->mX, cacheBlock->mY, cacheBlock->mWidth,
cacheBlock->mHeight);
#endif
}
if (cacheBlock->mHeight < std::min(glyphH, glyphW)) {
// If remaining space in this block is too small to be useful, remove it
mCacheBlocks = CacheBlock::removeBlock(mCacheBlocks, cacheBlock);
}
mDirty = true;
const Rect r(*retOriginX - TEXTURE_BORDER_SIZE, *retOriginY - TEXTURE_BORDER_SIZE,
*retOriginX + glyphW, *retOriginY + glyphH);
mDirtyRect.unionWith(r);
mNumGlyphs++;
#if DEBUG_FONT_RENDERER
ALOGD("fitBitmap: current block list:");
mCacheBlocks->output();
#endif
return true;
}
cacheBlock = cacheBlock->mNext;
}
#if DEBUG_FONT_RENDERER
ALOGD("fitBitmap: returning false for glyph of size %d, %d", glyphW, glyphH);
#endif
return false;
}
uint32_t CacheTexture::calculateFreeMemory() const {
CacheBlock* cacheBlock = mCacheBlocks;
uint32_t free = 0;
// currently only two formats are supported: GL_ALPHA or GL_RGBA;
uint32_t bpp = mFormat == GL_RGBA ? 4 : 1;
while (cacheBlock) {
free += bpp * cacheBlock->mWidth * cacheBlock->mHeight;
cacheBlock = cacheBlock->mNext;
}
return free;
}
}; // namespace uirenderer
}; // namespace android

View File

@@ -1,173 +0,0 @@
/*
* Copyright (C) 2012 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.
*/
#ifndef ANDROID_HWUI_CACHE_TEXTURE_H
#define ANDROID_HWUI_CACHE_TEXTURE_H
#include "PixelBuffer.h"
#include "Rect.h"
#include "Texture.h"
#include "Vertex.h"
#include <GLES3/gl3.h>
#include <SkGlyph.h>
#include <utils/Log.h>
namespace android {
namespace uirenderer {
class Caches;
/**
* CacheBlock is a node in a linked list of current free space areas in a CacheTexture.
* Using CacheBlocks enables us to pack the cache from top to bottom as well as left to right.
* When we add a glyph to the cache, we see if it fits within one of the existing columns that
* have already been started (this is the case if the glyph fits vertically as well as
* horizontally, and if its width is sufficiently close to the column width to avoid
* sub-optimal packing of small glyphs into wide columns). If there is no column in which the
* glyph fits, we check the final node, which is the remaining space in the cache, creating
* a new column as appropriate.
*
* As columns fill up, we remove their CacheBlock from the list to avoid having to check
* small blocks in the future.
*/
struct CacheBlock {
uint16_t mX;
uint16_t mY;
uint16_t mWidth;
uint16_t mHeight;
CacheBlock* mNext;
CacheBlock* mPrev;
CacheBlock(uint16_t x, uint16_t y, uint16_t width, uint16_t height)
: mX(x), mY(y), mWidth(width), mHeight(height), mNext(nullptr), mPrev(nullptr) {}
static CacheBlock* insertBlock(CacheBlock* head, CacheBlock* newBlock);
static CacheBlock* removeBlock(CacheBlock* head, CacheBlock* blockToRemove);
void output() {
CacheBlock* currBlock = this;
while (currBlock) {
ALOGD("Block: this, x, y, w, h = %p, %d, %d, %d, %d", currBlock, currBlock->mX,
currBlock->mY, currBlock->mWidth, currBlock->mHeight);
currBlock = currBlock->mNext;
}
}
};
class CacheTexture {
public:
CacheTexture(uint16_t width, uint16_t height, GLenum format, uint32_t maxQuadCount);
~CacheTexture();
void reset();
void init();
void releaseMesh();
void releasePixelBuffer();
void allocatePixelBuffer();
void allocateMesh();
// Returns true if glPixelStorei(GL_UNPACK_ROW_LENGTH) must be reset
// This method will also call setDirty(false)
bool upload();
bool fitBitmap(const SkGlyph& glyph, uint32_t* retOriginX, uint32_t* retOriginY);
inline uint16_t getWidth() const { return mWidth; }
inline uint16_t getHeight() const { return mHeight; }
inline GLenum getFormat() const { return mFormat; }
inline uint32_t getOffset(uint16_t x, uint16_t y) const {
return (y * getWidth() + x) * PixelBuffer::formatSize(mFormat);
}
inline const Rect* getDirtyRect() const { return &mDirtyRect; }
inline PixelBuffer* getPixelBuffer() const { return mPixelBuffer; }
Texture& getTexture() {
allocatePixelBuffer();
return mTexture;
}
GLuint getTextureId() {
allocatePixelBuffer();
return mTexture.id();
}
inline bool isDirty() const { return mDirty; }
inline bool getLinearFiltering() const { return mLinearFiltering; }
/**
* This method assumes that the proper texture unit is active.
*/
void setLinearFiltering(bool linearFiltering);
inline uint16_t getGlyphCount() const { return mNumGlyphs; }
TextureVertex* mesh() const { return mMesh; }
uint32_t meshElementCount() const { return mCurrentQuad * 6; }
uint16_t* indices() const { return (uint16_t*)nullptr; }
void resetMesh() { mCurrentQuad = 0; }
inline void addQuad(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) {
TextureVertex* mesh = mMesh + mCurrentQuad * 4;
TextureVertex::set(mesh++, x2, y2, u2, v2);
TextureVertex::set(mesh++, x3, y3, u3, v3);
TextureVertex::set(mesh++, x1, y1, u1, v1);
TextureVertex::set(mesh++, x4, y4, u4, v4);
mCurrentQuad++;
}
bool canDraw() const { return mCurrentQuad > 0; }
bool endOfMesh() const { return mCurrentQuad == mMaxQuadCount; }
uint32_t calculateFreeMemory() const;
private:
void setDirty(bool dirty);
PixelBuffer* mPixelBuffer = nullptr;
Texture mTexture;
uint32_t mWidth, mHeight;
GLenum mFormat;
bool mLinearFiltering = false;
bool mDirty = false;
uint16_t mNumGlyphs = 0;
TextureVertex* mMesh = nullptr;
uint32_t mCurrentQuad = 0;
uint32_t mMaxQuadCount;
Caches& mCaches;
CacheBlock* mCacheBlocks;
bool mHasUnpackRowLength;
Rect mDirtyRect;
};
}; // namespace uirenderer
}; // namespace android
#endif // ANDROID_HWUI_CACHE_TEXTURE_H

View File

@@ -1,490 +0,0 @@
/*
* Copyright (C) 2012 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 <cutils/compiler.h>
#include <utils/JenkinsHash.h>
#include <utils/Trace.h>
#include <SkGlyph.h>
#include <SkGlyphCache.h>
#include <SkSurfaceProps.h>
#include <SkUtils.h>
#include "../Debug.h"
#include "../FontRenderer.h"
#include "../PixelBuffer.h"
#include "../Properties.h"
#include "Font.h"
#include "FontUtil.h"
namespace android {
namespace uirenderer {
///////////////////////////////////////////////////////////////////////////////
// Font
///////////////////////////////////////////////////////////////////////////////
Font::Font(FontRenderer* state, const Font::FontDescription& desc)
: mState(state), mDescription(desc) {}
Font::FontDescription::FontDescription(const SkPaint* paint, const SkMatrix& rasterMatrix)
: mLookupTransform(rasterMatrix) {
mFontId = SkTypeface::UniqueID(paint->getTypeface());
mFontSize = paint->getTextSize();
mFlags = 0;
if (paint->isFakeBoldText()) {
mFlags |= Font::kFakeBold;
}
mItalicStyle = paint->getTextSkewX();
mScaleX = paint->getTextScaleX();
mStyle = paint->getStyle();
mStrokeWidth = paint->getStrokeWidth();
mAntiAliasing = paint->isAntiAlias();
mHinting = paint->getHinting();
if (!mLookupTransform.invert(&mInverseLookupTransform)) {
ALOGW("Could not query the inverse lookup transform for this font");
}
}
Font::~Font() {
for (uint32_t i = 0; i < mCachedGlyphs.size(); i++) {
delete mCachedGlyphs.valueAt(i);
}
}
hash_t Font::FontDescription::hash() const {
uint32_t hash = JenkinsHashMix(0, mFontId);
hash = JenkinsHashMix(hash, android::hash_type(mFontSize));
hash = JenkinsHashMix(hash, android::hash_type(mFlags));
hash = JenkinsHashMix(hash, android::hash_type(mItalicStyle));
hash = JenkinsHashMix(hash, android::hash_type(mScaleX));
hash = JenkinsHashMix(hash, android::hash_type(mStyle));
hash = JenkinsHashMix(hash, android::hash_type(mStrokeWidth));
hash = JenkinsHashMix(hash, int(mAntiAliasing));
hash = JenkinsHashMix(hash, android::hash_type(mHinting));
hash = JenkinsHashMix(hash, android::hash_type(mLookupTransform[SkMatrix::kMScaleX]));
hash = JenkinsHashMix(hash, android::hash_type(mLookupTransform[SkMatrix::kMScaleY]));
return JenkinsHashWhiten(hash);
}
int Font::FontDescription::compare(const Font::FontDescription& lhs,
const Font::FontDescription& rhs) {
int deltaInt = int(lhs.mFontId) - int(rhs.mFontId);
if (deltaInt != 0) return deltaInt;
if (lhs.mFontSize < rhs.mFontSize) return -1;
if (lhs.mFontSize > rhs.mFontSize) return +1;
if (lhs.mItalicStyle < rhs.mItalicStyle) return -1;
if (lhs.mItalicStyle > rhs.mItalicStyle) return +1;
deltaInt = int(lhs.mFlags) - int(rhs.mFlags);
if (deltaInt != 0) return deltaInt;
if (lhs.mScaleX < rhs.mScaleX) return -1;
if (lhs.mScaleX > rhs.mScaleX) return +1;
deltaInt = int(lhs.mStyle) - int(rhs.mStyle);
if (deltaInt != 0) return deltaInt;
if (lhs.mStrokeWidth < rhs.mStrokeWidth) return -1;
if (lhs.mStrokeWidth > rhs.mStrokeWidth) return +1;
deltaInt = int(lhs.mAntiAliasing) - int(rhs.mAntiAliasing);
if (deltaInt != 0) return deltaInt;
deltaInt = int(lhs.mHinting) - int(rhs.mHinting);
if (deltaInt != 0) return deltaInt;
if (lhs.mLookupTransform[SkMatrix::kMScaleX] < rhs.mLookupTransform[SkMatrix::kMScaleX])
return -1;
if (lhs.mLookupTransform[SkMatrix::kMScaleX] > rhs.mLookupTransform[SkMatrix::kMScaleX])
return +1;
if (lhs.mLookupTransform[SkMatrix::kMScaleY] < rhs.mLookupTransform[SkMatrix::kMScaleY])
return -1;
if (lhs.mLookupTransform[SkMatrix::kMScaleY] > rhs.mLookupTransform[SkMatrix::kMScaleY])
return +1;
return 0;
}
void Font::invalidateTextureCache(CacheTexture* cacheTexture) {
for (uint32_t i = 0; i < mCachedGlyphs.size(); i++) {
CachedGlyphInfo* cachedGlyph = mCachedGlyphs.valueAt(i);
if (!cacheTexture || cachedGlyph->mCacheTexture == cacheTexture) {
cachedGlyph->mIsValid = false;
}
}
}
void Font::measureCachedGlyph(CachedGlyphInfo* glyph, int x, int y, uint8_t* bitmap,
uint32_t bitmapW, uint32_t bitmapH, Rect* bounds, const float* pos) {
int width = (int)glyph->mBitmapWidth;
int height = (int)glyph->mBitmapHeight;
int nPenX = x + glyph->mBitmapLeft;
int nPenY = y + glyph->mBitmapTop;
if (bounds->bottom > nPenY) {
bounds->bottom = nPenY;
}
if (bounds->left > nPenX) {
bounds->left = nPenX;
}
if (bounds->right < nPenX + width) {
bounds->right = nPenX + width;
}
if (bounds->top < nPenY + height) {
bounds->top = nPenY + height;
}
}
void Font::drawCachedGlyph(CachedGlyphInfo* glyph, int x, int y, uint8_t* bitmap, uint32_t bitmapW,
uint32_t bitmapH, Rect* bounds, const float* pos) {
float width = (float)glyph->mBitmapWidth;
float height = (float)glyph->mBitmapHeight;
float nPenX = x + glyph->mBitmapLeft;
float nPenY = y + glyph->mBitmapTop + height;
float u1 = glyph->mBitmapMinU;
float u2 = glyph->mBitmapMaxU;
float v1 = glyph->mBitmapMinV;
float v2 = glyph->mBitmapMaxV;
mState->appendMeshQuad(nPenX, nPenY, u1, v2, nPenX + width, nPenY, u2, v2, nPenX + width,
nPenY - height, u2, v1, nPenX, nPenY - height, u1, v1,
glyph->mCacheTexture);
}
void Font::drawCachedGlyphTransformed(CachedGlyphInfo* glyph, int x, int y, uint8_t* bitmap,
uint32_t bitmapW, uint32_t bitmapH, Rect* bounds,
const float* pos) {
float width = (float)glyph->mBitmapWidth;
float height = (float)glyph->mBitmapHeight;
SkPoint p[4];
p[0].iset(glyph->mBitmapLeft, glyph->mBitmapTop + height);
p[1].iset(glyph->mBitmapLeft + width, glyph->mBitmapTop + height);
p[2].iset(glyph->mBitmapLeft + width, glyph->mBitmapTop);
p[3].iset(glyph->mBitmapLeft, glyph->mBitmapTop);
mDescription.mInverseLookupTransform.mapPoints(p, 4);
p[0].offset(x, y);
p[1].offset(x, y);
p[2].offset(x, y);
p[3].offset(x, y);
float u1 = glyph->mBitmapMinU;
float u2 = glyph->mBitmapMaxU;
float v1 = glyph->mBitmapMinV;
float v2 = glyph->mBitmapMaxV;
mState->appendRotatedMeshQuad(p[0].x(), p[0].y(), u1, v2, p[1].x(), p[1].y(), u2, v2, p[2].x(),
p[2].y(), u2, v1, p[3].x(), p[3].y(), u1, v1,
glyph->mCacheTexture);
}
void Font::drawCachedGlyphBitmap(CachedGlyphInfo* glyph, int x, int y, uint8_t* bitmap,
uint32_t bitmapWidth, uint32_t bitmapHeight, Rect* bounds,
const float* pos) {
int dstX = x + glyph->mBitmapLeft;
int dstY = y + glyph->mBitmapTop;
CacheTexture* cacheTexture = glyph->mCacheTexture;
PixelBuffer* pixelBuffer = cacheTexture->getPixelBuffer();
uint32_t formatSize = PixelBuffer::formatSize(pixelBuffer->getFormat());
uint32_t alpha_channel_offset = PixelBuffer::formatAlphaOffset(pixelBuffer->getFormat());
uint32_t cacheWidth = cacheTexture->getWidth();
uint32_t srcStride = formatSize * cacheWidth;
uint32_t startY = glyph->mStartY * srcStride;
uint32_t endY = startY + (glyph->mBitmapHeight * srcStride);
const uint8_t* cacheBuffer = pixelBuffer->map();
for (uint32_t cacheY = startY, bitmapY = dstY * bitmapWidth; cacheY < endY;
cacheY += srcStride, bitmapY += bitmapWidth) {
for (uint32_t i = 0; i < glyph->mBitmapWidth; ++i) {
uint8_t* dst = &(bitmap[bitmapY + dstX + i]);
const uint8_t& src =
cacheBuffer[cacheY + (glyph->mStartX + i) * formatSize + alpha_channel_offset];
// Add alpha values to a max of 255, full opacity. This is done to handle
// fonts/strings where glyphs overlap.
*dst = std::min(*dst + src, 255);
}
}
}
void Font::drawCachedGlyph(CachedGlyphInfo* glyph, float x, float hOffset, float vOffset,
SkPathMeasure& measure, SkPoint* position, SkVector* tangent) {
const float halfWidth = glyph->mBitmapWidth * 0.5f;
const float height = glyph->mBitmapHeight;
vOffset += glyph->mBitmapTop + height;
SkPoint destination[4];
bool ok = measure.getPosTan(x + hOffset + glyph->mBitmapLeft + halfWidth, position, tangent);
if (!ok) {
ALOGW("The path for drawTextOnPath is empty or null");
}
// Move along the tangent and offset by the normal
destination[0].set(-tangent->fX * halfWidth - tangent->fY * vOffset,
-tangent->fY * halfWidth + tangent->fX * vOffset);
destination[1].set(tangent->fX * halfWidth - tangent->fY * vOffset,
tangent->fY * halfWidth + tangent->fX * vOffset);
destination[2].set(destination[1].fX + tangent->fY * height,
destination[1].fY - tangent->fX * height);
destination[3].set(destination[0].fX + tangent->fY * height,
destination[0].fY - tangent->fX * height);
const float u1 = glyph->mBitmapMinU;
const float u2 = glyph->mBitmapMaxU;
const float v1 = glyph->mBitmapMinV;
const float v2 = glyph->mBitmapMaxV;
mState->appendRotatedMeshQuad(
position->x() + destination[0].x(), position->y() + destination[0].y(), u1, v2,
position->x() + destination[1].x(), position->y() + destination[1].y(), u2, v2,
position->x() + destination[2].x(), position->y() + destination[2].y(), u2, v1,
position->x() + destination[3].x(), position->y() + destination[3].y(), u1, v1,
glyph->mCacheTexture);
}
CachedGlyphInfo* Font::getCachedGlyph(const SkPaint* paint, glyph_t textUnit, bool precaching) {
CachedGlyphInfo* cachedGlyph = mCachedGlyphs.valueFor(textUnit);
if (cachedGlyph) {
// Is the glyph still in texture cache?
if (!cachedGlyph->mIsValid) {
SkSurfaceProps surfaceProps(0, kUnknown_SkPixelGeometry);
SkAutoGlyphCacheNoGamma autoCache(*paint, &surfaceProps,
&mDescription.mLookupTransform);
const SkGlyph& skiaGlyph = GET_METRICS(autoCache.get(), textUnit);
updateGlyphCache(paint, skiaGlyph, autoCache.get(), cachedGlyph, precaching);
}
} else {
cachedGlyph = cacheGlyph(paint, textUnit, precaching);
}
return cachedGlyph;
}
void Font::render(const SkPaint* paint, const glyph_t* glyphs, int numGlyphs, int x, int y,
const float* positions) {
render(paint, glyphs, numGlyphs, x, y, FRAMEBUFFER, nullptr, 0, 0, nullptr, positions);
}
void Font::render(const SkPaint* paint, const glyph_t* glyphs, int numGlyphs, const SkPath* path,
float hOffset, float vOffset) {
if (numGlyphs == 0 || glyphs == nullptr) {
return;
}
int glyphsCount = 0;
int prevRsbDelta = 0;
float penX = 0.0f;
SkPoint position;
SkVector tangent;
SkPathMeasure measure(*path, false);
float pathLength = SkScalarToFloat(measure.getLength());
if (paint->getTextAlign() != SkPaint::kLeft_Align) {
float textWidth = SkScalarToFloat(paint->measureText(glyphs, numGlyphs * 2));
float pathOffset = pathLength;
if (paint->getTextAlign() == SkPaint::kCenter_Align) {
textWidth *= 0.5f;
pathOffset *= 0.5f;
}
penX += pathOffset - textWidth;
}
while (glyphsCount < numGlyphs && penX < pathLength) {
glyph_t glyph = *(glyphs++);
if (IS_END_OF_STRING(glyph)) {
break;
}
CachedGlyphInfo* cachedGlyph = getCachedGlyph(paint, glyph);
penX += AUTO_KERN(prevRsbDelta, cachedGlyph->mLsbDelta);
prevRsbDelta = cachedGlyph->mRsbDelta;
if (cachedGlyph->mIsValid && cachedGlyph->mCacheTexture) {
drawCachedGlyph(cachedGlyph, penX, hOffset, vOffset, measure, &position, &tangent);
}
penX += cachedGlyph->mAdvanceX;
glyphsCount++;
}
}
void Font::measure(const SkPaint* paint, const glyph_t* glyphs, int numGlyphs, Rect* bounds,
const float* positions) {
if (bounds == nullptr) {
ALOGE("No return rectangle provided to measure text");
return;
}
bounds->set(1e6, -1e6, -1e6, 1e6);
render(paint, glyphs, numGlyphs, 0, 0, MEASURE, nullptr, 0, 0, bounds, positions);
}
void Font::precache(const SkPaint* paint, const glyph_t* glyphs, int numGlyphs) {
if (numGlyphs == 0 || glyphs == nullptr) {
return;
}
int glyphsCount = 0;
while (glyphsCount < numGlyphs) {
glyph_t glyph = *(glyphs++);
// Reached the end of the string
if (IS_END_OF_STRING(glyph)) {
break;
}
getCachedGlyph(paint, glyph, true);
glyphsCount++;
}
}
void Font::render(const SkPaint* paint, const glyph_t* glyphs, int numGlyphs, int x, int y,
RenderMode mode, uint8_t* bitmap, uint32_t bitmapW, uint32_t bitmapH,
Rect* bounds, const float* positions) {
if (numGlyphs == 0 || glyphs == nullptr) {
return;
}
static RenderGlyph gRenderGlyph[] = {&android::uirenderer::Font::drawCachedGlyph,
&android::uirenderer::Font::drawCachedGlyphTransformed,
&android::uirenderer::Font::drawCachedGlyphBitmap,
&android::uirenderer::Font::drawCachedGlyphBitmap,
&android::uirenderer::Font::measureCachedGlyph,
&android::uirenderer::Font::measureCachedGlyph};
RenderGlyph render = gRenderGlyph[(mode << 1) + !mIdentityTransform];
int glyphsCount = 0;
while (glyphsCount < numGlyphs) {
glyph_t glyph = *(glyphs++);
// Reached the end of the string
if (IS_END_OF_STRING(glyph)) {
break;
}
CachedGlyphInfo* cachedGlyph = getCachedGlyph(paint, glyph);
// If it's still not valid, we couldn't cache it, so we shouldn't
// draw garbage; also skip empty glyphs (spaces)
if (cachedGlyph->mIsValid && cachedGlyph->mCacheTexture) {
int penX = x + (int)roundf(positions[(glyphsCount << 1)]);
int penY = y + (int)roundf(positions[(glyphsCount << 1) + 1]);
#ifdef BUGREPORT_FONT_CACHE_USAGE
mState->historyTracker().glyphRendered(cachedGlyph, penX, penY);
#endif
(*this.*render)(cachedGlyph, penX, penY, bitmap, bitmapW, bitmapH, bounds, positions);
} else {
#ifdef BUGREPORT_FONT_CACHE_USAGE
mState->historyTracker().glyphRendered(cachedGlyph, -1, -1);
#endif
}
glyphsCount++;
}
}
void Font::updateGlyphCache(const SkPaint* paint, const SkGlyph& skiaGlyph,
SkGlyphCache* skiaGlyphCache, CachedGlyphInfo* glyph, bool precaching) {
glyph->mAdvanceX = skiaGlyph.fAdvanceX;
glyph->mAdvanceY = skiaGlyph.fAdvanceY;
glyph->mBitmapLeft = skiaGlyph.fLeft;
glyph->mBitmapTop = skiaGlyph.fTop;
glyph->mLsbDelta = skiaGlyph.fLsbDelta;
glyph->mRsbDelta = skiaGlyph.fRsbDelta;
uint32_t startX = 0;
uint32_t startY = 0;
// Get the bitmap for the glyph
if (!skiaGlyph.fImage) {
skiaGlyphCache->findImage(skiaGlyph);
}
mState->cacheBitmap(skiaGlyph, glyph, &startX, &startY, precaching);
if (!glyph->mIsValid) {
return;
}
uint32_t endX = startX + skiaGlyph.fWidth;
uint32_t endY = startY + skiaGlyph.fHeight;
glyph->mStartX = startX;
glyph->mStartY = startY;
glyph->mBitmapWidth = skiaGlyph.fWidth;
glyph->mBitmapHeight = skiaGlyph.fHeight;
bool empty = skiaGlyph.fWidth == 0 || skiaGlyph.fHeight == 0;
if (!empty) {
uint32_t cacheWidth = glyph->mCacheTexture->getWidth();
uint32_t cacheHeight = glyph->mCacheTexture->getHeight();
glyph->mBitmapMinU = startX / (float)cacheWidth;
glyph->mBitmapMinV = startY / (float)cacheHeight;
glyph->mBitmapMaxU = endX / (float)cacheWidth;
glyph->mBitmapMaxV = endY / (float)cacheHeight;
mState->setTextureDirty();
}
}
CachedGlyphInfo* Font::cacheGlyph(const SkPaint* paint, glyph_t glyph, bool precaching) {
CachedGlyphInfo* newGlyph = new CachedGlyphInfo();
mCachedGlyphs.add(glyph, newGlyph);
SkSurfaceProps surfaceProps(0, kUnknown_SkPixelGeometry);
SkAutoGlyphCacheNoGamma autoCache(*paint, &surfaceProps, &mDescription.mLookupTransform);
const SkGlyph& skiaGlyph = GET_METRICS(autoCache.get(), glyph);
newGlyph->mIsValid = false;
newGlyph->mGlyphIndex = skiaGlyph.fID;
updateGlyphCache(paint, skiaGlyph, autoCache.get(), newGlyph, precaching);
return newGlyph;
}
Font* Font::create(FontRenderer* state, const SkPaint* paint, const SkMatrix& matrix) {
FontDescription description(paint, matrix);
Font* font = state->mActiveFonts.get(description);
if (!font) {
font = new Font(state, description);
state->mActiveFonts.put(description, font);
}
font->mIdentityTransform = matrix.isIdentity();
return font;
}
}; // namespace uirenderer
}; // namespace android

View File

@@ -1,161 +0,0 @@
/*
* Copyright (C) 2012 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.
*/
#ifndef ANDROID_HWUI_FONT_H
#define ANDROID_HWUI_FONT_H
#include <vector>
#include <utils/KeyedVector.h>
#include <SkPaint.h>
#include <SkPathMeasure.h>
#include <SkScalar.h>
#include <SkTypeface.h>
#include "../Matrix.h"
#include "../Rect.h"
#include "FontUtil.h"
class SkGlyphCache;
namespace android {
namespace uirenderer {
///////////////////////////////////////////////////////////////////////////////
// Font
///////////////////////////////////////////////////////////////////////////////
struct CachedGlyphInfo;
class CacheTexture;
class FontRenderer;
/**
* Represents a font, defined by a Skia font id and a font size. A font is used
* to generate glyphs and cache them in the FontState.
*/
class Font {
public:
enum Style { kFakeBold = 1 };
struct FontDescription {
FontDescription(const SkPaint* paint, const SkMatrix& matrix);
static int compare(const FontDescription& lhs, const FontDescription& rhs);
hash_t hash() const;
bool operator==(const FontDescription& other) const { return compare(*this, other) == 0; }
bool operator!=(const FontDescription& other) const { return compare(*this, other) != 0; }
SkFontID mFontId;
float mFontSize;
int mFlags;
float mItalicStyle;
float mScaleX;
uint8_t mStyle;
float mStrokeWidth;
bool mAntiAliasing;
uint8_t mHinting;
SkMatrix mLookupTransform;
SkMatrix mInverseLookupTransform;
};
~Font();
void render(const SkPaint* paint, const glyph_t* glyphs, int numGlyphs, int x, int y,
const float* positions);
void render(const SkPaint* paint, const glyph_t* glyphs, int numGlyphs, const SkPath* path,
float hOffset, float vOffset);
const Font::FontDescription& getDescription() const { return mDescription; }
/**
* Creates a new font associated with the specified font state.
*/
static Font* create(FontRenderer* state, const SkPaint* paint, const SkMatrix& matrix);
private:
friend class FontRenderer;
Font(FontRenderer* state, const Font::FontDescription& desc);
typedef void (Font::*RenderGlyph)(CachedGlyphInfo*, int, int, uint8_t*, uint32_t, uint32_t,
Rect*, const float*);
enum RenderMode {
FRAMEBUFFER,
BITMAP,
MEASURE,
};
void precache(const SkPaint* paint, const glyph_t* glyphs, int numGlyphs);
void render(const SkPaint* paint, const glyph_t* glyphs, int numGlyphs, int x, int y,
RenderMode mode, uint8_t* bitmap, uint32_t bitmapW, uint32_t bitmapH, Rect* bounds,
const float* positions);
void measure(const SkPaint* paint, const glyph_t* glyphs, int numGlyphs, Rect* bounds,
const float* positions);
void invalidateTextureCache(CacheTexture* cacheTexture = nullptr);
CachedGlyphInfo* cacheGlyph(const SkPaint* paint, glyph_t glyph, bool precaching);
void updateGlyphCache(const SkPaint* paint, const SkGlyph& skiaGlyph,
SkGlyphCache* skiaGlyphCache, CachedGlyphInfo* glyph, bool precaching);
void measureCachedGlyph(CachedGlyphInfo* glyph, int x, int y, uint8_t* bitmap, uint32_t bitmapW,
uint32_t bitmapH, Rect* bounds, const float* pos);
void drawCachedGlyph(CachedGlyphInfo* glyph, int x, int y, uint8_t* bitmap, uint32_t bitmapW,
uint32_t bitmapH, Rect* bounds, const float* pos);
void drawCachedGlyphTransformed(CachedGlyphInfo* glyph, int x, int y, uint8_t* bitmap,
uint32_t bitmapW, uint32_t bitmapH, Rect* bounds,
const float* pos);
void drawCachedGlyphBitmap(CachedGlyphInfo* glyph, int x, int y, uint8_t* bitmap,
uint32_t bitmapW, uint32_t bitmapH, Rect* bounds, const float* pos);
void drawCachedGlyph(CachedGlyphInfo* glyph, float x, float hOffset, float vOffset,
SkPathMeasure& measure, SkPoint* position, SkVector* tangent);
CachedGlyphInfo* getCachedGlyph(const SkPaint* paint, glyph_t textUnit,
bool precaching = false);
FontRenderer* mState;
FontDescription mDescription;
// Cache of glyphs
DefaultKeyedVector<glyph_t, CachedGlyphInfo*> mCachedGlyphs;
bool mIdentityTransform;
};
inline int strictly_order_type(const Font::FontDescription& lhs, const Font::FontDescription& rhs) {
return Font::FontDescription::compare(lhs, rhs) < 0;
}
inline int compare_type(const Font::FontDescription& lhs, const Font::FontDescription& rhs) {
return Font::FontDescription::compare(lhs, rhs);
}
inline hash_t hash_type(const Font::FontDescription& entry) {
return entry.hash();
}
}; // namespace uirenderer
}; // namespace android
#endif // ANDROID_HWUI_FONT_H

View File

@@ -1,100 +0,0 @@
/*
* Copyright (C) 2016 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 "FontCacheHistoryTracker.h"
#include "CacheTexture.h"
#include "CachedGlyphInfo.h"
namespace android {
namespace uirenderer {
void FontCacheHistoryTracker::dumpCachedGlyph(String8& log, const CachedGlyph& glyph) {
log.appendFormat("glyph (texture %p, position: (%d, %d), size: %dx%d, gen: %d)", glyph.texture,
glyph.startX, glyph.startY, glyph.bitmapW, glyph.bitmapH, glyph.generation);
}
void FontCacheHistoryTracker::dumpRenderEntry(String8& log, const RenderEntry& entry) {
if (entry.penX == -1 && entry.penY == -1) {
log.appendFormat(" glyph skipped in gen: %d\n", entry.glyph.generation);
} else {
log.appendFormat(" rendered ");
dumpCachedGlyph(log, entry.glyph);
log.appendFormat(" at (%d, %d)\n", entry.penX, entry.penY);
}
}
void FontCacheHistoryTracker::dumpUploadEntry(String8& log, const CachedGlyph& glyph) {
if (glyph.bitmapW == 0 && glyph.bitmapH == 0) {
log.appendFormat(" cleared cachetexture %p in gen %d\n", glyph.texture,
glyph.generation);
} else {
log.appendFormat(" uploaded ");
dumpCachedGlyph(log, glyph);
log.appendFormat("\n");
}
}
void FontCacheHistoryTracker::dump(String8& log) const {
log.appendFormat("FontCacheHistory: \n");
log.appendFormat(" Upload history: \n");
for (size_t i = 0; i < mUploadHistory.size(); i++) {
dumpUploadEntry(log, mUploadHistory[i]);
}
log.appendFormat(" Render history: \n");
for (size_t i = 0; i < mRenderHistory.size(); i++) {
dumpRenderEntry(log, mRenderHistory[i]);
}
}
void FontCacheHistoryTracker::glyphRendered(CachedGlyphInfo* glyphInfo, int penX, int penY) {
RenderEntry& entry = mRenderHistory.next();
entry.glyph.generation = generation;
entry.glyph.texture = glyphInfo->mCacheTexture;
entry.glyph.startX = glyphInfo->mStartX;
entry.glyph.startY = glyphInfo->mStartY;
entry.glyph.bitmapW = glyphInfo->mBitmapWidth;
entry.glyph.bitmapH = glyphInfo->mBitmapHeight;
entry.penX = penX;
entry.penY = penY;
}
void FontCacheHistoryTracker::glyphUploaded(CacheTexture* texture, uint32_t x, uint32_t y,
uint16_t glyphW, uint16_t glyphH) {
CachedGlyph& glyph = mUploadHistory.next();
glyph.generation = generation;
glyph.texture = texture;
glyph.startX = x;
glyph.startY = y;
glyph.bitmapW = glyphW;
glyph.bitmapH = glyphH;
}
void FontCacheHistoryTracker::glyphsCleared(CacheTexture* texture) {
CachedGlyph& glyph = mUploadHistory.next();
glyph.generation = generation;
glyph.texture = texture;
glyph.startX = 0;
glyph.startY = 0;
glyph.bitmapW = 0;
glyph.bitmapH = 0;
}
void FontCacheHistoryTracker::frameCompleted() {
generation++;
}
}; // namespace uirenderer
}; // namespace android

View File

@@ -1,65 +0,0 @@
/*
* Copyright (C) 2016 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.
*/
#pragma once
#include "../utils/RingBuffer.h"
#include <utils/String8.h>
namespace android {
namespace uirenderer {
class CacheTexture;
struct CachedGlyphInfo;
// Tracks glyph uploads and recent rendered/skipped glyphs, so it can give an idea
// what a missing character is: skipped glyph, wrong coordinates in cache texture etc.
class FontCacheHistoryTracker {
public:
void glyphRendered(CachedGlyphInfo*, int penX, int penY);
void glyphUploaded(CacheTexture*, uint32_t x, uint32_t y, uint16_t glyphW, uint16_t glyphH);
void glyphsCleared(CacheTexture*);
void frameCompleted();
void dump(String8& log) const;
private:
struct CachedGlyph {
void* texture;
uint16_t generation;
uint16_t startX;
uint16_t startY;
uint16_t bitmapW;
uint16_t bitmapH;
};
struct RenderEntry {
CachedGlyph glyph;
int penX;
int penY;
};
static void dumpCachedGlyph(String8& log, const CachedGlyph& glyph);
static void dumpRenderEntry(String8& log, const RenderEntry& entry);
static void dumpUploadEntry(String8& log, const CachedGlyph& glyph);
RingBuffer<RenderEntry, 300> mRenderHistory;
RingBuffer<CachedGlyph, 120> mUploadHistory;
uint16_t generation = 0;
};
}; // namespace uirenderer
}; // namespace android

View File

@@ -1,46 +0,0 @@
/*
* Copyright (C) 2012 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.
*/
#ifndef ANDROID_HWUI_FONT_UTIL_H
#define ANDROID_HWUI_FONT_UTIL_H
#include <SkUtils.h>
#include "Properties.h"
///////////////////////////////////////////////////////////////////////////////
// Defines
///////////////////////////////////////////////////////////////////////////////
#ifdef TEXTURE_BORDER_SIZE
#if TEXTURE_BORDER_SIZE != 1
#error TEXTURE_BORDER_SIZE other than 1 is not currently supported
#endif
#else
#define TEXTURE_BORDER_SIZE 1
#endif
#define CACHE_BLOCK_ROUNDING_SIZE 4
typedef uint16_t glyph_t;
#define GET_METRICS(cache, glyph) cache->getGlyphIDMetrics(glyph)
#define IS_END_OF_STRING(glyph) false
// prev, next are assumed to be signed x.6 fixed-point numbers with range
// [-1, 1]. Result is an integral float.
#define AUTO_KERN(prev, next) static_cast<float>(((next) - (prev) + 32) >> 6)
#endif // ANDROID_HWUI_FONT_UTIL_H

View File

@@ -1,50 +0,0 @@
/*
* Copyright (C) 2016 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 <benchmark/benchmark.h>
#include "GammaFontRenderer.h"
#include "tests/common/TestUtils.h"
#include <SkPaint.h>
using namespace android;
using namespace android::uirenderer;
void BM_FontRenderer_precache_cachehits(benchmark::State& state) {
TestUtils::runOnRenderThread([&state](renderthread::RenderThread& thread) {
SkPaint paint;
paint.setTextSize(20);
paint.setTextEncoding(SkPaint::kGlyphID_TextEncoding);
GammaFontRenderer gammaFontRenderer;
FontRenderer& fontRenderer = gammaFontRenderer.getFontRenderer();
fontRenderer.setFont(&paint, SkMatrix::I());
std::vector<glyph_t> glyphs;
std::vector<float> positions;
float totalAdvance;
uirenderer::Rect bounds;
TestUtils::layoutTextUnscaled(paint, "This is a test", &glyphs, &positions, &totalAdvance,
&bounds);
fontRenderer.precache(&paint, glyphs.data(), glyphs.size(), SkMatrix::I());
while (state.KeepRunning()) {
fontRenderer.precache(&paint, glyphs.data(), glyphs.size(), SkMatrix::I());
}
});
}
BENCHMARK(BM_FontRenderer_precache_cachehits);