blob: 3cd7a64a73d3bee8d906349e40e272c26cf29db8 [file] [log] [blame]
joshualitt7c3a2f82015-03-31 13:32:05 -07001/*
2 * Copyright 2015 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.
6 */
7
8#include "GrBatchFontCache.h"
9#include "GrFontAtlasSizes.h"
10#include "GrGpu.h"
11#include "GrRectanizer.h"
12#include "GrSurfacePriv.h"
13#include "SkString.h"
14
15#include "SkDistanceFieldGen.h"
16
17///////////////////////////////////////////////////////////////////////////////
18
19static GrBatchAtlas* make_atlas(GrContext* context, GrPixelConfig config,
20 int textureWidth, int textureHeight,
21 int numPlotsX, int numPlotsY) {
22 GrSurfaceDesc desc;
23 desc.fFlags = kNone_GrSurfaceFlags;
24 desc.fWidth = textureWidth;
25 desc.fHeight = textureHeight;
26 desc.fConfig = config;
27
28 // We don't want to flush the context so we claim we're in the middle of flushing so as to
29 // guarantee we do not recieve a texture with pending IO
30 GrTexture* texture = context->refScratchTexture(desc, GrContext::kApprox_ScratchTexMatch, true);
31 if (!texture) {
32 return NULL;
33 }
34 return SkNEW_ARGS(GrBatchAtlas, (texture, numPlotsX, numPlotsY));
35}
36
37int GrBatchFontCache::MaskFormatToAtlasIndex(GrMaskFormat format) {
38 static const int sAtlasIndices[] = {
39 kA8_GrMaskFormat,
40 kA565_GrMaskFormat,
41 kARGB_GrMaskFormat,
42 };
43 SK_COMPILE_ASSERT(SK_ARRAY_COUNT(sAtlasIndices) == kMaskFormatCount, array_size_mismatch);
44
45 SkASSERT(sAtlasIndices[format] < kMaskFormatCount);
46 return sAtlasIndices[format];
47}
48
49GrMaskFormat GrBatchFontCache::AtlasIndexToMaskFormat(int atlasIndex) {
50 static GrMaskFormat sMaskFormats[] = {
51 kA8_GrMaskFormat,
52 kA565_GrMaskFormat,
53 kARGB_GrMaskFormat,
54 };
55 SK_COMPILE_ASSERT(SK_ARRAY_COUNT(sMaskFormats) == kMaskFormatCount, array_size_mismatch);
56
57 SkASSERT(sMaskFormats[atlasIndex] < kMaskFormatCount);
58 return sMaskFormats[atlasIndex];
59}
60
61GrBatchFontCache::GrBatchFontCache()
62 : fPreserveStrike(NULL) {
63}
64
65void GrBatchFontCache::init(GrContext* context) {
66 for (int i = 0; i < kMaskFormatCount; i++) {
67 GrMaskFormat format = AtlasIndexToMaskFormat(i);
68 GrPixelConfig config = this->getPixelConfig(format);
69
70 if (kA8_GrMaskFormat == format) {
71 fAtlases[i] = make_atlas(context, config,
72 GR_FONT_ATLAS_A8_TEXTURE_WIDTH,
73 GR_FONT_ATLAS_TEXTURE_HEIGHT,
74 GR_FONT_ATLAS_A8_NUM_PLOTS_X,
75 GR_FONT_ATLAS_NUM_PLOTS_Y);
76 } else {
77 fAtlases[i] = make_atlas(context, config,
78 GR_FONT_ATLAS_TEXTURE_WIDTH,
79 GR_FONT_ATLAS_TEXTURE_HEIGHT,
80 GR_FONT_ATLAS_NUM_PLOTS_X,
81 GR_FONT_ATLAS_NUM_PLOTS_Y);
82 }
83
84 if (fAtlases[i]) {
85 fAtlases[i]->registerEvictionCallback(&GrBatchFontCache::HandleEviction, (void*)this);
86 }
87 }
88}
89
90GrBatchFontCache::~GrBatchFontCache() {
91 SkTDynamicHash<GrBatchTextStrike, GrFontDescKey>::Iter iter(&fCache);
92 while (!iter.done()) {
93 SkDELETE(&(*iter));
94 ++iter;
95 }
96 for (int i = 0; i < kMaskFormatCount; ++i) {
97 SkDELETE(fAtlases[i]);
98 }
99}
100
101GrBatchTextStrike* GrBatchFontCache::generateStrike(GrFontScaler* scaler) {
102 GrBatchTextStrike* strike = SkNEW_ARGS(GrBatchTextStrike, (this, scaler->getKey()));
103 fCache.add(strike);
104 return strike;
105}
106
107void GrBatchFontCache::freeAll() {
108 SkTDynamicHash<GrBatchTextStrike, GrFontDescKey>::Iter iter(&fCache);
109 while (!iter.done()) {
110 SkDELETE(&(*iter));
111 ++iter;
112 }
113 fCache.rewind();
114 for (int i = 0; i < kMaskFormatCount; ++i) {
115 SkDELETE(fAtlases[i]);
116 fAtlases[i] = NULL;
117 }
118}
119
120inline GrBatchAtlas* GrBatchFontCache::getAtlas(GrMaskFormat format) const {
121 int atlasIndex = MaskFormatToAtlasIndex(format);
122 SkASSERT(fAtlases[atlasIndex]);
123 return fAtlases[atlasIndex];
124}
125
126bool GrBatchFontCache::hasGlyph(GrGlyph* glyph) {
127 SkASSERT(glyph);
128 return this->getAtlas(glyph->fMaskFormat)->hasID(glyph->fID);
129}
130
joshualittb4c507e2015-04-08 08:07:59 -0700131void GrBatchFontCache::addGlyphToBulkAndSetUseToken(GrBatchAtlas::BulkUseTokenUpdater* updater,
132 GrGlyph* glyph,
133 GrBatchAtlas::BatchToken token) {
joshualitt7c3a2f82015-03-31 13:32:05 -0700134 SkASSERT(glyph);
joshualittb4c507e2015-04-08 08:07:59 -0700135 updater->add(glyph->fID);
136 this->getAtlas(glyph->fMaskFormat)->setLastUseToken(glyph->fID, token);
137}
138
139void GrBatchFontCache::setUseTokenBulk(const GrBatchAtlas::BulkUseTokenUpdater& updater,
140 GrBatchAtlas::BatchToken token,
141 GrMaskFormat format) {
142 this->getAtlas(format)->setLastUseTokenBulk(updater, token);
joshualitt7c3a2f82015-03-31 13:32:05 -0700143}
144
145bool GrBatchFontCache::addToAtlas(GrBatchTextStrike* strike, GrBatchAtlas::AtlasID* id,
146 GrBatchTarget* batchTarget,
147 GrMaskFormat format, int width, int height, const void* image,
148 SkIPoint16* loc) {
149 fPreserveStrike = strike;
150 return this->getAtlas(format)->addToAtlas(id, batchTarget, width, height, image, loc);
151}
152
153uint64_t GrBatchFontCache::atlasGeneration(GrMaskFormat format) const {
154 return this->getAtlas(format)->atlasGeneration();
155}
156
157GrTexture* GrBatchFontCache::getTexture(GrMaskFormat format) {
158 int atlasIndex = MaskFormatToAtlasIndex(format);
159 SkASSERT(fAtlases[atlasIndex]);
160 return fAtlases[atlasIndex]->getTexture();
161}
162
163GrPixelConfig GrBatchFontCache::getPixelConfig(GrMaskFormat format) const {
164 static const GrPixelConfig kPixelConfigs[] = {
165 kAlpha_8_GrPixelConfig,
166 kRGB_565_GrPixelConfig,
167 kSkia8888_GrPixelConfig
168 };
169 SK_COMPILE_ASSERT(SK_ARRAY_COUNT(kPixelConfigs) == kMaskFormatCount, array_size_mismatch);
170
171 return kPixelConfigs[format];
172}
173
174void GrBatchFontCache::HandleEviction(GrBatchAtlas::AtlasID id, void* ptr) {
175 GrBatchFontCache* fontCache = reinterpret_cast<GrBatchFontCache*>(ptr);
176
177 SkTDynamicHash<GrBatchTextStrike, GrFontDescKey>::Iter iter(&fontCache->fCache);
178 for (; !iter.done(); ++iter) {
179 GrBatchTextStrike* strike = &*iter;
180 strike->removeID(id);
181
182 // clear out any empty strikes. We will preserve the strike whose call to addToAtlas
183 // triggered the eviction
184 if (strike != fontCache->fPreserveStrike && 0 == strike->fAtlasedGlyphs) {
185 fontCache->fCache.remove(*(strike->fFontScalerKey));
186 SkDELETE(strike);
187 }
188 }
189}
190
191void GrBatchFontCache::dump() const {
192 static int gDumpCount = 0;
193 for (int i = 0; i < kMaskFormatCount; ++i) {
194 if (fAtlases[i]) {
195 GrTexture* texture = fAtlases[i]->getTexture();
196 if (texture) {
197 SkString filename;
198#ifdef SK_BUILD_FOR_ANDROID
199 filename.printf("/sdcard/fontcache_%d%d.png", gDumpCount, i);
200#else
201 filename.printf("fontcache_%d%d.png", gDumpCount, i);
202#endif
203 texture->surfacePriv().savePixels(filename.c_str());
204 }
205 }
206 }
207 ++gDumpCount;
208}
209
210///////////////////////////////////////////////////////////////////////////////
211
212/*
213 The text strike is specific to a given font/style/matrix setup, which is
214 represented by the GrHostFontScaler object we are given in getGlyph().
215
216 We map a 32bit glyphID to a GrGlyph record, which in turn points to a
217 atlas and a position within that texture.
218 */
219
220GrBatchTextStrike::GrBatchTextStrike(GrBatchFontCache* cache, const GrFontDescKey* key)
221 : fFontScalerKey(SkRef(key))
222 , fPool(9/*start allocations at 512 bytes*/)
223 , fAtlasedGlyphs(0) {
224
225 fBatchFontCache = cache; // no need to ref, it won't go away before we do
226}
227
228GrBatchTextStrike::~GrBatchTextStrike() {
229 SkTDynamicHash<GrGlyph, GrGlyph::PackedID>::Iter iter(&fCache);
230 while (!iter.done()) {
231 (*iter).free();
232 ++iter;
233 }
234}
235
236GrGlyph* GrBatchTextStrike::generateGlyph(GrGlyph::PackedID packed,
237 GrFontScaler* scaler) {
238 SkIRect bounds;
239 if (GrGlyph::kDistance_MaskStyle == GrGlyph::UnpackMaskStyle(packed)) {
240 if (!scaler->getPackedGlyphDFBounds(packed, &bounds)) {
241 return NULL;
242 }
243 } else {
244 if (!scaler->getPackedGlyphBounds(packed, &bounds)) {
245 return NULL;
246 }
247 }
248 GrMaskFormat format = scaler->getPackedGlyphMaskFormat(packed);
249
250 GrGlyph* glyph = (GrGlyph*)fPool.alloc(sizeof(GrGlyph), SK_MALLOC_THROW);
251 glyph->init(packed, bounds, format);
252 fCache.add(glyph);
253 return glyph;
254}
255
256void GrBatchTextStrike::removeID(GrBatchAtlas::AtlasID id) {
257 SkTDynamicHash<GrGlyph, GrGlyph::PackedID>::Iter iter(&fCache);
258 while (!iter.done()) {
259 if (id == (*iter).fID) {
260 (*iter).fID = GrBatchAtlas::kInvalidAtlasID;
261 fAtlasedGlyphs--;
262 SkASSERT(fAtlasedGlyphs >= 0);
263 }
264 ++iter;
265 }
266}
267
268bool GrBatchTextStrike::glyphTooLargeForAtlas(GrGlyph* glyph) {
269 int width = glyph->fBounds.width();
270 int height = glyph->fBounds.height();
271 bool useDistanceField =
272 (GrGlyph::kDistance_MaskStyle == GrGlyph::UnpackMaskStyle(glyph->fPackedID));
273 int pad = useDistanceField ? 2 * SK_DistanceFieldPad : 0;
274 int plotWidth = (kA8_GrMaskFormat == glyph->fMaskFormat) ? GR_FONT_ATLAS_A8_PLOT_WIDTH
275 : GR_FONT_ATLAS_PLOT_WIDTH;
276 if (width + pad > plotWidth) {
277 return true;
278 }
279 if (height + pad > GR_FONT_ATLAS_PLOT_HEIGHT) {
280 return true;
281 }
282
283 return false;
284}
285
286bool GrBatchTextStrike::addGlyphToAtlas(GrBatchTarget* batchTarget, GrGlyph* glyph,
287 GrFontScaler* scaler) {
288 SkASSERT(glyph);
289 SkASSERT(scaler);
290 SkASSERT(fCache.find(glyph->fPackedID));
291 SkASSERT(NULL == glyph->fPlot);
292
293 SkAutoUnref ar(SkSafeRef(scaler));
294
295 int bytesPerPixel = GrMaskFormatBytesPerPixel(glyph->fMaskFormat);
296
297 size_t size = glyph->fBounds.area() * bytesPerPixel;
298 GrAutoMalloc<1024> storage(size);
299
300 if (GrGlyph::kDistance_MaskStyle == GrGlyph::UnpackMaskStyle(glyph->fPackedID)) {
301 if (!scaler->getPackedGlyphDFImage(glyph->fPackedID, glyph->width(),
302 glyph->height(),
303 storage.get())) {
304 return false;
305 }
306 } else {
307 if (!scaler->getPackedGlyphImage(glyph->fPackedID, glyph->width(),
308 glyph->height(),
309 glyph->width() * bytesPerPixel,
310 storage.get())) {
311 return false;
312 }
313 }
314
315 bool success = fBatchFontCache->addToAtlas(this, &glyph->fID, batchTarget, glyph->fMaskFormat,
316 glyph->width(), glyph->height(),
317 storage.get(), &glyph->fAtlasLocation);
318 if (success) {
319 fAtlasedGlyphs++;
320 }
321 return success;
322}