Merge "remove (dead) Font code"
This commit is contained in:
committed by
Android (Google) Code Review
commit
798cc60f54
@@ -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",
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -25,6 +25,8 @@
|
||||
namespace android {
|
||||
namespace uirenderer {
|
||||
|
||||
class BakedOpState;
|
||||
|
||||
namespace OpClipSideFlags {
|
||||
enum {
|
||||
None = 0x0,
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -50,6 +50,7 @@ class SkRegion;
|
||||
namespace android {
|
||||
namespace uirenderer {
|
||||
|
||||
struct ClipBase;
|
||||
class Rect;
|
||||
class Layer;
|
||||
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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);
|
||||
Reference in New Issue
Block a user