michael@0: /* michael@0: * Copyright 2010 Google Inc. michael@0: * michael@0: * Use of this source code is governed by a BSD-style license that can be michael@0: * found in the LICENSE file. michael@0: */ michael@0: michael@0: #include "GrAtlas.h" michael@0: #include "GrGpu.h" michael@0: #include "GrRectanizer.h" michael@0: #include "GrTextStrike.h" michael@0: #include "GrTextStrike_impl.h" michael@0: #include "SkString.h" michael@0: michael@0: #include "SkDistanceFieldGen.h" michael@0: michael@0: /////////////////////////////////////////////////////////////////////////////// michael@0: michael@0: #define FONT_CACHE_STATS 0 michael@0: #if FONT_CACHE_STATS michael@0: static int g_PurgeCount = 0; michael@0: #endif michael@0: michael@0: GrFontCache::GrFontCache(GrGpu* gpu) : fGpu(gpu) { michael@0: gpu->ref(); michael@0: for (int i = 0; i < kAtlasCount; ++i) { michael@0: fAtlasMgr[i] = NULL; michael@0: } michael@0: michael@0: fHead = fTail = NULL; michael@0: } michael@0: michael@0: GrFontCache::~GrFontCache() { michael@0: fCache.deleteAll(); michael@0: for (int i = 0; i < kAtlasCount; ++i) { michael@0: delete fAtlasMgr[i]; michael@0: } michael@0: fGpu->unref(); michael@0: #if FONT_CACHE_STATS michael@0: GrPrintf("Num purges: %d\n", g_PurgeCount); michael@0: #endif michael@0: } michael@0: michael@0: static GrPixelConfig mask_format_to_pixel_config(GrMaskFormat format) { michael@0: static const GrPixelConfig sPixelConfigs[] = { michael@0: kAlpha_8_GrPixelConfig, michael@0: kRGB_565_GrPixelConfig, michael@0: kSkia8888_GrPixelConfig, michael@0: kSkia8888_GrPixelConfig michael@0: }; michael@0: SK_COMPILE_ASSERT(SK_ARRAY_COUNT(sPixelConfigs) == kMaskFormatCount, array_size_mismatch); michael@0: michael@0: return sPixelConfigs[format]; michael@0: } michael@0: michael@0: static int mask_format_to_atlas_index(GrMaskFormat format) { michael@0: static const int sAtlasIndices[] = { michael@0: GrFontCache::kA8_AtlasType, michael@0: GrFontCache::k565_AtlasType, michael@0: GrFontCache::k8888_AtlasType, michael@0: GrFontCache::k8888_AtlasType michael@0: }; michael@0: SK_COMPILE_ASSERT(SK_ARRAY_COUNT(sAtlasIndices) == kMaskFormatCount, array_size_mismatch); michael@0: michael@0: SkASSERT(sAtlasIndices[format] < GrFontCache::kAtlasCount); michael@0: return sAtlasIndices[format]; michael@0: } michael@0: michael@0: GrTextStrike* GrFontCache::generateStrike(GrFontScaler* scaler, michael@0: const Key& key) { michael@0: GrMaskFormat format = scaler->getMaskFormat(); michael@0: GrPixelConfig config = mask_format_to_pixel_config(format); michael@0: int atlasIndex = mask_format_to_atlas_index(format); michael@0: if (NULL == fAtlasMgr[atlasIndex]) { michael@0: fAtlasMgr[atlasIndex] = SkNEW_ARGS(GrAtlasMgr, (fGpu, config)); michael@0: } michael@0: GrTextStrike* strike = SkNEW_ARGS(GrTextStrike, michael@0: (this, scaler->getKey(), format, fAtlasMgr[atlasIndex])); michael@0: fCache.insert(key, strike); michael@0: michael@0: if (fHead) { michael@0: fHead->fPrev = strike; michael@0: } else { michael@0: SkASSERT(NULL == fTail); michael@0: fTail = strike; michael@0: } michael@0: strike->fPrev = NULL; michael@0: strike->fNext = fHead; michael@0: fHead = strike; michael@0: michael@0: return strike; michael@0: } michael@0: michael@0: void GrFontCache::freeAll() { michael@0: fCache.deleteAll(); michael@0: for (int i = 0; i < kAtlasCount; ++i) { michael@0: delete fAtlasMgr[i]; michael@0: fAtlasMgr[i] = NULL; michael@0: } michael@0: fHead = NULL; michael@0: fTail = NULL; michael@0: } michael@0: michael@0: void GrFontCache::purgeStrike(GrTextStrike* strike) { michael@0: const GrFontCache::Key key(strike->fFontScalerKey); michael@0: fCache.remove(key, strike); michael@0: this->detachStrikeFromList(strike); michael@0: delete strike; michael@0: } michael@0: michael@0: bool GrFontCache::freeUnusedPlot(GrTextStrike* preserveStrike) { michael@0: SkASSERT(NULL != preserveStrike); michael@0: michael@0: GrAtlasMgr* atlasMgr = preserveStrike->fAtlasMgr; michael@0: GrPlot* plot = atlasMgr->getUnusedPlot(); michael@0: if (NULL == plot) { michael@0: return false; michael@0: } michael@0: plot->resetRects(); michael@0: michael@0: GrTextStrike* strike = fHead; michael@0: GrMaskFormat maskFormat = preserveStrike->fMaskFormat; michael@0: while (strike) { michael@0: if (maskFormat != strike->fMaskFormat) { michael@0: strike = strike->fNext; michael@0: continue; michael@0: } michael@0: michael@0: GrTextStrike* strikeToPurge = strike; michael@0: strike = strikeToPurge->fNext; michael@0: strikeToPurge->removePlot(plot); michael@0: michael@0: // clear out any empty strikes (except this one) michael@0: if (strikeToPurge != preserveStrike && strikeToPurge->fAtlas.isEmpty()) { michael@0: this->purgeStrike(strikeToPurge); michael@0: } michael@0: } michael@0: michael@0: #if FONT_CACHE_STATS michael@0: ++g_PurgeCount; michael@0: #endif michael@0: michael@0: return true; michael@0: } michael@0: michael@0: #ifdef SK_DEBUG michael@0: void GrFontCache::validate() const { michael@0: int count = fCache.count(); michael@0: if (0 == count) { michael@0: SkASSERT(!fHead); michael@0: SkASSERT(!fTail); michael@0: } else if (1 == count) { michael@0: SkASSERT(fHead == fTail); michael@0: } else { michael@0: SkASSERT(fHead != fTail); michael@0: } michael@0: michael@0: int count2 = 0; michael@0: const GrTextStrike* strike = fHead; michael@0: while (strike) { michael@0: count2 += 1; michael@0: strike = strike->fNext; michael@0: } michael@0: SkASSERT(count == count2); michael@0: michael@0: count2 = 0; michael@0: strike = fTail; michael@0: while (strike) { michael@0: count2 += 1; michael@0: strike = strike->fPrev; michael@0: } michael@0: SkASSERT(count == count2); michael@0: } michael@0: #endif michael@0: michael@0: #ifdef SK_DEVELOPER michael@0: void GrFontCache::dump() const { michael@0: static int gDumpCount = 0; michael@0: for (int i = 0; i < kAtlasCount; ++i) { michael@0: if (NULL != fAtlasMgr[i]) { michael@0: GrTexture* texture = fAtlasMgr[i]->getTexture(); michael@0: if (NULL != texture) { michael@0: SkString filename; michael@0: filename.printf("fontcache_%d%d.png", gDumpCount, i); michael@0: texture->savePixels(filename.c_str()); michael@0: } michael@0: } michael@0: } michael@0: ++gDumpCount; michael@0: } michael@0: #endif michael@0: michael@0: /////////////////////////////////////////////////////////////////////////////// michael@0: michael@0: #ifdef SK_DEBUG michael@0: static int gCounter; michael@0: #endif michael@0: michael@0: // this acts as the max magnitude for the distance field, michael@0: // as well as the pad we need around the glyph michael@0: #define DISTANCE_FIELD_RANGE 4 michael@0: michael@0: /* michael@0: The text strike is specific to a given font/style/matrix setup, which is michael@0: represented by the GrHostFontScaler object we are given in getGlyph(). michael@0: michael@0: We map a 32bit glyphID to a GrGlyph record, which in turn points to a michael@0: atlas and a position within that texture. michael@0: */ michael@0: michael@0: GrTextStrike::GrTextStrike(GrFontCache* cache, const GrKey* key, michael@0: GrMaskFormat format, michael@0: GrAtlasMgr* atlasMgr) : fPool(64) { michael@0: fFontScalerKey = key; michael@0: fFontScalerKey->ref(); michael@0: michael@0: fFontCache = cache; // no need to ref, it won't go away before we do michael@0: fAtlasMgr = atlasMgr; // no need to ref, it won't go away before we do michael@0: michael@0: fMaskFormat = format; michael@0: michael@0: #ifdef SK_DEBUG michael@0: // GrPrintf(" GrTextStrike %p %d\n", this, gCounter); michael@0: gCounter += 1; michael@0: #endif michael@0: } michael@0: michael@0: // this signature is needed because it's used with michael@0: // SkTDArray::visitAll() (see destructor) michael@0: static void free_glyph(GrGlyph*& glyph) { glyph->free(); } michael@0: michael@0: GrTextStrike::~GrTextStrike() { michael@0: fFontScalerKey->unref(); michael@0: fCache.getArray().visitAll(free_glyph); michael@0: michael@0: #ifdef SK_DEBUG michael@0: gCounter -= 1; michael@0: // GrPrintf("~GrTextStrike %p %d\n", this, gCounter); michael@0: #endif michael@0: } michael@0: michael@0: GrGlyph* GrTextStrike::generateGlyph(GrGlyph::PackedID packed, michael@0: GrFontScaler* scaler) { michael@0: SkIRect bounds; michael@0: if (!scaler->getPackedGlyphBounds(packed, &bounds)) { michael@0: return NULL; michael@0: } michael@0: michael@0: GrGlyph* glyph = fPool.alloc(); michael@0: // expand bounds to hold full distance field data michael@0: if (fUseDistanceField) { michael@0: bounds.fLeft -= DISTANCE_FIELD_RANGE; michael@0: bounds.fRight += DISTANCE_FIELD_RANGE; michael@0: bounds.fTop -= DISTANCE_FIELD_RANGE; michael@0: bounds.fBottom += DISTANCE_FIELD_RANGE; michael@0: } michael@0: glyph->init(packed, bounds); michael@0: fCache.insert(packed, glyph); michael@0: return glyph; michael@0: } michael@0: michael@0: void GrTextStrike::removePlot(const GrPlot* plot) { michael@0: SkTDArray& glyphArray = fCache.getArray(); michael@0: for (int i = 0; i < glyphArray.count(); ++i) { michael@0: if (plot == glyphArray[i]->fPlot) { michael@0: glyphArray[i]->fPlot = NULL; michael@0: } michael@0: } michael@0: michael@0: fAtlasMgr->removePlot(&fAtlas, plot); michael@0: } michael@0: michael@0: michael@0: bool GrTextStrike::addGlyphToAtlas(GrGlyph* glyph, GrFontScaler* scaler) { michael@0: #if 0 // testing hack to force us to flush our cache often michael@0: static int gCounter; michael@0: if ((++gCounter % 10) == 0) return false; michael@0: #endif michael@0: michael@0: SkASSERT(glyph); michael@0: SkASSERT(scaler); michael@0: SkASSERT(fCache.contains(glyph)); michael@0: SkASSERT(NULL == glyph->fPlot); michael@0: michael@0: SkAutoRef ar(scaler); michael@0: michael@0: int bytesPerPixel = GrMaskFormatBytesPerPixel(fMaskFormat); michael@0: michael@0: GrPlot* plot; michael@0: if (fUseDistanceField) { michael@0: // we've already expanded the glyph dimensions to match the final size michael@0: // but must shrink back down to get the packed glyph data michael@0: int dfWidth = glyph->width(); michael@0: int dfHeight = glyph->height(); michael@0: int width = dfWidth - 2*DISTANCE_FIELD_RANGE; michael@0: int height = dfHeight - 2*DISTANCE_FIELD_RANGE; michael@0: int stride = width*bytesPerPixel; michael@0: michael@0: size_t size = width * height * bytesPerPixel; michael@0: SkAutoSMalloc<1024> storage(size); michael@0: if (!scaler->getPackedGlyphImage(glyph->fPackedID, width, height, stride, storage.get())) { michael@0: return false; michael@0: } michael@0: michael@0: // alloc storage for distance field glyph michael@0: size_t dfSize = dfWidth * dfHeight * bytesPerPixel; michael@0: SkAutoSMalloc<1024> dfStorage(dfSize); michael@0: michael@0: if (1 == bytesPerPixel) { michael@0: (void) SkGenerateDistanceFieldFromImage((unsigned char*)dfStorage.get(), michael@0: (unsigned char*)storage.get(), michael@0: width, height, DISTANCE_FIELD_RANGE); michael@0: } else { michael@0: // TODO: Fix color emoji michael@0: // for now, copy glyph into distance field storage michael@0: // this is not correct, but it won't crash michael@0: sk_bzero(dfStorage.get(), dfSize); michael@0: unsigned char* ptr = (unsigned char*) storage.get(); michael@0: unsigned char* dfPtr = (unsigned char*) dfStorage.get(); michael@0: size_t dfStride = dfWidth*bytesPerPixel; michael@0: dfPtr += DISTANCE_FIELD_RANGE*dfStride; michael@0: dfPtr += DISTANCE_FIELD_RANGE*bytesPerPixel; michael@0: michael@0: for (int i = 0; i < height; ++i) { michael@0: memcpy(dfPtr, ptr, stride); michael@0: michael@0: dfPtr += dfStride; michael@0: ptr += stride; michael@0: } michael@0: } michael@0: michael@0: // copy to atlas michael@0: plot = fAtlasMgr->addToAtlas(&fAtlas, dfWidth, dfHeight, dfStorage.get(), michael@0: &glyph->fAtlasLocation); michael@0: michael@0: } else { michael@0: size_t size = glyph->fBounds.area() * bytesPerPixel; michael@0: SkAutoSMalloc<1024> storage(size); michael@0: if (!scaler->getPackedGlyphImage(glyph->fPackedID, glyph->width(), michael@0: glyph->height(), michael@0: glyph->width() * bytesPerPixel, michael@0: storage.get())) { michael@0: return false; michael@0: } michael@0: michael@0: plot = fAtlasMgr->addToAtlas(&fAtlas, glyph->width(), michael@0: glyph->height(), storage.get(), michael@0: &glyph->fAtlasLocation); michael@0: } michael@0: michael@0: if (NULL == plot) { michael@0: return false; michael@0: } michael@0: michael@0: glyph->fPlot = plot; michael@0: return true; michael@0: }