blob: c70e822cd9bf6230bc7d86bb5dc4d5a160ab9511 [file] [log] [blame]
/*
* Copyright 2010 Google Inc.
*
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE file.
*/
#include "GrAtlas.h"
#include "GrGpu.h"
#include "GrRectanizer.h"
#include "GrTextStrike.h"
#include "GrTextStrike_impl.h"
#include "SkString.h"
#if SK_DISTANCEFIELD_FONTS
#include "edtaa3.h"
#endif
///////////////////////////////////////////////////////////////////////////////
#define FONT_CACHE_STATS 0
#if FONT_CACHE_STATS
static int g_PurgeCount = 0;
#endif
GrFontCache::GrFontCache(GrGpu* gpu) : fGpu(gpu) {
gpu->ref();
for (int i = 0; i < kAtlasCount; ++i) {
fAtlasMgr[i] = NULL;
}
fHead = fTail = NULL;
}
GrFontCache::~GrFontCache() {
fCache.deleteAll();
for (int i = 0; i < kAtlasCount; ++i) {
delete fAtlasMgr[i];
}
fGpu->unref();
#if FONT_CACHE_STATS
GrPrintf("Num purges: %d\n", g_PurgeCount);
#endif
}
static GrPixelConfig mask_format_to_pixel_config(GrMaskFormat format) {
static const GrPixelConfig sPixelConfigs[] = {
kAlpha_8_GrPixelConfig,
kRGB_565_GrPixelConfig,
kSkia8888_GrPixelConfig,
kSkia8888_GrPixelConfig
};
SK_COMPILE_ASSERT(SK_ARRAY_COUNT(sPixelConfigs) == kMaskFormatCount, array_size_mismatch);
return sPixelConfigs[format];
}
static int mask_format_to_atlas_index(GrMaskFormat format) {
static const int sAtlasIndices[] = {
GrFontCache::kA8_AtlasType,
GrFontCache::k565_AtlasType,
GrFontCache::k8888_AtlasType,
GrFontCache::k8888_AtlasType
};
SK_COMPILE_ASSERT(SK_ARRAY_COUNT(sAtlasIndices) == kMaskFormatCount, array_size_mismatch);
SkASSERT(sAtlasIndices[format] < GrFontCache::kAtlasCount);
return sAtlasIndices[format];
}
GrTextStrike* GrFontCache::generateStrike(GrFontScaler* scaler,
const Key& key) {
GrMaskFormat format = scaler->getMaskFormat();
GrPixelConfig config = mask_format_to_pixel_config(format);
int atlasIndex = mask_format_to_atlas_index(format);
if (NULL == fAtlasMgr[atlasIndex]) {
fAtlasMgr[atlasIndex] = SkNEW_ARGS(GrAtlasMgr, (fGpu, config));
}
GrTextStrike* strike = SkNEW_ARGS(GrTextStrike,
(this, scaler->getKey(), format, fAtlasMgr[atlasIndex]));
fCache.insert(key, strike);
if (fHead) {
fHead->fPrev = strike;
} else {
SkASSERT(NULL == fTail);
fTail = strike;
}
strike->fPrev = NULL;
strike->fNext = fHead;
fHead = strike;
return strike;
}
void GrFontCache::freeAll() {
fCache.deleteAll();
for (int i = 0; i < kAtlasCount; ++i) {
delete fAtlasMgr[i];
fAtlasMgr[i] = NULL;
}
fHead = NULL;
fTail = NULL;
}
void GrFontCache::purgeStrike(GrTextStrike* strike) {
const GrFontCache::Key key(strike->fFontScalerKey);
fCache.remove(key, strike);
this->detachStrikeFromList(strike);
delete strike;
}
void GrFontCache::purgeExceptFor(GrTextStrike* preserveStrike) {
SkASSERT(NULL != preserveStrike);
GrTextStrike* strike = fTail;
bool purge = true;
GrMaskFormat maskFormat = preserveStrike->fMaskFormat;
while (strike) {
if (strike == preserveStrike || maskFormat != strike->fMaskFormat) {
strike = strike->fPrev;
continue;
}
GrTextStrike* strikeToPurge = strike;
strike = strikeToPurge->fPrev;
if (purge) {
// keep purging if we won't free up any atlases with this strike.
purge = strikeToPurge->fAtlas.isEmpty();
this->purgeStrike(strikeToPurge);
}
}
#if FONT_CACHE_STATS
++g_PurgeCount;
#endif
}
void GrFontCache::freePlotExceptFor(GrTextStrike* preserveStrike) {
SkASSERT(NULL != preserveStrike);
GrTextStrike* strike = fTail;
GrMaskFormat maskFormat = preserveStrike->fMaskFormat;
while (strike) {
if (strike == preserveStrike || maskFormat != strike->fMaskFormat) {
strike = strike->fPrev;
continue;
}
GrTextStrike* strikeToPurge = strike;
strike = strikeToPurge->fPrev;
if (strikeToPurge->removeUnusedPlots()) {
if (strikeToPurge->fAtlas.isEmpty()) {
this->purgeStrike(strikeToPurge);
}
break;
}
}
}
#ifdef SK_DEBUG
void GrFontCache::validate() const {
int count = fCache.count();
if (0 == count) {
SkASSERT(!fHead);
SkASSERT(!fTail);
} else if (1 == count) {
SkASSERT(fHead == fTail);
} else {
SkASSERT(fHead != fTail);
}
int count2 = 0;
const GrTextStrike* strike = fHead;
while (strike) {
count2 += 1;
strike = strike->fNext;
}
SkASSERT(count == count2);
count2 = 0;
strike = fTail;
while (strike) {
count2 += 1;
strike = strike->fPrev;
}
SkASSERT(count == count2);
}
#endif
#ifdef SK_DEVELOPER
void GrFontCache::dump() const {
static int gDumpCount = 0;
for (int i = 0; i < kAtlasCount; ++i) {
if (NULL != fAtlasMgr[i]) {
GrTexture* texture = fAtlasMgr[i]->getTexture();
if (NULL != texture) {
SkString filename;
filename.printf("fontcache_%d%d.png", gDumpCount, i);
texture->savePixels(filename.c_str());
}
}
}
++gDumpCount;
}
#endif
///////////////////////////////////////////////////////////////////////////////
#ifdef SK_DEBUG
static int gCounter;
#endif
#if SK_DISTANCEFIELD_FONTS
#define DISTANCE_FIELD_PAD 4
#define DISTANCE_FIELD_RANGE (4.0)
#endif
/*
The text strike is specific to a given font/style/matrix setup, which is
represented by the GrHostFontScaler object we are given in getGlyph().
We map a 32bit glyphID to a GrGlyph record, which in turn points to a
atlas and a position within that texture.
*/
GrTextStrike::GrTextStrike(GrFontCache* cache, const GrKey* key,
GrMaskFormat format,
GrAtlasMgr* atlasMgr) : fPool(64), fAtlas(atlasMgr) {
fFontScalerKey = key;
fFontScalerKey->ref();
fFontCache = cache; // no need to ref, it won't go away before we do
fAtlasMgr = atlasMgr; // no need to ref, it won't go away before we do
fMaskFormat = format;
#ifdef SK_DEBUG
// GrPrintf(" GrTextStrike %p %d\n", this, gCounter);
gCounter += 1;
#endif
}
// these signatures are needed because they're used with
// SkTDArray::visitAll() (see destructor & removeUnusedAtlases())
static void free_glyph(GrGlyph*& glyph) { glyph->free(); }
static void invalidate_glyph(GrGlyph*& glyph) {
if (glyph->fPlot && glyph->fPlot->drawToken().isIssued()) {
glyph->fPlot = NULL;
}
}
GrTextStrike::~GrTextStrike() {
fFontScalerKey->unref();
fCache.getArray().visitAll(free_glyph);
#ifdef SK_DEBUG
gCounter -= 1;
// GrPrintf("~GrTextStrike %p %d\n", this, gCounter);
#endif
}
GrGlyph* GrTextStrike::generateGlyph(GrGlyph::PackedID packed,
GrFontScaler* scaler) {
SkIRect bounds;
if (!scaler->getPackedGlyphBounds(packed, &bounds)) {
return NULL;
}
GrGlyph* glyph = fPool.alloc();
#if SK_DISTANCEFIELD_FONTS
// expand bounds to hold full distance field data
if (fUseDistanceField) {
bounds.fLeft -= DISTANCE_FIELD_PAD;
bounds.fRight += DISTANCE_FIELD_PAD;
bounds.fTop -= DISTANCE_FIELD_PAD;
bounds.fBottom += DISTANCE_FIELD_PAD;
}
#endif
glyph->init(packed, bounds);
fCache.insert(packed, glyph);
return glyph;
}
bool GrTextStrike::removeUnusedPlots() {
fCache.getArray().visitAll(invalidate_glyph);
return fAtlasMgr->removeUnusedPlots(&fAtlas);
}
bool GrTextStrike::getGlyphAtlas(GrGlyph* glyph, GrFontScaler* scaler) {
#if 0 // testing hack to force us to flush our cache often
static int gCounter;
if ((++gCounter % 10) == 0) return false;
#endif
SkASSERT(glyph);
SkASSERT(scaler);
SkASSERT(fCache.contains(glyph));
SkASSERT(NULL == glyph->fPlot);
SkAutoRef ar(scaler);
int bytesPerPixel = GrMaskFormatBytesPerPixel(fMaskFormat);
GrPlot* plot;
#if SK_DISTANCEFIELD_FONTS
if (fUseDistanceField) {
SkASSERT(1 == bytesPerPixel);
// we've already expanded the glyph dimensions to match the final size
// but must shrink back down to get the packed glyph data
int dfWidth = glyph->width();
int dfHeight = glyph->height();
int width = dfWidth - 2*DISTANCE_FIELD_PAD;
int height = dfHeight - 2*DISTANCE_FIELD_PAD;
size_t stride = width*bytesPerPixel;
size_t size = width * height * bytesPerPixel;
SkAutoSMalloc<1024> storage(size);
if (!scaler->getPackedGlyphImage(glyph->fPackedID, width, height, stride, storage.get())) {
return false;
}
// alloc storage for distance field glyph
size_t dfSize = dfWidth * dfHeight * bytesPerPixel;
SkAutoSMalloc<1024> dfStorage(dfSize);
// copy glyph into distance field storage
sk_bzero(dfStorage.get(), dfSize);
unsigned char* ptr = (unsigned char*) storage.get();
unsigned char* dfPtr = (unsigned char*) dfStorage.get();
size_t dfStride = dfWidth*bytesPerPixel;
dfPtr += DISTANCE_FIELD_PAD*dfStride;
dfPtr += DISTANCE_FIELD_PAD*bytesPerPixel;
for (int i = 0; i < height; ++i) {
memcpy(dfPtr, ptr, stride);
dfPtr += dfStride;
ptr += stride;
}
// generate distance field data
SkAutoSMalloc<1024> distXStorage(dfWidth*dfHeight*sizeof(short));
SkAutoSMalloc<1024> distYStorage(dfWidth*dfHeight*sizeof(short));
SkAutoSMalloc<1024> outerDistStorage(dfWidth*dfHeight*sizeof(double));
SkAutoSMalloc<1024> innerDistStorage(dfWidth*dfHeight*sizeof(double));
SkAutoSMalloc<1024> gxStorage(dfWidth*dfHeight*sizeof(double));
SkAutoSMalloc<1024> gyStorage(dfWidth*dfHeight*sizeof(double));
short* distX = (short*) distXStorage.get();
short* distY = (short*) distYStorage.get();
double* outerDist = (double*) outerDistStorage.get();
double* innerDist = (double*) innerDistStorage.get();
double* gx = (double*) gxStorage.get();
double* gy = (double*) gyStorage.get();
dfPtr = (unsigned char*) dfStorage.get();
EDTAA::computegradient(dfPtr, dfWidth, dfHeight, gx, gy);
EDTAA::edtaa3(dfPtr, gx, gy, dfWidth, dfHeight, distX, distY, outerDist);
for (int i = 0; i < dfWidth*dfHeight; ++i) {
*dfPtr = 255 - *dfPtr;
dfPtr++;
}
dfPtr = (unsigned char*) dfStorage.get();
sk_bzero(gx, sizeof(double)*dfWidth*dfHeight);
sk_bzero(gy, sizeof(double)*dfWidth*dfHeight);
EDTAA::computegradient(dfPtr, dfWidth, dfHeight, gx, gy);
EDTAA::edtaa3(dfPtr, gx, gy, dfWidth, dfHeight, distX, distY, innerDist);
for (int i = 0; i < dfWidth*dfHeight; ++i) {
unsigned char val;
double outerval = outerDist[i];
if (outerval < 0.0) {
outerval = 0.0;
}
double innerval = innerDist[i];
if (innerval < 0.0) {
innerval = 0.0;
}
double dist = outerval - innerval;
if (dist <= -DISTANCE_FIELD_RANGE) {
val = 255;
} else if (dist > DISTANCE_FIELD_RANGE) {
val = 0;
} else {
val = (unsigned char)((DISTANCE_FIELD_RANGE-dist)*128.0/DISTANCE_FIELD_RANGE);
}
*dfPtr++ = val;
}
// copy to atlas
plot = fAtlasMgr->addToAtlas(&fAtlas, dfWidth, dfHeight, dfStorage.get(),
&glyph->fAtlasLocation);
} else {
#endif
size_t size = glyph->fBounds.area() * bytesPerPixel;
SkAutoSMalloc<1024> storage(size);
if (!scaler->getPackedGlyphImage(glyph->fPackedID, glyph->width(),
glyph->height(),
glyph->width() * bytesPerPixel,
storage.get())) {
return false;
}
plot = fAtlasMgr->addToAtlas(&fAtlas, glyph->width(),
glyph->height(), storage.get(),
&glyph->fAtlasLocation);
#if SK_DISTANCEFIELD_FONTS
}
#endif
if (NULL == plot) {
return false;
}
glyph->fPlot = plot;
return true;
}