blob: 65ead0f0bbf08268d7247e5c8cb95cbaeaa9aaaa [file] [log] [blame]
reed@google.comac10a2d2010-12-22 21:39:39 +00001/*
epoger@google.comec3ed6a2011-07-28 14:26:00 +00002 * Copyright 2010 Google Inc.
3 *
4 * Use of this source code is governed by a BSD-style license that can be
5 * found in the LICENSE file.
reed@google.comac10a2d2010-12-22 21:39:39 +00006 */
7
reed@google.comac10a2d2010-12-22 21:39:39 +00008#include "GrAtlas.h"
9#include "GrGpu.h"
reed@google.comac10a2d2010-12-22 21:39:39 +000010#include "GrRectanizer.h"
11#include "GrTextStrike.h"
12#include "GrTextStrike_impl.h"
commit-bot@chromium.org03e3e892013-10-02 18:19:17 +000013#include "SkString.h"
reed@google.comac10a2d2010-12-22 21:39:39 +000014
commit-bot@chromium.org8065ec52014-03-11 15:57:40 +000015#include "SkDistanceFieldGen.h"
jvanverth@google.comd830d132013-11-11 20:54:09 +000016
reed@google.comfa35e3d2012-06-26 20:16:17 +000017///////////////////////////////////////////////////////////////////////////////
18
commit-bot@chromium.org53e1e4d2014-04-01 16:25:11 +000019#define GR_ATLAS_TEXTURE_WIDTH 1024
20#define GR_ATLAS_TEXTURE_HEIGHT 2048
21
22#define GR_PLOT_WIDTH 256
23#define GR_PLOT_HEIGHT 256
24
25#define GR_NUM_PLOTS_X (GR_ATLAS_TEXTURE_WIDTH / GR_PLOT_WIDTH)
26#define GR_NUM_PLOTS_Y (GR_ATLAS_TEXTURE_HEIGHT / GR_PLOT_HEIGHT)
27
commit-bot@chromium.org67ed64e2013-08-05 19:42:56 +000028#define FONT_CACHE_STATS 0
29#if FONT_CACHE_STATS
30static int g_PurgeCount = 0;
31#endif
32
reed@google.comac10a2d2010-12-22 21:39:39 +000033GrFontCache::GrFontCache(GrGpu* gpu) : fGpu(gpu) {
34 gpu->ref();
commit-bot@chromium.orgf8cb1842013-12-03 19:45:22 +000035 for (int i = 0; i < kAtlasCount; ++i) {
commit-bot@chromium.org3fddf0e2013-09-26 12:57:19 +000036 fAtlasMgr[i] = NULL;
37 }
reed@google.comac10a2d2010-12-22 21:39:39 +000038
39 fHead = fTail = NULL;
40}
41
42GrFontCache::~GrFontCache() {
43 fCache.deleteAll();
commit-bot@chromium.orgf8cb1842013-12-03 19:45:22 +000044 for (int i = 0; i < kAtlasCount; ++i) {
commit-bot@chromium.org3fddf0e2013-09-26 12:57:19 +000045 delete fAtlasMgr[i];
46 }
reed@google.comac10a2d2010-12-22 21:39:39 +000047 fGpu->unref();
commit-bot@chromium.org67ed64e2013-08-05 19:42:56 +000048#if FONT_CACHE_STATS
49 GrPrintf("Num purges: %d\n", g_PurgeCount);
50#endif
reed@google.comac10a2d2010-12-22 21:39:39 +000051}
52
commit-bot@chromium.org95294412013-09-26 15:28:40 +000053static GrPixelConfig mask_format_to_pixel_config(GrMaskFormat format) {
skia.committer@gmail.com6e515d62013-12-04 07:02:26 +000054 static const GrPixelConfig sPixelConfigs[] = {
55 kAlpha_8_GrPixelConfig,
56 kRGB_565_GrPixelConfig,
commit-bot@chromium.orgf8cb1842013-12-03 19:45:22 +000057 kSkia8888_GrPixelConfig,
58 kSkia8888_GrPixelConfig
59 };
60 SK_COMPILE_ASSERT(SK_ARRAY_COUNT(sPixelConfigs) == kMaskFormatCount, array_size_mismatch);
61
62 return sPixelConfigs[format];
63}
64
65static int mask_format_to_atlas_index(GrMaskFormat format) {
skia.committer@gmail.com6e515d62013-12-04 07:02:26 +000066 static const int sAtlasIndices[] = {
67 GrFontCache::kA8_AtlasType,
68 GrFontCache::k565_AtlasType,
69 GrFontCache::k8888_AtlasType,
70 GrFontCache::k8888_AtlasType
commit-bot@chromium.orgf8cb1842013-12-03 19:45:22 +000071 };
72 SK_COMPILE_ASSERT(SK_ARRAY_COUNT(sAtlasIndices) == kMaskFormatCount, array_size_mismatch);
73
74 SkASSERT(sAtlasIndices[format] < GrFontCache::kAtlasCount);
75 return sAtlasIndices[format];
commit-bot@chromium.org95294412013-09-26 15:28:40 +000076}
77
reed@google.comac10a2d2010-12-22 21:39:39 +000078GrTextStrike* GrFontCache::generateStrike(GrFontScaler* scaler,
79 const Key& key) {
commit-bot@chromium.org3fddf0e2013-09-26 12:57:19 +000080 GrMaskFormat format = scaler->getMaskFormat();
commit-bot@chromium.org95294412013-09-26 15:28:40 +000081 GrPixelConfig config = mask_format_to_pixel_config(format);
commit-bot@chromium.orgf8cb1842013-12-03 19:45:22 +000082 int atlasIndex = mask_format_to_atlas_index(format);
83 if (NULL == fAtlasMgr[atlasIndex]) {
commit-bot@chromium.org53e1e4d2014-04-01 16:25:11 +000084 SkISize textureSize = SkISize::Make(GR_ATLAS_TEXTURE_WIDTH,
85 GR_ATLAS_TEXTURE_HEIGHT);
86 fAtlasMgr[atlasIndex] = SkNEW_ARGS(GrAtlasMgr, (fGpu, config,
87 textureSize,
88 GR_NUM_PLOTS_X,
89 GR_NUM_PLOTS_Y));
reed@google.comac10a2d2010-12-22 21:39:39 +000090 }
tomhudson@google.comc377baf2012-07-09 20:17:56 +000091 GrTextStrike* strike = SkNEW_ARGS(GrTextStrike,
commit-bot@chromium.orgf8cb1842013-12-03 19:45:22 +000092 (this, scaler->getKey(), format, fAtlasMgr[atlasIndex]));
reed@google.comac10a2d2010-12-22 21:39:39 +000093 fCache.insert(key, strike);
94
95 if (fHead) {
96 fHead->fPrev = strike;
97 } else {
tfarina@chromium.orgf6de4752013-08-17 00:02:59 +000098 SkASSERT(NULL == fTail);
reed@google.comac10a2d2010-12-22 21:39:39 +000099 fTail = strike;
100 }
101 strike->fPrev = NULL;
102 strike->fNext = fHead;
103 fHead = strike;
104
105 return strike;
106}
107
reed@google.comac10a2d2010-12-22 21:39:39 +0000108void GrFontCache::freeAll() {
109 fCache.deleteAll();
commit-bot@chromium.orgf8cb1842013-12-03 19:45:22 +0000110 for (int i = 0; i < kAtlasCount; ++i) {
commit-bot@chromium.org3fddf0e2013-09-26 12:57:19 +0000111 delete fAtlasMgr[i];
112 fAtlasMgr[i] = NULL;
113 }
bsalomon@google.com8fe72472011-03-30 21:26:44 +0000114 fHead = NULL;
115 fTail = NULL;
reed@google.comac10a2d2010-12-22 21:39:39 +0000116}
117
commit-bot@chromium.orgb2e9fa52013-10-27 20:50:23 +0000118void GrFontCache::purgeStrike(GrTextStrike* strike) {
119 const GrFontCache::Key key(strike->fFontScalerKey);
120 fCache.remove(key, strike);
121 this->detachStrikeFromList(strike);
122 delete strike;
123}
124
commit-bot@chromium.orgc9b2c882014-03-03 14:30:25 +0000125bool GrFontCache::freeUnusedPlot(GrTextStrike* preserveStrike) {
jvanverth@google.combbe55fd2013-09-16 20:28:37 +0000126 SkASSERT(NULL != preserveStrike);
commit-bot@chromium.orgc9b2c882014-03-03 14:30:25 +0000127
128 GrAtlasMgr* atlasMgr = preserveStrike->fAtlasMgr;
129 GrPlot* plot = atlasMgr->getUnusedPlot();
130 if (NULL == plot) {
131 return false;
132 }
133 plot->resetRects();
134
135 GrTextStrike* strike = fHead;
jvanverth@google.combbe55fd2013-09-16 20:28:37 +0000136 GrMaskFormat maskFormat = preserveStrike->fMaskFormat;
bsalomon@google.com7359eae2011-06-21 21:18:25 +0000137 while (strike) {
commit-bot@chromium.orgc9b2c882014-03-03 14:30:25 +0000138 if (maskFormat != strike->fMaskFormat) {
139 strike = strike->fNext;
bsalomon@google.com7359eae2011-06-21 21:18:25 +0000140 continue;
141 }
commit-bot@chromium.orgc9b2c882014-03-03 14:30:25 +0000142
bsalomon@google.com7359eae2011-06-21 21:18:25 +0000143 GrTextStrike* strikeToPurge = strike;
commit-bot@chromium.orgc9b2c882014-03-03 14:30:25 +0000144 strike = strikeToPurge->fNext;
145 strikeToPurge->removePlot(plot);
146
147 // clear out any empty strikes (except this one)
148 if (strikeToPurge != preserveStrike && strikeToPurge->fAtlas.isEmpty()) {
commit-bot@chromium.orgb2e9fa52013-10-27 20:50:23 +0000149 this->purgeStrike(strikeToPurge);
commit-bot@chromium.org67ed64e2013-08-05 19:42:56 +0000150 }
151 }
commit-bot@chromium.orgc9b2c882014-03-03 14:30:25 +0000152
commit-bot@chromium.org67ed64e2013-08-05 19:42:56 +0000153#if FONT_CACHE_STATS
154 ++g_PurgeCount;
155#endif
commit-bot@chromium.org67ed64e2013-08-05 19:42:56 +0000156
commit-bot@chromium.orgc9b2c882014-03-03 14:30:25 +0000157 return true;
reed@google.comac10a2d2010-12-22 21:39:39 +0000158}
159
commit-bot@chromium.org515dcd32013-08-28 14:17:03 +0000160#ifdef SK_DEBUG
reed@google.comac10a2d2010-12-22 21:39:39 +0000161void GrFontCache::validate() const {
162 int count = fCache.count();
163 if (0 == count) {
tfarina@chromium.orgf6de4752013-08-17 00:02:59 +0000164 SkASSERT(!fHead);
165 SkASSERT(!fTail);
reed@google.comac10a2d2010-12-22 21:39:39 +0000166 } else if (1 == count) {
tfarina@chromium.orgf6de4752013-08-17 00:02:59 +0000167 SkASSERT(fHead == fTail);
reed@google.comac10a2d2010-12-22 21:39:39 +0000168 } else {
tfarina@chromium.orgf6de4752013-08-17 00:02:59 +0000169 SkASSERT(fHead != fTail);
reed@google.comac10a2d2010-12-22 21:39:39 +0000170 }
171
172 int count2 = 0;
173 const GrTextStrike* strike = fHead;
174 while (strike) {
175 count2 += 1;
176 strike = strike->fNext;
177 }
tfarina@chromium.orgf6de4752013-08-17 00:02:59 +0000178 SkASSERT(count == count2);
reed@google.comac10a2d2010-12-22 21:39:39 +0000179
180 count2 = 0;
181 strike = fTail;
182 while (strike) {
183 count2 += 1;
184 strike = strike->fPrev;
185 }
tfarina@chromium.orgf6de4752013-08-17 00:02:59 +0000186 SkASSERT(count == count2);
reed@google.comac10a2d2010-12-22 21:39:39 +0000187}
188#endif
189
commit-bot@chromium.org03e3e892013-10-02 18:19:17 +0000190#ifdef SK_DEVELOPER
191void GrFontCache::dump() const {
192 static int gDumpCount = 0;
commit-bot@chromium.orgf8cb1842013-12-03 19:45:22 +0000193 for (int i = 0; i < kAtlasCount; ++i) {
commit-bot@chromium.org03e3e892013-10-02 18:19:17 +0000194 if (NULL != fAtlasMgr[i]) {
195 GrTexture* texture = fAtlasMgr[i]->getTexture();
196 if (NULL != texture) {
197 SkString filename;
commit-bot@chromium.org4362a382014-03-26 19:49:03 +0000198#ifdef SK_BUILD_FOR_ANDROID
199 filename.printf("/sdcard/fontcache_%d%d.png", gDumpCount, i);
200#else
commit-bot@chromium.org03e3e892013-10-02 18:19:17 +0000201 filename.printf("fontcache_%d%d.png", gDumpCount, i);
commit-bot@chromium.org4362a382014-03-26 19:49:03 +0000202#endif
commit-bot@chromium.org03e3e892013-10-02 18:19:17 +0000203 texture->savePixels(filename.c_str());
204 }
205 }
206 }
207 ++gDumpCount;
208}
209#endif
210
reed@google.comac10a2d2010-12-22 21:39:39 +0000211///////////////////////////////////////////////////////////////////////////////
212
commit-bot@chromium.org515dcd32013-08-28 14:17:03 +0000213#ifdef SK_DEBUG
reed@google.comac10a2d2010-12-22 21:39:39 +0000214 static int gCounter;
215#endif
216
commit-bot@chromium.org8065ec52014-03-11 15:57:40 +0000217// this acts as the max magnitude for the distance field,
218// as well as the pad we need around the glyph
219#define DISTANCE_FIELD_RANGE 4
jvanverth@google.comd830d132013-11-11 20:54:09 +0000220
reed@google.comac10a2d2010-12-22 21:39:39 +0000221/*
222 The text strike is specific to a given font/style/matrix setup, which is
223 represented by the GrHostFontScaler object we are given in getGlyph().
224
225 We map a 32bit glyphID to a GrGlyph record, which in turn points to a
226 atlas and a position within that texture.
227 */
228
229GrTextStrike::GrTextStrike(GrFontCache* cache, const GrKey* key,
reed@google.com98539c62011-03-15 15:40:16 +0000230 GrMaskFormat format,
commit-bot@chromium.orgc9b2c882014-03-03 14:30:25 +0000231 GrAtlasMgr* atlasMgr) : fPool(64) {
reed@google.comac10a2d2010-12-22 21:39:39 +0000232 fFontScalerKey = key;
233 fFontScalerKey->ref();
234
235 fFontCache = cache; // no need to ref, it won't go away before we do
236 fAtlasMgr = atlasMgr; // no need to ref, it won't go away before we do
reed@google.comac10a2d2010-12-22 21:39:39 +0000237
reed@google.com98539c62011-03-15 15:40:16 +0000238 fMaskFormat = format;
239
commit-bot@chromium.org515dcd32013-08-28 14:17:03 +0000240#ifdef SK_DEBUG
reed@google.com3ef80cf2011-07-05 19:09:47 +0000241// GrPrintf(" GrTextStrike %p %d\n", this, gCounter);
reed@google.comac10a2d2010-12-22 21:39:39 +0000242 gCounter += 1;
243#endif
244}
245
commit-bot@chromium.orgc9b2c882014-03-03 14:30:25 +0000246// this signature is needed because it's used with
247// SkTDArray::visitAll() (see destructor)
commit-bot@chromium.org67ed64e2013-08-05 19:42:56 +0000248static void free_glyph(GrGlyph*& glyph) { glyph->free(); }
249
reed@google.comac10a2d2010-12-22 21:39:39 +0000250GrTextStrike::~GrTextStrike() {
reed@google.comac10a2d2010-12-22 21:39:39 +0000251 fFontScalerKey->unref();
commit-bot@chromium.org67ed64e2013-08-05 19:42:56 +0000252 fCache.getArray().visitAll(free_glyph);
reed@google.comac10a2d2010-12-22 21:39:39 +0000253
commit-bot@chromium.org515dcd32013-08-28 14:17:03 +0000254#ifdef SK_DEBUG
reed@google.comac10a2d2010-12-22 21:39:39 +0000255 gCounter -= 1;
reed@google.com3ef80cf2011-07-05 19:09:47 +0000256// GrPrintf("~GrTextStrike %p %d\n", this, gCounter);
reed@google.comac10a2d2010-12-22 21:39:39 +0000257#endif
258}
259
260GrGlyph* GrTextStrike::generateGlyph(GrGlyph::PackedID packed,
261 GrFontScaler* scaler) {
commit-bot@chromium.orgfd03d4a2013-07-17 21:39:42 +0000262 SkIRect bounds;
reed@google.comac10a2d2010-12-22 21:39:39 +0000263 if (!scaler->getPackedGlyphBounds(packed, &bounds)) {
264 return NULL;
265 }
266
267 GrGlyph* glyph = fPool.alloc();
jvanverth@google.comd830d132013-11-11 20:54:09 +0000268 // expand bounds to hold full distance field data
commit-bot@chromium.org4362a382014-03-26 19:49:03 +0000269 // + room for bilerp
270 int pad = DISTANCE_FIELD_RANGE+1;
jvanverth@google.comd830d132013-11-11 20:54:09 +0000271 if (fUseDistanceField) {
commit-bot@chromium.org4362a382014-03-26 19:49:03 +0000272 bounds.fLeft -= pad;
273 bounds.fRight += pad;
274 bounds.fTop -= pad;
275 bounds.fBottom += pad;
jvanverth@google.comd830d132013-11-11 20:54:09 +0000276 }
reed@google.comac10a2d2010-12-22 21:39:39 +0000277 glyph->init(packed, bounds);
278 fCache.insert(packed, glyph);
279 return glyph;
280}
281
commit-bot@chromium.orgc9b2c882014-03-03 14:30:25 +0000282void GrTextStrike::removePlot(const GrPlot* plot) {
283 SkTDArray<GrGlyph*>& glyphArray = fCache.getArray();
284 for (int i = 0; i < glyphArray.count(); ++i) {
285 if (plot == glyphArray[i]->fPlot) {
286 glyphArray[i]->fPlot = NULL;
287 }
288 }
289
290 fAtlasMgr->removePlot(&fAtlas, plot);
commit-bot@chromium.org67ed64e2013-08-05 19:42:56 +0000291}
292
jvanverth@google.comd830d132013-11-11 20:54:09 +0000293
commit-bot@chromium.orgc9b2c882014-03-03 14:30:25 +0000294bool GrTextStrike::addGlyphToAtlas(GrGlyph* glyph, GrFontScaler* scaler) {
reed@google.com0ebe81a2011-04-04 20:06:59 +0000295#if 0 // testing hack to force us to flush our cache often
296 static int gCounter;
297 if ((++gCounter % 10) == 0) return false;
298#endif
299
tfarina@chromium.orgf6de4752013-08-17 00:02:59 +0000300 SkASSERT(glyph);
301 SkASSERT(scaler);
302 SkASSERT(fCache.contains(glyph));
commit-bot@chromium.org49e80832013-10-07 18:20:27 +0000303 SkASSERT(NULL == glyph->fPlot);
reed@google.comac10a2d2010-12-22 21:39:39 +0000304
commit-bot@chromium.orga4de8c22013-09-09 13:38:37 +0000305 SkAutoRef ar(scaler);
reed@google.com98539c62011-03-15 15:40:16 +0000306
307 int bytesPerPixel = GrMaskFormatBytesPerPixel(fMaskFormat);
reed@google.comac10a2d2010-12-22 21:39:39 +0000308
jvanverth@google.comd830d132013-11-11 20:54:09 +0000309 GrPlot* plot;
jvanverth@google.comd830d132013-11-11 20:54:09 +0000310 if (fUseDistanceField) {
jvanverth@google.comd830d132013-11-11 20:54:09 +0000311 // we've already expanded the glyph dimensions to match the final size
312 // but must shrink back down to get the packed glyph data
313 int dfWidth = glyph->width();
314 int dfHeight = glyph->height();
commit-bot@chromium.org4362a382014-03-26 19:49:03 +0000315 int pad = DISTANCE_FIELD_RANGE+1;
316 int width = dfWidth - 2*pad;
317 int height = dfHeight - 2*pad;
commit-bot@chromium.org8065ec52014-03-11 15:57:40 +0000318 int stride = width*bytesPerPixel;
jvanverth@google.comd830d132013-11-11 20:54:09 +0000319
320 size_t size = width * height * bytesPerPixel;
321 SkAutoSMalloc<1024> storage(size);
322 if (!scaler->getPackedGlyphImage(glyph->fPackedID, width, height, stride, storage.get())) {
323 return false;
324 }
325
326 // alloc storage for distance field glyph
327 size_t dfSize = dfWidth * dfHeight * bytesPerPixel;
328 SkAutoSMalloc<1024> dfStorage(dfSize);
skia.committer@gmail.com0b708162014-03-12 03:02:18 +0000329
commit-bot@chromium.org8065ec52014-03-11 15:57:40 +0000330 if (1 == bytesPerPixel) {
331 (void) SkGenerateDistanceFieldFromImage((unsigned char*)dfStorage.get(),
332 (unsigned char*)storage.get(),
333 width, height, DISTANCE_FIELD_RANGE);
334 } else {
commit-bot@chromium.orgeefd8a02014-04-08 20:14:32 +0000335 // distance fields should only be used to represent alpha masks
336 SkASSERT(false);
337 return false;
jvanverth@google.comd830d132013-11-11 20:54:09 +0000338 }
skia.committer@gmail.com11a253b2013-11-12 07:02:05 +0000339
jvanverth@google.comd830d132013-11-11 20:54:09 +0000340 // copy to atlas
skia.committer@gmail.com11a253b2013-11-12 07:02:05 +0000341 plot = fAtlasMgr->addToAtlas(&fAtlas, dfWidth, dfHeight, dfStorage.get(),
jvanverth@google.comd830d132013-11-11 20:54:09 +0000342 &glyph->fAtlasLocation);
343
344 } else {
jvanverth@google.comd830d132013-11-11 20:54:09 +0000345 size_t size = glyph->fBounds.area() * bytesPerPixel;
346 SkAutoSMalloc<1024> storage(size);
347 if (!scaler->getPackedGlyphImage(glyph->fPackedID, glyph->width(),
348 glyph->height(),
349 glyph->width() * bytesPerPixel,
350 storage.get())) {
351 return false;
352 }
353
skia.committer@gmail.com11a253b2013-11-12 07:02:05 +0000354 plot = fAtlasMgr->addToAtlas(&fAtlas, glyph->width(),
355 glyph->height(), storage.get(),
jvanverth@google.comd830d132013-11-11 20:54:09 +0000356 &glyph->fAtlasLocation);
jvanverth@google.comd830d132013-11-11 20:54:09 +0000357 }
jvanverth@google.comd830d132013-11-11 20:54:09 +0000358
commit-bot@chromium.org7d330eb2013-09-27 19:39:38 +0000359 if (NULL == plot) {
reed@google.comac10a2d2010-12-22 21:39:39 +0000360 return false;
361 }
362
commit-bot@chromium.org7d330eb2013-09-27 19:39:38 +0000363 glyph->fPlot = plot;
reed@google.comac10a2d2010-12-22 21:39:39 +0000364 return true;
365}