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 "GrContext.h"
10#include "GrGpu.h"
11#include "GrRectanizer.h"
12#include "GrResourceProvider.h"
13#include "GrSurfacePriv.h"
14#include "SkString.h"
15
16#include "SkDistanceFieldGen.h"
17
18///////////////////////////////////////////////////////////////////////////////
19
20bool GrBatchFontCache::initAtlas(GrMaskFormat format) {
21    int index = MaskFormatToAtlasIndex(format);
22    if (!fAtlases[index]) {
23        GrPixelConfig config = MaskFormatToPixelConfig(format);
24        int width = fAtlasConfigs[index].fWidth;
25        int height = fAtlasConfigs[index].fHeight;
26        int numPlotsX = fAtlasConfigs[index].numPlotsX();
27        int numPlotsY = fAtlasConfigs[index].numPlotsY();
28
29        fAtlases[index] =
30                fContext->resourceProvider()->createAtlas(config, width, height,
31                                                          numPlotsX, numPlotsY,
32                                                          &GrBatchFontCache::HandleEviction,
33                                                          (void*)this);
34        if (!fAtlases[index]) {
35            return false;
36        }
37    }
38    return true;
39}
40
41GrBatchFontCache::GrBatchFontCache(GrContext* context)
42    : fContext(context)
43    , fPreserveStrike(nullptr) {
44    for (int i = 0; i < kMaskFormatCount; ++i) {
45        fAtlases[i] = nullptr;
46    }
47
48    // setup default atlas configs
49    fAtlasConfigs[kA8_GrMaskFormat].fWidth = 2048;
50    fAtlasConfigs[kA8_GrMaskFormat].fHeight = 2048;
51    fAtlasConfigs[kA8_GrMaskFormat].fLog2Width = 11;
52    fAtlasConfigs[kA8_GrMaskFormat].fLog2Height = 11;
53    fAtlasConfigs[kA8_GrMaskFormat].fPlotWidth = 512;
54    fAtlasConfigs[kA8_GrMaskFormat].fPlotHeight = 256;
55
56    fAtlasConfigs[kA565_GrMaskFormat].fWidth = 1024;
57    fAtlasConfigs[kA565_GrMaskFormat].fHeight = 2048;
58    fAtlasConfigs[kA565_GrMaskFormat].fLog2Width = 10;
59    fAtlasConfigs[kA565_GrMaskFormat].fLog2Height = 11;
60    fAtlasConfigs[kA565_GrMaskFormat].fPlotWidth = 256;
61    fAtlasConfigs[kA565_GrMaskFormat].fPlotHeight = 256;
62
63    fAtlasConfigs[kARGB_GrMaskFormat].fWidth = 1024;
64    fAtlasConfigs[kARGB_GrMaskFormat].fHeight = 2048;
65    fAtlasConfigs[kARGB_GrMaskFormat].fLog2Width = 10;
66    fAtlasConfigs[kARGB_GrMaskFormat].fLog2Height = 11;
67    fAtlasConfigs[kARGB_GrMaskFormat].fPlotWidth = 256;
68    fAtlasConfigs[kARGB_GrMaskFormat].fPlotHeight = 256;
69}
70
71GrBatchFontCache::~GrBatchFontCache() {
72    SkTDynamicHash<GrBatchTextStrike, GrFontDescKey>::Iter iter(&fCache);
73    while (!iter.done()) {
74        (*iter).fIsAbandoned = true;
75        (*iter).unref();
76        ++iter;
77    }
78    for (int i = 0; i < kMaskFormatCount; ++i) {
79        delete fAtlases[i];
80    }
81}
82
83void GrBatchFontCache::freeAll() {
84    SkTDynamicHash<GrBatchTextStrike, GrFontDescKey>::Iter iter(&fCache);
85    while (!iter.done()) {
86        (*iter).fIsAbandoned = true;
87        (*iter).unref();
88        ++iter;
89    }
90    fCache.rewind();
91    for (int i = 0; i < kMaskFormatCount; ++i) {
92        delete fAtlases[i];
93        fAtlases[i] = nullptr;
94    }
95}
96
97void GrBatchFontCache::HandleEviction(GrBatchAtlas::AtlasID id, void* ptr) {
98    GrBatchFontCache* fontCache = reinterpret_cast<GrBatchFontCache*>(ptr);
99
100    SkTDynamicHash<GrBatchTextStrike, GrFontDescKey>::Iter iter(&fontCache->fCache);
101    for (; !iter.done(); ++iter) {
102        GrBatchTextStrike* strike = &*iter;
103        strike->removeID(id);
104
105        // clear out any empty strikes.  We will preserve the strike whose call to addToAtlas
106        // triggered the eviction
107        if (strike != fontCache->fPreserveStrike && 0 == strike->fAtlasedGlyphs) {
108            fontCache->fCache.remove(*(strike->fFontScalerKey));
109            strike->fIsAbandoned = true;
110            strike->unref();
111        }
112    }
113}
114
115void GrBatchFontCache::dump() const {
116    static int gDumpCount = 0;
117    for (int i = 0; i < kMaskFormatCount; ++i) {
118        if (fAtlases[i]) {
119            GrTexture* texture = fAtlases[i]->getTexture();
120            if (texture) {
121                SkString filename;
122#ifdef SK_BUILD_FOR_ANDROID
123                filename.printf("/sdcard/fontcache_%d%d.png", gDumpCount, i);
124#else
125                filename.printf("fontcache_%d%d.png", gDumpCount, i);
126#endif
127                texture->surfacePriv().savePixels(filename.c_str());
128            }
129        }
130    }
131    ++gDumpCount;
132}
133
134void GrBatchFontCache::setAtlasSizes_ForTesting(const GrBatchAtlasConfig configs[3]) {
135    // delete any old atlases, this should be safe to do as long as we are not in the middle of a
136    // flush
137    for (int i = 0; i < kMaskFormatCount; i++) {
138        if (fAtlases[i]) {
139            delete fAtlases[i];
140            fAtlases[i] = nullptr;
141        }
142    }
143    memcpy(fAtlasConfigs, configs, sizeof(fAtlasConfigs));
144}
145
146///////////////////////////////////////////////////////////////////////////////
147
148/*
149    The text strike is specific to a given font/style/matrix setup, which is
150    represented by the GrHostFontScaler object we are given in getGlyph().
151
152    We map a 32bit glyphID to a GrGlyph record, which in turn points to a
153    atlas and a position within that texture.
154 */
155
156GrBatchTextStrike::GrBatchTextStrike(GrBatchFontCache* cache, const GrFontDescKey* key)
157    : fFontScalerKey(SkRef(key))
158    , fPool(9/*start allocations at 512 bytes*/)
159    , fAtlasedGlyphs(0)
160    , fIsAbandoned(false) {
161
162    fBatchFontCache = cache;     // no need to ref, it won't go away before we do
163}
164
165GrBatchTextStrike::~GrBatchTextStrike() {
166    SkTDynamicHash<GrGlyph, GrGlyph::PackedID>::Iter iter(&fCache);
167    while (!iter.done()) {
168        (*iter).free();
169        ++iter;
170    }
171}
172
173GrGlyph* GrBatchTextStrike::generateGlyph(const SkGlyph& skGlyph, GrGlyph::PackedID packed,
174                                          GrFontScaler* scaler) {
175    SkIRect bounds;
176    if (GrGlyph::kDistance_MaskStyle == GrGlyph::UnpackMaskStyle(packed)) {
177        if (!scaler->getPackedGlyphDFBounds(skGlyph, &bounds)) {
178            return nullptr;
179        }
180    } else {
181        if (!scaler->getPackedGlyphBounds(skGlyph, &bounds)) {
182            return nullptr;
183        }
184    }
185    GrMaskFormat format = scaler->getPackedGlyphMaskFormat(skGlyph);
186
187    GrGlyph* glyph = (GrGlyph*)fPool.alloc(sizeof(GrGlyph));
188    glyph->init(packed, bounds, format);
189    fCache.add(glyph);
190    return glyph;
191}
192
193void GrBatchTextStrike::removeID(GrBatchAtlas::AtlasID id) {
194    SkTDynamicHash<GrGlyph, GrGlyph::PackedID>::Iter iter(&fCache);
195    while (!iter.done()) {
196        if (id == (*iter).fID) {
197            (*iter).fID = GrBatchAtlas::kInvalidAtlasID;
198            fAtlasedGlyphs--;
199            SkASSERT(fAtlasedGlyphs >= 0);
200        }
201        ++iter;
202    }
203}
204
205bool GrBatchTextStrike::addGlyphToAtlas(GrDrawBatch::Target* target,
206                                        GrGlyph* glyph,
207                                        GrFontScaler* scaler,
208                                        GrMaskFormat expectedMaskFormat) {
209    SkASSERT(glyph);
210    SkASSERT(scaler);
211    SkASSERT(fCache.find(glyph->fPackedID));
212
213    SkAutoUnref ar(SkSafeRef(scaler));
214
215    int bytesPerPixel = GrMaskFormatBytesPerPixel(expectedMaskFormat);
216
217    size_t size = glyph->fBounds.area() * bytesPerPixel;
218    SkAutoSMalloc<1024> storage(size);
219
220    const SkGlyph& skGlyph = scaler->grToSkGlyph(glyph->fPackedID);
221    if (GrGlyph::kDistance_MaskStyle == GrGlyph::UnpackMaskStyle(glyph->fPackedID)) {
222        if (!scaler->getPackedGlyphDFImage(skGlyph, glyph->width(), glyph->height(),
223                                           storage.get())) {
224            return false;
225        }
226    } else {
227        if (!scaler->getPackedGlyphImage(skGlyph, glyph->width(), glyph->height(),
228                                         glyph->width() * bytesPerPixel, expectedMaskFormat,
229                                         storage.get())) {
230            return false;
231        }
232    }
233
234    bool success = fBatchFontCache->addToAtlas(this, &glyph->fID, target, expectedMaskFormat,
235                                               glyph->width(), glyph->height(),
236                                               storage.get(), &glyph->fAtlasLocation);
237    if (success) {
238        SkASSERT(GrBatchAtlas::kInvalidAtlasID != glyph->fID);
239        fAtlasedGlyphs++;
240    }
241    return success;
242}
243