1 /*
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
make_atlas(GrContext * context,GrPixelConfig config,int textureWidth,int textureHeight,int numPlotsX,int numPlotsY)19 static 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->textureProvider()->refScratchTexture(
31 desc, GrTextureProvider::kApprox_ScratchTexMatch, true);
32 if (!texture) {
33 return NULL;
34 }
35 return SkNEW_ARGS(GrBatchAtlas, (texture, numPlotsX, numPlotsY));
36 }
37
initAtlas(GrMaskFormat format)38 bool GrBatchFontCache::initAtlas(GrMaskFormat format) {
39 int index = MaskFormatToAtlasIndex(format);
40 if (!fAtlases[index]) {
41 GrPixelConfig config = this->getPixelConfig(format);
42 if (kA8_GrMaskFormat == format) {
43 fAtlases[index] = make_atlas(fContext, config,
44 GR_FONT_ATLAS_A8_TEXTURE_WIDTH,
45 GR_FONT_ATLAS_TEXTURE_HEIGHT,
46 GR_FONT_ATLAS_A8_NUM_PLOTS_X,
47 GR_FONT_ATLAS_NUM_PLOTS_Y);
48 } else {
49 fAtlases[index] = make_atlas(fContext, config,
50 GR_FONT_ATLAS_TEXTURE_WIDTH,
51 GR_FONT_ATLAS_TEXTURE_HEIGHT,
52 GR_FONT_ATLAS_NUM_PLOTS_X,
53 GR_FONT_ATLAS_NUM_PLOTS_Y);
54 }
55
56 // Atlas creation can fail
57 if (fAtlases[index]) {
58 fAtlases[index]->registerEvictionCallback(&GrBatchFontCache::HandleEviction,
59 (void*)this);
60 } else {
61 return false;
62 }
63 }
64 return true;
65 }
66
GrBatchFontCache(GrContext * context)67 GrBatchFontCache::GrBatchFontCache(GrContext* context)
68 : fContext(context)
69 , fPreserveStrike(NULL) {
70 for (int i = 0; i < kMaskFormatCount; ++i) {
71 fAtlases[i] = NULL;
72 }
73 }
74
~GrBatchFontCache()75 GrBatchFontCache::~GrBatchFontCache() {
76 SkTDynamicHash<GrBatchTextStrike, GrFontDescKey>::Iter iter(&fCache);
77 while (!iter.done()) {
78 (*iter).unref();
79 ++iter;
80 }
81 for (int i = 0; i < kMaskFormatCount; ++i) {
82 SkDELETE(fAtlases[i]);
83 }
84 }
85
freeAll()86 void GrBatchFontCache::freeAll() {
87 SkTDynamicHash<GrBatchTextStrike, GrFontDescKey>::Iter iter(&fCache);
88 while (!iter.done()) {
89 (*iter).unref();
90 ++iter;
91 }
92 fCache.rewind();
93 for (int i = 0; i < kMaskFormatCount; ++i) {
94 SkDELETE(fAtlases[i]);
95 fAtlases[i] = NULL;
96 }
97 }
98
getPixelConfig(GrMaskFormat format) const99 GrPixelConfig GrBatchFontCache::getPixelConfig(GrMaskFormat format) const {
100 static const GrPixelConfig kPixelConfigs[] = {
101 kAlpha_8_GrPixelConfig,
102 kRGB_565_GrPixelConfig,
103 kSkia8888_GrPixelConfig
104 };
105 SK_COMPILE_ASSERT(SK_ARRAY_COUNT(kPixelConfigs) == kMaskFormatCount, array_size_mismatch);
106
107 return kPixelConfigs[format];
108 }
109
HandleEviction(GrBatchAtlas::AtlasID id,void * ptr)110 void GrBatchFontCache::HandleEviction(GrBatchAtlas::AtlasID id, void* ptr) {
111 GrBatchFontCache* fontCache = reinterpret_cast<GrBatchFontCache*>(ptr);
112
113 SkTDynamicHash<GrBatchTextStrike, GrFontDescKey>::Iter iter(&fontCache->fCache);
114 for (; !iter.done(); ++iter) {
115 GrBatchTextStrike* strike = &*iter;
116 strike->removeID(id);
117
118 // clear out any empty strikes. We will preserve the strike whose call to addToAtlas
119 // triggered the eviction
120 if (strike != fontCache->fPreserveStrike && 0 == strike->fAtlasedGlyphs) {
121 fontCache->fCache.remove(*(strike->fFontScalerKey));
122 strike->fIsAbandoned = true;
123 strike->unref();
124 }
125 }
126 }
127
dump() const128 void GrBatchFontCache::dump() const {
129 static int gDumpCount = 0;
130 for (int i = 0; i < kMaskFormatCount; ++i) {
131 if (fAtlases[i]) {
132 GrTexture* texture = fAtlases[i]->getTexture();
133 if (texture) {
134 SkString filename;
135 #ifdef SK_BUILD_FOR_ANDROID
136 filename.printf("/sdcard/fontcache_%d%d.png", gDumpCount, i);
137 #else
138 filename.printf("fontcache_%d%d.png", gDumpCount, i);
139 #endif
140 texture->surfacePriv().savePixels(filename.c_str());
141 }
142 }
143 }
144 ++gDumpCount;
145 }
146
147 ///////////////////////////////////////////////////////////////////////////////
148
149 /*
150 The text strike is specific to a given font/style/matrix setup, which is
151 represented by the GrHostFontScaler object we are given in getGlyph().
152
153 We map a 32bit glyphID to a GrGlyph record, which in turn points to a
154 atlas and a position within that texture.
155 */
156
GrBatchTextStrike(GrBatchFontCache * cache,const GrFontDescKey * key)157 GrBatchTextStrike::GrBatchTextStrike(GrBatchFontCache* cache, const GrFontDescKey* key)
158 : fFontScalerKey(SkRef(key))
159 , fPool(9/*start allocations at 512 bytes*/)
160 , fAtlasedGlyphs(0)
161 , fIsAbandoned(false) {
162
163 fBatchFontCache = cache; // no need to ref, it won't go away before we do
164 }
165
~GrBatchTextStrike()166 GrBatchTextStrike::~GrBatchTextStrike() {
167 SkTDynamicHash<GrGlyph, GrGlyph::PackedID>::Iter iter(&fCache);
168 while (!iter.done()) {
169 (*iter).free();
170 ++iter;
171 }
172 }
173
generateGlyph(GrGlyph::PackedID packed,GrFontScaler * scaler)174 GrGlyph* GrBatchTextStrike::generateGlyph(GrGlyph::PackedID packed,
175 GrFontScaler* scaler) {
176 SkIRect bounds;
177 if (GrGlyph::kDistance_MaskStyle == GrGlyph::UnpackMaskStyle(packed)) {
178 if (!scaler->getPackedGlyphDFBounds(packed, &bounds)) {
179 return NULL;
180 }
181 } else {
182 if (!scaler->getPackedGlyphBounds(packed, &bounds)) {
183 return NULL;
184 }
185 }
186 GrMaskFormat format = scaler->getPackedGlyphMaskFormat(packed);
187
188 GrGlyph* glyph = (GrGlyph*)fPool.alloc(sizeof(GrGlyph), SK_MALLOC_THROW);
189 glyph->init(packed, bounds, format);
190 fCache.add(glyph);
191 return glyph;
192 }
193
removeID(GrBatchAtlas::AtlasID id)194 void GrBatchTextStrike::removeID(GrBatchAtlas::AtlasID id) {
195 SkTDynamicHash<GrGlyph, GrGlyph::PackedID>::Iter iter(&fCache);
196 while (!iter.done()) {
197 if (id == (*iter).fID) {
198 (*iter).fID = GrBatchAtlas::kInvalidAtlasID;
199 fAtlasedGlyphs--;
200 SkASSERT(fAtlasedGlyphs >= 0);
201 }
202 ++iter;
203 }
204 }
205
addGlyphToAtlas(GrBatchTarget * batchTarget,GrGlyph * glyph,GrFontScaler * scaler)206 bool GrBatchTextStrike::addGlyphToAtlas(GrBatchTarget* batchTarget, GrGlyph* glyph,
207 GrFontScaler* scaler) {
208 SkASSERT(glyph);
209 SkASSERT(scaler);
210 SkASSERT(fCache.find(glyph->fPackedID));
211 SkASSERT(NULL == glyph->fPlot);
212
213 SkAutoUnref ar(SkSafeRef(scaler));
214
215 int bytesPerPixel = GrMaskFormatBytesPerPixel(glyph->fMaskFormat);
216
217 size_t size = glyph->fBounds.area() * bytesPerPixel;
218 GrAutoMalloc<1024> storage(size);
219
220 if (GrGlyph::kDistance_MaskStyle == GrGlyph::UnpackMaskStyle(glyph->fPackedID)) {
221 if (!scaler->getPackedGlyphDFImage(glyph->fPackedID, glyph->width(),
222 glyph->height(),
223 storage.get())) {
224 return false;
225 }
226 } else {
227 if (!scaler->getPackedGlyphImage(glyph->fPackedID, glyph->width(),
228 glyph->height(),
229 glyph->width() * bytesPerPixel,
230 storage.get())) {
231 return false;
232 }
233 }
234
235 bool success = fBatchFontCache->addToAtlas(this, &glyph->fID, batchTarget, glyph->fMaskFormat,
236 glyph->width(), glyph->height(),
237 storage.get(), &glyph->fAtlasLocation);
238 if (success) {
239 fAtlasedGlyphs++;
240 }
241 return success;
242 }
243